diff --git a/.drone.yml b/.drone.yml index 1da24e760..6e99b36d5 100644 --- a/.drone.yml +++ b/.drone.yml @@ -81,7 +81,7 @@ pipeline: TESTS: qt-5.9 AppImage-5.9: - image: nextcloudci/client-appimage-ci:client-appimage-ci-6 + image: nextcloudci/client-appimage-ci:client-appimage-ci-8 commands: - /bin/bash -c "./admin/linux/build-appimage.sh" when: diff --git a/CMakeLists.txt b/CMakeLists.txt index a4bb50cb9..6ddd000f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,5 @@ - -cmake_minimum_required(VERSION 2.6) -cmake_policy(VERSION 2.8.0) -if(POLICY CMP0020) - cmake_policy(SET CMP0020 NEW) -endif() +cmake_minimum_required(VERSION 3.1) +set(CMAKE_CXX_STANDARD 14) project(client) @@ -61,14 +57,6 @@ if(NOT WITH_CRASHREPORTER) message(STATUS "Build of crashreporter disabled.") endif() -##### -## handle DBUS for Fdo notifications -if( UNIX AND NOT APPLE ) - add_definitions( -DUSE_FDO_NOTIFICATIONS) - set(WITH_DBUS ON) -endif() -#### - include(GNUInstallDirs) include(DefineInstallationPaths) include(GenerateExportHeader) @@ -77,6 +65,12 @@ include(GetGitRevisionDescription) 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 +) + # 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. @@ -190,15 +184,6 @@ if(BUILD_CLIENT) find_package(Sphinx) find_package(PdfLatex) - find_package(SQLite3 3.8.0 REQUIRED) - # On some OS, we want to use our own, not the system sqlite - if (USE_OUR_OWN_SQLITE3) - include_directories(BEFORE ${SQLITE3_INCLUDE_DIR}) - if (WIN32) - add_definitions(-DSQLITE_API=__declspec\(dllimport\)) - endif() - endif() - find_package(ZLIB REQUIRED) find_package(GLib2) find_package(Gio) @@ -221,20 +206,6 @@ add_definitions( -D_WIN32_WINNT=0x0600) add_definitions( -DWINVER=0x0600) endif( WIN32 ) -include(QtVersionAbstraction) -setup_qt() -if (${Qt5Core_VERSION_MAJOR} EQUAL "5") - if (${Qt5Core_VERSION_MINOR} EQUAL "6" OR ${Qt5Core_VERSION_MINOR} GREATER 6) - else() - message(STATUS "If possible compile me with Qt 5.6 or higher.") - endif() - if (${Qt5Core_VERSION_MINOR} EQUAL "9" OR ${Qt5Core_VERSION_MINOR} GREATER 9) - else() - message(STATUS "For HTTP2 use Qt 5.9.2 or higher.") - endif() -endif() -message("Qt ${Qt5Core_VERSION} at ${Qt5Core_INCLUDE_DIRS}") - if (APPLE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") endif() diff --git a/ChangeLog b/ChangeLog index f470b78bf..20e5b546e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,7 +2,7 @@ ChangeLog ========= version 2.4.0 (2017-12-XX) -* If you're using 2.4.0 alpha1, please upgrade as the alpha1 had an issue with hidden files! +* If you're using 2.4.0 alpha1, please upgrade as previous alphas/rcs had an issue with hidden files and renames! * OAuth2 authentication support by opening external browser (#5668) * Shibboleth: Change to use OAuth2 if supported (#6198) * Sharing: Add support for multiple public link shares (#5655) @@ -66,7 +66,7 @@ version 2.4.0 (2017-12-XX) * Sync: Upload conflict files if OWNCLOUD_UPLOAD_CONFLICT_FILES environment variable is set (#6038) * Sync: Blacklist: Don't let errors become warnings (#5516) * Sync: Check etag again after active sync (#4116) -* Sync: Rename handling fixes: duplicate file ids (#6096) +* Sync: Rename handling fixes: duplicate file ids (#6096, #6212) * Sync: Rename handling fixes: File size must be equal * Sync: Rename handling: Fix duplicate files on abort/resume sync (#5949) * Sync: Add capability for invalid filename regexes (#6092) @@ -80,6 +80,7 @@ version 2.4.0 (2017-12-XX) * Crash fixes * Test improvements * Small UI layout fixes +* Performance improvements * Maintenance Mode: Detect maintenance mode (#4485) * Maintenance Mode: Add a 1 to 5 min reconnection delay (#5872) * HTTP: Send a unique X-Request-ID with each request (#5853) @@ -95,6 +96,7 @@ version 2.4.0 (2017-12-XX) * Compilation: Remove Qt 4 code (#6025, #5702, #5505) * Harmonize source code style with clang-format (#5732) * Switch over to Qt 5 function pointer signal/slot syntax (#6041) +* Compile with stack-smashing protection * Updater: Rudimentary support for beta channel (#6048) version 2.3.4 (2017-11-02) @@ -110,6 +112,7 @@ 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) * Switch Linux build also to Qt 5.6.2 (#5470) * Stopped maintaining Qt 4 buildability diff --git a/admin/linux/Dockerfile b/admin/linux/Dockerfile new file mode 100644 index 000000000..8f2326fb2 --- /dev/null +++ b/admin/linux/Dockerfile @@ -0,0 +1,22 @@ +# This DockerFile is used to create the image used for Jenkins, the CI system (see Jenkinsfile) +# It is not meant to be used to create the production packages. + +# Distro with Qt 5.6 +FROM ubuntu:yakkety + +RUN apt-get update -q && DEBIAN_FRONTEND=noninteractive apt-get install -q -y --no-install-recommends \ + locales \ + build-essential \ + clang \ + ninja-build \ + cmake \ + extra-cmake-modules \ + libsqlite3-dev \ + libssl-dev \ + libcmocka-dev \ + qt5-default \ + qttools5-dev-tools \ + libqt5webkit5-dev \ + qt5keychain-dev \ + kio-dev \ + && apt-get clean diff --git a/admin/win/Toolchain-mingw32-openSUSE.cmake b/admin/win/Toolchain-mingw32-openSUSE.cmake index 3877ca1d4..ae1c5745f 100644 --- a/admin/win/Toolchain-mingw32-openSUSE.cmake +++ b/admin/win/Toolchain-mingw32-openSUSE.cmake @@ -27,8 +27,8 @@ SET(QT_MKSPECS_DIR ${CMAKE_FIND_ROOT_PATH}/share/qt5/mkspecs) SET(QT_QT_INCLUDE_DIR ${CMAKE_FIND_ROOT_PATH}/include) # qt tools -SET(QT_QMAKE_EXECUTABLE ${MINGW_PREFIX}-qmake ) -SET(QT_MOC_EXECUTABLE ${MINGW_PREFIX}-moc) -SET(QT_RCC_EXECUTABLE ${MINGW_PREFIX}-rcc) -SET(QT_UIC_EXECUTABLE ${MINGW_PREFIX}-uic) -SET(QT_LRELEASE_EXECUTABLE ${MINGW_PREFIX}-lrelease) +SET(QT_QMAKE_EXECUTABLE ${MINGW_PREFIX}-qmake-qt5) +SET(QT_MOC_EXECUTABLE ${MINGW_PREFIX}-moc-qt5) +SET(QT_RCC_EXECUTABLE ${MINGW_PREFIX}-rcc-qt5) +SET(Qt5Widgets_UIC_EXECUTABLE ${MINGW_PREFIX}-uic-qt5) +SET(QT_LRELEASE_EXECUTABLE ${MINGW_PREFIX}-lrelease-qt5) diff --git a/admin/win/docker/Dockerfile b/admin/win/docker/Dockerfile index 1629b9760..1ff621175 100644 --- a/admin/win/docker/Dockerfile +++ b/admin/win/docker/Dockerfile @@ -1,22 +1,20 @@ FROM opensuse:42.1 -MAINTAINER Daniel Molkentin - ENV TERM ansi ENV HOME /root -ENV REFRESHED_AT 20160421 +ENV REFRESHED_AT 20170113 RUN zypper --non-interactive --gpg-auto-import-keys refresh RUN zypper --non-interactive --gpg-auto-import-keys ar http://download.opensuse.org/repositories/windows:/mingw/openSUSE_Leap_42.1/windows:mingw.repo RUN zypper --non-interactive --gpg-auto-import-keys ar http://download.opensuse.org/repositories/isv:ownCloud:toolchains:mingw:win32:2.3/openSUSE_Leap_42.1/isv:ownCloud:toolchains:mingw:win32:2.3.repo RUN zypper --non-interactive --gpg-auto-import-keys install cmake make mingw32-cross-binutils mingw32-cross-cpp mingw32-cross-gcc \ mingw32-cross-gcc-c++ mingw32-cross-pkg-config mingw32-filesystem \ - mingw32-headers mingw32-runtime site-config mingw32-libwebp \ + mingw32-headers mingw32-runtime site-config mingw32-libwebp mingw32-libssp0 \ mingw32-cross-libqt5-qmake mingw32-cross-libqt5-qttools mingw32-libqt5* \ mingw32-qt5keychain* mingw32-angleproject* \ mingw32-cross-nsis mingw32-libopenssl* \ - mingw32-sqlite* kdewin-png2ico \ + mingw32-sqlite* png2ico \ osslsigncode wget # RPM depends on curl for installs from HTTP diff --git a/appveyor.ini b/appveyor.ini new file mode 100644 index 000000000..658eeac24 --- /dev/null +++ b/appveyor.ini @@ -0,0 +1,45 @@ +[General] +Branch = master +ShallowClone = True +Command=craft + +# Variables defined here override the default value +# The variable names are casesensitive +[Variables] +#Values need to be overwritten to create a chache +UseCache = True +CreateCache = False + +# Settings applicable for all Crafts matrices +# Settings are Category/key=value +# Category is case sensitive + +[GeneralSettings] +General/EMERGE_PKGDSTDIR=${Variables:APPVEYOR_BUILD_FOLDER}/binaries +Paths/python = C:\Python36 +Paths/python27 = C:\Python27 +Paths/downloaddir = ${Variables:Root}\downloads +ShortPath/Enabled = False +ShortPath/EnableJunctions = True +ShortPath/JunctionDir = C:\CM-SP\ +Packager/CacheDir = ${Variables:Root}\cache +Packager/UseCache = ${Variables:UseCache} +Packager/CreateCache = ${Variables:CreateCache} +; Packager/RepositoryUrl = https://files.kde.org/craft/ +Packager/PackageType = PortablePackager +Packager/RepositoryUrl = http://ftp.acc.umu.se/mirror/kde.org/files/craft/master/ +Compile/BuildType = RelWithDebInfo +ContinuousIntegration/Enabled = True + +[BlueprintSettings] +# don't try to pip install on the ci +python-modules.ignored = True + +libs/qt5.version = 5.9.3 +craft/craft-core.version = master + +[windows-msvc2017_64-cl] +General/ABI = windows-msvc2017_64-cl + +[windows-msvc2017_32-cl] +General/ABI = windows-msvc2017_32-cl diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 000000000..289f58958 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,50 @@ +version: '{build}-{branch}' + +branches: + only: + - master + +clone_depth: 50 + + +init: +- ps: | + function craft($target) { + & C:\Python36\python.exe "C:\CraftMaster\CraftMaster\CraftMaster.py" --config "$env:APPVEYOR_BUILD_FOLDER\appveyor.ini" --variables "APPVEYOR_BUILD_FOLDER=$env:APPVEYOR_BUILD_FOLDER" --target $target -c $args + if($LASTEXITCODE -ne 0) {exit $LASTEXITCODE} + } + +install: +- ps: | + #use cmd to silence powershell behaviour for stderr + & cmd /C "git clone -q --depth=1 git://anongit.kde.org/craftmaster.git C:\CraftMaster\CraftMaster 2>&1" + + craft $env:TARGET -i craft + craft $env:TARGET --install-deps owncloud-client + +build_script: +- ps: | + craft $env:TARGET --no-cache --src-dir $env:APPVEYOR_BUILD_FOLDER owncloud-client + +after_build: +- ps: | + craft $env:TARGET --src-dir $env:APPVEYOR_BUILD_FOLDER --package owncloud-client + + +on_finish: +- ps: | + Get-ChildItem $env:USERPROFILE\.craft\* | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name } + +test_script: +- ps: | + craft $env:TARGET --src-dir $env:APPVEYOR_BUILD_FOLDER --test owncloud-client + +environment: + matrix: + - TARGET: windows-msvc2017_32-cl + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + - TARGET: windows-msvc2017_64-cl + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + +artifacts: + - path: binaries/* diff --git a/cmake/modules/ECMAddAppIcon.cmake b/cmake/modules/ECMAddAppIcon.cmake new file mode 100644 index 000000000..ee2296ead --- /dev/null +++ b/cmake/modules/ECMAddAppIcon.cmake @@ -0,0 +1,332 @@ +#.rst: +# ECMAddAppIcon +# ------------- +# +# Add icons to executable files and packages. +# +# :: +# +# ecm_add_app_icon( +# ICONS [ [...]] +# [SIDEBAR_ICONS [ [...]] # Since 5.4x +# [OUTFILE_BASE ]) # Since 5.4x +# ) +# +# The given icons, whose names must match the pattern:: +# +# -.png +# +# will be added to the executable target whose sources are specified by +# ```` on platforms that support it (Windows and Mac OS X). +# Other icon files are ignored but on Mac SVG files can be supported and +# it is thus possible to mix those with png files in a single macro call. +# +# ```` is a numeric pixel size (typically 16, 32, 48, 64, 128 or 256). +# ```` can be any other text. See the platform notes below for any +# recommendations about icon sizes. +# +# ``SIDEBAR_ICONS`` can be used to add Mac OS X sidebar +# icons to the generated iconset. They are used when a folder monitored by the +# application is dragged into Finder's sidebar. Since 5.4x. +# +# ``OUTFILE_BASE`` will be used as the basename for the icon file. If +# you specify it, the icon file will be called ``.icns`` on Mac OS X +# and ``.ico`` on Windows. If you don't specify it, it defaults +# to ``.``. Since 5.4x. +# +# +# Windows notes +# * Icons are compiled into the executable using a resource file. +# * Icons may not show up in Windows Explorer if the executable +# target does not have the ``WIN32_EXECUTABLE`` property set. +# * The tool png2ico is required. See :find-module:`FindPng2Ico`. +# * Supported sizes: 16, 32, 48, 64, 128. +# +# Mac OS X notes +# * The executable target must have the ``MACOSX_BUNDLE`` property set. +# * Icons are added to the bundle. +# * If the ksvg2icns tool from KIconThemes is available, .svg and .svgz +# files are accepted; the first that is converted successfully to .icns +# will provide the application icon. SVG files are ignored otherwise. +# * The tool iconutil (provided by Apple) is required for bitmap icons. +# * Supported sizes: 16, 32, 64, 128, 256 (and 512, 1024 after OS X 10.9). +# * At least a 128x128px (or an SVG) icon is required. +# * Larger sizes are automatically used to substitute for smaller sizes on +# "Retina" (high-resolution) displays. For example, a 32px icon, if +# provided, will be used as a 32px icon on standard-resolution displays, +# and as a 16px-equivalent icon (with an "@2x" tag) on high-resolution +# displays. That is why you should provide 64px and 1024px icons although +# they are not supported anymore directly. Instead they will be used as +# 32px@2x and 512px@2x. ksvg2icns handles this internally. +# * This function sets the ``MACOSX_BUNDLE_ICON_FILE`` variable to the name +# of the generated icns file, so that it will be used as the +# ``MACOSX_BUNDLE_ICON_FILE`` target property when you call +# ``add_executable``. +# * Sidebar icons should typically provided in 16, 32, 64, 128 and 256px. +# +# Since 1.7.0. + + +#============================================================================= +# Copyright 2014 Alex Merry +# Copyright 2014 Ralf Habacker +# Copyright 2006-2009 Alexander Neundorf, +# Copyright 2006, 2007, Laurent Montel, +# Copyright 2007 Matthias Kretz +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +include(CMakeParseArguments) + +function(ecm_add_app_icon appsources) + set(options) + set(oneValueArgs OUTFILE_BASE) + set(multiValueArgs ICONS SIDEBAR_ICONS) + cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT ARG_ICONS) + message(FATAL_ERROR "No ICONS argument given to ecm_add_app_icon") + endif() + if(ARG_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Unexpected arguments to ecm_add_app_icon: ${ARG_UNPARSED_ARGUMENTS}") + endif() + + if(APPLE) + find_program(KSVG2ICNS NAMES ksvg2icns) + foreach(icon ${ARG_ICONS}) + get_filename_component(icon_full ${icon} ABSOLUTE) + get_filename_component(icon_type ${icon_full} EXT) + # do we have ksvg2icns in the path and did we receive an svg (or compressed svg) icon? + if(KSVG2ICNS AND (${icon_type} STREQUAL ".svg" OR ${icon_type} STREQUAL ".svgz")) + # convert the svg icon to an icon resource + execute_process(COMMAND ${KSVG2ICNS} "${icon_full}" + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} RESULT_VARIABLE KSVG2ICNS_ERROR) + if(${KSVG2ICNS_ERROR}) + message(AUTHOR_WARNING "ksvg2icns could not generate an OS X application icon from ${icon}") + else() + # install the icns file we just created + get_filename_component(icon_name ${icon_full} NAME_WE) + set(MACOSX_BUNDLE_ICON_FILE ${icon_name}.icns PARENT_SCOPE) + set(${appsources} "${${appsources}};${CMAKE_CURRENT_BINARY_DIR}/${icon_name}.icns" PARENT_SCOPE) + set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/${icon_name}.icns PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + # we're done now + return() + endif() + endif() + endforeach() + endif() + + + _ecm_add_app_icon_categorize_icons("${ARG_ICONS}" "icons" "16;32;48;64;128;256;512;1024") + if(ARG_SIDEBAR_ICONS) + _ecm_add_app_icon_categorize_icons("${ARG_SIDEBAR_ICONS}" "sidebar_icons" "16;18;32;36;64") + endif() + + set(mac_icons + # Icons: https://developer.apple.com/library/content/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Optimizing/Optimizing.html#//apple_ref/doc/uid/TP40012302-CH7-SW4 + ${icons_at_16px} + ${icons_at_32px} + ${icons_at_64px} + ${icons_at_128px} + ${icons_at_256px} + ${icons_at_512px} + ${icons_at_1024px} + + # Sidebar Icons: https://developer.apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/Finder.html#//apple_ref/doc/uid/TP40014214-CH15-SW15 + ${sidebar_icons_at_16px} + ${sidebar_icons_at_18px} + ${sidebar_icons_at_32px} + ${sidebar_icons_at_36px} + ${sidebar_icons_at_64px}) + if (NOT icons_at_128px) + message(AUTHOR_WARNING "No 128px icon provided; this will not work on Mac OS X") + endif() + + + set(windows_icons ${icons_at_16px} + ${icons_at_32px} + ${icons_at_48px} + ${icons_at_64px} + ${icons_at_128px}) + if (NOT windows_icons) + message(AUTHOR_WARNING "No icons suitable for use on Windows provided") + endif() + + if (ARG_OUTFILE_BASE) + set (_outfilebasename "${ARG_OUTFILE_BASE}") + else() + set (_outfilebasename "${appsources}") + endif() + set (_outfilename "${CMAKE_CURRENT_BINARY_DIR}/${_outfilebasename}") + + if (WIN32 AND windows_icons) + set(saved_CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}") + set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_FIND_MODULE_DIR}) + find_package(Png2Ico) + set(CMAKE_MODULE_PATH "${saved_CMAKE_MODULE_PATH}") + + if (Png2Ico_FOUND) + if (Png2Ico_HAS_RCFILE_ARGUMENT) + add_custom_command( + OUTPUT "${_outfilename}.rc" "${_outfilename}.ico" + COMMAND Png2Ico::Png2Ico + ARGS + --rcfile "${_outfilename}.rc" + "${_outfilename}.ico" + ${windows_icons} + DEPENDS ${windows_icons} + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + ) + else() + add_custom_command( + OUTPUT "${_outfilename}.ico" + COMMAND Png2Ico::Png2Ico + ARGS "${_outfilename}.ico" ${windows_icons} + DEPENDS ${windows_icons} + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + ) + # this bit's a little hacky to make the dependency stuff work + file(WRITE "${_outfilename}.rc.in" "IDI_ICON1 ICON DISCARDABLE \"${_outfilename}.ico\"\n") + add_custom_command( + OUTPUT "${_outfilename}.rc" + COMMAND ${CMAKE_COMMAND} + ARGS -E copy "${_outfilename}.rc.in" "${_outfilename}.rc" + DEPENDS "${_outfilename}.ico" + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + ) + endif() + set(${appsources} "${${appsources}};${_outfilename}.rc" PARENT_SCOPE) + else() + message(WARNING "Unable to find the png2ico utility - application will not have an application icon!") + endif() + elseif (APPLE AND mac_icons) + # first generate .iconset directory structure, then convert to .icns format using the Mac OS X "iconutil" utility, + # to create retina compatible icon, you need png source files in pixel resolution 16x16, 32x32, 64x64, 128x128, + # 256x256, 512x512, 1024x1024 + find_program(ICONUTIL_EXECUTABLE NAMES iconutil) + if (ICONUTIL_EXECUTABLE) + add_custom_command( + OUTPUT "${_outfilename}.iconset" + COMMAND ${CMAKE_COMMAND} + ARGS -E make_directory "${_outfilename}.iconset" + ) + set(iconset_icons) + macro(copy_icon filename sizename type) + add_custom_command( + OUTPUT "${_outfilename}.iconset/${type}_${sizename}.png" + COMMAND ${CMAKE_COMMAND} + ARGS -E copy + "${filename}" + "${_outfilename}.iconset/${type}_${sizename}.png" + DEPENDS + "${_outfilename}.iconset" + "${filename}" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + ) + list(APPEND iconset_icons + "${_outfilename}.iconset/${type}_${sizename}.png") + endmacro() + foreach(size 16 32 128 256 512) + math(EXPR double_size "2 * ${size}") + foreach(file ${icons_at_${size}px}) + copy_icon("${file}" "${size}x${size}" "icon") + endforeach() + foreach(file ${icons_at_${double_size}px}) + copy_icon("${file}" "${size}x${size}@2x" "icon") + endforeach() + endforeach() + + foreach(size 16 18 32) + math(EXPR double_size "2 * ${size}") + foreach(file ${sidebar_icons_at_${size}px}) + copy_icon("${file}" "${size}x${size}" "sidebar") + endforeach() + foreach(file ${sidebar_icons_at_${double_size}px}) + copy_icon("${file}" "${size}x${size}@2x" "sidebar") + endforeach() + endforeach() + + # generate .icns icon file + add_custom_command( + OUTPUT "${_outfilename}.icns" + COMMAND ${ICONUTIL_EXECUTABLE} + ARGS + --convert icns + --output "${_outfilename}.icns" + "${_outfilename}.iconset" + DEPENDS "${iconset_icons}" + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + ) + # This will register the icon into the bundle + set(MACOSX_BUNDLE_ICON_FILE "${_outfilebasename}.icns" PARENT_SCOPE) + + # Append the icns file to the sources list so it will be a dependency to the + # main target + set(${appsources} "${${appsources}};${_outfilename}.icns" PARENT_SCOPE) + + # Install the icon into the Resources dir in the bundle + set_source_files_properties("${_outfilename}.icns" PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + else() + message(STATUS "Unable to find the iconutil utility - application will not have an application icon!") + endif() + endif() +endfunction() + +macro(_ecm_add_app_icon_categorize_icons icons type known_sizes) + set(_${type}_known_sizes) + foreach(size ${known_sizes}) + set(${type}_at_${size}px) + list(APPEND _${type}_known_sizes ${size}) + endforeach() + + + foreach(icon ${icons}) + get_filename_component(icon_full ${icon} ABSOLUTE) + if (NOT EXISTS "${icon_full}") + message(AUTHOR_WARNING "${icon_full} does not exist, ignoring") + else() + get_filename_component(icon_name ${icon} NAME) + string(REGEX MATCH "([0-9]+)\\-[^/]+\\.([a-z]+)$" + _dummy "${icon_name}") + set(size "${CMAKE_MATCH_1}") + set(ext "${CMAKE_MATCH_2}") + + if (NOT (ext STREQUAL "svg" OR ext STREQUAL "svgz")) + if (NOT size) + message(AUTHOR_WARNING "${icon_full} is not named correctly for ecm_add_app_icon - ignoring") + elseif (NOT ext STREQUAL "png") + message(AUTHOR_WARNING "${icon_full} is not a png file - ignoring") + else() + list(FIND _${type}_known_sizes ${size} offset) + + if (offset GREATER -1) + list(APPEND ${type}_at_${size}px "${icon_full}") + elseif() + message(STATUS "not found ${type}_at_${size}px ${icon_full}") + endif() + endif() + endif() + endif() + endforeach() +endmacro() diff --git a/cmake/modules/ECMFindModuleHelpers.cmake b/cmake/modules/ECMFindModuleHelpers.cmake new file mode 100644 index 000000000..f2e32f959 --- /dev/null +++ b/cmake/modules/ECMFindModuleHelpers.cmake @@ -0,0 +1,297 @@ +#.rst: +# ECMFindModuleHelpers +# -------------------- +# +# Helper macros for find modules: ecm_find_package_version_check(), +# ecm_find_package_parse_components() and +# ecm_find_package_handle_library_components(). +# +# :: +# +# ecm_find_package_version_check() +# +# Prints warnings if the CMake version or the project's required CMake version +# is older than that required by extra-cmake-modules. +# +# :: +# +# ecm_find_package_parse_components( +# RESULT_VAR +# KNOWN_COMPONENTS [ [...]] +# [SKIP_DEPENDENCY_HANDLING]) +# +# This macro will populate with a list of components found in +# _FIND_COMPONENTS, after checking that all those components are in the +# list of KNOWN_COMPONENTS; if there are any unknown components, it will print +# an error or warning (depending on the value of _FIND_REQUIRED) and call +# return(). +# +# The order of components in is guaranteed to match the order they +# are listed in the KNOWN_COMPONENTS argument. +# +# If SKIP_DEPENDENCY_HANDLING is not set, for each component the variable +# __component_deps will be checked for dependent components. +# If is listed in _FIND_COMPONENTS, then all its (transitive) +# dependencies will also be added to . +# +# :: +# +# ecm_find_package_handle_library_components( +# COMPONENTS [ [...]] +# [SKIP_DEPENDENCY_HANDLING]) +# [SKIP_PKG_CONFIG]) +# +# Creates an imported library target for each component. The operation of this +# macro depends on the presence of a number of CMake variables. +# +# The __lib variable should contain the name of this library, +# and __header variable should contain the name of a header +# file associated with it (whatever relative path is normally passed to +# '#include'). __header_subdir variable can be used to specify +# which subdirectory of the include path the headers will be found in. +# ecm_find_package_components() will then search for the library +# and include directory (creating appropriate cache variables) and create an +# imported library target named ::. +# +# Additional variables can be used to provide additional information: +# +# If SKIP_PKG_CONFIG, the __pkg_config variable is set, and +# pkg-config is found, the pkg-config module given by +# __pkg_config will be searched for and used to help locate the +# library and header file. It will also be used to set +# __VERSION. +# +# Note that if version information is found via pkg-config, +# __FIND_VERSION can be set to require a particular version +# for each component. +# +# If SKIP_DEPENDENCY_HANDLING is not set, the INTERFACE_LINK_LIBRARIES property +# of the imported target for will be set to contain the imported +# targets for the components listed in __component_deps. +# _FOUND will also be set to false if any of the compoments in +# __component_deps are not found. This requires the components +# in __component_deps to be listed before in the +# COMPONENTS argument. +# +# The following variables will be set: +# +# ``_TARGETS`` +# the imported targets +# ``_LIBRARIES`` +# the found libraries +# ``_INCLUDE_DIRS`` +# the combined required include directories for the components +# ``_DEFINITIONS`` +# the "other" CFLAGS provided by pkg-config, if any +# ``_VERSION`` +# the value of ``__VERSION`` for the first component that +# has this variable set (note that components are searched for in the order +# they are passed to the macro), although if it is already set, it will not +# be altered +# +# Note that these variables are never cleared, so if +# ecm_find_package_handle_library_components() is called multiple times with +# different components (typically because of multiple find_package() calls) then +# ``_TARGETS``, for example, will contain all the targets found in any +# call (although no duplicates). +# +# Since pre-1.0.0. + +#============================================================================= +# Copyright 2014 Alex Merry +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +include(CMakeParseArguments) + +macro(ecm_find_package_version_check module_name) + if(CMAKE_VERSION VERSION_LESS 2.8.12) + message(FATAL_ERROR "CMake 2.8.12 is required by Find${module_name}.cmake") + endif() + if(CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 2.8.12) + message(AUTHOR_WARNING "Your project should require at least CMake 2.8.12 to use Find${module_name}.cmake") + endif() +endmacro() + +macro(ecm_find_package_parse_components module_name) + set(ecm_fppc_options SKIP_DEPENDENCY_HANDLING) + set(ecm_fppc_oneValueArgs RESULT_VAR) + set(ecm_fppc_multiValueArgs KNOWN_COMPONENTS DEFAULT_COMPONENTS) + cmake_parse_arguments(ECM_FPPC "${ecm_fppc_options}" "${ecm_fppc_oneValueArgs}" "${ecm_fppc_multiValueArgs}" ${ARGN}) + + if(ECM_FPPC_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Unexpected arguments to ecm_find_package_parse_components: ${ECM_FPPC_UNPARSED_ARGUMENTS}") + endif() + if(NOT ECM_FPPC_RESULT_VAR) + message(FATAL_ERROR "Missing RESULT_VAR argument to ecm_find_package_parse_components") + endif() + if(NOT ECM_FPPC_KNOWN_COMPONENTS) + message(FATAL_ERROR "Missing KNOWN_COMPONENTS argument to ecm_find_package_parse_components") + endif() + if(NOT ECM_FPPC_DEFAULT_COMPONENTS) + set(ECM_FPPC_DEFAULT_COMPONENTS ${ECM_FPPC_KNOWN_COMPONENTS}) + endif() + + if(${module_name}_FIND_COMPONENTS) + set(ecm_fppc_requestedComps ${${module_name}_FIND_COMPONENTS}) + + if(NOT ECM_FPPC_SKIP_DEPENDENCY_HANDLING) + # Make sure deps are included + foreach(ecm_fppc_comp ${ecm_fppc_requestedComps}) + foreach(ecm_fppc_dep_comp ${${module_name}_${ecm_fppc_comp}_component_deps}) + list(FIND ecm_fppc_requestedComps "${ecm_fppc_dep_comp}" ecm_fppc_index) + if("${ecm_fppc_index}" STREQUAL "-1") + if(NOT ${module_name}_FIND_QUIETLY) + message(STATUS "${module_name}: ${ecm_fppc_comp} requires ${${module_name}_${ecm_fppc_comp}_component_deps}") + endif() + list(APPEND ecm_fppc_requestedComps "${ecm_fppc_dep_comp}") + endif() + endforeach() + endforeach() + else() + message(STATUS "Skipping dependency handling for ${module_name}") + endif() + list(REMOVE_DUPLICATES ecm_fppc_requestedComps) + + # This makes sure components are listed in the same order as + # KNOWN_COMPONENTS (potentially important for inter-dependencies) + set(${ECM_FPPC_RESULT_VAR}) + foreach(ecm_fppc_comp ${ECM_FPPC_KNOWN_COMPONENTS}) + list(FIND ecm_fppc_requestedComps "${ecm_fppc_comp}" ecm_fppc_index) + if(NOT "${ecm_fppc_index}" STREQUAL "-1") + list(APPEND ${ECM_FPPC_RESULT_VAR} "${ecm_fppc_comp}") + list(REMOVE_AT ecm_fppc_requestedComps ${ecm_fppc_index}) + endif() + endforeach() + # if there are any left, they are unknown components + if(ecm_fppc_requestedComps) + set(ecm_fppc_msgType STATUS) + if(${module_name}_FIND_REQUIRED) + set(ecm_fppc_msgType FATAL_ERROR) + endif() + if(NOT ${module_name}_FIND_QUIETLY) + message(${ecm_fppc_msgType} "${module_name}: requested unknown components ${ecm_fppc_requestedComps}") + endif() + return() + endif() + else() + set(${ECM_FPPC_RESULT_VAR} ${ECM_FPPC_DEFAULT_COMPONENTS}) + endif() +endmacro() + +macro(ecm_find_package_handle_library_components module_name) + set(ecm_fpwc_options SKIP_PKG_CONFIG SKIP_DEPENDENCY_HANDLING) + set(ecm_fpwc_oneValueArgs) + set(ecm_fpwc_multiValueArgs COMPONENTS) + cmake_parse_arguments(ECM_FPWC "${ecm_fpwc_options}" "${ecm_fpwc_oneValueArgs}" "${ecm_fpwc_multiValueArgs}" ${ARGN}) + + if(ECM_FPWC_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Unexpected arguments to ecm_find_package_handle_components: ${ECM_FPWC_UNPARSED_ARGUMENTS}") + endif() + if(NOT ECM_FPWC_COMPONENTS) + message(FATAL_ERROR "Missing COMPONENTS argument to ecm_find_package_handle_components") + endif() + + include(FindPackageHandleStandardArgs) + find_package(PkgConfig) + foreach(ecm_fpwc_comp ${ECM_FPWC_COMPONENTS}) + set(ecm_fpwc_dep_vars) + set(ecm_fpwc_dep_targets) + if(NOT SKIP_DEPENDENCY_HANDLING) + foreach(ecm_fpwc_dep ${${module_name}_${ecm_fpwc_comp}_component_deps}) + list(APPEND ecm_fpwc_dep_vars "${module_name}_${ecm_fpwc_dep}_FOUND") + list(APPEND ecm_fpwc_dep_targets "${module_name}::${ecm_fpwc_dep}") + endforeach() + endif() + + if(NOT ECM_FPWC_SKIP_PKG_CONFIG AND ${module_name}_${ecm_fpwc_comp}_pkg_config) + pkg_check_modules(PKG_${module_name}_${ecm_fpwc_comp} QUIET + ${${module_name}_${ecm_fpwc_comp}_pkg_config}) + endif() + + find_path(${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR + NAMES ${${module_name}_${ecm_fpwc_comp}_header} + HINTS ${PKG_${module_name}_${ecm_fpwc_comp}_INCLUDE_DIRS} + PATH_SUFFIXES ${${module_name}_${ecm_fpwc_comp}_header_subdir} + ) + find_library(${module_name}_${ecm_fpwc_comp}_LIBRARY + NAMES ${${module_name}_${ecm_fpwc_comp}_lib} + HINTS ${PKG_${module_name}_${ecm_fpwc_comp}_LIBRARY_DIRS} + ) + + set(${module_name}_${ecm_fpwc_comp}_VERSION "${PKG_${module_name}_${ecm_fpwc_comp}_VERSION}") + if(NOT ${module_name}_VERSION) + set(${module_name}_VERSION ${${module_name}_${ecm_fpwc_comp}_VERSION}) + endif() + + find_package_handle_standard_args(${module_name}_${ecm_fpwc_comp} + FOUND_VAR + ${module_name}_${ecm_fpwc_comp}_FOUND + REQUIRED_VARS + ${module_name}_${ecm_fpwc_comp}_LIBRARY + ${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR + ${ecm_fpwc_dep_vars} + VERSION_VAR + ${module_name}_${ecm_fpwc_comp}_VERSION + ) + + mark_as_advanced( + ${module_name}_${ecm_fpwc_comp}_LIBRARY + ${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR + ) + + if(${module_name}_${ecm_fpwc_comp}_FOUND) + list(APPEND ${module_name}_LIBRARIES + "${${module_name}_${ecm_fpwc_comp}_LIBRARY}") + list(APPEND ${module_name}_INCLUDE_DIRS + "${${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR}") + set(${module_name}_DEFINITIONS + ${${module_name}_DEFINITIONS} + ${PKG_${module_name}_${ecm_fpwc_comp}_DEFINITIONS}) + if(NOT TARGET ${module_name}::${ecm_fpwc_comp}) + add_library(${module_name}::${ecm_fpwc_comp} UNKNOWN IMPORTED) + set_target_properties(${module_name}::${ecm_fpwc_comp} PROPERTIES + IMPORTED_LOCATION "${${module_name}_${ecm_fpwc_comp}_LIBRARY}" + INTERFACE_COMPILE_OPTIONS "${PKG_${module_name}_${ecm_fpwc_comp}_DEFINITIONS}" + INTERFACE_INCLUDE_DIRECTORIES "${${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES "${ecm_fpwc_dep_targets}" + ) + endif() + list(APPEND ${module_name}_TARGETS + "${module_name}::${ecm_fpwc_comp}") + endif() + endforeach() + if(${module_name}_LIBRARIES) + list(REMOVE_DUPLICATES ${module_name}_LIBRARIES) + endif() + if(${module_name}_INCLUDE_DIRS) + list(REMOVE_DUPLICATES ${module_name}_INCLUDE_DIRS) + endif() + if(${module_name}_DEFINITIONS) + list(REMOVE_DUPLICATES ${module_name}_DEFINITIONS) + endif() + if(${module_name}_TARGETS) + list(REMOVE_DUPLICATES ${module_name}_TARGETS) + endif() +endmacro() diff --git a/cmake/modules/ECMFindModuleHelpersStub.cmake b/cmake/modules/ECMFindModuleHelpersStub.cmake new file mode 100644 index 000000000..bb8c9a62f --- /dev/null +++ b/cmake/modules/ECMFindModuleHelpersStub.cmake @@ -0,0 +1 @@ +include(${CMAKE_CURRENT_LIST_DIR}/../modules/ECMFindModuleHelpers.cmake) diff --git a/cmake/modules/FindPng2Ico.cmake b/cmake/modules/FindPng2Ico.cmake new file mode 100644 index 000000000..d84f66730 --- /dev/null +++ b/cmake/modules/FindPng2Ico.cmake @@ -0,0 +1,117 @@ +#.rst: +# FindPng2Ico +# ----------- +# +# Try to find png2ico. +# +# If the png2ico executable is not in your PATH, you can provide +# an alternative name or full path location with the ``Png2Ico_EXECUTABLE`` +# variable. +# +# This will define the following variables: +# +# ``Png2Ico_FOUND`` +# True if png2ico is available. +# +# ``Png2Ico_EXECUTABLE`` +# The png2ico executable. +# +# If ``Png2Ico_FOUND`` is TRUE, it will also define the following imported +# target: +# +# ``Png2Ico::Png2Ico`` +# The png2ico executable. +# +# and the following variables: +# +# ``Png2Ico_HAS_COLORS_ARGUMENT`` +# Whether png2ico accepts a ``--colors`` argument. `Matthias Benkmann's +# tool `_ does, while the +# version of png2ico from the `"KDE On Windows" (kdewin) +# `_ project does not. +# +# ``Png2Ico_HAS_RCFILE_ARGUMENT`` +# Whether png2ico accepts an ``--rcfile`` argument. The version of png2ico +# from the `"KDE On Windows" (kdewin) +# `_ project does, +# while `Matthias Benkmann's tool +# `_ does not. +# +# Since 1.7.0. + +#============================================================================= +# Copyright 2014 Alex Merry +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#============================================================================= + +include(${CMAKE_CURRENT_LIST_DIR}/ECMFindModuleHelpersStub.cmake) + +ecm_find_package_version_check(Png2Ico) + +# Find png2ico +find_program(Png2Ico_EXECUTABLE NAMES png2ico) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Png2Ico + FOUND_VAR + Png2Ico_FOUND + REQUIRED_VARS + Png2Ico_EXECUTABLE +) + +mark_as_advanced(Png2Ico_EXECUTABLE) + +if (Png2Ico_FOUND) + execute_process( + COMMAND "${Png2Ico_EXECUTABLE}" --help + OUTPUT_VARIABLE _png2ico_help_text + ERROR_VARIABLE _png2ico_help_text + ) + if (_png2ico_help_text MATCHES ".*--rcfile .*") + set(Png2Ico_HAS_RCFILE_ARGUMENT TRUE) + else() + set(Png2Ico_HAS_RCFILE_ARGUMENT FALSE) + endif() + if (_png2ico_help_text MATCHES ".*--colors .*") + set(Png2Ico_HAS_COLORS_ARGUMENT TRUE) + else() + set(Png2Ico_HAS_COLORS_ARGUMENT FALSE) + endif() + unset(_png2ico_help_text) + + if (NOT TARGET Png2Ico::Png2Ico) + add_executable(Png2Ico::Png2Ico IMPORTED) + set_target_properties(Png2Ico::Png2Ico PROPERTIES + IMPORTED_LOCATION "${Png2Ico_EXECUTABLE}" + ) + endif() +endif() + +include(FeatureSummary) +set_package_properties(Png2Ico PROPERTIES + URL "http://www.winterdrache.de/freeware/png2ico/ or https://projects.kde.org/projects/kdesupport/kdewin" + DESCRIPTION "Executable that converts a collection of PNG files into a Windows icon file" +) + diff --git a/cmake/modules/MacroAddPlugin.cmake b/cmake/modules/MacroAddPlugin.cmake deleted file mode 100644 index 8ddf8d689..000000000 --- a/cmake/modules/MacroAddPlugin.cmake +++ /dev/null @@ -1,30 +0,0 @@ -# - MACRO_ADD_PLUGIN(name [WITH_PREFIX] file1 .. fileN) -# -# Create a plugin from the given source files. -# If WITH_PREFIX is given, the resulting plugin will have the -# prefix "lib", otherwise it won't. -# -# Copyright (c) 2006, Alexander Neundorf, -# Copyright (c) 2006, Laurent Montel, -# Copyright (c) 2006, Andreas Schneider, -# -# Redistribution and use is allowed according to the terms of the BSD license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. - - -macro (MACRO_ADD_PLUGIN _target_NAME _with_PREFIX) - - if (${_with_PREFIX} STREQUAL "WITH_PREFIX") - set(_first_SRC) - else (${_with_PREFIX} STREQUAL "WITH_PREFIX") - set(_first_SRC ${_with_PREFIX}) - endif (${_with_PREFIX} STREQUAL "WITH_PREFIX") - - add_library(${_target_NAME} MODULE ${_first_SRC} ${ARGN}) - - if (_first_SRC) - set_target_properties(${_target_NAME} PROPERTIES PREFIX "") - endif (_first_SRC) - -endmacro (MACRO_ADD_PLUGIN _name _sources) - diff --git a/cmake/modules/MacroCopyFile.cmake b/cmake/modules/MacroCopyFile.cmake deleted file mode 100644 index 210c7a427..000000000 --- a/cmake/modules/MacroCopyFile.cmake +++ /dev/null @@ -1,37 +0,0 @@ -# (c) 2014 Copyright ownCloud GmbH -# Redistribution and use is allowed according to the terms of the BSD license. -# For details see the accompanying COPYING* file. - -# - macro_copy_file(_src _dst) -# Copies a file to ${_dst} only if ${_src} is different (newer) than ${_dst} -# -# Example: -# macro_copy_file(${CMAKE_CURRENT_SOURCE_DIR}/icon.png ${CMAKE_CURRENT_BINARY_DIR}/.) -# Copies file icon.png to ${CMAKE_CURRENT_BINARY_DIR} directory -# -# Copyright (c) 2006-2007 Wengo -# Copyright (c) 2006-2008 Andreas Schneider -# -# Redistribution and use is allowed according to the terms of the BSD license. -# For details see the accompanying COPYING file. - - -macro (macro_copy_file _src _dst) - # Removes all path containing .svn or CVS or CMakeLists.txt during the copy - if (NOT ${_src} MATCHES ".*\\.svn|CVS|CMakeLists\\.txt.*") - - if (CMAKE_VERBOSE_MAKEFILE) - message(STATUS "Copy file from ${_src} to ${_dst}") - endif (CMAKE_VERBOSE_MAKEFILE) - - # Creates directory if necessary - get_filename_component(_path ${_dst} PATH) - file(MAKE_DIRECTORY ${_path}) - - execute_process( - COMMAND - ${CMAKE_COMMAND} -E copy_if_different ${_src} ${_dst} - OUTPUT_QUIET - ) - endif (NOT ${_src} MATCHES ".*\\.svn|CVS|CMakeLists\\.txt.*") -endmacro (macro_copy_file) diff --git a/cmake/modules/NSIS.template.in b/cmake/modules/NSIS.template.in index 9b52bf338..967eebdf5 100644 --- a/cmake/modules/NSIS.template.in +++ b/cmake/modules/NSIS.template.in @@ -461,6 +461,7 @@ Section "${APPLICATION_NAME}" SEC_APPLICATION File "${MING_BIN}\libgcc_s_sjlj-1.dll" File "${MING_BIN}\libstdc++-6.dll" File "${MING_BIN}\libwinpthread-1.dll" + File "${MING_BIN}\libssp-0.dll" ;CSync configs File "${SOURCE_PATH}/sync-exclude.lst" diff --git a/cmake/modules/QtVersionAbstraction.cmake b/cmake/modules/QtVersionAbstraction.cmake deleted file mode 100644 index ce48b4487..000000000 --- a/cmake/modules/QtVersionAbstraction.cmake +++ /dev/null @@ -1,104 +0,0 @@ -# (c) 2014 Copyright ownCloud GmbH -# Redistribution and use is allowed according to the terms of the BSD license. -# For details see the accompanying COPYING* file. - -include (MacroOptionalFindPackage) -include (MacroLogFeature) - -find_package(Qt5Core REQUIRED) -find_package(Qt5Network REQUIRED) -find_package(Qt5Xml REQUIRED) -find_package(Qt5Concurrent REQUIRED) -if(UNIT_TESTING) - find_package(Qt5Test REQUIRED) -endif() - -if(NOT TOKEN_AUTH_ONLY) - find_package(Qt5Widgets REQUIRED) - if(APPLE) - find_package(Qt5MacExtras REQUIRED) - endif(APPLE) - - if(NOT NO_SHIBBOLETH) - find_package(Qt5WebKitWidgets) - find_package(Qt5WebKit) - if(NOT Qt5WebKitWidgets_FOUND) - message(FATAL_ERROR "Qt5WebKit required for Shibboleth. Use -DNO_SHIBBOLETH=1 to disable it.") - endif() - endif() -endif() - -# We need this to find the paths to qdbusxml2cpp and co -if (WITH_DBUS) - find_package(Qt5DBus REQUIRED) - include_directories(${Qt5DBus_INCLUDES}) - add_definitions(${Qt5DBus_DEFINITIONS}) -endif (WITH_DBUS) -include_directories(${Qt5Core_INCLUDES}) -add_definitions(${Qt5Core_DEFINITIONS}) -if (NOT WIN32) #implied on Win32 - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") -endif(NOT WIN32) -# set(CMAKE_CXX_FLAGS "${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}") - -if(APPLE AND NOT TOKEN_AUTH_ONLY) - include_directories(${Qt5MacExtras_INCLUDE_DIRS}) - add_definitions(${Qt5MacExtras_DEFINITIONS}) - set (QT_LIBRARIES ${QT_LIBRARIES} ${Qt5MacExtras_LIBRARIES}) -endif() - -if(NOT BUILD_LIBRARIES_ONLY) - macro(qt_wrap_ui) - qt5_wrap_ui(${ARGN}) - endmacro() -else() - # hack - SET(QT_UIC_EXECUTABLE "") -endif() - -macro(qt_add_resources) - qt5_add_resources(${ARGN}) -endmacro() - -if(NOT TOKEN_AUTH_ONLY) - find_package(Qt5LinguistTools) - if(Qt5LinguistTools_FOUND) - macro(qt_add_translation) - qt5_add_translation(${ARGN}) - endmacro() - else() - macro(qt_add_translation) - endmacro() - endif() -else() - macro(qt_add_translation) - endmacro() -endif() - -macro(qt_add_dbus_interface) - qt5_add_dbus_interface(${ARGN}) -endmacro() - -macro(qt_add_dbus_adaptor) - qt5_add_dbus_adaptor(${ARGN}) -endmacro() - -macro(qt_wrap_cpp) - qt5_wrap_cpp(${ARGN}) -endmacro() - -macro(install_qt_executable) - install_qt5_executable(${ARGN}) -endmacro() - -macro(setup_qt) -endmacro() - -set(QT_RCC_EXECUTABLE "${Qt5Core_RCC_EXECUTABLE}") - -#Enable deprecated symbols -add_definitions("-DQT_DISABLE_DEPRECATED_BEFORE=0") -add_definitions("-DQT_DEPRECATED_WARNINGS") -add_definitions("-DQT_USE_QSTRINGBUILDER") #optimize string concatenation -add_definitions("-DQT_MESSAGELOGCONTEXT") #enable function name and line number in debug output - diff --git a/cmake/modules/UseAsciidoc.cmake b/cmake/modules/UseAsciidoc.cmake deleted file mode 100644 index 42024b29e..000000000 --- a/cmake/modules/UseAsciidoc.cmake +++ /dev/null @@ -1,58 +0,0 @@ -# - macro_asciidoc2man(inputfile outputfile) -# -# Create a manpage with asciidoc. -# Example: macro_asciidoc2man(foo.txt foo.1) -# -# Copyright (c) 2006, Andreas Schneider, -# -# Redistribution and use is allowed according to the terms of the BSD license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. - -include(MacroCopyFile) - -macro(MACRO_ASCIIDOC2MAN _a2m_input _a2m_output) - find_program(A2X - NAMES - a2x - ) - #message("+++ A2X: ${A2X}") - - if (A2X) - - #message("+++ ${A2X} --doctype=manpage --format=manpage --destination-dir=${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${_a2m_input}") - macro_copy_file(${CMAKE_CURRENT_SOURCE_DIR}/${_a2m_input} ${CMAKE_CURRENT_BINARY_DIR}/${_a2m_input}) - - execute_process( - COMMAND - ${A2X} --doctype=manpage --format=manpage ${_a2m_input} - WORKING_DIRECTORY - ${CMAKE_CURRENT_BINARY_DIR} - RESULT_VARIABLE - A2M_MAN_GENERATED - ERROR_QUIET - ) - - #message("+++ A2M_MAN_GENERATED: ${A2M_MAN_GENERATED}") - if (A2M_MAN_GENERATED EQUAL 0) - find_file(A2M_MAN_FILE - NAME - ${_a2m_output} - PATHS - ${CMAKE_CURRENT_BINARY_DIR} - NO_DEFAULT_PATH - ) - - if (A2M_MAN_FILE) - get_filename_component(A2M_MAN_CATEGORY ${A2M_MAN_FILE} EXT) - string(SUBSTRING ${A2M_MAN_CATEGORY} 1 1 A2M_MAN_CATEGORY) - install( - FILES - ${A2M_MAN_FILE} - DESTINATION - ${MAN_INSTALL_DIR}/man${A2M_MAN_CATEGORY} - ) - endif (A2M_MAN_FILE) - endif (A2M_MAN_GENERATED EQUAL 0) - - endif (A2X) -endmacro(MACRO_ASCIIDOC2MAN _a2m_input _a2m_file) diff --git a/cmake/modules/Warnings.cmake b/cmake/modules/Warnings.cmake index 0edabd2a9..f2c271e88 100644 --- a/cmake/modules/Warnings.cmake +++ b/cmake/modules/Warnings.cmake @@ -4,7 +4,11 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -Wno-long-long -Wno-gnu-zero-variadic-macro-arguments") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") + + # Fix sqlite compilation on macOS + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-incompatible-pointer-types-discards-qualifiers") + # Fix sqlite compilation on MinGW + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-discarded-qualifiers") if (CMAKE_CXX_COMPILER_ID MATCHES "GNU") execute_process(COMMAND ${CMAKE_C_COMPILER} -dumpversion diff --git a/csync/tests/csync_tests/check_csync_update.cpp b/csync/tests/csync_tests/check_csync_update.cpp deleted file mode 100644 index cc11bad83..000000000 --- a/csync/tests/csync_tests/check_csync_update.cpp +++ /dev/null @@ -1,460 +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_update.cpp" - -extern "C" { - -#include "torture.h" - -#define TESTDB "/tmp/check_csync/journal.db" - -static int firstrun = 1; - -static void statedb_create_metadata_table(sqlite3 *db) -{ - int rc = 0; - - if( db ) { - const char *sql = "CREATE TABLE IF NOT EXISTS metadata(" - "phash INTEGER(8)," - "pathlen INTEGER," - "path VARCHAR(4096)," - "inode INTEGER," - "uid INTEGER," - "gid INTEGER," - "mode INTEGER," - "modtime INTEGER(8)," - "type INTEGER," - "md5 VARCHAR(32)," - "fileid VARCHAR(128)," - "remotePerm VARCHAR(128)," - "filesize BIGINT," - "ignoredChildrenRemote INT," - "contentChecksum TEXT," - "contentChecksumTypeId INTEGER," - "PRIMARY KEY(phash));"; - - rc = sqlite3_exec(db, sql, NULL, NULL, NULL); - //const char *msg = sqlite3_errmsg(db); - assert_int_equal( rc, SQLITE_OK ); - - sql = "CREATE TABLE IF NOT EXISTS checksumtype(" - "id INTEGER PRIMARY KEY," - "name TEXT UNIQUE" - ");"; - rc = sqlite3_exec(db, sql, NULL, NULL, NULL); - assert_int_equal( rc, SQLITE_OK ); - } -} - -static void statedb_insert_metadata(sqlite3 *db) -{ - int rc = 0; - - if( db ) { - char *stmt = sqlite3_mprintf("INSERT INTO metadata" - "(phash, pathlen, path, inode, uid, gid, mode, modtime,type,md5) VALUES" - "(%lld, %d, '%q', %d, %d, %d, %d, %lld, %d, '%q');", - (long long signed int)42, - 42, - "I_was_wurst_before_I_became_wurstsalat", - 619070, - 42, - 42, - 42, - (long long signed int)42, - 0, - "4711"); - - char *errmsg; - rc = sqlite3_exec(db, stmt, NULL, NULL, &errmsg); - sqlite3_free(stmt); - assert_int_equal( rc, SQLITE_OK ); - } -} - -static int setup(void **state) -{ - CSYNC *csync; - int rc; - - unlink(TESTDB); - rc = system("mkdir -p /tmp/check_csync"); - assert_int_equal(rc, 0); - rc = system("mkdir -p /tmp/check_csync1"); - assert_int_equal(rc, 0); - csync_create(&csync, "/tmp/check_csync1"); - csync_init(csync, TESTDB); - - /* Create a new db with metadata */ - sqlite3 *db; - csync->statedb.file = c_strdup(TESTDB); - rc = sqlite3_open(csync->statedb.file, &db); - statedb_create_metadata_table(db); - if( firstrun ) { - statedb_insert_metadata(db); - firstrun = 0; - } - sqlite3_close(db); - - rc = csync_statedb_load(csync, TESTDB, &csync->statedb.db); - assert_int_equal(rc, 0); - - *state = csync; - - return 0; -} - -static int setup_ftw(void **state) -{ - CSYNC *csync; - int rc; - - rc = system("mkdir -p /tmp/check_csync"); - assert_int_equal(rc, 0); - rc = system("mkdir -p /tmp/check_csync1"); - assert_int_equal(rc, 0); - csync_create(&csync, "/tmp"); - csync_init(csync, TESTDB); - - sqlite3 *db = NULL; - rc = sqlite3_open_v2(TESTDB, &db, SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, NULL); - assert_int_equal(rc, SQLITE_OK); - statedb_create_metadata_table(db); - rc = sqlite3_close(db); - assert_int_equal(rc, SQLITE_OK); - - rc = csync_statedb_load(csync, TESTDB, &csync->statedb.db); - assert_int_equal(rc, 0); - - csync->statedb.file = c_strdup( TESTDB ); - *state = csync; - - return 0; -} - -static int teardown(void **state) -{ - CSYNC *csync = (CSYNC*)*state; - int rc; - - unlink( csync->statedb.file); - rc = csync_destroy(csync); - assert_int_equal(rc, 0); - - *state = NULL; - - return 0; -} - -static int teardown_rm(void **state) { - int rc; - - teardown(state); - - rc = system("rm -rf /tmp/check_csync"); - assert_int_equal(rc, 0); - rc = system("rm -rf /tmp/check_csync1"); - assert_int_equal(rc, 0); - - return 0; -} - -/* create a file stat, caller must free memory */ -static csync_vio_file_stat_t* create_fstat(const char *name, - ino_t inode, - time_t mtime) -{ - csync_vio_file_stat_t *fs = NULL; - time_t t; - - fs = csync_vio_file_stat_new(); - if (fs == NULL) { - return NULL; - } - - if (name && *name) { - fs->name = c_strdup(name); - } else { - fs->name = c_strdup("file.txt"); - } - - if (fs->name == NULL) { - csync_vio_file_stat_destroy(fs); - return NULL; - } - - fs->fields = CSYNC_VIO_FILE_STAT_FIELDS_NONE; - - fs->type = CSYNC_VIO_FILE_TYPE_REGULAR; - fs->fields |= CSYNC_VIO_FILE_STAT_FIELDS_TYPE; - - - if (inode == 0) { - fs->inode = 619070; - } else { - fs->inode = inode; - } - fs->fields |= CSYNC_VIO_FILE_STAT_FIELDS_INODE; - - - fs->size = 157459; - fs->fields |= CSYNC_VIO_FILE_STAT_FIELDS_SIZE; - - - - if (mtime == 0) { - fs->atime = fs->ctime = fs->mtime = time(&t); - } else { - fs->atime = fs->ctime = fs->mtime = mtime; - } - fs->fields |= CSYNC_VIO_FILE_STAT_FIELDS_ATIME; - fs->fields |= CSYNC_VIO_FILE_STAT_FIELDS_CTIME; - fs->fields |= CSYNC_VIO_FILE_STAT_FIELDS_MTIME; - - return fs; -} - -static int failing_fn(CSYNC *ctx, - const char *file, - const csync_vio_file_stat_t *fs, - int flag) -{ - (void) ctx; - (void) file; - (void) fs; - (void) flag; - - return -1; -} - -/* detect a new file */ -static void check_csync_detect_update(void **state) -{ - CSYNC *csync = (CSYNC*)*state; - csync_file_stat_t *st; - csync_vio_file_stat_t *fs; - int rc; - - fs = create_fstat("file.txt", 0, 1217597845); - assert_non_null(fs); - - rc = _csync_detect_update(csync, - "/tmp/check_csync1/file.txt", - fs, - CSYNC_FTW_TYPE_FILE); - assert_int_equal(rc, 0); - - /* the instruction should be set to new */ - st = (csync_file_stat_t*)c_rbtree_node_data(csync->local.tree->root); - assert_int_equal(st->instruction, CSYNC_INSTRUCTION_NEW); - - /* create a statedb */ - csync_set_status(csync, 0xFFFF); - - csync_vio_file_stat_destroy(fs); -} - -/* Test behaviour in case no db is there. For that its important that the - * test before this one uses teardown_rm. - */ -static void check_csync_detect_update_db_none(void **state) -{ - CSYNC *csync = (CSYNC*)*state; - csync_file_stat_t *st; - csync_vio_file_stat_t *fs; - int rc; - - fs = create_fstat("file.txt", 0, 1217597845); - assert_non_null(fs); - - rc = _csync_detect_update(csync, - "/tmp/check_csync1/file.txt", - fs, - CSYNC_FTW_TYPE_FILE); - assert_int_equal(rc, 0); - - /* the instruction should be set to new */ - st = (csync_file_stat_t*)c_rbtree_node_data(csync->local.tree->root); - assert_int_equal(st->instruction, CSYNC_INSTRUCTION_NEW); - - - /* create a statedb */ - csync_set_status(csync, 0xFFFF); - - csync_vio_file_stat_destroy(fs); -} - -static void check_csync_detect_update_db_eval(void **state) -{ - CSYNC *csync = (CSYNC*)*state; - csync_file_stat_t *st; - csync_vio_file_stat_t *fs; - int rc; - - fs = create_fstat("file.txt", 0, 42); - assert_non_null(fs); - - rc = _csync_detect_update(csync, - "/tmp/check_csync1/file.txt", - fs, - CSYNC_FTW_TYPE_FILE); - assert_int_equal(rc, 0); - - /* the instruction should be set to new */ - st = (csync_file_stat_t*)c_rbtree_node_data(csync->local.tree->root); - assert_int_equal(st->instruction, CSYNC_INSTRUCTION_NEW); - - /* create a statedb */ - csync_set_status(csync, 0xFFFF); - - csync_vio_file_stat_destroy(fs); -} - - -static void check_csync_detect_update_db_rename(void **state) -{ - CSYNC *csync = (CSYNC*)*state; - // csync_file_stat_t *st; - - csync_vio_file_stat_t *fs; - int rc = 0; - - fs = create_fstat("wurst.txt", 0, 42); - assert_non_null(fs); - csync_set_statedb_exists(csync, 1); - - rc = _csync_detect_update(csync, - "/tmp/check_csync1/wurst.txt", - fs, - CSYNC_FTW_TYPE_FILE); - assert_int_equal(rc, 0); - - /* the instruction should be set to rename */ - /* - * temporarily broken. - st = (csync_file_stat_t*)c_rbtree_node_data(csync->local.tree->root); - assert_int_equal(st->instruction, CSYNC_INSTRUCTION_RENAME); - - st->instruction = CSYNC_INSTRUCTION_UPDATED; - */ - /* create a statedb */ - csync_set_status(csync, 0xFFFF); - - csync_vio_file_stat_destroy(fs); -} - -static void check_csync_detect_update_db_new(void **state) -{ - CSYNC *csync = (CSYNC*)*state; - csync_file_stat_t *st; - csync_vio_file_stat_t *fs; - int rc; - - fs = create_fstat("file.txt", 42000, 0); - assert_non_null(fs); - - rc = _csync_detect_update(csync, - "/tmp/check_csync1/file.txt", - fs, - CSYNC_FTW_TYPE_FILE); - assert_int_equal(rc, 0); - - /* the instruction should be set to new */ - st = (csync_file_stat_t*)c_rbtree_node_data(csync->local.tree->root); - assert_int_equal(st->instruction, CSYNC_INSTRUCTION_NEW); - - - /* create a statedb */ - csync_set_status(csync, 0xFFFF); - - csync_vio_file_stat_destroy(fs); -} - -static void check_csync_detect_update_null(void **state) -{ - CSYNC *csync = (CSYNC*)*state; - csync_vio_file_stat_t *fs; - int rc; - - fs = create_fstat("file.txt", 0, 0); - assert_non_null(fs); - - rc = _csync_detect_update(csync, - NULL, - fs, - CSYNC_FTW_TYPE_FILE); - assert_int_equal(rc, -1); - - rc = _csync_detect_update(csync, - "/tmp/check_csync1/file.txt", - NULL, - CSYNC_FTW_TYPE_FILE); - assert_int_equal(rc, -1); - - csync_vio_file_stat_destroy(fs); -} - -static void check_csync_ftw(void **state) -{ - CSYNC *csync = (CSYNC*)*state; - int rc; - - rc = csync_ftw(csync, "/tmp", csync_walker, MAX_DEPTH); - assert_int_equal(rc, 0); -} - -static void check_csync_ftw_empty_uri(void **state) -{ - CSYNC *csync = (CSYNC*)*state; - int rc; - - rc = csync_ftw(csync, "", csync_walker, MAX_DEPTH); - assert_int_equal(rc, -1); -} - -static void check_csync_ftw_failing_fn(void **state) -{ - CSYNC *csync = (CSYNC*)*state; - int rc; - - rc = csync_ftw(csync, "/tmp", failing_fn, MAX_DEPTH); - assert_int_equal(rc, -1); -} - -int torture_run_tests(void) -{ - const struct CMUnitTest tests[] = { - cmocka_unit_test_setup_teardown(check_csync_detect_update, setup, teardown_rm), - cmocka_unit_test_setup_teardown(check_csync_detect_update_db_none, setup, teardown), - cmocka_unit_test_setup_teardown(check_csync_detect_update_db_eval, setup, teardown), - cmocka_unit_test_setup_teardown(check_csync_detect_update_db_rename, setup, teardown), - cmocka_unit_test_setup_teardown(check_csync_detect_update_db_new, setup, teardown_rm), - cmocka_unit_test_setup_teardown(check_csync_detect_update_null, setup, teardown_rm), - - cmocka_unit_test_setup_teardown(check_csync_ftw, setup_ftw, teardown_rm), - cmocka_unit_test_setup_teardown(check_csync_ftw_empty_uri, setup_ftw, teardown_rm), - cmocka_unit_test_setup_teardown(check_csync_ftw_failing_fn, setup_ftw, teardown_rm), - }; - - return cmocka_run_group_tests(tests, NULL, NULL); -} - -} diff --git a/mirall.desktop.in b/mirall.desktop.in index 79b65d54b..b7cf6bf30 100644 --- a/mirall.desktop.in +++ b/mirall.desktop.in @@ -77,6 +77,123 @@ X-GNOME-Autostart-Delay=3 # Translations +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + # Translations Comment[oc]=@APPLICATION_NAME@ sincronizacion del client GenericName[oc]=Dorsièr de Sincronizacion @@ -160,6 +277,7 @@ Icon[it]=@APPLICATION_EXECUTABLE@ Comment[ko]=@APPLICATION_NAME@ 데스크톱 동기화 클라이언트 GenericName[ko]=폴더 동기화 Name[ko]=@APPLICATION_NAME@ 데스크톱 동기화 클라이언트 +Icon[ko]=@APPLICATION_EXECUTABLE@ Comment[hu_HU]=@APPLICATION_NAME@ asztali szinkronizációs kliens GenericName[hu_HU]=Könyvtár szinkronizálás Name[hu_HU]=@APPLICATION_NAME@ asztali szinkr. kliens diff --git a/shell_integration/CMakeLists.txt b/shell_integration/CMakeLists.txt index f428fccb1..b53f19cd5 100644 --- a/shell_integration/CMakeLists.txt +++ b/shell_integration/CMakeLists.txt @@ -23,3 +23,6 @@ if( UNIX AND NOT APPLE ) add_subdirectory(libcloudproviders) endif() +if(MSVC) + add_subdirectory(windows) +endif() diff --git a/shell_integration/MacOSX/CMakeLists.txt b/shell_integration/MacOSX/CMakeLists.txt index 06174af7c..4173f6e94 100644 --- a/shell_integration/MacOSX/CMakeLists.txt +++ b/shell_integration/MacOSX/CMakeLists.txt @@ -1,6 +1,5 @@ if(APPLE) -# Contrary to popular belief, this is called like this no matter what theme/OEM. -set(OC_OEM_SHARE_ICNS "${CMAKE_BINARY_DIR}/src/gui/ownCloud.icns") +set(OC_OEM_SHARE_ICNS "${CMAKE_BINARY_DIR}/src/gui/${APPLICATION_ICON_NAME}.icns") # The bundle identifier and application group need to have compatible values with the client # to be able to open a Mach port across the extension's sandbox boundary. diff --git a/shell_integration/dolphin/ownclouddolphinactionplugin.cpp b/shell_integration/dolphin/ownclouddolphinactionplugin.cpp index c47cb783d..8b306ba95 100644 --- a/shell_integration/dolphin/ownclouddolphinactionplugin.cpp +++ b/shell_integration/dolphin/ownclouddolphinactionplugin.cpp @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ******************************************************************************/ -#include -#include +#include +#include #include #include #include @@ -27,10 +27,12 @@ #include #include #include +#include #include "ownclouddolphinpluginhelper.h" class OwncloudDolphinPluginAction : public KAbstractFileItemActionPlugin { + Q_OBJECT public: explicit OwncloudDolphinPluginAction(QObject* parent, const QList&) : KAbstractFileItemActionPlugin(parent) { } @@ -38,22 +40,72 @@ public: QList actions(const KFileItemListProperties& fileItemInfos, QWidget* parentWidget) Q_DECL_OVERRIDE { auto helper = OwncloudDolphinPluginHelper::instance(); - QList urls = fileItemInfos.urlList(); - if (urls.count() != 1 || !helper->isConnected()) + if (!helper->isConnected() || !fileItemInfos.isLocal()) return {}; - auto url = urls.first(); - if (!url.isLocalFile()) - return {}; - QDir localPath(url.toLocalFile()); - auto localFile = localPath.canonicalPath(); - + // 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(); - if (!std::any_of(paths.begin(), paths.end(), [&](const QString &s) { - return localFile.startsWith(s); - } )) - return {}; + 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")); + }); + } + }); + QTimer::singleShot(100, &loop, SLOT(quit())); // add a timeout to be sure we don't freeze dolphin + helper->sendCommand(QByteArray("GET_MENU_ITEMS:" + files + "\n")); + loop.exec(QEventLoop::ExcludeUserInputEvents); + disconnect(con); + if (menu->actions().isEmpty()) { + delete menu; + return {}; + } + + auto menuaction = new QAction(parentWidget); + menuaction->setText(helper->contextMenuTitle()); + 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); @@ -61,8 +113,8 @@ public: auto shareAction = menu->addAction(helper->shareActionTitle()); connect(shareAction, &QAction::triggered, this, [localFile, helper] { - helper->sendCommand(QByteArray("SHARE:"+localFile.toUtf8()+"\n")); - } ); + helper->sendCommand(QByteArray("SHARE:" + localFile.toUtf8() + "\n")); + }); if (!helper->copyPrivateLinkTitle().isEmpty()) { auto copyPrivateLinkAction = menu->addAction(helper->copyPrivateLinkTitle()); @@ -77,7 +129,6 @@ public: helper->sendCommand(QByteArray("EMAIL_PRIVATE_LINK:" + localFile.toUtf8() + "\n")); }); } - return { menuaction }; } diff --git a/shell_integration/dolphin/ownclouddolphinpluginhelper.cpp b/shell_integration/dolphin/ownclouddolphinpluginhelper.cpp index ae1705220..248aa06cf 100644 --- a/shell_integration/dolphin/ownclouddolphinpluginhelper.cpp +++ b/shell_integration/dolphin/ownclouddolphinpluginhelper.cpp @@ -59,6 +59,7 @@ void OwncloudDolphinPluginHelper::sendCommand(const char* data) void OwncloudDolphinPluginHelper::slotConnected() { + sendCommand("VERSION:\n"); sendCommand("GET_STRINGS:\n"); } @@ -98,6 +99,16 @@ void OwncloudDolphinPluginHelper::slotReadyRead() _strings[args[1]] = args.mid(2).join(QLatin1Char(':')); } continue; + } else if (line.startsWith("VERSION:")) { + auto args = line.split(':'); + auto version = args.value(2); + _version = version; + if (!version.startsWith("1.")) { + // Incompatible version, disconnect forever + _connectTimer.stop(); + _socket.disconnectFromServer(); + return; + } } emit commandRecieved(line); } diff --git a/shell_integration/dolphin/ownclouddolphinpluginhelper.h b/shell_integration/dolphin/ownclouddolphinpluginhelper.h index 294d1969d..3e7a97477 100644 --- a/shell_integration/dolphin/ownclouddolphinpluginhelper.h +++ b/shell_integration/dolphin/ownclouddolphinpluginhelper.h @@ -21,6 +21,7 @@ #include #include #include +#include #include "ownclouddolphinpluginhelper_export.h" class OWNCLOUDDOLPHINPLUGINHELPER_EXPORT OwncloudDolphinPluginHelper : public QObject { @@ -44,6 +45,8 @@ public: 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); @@ -61,4 +64,5 @@ private: QBasicTimer _connectTimer; QMap _strings; + QByteArray _version; }; diff --git a/shell_integration/libcloudproviders/CMakeLists.txt b/shell_integration/libcloudproviders/CMakeLists.txt index 8a6cab2fa..4c7af0042 100644 --- a/shell_integration/libcloudproviders/CMakeLists.txt +++ b/shell_integration/libcloudproviders/CMakeLists.txt @@ -41,7 +41,7 @@ macro(libcloudproviders_add_config _sources) endmacro(libcloudproviders_add_config _sources) -IF (UNIX AND WITH_DBUS AND LIBCLOUDPROVIDERS_FOUND) +IF (UNIX AND Qt5DBus_FOUND AND LIBCLOUDPROVIDERS_FOUND) STRING(TOLOWER "${APPLICATION_VENDOR}" DBUS_VENDOR) STRING(REGEX REPLACE "[^A-z0-9]" "" DBUS_VENDOR "${DBUS_VENDOR}") STRING(REGEX REPLACE "[^A-z0-9]" "" DBUS_APPLICATION_NAME "${APPLICATION_SHORTNAME}") diff --git a/shell_integration/nautilus/syncstate.py b/shell_integration/nautilus/syncstate.py index 082be4e22..6253c07e4 100644 --- a/shell_integration/nautilus/syncstate.py +++ b/shell_integration/nautilus/syncstate.py @@ -15,10 +15,16 @@ # 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 @@ -30,11 +36,11 @@ appname = 'ownCloud' print("Initializing "+appname+"-client-nautilus extension") - def get_local_path(url): if url[0:7] == 'file://': url = url[7:] - return urllib.unquote(url) + unquote = urllib.parse.unquote if python3 else urllib.unquote + return unquote(url) def get_runtime_dir(): """Returns the value of $XDG_RUNTIME_DIR, a directory path. @@ -55,8 +61,9 @@ class SocketConnect(GObject.GObject): self.registered_paths = {} self._watch_id = 0 self._sock = None - self._listeners = [self._update_registered_paths] - self._remainder = '' + self._listeners = [self._update_registered_paths, self._get_version] + self._remainder = ''.encode() + self.protocolVersion = '1.0' self.nautilusVFSFile_table = {} # not needed in this object actually but shared # all over the other objects. @@ -74,7 +81,7 @@ class SocketConnect(GObject.GObject): # print("Server command: " + cmd) if self.connected: try: - self._sock.send(cmd) + self._sock.send(cmd.encode()) except: print("Sending failed.") self.reconnect() @@ -96,6 +103,7 @@ class SocketConnect(GObject.GObject): self._watch_id = GObject.io_add_watch(self._sock, GObject.IO_IN, self._handle_notify) print("Socket watch id: " + str(self._watch_id)) + self.sendCommand('VERSION:\n') self.sendCommand('GET_STRINGS:\n') return False # Don't run again @@ -107,29 +115,43 @@ class SocketConnect(GObject.GObject): 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('\n'.encode()) + if end == -1: + return [] + data = self._remainder[:end] + self._remainder = self._remainder[end+1:] + return data.decode().split('\n') + # Notify is the raw answer from the socket def _handle_notify(self, source, condition): - data = source.recv(1024) - # Prepend the remaining data from last call - if len(self._remainder) > 0: - data = self._remainder + data - self._remainder = '' + # Blocking is ok since we're notified of available data + self._remainder += self._sock.recv(1024) - if len(data) > 0: - # Remember the remainder for next round - lastNL = data.rfind('\n'); - if lastNL > -1 and lastNL < len(data): - self._remainder = data[lastNL+1:] - data = data[:lastNL] - - for l in data.split('\n'): - self._handle_server_response(l) - else: + 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): + def handle_server_response(self, line): print("Server response: " + line) parts = line.split(':') action = parts[0] @@ -149,6 +171,10 @@ class SocketConnect(GObject.GObject): if not self.registered_paths: self.reconnect() + def _get_version(self, action, args): + if action == 'VERSION': + self.protocolVersion = args[1] + socketConnect = SocketConnect() @@ -178,29 +204,89 @@ class MenuExtension(GObject.GObject, Nautilus.MenuProvider): def get_file_items(self, window, files): # Show the menu extension to share a file or folder - # - # Show if file is OK. - # Ignore top level folders. - # Also show extension for folders - # if there is a OK or SYNC underneath. - # This is only - if len(files) != 1: - return - file = files[0] + # 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()) - filename = get_local_path(file.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 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 topLevelFolder or not internalFile: + # 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('GET_MENU_ITEMS:{}\n'.format(filesstring)) + + done = False + start = time.time() + timeout = 0.1 # 100ms + 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(): + # Process lines we don't care about + if done or not (line.startswith('GET_MENU_ITEMS:') or line.startswith('MENU_ITEM:')): + socketConnect.handle_server_response(line) + continue + if line == 'GET_MENU_ITEMS:END': + done = True + # don't break - we'd discard other responses + if line.startswith('MENU_ITEM:'): + args = line.split(':') + if len(args) < 4: + continue + menu_items.append([args[1], 'd' not in args[2], ':'.join(args[3:])]) + + if not done: + return self.legacy_menu_items(files) + + if len(menu_items) == 0: + 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) + + for action, enabled, label in menu_items: + item = Nautilus.MenuItem(name=action, label=label, sensitive=enabled) + item.connect("activate", self.context_menu_action, action, filesstring) + menu.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 [] @@ -235,7 +321,7 @@ class MenuExtension(GObject.GObject, Nautilus.MenuProvider): item = Nautilus.MenuItem( name='NautilusPython::ShareItem', label=self.strings.get('SHARE_MENU_TITLE', 'Share...')) - item.connect("activate", self.context_menu_action, 'SHARE', file) + item.connect("activate", self.context_menu_action, 'SHARE', filename) menu.append_item(item) # Add permalink menu options, but hide these options for older clients @@ -243,20 +329,19 @@ class MenuExtension(GObject.GObject, Nautilus.MenuProvider): 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', file) + 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', file) + 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, file): - filename = get_local_path(file.get_uri()) + def context_menu_action(self, menu, action, filename): print("Context menu: " + action + ' ' + filename) socketConnect.sendCommand(action + ":" + filename + "\n") diff --git a/shell_integration/windows/CMakeLists.txt b/shell_integration/windows/CMakeLists.txt new file mode 100644 index 000000000..b9c080b6e --- /dev/null +++ b/shell_integration/windows/CMakeLists.txt @@ -0,0 +1,4 @@ +add_subdirectory(OCContextMenu) +add_subdirectory(OCOverlays) +add_subdirectory(OCUtil) + diff --git a/shell_integration/windows/OCContextMenu/CMakeLists.txt b/shell_integration/windows/OCContextMenu/CMakeLists.txt new file mode 100644 index 000000000..badfbb638 --- /dev/null +++ b/shell_integration/windows/OCContextMenu/CMakeLists.txt @@ -0,0 +1,18 @@ +add_library(OCContextMenu MODULE + dllmain.cpp + OCClientInterface.cpp + OCContextMenu.cpp + OCContextMenuFactory.cpp + OCContextMenuRegHandler.cpp + stdafx.cpp + OCContextMenu.rc + OCContextMenu.def +) + +target_link_libraries(OCContextMenu + OCUtil) + +install(TARGETS OCContextMenu + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_BINDIR} +) diff --git a/shell_integration/windows/OCContextMenu/OCClientInterface.cpp b/shell_integration/windows/OCContextMenu/OCClientInterface.cpp index 6c6396fa5..019ca5c27 100644 --- a/shell_integration/windows/OCContextMenu/OCClientInterface.cpp +++ b/shell_integration/windows/OCContextMenu/OCClientInterface.cpp @@ -34,7 +34,7 @@ using namespace std; #define PIPE_TIMEOUT 5*1000 //ms #define SOCK_BUFFER 4096 -OCClientInterface::ContextMenuInfo OCClientInterface::FetchInfo() +OCClientInterface::ContextMenuInfo OCClientInterface::FetchInfo(const std::wstring &files) { auto pipename = CommunicationSocket::DefaultPipePath(); @@ -45,7 +45,8 @@ OCClientInterface::ContextMenuInfo OCClientInterface::FetchInfo() if (!socket.Connect(pipename)) { return {}; } - socket.SendMsg(L"GET_STRINGS\n"); + socket.SendMsg(L"GET_STRINGS:CONTEXT_MENU_TITLE\n"); + socket.SendMsg((L"GET_MENU_ITEMS:" + files + L"\n").data()); ContextMenuInfo info; std::wstring response; @@ -60,16 +61,14 @@ OCClientInterface::ContextMenuInfo OCClientInterface::FetchInfo() wstring stringName, stringValue; if (!StringUtil::extractChunks(response, stringName, stringValue)) continue; - if (stringName == L"SHARE_MENU_TITLE") - info.shareMenuTitle = move(stringValue); - else if (stringName == L"CONTEXT_MENU_TITLE") + if (stringName == L"CONTEXT_MENU_TITLE") info.contextMenuTitle = move(stringValue); - else if (stringName == L"COPY_PRIVATE_LINK_MENU_TITLE") - info.copyLinkMenuTitle = move(stringValue); - else if (stringName == L"EMAIL_PRIVATE_LINK_MENU_TITLE") - info.emailLinkMenuTitle = move(stringValue); - } - else if (StringUtil::begins_with(response, wstring(L"GET_STRINGS:END"))) { + } else if (StringUtil::begins_with(response, wstring(L"MENU_ITEM:"))) { + wstring 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 } } @@ -81,22 +80,7 @@ OCClientInterface::ContextMenuInfo OCClientInterface::FetchInfo() return info; } -void OCClientInterface::RequestShare(const std::wstring &path) -{ - SendRequest(L"SHARE", path); -} - -void OCClientInterface::RequestCopyLink(const std::wstring &path) -{ - SendRequest(L"COPY_PRIVATE_LINK", path); -} - -void OCClientInterface::RequestEmailLink(const std::wstring &path) -{ - SendRequest(L"EMAIL_PRIVATE_LINK", path); -} - -void OCClientInterface::SendRequest(wchar_t *verb, const std::wstring &path) +void OCClientInterface::SendRequest(const wchar_t *verb, const std::wstring &path) { auto pipename = CommunicationSocket::DefaultPipePath(); diff --git a/shell_integration/windows/OCContextMenu/OCClientInterface.h b/shell_integration/windows/OCContextMenu/OCClientInterface.h index 74e6364fe..586a03f2f 100644 --- a/shell_integration/windows/OCContextMenu/OCClientInterface.h +++ b/shell_integration/windows/OCContextMenu/OCClientInterface.h @@ -46,18 +46,14 @@ public: struct ContextMenuInfo { std::vector watchedDirectories; std::wstring contextMenuTitle; - std::wstring shareMenuTitle; - std::wstring copyLinkMenuTitle; - std::wstring emailLinkMenuTitle; + struct MenuItem + { + std::wstring command, flags, title; + }; + std::vector menuItems; }; - static ContextMenuInfo FetchInfo(); - - static void RequestShare(const std::wstring &path); - static void RequestCopyLink(const std::wstring &path); - static void RequestEmailLink(const std::wstring &path); - -private: - static void SendRequest(wchar_t *verb, const std::wstring &path); + static ContextMenuInfo FetchInfo(const std::wstring &files); + static void SendRequest(const wchar_t *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 bda86b86d..bd305da65 100644 --- a/shell_integration/windows/OCContextMenu/OCContextMenu.cpp +++ b/shell_integration/windows/OCContextMenu/OCContextMenu.cpp @@ -22,13 +22,8 @@ #include #include -extern HINSTANCE g_hInst; extern long g_cDllRef; -#define IDM_SHARE 0 -#define IDM_COPYLINK 1 -#define IDM_EMAILLINK 2 - OCContextMenu::OCContextMenu(void) : m_cRef(1) { @@ -40,23 +35,6 @@ OCContextMenu::~OCContextMenu(void) InterlockedDecrement(&g_cDllRef); } - -void OCContextMenu::OnVerbShare(HWND hWnd) -{ - OCClientInterface::RequestShare(std::wstring(m_szSelectedFile)); -} - -void OCContextMenu::OnVerbCopyLink(HWND hWnd) -{ - OCClientInterface::RequestCopyLink(std::wstring(m_szSelectedFile)); -} - -void OCContextMenu::OnVerbEmailLink(HWND hWnd) -{ - OCClientInterface::RequestEmailLink(std::wstring(m_szSelectedFile)); -} - - #pragma region IUnknown // Query to the interface the component supported. @@ -97,12 +75,12 @@ IFACEMETHODIMP_(ULONG) OCContextMenu::Release() IFACEMETHODIMP OCContextMenu::Initialize( LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDataObj, HKEY hKeyProgID) { + m_selectedFiles.clear(); + if (!pDataObj) { return E_INVALIDARG; } - HRESULT hr = E_FAIL; - FORMATETC fe = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; STGMEDIUM stm; @@ -110,14 +88,19 @@ IFACEMETHODIMP OCContextMenu::Initialize( // Get an HDROP handle. HDROP hDrop = static_cast(GlobalLock(stm.hGlobal)); if (hDrop) { - // Ignore multi-selections UINT nFiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0); - if (nFiles == 1) { + for (int i = 0; i < nFiles; ++i) { // Get the path of the file. - if (0 != DragQueryFile(hDrop, 0, m_szSelectedFile, ARRAYSIZE(m_szSelectedFile))) - { - hr = S_OK; + wchar_t buffer[MAX_PATH]; + + if (!DragQueryFile(hDrop, i, buffer, ARRAYSIZE(buffer))) { + m_selectedFiles.clear(); + break; } + + if (i) + m_selectedFiles += L'\x1e'; + m_selectedFiles += buffer; } GlobalUnlock(stm.hGlobal); @@ -128,7 +111,7 @@ IFACEMETHODIMP OCContextMenu::Initialize( // If any value other than S_OK is returned from the method, the context // menu item is not displayed. - return hr; + return m_selectedFiles.empty() ? E_FAIL : S_OK; } #pragma endregion @@ -153,17 +136,8 @@ IFACEMETHODIMP OCContextMenu::QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(0)); } - OCClientInterface::ContextMenuInfo info = OCClientInterface::FetchInfo(); - bool skip = true; - size_t selectedFileLength = wcslen(m_szSelectedFile); - for (const std::wstring path : info.watchedDirectories) { - if (StringUtil::isDescendantOf(m_szSelectedFile, selectedFileLength, path)) { - skip = false; - break; - } - } - - if (skip) { + m_info = OCClientInterface::FetchInfo(m_selectedFiles); + if (m_info.menuItems.empty()) { return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(0)); } @@ -175,7 +149,7 @@ IFACEMETHODIMP OCContextMenu::QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT mii.fMask = MIIM_SUBMENU | MIIM_FTYPE | MIIM_STRING; mii.hSubMenu = hSubmenu; mii.fType = MFT_STRING; - mii.dwTypeData = &info.contextMenuTitle[0]; + mii.dwTypeData = &m_info.contextMenuTitle[0]; if (!InsertMenuItem(hMenu, indexMenu++, TRUE, &mii)) return HRESULT_FROM_WIN32(GetLastError()); @@ -183,133 +157,59 @@ IFACEMETHODIMP OCContextMenu::QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT InsertSeperator(hMenu, indexMenu++); UINT indexSubMenu = 0; - { - assert(!info.shareMenuTitle.empty()); + for (auto &item : m_info.menuItems) { + bool disabled = item.flags.find(L'd') != std::string::npos; + MENUITEMINFO mii = { sizeof(mii) }; - mii.fMask = MIIM_ID | MIIM_FTYPE | MIIM_STRING; - mii.wID = idCmdFirst + IDM_SHARE; + mii.fMask = MIIM_ID | MIIM_FTYPE | MIIM_STRING | MIIM_STATE; + mii.wID = indexSubMenu; mii.fType = MFT_STRING; - mii.dwTypeData = &info.shareMenuTitle[0]; + mii.dwTypeData = &item.title[0]; + mii.fState = disabled ? MFS_DISABLED : MFS_ENABLED; - if (!InsertMenuItem(hSubmenu, indexSubMenu++, TRUE, &mii)) + if (!InsertMenuItem(hSubmenu, indexSubMenu, true, &mii)) return HRESULT_FROM_WIN32(GetLastError()); + indexSubMenu++; } - { - assert(!info.copyLinkMenuTitle.empty()); - MENUITEMINFO mii = { sizeof(mii) }; - mii.fMask = MIIM_ID | MIIM_FTYPE | MIIM_STRING; - mii.wID = idCmdFirst + IDM_COPYLINK; - mii.fType = MFT_STRING; - mii.dwTypeData = &info.copyLinkMenuTitle[0]; - - if (!InsertMenuItem(hSubmenu, indexSubMenu++, TRUE, &mii)) - return HRESULT_FROM_WIN32(GetLastError()); - } - { - assert(!info.emailLinkMenuTitle.empty()); - MENUITEMINFO mii = { sizeof(mii) }; - mii.fMask = MIIM_ID | MIIM_FTYPE | MIIM_STRING; - mii.wID = idCmdFirst + IDM_EMAILLINK; - mii.fType = MFT_STRING; - mii.dwTypeData = &info.emailLinkMenuTitle[0]; - - if (!InsertMenuItem(hSubmenu, indexSubMenu++, TRUE, &mii)) - return HRESULT_FROM_WIN32(GetLastError()); - } - // Return an HRESULT value with the severity set to SEVERITY_SUCCESS. // Set the code value to the offset of the largest command identifier // that was assigned, plus one (1). - return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(IDM_EMAILLINK + 1)); + return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(indexSubMenu)); } IFACEMETHODIMP OCContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO pici) { + std::wstring command; // For the Unicode case, if the high-order word is not zero, the // command's verb string is in lpcmi->lpVerbW. if (HIWORD(((CMINVOKECOMMANDINFOEX*)pici)->lpVerbW)) { - // Is the verb supported by this context menu extension? - if (StrCmpIW(((CMINVOKECOMMANDINFOEX*)pici)->lpVerbW, L"ocshare") == 0) { - OnVerbShare(pici->hwnd); - } - else if (StrCmpIW(((CMINVOKECOMMANDINFOEX*)pici)->lpVerbW, L"occopylink") == 0) { - OnVerbCopyLink(pici->hwnd); - } - else if (StrCmpIW(((CMINVOKECOMMANDINFOEX*)pici)->lpVerbW, L"ocemaillink") == 0) { - OnVerbEmailLink(pici->hwnd); - } - else { - // If the verb is not recognized by the context menu handler, it - // must return E_FAIL to allow it to be passed on to the other - // context menu handlers that might implement that verb. + command = ((CMINVOKECOMMANDINFOEX *)pici)->lpVerbW; + } else { + // If the command cannot be identified through the verb string, then + // check the identifier offset. + + auto offset = LOWORD(pici->lpVerb); + if (offset < m_info.menuItems.size()) return E_FAIL; - } - } - - // If the command cannot be identified through the verb string, then - // check the identifier offset. - else - { - // Is the command identifier offset supported by this context menu - // extension? - if (LOWORD(pici->lpVerb) == IDM_SHARE) { - OnVerbShare(pici->hwnd); - } - else if (LOWORD(pici->lpVerb) == IDM_COPYLINK) { - OnVerbCopyLink(pici->hwnd); - } - else if (LOWORD(pici->lpVerb) == IDM_EMAILLINK) { - OnVerbEmailLink(pici->hwnd); - } - else { - // If the verb is not recognized by the context menu handler, it - // must return E_FAIL to allow it to be passed on to the other - // context menu handlers that might implement that verb. - return E_FAIL; - } + + command = m_info.menuItems[offset].command; } + OCClientInterface::SendRequest(command.data(), m_selectedFiles); return S_OK; } IFACEMETHODIMP OCContextMenu::GetCommandString(UINT_PTR idCommand, UINT uFlags, UINT *pwReserved, LPSTR pszName, UINT cchMax) { - HRESULT hr = E_INVALIDARG; - - switch (idCommand) { - case IDM_SHARE: - if (uFlags == GCS_VERBW) { - // GCS_VERBW is an optional feature that enables a caller to - // discover the canonical name for the verb passed in through - // idCommand. - hr = StringCchCopy(reinterpret_cast(pszName), cchMax, - L"OCShareViaOC"); - } - break; - case IDM_COPYLINK: - if (uFlags == GCS_VERBW) { - hr = StringCchCopy(reinterpret_cast(pszName), cchMax, - L"OCCopyLink"); - } - break; - case IDM_EMAILLINK: - if (uFlags == GCS_VERBW) { - hr = StringCchCopy(reinterpret_cast(pszName), cchMax, - L"OCEmailLink"); - } - break; - default: - break; + if (idCommand < m_info.menuItems.size() && uFlags == GCS_VERBW) { + return StringCchCopyW(reinterpret_cast(pszName), cchMax, + m_info.menuItems[idCommand].command.data()); } - - // If the idCommand or uFlags is not supported by this context menu - // extension handler, return E_INVALIDARG. - - return hr; + return E_INVALIDARG; } -#pragma endregion \ No newline at end of file +#pragma endregion diff --git a/shell_integration/windows/OCContextMenu/OCContextMenu.h b/shell_integration/windows/OCContextMenu/OCContextMenu.h index 7fe3e2ade..e8836d6fe 100644 --- a/shell_integration/windows/OCContextMenu/OCContextMenu.h +++ b/shell_integration/windows/OCContextMenu/OCContextMenu.h @@ -17,6 +17,8 @@ #pragma once #include // For IShellExtInit and IContextMenu +#include +#include "OCClientInterface.h" class OCContextMenu : public IShellExtInit, public IContextMenu { @@ -43,21 +45,9 @@ private: // Reference count of component. long m_cRef; - // The name of the selected file. - wchar_t m_szSelectedFile[MAX_PATH]; - - // The method that handles the "ocshare" verb. - void OnVerbShare(HWND hWnd); - void OnVerbCopyLink(HWND hWnd); - void OnVerbEmailLink(HWND hWnd); - - PWSTR m_pszMenuText; - PCSTR m_pszVerb; - PCWSTR m_pwszVerb; - PCSTR m_pszVerbCanonicalName; - PCWSTR m_pwszVerbCanonicalName; - PCSTR m_pszVerbHelpText; - PCWSTR m_pwszVerbHelpText; + // The name of the selected files (separated by '\x1e') + std::wstring m_selectedFiles; + OCClientInterface::ContextMenuInfo m_info; }; #endif //OCCONTEXTMENU_H diff --git a/shell_integration/windows/OCContextMenu/OCContextMenu.rc b/shell_integration/windows/OCContextMenu/OCContextMenu.rc index 5025d081f..ab3041ee1 100644 Binary files a/shell_integration/windows/OCContextMenu/OCContextMenu.rc and b/shell_integration/windows/OCContextMenu/OCContextMenu.rc differ diff --git a/shell_integration/windows/OCOverlays/CMakeLists.txt b/shell_integration/windows/OCOverlays/CMakeLists.txt new file mode 100644 index 000000000..13526b749 --- /dev/null +++ b/shell_integration/windows/OCOverlays/CMakeLists.txt @@ -0,0 +1,17 @@ +add_library(OCOverlays MODULE + DllMain.cpp + OCOverlay.cpp + OCOverlayFactory.cpp + OCOverlayRegistrationHandler.cpp + stdafx.cpp + OCOverlay.rc + OCOverlays.def +) + +target_link_libraries(OCOverlays + OCUtil) + +install(TARGETS OCOverlays + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_BINDIR} +) diff --git a/shell_integration/windows/OCOverlays/OCOverlay.rc b/shell_integration/windows/OCOverlays/OCOverlay.rc index 7e937bd90..fb298e242 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/OCUtil/CMakeLists.txt b/shell_integration/windows/OCUtil/CMakeLists.txt new file mode 100644 index 000000000..718a6c327 --- /dev/null +++ b/shell_integration/windows/OCUtil/CMakeLists.txt @@ -0,0 +1,17 @@ +add_library(OCUtil SHARED + CommunicationSocket.cpp + FileUtil.cpp + RegistryUtil.cpp + RemotePathChecker.cpp + stdafx.cpp + StringUtil.cpp +) + +target_include_directories(OCUtil + PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}" +) + +install(TARGETS OCUtil + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) diff --git a/shell_integration/windows/OCUtil/OCMessage.cpp b/shell_integration/windows/OCUtil/OCMessage.cpp deleted file mode 100644 index 31da93180..000000000 --- a/shell_integration/windows/OCUtil/OCMessage.cpp +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Copyright (c) 2000-2013 Liferay, Inc. 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. - */ - -#include "OCMessage.h" -#include "ParserUtil.h" -#include "UtilConstants.h" - -#include - -#include -#include - -using namespace std; - -OCMessage::OCMessage(void) -{ - _command = new wstring(); - _value = new wstring(); -} - -OCMessage::~OCMessage(void) -{ -} - -bool OCMessage::InitFromMessage(const wstring* message) -{ - if(message->length() == 0) - { - return false; - } - - if(!ParserUtil::GetItem(COMMAND, message, _command)) - { - return false; - } - - if(!ParserUtil::GetItem(VALUE, message, _value)) - { - return false; - } - - return true; -} - -std::wstring* OCMessage::GetCommand() -{ - return _command; -} - -std::wstring* OCMessage::GetValue() -{ - return _value; -} - -void OCMessage::SetCommand(std::wstring* command) -{ - _command = command; -} - -void OCMessage::SetValue(std::wstring* value) -{ - _value = value; -} diff --git a/shell_integration/windows/OCUtil/OCMessage.h b/shell_integration/windows/OCUtil/OCMessage.h deleted file mode 100644 index 7b0eefca3..000000000 --- a/shell_integration/windows/OCUtil/OCMessage.h +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright (c) 2000-2013 Liferay, Inc. 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. - */ - -#ifndef OCMESSAGE_H -#define OCMESSAGE_H - -#include - -#pragma once - -class __declspec(dllexport) OCMessage -{ -public: - OCMessage(void); - ~OCMessage(void); - - bool InitFromMessage(const std::wstring*); - - std::wstring* GetCommand(); - std::wstring* GetValue(); - - void SetCommand(std::wstring*); - void SetValue(std::wstring*); - -private: - - std::wstring* _command; - std::wstring* _value; -}; - -#endif \ No newline at end of file diff --git a/shell_integration/windows/OCUtil/ParserUtil.cpp b/shell_integration/windows/OCUtil/ParserUtil.cpp deleted file mode 100644 index 5ca05f670..000000000 --- a/shell_integration/windows/OCUtil/ParserUtil.cpp +++ /dev/null @@ -1,389 +0,0 @@ -/** - * Copyright (c) 2000-2013 Liferay, Inc. 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. - */ - -#include "ParserUtil.h" -#include "UtilConstants.h" - -#include - -using namespace std; - -bool ParserUtil::GetItem(const wchar_t* item, const wstring* message, wstring* result) -{ - size_t start = message->find(item, 0); - - if(start == string::npos) - { - return false; - } - - size_t end = message->find(COLON, start); - - if(end == string::npos) - { - return false; - } - - //Move to next character after : - end += 1; - - wchar_t c = message->at(end); - - //Move to the next character, which is the start of the value - end += 1; - - if(c == '[') - { - return GetList(end - 1, message, result); - } - else - { - return GetValue(end, message, result); - } -} - -bool ParserUtil::GetList(size_t start, const wstring* message, wstring* result) -{ - size_t end = start + 1; - - int openBraceCount = 1; - - while(openBraceCount > 0) - { - size_t closeBraceLocation = message->find(CLOSE_BRACE, end); - size_t openBraceLocation = message->find(OPEN_BRACE, end); - - if(closeBraceLocation < openBraceLocation) - { - openBraceCount--; - end = closeBraceLocation + 1; - } - else if(openBraceLocation < closeBraceLocation) - { - openBraceCount++; - end = openBraceLocation + 1; - } - - } - - size_t length = end - start; - - return GetString(start, end, message, result); -} - -size_t ParserUtil::GetNextStringItemInList(const wstring* message, size_t start, wstring* result) -{ - size_t end = string::npos; - size_t commaLocation = message->find(COMMA, start); - - if(commaLocation == string::npos) - { - end = message->find(CLOSE_BRACE, start); - if(end == string::npos) - { - end = message->length(); - } - else - { - end = end - 1; - } - } - else - { - end = commaLocation - 1; - } - - if(!GetString(start + 2, end, message, result)) - { - return string::npos; - } - - return end + 2; -} - -size_t ParserUtil::GetNextOCItemInList(const wstring* message, size_t start, wstring* result) -{ - size_t end = message->find(OPEN_CURLY_BRACE, start) + 1; - - int openBraceCount = 1; - - while(openBraceCount > 0) - { - size_t closeBraceLocation = message->find(CLOSE_CURLY_BRACE, end); - size_t openBraceLocation = message->find(OPEN_CURLY_BRACE, end); - - if(closeBraceLocation < openBraceLocation) - { - openBraceCount--; - end = closeBraceLocation + 1; - } - else if(openBraceLocation < closeBraceLocation) - { - openBraceCount++; - end = openBraceLocation + 1; - } - } - - size_t length = end - start; - - if(!GetString(start, end, message, result)) - { - return string::npos; - } - - return end; -} - -bool ParserUtil::GetValue(size_t start, const wstring* message, wstring* result) -{ - if(message->at(start - 1) == '\"') - { - size_t end = message->find(QUOTE, start); - return GetString(start, end, message, result); - } - else - { - start = start - 1; - - size_t end = message->find(COMMA, start); - - result->append(message->substr(start, end-start)); - } - - return true; -} - -bool ParserUtil::GetString(size_t start, size_t end, const wstring* message, wstring* result) -{ - if(end == string::npos) - { - return false; - } - - size_t length = end - start; - - if(length > 0) - { - result->append(message->substr(start, length)); - } - else - { - result->append(L""); - } - - - return true; -} - -bool ParserUtil::IsList(wstring* message) -{ - wchar_t c = message->at(0); - - if(c == '[') - { - return true; - } - - return false; -} - -bool ParserUtil::ParseJsonList(wstring* message, vector* items) -{ - - size_t currentLocation = message->find(OPEN_BRACE, 0); - - while(currentLocation < message->size()) - { - wstring* item = new wstring(); - - currentLocation = ParserUtil::GetNextStringItemInList(message, currentLocation, item); - - if(currentLocation == string::npos) - { - return false; - } - - items->push_back(item); - } - - return true; -} - -bool ParserUtil::ParseOCList(wstring* message, vector* items) -{ - - size_t currentLocation = message->find(OPEN_CURLY_BRACE, 0); - - while(currentLocation < message->size()) - { - wstring* item = new wstring(); - - currentLocation = ParserUtil::GetNextOCItemInList(message, currentLocation, item); - - if(currentLocation == string::npos) - { - return false; - } - - items->push_back(item); - } - - return true; -} - -bool ParserUtil::ParseOCMessageList(wstring* message, vector* messages) -{ - vector* items = new vector(); - - if(!ParseOCList(message, items)) - { - return false; - } - - for(vector::iterator it = items->begin(); it != items->end(); it++) - { - wstring* temp = *it; - - OCMessage* message = new OCMessage(); - message->InitFromMessage(temp); - - messages->push_back(message); - } - - return true; -} - -bool ParserUtil::SerializeList(std::vector* list, std::wstring* result, bool escapeQuotes) -{ - if(result == 0) - { - return false; - } - - result->append(OPEN_BRACE); - - for(vector::iterator it = list->begin(); it != list->end(); it++) - { - wstring value = *it; - - if(escapeQuotes) - { - result->append(BACK_SLASH); - } - - result->append(QUOTE); - result->append(value.c_str()); - - if(escapeQuotes) - { - result->append(BACK_SLASH); - } - - result->append(QUOTE); - result->append(COMMA); - } - - //Erase last comma - result->erase(result->size() - 1, 1); - - result->append(CLOSE_BRACE); - - return true; -} - -bool ParserUtil::SerializeMessage(std::map* arguments, std::wstring* result, bool escapeQuotes) -{ - if(result == 0) - { - return false; - } - - result->append(OPEN_CURLY_BRACE); - - for(map::iterator it = arguments->begin(); it != arguments->end(); it++) - { - wstring key = *it->first; - wstring value = *it->second; - - if(escapeQuotes) - { - result->append(BACK_SLASH); - } - - result->append(QUOTE); - result->append(key.c_str()); - - if(escapeQuotes) - { - result->append(BACK_SLASH); - } - - result->append(QUOTE); - result->append(COLON); - result->append(value.c_str()); - result->append(COMMA); - } - - //Erase last comma - result->erase(result->size() - 1, 1); - - result->append(CLOSE_CURLY_BRACE); - - return true; -} - -bool ParserUtil::SerializeMessage(OCMessage* OCMessage, std::wstring* result) -{ - if(result == 0) - { - return false; - } - - result->append(OPEN_CURLY_BRACE); - - result->append(QUOTE); - result->append(COMMAND); - result->append(QUOTE); - - result->append(COLON); - - result->append(QUOTE); - result->append(OCMessage->GetCommand()->c_str()); - result->append(QUOTE); - - result->append(COMMA); - - result->append(QUOTE); - result->append(VALUE); - result->append(QUOTE); - - result->append(COLON); - - if(!IsList(OCMessage->GetValue())) - { - result->append(QUOTE); - } - - result->append(OCMessage->GetValue()->c_str()); - - if(!IsList(OCMessage->GetValue())) - { - result->append(QUOTE); - } - - result->append(CLOSE_CURLY_BRACE); - - return true; -} - diff --git a/shell_integration/windows/OCUtil/StringUtil.h b/shell_integration/windows/OCUtil/StringUtil.h index 8ec325aa9..c0cef5851 100644 --- a/shell_integration/windows/OCUtil/StringUtil.h +++ b/shell_integration/windows/OCUtil/StringUtil.h @@ -61,6 +61,30 @@ public: thirdChunk = source.substr(statusEnd + 1); return true; } + + static bool extractChunks(const std::wstring &source, std::wstring &secondChunk, std::wstring &thirdChunk, std::wstring &forthChunk) + { + auto statusBegin = source.find(L':', 0); + assert(statusBegin != std::wstring::npos); + + auto statusEnd = source.find(L':', statusBegin + 1); + if (statusEnd == std::wstring::npos) { + // the command do not contains two colon? + return false; + } + + auto thirdColon = source.find(L':', statusEnd + 1); + if (statusEnd == std::wstring::npos) { + // the command do not contains three colon? + return false; + } + + // Assume the caller extracted the chunk before the first colon. + secondChunk = source.substr(statusBegin + 1, statusEnd - statusBegin - 1); + thirdChunk = source.substr(statusEnd + 1, thirdColon - statusEnd - 1); + forthChunk = source.substr(thirdColon + 1); + return true; + } }; #endif // STRINGUTIL_H diff --git a/src/3rdparty/libcrashreporter-qt b/src/3rdparty/libcrashreporter-qt index e8fffe61e..7df66f72a 160000 --- a/src/3rdparty/libcrashreporter-qt +++ b/src/3rdparty/libcrashreporter-qt @@ -1 +1 @@ -Subproject commit e8fffe61e7c94ce88e59b80579754c4a46da65ea +Subproject commit 7df66f72aac595295dffcf4dc8a536822008c51d diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 172422988..ac60b1b66 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,12 +4,17 @@ endif() set(synclib_NAME ${APPLICATION_EXECUTABLE}sync) +find_package(Qt5 5.6 COMPONENTS Core Network Xml Concurrent REQUIRED) +if (Qt5Core_VERSION VERSION_LESS 5.9.0) +message(STATUS "For HTTP/2 support, compile with Qt 5.9 or higher.") +endif() + if(NOT TOKEN_AUTH_ONLY) find_package(Qt5Keychain REQUIRED) endif() if(NOT MSVC) - if(NOT (CMAKE_SYSTEM_PROCESSOR MATCHES "^(parisc|hppa)")) + if(NOT (CMAKE_SYSTEM_PROCESSOR MATCHES "^(alpha|parisc|hppa)")) if((CMAKE_CXX_COMPILER_ID MATCHES "GNU") AND (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9)) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fstack-protector --param=ssp-buffer-size=4") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fstack-protector --param=ssp-buffer-size=4") diff --git a/src/cmd/CMakeLists.txt b/src/cmd/CMakeLists.txt index 8a62f0779..3b4100c9a 100644 --- a/src/cmd/CMakeLists.txt +++ b/src/cmd/CMakeLists.txt @@ -7,17 +7,7 @@ set(cmd_SRC simplesslerrorhandler.cpp netrcparser.cpp ) -include_directories(${CMAKE_SOURCE_DIR}/src/libsync - ${CMAKE_BINARY_DIR}/src/libsync - ) -# csync is required. -include_directories(${CMAKE_SOURCE_DIR}/src/csync - ${CMAKE_BINARY_DIR}/src/csync - ) - -# Need tokenizer for netrc parser -include_directories(${CMAKE_SOURCE_DIR}/src/3rdparty/qtokenizer) if(UNIX AND NOT APPLE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIE") @@ -31,13 +21,15 @@ endif() if(NOT BUILD_LIBRARIES_ONLY) add_executable(${cmd_NAME} ${cmd_SRC}) - qt5_use_modules(${cmd_NAME} Network ) set_target_properties(${cmd_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${BIN_OUTPUT_DIRECTORY} ) set_target_properties(${cmd_NAME} PROPERTIES INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${LIB_INSTALL_DIR}/${APPLICATION_EXECUTABLE};${CMAKE_INSTALL_RPATH}" ) - target_link_libraries(${cmd_NAME} ${synclib_NAME}) + target_link_libraries(${cmd_NAME} ocsync ${synclib_NAME} Qt5::Core Qt5::Network) + + # Need tokenizer for netrc parser + target_include_directories(${cmd_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/src/3rdparty/qtokenizer) endif() if(BUILD_OWNCLOUD_OSX_BUNDLE) @@ -49,7 +41,6 @@ elseif(NOT BUILD_LIBRARIES_ONLY) ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) endif() - # FIXME: The following lines are dup in src/gui and src/cmd because it needs to be done after both are installed #FIXME: find a nice solution to make the second if(BUILD_OWNCLOUD_OSX_BUNDLE) unnecessary # currently it needs to be done because the code right above needs to be executed no matter diff --git a/src/cmd/cmd.cpp b/src/cmd/cmd.cpp index 7544ae5cc..2abed85d2 100644 --- a/src/cmd/cmd.cpp +++ b/src/cmd/cmd.cpp @@ -331,7 +331,6 @@ int main(int argc, char **argv) parseOptions(app.arguments(), &options); - csync_set_log_level(options.silent ? 1 : 11); if (options.silent) { qInstallMessageHandler(nullMessageHandler); } else { @@ -420,6 +419,30 @@ int main(int argc, char **argv) folder.chop(1); } + if (!options.proxy.isNull()) { + QString host; + int port = 0; + bool ok; + + QStringList pList = 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\"."); + } + } else { + clientProxy.setupQtProxyFromConfig(); + } + SimpleSslErrorHandler *sslErrorHandler = new SimpleSslErrorHandler; HttpCredentialsText *cred = new HttpCredentialsText(user, password); @@ -457,36 +480,8 @@ int main(int argc, char **argv) int restartCount = 0; restart_sync: - opts = &options; - if (!options.proxy.isNull()) { - QString host; - int port = 0; - bool ok; - - QStringList pList = 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 { - clientProxy.setupQtProxyFromConfig(); - QString url(options.target_url); - if (url.startsWith("owncloud")) { - url.remove(0, 8); - url = QString("http%1").arg(url); - } - } - QStringList selectiveSyncList; if (!options.unsyncedfolders.isEmpty()) { QFile f(options.unsyncedfolders); diff --git a/src/common/c_jhash.h b/src/common/c_jhash.h index 699244a0d..cd1549d7e 100644 --- a/src/common/c_jhash.h +++ b/src/common/c_jhash.h @@ -207,6 +207,7 @@ static inline uint64_t c_jhash64(const uint8_t *k, uint64_t length, uint64_t int /* handle the last 23 bytes */ c += length; switch(len) { +#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" case 23: c+=((uint64_t)k[22]<<56); case 22: c+=((uint64_t)k[21]<<48); case 21: c+=((uint64_t)k[20]<<40); diff --git a/src/common/filesystembase.cpp b/src/common/filesystembase.cpp index 1d95e3918..8de47312e 100644 --- a/src/common/filesystembase.cpp +++ b/src/common/filesystembase.cpp @@ -403,27 +403,6 @@ QByteArray FileSystem::calcAdler32(const QString &filename) } #endif -QString FileSystem::makeConflictFileName(const QString &fn, const QDateTime &dt) -{ - QString conflictFileName(fn); - // Add _conflict-XXXX before the extension. - int dotLocation = conflictFileName.lastIndexOf('.'); - // If no extension, add it at the end (take care of cases like foo/.hidden or foo.bar/file) - if (dotLocation <= conflictFileName.lastIndexOf('/') + 1) { - dotLocation = conflictFileName.size(); - } - QString timeString = dt.toString("yyyyMMdd-hhmmss"); - - // Additional marker - QByteArray conflictFileUserName = qgetenv("CSYNC_CONFLICT_FILE_USERNAME"); - if (conflictFileUserName.isEmpty()) - conflictFileName.insert(dotLocation, "_conflict-" + timeString); - else - conflictFileName.insert(dotLocation, "_conflict_" + QString::fromUtf8(conflictFileUserName) + "-" + timeString); - - return conflictFileName; -} - bool FileSystem::remove(const QString &fileName, QString *errorString) { #ifdef Q_OS_WIN diff --git a/src/common/filesystembase.h b/src/common/filesystembase.h index 9568dac12..e7c7672b1 100644 --- a/src/common/filesystembase.h +++ b/src/common/filesystembase.h @@ -131,11 +131,6 @@ namespace FileSystem { QByteArray OCSYNC_EXPORT calcAdler32(const QString &fileName); #endif - /** - * Returns a file name based on \a fn that's suitable for a conflict. - */ - QString OCSYNC_EXPORT makeConflictFileName(const QString &fn, const QDateTime &dt); - /** * Returns true when a file is locked. (Windows only) */ diff --git a/src/common/ownsql.cpp b/src/common/ownsql.cpp index c4277a130..bcd9dd6b0 100644 --- a/src/common/ownsql.cpp +++ b/src/common/ownsql.cpp @@ -26,6 +26,7 @@ #include "ownsql.h" #include "common/utility.h" #include "common/asserts.h" +#include #define SQLITE_SLEEP_TIME_USEC 100000 #define SQLITE_REPEAT_COUNT 20 diff --git a/src/common/ownsql.h b/src/common/ownsql.h index bfff4b65d..1ad7e93b7 100644 --- a/src/common/ownsql.h +++ b/src/common/ownsql.h @@ -19,13 +19,14 @@ #ifndef OWNSQL_H #define OWNSQL_H -#include - #include #include #include "ocsynclib.h" +struct sqlite3; +struct sqlite3_stmt; + namespace OCC { /** diff --git a/src/common/syncjournaldb.cpp b/src/common/syncjournaldb.cpp index 3710f36d3..63d2f7ef4 100644 --- a/src/common/syncjournaldb.cpp +++ b/src/common/syncjournaldb.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include "common/syncjournaldb.h" #include "version.h" @@ -47,7 +48,7 @@ static void fillFileRecordFromGetQuery(SyncJournalFileRecord &rec, SqlQuery &que rec._path = query.baValue(0); rec._inode = query.int64Value(1); rec._modtime = query.int64Value(2); - rec._type = query.intValue(3); + rec._type = static_cast(query.intValue(3)); rec._etag = query.baValue(4); rec._fileId = query.baValue(5); rec._remotePerm = RemotePermissions(query.baValue(6).constData()); @@ -389,6 +390,7 @@ bool SyncJournalDb::checkConnect() "errorcount INTEGER," "size INTEGER(8)," "modtime INTEGER(8)," + "contentChecksum TEXT," "PRIMARY KEY(path)" ");"); @@ -437,7 +439,7 @@ bool SyncJournalDb::checkConnect() return sqlFail("Create table version", createQuery); } - // create the checksumtype table. + // create the datafingerprint table. createQuery.prepare("CREATE TABLE IF NOT EXISTS datafingerprint(" "fingerprint TEXT UNIQUE" ");"); @@ -445,6 +447,17 @@ bool SyncJournalDb::checkConnect() return sqlFail("Create table datafingerprint", createQuery); } + // create the conflicts table. + createQuery.prepare("CREATE TABLE IF NOT EXISTS conflicts(" + "path TEXT PRIMARY KEY," + "baseFileId TEXT," + "baseEtag TEXT," + "baseModtime INTEGER" + ");"); + if (!createQuery.exec()) { + return sqlFail("Create table conflicts", createQuery); + } + createQuery.prepare("CREATE TABLE IF NOT EXISTS version(" "major INTEGER(8)," "minor INTEGER(8)," @@ -611,15 +624,15 @@ bool SyncJournalDb::checkConnect() } _getUploadInfoQuery.reset(new SqlQuery(_db)); - if (_getUploadInfoQuery->prepare("SELECT chunk, transferid, errorcount, size, modtime FROM " + if (_getUploadInfoQuery->prepare("SELECT chunk, transferid, errorcount, size, modtime, contentChecksum FROM " "uploadinfo WHERE path=?1")) { return sqlFail("prepare _getUploadInfoQuery", *_getUploadInfoQuery); } _setUploadInfoQuery.reset(new SqlQuery(_db)); if (_setUploadInfoQuery->prepare("INSERT OR REPLACE INTO uploadinfo " - "(path, chunk, transferid, errorcount, size, modtime) " - "VALUES ( ?1 , ?2, ?3 , ?4 , ?5, ?6 )")) { + "(path, chunk, transferid, errorcount, size, modtime, contentChecksum) " + "VALUES ( ?1 , ?2, ?3 , ?4 , ?5, ?6 , ?7 )")) { return sqlFail("prepare _setUploadInfoQuery", *_setUploadInfoQuery); } @@ -692,6 +705,23 @@ bool SyncJournalDb::checkConnect() return sqlFail("prepare _setDataFingerprintQuery2", *_setDataFingerprintQuery2); } + _getConflictRecordQuery.reset(new SqlQuery(_db)); + if (_getConflictRecordQuery->prepare("SELECT baseFileId, baseModtime, baseEtag FROM conflicts WHERE path=?1;")) { + return sqlFail("prepare _getConflictRecordQuery", *_getConflictRecordQuery); + } + + _setConflictRecordQuery.reset(new SqlQuery(_db)); + if (_setConflictRecordQuery->prepare("INSERT OR REPLACE INTO conflicts " + "(path, baseFileId, baseModtime, baseEtag) " + "VALUES (?1, ?2, ?3, ?4);")) { + return sqlFail("prepare _setConflictRecordQuery", *_setConflictRecordQuery); + } + + _deleteConflictRecordQuery.reset(new SqlQuery(_db)); + if (_deleteConflictRecordQuery->prepare("DELETE FROM conflicts WHERE path=?1;")) { + return sqlFail("prepare _deleteConflictRecordQuery", *_deleteConflictRecordQuery); + } + // don't start a new transaction now commitInternal(QString("checkConnect End"), false); @@ -740,6 +770,9 @@ void SyncJournalDb::close() _getDataFingerprintQuery.reset(0); _setDataFingerprintQuery1.reset(0); _setDataFingerprintQuery2.reset(0); + _getConflictRecordQuery.reset(0); + _setConflictRecordQuery.reset(0); + _deleteConflictRecordQuery.reset(0); _db.close(); _avoidReadFromDbOnNextSyncFilter.clear(); @@ -849,6 +882,16 @@ bool SyncJournalDb::updateMetadataTableStructure() commitInternal("update database structure: add contentChecksumTypeId col"); } + if (!tableColumns("uploadinfo").contains("contentChecksum")) { + SqlQuery query(_db); + query.prepare("ALTER TABLE uploadinfo ADD COLUMN contentChecksum TEXT;"); + if (!query.exec()) { + sqlFail("updateMetadataTableStructure: add contentChecksum column", query); + re = false; + } + commitInternal("update database structure: add contentChecksum col for uploadinfo"); + } + return re; } @@ -1472,6 +1515,7 @@ SyncJournalDb::UploadInfo SyncJournalDb::getUploadInfo(const QString &file) res._errorCount = _getUploadInfoQuery->intValue(2); res._size = _getUploadInfoQuery->int64Value(3); res._modtime = _getUploadInfoQuery->int64Value(4); + res._contentChecksum = _getUploadInfoQuery->baValue(5); res._valid = ok; } } @@ -1494,6 +1538,7 @@ void SyncJournalDb::setUploadInfo(const QString &file, const SyncJournalDb::Uplo _setUploadInfoQuery->bindValue(4, i._errorCount); _setUploadInfoQuery->bindValue(5, i._size); _setUploadInfoQuery->bindValue(6, i._modtime); + _setUploadInfoQuery->bindValue(7, i._contentChecksum); if (!_setUploadInfoQuery->exec()) { return; @@ -1827,7 +1872,7 @@ void SyncJournalDb::avoidReadFromDbOnNextSync(const QByteArray &fileName) SqlQuery query(_db); // This query will match entries for which the path is a prefix of fileName - // Note: CSYNC_FTW_TYPE_DIR == 2 + // Note: ItemTypeDirectory == 2 query.prepare("UPDATE metadata SET md5='_invalid_' WHERE ?1 LIKE(path||'/%') AND type == 2;"); query.bindValue(1, fileName); query.exec(); @@ -1938,6 +1983,72 @@ void SyncJournalDb::setDataFingerprint(const QByteArray &dataFingerprint) _setDataFingerprintQuery2->exec(); } +void SyncJournalDb::setConflictRecord(const ConflictRecord &record) +{ + QMutexLocker locker(&_mutex); + if (!checkConnect()) + return; + + auto &query = *_setConflictRecordQuery; + query.reset_and_clear_bindings(); + query.bindValue(1, record.path); + query.bindValue(2, record.baseFileId); + query.bindValue(3, record.baseModtime); + query.bindValue(4, record.baseEtag); + ASSERT(query.exec()); +} + +ConflictRecord SyncJournalDb::conflictRecord(const QByteArray &path) +{ + ConflictRecord entry; + + QMutexLocker locker(&_mutex); + if (!checkConnect()) + return entry; + + auto &query = *_getConflictRecordQuery; + query.reset_and_clear_bindings(); + query.bindValue(1, path); + ASSERT(query.exec()); + if (!query.next()) + return entry; + + entry.path = path; + entry.baseFileId = query.baValue(0); + entry.baseModtime = query.int64Value(1); + entry.baseEtag = query.baValue(2); + return entry; +} + +void SyncJournalDb::deleteConflictRecord(const QByteArray &path) +{ + QMutexLocker locker(&_mutex); + if (!checkConnect()) + return; + + auto &query = *_deleteConflictRecordQuery; + query.reset_and_clear_bindings(); + query.bindValue(1, path); + ASSERT(query.exec()); +} + +QByteArrayList SyncJournalDb::conflictRecordPaths() +{ + QMutexLocker locker(&_mutex); + if (!checkConnect()) + return {}; + + SqlQuery query(_db); + query.prepare("SELECT path FROM conflicts"); + ASSERT(query.exec()); + + QByteArrayList paths; + while (query.next()) + paths.append(query.baValue(0)); + + return paths; +} + void SyncJournalDb::clearFileTable() { QMutexLocker lock(&_mutex); @@ -2001,7 +2112,8 @@ bool operator==(const SyncJournalDb::UploadInfo &lhs, && lhs._modtime == rhs._modtime && lhs._valid == rhs._valid && lhs._size == rhs._size - && lhs._transferid == rhs._transferid; + && lhs._transferid == rhs._transferid + && lhs._contentChecksum == rhs._contentChecksum; } } // namespace OCC diff --git a/src/common/syncjournaldb.h b/src/common/syncjournaldb.h index ad61a6ad2..aa30d349c 100644 --- a/src/common/syncjournaldb.h +++ b/src/common/syncjournaldb.h @@ -112,6 +112,7 @@ public: qint64 _modtime; int _errorCount; bool _valid; + QByteArray _contentChecksum; }; struct PollInfo @@ -206,6 +207,22 @@ public: void setDataFingerprint(const QByteArray &dataFingerprint); QByteArray dataFingerprint(); + + // Conflict record functions + + /// Store a new or updated record in the database + void setConflictRecord(const ConflictRecord &record); + + /// Retrieve a conflict record by path of the _conflict- file + ConflictRecord conflictRecord(const QByteArray &path); + + /// Delete a conflict record by path of the _conflict- file + void deleteConflictRecord(const QByteArray &path); + + /// Return all paths of _conflict- files with records in the db + QByteArrayList conflictRecordPaths(); + + /** * Delete any file entry. This will force the next sync to re-sync everything as if it was new, * restoring everyfile on every remote. If a file is there both on the client and server side, @@ -265,6 +282,9 @@ private: QScopedPointer _getDataFingerprintQuery; QScopedPointer _setDataFingerprintQuery1; QScopedPointer _setDataFingerprintQuery2; + QScopedPointer _getConflictRecordQuery; + QScopedPointer _setConflictRecordQuery; + QScopedPointer _deleteConflictRecordQuery; /* This is the list of paths we called avoidReadFromDbOnNextSync on. * It means that they should not be written to the DB in any case since doing diff --git a/src/common/syncjournalfilerecord.cpp b/src/common/syncjournalfilerecord.cpp index 226c25d6c..607d2e6db 100644 --- a/src/common/syncjournalfilerecord.cpp +++ b/src/common/syncjournalfilerecord.cpp @@ -23,7 +23,7 @@ namespace OCC { SyncJournalFileRecord::SyncJournalFileRecord() : _inode(0) - , _type(0) + , _type(ItemTypeSkip) , _fileSize(0) , _serverHasIgnoredFiles(false) { diff --git a/src/common/syncjournalfilerecord.h b/src/common/syncjournalfilerecord.h index bc34ef135..b09006365 100644 --- a/src/common/syncjournalfilerecord.h +++ b/src/common/syncjournalfilerecord.h @@ -22,6 +22,7 @@ #include #include +#include "csync.h" #include "ocsynclib.h" #include "remotepermissions.h" #include "common/utility.h" @@ -56,7 +57,7 @@ public: QByteArray _path; quint64 _inode; qint64 _modtime; - int _type; + ItemType _type; QByteArray _etag; QByteArray _fileId; qint64 _fileSize; @@ -110,6 +111,43 @@ public: bool isValid() const; }; + +/** Represents a conflict in the conflicts table. + * + * In the following the "conflict file" is the file with the "_conflict-" + * tag and the base file is the file that its a conflict for. So if + * a/foo.txt is the base file, its conflict file could be + * a/foo_conflict-1234.txt. + */ +class OCSYNC_EXPORT ConflictRecord +{ +public: + /** Path to the _conflict- file + * + * So if a/foo.txt has a conflict, this path would point to + * a/foo_conflict-1234.txt. + * + * The path is sync-folder relative. + */ + QByteArray path; + + /// File id of the base file + QByteArray baseFileId; + + /** Modtime of the base file + * + * may not be available and be -1 + */ + qint64 baseModtime = -1; + + /** Etag of the base file + * + * may not be available and empty + */ + QByteArray baseEtag; + + bool isValid() const { return !path.isEmpty(); } +}; } #endif // SYNCJOURNALFILERECORD_H diff --git a/src/common/utility.cpp b/src/common/utility.cpp index bdf603515..29308ea84 100644 --- a/src/common/utility.cpp +++ b/src/common/utility.cpp @@ -542,6 +542,21 @@ QUrl Utility::concatUrlPath(const QUrl &url, const QString &concatPath, return tmpUrl; } +QString Utility::makeConflictFileName(const QString &fn, const QDateTime &dt) +{ + QString conflictFileName(fn); + // Add _conflict-XXXX before the extension. + int dotLocation = conflictFileName.lastIndexOf('.'); + // If no extension, add it at the end (take care of cases like foo/.hidden or foo.bar/file) + if (dotLocation <= conflictFileName.lastIndexOf('/') + 1) { + dotLocation = conflictFileName.size(); + } + QString timeString = dt.toString("yyyyMMdd-hhmmss"); + + conflictFileName.insert(dotLocation, "_conflict-" + timeString); + return conflictFileName; +} + bool Utility::isConflictFile(const char *name) { const char *bname = std::strrchr(name, '/'); @@ -551,32 +566,33 @@ bool Utility::isConflictFile(const char *name) bname = name; } - if (std::strstr(bname, "_conflict-")) - return true; - - if (shouldUploadConflictFiles()) { - // For uploads, we want to consider files with any kind of username tag - // as conflict files. (pattern *_conflict_*-) - const char *startOfMarker = std::strstr(bname, "_conflict_"); - if (startOfMarker && std::strchr(startOfMarker, '-')) - return true; - } else { - // Old behavior: optionally, files with the specific string in the env variable - // appended are also considered conflict files. - static auto conflictFileUsername = qgetenv("CSYNC_CONFLICT_FILE_USERNAME"); - static auto usernameConflictId = QByteArray("_conflict_" + conflictFileUsername + "-"); - if (!conflictFileUsername.isEmpty() && std::strstr(bname, usernameConflictId.constData())) { - return true; - } - } - - return false; + return std::strstr(bname, "_conflict-"); } -bool Utility::shouldUploadConflictFiles() +bool Utility::isConflictFile(const QString &name) { - static bool uploadConflictFiles = qEnvironmentVariableIntValue("OWNCLOUD_UPLOAD_CONFLICT_FILES") != 0; - return uploadConflictFiles; + auto bname = name.midRef(name.lastIndexOf('/') + 1); + return bname.contains("_conflict-", Utility::fsCasePreserving() ? Qt::CaseInsensitive : Qt::CaseSensitive); +} + +QByteArray Utility::conflictFileBaseName(const QByteArray &conflictName) +{ + // This function must be able to deal with conflict files for conflict files. + // To do this, we scan backwards, for the outermost conflict marker and + // strip only that to generate the conflict file base name. + int from = conflictName.size(); + while (from != -1) { + auto start = conflictName.lastIndexOf("_conflict-", from); + if (start == -1) + return ""; + from = start - 1; + + auto end = conflictName.indexOf('.', start); + if (end == -1) + end = conflictName.size(); + return conflictName.left(start) + conflictName.mid(end); + } + return ""; } } // namespace OCC diff --git a/src/common/utility.h b/src/common/utility.h index 666e44cfd..7247673ce 100644 --- a/src/common/utility.h +++ b/src/common/utility.h @@ -182,17 +182,23 @@ namespace Utility { 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 = 0); + /** Returns a file name based on \a fn that's suitable for a conflict. + */ + OCSYNC_EXPORT QString makeConflictFileName(const QString &fn, const QDateTime &dt); + /** Returns whether a file name indicates a conflict file - * - * See FileSystem::makeConflictFileName. */ OCSYNC_EXPORT bool isConflictFile(const char *name); + OCSYNC_EXPORT bool isConflictFile(const QString &name); - /** Returns whether conflict files should be uploaded. + /** Find the base name for a conflict file name * - * Experimental! Real feature planned for 2.5. + * Will return an empty string if it's not a conflict file. + * + * Prefer to use the data from the conflicts table in the journal to determine + * a conflict's base file. */ - OCSYNC_EXPORT bool shouldUploadConflictFiles(); + OCSYNC_EXPORT QByteArray conflictFileBaseName(const QByteArray &conflictName); #ifdef Q_OS_WIN OCSYNC_EXPORT QVariant registryGetKeyValue(HKEY hRootKey, const QString &subKey, const QString &valueName); diff --git a/src/common/utility_unix.cpp b/src/common/utility_unix.cpp index 2ccc88b9c..fa11aa67f 100644 --- a/src/common/utility_unix.cpp +++ b/src/common/utility_unix.cpp @@ -57,7 +57,7 @@ void setLaunchOnStartup_private(const QString &appName, const QString &guiName, QString desktopFileLocation = userAutoStartPath + appName + QLatin1String(".desktop"); if (enable) { if (!QDir().exists(userAutoStartPath) && !QDir().mkpath(userAutoStartPath)) { - qCWarning(lcUtility) << "Could not create autostart folder"; + qCWarning(lcUtility) << "Could not create autostart folder" << userAutoStartPath; return; } QFile iniFile(desktopFileLocation); diff --git a/src/crashreporter/CMakeLists.txt b/src/crashreporter/CMakeLists.txt index b73e38fab..693fd9efd 100644 --- a/src/crashreporter/CMakeLists.txt +++ b/src/crashreporter/CMakeLists.txt @@ -4,10 +4,6 @@ cmake_policy(SET CMP0017 NEW) list(APPEND crashreporter_SOURCES main.cpp) list(APPEND crashreporter_RC resources.qrc) -qt_wrap_ui( crashreporter_UI_HEADERS ${crashreporter_UI} ) -qt_add_resources( crashreporter_RC_RCC ${crashreporter_RC} ) - - # TODO: differentiate release channel # if(BUILD_RELEASE) # set(CRASHREPORTER_RELEASE_CHANNEL "release") @@ -19,10 +15,6 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/CrashReporterConfig.h.in ${CMAKE_CURRENT_BINARY_DIR}/CrashReporterConfig.h) -include_directories(${CMAKE_CURRENT_BINARY_DIR} - "../3rdparty/libcrashreporter-qt/src/" -) - if(NOT BUILD_LIBRARIES_ONLY) add_executable( ${CRASHREPORTER_EXECUTABLE} WIN32 @@ -32,14 +24,15 @@ if(NOT BUILD_LIBRARIES_ONLY) ${crashreporter_RC_RCC} ) - qt5_use_modules(${CRASHREPORTER_EXECUTABLE} Widgets Network) + find_package(Qt5 REQUIRED COMPONENTS Widgets) + target_include_directories(${CRASHREPORTER_EXECUTABLE} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) set_target_properties(${CRASHREPORTER_EXECUTABLE} PROPERTIES AUTOMOC ON) set_target_properties(${CRASHREPORTER_EXECUTABLE} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${BIN_OUTPUT_DIRECTORY} ) set_target_properties(${CRASHREPORTER_EXECUTABLE} PROPERTIES INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${LIB_INSTALL_DIR}/${APPLICATION_EXECUTABLE}" ) target_link_libraries(${CRASHREPORTER_EXECUTABLE} crashreporter-gui - ${QT_LIBRARIES} + Qt5::Core Qt5::Widgets ) if(BUILD_OWNCLOUD_OSX_BUNDLE) diff --git a/src/csync/CMakeLists.txt b/src/csync/CMakeLists.txt index 701405431..26d66e655 100644 --- a/src/csync/CMakeLists.txt +++ b/src/csync/CMakeLists.txt @@ -14,49 +14,15 @@ include(DefineOptions.cmake) include(DefineInstallationPaths) -# add macros -include(MacroAddPlugin) -include(MacroCopyFile) - find_package(SQLite3 3.8.0 REQUIRED) include(ConfigureChecks.cmake) include(../common/common.cmake) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) - if (MEM_NULL_TESTS) add_definitions(-DCSYNC_MEM_NULL_TESTS) endif (MEM_NULL_TESTS) -add_subdirectory(std) - -# Statically include sqlite - -set(CSYNC_PUBLIC_INCLUDE_DIRS - ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_SOURCE_DIR} - CACHE INTERNAL "csync public include directories" -) - -set(CSYNC_PRIVATE_INCLUDE_DIRS - ${SQLITE3_INCLUDE_DIRS} - ${CSTDLIB_PUBLIC_INCLUDE_DIRS} - ${CMAKE_BINARY_DIR} -) - -set(CSYNC_LIBRARY - ocsync - CACHE INTERNAL "ocsync library" -) - -set(CSYNC_LINK_LIBRARIES - ${CSTDLIB_LIBRARY} - ${CSYNC_REQUIRED_LIBRARIES} - ${SQLITE3_LIBRARIES} -) - # Specific option for builds tied to servers that do not support renaming extensions set(NO_RENAME_EXTENSION 0 CACHE BOOL "Do not issue rename if the extension changes") if(NO_RENAME_EXTENSION) @@ -66,8 +32,6 @@ endif() set(csync_SRCS csync.cpp csync_exclude.cpp - csync_log.cpp - csync_time.c csync_util.cpp csync_misc.cpp @@ -77,6 +41,12 @@ set(csync_SRCS csync_rename.cpp vio/csync_vio.cpp + + std/c_alloc.c + std/c_string.c + std/c_time.c + std/c_utf8.cpp + ) if (WIN32) @@ -89,52 +59,48 @@ else() ) endif() +if(NOT HAVE_ASPRINTF AND NOT HAVE___MINGW_ASPRINTF) + list(APPEND csync_SRCS std/asprintf.c) +endif() + +if (USE_OUR_OWN_SQLITE3) + list(APPEND csync_SRCS ${SQLITE3_SOURCE}) +endif() configure_file(csync_version.h.in ${CMAKE_CURRENT_BINARY_DIR}/csync_version.h) -set(csync_HDRS - ${CMAKE_CURRENT_BINARY_DIR}/csync_version.h - csync.h - vio/csync_vio.h - vio/csync_vio_method.h - vio/csync_vio_module.h +set(CSYNC_LIBRARY ocsync) +add_library(${CSYNC_LIBRARY} SHARED ${common_SOURCES} ${csync_SRCS}) + +target_include_directories( + ${CSYNC_LIBRARY} + PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/std ) -# Statically include sqlite +find_package(SQLite3 3.8.0 REQUIRED) if (USE_OUR_OWN_SQLITE3) - list(APPEND csync_SRCS ${SQLITE3_SOURCE}) - if (WIN32) - # We want to export sqlite symbols from the ocsync DLL without - # having to patch both sqlite3.h and the amalgation sqlite3.c, - # so do the import/export magic manually through the build system. - remove_definitions(-DSQLITE_API=__declspec\(dllimport\)) - add_definitions(-DSQLITE_API=__declspec\(dllexport\)) - endif() + # make sure that the path for the local sqlite3 is before the system one + target_include_directories(${CSYNC_LIBRARY} BEFORE PRIVATE ${SQLITE3_INCLUDE_DIR}) +else() + target_include_directories(${CSYNC_LIBRARY} PRIVATE ${SQLITE3_INCLUDE_DIR}) endif() -include_directories( - ${CSYNC_PUBLIC_INCLUDE_DIRS} - ${CSYNC_PRIVATE_INCLUDE_DIRS} -) -add_library(${CSYNC_LIBRARY} SHARED ${common_SOURCES} ${csync_SRCS}) -#add_library(${CSYNC_LIBRARY}_static STATIC ${csync_SRCS}) - -generate_export_header( ${CSYNC_LIBRARY} +generate_export_header(${CSYNC_LIBRARY} EXPORT_MACRO_NAME OCSYNC_EXPORT EXPORT_FILE_NAME ocsynclib.h ) -target_link_libraries(${CSYNC_LIBRARY} ${CSYNC_LINK_LIBRARIES}) -#target_link_libraries(${CSYNC_LIBRARY}_static ${CSYNC_LINK_LIBRARIES}) +target_link_libraries(${CSYNC_LIBRARY} + ${CSYNC_REQUIRED_LIBRARIES} + ${SQLITE3_LIBRARIES} + Qt5::Core Qt5::Concurrent +) if(ZLIB_FOUND) - target_link_libraries(${CSYNC_LIBRARY} ${ZLIB_LIBRARIES}) - include_directories(${ZLIB_INCLUDE_DIRS}) + target_link_libraries(${CSYNC_LIBRARY} ZLIB::ZLIB) endif(ZLIB_FOUND) -find_package(Qt5Core REQUIRED) -qt5_use_modules(${CSYNC_LIBRARY} Core Concurrent) # For src/common/utility_mac.cpp if (APPLE) @@ -169,11 +135,11 @@ else() TARGETS ${CSYNC_LIBRARY} LIBRARY DESTINATION - ${LIB_INSTALL_DIR}/${APPLICATION_EXECUTABLE} + ${CMAKE_INSTALL_LIBDIR}/${APPLICATION_EXECUTABLE} ARCHIVE DESTINATION - ${LIB_INSTALL_DIR}/${APPLICATION_EXECUTABLE} + ${CMAKE_INSTALL_LIBDIR}/${APPLICATION_EXECUTABLE} RUNTIME DESTINATION - ${BIN_INSTALL_DIR}/${APPLICATION_EXECUTABLE} + ${CMAKE_INSTALL_BINDIR}/${APPLICATION_EXECUTABLE} ) endif() diff --git a/src/csync/ConfigureChecks.cmake b/src/csync/ConfigureChecks.cmake index 6d64d5a63..e035e2ebb 100644 --- a/src/csync/ConfigureChecks.cmake +++ b/src/csync/ConfigureChecks.cmake @@ -25,16 +25,6 @@ if (NOT LINUX) set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} ) endif (NOT LINUX) -check_library_exists(rt clock_gettime "" HAVE_CLOCK_GETTIME) -if (HAVE_LIBRT OR HAVE_CLOCK_GETTIME) - set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} rt) -endif (HAVE_LIBRT OR HAVE_CLOCK_GETTIME) - -check_library_exists(dl dlopen "" HAVE_LIBDL) -if (HAVE_LIBDL) - set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} dl) -endif (HAVE_LIBDL) - check_function_exists(asprintf HAVE_ASPRINTF) check_function_exists(fnmatch HAVE_FNMATCH) diff --git a/src/csync/config_csync.h.cmake b/src/csync/config_csync.h.cmake index f5c9f09b2..d1a64cb95 100644 --- a/src/csync/config_csync.h.cmake +++ b/src/csync/config_csync.h.cmake @@ -6,10 +6,6 @@ #cmakedefine BINARYDIR "${BINARYDIR}" #cmakedefine SOURCEDIR "${SOURCEDIR}" -#cmakedefine HAVE_CLOCK_GETTIME - -#cmakedefine WITH_LOG4C 1 - #cmakedefine HAVE_ARGP_H 1 #cmakedefine HAVE_TIMEGM 1 diff --git a/src/csync/csync.cpp b/src/csync/csync.cpp index f80d4d4d2..992e60f21 100644 --- a/src/csync/csync.cpp +++ b/src/csync/csync.cpp @@ -36,7 +36,6 @@ #include "c_lib.h" #include "csync_private.h" #include "csync_exclude.h" -#include "csync_time.h" #include "csync_util.h" #include "csync_misc.h" #include "std/c_private.h" @@ -46,9 +45,11 @@ #include "vio/csync_vio.h" -#include "csync_log.h" #include "csync_rename.h" #include "common/c_jhash.h" +#include "common/syncjournalfilerecord.h" + +Q_LOGGING_CATEGORY(lcCSync, "sync.csync.csync", QtInfoMsg) csync_s::csync_s(const char *localUri, OCC::SyncJournalDb *statedb) @@ -65,7 +66,6 @@ csync_s::csync_s(const char *localUri, OCC::SyncJournalDb *statedb) int csync_update(CSYNC *ctx) { int rc = -1; - struct timespec start, finish; if (ctx == NULL) { errno = EBADF; @@ -78,13 +78,16 @@ int csync_update(CSYNC *ctx) { csync_memstat_check(); if (!ctx->exclude_traversal_fn) { - CSYNC_LOG(CSYNC_LOG_PRIORITY_INFO, "No exclude file loaded or defined!"); + qCInfo(lcCSync, "No exclude file loaded or defined!"); } /* update detection for local replica */ - csync_gettime(&start); + QElapsedTimer timer; + timer.start(); ctx->current = LOCAL_REPLICA; + qCInfo(lcCSync, "## Starting local discovery ##"); + rc = csync_ftw(ctx, ctx->local.uri, csync_walker, MAX_DEPTH); if (rc < 0) { if(ctx->status_code == CSYNC_STATUS_OK) { @@ -93,17 +96,16 @@ int csync_update(CSYNC *ctx) { return rc; } - csync_gettime(&finish); - - CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, - "Update detection for local replica took %.2f seconds walking %zu files.", - c_secdiff(finish, start), ctx->local.files.size()); + qCInfo(lcCSync) << "Update detection for local replica took" << timer.elapsed() / 1000. + << "seconds walking" << ctx->local.files.size() << "files"; csync_memstat_check(); /* update detection for remote replica */ - csync_gettime(&start); + timer.restart(); ctx->current = REMOTE_REPLICA; + qCInfo(lcCSync, "## Starting remote discovery ##"); + rc = csync_ftw(ctx, "", csync_walker, MAX_DEPTH); if (rc < 0) { if(ctx->status_code == CSYNC_STATUS_OK) { @@ -112,12 +114,9 @@ int csync_update(CSYNC *ctx) { return rc; } - csync_gettime(&finish); - CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, - "Update detection for remote replica took %.2f seconds " - "walking %zu files.", - c_secdiff(finish, start), ctx->remote.files.size()); + qCInfo(lcCSync) << "Update detection for remote replica took" << timer.elapsed() / 1000. + << "seconds walking" << ctx->remote.files.size() << "files"; csync_memstat_check(); ctx->status |= CSYNC_STATUS_UPDATE; @@ -128,7 +127,6 @@ int csync_update(CSYNC *ctx) { int csync_reconcile(CSYNC *ctx) { int rc = -1; - struct timespec start, finish; if (ctx == NULL) { errno = EBADF; @@ -137,17 +135,15 @@ int csync_reconcile(CSYNC *ctx) { ctx->status_code = CSYNC_STATUS_OK; /* Reconciliation for local replica */ - csync_gettime(&start); + QElapsedTimer timer; + timer.start(); ctx->current = LOCAL_REPLICA; rc = csync_reconcile_updates(ctx); - csync_gettime(&finish); - - CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, - "Reconciliation for local replica took %.2f seconds visiting %zu files.", - c_secdiff(finish, start), ctx->local.files.size()); + qCInfo(lcCSync) << "Reconciliation for local replica took " << timer.elapsed() / 1000. + << "seconds visiting " << ctx->local.files.size() << " files."; if (rc < 0) { if (!CSYNC_STATUS_IS_OK(ctx->status_code)) { @@ -157,17 +153,14 @@ int csync_reconcile(CSYNC *ctx) { } /* Reconciliation for remote replica */ - csync_gettime(&start); + timer.restart(); ctx->current = REMOTE_REPLICA; rc = csync_reconcile_updates(ctx); - csync_gettime(&finish); - - CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, - "Reconciliation for remote replica took %.2f seconds visiting %zu files.", - c_secdiff(finish, start), ctx->remote.files.size()); + qCInfo(lcCSync) << "Reconciliation for remote replica took " << timer.elapsed() / 1000. + << "seconds visiting " << ctx->remote.files.size() << " files."; if (rc < 0) { if (!CSYNC_STATUS_IS_OK(ctx->status_code)) { @@ -402,3 +395,19 @@ int csync_abort_requested(CSYNC *ctx) return (1 == 0); } } + +std::unique_ptr csync_file_stat_s::fromSyncJournalFileRecord(const OCC::SyncJournalFileRecord &rec) +{ + std::unique_ptr st(new csync_file_stat_t); + st->path = rec._path; + st->inode = rec._inode; + st->modtime = rec._modtime; + st->type = static_cast(rec._type); + st->etag = rec._etag; + st->file_id = rec._fileId; + st->remotePerm = rec._remotePerm; + st->size = rec._fileSize; + st->has_ignored_files = rec._serverHasIgnoredFiles; + st->checksumHeader = rec._checksumHeader; + return st; +} diff --git a/src/csync/csync.h b/src/csync/csync.h index 8c59f1707..e913a726a 100644 --- a/src/csync/csync.h +++ b/src/csync/csync.h @@ -34,7 +34,6 @@ #include "std/c_private.h" #include "ocsynclib.h" -#include "common/syncjournalfilerecord.h" #include #include @@ -45,9 +44,16 @@ #include #include "common/remotepermissions.h" +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 @@ -102,7 +108,6 @@ enum csync_status_codes_e { CSYNC_STATUS_INDIVIDUAL_IS_INVALID_CHARS, CSYNC_STATUS_INDIVIDUAL_TRAILING_SPACE, CSYNC_STATUS_INDIVIDUAL_EXCLUDE_LONG_FILENAME, - CYSNC_STATUS_FILE_LOCKED_OR_OPEN, CSYNC_STATUS_INDIVIDUAL_EXCLUDE_HIDDEN, CSYNC_STATUS_INVALID_CHARACTERS, CSYNC_STATUS_INDIVIDUAL_STAT_FAILED, @@ -148,11 +153,13 @@ enum csync_instructions_e { but without any propagation (UPDATE|RECONCILE) */ }; -enum csync_ftw_type_e { - CSYNC_FTW_TYPE_FILE, - CSYNC_FTW_TYPE_SLINK, - CSYNC_FTW_TYPE_DIR, - CSYNC_FTW_TYPE_SKIP +// 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 { + ItemTypeFile = 0, + ItemTypeSoftLink = 1, + ItemTypeDirectory = 2, + ItemTypeSkip = 3 }; @@ -169,7 +176,7 @@ struct OCSYNC_EXPORT csync_file_stat_s { uint64_t inode; OCC::RemotePermissions remotePerm; - enum csync_ftw_type_e type BITFIELD(4); + 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. @@ -196,7 +203,7 @@ struct OCSYNC_EXPORT csync_file_stat_s { : modtime(0) , size(0) , inode(0) - , type(CSYNC_FTW_TYPE_SKIP) + , type(ItemTypeSkip) , child_modified(false) , has_ignored_files(false) , is_hidden(false) @@ -204,21 +211,7 @@ struct OCSYNC_EXPORT csync_file_stat_s { , instruction(CSYNC_INSTRUCTION_NONE) { } - static std::unique_ptr fromSyncJournalFileRecord(const OCC::SyncJournalFileRecord &rec) - { - std::unique_ptr st(new csync_file_stat_t); - st->path = rec._path; - st->inode = rec._inode; - st->modtime = rec._modtime; - st->type = static_cast(rec._type); - st->etag = rec._etag; - st->file_id = rec._fileId; - st->remotePerm = rec._remotePerm; - st->size = rec._fileSize; - st->has_ignored_files = rec._serverHasIgnoredFiles; - st->checksumHeader = rec._checksumHeader; - return st; - } + static std::unique_ptr fromSyncJournalFileRecord(const OCC::SyncJournalFileRecord &rec); }; /** @@ -229,10 +222,6 @@ typedef struct csync_s CSYNC; typedef int (*csync_auth_callback) (const char *prompt, char *buf, size_t len, int echo, int verify, void *userdata); -typedef void (*csync_log_callback) (int verbosity, - const char *function, - const char *buffer); - typedef void (*csync_update_callback) (bool local, const char *dirUrl, void *userdata); @@ -310,39 +299,6 @@ csync_auth_callback OCSYNC_EXPORT csync_get_auth_callback(CSYNC *ctx); */ int OCSYNC_EXPORT csync_set_auth_callback(CSYNC *ctx, csync_auth_callback cb); -/** - * @brief Set the log level. - * - * @param[in] level The log verbosity. - * - * @return 0 on success, < 0 if an error occurred. - */ -int OCSYNC_EXPORT csync_set_log_level(int level); - -/** - * @brief Get the log verbosity - * - * @return The log verbosity, -1 on error. - */ -int OCSYNC_EXPORT csync_get_log_level(void); - -/** - * @brief Get the logging callback set. - * - * @return The logging callback set or NULL if an error - * occurred. - */ -csync_log_callback OCSYNC_EXPORT csync_get_log_callback(void); - -/** - * @brief Set the logging callback. - * - * @param cb The logging callback. - * - * @return 0 on success, less than 0 if an error occurred. - */ -int OCSYNC_EXPORT csync_set_log_callback(csync_log_callback cb); - /* Used for special modes or debugging */ CSYNC_STATUS OCSYNC_EXPORT csync_get_status(CSYNC *ctx); diff --git a/src/csync/csync_exclude.cpp b/src/csync/csync_exclude.cpp index c090b1f1a..c52b1ac91 100644 --- a/src/csync/csync_exclude.cpp +++ b/src/csync/csync_exclude.cpp @@ -23,11 +23,6 @@ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif -#include - -#include -#include -#include #include "c_lib.h" #include "c_private.h" @@ -42,125 +37,43 @@ #include #include -#ifdef _WIN32 -#include -#else -#include -#endif -#define CSYNC_LOG_CATEGORY_NAME "csync.exclude" -#include "csync_log.h" - -/** Expands C-like escape sequences. - * - * The returned string is heap-allocated and owned by the caller. +/** Expands C-like escape sequences (in place) */ -static const char *csync_exclude_expand_escapes(const char * input) +static void csync_exclude_expand_escapes(QByteArray &input) { - size_t i_len = strlen(input) + 1; - char *out = (char*)c_malloc(i_len); // out can only be shorter - - size_t i = 0; size_t o = 0; - for (; i < i_len; ++i) { - if (input[i] == '\\') { + char *line = input.data(); + auto len = input.size(); + for (int i = 0; i < len; ++i) { + if (line[i] == '\\') { // at worst input[i+1] is \0 - switch (input[i+1]) { - case '\'': out[o++] = '\''; break; - case '"': out[o++] = '"'; break; - case '?': out[o++] = '?'; break; - case '#': out[o++] = '#'; break; - case 'a': out[o++] = '\a'; break; - case 'b': out[o++] = '\b'; break; - case 'f': out[o++] = '\f'; break; - case 'n': out[o++] = '\n'; break; - case 'r': out[o++] = '\r'; break; - case 't': out[o++] = '\t'; break; - case 'v': out[o++] = '\v'; break; + switch (line[i+1]) { + case '\'': line[o++] = '\''; break; + case '"': line[o++] = '"'; break; + case '?': line[o++] = '?'; break; + case '#': line[o++] = '#'; break; + case 'a': line[o++] = '\a'; break; + case 'b': line[o++] = '\b'; break; + case 'f': line[o++] = '\f'; break; + case 'n': line[o++] = '\n'; break; + case 'r': line[o++] = '\r'; break; + case 't': line[o++] = '\t'; break; + case 'v': line[o++] = '\v'; break; default: // '\*' '\?' '\[' '\\' will be processed during regex translation // '\\' is intentionally not expanded here (to avoid '\\*' and '\*' // ending up meaning the same thing) - out[o++] = input[i]; - out[o++] = input[i+1]; + line[o++] = line[i]; + line[o++] = line[i + 1]; break; } ++i; } else { - out[o++] = input[i]; + line[o++] = line[i]; } } - return out; -} - -/** Loads patterns from a file and adds them to excludes */ -int csync_exclude_load(const char *fname, QList *excludes) { - int fd = -1; - int i = 0; - int rc = -1; - int64_t size; - char *buf = NULL; - char *entry = NULL; - mbchar_t *w_fname; - - if (fname == NULL) { - return -1; - } - -#ifdef _WIN32 - _fmode = _O_BINARY; -#endif - - w_fname = c_utf8_path_to_locale(fname); - if (w_fname == NULL) { - return -1; - } - - fd = _topen(w_fname, O_RDONLY); - c_free_locale_string(w_fname); - if (fd < 0) { - return -1; - } - - size = lseek(fd, 0, SEEK_END); - if (size < 0) { - rc = -1; - goto out; - } - lseek(fd, 0, SEEK_SET); - if (size == 0) { - rc = 0; - goto out; - } - buf = (char*)c_malloc(size + 1); - if (read(fd, buf, size) != size) { - rc = -1; - goto out; - } - buf[size] = '\0'; - - /* FIXME: Use fgets and don't add duplicates */ - entry = buf; - for (i = 0; i < size; i++) { - if (buf[i] == '\n' || buf[i] == '\r') { - if (entry != buf + i) { - buf[i] = '\0'; - if (*entry != '#') { - const char *unescaped = csync_exclude_expand_escapes(entry); - excludes->append(unescaped); - CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "Adding entry: %s", unescaped); - SAFE_FREE(unescaped); - } - } - entry = buf + i + 1; - } - } - - rc = 0; -out: - SAFE_FREE(buf); - close(fd); - return rc; + input.resize(o); } // See http://support.microsoft.com/kb/74496 and @@ -218,7 +131,7 @@ bool csync_is_windows_reserved_word(const char *filename) return false; } -static CSYNC_EXCLUDE_TYPE _csync_excluded_common(const char *path) +static CSYNC_EXCLUDE_TYPE _csync_excluded_common(const char *path, bool excludeConflictFiles) { const char *bname = NULL; size_t blen = 0; @@ -313,11 +226,9 @@ static CSYNC_EXCLUDE_TYPE _csync_excluded_common(const char *path) } } - if (!OCC::Utility::shouldUploadConflictFiles()) { - if (OCC::Utility::isConflictFile(bname)) { - match = CSYNC_FILE_EXCLUDE_CONFLICT; - goto out; - } + if (excludeConflictFiles && OCC::Utility::isConflictFile(bname)) { + match = CSYNC_FILE_EXCLUDE_CONFLICT; + goto out; } out: @@ -330,6 +241,8 @@ using namespace OCC; ExcludedFiles::ExcludedFiles() { + // Windows used to use PathMatchSpec which allows *foo to match abc/deffoo. + _wildcardsMatchSlash = Utility::isWindows(); } ExcludedFiles::~ExcludedFiles() @@ -341,6 +254,11 @@ void ExcludedFiles::addExcludeFilePath(const QString &path) _excludeFiles.insert(path); } +void ExcludedFiles::setExcludeConflictFiles(bool onoff) +{ + _excludeConflictFiles = onoff; +} + void ExcludedFiles::addManualExclude(const QByteArray &expr) { _manualExcludes.append(expr); @@ -354,13 +272,29 @@ void ExcludedFiles::clearManualExcludes() reloadExcludeFiles(); } +void ExcludedFiles::setWildcardsMatchSlash(bool onoff) +{ + _wildcardsMatchSlash = onoff; + prepare(); +} + bool ExcludedFiles::reloadExcludeFiles() { _allExcludes.clear(); bool success = true; foreach (const QString &file, _excludeFiles) { - if (csync_exclude_load(file.toUtf8(), &_allExcludes) < 0) + QFile f(file); + if (!f.open(QIODevice::ReadOnly)) { success = false; + continue; + } + while (!f.atEnd()) { + QByteArray line = f.readLine().trimmed(); + if (line.isEmpty() || line.startsWith('#')) + continue; + csync_exclude_expand_escapes(line); + _allExcludes.append(line); + } } _allExcludes.append(_manualExcludes); prepare(); @@ -393,9 +327,9 @@ bool ExcludedFiles::isExcluded( } QFileInfo fi(filePath); - csync_ftw_type_e type = CSYNC_FTW_TYPE_FILE; + ItemType type = ItemTypeFile; if (fi.isDir()) { - type = CSYNC_FTW_TYPE_DIR; + type = ItemTypeDirectory; } QString relativePath = filePath.mid(basePath.size()); @@ -406,9 +340,9 @@ bool ExcludedFiles::isExcluded( return fullPatternMatch(relativePath.toUtf8(), type) != CSYNC_NOT_EXCLUDED; } -CSYNC_EXCLUDE_TYPE ExcludedFiles::traversalPatternMatch(const char *path, int filetype) const +CSYNC_EXCLUDE_TYPE ExcludedFiles::traversalPatternMatch(const char *path, ItemType filetype) const { - auto match = _csync_excluded_common(path); + auto match = _csync_excluded_common(path, _excludeConflictFiles); if (match != CSYNC_NOT_EXCLUDED) return match; if (_allExcludes.isEmpty()) @@ -426,35 +360,40 @@ CSYNC_EXCLUDE_TYPE ExcludedFiles::traversalPatternMatch(const char *path, int fi QString bnameStr = QString::fromUtf8(bname); QRegularExpressionMatch m; - if (filetype == CSYNC_FTW_TYPE_DIR) { - m = _bnameActivationRegexDir.match(bnameStr); + if (filetype == ItemTypeDirectory) { + m = _bnameTraversalRegexDir.match(bnameStr); } else { - m = _bnameActivationRegexFile.match(bnameStr); + m = _bnameTraversalRegexFile.match(bnameStr); } if (!m.hasMatch()) - return match; - - // Now run the full match + return CSYNC_NOT_EXCLUDED; + if (m.capturedStart(QStringLiteral("exclude")) != -1) { + return CSYNC_FILE_EXCLUDE_LIST; + } else if (m.capturedStart(QStringLiteral("excluderemove")) != -1) { + return CSYNC_FILE_EXCLUDE_AND_REMOVE; + } + // third capture: full path matching is triggered QString pathStr = QString::fromUtf8(path); - if (filetype == CSYNC_FTW_TYPE_DIR) { - m = _fullRegexDir.match(pathStr); + + if (filetype == ItemTypeDirectory) { + m = _fullTraversalRegexDir.match(pathStr); } else { - m = _fullRegexFile.match(pathStr); + m = _fullTraversalRegexFile.match(pathStr); } if (m.hasMatch()) { - if (!m.captured(1).isEmpty()) { - match = CSYNC_FILE_EXCLUDE_LIST; - } else if (!m.captured(2).isEmpty()) { - match = CSYNC_FILE_EXCLUDE_AND_REMOVE; + if (m.capturedStart(QStringLiteral("exclude")) != -1) { + return CSYNC_FILE_EXCLUDE_LIST; + } else if (m.capturedStart(QStringLiteral("excluderemove")) != -1) { + return CSYNC_FILE_EXCLUDE_AND_REMOVE; } } - return match; + return CSYNC_NOT_EXCLUDED; } -CSYNC_EXCLUDE_TYPE ExcludedFiles::fullPatternMatch(const char *path, int filetype) const +CSYNC_EXCLUDE_TYPE ExcludedFiles::fullPatternMatch(const char *path, ItemType filetype) const { - auto match = _csync_excluded_common(path); + auto match = _csync_excluded_common(path, _excludeConflictFiles); if (match != CSYNC_NOT_EXCLUDED) return match; if (_allExcludes.isEmpty()) @@ -462,15 +401,15 @@ CSYNC_EXCLUDE_TYPE ExcludedFiles::fullPatternMatch(const char *path, int filetyp QString p = QString::fromUtf8(path); QRegularExpressionMatch m; - if (filetype == CSYNC_FTW_TYPE_DIR) { + if (filetype == ItemTypeDirectory) { m = _fullRegexDir.match(p); } else { m = _fullRegexFile.match(p); } if (m.hasMatch()) { - if (!m.captured(1).isEmpty()) { + if (m.capturedStart(QStringLiteral("exclude")) != -1) { return CSYNC_FILE_EXCLUDE_LIST; - } else if (!m.captured(2).isEmpty()) { + } else if (m.capturedStart(QStringLiteral("excluderemove")) != -1) { return CSYNC_FILE_EXCLUDE_AND_REMOVE; } } @@ -478,12 +417,17 @@ CSYNC_EXCLUDE_TYPE ExcludedFiles::fullPatternMatch(const char *path, int filetyp } auto ExcludedFiles::csyncTraversalMatchFun() const - -> std::function + -> std::function { - return [this](const char *path, int filetype) { return this->traversalPatternMatch(path, filetype); }; + return [this](const char *path, ItemType filetype) { return this->traversalPatternMatch(path, filetype); }; } -static QString convertToRegexpSyntax(QString exclude) +/** + * On linux we used to use fnmatch with FNM_PATHNAME, but the windows function we used + * didn't have that behavior. wildcardsMatchSlash can be used to control which behavior + * the resulting regex shall use. + */ +static QString convertToRegexpSyntax(QString exclude, bool wildcardsMatchSlash) { // Translate *, ?, [...] to their regex variants. // The escape sequences \*, \?, \[. \\ have a special meaning, @@ -507,11 +451,19 @@ static QString convertToRegexpSyntax(QString exclude) switch (exclude[i].unicode()) { case '*': flush(); - regex.append("[^/]*"); + if (wildcardsMatchSlash) { + regex.append(".*"); + } else { + regex.append("[^/]*"); + } break; case '?': flush(); - regex.append("[^/]"); + if (wildcardsMatchSlash) { + regex.append("."); + } else { + regex.append("[^/]"); + } break; case '[': { flush(); @@ -565,20 +517,56 @@ static QString convertToRegexpSyntax(QString exclude) return regex; } +static QString extractBnameTrigger(const QString &exclude, bool wildcardsMatchSlash) +{ + // We can definitely drop everything to the left of a / - that will never match + // any bname. + QString pattern = exclude.mid(exclude.lastIndexOf('/') + 1); + + // Easy case, nothing else can match a slash, so that's it. + if (!wildcardsMatchSlash) + return pattern; + + // Otherwise it's more complicated. Examples: + // - "foo*bar" can match "fooX/Xbar", pattern is "*bar" + // - "foo*bar*" can match "fooX/XbarX", pattern is "*bar*" + // - "foo?bar" can match "foo/bar" but also "fooXbar", pattern is "*bar" + + auto isWildcard = [](QChar c) { return c == QLatin1Char('*') || c == QLatin1Char('?'); }; + + // First, skip wildcards on the very right of the pattern + int i = pattern.size() - 1; + while (i >= 0 && isWildcard(pattern[i])) + --i; + + // Then scan further until the next wildcard that could match a / + while (i >= 0 && !isWildcard(pattern[i])) + --i; + + // Everything to the right is part of the pattern + pattern = pattern.mid(i + 1); + + // And if there was a wildcard, it starts with a * + if (i >= 0) + pattern.prepend('*'); + + return pattern; +} + void ExcludedFiles::prepare() { // Build regular expressions for the different cases. // - // To compose the _bnameActivationRegex and _fullRegex patterns we - // collect several subgroups of patterns here. + // To compose the _bnameTraversalRegex, _fullTraversalRegex and _fullRegex + // patterns we collect several subgroups of patterns here. // // * The "full" group will contain all patterns that contain a non-trailing - // slash. They only make sense in the fullRegex. + // slash. They only make sense in the fullRegex and fullTraversalRegex. // * The "bname" group contains all patterns without a non-trailing slash. // These need separate handling in the _fullRegex (slash-containing // patterns must be anchored to the front, these don't need it) // * The "bnameTrigger" group contains the bname part of all patterns in the - // "full" group. These and the "bname" group become _bnameActivationRegex. + // "full" group. These and the "bname" group become _bnameTraversalRegex. // // To complicate matters, the exclude patterns have two binary attributes // meaning we'll end up with 4 variants: @@ -630,15 +618,15 @@ void ExcludedFiles::prepare() auto &fullFileDir = removeExcluded ? fullFileDirRemove : fullFileDirKeep; auto &fullDir = removeExcluded ? fullDirRemove : fullDirKeep; - auto regexExclude = convertToRegexpSyntax(QString::fromUtf8(exclude)); + auto regexExclude = convertToRegexpSyntax(QString::fromUtf8(exclude), _wildcardsMatchSlash); if (!fullPath) { regexAppend(bnameFileDir, bnameDir, regexExclude, matchDirOnly); } else { regexAppend(fullFileDir, fullDir, regexExclude, matchDirOnly); - // for activation, trigger on the 'bname' part of the full pattern - auto bnameExclude = exclude.mid(exclude.lastIndexOf('/') + 1); - auto regexBname = convertToRegexpSyntax(bnameExclude); + // For activation, trigger on the 'bname' part of the full pattern. + QString bnameExclude = extractBnameTrigger(exclude, _wildcardsMatchSlash); + auto regexBname = convertToRegexpSyntax(bnameExclude, true); regexAppend(bnameTriggerFileDir, bnameTriggerDir, regexBname, matchDirOnly); } } @@ -661,22 +649,40 @@ void ExcludedFiles::prepare() emptyMatchNothing(bnameTriggerFileDir); emptyMatchNothing(bnameTriggerDir); - // The bname activation regexe is applied to the bname only, so must be - // anchored in the beginning and in the end. It has the explicit triggers - // plus the bname-only patterns. Here we don't care about the remove/keep - // distinction. - _bnameActivationRegexFile.setPattern( - "^(?:" + bnameFileDirKeep + "|" + bnameFileDirRemove + "|" + bnameTriggerFileDir + ")$"); - _bnameActivationRegexDir.setPattern( - "^(?:" + bnameFileDirKeep + "|" + bnameFileDirRemove - + "|" + bnameDirKeep + "|" + bnameFileDirRemove - + "|" + bnameTriggerFileDir + "|" + bnameTriggerDir + ")$"); + // The bname regex is applied to the bname only, so it must be + // anchored in the beginning and in the end. It has the structure: + // (exclude)|(excluderemove)|(bname triggers). + // If the third group matches, the fullActivatedRegex needs to be applied + // to the full path. + _bnameTraversalRegexFile.setPattern( + "^(?P" + bnameFileDirKeep + ")$|" + + "^(?P" + bnameFileDirRemove + ")$|" + + "^(?P" + bnameTriggerFileDir + ")$"); + _bnameTraversalRegexDir.setPattern( + "^(?P" + bnameFileDirKeep + "|" + bnameDirKeep + ")$|" + + "^(?P" + bnameFileDirRemove + "|" + bnameDirRemove + ")$|" + + "^(?P" + bnameTriggerFileDir + "|" + bnameTriggerDir + ")$"); - // The full regex has two captures, it's basic form is "(...)|(...)". The first - // capture has the keep/exclude-only patterns, the second the remove/exclude-and-remove - // patterns. + // The full traveral regex is applied to the full path if the trigger capture of + // the bname regex matches. Its basic form is (exclude)|(excluderemove)". + // This pattern can be much simpler than fullRegex since we can assume a traversal + // situation and doesn't need to look for bname patterns in parent paths. + _fullTraversalRegexFile.setPattern( + QLatin1String("") + // Full patterns are anchored to the beginning + + "^(?P" + fullFileDirKeep + ")(?:$|/)" + + "|" + + "^(?P" + fullFileDirRemove + ")(?:$|/)"); + _fullTraversalRegexDir.setPattern( + QLatin1String("") + + "^(?P" + fullFileDirKeep + "|" + fullDirKeep + ")(?:$|/)" + + "|" + + "^(?P" + fullFileDirRemove + "|" + fullDirRemove + ")(?:$|/)"); + + // The full regex is applied to the full path and incorporates both bname and + // full-path patterns. It has the form "(exclude)|(excluderemove)". _fullRegexFile.setPattern( - QLatin1String("(") + QLatin1String("(?P") // Full patterns are anchored to the beginning + "^(?:" + fullFileDirKeep + ")(?:$|/)" + "|" // Simple bname patterns can be any path component @@ -684,16 +690,20 @@ void ExcludedFiles::prepare() // When checking a file for exclusion we must check all parent paths // against the dir-only patterns as well. + "(?:^|/)(?:" + bnameDirKeep + ")/" - + ")|(" + + ")" + + "|" + + "(?P" + "^(?:" + fullFileDirRemove + ")(?:$|/)" + "|" + "(?:^|/)(?:" + bnameFileDirRemove + ")(?:$|/)" + "|" + "(?:^|/)(?:" + bnameDirRemove + ")/" + ")"); _fullRegexDir.setPattern( - QLatin1String("(") + QLatin1String("(?P") + "^(?:" + fullFileDirKeep + "|" + fullDirKeep + ")(?:$|/)" + "|" + "(?:^|/)(?:" + bnameFileDirKeep + "|" + bnameDirKeep + ")(?:$|/)" - + ")|(" + + ")" + + "|" + + "(?P" + "^(?:" + fullFileDirRemove + "|" + fullDirRemove + ")(?:$|/)" + "|" + "(?:^|/)(?:" + bnameFileDirRemove + "|" + bnameDirRemove + ")(?:$|/)" + ")"); @@ -701,10 +711,14 @@ void ExcludedFiles::prepare() QRegularExpression::PatternOptions patternOptions = QRegularExpression::NoPatternOption; if (OCC::Utility::fsCasePreserving()) patternOptions |= QRegularExpression::CaseInsensitiveOption; - _bnameActivationRegexFile.setPatternOptions(patternOptions); - _bnameActivationRegexFile.optimize(); - _bnameActivationRegexDir.setPatternOptions(patternOptions); - _bnameActivationRegexDir.optimize(); + _bnameTraversalRegexFile.setPatternOptions(patternOptions); + _bnameTraversalRegexFile.optimize(); + _bnameTraversalRegexDir.setPatternOptions(patternOptions); + _bnameTraversalRegexDir.optimize(); + _fullTraversalRegexFile.setPatternOptions(patternOptions); + _fullTraversalRegexFile.optimize(); + _fullTraversalRegexDir.setPatternOptions(patternOptions); + _fullTraversalRegexDir.optimize(); _fullRegexFile.setPatternOptions(patternOptions); _fullRegexFile.optimize(); _fullRegexDir.setPatternOptions(patternOptions); diff --git a/src/csync/csync_exclude.h b/src/csync/csync_exclude.h index f2c14a767..249ec7bff 100644 --- a/src/csync/csync_exclude.h +++ b/src/csync/csync_exclude.h @@ -30,6 +30,8 @@ #include #include +#include + enum csync_exclude_type_e { CSYNC_NOT_EXCLUDED = 0, CSYNC_FILE_SILENTLY_EXCLUDED, @@ -74,6 +76,13 @@ public: */ 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. * @@ -100,6 +109,11 @@ public: */ void clearManualExcludes(); + /** + * Adjusts behavior of wildcards. Only used for testing. + */ + void setWildcardsMatchSlash(bool onoff); + /** * Generate a hook for traversal exclude pattern matching * that csync can use. @@ -108,7 +122,7 @@ public: * ExcludedFiles instance stays alive. */ auto csyncTraversalMatchFun() const - -> std::function; + -> std::function; public slots: /** @@ -125,7 +139,7 @@ private: * Note that this only matches patterns. It does not check whether the file * or directory pointed to is hidden (or whether it even exists). */ - CSYNC_EXCLUDE_TYPE fullPatternMatch(const char *path, int filetype) const; + CSYNC_EXCLUDE_TYPE fullPatternMatch(const char *path, ItemType filetype) const; /** * @brief Check if the given path should be excluded in a traversal situation. @@ -142,7 +156,7 @@ private: * Note that this only matches patterns. It does not check whether the file * or directory pointed to is hidden (or whether it even exists). */ - CSYNC_EXCLUDE_TYPE traversalPatternMatch(const char *path, int filetype) const; + CSYNC_EXCLUDE_TYPE traversalPatternMatch(const char *path, ItemType filetype) const; /** * Generate optimized regular expressions for the exclude patterns. @@ -165,8 +179,9 @@ private: * full("a/b/c/d") == traversal("a") || traversal("a/b") || traversal("a/b/c") * * The traversal matcher can be extremely fast because it has a fast early-out - * case: It checks the bname part of the path against _bnameActivationRegex - * and only runs the full regex if the bname activation was triggered. + * case: It checks the bname part of the path against _bnameTraversalRegex + * and only runs a simplified _fullTraversalRegex on the whole path if bname + * activation for it was triggered. * * Note: The traversal matcher will return not-excluded on some paths that the * full matcher would exclude. Example: "b" is excluded. traversal("b/c") @@ -184,11 +199,23 @@ private: QList _allExcludes; /// see prepare() - QRegularExpression _bnameActivationRegexFile; - QRegularExpression _bnameActivationRegexDir; + QRegularExpression _bnameTraversalRegexFile; + QRegularExpression _bnameTraversalRegexDir; + QRegularExpression _fullTraversalRegexFile; + QRegularExpression _fullTraversalRegexDir; QRegularExpression _fullRegexFile; QRegularExpression _fullRegexDir; + bool _excludeConflictFiles = true; + + /** + * Whether * and ? in patterns can match a / + * + * Unfortunately this was how matching was done on Windows so + * it continues to be enabled there. + */ + bool _wildcardsMatchSlash = false; + friend class ExcludedFilesTest; }; diff --git a/src/csync/csync_log.cpp b/src/csync/csync_log.cpp deleted file mode 100644 index 4a2e24a32..000000000 --- a/src/csync/csync_log.cpp +++ /dev/null @@ -1,77 +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 "config_csync.h" - -#include -#include -#include - -#include "csync_private.h" -#include "csync_log.h" - -CSYNC_THREAD int csync_log_level; -CSYNC_THREAD csync_log_callback csync_log_cb; - -void csync_log(int verbosity, - const char *function, - const char *format, ...) -{ - csync_log_callback log_fn = csync_get_log_callback(); - if (log_fn && verbosity <= csync_get_log_level()) { - char buffer[1024]; - va_list va; - - va_start(va, format); - vsnprintf(buffer, sizeof(buffer), format, va); - va_end(va); - - log_fn(verbosity, function, buffer); - return; - } -} - -int csync_set_log_level(int level) { - if (level < 0) { - return -1; - } - - csync_log_level = level; - - return 0; -} - -int csync_get_log_level(void) { - return csync_log_level; -} - -int csync_set_log_callback(csync_log_callback cb) { - if (cb == NULL) { - return -1; - } - - csync_log_cb = cb; - - return 0; -} - -csync_log_callback csync_get_log_callback(void) { - return csync_log_cb; -} diff --git a/src/csync/csync_log.h b/src/csync/csync_log.h deleted file mode 100644 index 0cd0d01fc..000000000 --- a/src/csync/csync_log.h +++ /dev/null @@ -1,75 +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 - */ - -/** - * @file csync_log.h - * - * @brief Logging interface of csync - * - * @defgroup csyncLogInternals csync logging internals - * @ingroup csyncInternalAPI - * - * @{ - */ - -#ifndef _CSYNC_LOG_H -#define _CSYNC_LOG_H - -/* GCC have printf type attribute check. */ -#ifndef PRINTF_ATTRIBUTE -#ifdef __GNUC__ -#ifdef _WIN32 -#define PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__gnu_printf__, a, b))) -#else -#define PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b))) -#endif -#else -#define PRINTF_ATTRIBUTE(a,b) -#endif /* __GNUC__ */ -#endif /* ndef PRINTF_ATTRIBUTE */ - -enum csync_log_priority_e { - CSYNC_LOG_PRIORITY_NOLOG = 0, - CSYNC_LOG_PRIORITY_FATAL, - CSYNC_LOG_PRIORITY_ALERT, - CSYNC_LOG_PRIORITY_CRIT, - CSYNC_LOG_PRIORITY_ERROR, - CSYNC_LOG_PRIORITY_WARN, - CSYNC_LOG_PRIORITY_NOTICE, - CSYNC_LOG_PRIORITY_INFO, - CSYNC_LOG_PRIORITY_DEBUG, - CSYNC_LOG_PRIORITY_TRACE, - CSYNC_LOG_PRIORITY_NOTSET, - CSYNC_LOG_PRIORITY_UNKNOWN, -}; - -#define CSYNC_LOG(priority, ...) \ - csync_log(priority, __func__, __VA_ARGS__) - -void csync_log(int verbosity, - const char *function, - const char *format, ...) PRINTF_ATTRIBUTE(3, 4); - -/** - * }@ - */ -#endif /* _CSYNC_LOG_H */ - -/* vim: set ft=c.doxygen ts=4 sw=4 et cindent: */ diff --git a/src/csync/csync_misc.cpp b/src/csync/csync_misc.cpp index bb8e98c8a..7945c428f 100644 --- a/src/csync/csync_misc.cpp +++ b/src/csync/csync_misc.cpp @@ -44,7 +44,6 @@ #include "c_lib.h" #include "csync_misc.h" #include "csync_macros.h" -#include "csync_log.h" #ifdef HAVE_FNMATCH #include diff --git a/src/csync/csync_private.h b/src/csync/csync_private.h index e692e7ba1..f73a1b2d5 100644 --- a/src/csync/csync_private.h +++ b/src/csync/csync_private.h @@ -36,7 +36,6 @@ #include #include #include -#include #include #include #include @@ -153,7 +152,7 @@ struct OCSYNC_EXPORT csync_s { * * See ExcludedFiles in csync_exclude. */ - std::function exclude_traversal_fn; + std::function exclude_traversal_fn; struct { std::unordered_map folder_renamed_to; // map from->to @@ -203,6 +202,8 @@ struct OCSYNC_EXPORT csync_s { bool ignore_hidden_files = true; + bool upload_conflict_files = false; + csync_s(const char *localUri, OCC::SyncJournalDb *statedb); ~csync_s(); int reinitialize(); diff --git a/src/csync/csync_reconcile.cpp b/src/csync/csync_reconcile.cpp index 97863fb8b..ce1beb142 100644 --- a/src/csync/csync_reconcile.cpp +++ b/src/csync/csync_reconcile.cpp @@ -27,6 +27,7 @@ #include "csync_rename.h" #include "common/c_jhash.h" #include "common/asserts.h" +#include "common/syncjournalfilerecord.h" #include Q_LOGGING_CATEGORY(lcReconcile, "sync.csync.reconciler", QtInfoMsg) @@ -177,7 +178,7 @@ static int _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) { // other is found as well? qCDebug(lcReconcile, "Other has already been renamed to %s", other->rename_path.constData()); - } else if (cur->type == CSYNC_FTW_TYPE_DIR + } else if (cur->type == ItemTypeDirectory // The local replica is reconciled first, so the remote tree would // have either NONE or UPDATE_METADATA if the remote file is safe to // move. @@ -278,26 +279,16 @@ static int _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) { /* If the file already exist on the other side, we have a conflict. Abort the rename and consider it is a new file. */ cur->instruction = CSYNC_INSTRUCTION_NEW; - /* fall trough */ + /* fall through */ /* file on current replica is changed or new */ case CSYNC_INSTRUCTION_EVAL: case CSYNC_INSTRUCTION_NEW: - // This operation is usually a no-op and will by default return false - if (csync_file_locked_or_open(ctx->local.uri, cur->path)) { - qCDebug(lcReconcile, "[Reconciler] IGNORING file %s/%s since it is locked / open", ctx->local.uri, cur->path.constData()); - cur->instruction = CSYNC_INSTRUCTION_ERROR; - if (cur->error_status == CSYNC_STATUS_OK) // don't overwrite error - cur->error_status = CYSNC_STATUS_FILE_LOCKED_OR_OPEN; - break; - } else { - //qCDebug(lcReconcile, "[Reconciler] not ignoring file %s/%s", ctx->local.uri, cur->path); - } switch (other->instruction) { /* file on other replica is changed or new */ case CSYNC_INSTRUCTION_NEW: case CSYNC_INSTRUCTION_EVAL: - if (other->type == CSYNC_FTW_TYPE_DIR && - cur->type == CSYNC_FTW_TYPE_DIR) { + if (other->type == ItemTypeDirectory && + cur->type == ItemTypeDirectory) { // Folders of the same path are always considered equals is_conflict = false; } else { @@ -316,6 +307,35 @@ static int _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) { (ctx->current == REMOTE_REPLICA ? cur->checksumHeader : other->checksumHeader); if (!remoteChecksumHeader.isEmpty()) { is_conflict = true; + + // 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) + auto up = ctx->statedb->getUploadInfo(cur->path); + if (up._valid && up._contentChecksum == remoteChecksumHeader) { + // Solve the conflict into an upload, or nothing + auto remoteNode = ctx->current == REMOTE_REPLICA ? cur : other; + auto localNode = ctx->current == REMOTE_REPLICA ? other : cur; + remoteNode->instruction = CSYNC_INSTRUCTION_NONE; + localNode->instruction = up._modtime == localNode->modtime ? CSYNC_INSTRUCTION_UPDATE_METADATA : CSYNC_INSTRUCTION_SYNC; + + // Update the etag and other server metadata in the journal already + // (We can't use a typical CSYNC_INSTRUCTION_UPDATE_METADATA because + // we must not store the size/modtime from the file system) + OCC::SyncJournalFileRecord rec; + if (ctx->statedb->getFileRecord(remoteNode->path, &rec)) { + rec._path = remoteNode->path; + rec._etag = remoteNode->etag; + rec._fileId = remoteNode->file_id; + rec._modtime = remoteNode->modtime; + rec._type = remoteNode->type; + rec._fileSize = remoteNode->size; + rec._remotePerm = remoteNode->remotePerm; + rec._checksumHeader = remoteNode->checksumHeader; + ctx->statedb->setFileRecordMetadata(rec); + } + break; + } } // SO: If there is no checksum, we can have !is_conflict here @@ -345,7 +365,7 @@ static int _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) { // needs to delete the other entity first. cur->instruction = CSYNC_INSTRUCTION_TYPE_CHANGE; other->instruction = CSYNC_INSTRUCTION_NONE; - } else if (cur->type == CSYNC_FTW_TYPE_DIR) { + } else if (cur->type == ItemTypeDirectory) { cur->instruction = CSYNC_INSTRUCTION_UPDATE_METADATA; other->instruction = CSYNC_INSTRUCTION_NONE; } else { @@ -378,7 +398,7 @@ static int _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) { const char *repo = ctx->current == REMOTE_REPLICA ? "server" : "client"; if(cur->instruction ==CSYNC_INSTRUCTION_NONE) { - if(cur->type == CSYNC_FTW_TYPE_DIR) + if(cur->type == ItemTypeDirectory) { qCDebug(lcReconcile, "%-30s %s dir: %s", @@ -397,7 +417,7 @@ static int _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) { } else { - if(cur->type == CSYNC_FTW_TYPE_DIR) + if(cur->type == ItemTypeDirectory) { qCInfo(lcReconcile, "%-30s %s dir: %s", diff --git a/src/csync/csync_time.c b/src/csync/csync_time.c deleted file mode 100644 index 442263cee..000000000 --- a/src/csync/csync_time.c +++ /dev/null @@ -1,84 +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 "config_csync.h" - -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif - -#include -#include -#include - -#include "csync_time.h" - -#ifndef _WIN32 -#include -#include -#else -#include -#endif - -#define CSYNC_LOG_CATEGORY_NAME "csync.time" -#include "csync_log.h" - -#ifdef HAVE_CLOCK_GETTIME -# ifdef _POSIX_MONOTONIC_CLOCK -# define CSYNC_CLOCK CLOCK_MONOTONIC -# else -# define CSYNC_CLOCK CLOCK_REALTIME -# endif -#endif - - -int csync_gettime(struct timespec *tp) -{ -#if defined(_WIN32) - __int64 wintime; - GetSystemTimeAsFileTime((FILETIME*)&wintime); - wintime -= 116444736000000000ll; //1jan1601 to 1jan1970 - tp->tv_sec = wintime / 10000000ll; //seconds - tp->tv_nsec = wintime % 10000000ll * 100; //nano-seconds -#elif defined(HAVE_CLOCK_GETTIME) - return clock_gettime(CSYNC_CLOCK, tp); -#else - struct timeval tv; - - if (gettimeofday(&tv, NULL) < 0) { - return -1; - } - - tp->tv_sec = tv.tv_sec; - tp->tv_nsec = tv.tv_usec * 1000; -#endif - return 0; -} - -#undef CSYNC_CLOCK - -void csync_sleep(unsigned int msecs) -{ -#if defined(_WIN32) - Sleep(msecs); -#else - usleep(msecs * 1000); -#endif -} diff --git a/src/csync/csync_time.h b/src/csync/csync_time.h deleted file mode 100644 index 14d355b32..000000000 --- a/src/csync/csync_time.h +++ /dev/null @@ -1,37 +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 _CSYNC_TIME_H -#define _CSYNC_TIME_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -int csync_gettime(struct timespec *tp); -void csync_sleep(unsigned int msecs); - -#ifdef __cplusplus -} -#endif - -#endif /* _CSYNC_TIME_H */ diff --git a/src/csync/csync_update.cpp b/src/csync/csync_update.cpp index ae7efba77..9ce636476 100644 --- a/src/csync/csync_update.cpp +++ b/src/csync/csync_update.cpp @@ -117,7 +117,7 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr f return -1; } - if (fs->type == CSYNC_FTW_TYPE_SKIP) { + if (fs->type == ItemTypeSkip) { excluded =CSYNC_FILE_EXCLUDE_STAT_FAILED; } else { /* Check if file is excluded */ @@ -151,21 +151,29 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr f } } - if (ctx->current == REMOTE_REPLICA && QTextCodec::codecForLocale()->mibEnum() != 106) { + auto localCodec = QTextCodec::codecForLocale(); + if (ctx->current == REMOTE_REPLICA && localCodec->mibEnum() != 106) { /* If the locale codec is not UTF-8, we must check that the filename from the server can - * be encoded in the local file system. */ - if (!QTextCodec::codecForLocale()->canEncode(QString::fromUtf8(fs->path))) { + * be encoded in the local file system. + * + * We cannot use QTextCodec::canEncode() since that can incorrectly return true, see + * https://bugreports.qt.io/browse/QTBUG-6925. + */ + QTextEncoder encoder(localCodec, QTextCodec::ConvertInvalidToNull); + if (encoder.fromUnicode(QString::fromUtf8(fs->path)).contains('\0')) { + qCDebug(lcUpdate, "cannot encode %s to local encoding %d", + fs->path.constData(), localCodec->mibEnum()); excluded = CSYNC_FILE_EXCLUDE_CANNOT_ENCODE; } } - if (fs->type == CSYNC_FTW_TYPE_FILE ) { + if (fs->type == ItemTypeFile ) { if (fs->modtime == 0) { qCDebug(lcUpdate, "file: %s - mtime is zero!", fs->path.constData()); } } - if (excluded > CSYNC_NOT_EXCLUDED || fs->type == CSYNC_FTW_TYPE_SLINK) { + if (excluded > CSYNC_NOT_EXCLUDED || fs->type == ItemTypeSoftLink) { fs->instruction = CSYNC_INSTRUCTION_IGNORE; if (ctx->current_fs) { ctx->current_fs->has_ignored_files = true; @@ -238,7 +246,7 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr f bool metadata_differ = (ctx->current == REMOTE_REPLICA && (fs->file_id != base._fileId || fs->remotePerm != base._remotePerm)) || (ctx->current == LOCAL_REPLICA && fs->inode != base._inode); - if (fs->type == CSYNC_FTW_TYPE_DIR && ctx->current == REMOTE_REPLICA + if (fs->type == ItemTypeDirectory && ctx->current == REMOTE_REPLICA && !metadata_differ && ctx->read_remote_from_db) { /* If both etag and file id are equal for a directory, read all contents from * the database. @@ -277,7 +285,7 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr f bool isRename = base.isValid() && base._type == fs->type - && ((base._modtime == fs->modtime && base._fileSize == fs->size) || fs->type == CSYNC_FTW_TYPE_DIR) + && ((base._modtime == fs->modtime && base._fileSize == fs->size) || fs->type == ItemTypeDirectory) #ifdef NO_RENAME_EXTENSION && _csync_sameextension(base._path, fs->path) #endif @@ -286,7 +294,7 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr f // Verify the checksum where possible if (isRename && !base._checksumHeader.isEmpty() && ctx->callbacks.checksum_hook - && fs->type == CSYNC_FTW_TYPE_FILE) { + && fs->type == ItemTypeFile) { fs->checksumHeader = ctx->callbacks.checksum_hook( _rel_to_abs(ctx, fs->path), base._checksumHeader, ctx->callbacks.checksum_userdata); @@ -300,7 +308,7 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr f qCDebug(lcUpdate, "pot rename detected based on inode # %" PRId64 "", (uint64_t) fs->inode); /* inode found so the file has been renamed */ fs->instruction = CSYNC_INSTRUCTION_EVAL_RENAME; - if (fs->type == CSYNC_FTW_TYPE_DIR) { + if (fs->type == ItemTypeDirectory) { csync_rename_record(ctx, base._path, fs->path); } } @@ -325,7 +333,7 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr f done = true; return; } - if (fs->type != CSYNC_FTW_TYPE_DIR && base._etag != fs->etag) { + if (fs->type != ItemTypeDirectory && base._etag != fs->etag) { /* File with different etag, don't do a rename, but download the file again */ qCWarning(lcUpdate, "file etag different, not a rename"); done = true; @@ -333,7 +341,7 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr f } // Record directory renames - if (fs->type == CSYNC_FTW_TYPE_DIR) { + if (fs->type == ItemTypeDirectory) { // If the same folder was already renamed by a different entry, // skip to the next candidate if (ctx->renames.folder_renamed_to.count(base._path) > 0) { @@ -354,7 +362,7 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr f } if (fs->instruction == CSYNC_INSTRUCTION_NEW - && fs->type == CSYNC_FTW_TYPE_DIR + && fs->type == ItemTypeDirectory && ctx->current == REMOTE_REPLICA && ctx->callbacks.checkSelectiveSyncNewFolderHook) { if (ctx->callbacks.checkSelectiveSyncNewFolderHook(ctx->callbacks.update_callback_userdata, fs->path, fs->remotePerm)) { @@ -369,7 +377,7 @@ out: /* Set the ignored error string. */ if (fs->instruction == CSYNC_INSTRUCTION_IGNORE) { - if( fs->type == CSYNC_FTW_TYPE_SLINK ) { + if( fs->type == ItemTypeSoftLink ) { fs->error_status = CSYNC_STATUS_INDIVIDUAL_IS_SYMLINK; /* Symbolic links are ignored. */ } else { if (excluded == CSYNC_FILE_EXCLUDE_LIST) { @@ -394,13 +402,13 @@ out: if (fs->instruction != CSYNC_INSTRUCTION_NONE && fs->instruction != CSYNC_INSTRUCTION_IGNORE && fs->instruction != CSYNC_INSTRUCTION_UPDATE_METADATA - && fs->type != CSYNC_FTW_TYPE_DIR) { + && fs->type != ItemTypeDirectory) { fs->child_modified = true; } // If conflict files are uploaded, they won't be marked as IGNORE / CSYNC_FILE_EXCLUDE_CONFLICT // but we still want them marked! - if (OCC::Utility::shouldUploadConflictFiles()) { + if (ctx->upload_conflict_files) { if (OCC::Utility::isConflictFile(fs->path.constData())) { fs->error_status = CSYNC_STATUS_INDIVIDUAL_IS_CONFLICT_FILE; } @@ -436,21 +444,21 @@ int csync_walker(CSYNC *ctx, std::unique_ptr fs) { } switch (fs->type) { - case CSYNC_FTW_TYPE_FILE: + case ItemTypeFile: if (ctx->current == REMOTE_REPLICA) { qCDebug(lcUpdate, "file: %s [file_id=%s size=%" PRIu64 "]", fs->path.constData(), fs->file_id.constData(), fs->size); } else { qCDebug(lcUpdate, "file: %s [inode=%" PRIu64 " size=%" PRIu64 "]", fs->path.constData(), fs->inode, fs->size); } break; - case CSYNC_FTW_TYPE_DIR: /* enter directory */ + case ItemTypeDirectory: /* enter directory */ if (ctx->current == REMOTE_REPLICA) { qCDebug(lcUpdate, "directory: %s [file_id=%s]", fs->path.constData(), fs->file_id.constData()); } else { qCDebug(lcUpdate, "directory: %s [inode=%" PRIu64 "]", fs->path.constData(), fs->inode); } break; - case CSYNC_FTW_TYPE_SLINK: + case ItemTypeSoftLink: qCDebug(lcUpdate, "symlink: %s - not supported", fs->path.constData()); break; default: @@ -503,7 +511,7 @@ static bool fill_tree_from_db(CSYNC *ctx, const char *uri) if (ctx->exclude_traversal_fn) excluded = ctx->exclude_traversal_fn(st->path, st->type); if (excluded != CSYNC_NOT_EXCLUDED) { - qDebug(lcUpdate, "%s excluded (%d)", st->path.constData(), excluded); + qInfo(lcUpdate, "%s excluded from db read (%d)", st->path.constData(), excluded); if (excluded == CSYNC_FILE_EXCLUDE_AND_REMOVE || excluded == CSYNC_FILE_SILENTLY_EXCLUDED) { @@ -522,7 +530,7 @@ static bool fill_tree_from_db(CSYNC *ctx, const char *uri) ctx->status_code = CSYNC_STATUS_STATEDB_LOAD_ERROR; return false; } - qDebug(lcUpdate, "%" PRId64 " entries read below path %s from db.", count, uri); + qInfo(lcUpdate, "%" PRId64 " entries read below path %s from db.", count, uri); return true; } @@ -690,7 +698,7 @@ int csync_ftw(CSYNC *ctx, const char *uri, csync_walker_fn fn, } previous_fs = ctx->current_fs; - bool recurse = dirent->type == CSYNC_FTW_TYPE_DIR; + bool recurse = dirent->type == ItemTypeDirectory; /* Call walker function for each file */ rc = fn(ctx, std::move(dirent)); diff --git a/src/csync/csync_util.cpp b/src/csync/csync_util.cpp index b05228efe..761537bb8 100644 --- a/src/csync/csync_util.cpp +++ b/src/csync/csync_util.cpp @@ -34,8 +34,8 @@ #include "csync_util.h" #include "vio/csync_vio.h" -#define CSYNC_LOG_CATEGORY_NAME "csync.util" -#include "csync_log.h" +Q_LOGGING_CATEGORY(lcCSyncUtils, "sync.csync.utils", QtInfoMsg) + typedef struct { const char *instr_str; @@ -102,29 +102,8 @@ void csync_memstat_check(void) { return; } - CSYNC_LOG(CSYNC_LOG_PRIORITY_INFO, "Memory: %dK total size, %dK resident, %dK shared", - m.size * 4, m.resident * 4, m.shared * 4); -} - -bool (*csync_file_locked_or_open_ext) (const char*) = 0; // filled in by library user -void set_csync_file_locked_or_open_ext(bool (*f) (const char*)); -void set_csync_file_locked_or_open_ext(bool (*f) (const char*)) { - csync_file_locked_or_open_ext = f; -} - -bool csync_file_locked_or_open( const char *dir, const char *fname) { - char *tmp_uri = NULL; - bool ret; - if (!csync_file_locked_or_open_ext) { - return false; - } - if (asprintf(&tmp_uri, "%s/%s", dir, fname) < 0) { - return -1; - } - CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "csync_file_locked_or_open %s", tmp_uri); - ret = csync_file_locked_or_open_ext(tmp_uri); - SAFE_FREE(tmp_uri); - return ret; + qCInfo(lcCSyncUtils, "Memory: %dK total size, %dK resident, %dK shared", + m.size * 4, m.resident * 4, m.shared * 4); } #ifndef HAVE_TIMEGM diff --git a/src/csync/csync_util.h b/src/csync/csync_util.h index 57a5f12a0..f9c0d8876 100644 --- a/src/csync/csync_util.h +++ b/src/csync/csync_util.h @@ -30,8 +30,6 @@ const char OCSYNC_EXPORT *csync_instruction_str(enum csync_instructions_e instr) void OCSYNC_EXPORT csync_memstat_check(void); -bool OCSYNC_EXPORT csync_file_locked_or_open( const char *dir, const char *fname); - /* Returns true if we're reasonably certain that hash equality * for the header means content equality. * diff --git a/src/csync/std/CMakeLists.txt b/src/csync/std/CMakeLists.txt deleted file mode 100644 index fddd02e99..000000000 --- a/src/csync/std/CMakeLists.txt +++ /dev/null @@ -1,39 +0,0 @@ -project(cstdlib) - -set(CSTDLIB_PUBLIC_INCLUDE_DIRS - ${CMAKE_CURRENT_SOURCE_DIR} - CACHE INTERNAL "cstdlib public include directories" -) - -set(CSTDLIB_LIBRARY - cstdlib - CACHE INTERNAL "cstdlib library" -) - -set(CSTDLIB_LINK_LIBRARIES - ${CSTDLIB_LIBRARY} -) - -set(cstdlib_SRCS - c_alloc.c - c_path.c - c_string.c - c_time.c - c_utf8.cpp -) - -if(NOT HAVE_ASPRINTF AND NOT HAVE___MINGW_ASPRINTF) - list(APPEND cstdlib_SRCS - asprintf.c - ) -endif() - -include_directories( - ${CSTDLIB_PUBLIC_INCLUDE_DIRS} -) - -add_library(${CSTDLIB_LIBRARY} STATIC ${cstdlib_SRCS}) -if(NOT WIN32) - add_definitions( -fPIC ) -endif() -qt5_use_modules(${CSTDLIB_LIBRARY} Core) diff --git a/src/csync/std/c_lib.h b/src/csync/std/c_lib.h index 43f352ff4..2f0135ede 100644 --- a/src/csync/std/c_lib.h +++ b/src/csync/std/c_lib.h @@ -23,7 +23,6 @@ #include "c_macro.h" #include "c_alloc.h" -#include "c_path.h" #include "c_string.h" #include "c_time.h" #include "c_private.h" diff --git a/src/csync/std/c_path.c b/src/csync/std/c_path.c deleted file mode 100644 index f81799c05..000000000 --- a/src/csync/std/c_path.c +++ /dev/null @@ -1,392 +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 - */ - -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif - -#include -#include -#include -#include -#include -#include - -#include "c_private.h" -#include "c_alloc.h" -#include "c_path.h" -#include "c_string.h" -#include "c_utf8.h" - -/* - * dirname - parse directory component. - */ -char *c_dirname (const char *path) { - char *newbuf = NULL; - unsigned int len; - - if (path == NULL || *path == '\0') { - return c_strdup("."); - } - - len = strlen(path); - - /* Remove trailing slashes */ - while(len > 0 && path[len - 1] == '/') --len; - - /* We have only slashes */ - if (len == 0) { - return c_strdup("/"); - } - - /* goto next slash */ - while(len > 0 && path[len - 1] != '/') --len; - - if (len == 0) { - return c_strdup("."); - } else if (len == 1) { - return c_strdup("/"); - } - - /* Remove slashes again */ - while(len > 0 && path[len - 1] == '/') --len; - - newbuf = c_malloc(len + 1); - - strncpy(newbuf, path, len); - newbuf[len] = '\0'; - - return newbuf; -} - -char *c_basename (const char *path) { - char *newbuf = NULL; - const char *s; - unsigned int len; - - if (path == NULL || *path == '\0') { - return c_strdup("."); - } - - len = strlen(path); - /* Remove trailing slashes */ - while(len > 0 && path[len - 1] == '/') --len; - - /* We have only slashes */ - if (len == 0) { - return c_strdup("/"); - } - - while(len > 0 && path[len - 1] != '/') --len; - - if (len > 0) { - s = path + len; - len = strlen(s); - - while(len > 0 && s[len - 1] == '/') --len; - } else { - return c_strdup(path); - } - - newbuf = c_malloc(len + 1); - - strncpy(newbuf, s, len); - newbuf[len] = '\0'; - - return newbuf; -} - -int c_parse_uri(const char *uri, - char **scheme, - char **user, char **passwd, - char **host, unsigned int *port, - char **path) { - const char *p, *z; - - if (uri == NULL || *uri == '\0') { - return -1; - } - - /* - * uri = scheme://user:password@host:port/path - * p = ^ - * z = ^ - */ - p = z = uri; - - /* check for valid scheme; git+ssh, pop3 */ - while (isalpha((int) *p) || isdigit((int) *p) || - *p == '+' || *p == '-') { - /* - * uri = scheme://user:password@host:port/path - * p = ^ - * z = ^ - */ - p++; - } - - /* get scheme */ - if (*p == ':') { - if (scheme != NULL) { - *scheme = c_strndup(z, p - z); - - if (*scheme == NULL) { - errno = ENOMEM; - return -1; - } - } - p++; - z = p; - } - - /* - * uri = scheme://user:password@host:port/path - * p = ^ - * z = ^ - */ - p = z; - - /* do we have a hostname */ - if (p[0] == '/' && p[1] == '/') { - /* - * uri = scheme://user:password@host:port/path - * p = ^ - * z = ^ - */ - z += 2; - p = z; - - /* check for user and passwd */ - while (*p && *p != '@' && *p != '/') { - /* - * uri = scheme://user:password@host:port/path - * p = ^ or ^ - * z = ^ - */ - p++; - } - - /* check for user and password */ - if (*p == '@') { - const char *q; - - q = p; - - /* check if we have a password */ - while (q > z && *q != ':') { - /* - * uri = scheme://user:password@host:port/path - * p = ^ - * z = ^ - * q = ^ - */ - q--; - } - - /* password found */ - if (*q == ':') { - if (user != NULL) { - *user = c_strndup(z, q - z); - if (*user == NULL) { - errno = ENOMEM; - if (scheme != NULL) SAFE_FREE(*scheme); - return -1; - } - } - - if (passwd != NULL) { - *passwd = c_strndup(q + 1, p - (q + 1)); - if (*passwd == NULL) { - if (scheme != NULL) SAFE_FREE(*scheme); - if (user != NULL) SAFE_FREE(*user); - errno = ENOMEM; - return -1; - } - } - } else { - /* user only */ - if (user != NULL) { - *user = c_strndup(z, p - z); - if( *user == NULL) { - if (scheme != NULL) SAFE_FREE(*scheme); - errno = ENOMEM; - return -1; - } - } - } - - p++; - z = p; - } - - /* - * uri = scheme://user:password@host:port/path - * p = ^ - * z = ^ - */ - p = z; - - /* check for IPv6 address */ - if (*p == '[') { - /* - * uri = scheme://user:password@[2001:0db8:85a3:08d3:1319:8a2e:0370:7344]:port/path - * p = ^ - * z = ^ - */ - p++; - - /* check if we have a valid IPv6 address */ - while (*p && (isxdigit((int) *p) || *p == '.' || *p == ':')) { - /* - * uri = scheme://user:password@[2001:0db8:85a3:08d3:1319:8a2e:0370:7344]:port/path - * p = ^ - * z = ^ - */ - p++; - } - - /* valid IPv6 address found */ - if (*p == ']') { - /* - * uri = scheme://user:password@[2001:0db8:85a3:08d3:1319:8a2e:0370:7344]:port/path - * p = ^ - * z = ^ - */ - z++; - - if (host != NULL) { - *host = c_strndup(z, p - z); - if (*host == NULL) { - if (scheme != NULL) SAFE_FREE(*scheme); - if (user != NULL) SAFE_FREE(*user); - if (passwd != NULL) SAFE_FREE(*passwd); - errno = ENOMEM; - return -1; - } - } - - /* - * uri = scheme://user:password@[2001:0db8:85a3:08d3:1319:8a2e:0370:7344]:port/path - * p = ^ - * z = ^ - */ - p++; - } else { - /* invalid IPv6 address, assume a hostname */ - p = z; - - while (*p && *p != ':' && *p != '/') { - p++; - /* - * uri = scheme://user:password@host:port/path - * p = ^ or ^ - * z = ^ - */ - } - - if (host != NULL) { - *host = c_strndup(z, p - z); - if (*host == NULL) { - if (scheme != NULL) SAFE_FREE(*scheme); - if (user != NULL) SAFE_FREE(*user); - if (passwd != NULL) SAFE_FREE(*passwd); - errno = ENOMEM; - return -1; - } - } - } - } else { - /* check for hostname */ - while (*p && *p != ':' && *p != '/') { - /* - * uri = scheme://user:password@host:port/path - * p = ^ ^ - * z = ^ - */ - p++; - } - - if (host != NULL) { - *host = c_strndup(z, p - z); - if (*host == NULL) { - if (scheme != NULL) SAFE_FREE(*scheme); - if (user != NULL) SAFE_FREE(*user); - if (passwd != NULL) SAFE_FREE(*passwd); - errno = ENOMEM; - return -1; - } - } - } - - /* check for port */ - if (*p == ':') { - char **e = NULL; - /* - * uri = scheme://user:password@host:port/path - * p = ^ - * z = ^ - */ - z = ++p; - - /* get only the digits */ - while (isdigit((int) *p)) { - /* - * uri = scheme://user:password@host:port/path - * p = ^ - * z = ^ - */ - e = (char **) &p; - p++; - } - - if (port != NULL) { - *port = strtoul(z, e, 0); - } - - /* - * uri = scheme://user:password@host:port/path - * p = ^ - */ - } - } - - if (*p == '\0') { - return 0; - } - - /* get the path with the leading slash */ - if (*p == '/') { - if (path != NULL) { - *path = c_strdup(p); - if (*path == NULL) { - if (scheme != NULL) SAFE_FREE(*scheme); - if (user != NULL) SAFE_FREE(*user); - if (passwd != NULL) SAFE_FREE(*passwd); - if (host != NULL) SAFE_FREE(*host); - errno = ENOMEM; - return -1; - } - } - - return 0; - } - - return -1; -} diff --git a/src/csync/std/c_path.h b/src/csync/std/c_path.h deleted file mode 100644 index d0dd43d9e..000000000 --- a/src/csync/std/c_path.h +++ /dev/null @@ -1,123 +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 - */ - -/** - * @file c_path.h - * - * @brief Interface of the cynapses libc path functions - * - * @defgroup cynPathInternals cynapses libc path functions - * @ingroup cynLibraryAPI - * - * @{ - */ - -#ifndef _C_PATH_H -#define _C_PATH_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include "c_macro.h" -#include "c_private.h" - -/** - * @brief Parse directory component. - * - * dirname breaks a null-terminated pathname string into a directory component. - * In the usual case, c_dirname() returns the string up to, but not including, - * the final '/'. Trailing '/' characters are not counted as part of the - * pathname. The caller must free the memory. - * - * @param path The path to parse. - * - * @return The dirname of path or NULL if we can't allocate memory. If path - * does not contain a slash, c_dirname() returns the string ".". If - * path is the string "/", it returns the string "/". If path is - * NULL or an empty string, "." is returned. - */ -char *c_dirname(const char *path); - -/** - * @brief basename - parse filename component. - * - * basename breaks a null-terminated pathname string into a filename component. - * c_basename() returns the component following the final '/'. Trailing '/' - * characters are not counted as part of the pathname. - * - * @param path The path to parse. - * - * @return The filename of path or NULL if we can't allocate memory. If path - * is a the string "/", basename returns the string "/". If path is - * NULL or an empty string, "." is returned. - */ -char *c_basename (const char *path); - -/** - * @brief parse a uri and split it into components. - * - * parse_uri parses an uri in the format - * - * [:][//[[:]@][:]]/[] - * - * into its compoments. If you only want a special component, - * pass NULL for all other components. All components will be allocated if they have - * been found. - * - * @param uri The uri to parse. - * @param scheme String for the scheme component - * @param user String for the username component - * @param passwd String for the password component - * @param host String for the password component - * @param port Integer for the port - * @param path String for the path component with a leading slash. - * - * @return 0 on success, < 0 on error. - */ -int c_parse_uri(const char *uri, char **scheme, char **user, char **passwd, - char **host, unsigned int *port, char **path); - -/** - * @brief Parts of a path. - * - * @param directory '\0' terminated path including the final '/' - * - * @param filename '\0' terminated string - * - * @param extension '\0' terminated string - * - */ -typedef struct -{ - char * directory; - char * filename; - char * extension; -} C_PATHINFO; - -/** - * }@ - */ - -#ifdef __cplusplus -} -#endif - -#endif /* _C_PATH_H */ diff --git a/src/csync/std/c_private.h b/src/csync/std/c_private.h index 26bba70db..76f25e453 100644 --- a/src/csync/std/c_private.h +++ b/src/csync/std/c_private.h @@ -102,10 +102,6 @@ typedef struct stat csync_stat_t; #endif #endif -#ifndef HAVE_STRERROR_R -#define strerror_r(errnum, buf, buflen) snprintf(buf, buflen, "%s", strerror(errnum)) -#endif - #ifndef HAVE_LSTAT #define lstat _stat #endif diff --git a/src/csync/std/c_string.c b/src/csync/std/c_string.c index 8929f2458..553b97cb9 100644 --- a/src/csync/std/c_string.c +++ b/src/csync/std/c_string.c @@ -32,7 +32,6 @@ #include #include "c_string.h" -#include "c_path.h" #include "c_alloc.h" #include "c_macro.h" @@ -64,106 +63,3 @@ int c_streq(const char *a, const char *b) { return 0; } - -c_strlist_t *c_strlist_new(size_t size) { - c_strlist_t *strlist = NULL; - - if (size == 0) { - errno = EINVAL; - return NULL; - } - - strlist = c_malloc(sizeof(c_strlist_t)); - if (strlist == NULL) { - return NULL; - } - - strlist->vector = (char **) c_malloc(size * sizeof(char *)); - strlist->count = 0; - strlist->size = size; - - return strlist; -} - -c_strlist_t *c_strlist_expand(c_strlist_t *strlist, size_t size) { - if (strlist == NULL || size == 0) { - errno = EINVAL; - return NULL; - } - - if (strlist->size >= size) { - return strlist; - } - - strlist->vector = (char **) c_realloc(strlist->vector, size * sizeof(char *)); - if (strlist->vector == NULL) { - return NULL; - } - - strlist->size = size; - - return strlist; -} - -int c_strlist_add(c_strlist_t *strlist, const char *string) { - if (strlist == NULL || string == NULL) { - return -1; - } - - if (strlist->count < strlist->size) { - strlist->vector[strlist->count] = c_strdup(string); - if (strlist->vector[strlist->count] == NULL) { - return -1; - } - strlist->count++; - } else { - errno = ENOBUFS; - return -1; - } - - return 0; -} - -int c_strlist_add_grow(c_strlist_t **strlist, const char *string) { - if (*strlist == NULL) { - *strlist = c_strlist_new(32); - if (*strlist == NULL) { - return -1; - } - } - - if ((*strlist)->count == (*strlist)->size) { - c_strlist_t *list = c_strlist_expand(*strlist, 2 * (*strlist)->size); - if (list == NULL) { - return -1; - } - *strlist = list; - } - - return c_strlist_add(*strlist, string); -} - -void c_strlist_clear(c_strlist_t *strlist) { - size_t i = 0; - - if (strlist == NULL) { - return; - } - - for (i = 0; i < strlist->count; i++) { - SAFE_FREE(strlist->vector[i]); - } - - strlist->count = 0; -} - -void c_strlist_destroy(c_strlist_t *strlist) { - - if (strlist == NULL) { - return; - } - - c_strlist_clear(strlist); - SAFE_FREE(strlist->vector); - SAFE_FREE(strlist); -} diff --git a/src/csync/std/c_string.h b/src/csync/std/c_string.h index 8b0421b2c..3066ee2b3 100644 --- a/src/csync/std/c_string.h +++ b/src/csync/std/c_string.h @@ -41,28 +41,6 @@ extern "C" { #include -struct c_strlist_s; typedef struct c_strlist_s c_strlist_t; - -/** - * @brief Structure for a stringlist - * - * Using a for loop you can access the strings saved in the vector. - * - * c_strlist_t strlist; - * int i; - * for (i = 0; i < strlist->count; i++) { - * printf("value: %s", strlist->vector[i]; - * } - */ -struct c_strlist_s { - /** The string vector */ - char **vector; - /** The count of the strings saved in the vector */ - size_t count; - /** Size of strings allocated */ - size_t size; -}; - /** * @brief Compare to strings case insensitively. * @@ -84,68 +62,6 @@ int c_strncasecmp(const char *a, const char *b, size_t n); */ int c_streq(const char *a, const char *b); -/** - * @brief Create a new stringlist. - * - * @param size Size to allocate. - * - * @return Pointer to the newly allocated stringlist. NULL if an error occurred. - */ -c_strlist_t *c_strlist_new(size_t size); - -/** - * @brief Expand the stringlist - * - * @param strlist Stringlist to expand - * @param size New size of the strlinglist to expand - * - * @return Pointer to the expanded stringlist. NULL if an error occurred. - */ -c_strlist_t *c_strlist_expand(c_strlist_t *strlist, size_t size); - -/** - * @brief Add a string to the stringlist. - * - * Duplicates the string and stores it in the stringlist. - * - * @param strlist Stringlist to add the string. - * @param string String to add. - * - * @return 0 on success, less than 0 and errno set if an error occurred. - * ENOBUFS if the list is full. - */ -int c_strlist_add(c_strlist_t *strlist, const char *string); - -/** - * @brief Add a string to the stringlist, growing it if necessary - * - * Duplicates the string and stores it in the stringlist. - * It also initializes the stringlist if it starts out as null. - * - * @param strlist Stringlist to add the string. - * @param string String to add. - * - * @return 0 on success, less than 0 and errno set if an error occurred. - */ -int c_strlist_add_grow(c_strlist_t **strlist, const char *string); - -/** - * @brief Removes all strings from the list. - * - * Frees the strings. - * - * @param strlist Stringlist to clear - */ -void c_strlist_clear(c_strlist_t *strlist); - -/** - * @brief Destroy the memory of the stringlist. - * - * Frees the strings and the stringlist. - * - * @param strlist Stringlist to destroy - */ -void c_strlist_destroy(c_strlist_t *strlist); /** * }@ diff --git a/src/csync/std/c_time.c b/src/csync/std/c_time.c index 4bcdcbf56..28528fdc6 100644 --- a/src/csync/std/c_time.c +++ b/src/csync/std/c_time.c @@ -22,52 +22,9 @@ #include "c_private.h" #include "c_string.h" -#include "c_path.h" #include "c_time.h" #include "c_utf8.h" -struct timespec c_tspecdiff(struct timespec time1, struct timespec time0) { - struct timespec ret; - int xsec = 0; - int sign = 1; - - if (time0.tv_nsec > time1.tv_nsec) { - xsec = (int) ((time0.tv_nsec - time1.tv_nsec) / (1E9 + 1)); - time0.tv_nsec -= (long int) (1E9 * xsec); - time0.tv_sec += xsec; - } - - if ((time1.tv_nsec - time0.tv_nsec) > 1E9) { - xsec = (int) ((time1.tv_nsec - time0.tv_nsec) / 1E9); - time0.tv_nsec += (long int) (1E9 * xsec); - time0.tv_sec -= xsec; - } - - ret.tv_sec = time1.tv_sec - time0.tv_sec; - ret.tv_nsec = time1.tv_nsec - time0.tv_nsec; - - if (time1.tv_sec < time0.tv_sec) { - sign = -1; - } - - ret.tv_sec = ret.tv_sec * sign; - - return ret; -} - -double c_secdiff(struct timespec clock1, struct timespec clock0) { - double ret; - struct timespec diff; - - diff = c_tspecdiff(clock1, clock0); - - ret = diff.tv_sec; - ret += (double) diff.tv_nsec / (double) 1E9; - - return ret; -} - - #ifdef HAVE_UTIMES int c_utimes(const char *uri, const struct timeval *times) { mbchar_t *wuri = c_utf8_path_to_locale(uri); diff --git a/src/csync/std/c_time.h b/src/csync/std/c_time.h index 67e7fcbad..3792198f8 100644 --- a/src/csync/std/c_time.h +++ b/src/csync/std/c_time.h @@ -21,6 +21,8 @@ #ifndef _C_TIME_H #define _C_TIME_H +#include "ocsynclib.h" + #ifdef __cplusplus extern "C" { #endif @@ -31,34 +33,7 @@ extern "C" { #include #endif -/** - * @brief Calculate time difference - * - * The c_tspecdiff function returns the time elapsed between time time1 and time - * time0 represented as timespec. - * - * @param time1 The time. - * @param time0 The time. - * - * @return time elapsed between time1 and time0. - */ -struct timespec c_tspecdiff(struct timespec time1, struct timespec time0); - -/** - * @brief Calculate time difference. - * - * The function returns the time elapsed between time clock1 and time - * clock0 represented as double (in seconds and milliseconds). - * - * @param clock1 The time. - * @param clock0 The time. - * - * @return time elapsed between clock1 and clock0 in seconds and - * milliseconds. - */ -double c_secdiff(struct timespec clock1, struct timespec clock0); - -int c_utimes(const char *uri, const struct timeval *times); +OCSYNC_EXPORT int c_utimes(const char *uri, const struct timeval *times); #ifdef __cplusplus } diff --git a/src/csync/vio/csync_vio_local_unix.cpp b/src/csync/vio/csync_vio_local_unix.cpp index b60fe99ce..f2620bbb6 100644 --- a/src/csync/vio/csync_vio_local_unix.cpp +++ b/src/csync/vio/csync_vio_local_unix.cpp @@ -31,11 +31,12 @@ #include "c_string.h" #include "c_utf8.h" #include "csync_util.h" -#include "csync_log.h" #include "csync_vio.h" #include "vio/csync_vio_local.h" +Q_LOGGING_CATEGORY(lcCSyncVIOLocal, "sync.csync.vio_local", QtInfoMsg) + /* * directory functions */ @@ -105,8 +106,7 @@ std::unique_ptr csync_vio_local_readdir(csync_vio_handle_t *d QByteArray fullPath = QByteArray() % const_cast(handle->path) % '/' % QByteArray() % const_cast(dirent->d_name); if (file_stat->path.isNull()) { file_stat->original_path = fullPath; - CSYNC_LOG(CSYNC_LOG_PRIORITY_WARN, "Invalid characters in file/directory name, please rename: \"%s\" (%s)", - dirent->d_name, handle->path); + qCWarning(lcCSyncVIOLocal) << "Invalid characters in file/directory name, please rename:" << dirent->d_name << handle->path; } /* Check for availability of d_type, see manpage. */ @@ -120,9 +120,9 @@ std::unique_ptr csync_vio_local_readdir(csync_vio_handle_t *d case DT_DIR: case DT_REG: if (dirent->d_type == DT_DIR) { - file_stat->type = CSYNC_FTW_TYPE_DIR; + file_stat->type = ItemTypeDirectory; } else { - file_stat->type = CSYNC_FTW_TYPE_FILE; + file_stat->type = ItemTypeFile; } break; default: @@ -135,7 +135,7 @@ std::unique_ptr csync_vio_local_readdir(csync_vio_handle_t *d if (_csync_vio_local_stat_mb(fullPath.constData(), file_stat.get()) < 0) { // Will get excluded by _csync_detect_update. - file_stat->type = CSYNC_FTW_TYPE_SKIP; + file_stat->type = ItemTypeSkip; } return file_stat; } @@ -160,17 +160,17 @@ static int _csync_vio_local_stat_mb(const mbchar_t *wuri, csync_file_stat_t *buf switch (sb.st_mode & S_IFMT) { case S_IFDIR: - buf->type = CSYNC_FTW_TYPE_DIR; + buf->type = ItemTypeDirectory; break; case S_IFREG: - buf->type = CSYNC_FTW_TYPE_FILE; + buf->type = ItemTypeFile; break; case S_IFLNK: case S_IFSOCK: - buf->type = CSYNC_FTW_TYPE_SLINK; + buf->type = ItemTypeSoftLink; break; default: - buf->type = CSYNC_FTW_TYPE_SKIP; + buf->type = ItemTypeSkip; break; } diff --git a/src/csync/vio/csync_vio_local_win.cpp b/src/csync/vio/csync_vio_local_win.cpp index 588575ed1..1c9a5907f 100644 --- a/src/csync/vio/csync_vio_local_win.cpp +++ b/src/csync/vio/csync_vio_local_win.cpp @@ -31,11 +31,11 @@ #include "c_lib.h" #include "c_utf8.h" #include "csync_util.h" -#include "csync_log.h" #include "csync_vio.h" #include "vio/csync_vio_local.h" +Q_LOGGING_CATEGORY(lcCSyncVIOLocal, "sync.csync.vio_local", QtInfoMsg) /* * directory functions @@ -172,21 +172,21 @@ std::unique_ptr csync_vio_local_readdir(csync_vio_handle_t *d // 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 = CSYNC_FTW_TYPE_SLINK; + 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 = CSYNC_FTW_TYPE_FILE; + file_stat->type = ItemTypeFile; } } else if (handle->ffd.dwFileAttributes & FILE_ATTRIBUTE_DEVICE || handle->ffd.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE || handle->ffd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) { - file_stat->type = CSYNC_FTW_TYPE_SKIP; + file_stat->type = ItemTypeSkip; } else if (handle->ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - file_stat->type = CSYNC_FTW_TYPE_DIR; + file_stat->type = ItemTypeDirectory; } else { - file_stat->type = CSYNC_FTW_TYPE_FILE; + file_stat->type = ItemTypeFile; } /* Check for the hidden flag */ @@ -204,7 +204,7 @@ std::unique_ptr csync_vio_local_readdir(csync_vio_handle_t *d if (_csync_vio_local_stat_mb(fullPath.data(), file_stat.get()) < 0) { // Will get excluded by _csync_detect_update. - file_stat->type = CSYNC_FTW_TYPE_SKIP; + file_stat->type = ItemTypeSkip; } return file_stat; @@ -235,13 +235,13 @@ static int _csync_vio_local_stat_mb(const mbchar_t *wuri, csync_file_stat_t *buf FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL ); if( h == INVALID_HANDLE_VALUE ) { - CSYNC_LOG(CSYNC_LOG_PRIORITY_CRIT, "CreateFileW failed on %ls", wuri); + qCCritical(lcCSyncVIOLocal, "CreateFileW failed on %ls", wuri); errno = GetLastError(); return -1; } if(!GetFileInformationByHandle( h, &fileInfo ) ) { - CSYNC_LOG(CSYNC_LOG_PRIORITY_CRIT, "GetFileInformationByHandle failed on %ls", wuri); + qCCritical(lcCSyncVIOLocal, "GetFileInformationByHandle failed on %ls", wuri); errno = GetLastError(); CloseHandle(h); return -1; diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 1ce5001b2..22b313b5d 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -1,19 +1,21 @@ project(gui) +find_package(Qt5 REQUIRED COMPONENTS Widgets) set(CMAKE_AUTOMOC TRUE) - +set(CMAKE_AUTOUIC TRUE) +set(CMAKE_AUTORCC TRUE) add_subdirectory(updater) #TODO Move resources files -qt_add_resources(MIRALL_RC_SRC ../../client.qrc) +set(MIRALL_RC_SRC ../../client.qrc) if (EXISTS "${OEM_THEME_DIR}/theme.qrc") - qt_add_resources(MIRALL_RC_SRC ${OEM_THEME_DIR}/theme.qrc) + list(APPEND MIRALL_RC_SRC ${OEM_THEME_DIR}/theme.qrc) set(theme_dir ${OEM_THEME_DIR}/theme) else() - qt_add_resources(MIRALL_RC_SRC ../../theme.qrc) + list(APPEND MIRALL_RC_SRC ../../theme.qrc) set(theme_dir ${CMAKE_SOURCE_DIR}/theme) endif() -set(client_UI +set(client_UI_SRCS accountsettings.ui folderwizardsourcepage.ui folderwizardtargetpage.ui @@ -41,8 +43,6 @@ set(client_UI wizard/owncloudwizardresultpage.ui ) -qt_wrap_ui(client_UI_SRCS ${client_UI}) - set(client_SRCS accountmanager.cpp accountsettings.cpp @@ -173,16 +173,7 @@ else() list(APPEND 3rdparty_SRC ../3rdparty/qtlockedfile/qtlockedfile_win.cpp ) endif() -set(3rdparty_INC - ${CMAKE_SOURCE_DIR}/src/3rdparty/QProgressIndicator - ${CMAKE_SOURCE_DIR}/src/3rdparty/qtlockedfile - ${CMAKE_SOURCE_DIR}/src/3rdparty/qtmacgoodies/src - ${CMAKE_SOURCE_DIR}/src/3rdparty/qtsingleapplication - ) - -include_directories(${3rdparty_INC}) - -IF( NOT WIN32 AND NOT APPLE AND WITH_DBUS AND LIBCLOUDPROVIDERS_FOUND) +IF( NOT WIN32 AND NOT APPLE AND LIBCLOUDPROVIDERS_FOUND) message("Building with libcloudproviderssupport") add_definitions(-DWITH_LIBCLOUDPROVIDERS) set(client_SRCS ${client_SRCS} cloudproviders/cloudprovidermanager.cpp) @@ -193,20 +184,10 @@ IF( NOT WIN32 AND NOT APPLE AND WITH_DBUS AND LIBCLOUDPROVIDERS_FOUND) include_directories(${LIBCLOUDPROVIDERS_INCLUDE_DIR}) ENDIF() - - - -# csync is required. -include_directories(${CMAKE_SOURCE_DIR}/src/csync - ${CMAKE_BINARY_DIR}/src/csync - ) -include_directories(../libsync ${CMAKE_CURRENT_BINARY_DIR}/../libsync) -include_directories(${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_CURRENT_BINARY_DIR} - ) - - -qt_add_translation(client_I18N ${TRANSLATIONS}) +find_package(Qt5LinguistTools) +if(Qt5LinguistTools_FOUND) + qt5_add_translation(client_I18N ${TRANSLATIONS}) +endif() IF( WIN32 ) configure_file( @@ -237,8 +218,11 @@ if(QTKEYCHAIN_FOUND OR QT5KEYCHAIN_FOUND) endif() # add executable icon on windows and osx -include( AddAppIconMacro ) -set(ownCloud_old ${ownCloud}) + +# UPSTREAM our ECMAddAppIcon.cmake then require that version here +# find_package(ECM 1.7.0 REQUIRED NO_MODULE) +# list(APPEND CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) +include(ECMAddAppIcon) # For historical reasons we can not use the application_shortname # for ownCloud but must rather set it manually. @@ -246,16 +230,12 @@ if (NOT DEFINED APPLICATION_ICON_NAME) set(APPLICATION_ICON_NAME ${APPLICATION_SHORTNAME}) endif() -kde4_add_app_icon( ownCloud "${theme_dir}/colored/${APPLICATION_ICON_NAME}-*.png") -list(APPEND final_src ${ownCloud}) -set(ownCloud ${ownCloud_old}) - -if (WITH_DBUS) - set(ADDITIONAL_APP_MODULES DBus) -endif(WITH_DBUS) -if (NOT NO_SHIBBOLETH) - list(APPEND ADDITIONAL_APP_MODULES WebKitWidgets) +file(GLOB_RECURSE OWNCLOUD_ICONS "${theme_dir}/colored/*-${APPLICATION_ICON_NAME}-icon*") +if(APPLE) + file(GLOB_RECURSE OWNCLOUD_SIDEBAR_ICONS "${theme_dir}/colored/*-${APPLICATION_ICON_NAME}-sidebar*") + MESSAGE(STATUS "OWNCLOUD_SIDEBAR_ICONS: ${APPLICATION_ICON_NAME}: ${OWNCLOUD_SIDEBAR_ICONS}") endif() +ecm_add_app_icon(final_src ICONS "${OWNCLOUD_ICONS}" SIDEBAR_ICONS "${OWNCLOUD_SIDEBAR_ICONS}" OUTFILE_BASE "${APPLICATION_ICON_NAME}") if(UNIX AND NOT APPLE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIE") @@ -268,14 +248,13 @@ if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") endif() if(NOT BUILD_OWNCLOUD_OSX_BUNDLE) - if(NOT WIN32) - file( GLOB _icons "${theme_dir}/colored/${APPLICATION_ICON_NAME}-icon-*.png" ) - foreach( _file ${_icons} ) - string( REPLACE "${theme_dir}/colored/${APPLICATION_ICON_NAME}-icon-" "" _res ${_file} ) - string( REPLACE ".png" "" _res ${_res} ) - install( FILES ${_file} RENAME ${APPLICATION_ICON_NAME}.png DESTINATION ${DATADIR}/icons/hicolor/${_res}x${_res}/apps ) - endforeach( _file ) + file(GLOB _icons "${theme_dir}/colored/*-${APPLICATION_ICON_NAME}-icon.png") + foreach(_file ${_icons}) + string(REPLACE "${theme_dir}/colored/" "" _res ${_file}) + string(REPLACE "-${APPLICATION_ICON_NAME}-icon.png" "" _res ${_res}) + install(FILES ${_file} RENAME ${APPLICATION_ICON_NAME}.png DESTINATION ${DATADIR}/icons/hicolor/${_res}x${_res}/apps) + endforeach(_file) endif(NOT WIN32) install(FILES ${client_I18N} DESTINATION ${SHAREDIR}/${APPLICATION_EXECUTABLE}/i18n) @@ -284,14 +263,12 @@ if(NOT BUILD_OWNCLOUD_OSX_BUNDLE) # add_executable( ${APPLICATION_EXECUTABLE} main.cpp ${final_src}) add_executable( ${APPLICATION_EXECUTABLE} WIN32 main.cpp ${final_src}) - qt5_use_modules(${APPLICATION_EXECUTABLE} Widgets Network Xml Sql ${ADDITIONAL_APP_MODULES}) else() # set(CMAKE_INSTALL_PREFIX ".") # Examples use /Applications. hurmpf. set(MACOSX_BUNDLE_ICON_FILE "ownCloud.icns") # we must add MACOSX_BUNDLE only if building a bundle add_executable( ${APPLICATION_EXECUTABLE} WIN32 MACOSX_BUNDLE main.cpp ${final_src}) - qt5_use_modules(${APPLICATION_EXECUTABLE} Widgets Network Xml Sql ${ADDITIONAL_APP_MODULES}) set (QM_DIR ${OWNCLOUD_OSX_BUNDLE}/Contents/Resources/Translations) install(FILES ${client_I18N} DESTINATION ${QM_DIR}) @@ -308,18 +285,18 @@ else() install(FILES ${qtkeychain_I18N} DESTINATION ${QM_DIR}) endif() -add_library(updater STATIC ${updater_SRCS} ${updaterMoc}) -target_link_libraries(updater ${synclib_NAME}) -qt5_use_modules(updater Widgets Network Xml) +add_library(updater STATIC ${updater_SRCS}) +target_link_libraries(updater ${synclib_NAME} Qt5::Widgets Qt5::Network Qt5::Xml) +target_include_directories(updater PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) set_target_properties( ${APPLICATION_EXECUTABLE} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${BIN_OUTPUT_DIRECTORY} ) # Only relevant for Linux? On OS X it by default properly checks in the bundle directory next to the exe set_target_properties( ${APPLICATION_EXECUTABLE} PROPERTIES - INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${LIB_INSTALL_DIR}/${APPLICATION_EXECUTABLE};${CMAKE_INSTALL_RPATH}" ) + INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${LIB_INSTALL_DIR}/${APPLICATION_EXECUTABLE};${CMAKE_INSTALL_RPATH}" ) -target_link_libraries( ${APPLICATION_EXECUTABLE} ${QT_LIBRARIES} ) +target_link_libraries( ${APPLICATION_EXECUTABLE} Qt5::Widgets Qt5::Network Qt5::Xml) target_link_libraries( ${APPLICATION_EXECUTABLE} ${synclib_NAME} ) target_link_libraries( ${APPLICATION_EXECUTABLE} updater ) target_link_libraries( ${APPLICATION_EXECUTABLE} ${OS_SPECIFIC_LINK_LIBRARIES} ) @@ -338,9 +315,33 @@ IF( LIBCLOUDPROVIDERS_FOUND ) ENDIF() +target_include_directories(${APPLICATION_EXECUTABLE} PRIVATE + ${CMAKE_SOURCE_DIR}/src/3rdparty/QProgressIndicator + ${CMAKE_SOURCE_DIR}/src/3rdparty/qtlockedfile + ${CMAKE_SOURCE_DIR}/src/3rdparty/qtmacgoodies/src + ${CMAKE_SOURCE_DIR}/src/3rdparty/qtsingleapplication + ${CMAKE_CURRENT_BINARY_DIR} +) + +## handle DBUS for Fdo notifications +if( UNIX AND NOT APPLE ) + find_package(Qt5 COMPONENTS DBus) + target_link_libraries( ${APPLICATION_EXECUTABLE} Qt5::DBus) + target_compile_definitions(${APPLICATION_EXECUTABLE} PRIVATE "USE_FDO_NOTIFICATIONS") +endif() + +if (APPLE) + find_package(Qt5 COMPONENTS MacExtras) + target_link_libraries( ${APPLICATION_EXECUTABLE} Qt5::MacExtras) +endif() + +if (NOT NO_SHIBBOLETH) + find_package(Qt5 COMPONENTS WebKitWidgets) + target_link_libraries( ${APPLICATION_EXECUTABLE} Qt5::WebKitWidgets) +endif() + if(WITH_CRASHREPORTER) - target_link_libraries( ${APPLICATION_EXECUTABLE} crashreporter-handler) - include_directories( "../3rdparty/libcrashreporter-qt/src/" ) + target_link_libraries(${APPLICATION_EXECUTABLE} crashreporter-handler) if(UNIX AND NOT MAC) find_package(Threads REQUIRED) @@ -348,6 +349,10 @@ if(WITH_CRASHREPORTER) endif() endif() +# application.cpp still uses QDesktopServices::storageLocation +target_compile_definitions(${APPLICATION_EXECUTABLE} PRIVATE "QT_DISABLE_DEPRECATED_BEFORE=0") + + install(TARGETS ${APPLICATION_EXECUTABLE} RUNTIME DESTINATION bin LIBRARY DESTINATION lib diff --git a/src/gui/accountstate.cpp b/src/gui/accountstate.cpp index 0b19f3f88..a4b44b6a7 100644 --- a/src/gui/accountstate.cpp +++ b/src/gui/accountstate.cpp @@ -151,6 +151,13 @@ void AccountState::signOutByUi() setState(SignedOut); } +void AccountState::freshConnectionAttempt() +{ + if (isConnected()) + setState(Disconnected); + checkConnectivity(); +} + void AccountState::signIn() { if (_state == SignedOut) { diff --git a/src/gui/accountstate.h b/src/gui/accountstate.h index 3256424af..0ee6ec96a 100644 --- a/src/gui/accountstate.h +++ b/src/gui/accountstate.h @@ -105,6 +105,16 @@ public: * for the account and forgets the password. */ void signOutByUi(); + /** Tries to connect from scratch. + * + * Does nothing for signed out accounts. + * Connected accounts will be disconnected and try anew. + * Disconnected accounts will go to checkConnectivity(). + * + * Useful for when network settings (proxy) change. + */ + void freshConnectionAttempt(); + /// Move from SignedOut state to Disconnected (attempting to connect) void signIn(); diff --git a/src/gui/application.cpp b/src/gui/application.cpp index cbdb8514c..a564fd372 100644 --- a/src/gui/application.cpp +++ b/src/gui/application.cpp @@ -128,7 +128,15 @@ Application::Application(int &argc, char **argv) if (!QFileInfo(confDir).exists()) { // Migrate from version <= 2.4 setApplicationName(_theme->appNameGUI()); +#ifndef QT_WARNING_DISABLE_DEPRECATED // Was added in Qt 5.9 +#define QT_WARNING_DISABLE_DEPRECATED QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations") +#endif + QT_WARNING_PUSH + QT_WARNING_DISABLE_DEPRECATED + // We need to use the deprecated QDesktopServices::storageLocation because of its Qt4 + // behavior of adding "data" to the path QString oldDir = QDesktopServices::storageLocation(QDesktopServices::DataLocation); + QT_WARNING_POP setApplicationName(_theme->appName()); if (QFileInfo(oldDir).isDir()) { qCInfo(lcApplication) << "Migrating old config from" << oldDir << "to" << confDir; diff --git a/src/gui/application.h b/src/gui/application.h index 47f7bee50..c61f7645e 100644 --- a/src/gui/application.h +++ b/src/gui/application.h @@ -77,8 +77,6 @@ protected: void parseOptions(const QStringList &); void setupTranslations(); void setupLogging(); - void enterNextLogFile(); - bool checkConfigExists(bool openSettings); signals: void folderRemoved(); diff --git a/src/gui/folder.cpp b/src/gui/folder.cpp index d854f92ca..f0664d218 100644 --- a/src/gui/folder.cpp +++ b/src/gui/folder.cpp @@ -52,7 +52,6 @@ Folder::Folder(const FolderDefinition &definition, , _accountState(accountState) , _definition(definition) , _csyncUnavail(false) - , _proxyDirty(true) , _lastSyncDuration(0) , _consecutiveFailingSyncs(0) , _consecutiveFollowUpSyncs(0) @@ -116,7 +115,6 @@ Folder::~Folder() _engine.reset(); } - void Folder::checkLocalPath() { const QFileInfo fi(_definition.localPath); @@ -600,22 +598,9 @@ bool Folder::reloadExcludes() return _engine->excludedFiles().reloadExcludeFiles(); } -void Folder::setProxyDirty(bool value) -{ - _proxyDirty = value; -} - -bool Folder::proxyDirty() -{ - return _proxyDirty; -} - void Folder::startSync(const QStringList &pathList) { Q_UNUSED(pathList) - if (proxyDirty()) { - setProxyDirty(false); - } if (isBusy()) { qCCritical(lcFolder) << "ERROR csync is still running and new sync requested."; diff --git a/src/gui/folder.h b/src/gui/folder.h index 8ffa1e6d5..51ae7cd58 100644 --- a/src/gui/folder.h +++ b/src/gui/folder.h @@ -171,12 +171,6 @@ public: */ SyncResult syncResult() const; - /** - * set the config file name. - */ - void setConfigFile(const QString &); - QString configFile(); - /** * This is called if the sync folder definition is removed. Do cleanups here. */ @@ -276,9 +270,6 @@ public slots: */ void startSync(const QStringList &pathList = QStringList()); - void setProxyDirty(bool value); - bool proxyDirty(); - int slotDiscardDownloadProgress(); int downloadInfoCount(); int slotWipeErrorBlacklist(); @@ -351,7 +342,6 @@ private: SyncResult _syncResult; QScopedPointer _engine; bool _csyncUnavail; - bool _proxyDirty; QPointer _requestEtagJob; QString _lastEtag; QElapsedTimer _timeSinceLastSyncDone; diff --git a/src/gui/folderman.cpp b/src/gui/folderman.cpp index ca73ea0ec..c1b684f59 100644 --- a/src/gui/folderman.cpp +++ b/src/gui/folderman.cpp @@ -1085,12 +1085,10 @@ bool FolderMan::startFromScratch(const QString &localFolder) return true; } -void FolderMan::setDirtyProxy(bool value) +void FolderMan::setDirtyProxy() { foreach (Folder *f, _folderMap.values()) { if (f) { - f->setProxyDirty(value); - if (f->accountState() && f->accountState()->account() && f->accountState()->account()->networkAccessManager()) { // Need to do this so we do not use the old determined system proxy diff --git a/src/gui/folderman.h b/src/gui/folderman.h index 415118689..44291dd74 100644 --- a/src/gui/folderman.h +++ b/src/gui/folderman.h @@ -178,7 +178,7 @@ public: /** Queues all folders for syncing. */ void scheduleAllFolders(); - void setDirtyProxy(bool value = true); + void setDirtyProxy(); void setDirtyNetworkLimits(); /** diff --git a/src/gui/folderstatusmodel.cpp b/src/gui/folderstatusmodel.cpp index d51b987dc..6c2925614 100644 --- a/src/gui/folderstatusmodel.cpp +++ b/src/gui/folderstatusmodel.cpp @@ -298,7 +298,7 @@ bool FolderStatusModel::setData(const QModelIndex &index, const QVariant &value, // also check all the children for (int i = 0; i < info->_subs.count(); ++i) { if (info->_subs[i]._checked != Qt::Checked) { - setData(index.child(i, 0), Qt::Checked, Qt::CheckStateRole); + setData(this->index(i, 0, index), Qt::Checked, Qt::CheckStateRole); } } } @@ -313,7 +313,7 @@ bool FolderStatusModel::setData(const QModelIndex &index, const QVariant &value, // Uncheck all the children for (int i = 0; i < info->_subs.count(); ++i) { if (info->_subs[i]._checked != Qt::Unchecked) { - setData(index.child(i, 0), Qt::Unchecked, Qt::CheckStateRole); + setData(this->index(i, 0, index), Qt::Unchecked, Qt::CheckStateRole); } } } @@ -703,7 +703,7 @@ void FolderStatusModel::slotUpdateDirectories(const QStringList &list) } for (auto it = undecidedIndexes.begin(); it != undecidedIndexes.end(); ++it) { - suggestExpand(idx.child(*it, 0)); + suggestExpand(index(*it, 0, idx)); } /* We need lambda function for the following code. diff --git a/src/gui/folderwizard.cpp b/src/gui/folderwizard.cpp index e42a4dda8..549d2095f 100644 --- a/src/gui/folderwizard.cpp +++ b/src/gui/folderwizard.cpp @@ -568,10 +568,12 @@ void FolderWizard::resizeEvent(QResizeEvent *event) QWizard::resizeEvent(event); // workaround for QTBUG-22819: when the error label word wrap, the minimum height is not adjusted - int hfw = currentPage()->heightForWidth(currentPage()->width()); - if (currentPage()->height() < hfw) { - currentPage()->setMinimumSize(currentPage()->minimumSizeHint().width(), hfw); - setTitleFormat(titleFormat()); // And another workaround for QTBUG-3396 + 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 + } } } diff --git a/src/gui/issueswidget.cpp b/src/gui/issueswidget.cpp index c1692473e..93dbe748c 100644 --- a/src/gui/issueswidget.cpp +++ b/src/gui/issueswidget.cpp @@ -39,6 +39,12 @@ namespace OCC { +/** + * If more issues are reported than this they will not show up + * to avoid performance issues around sorting this many issues. + */ +static const int maxIssueCount = 50000; + IssuesWidget::IssuesWidget(QWidget *parent) : QWidget(parent) , _ui(new Ui::IssuesWidget) @@ -100,6 +106,14 @@ IssuesWidget::IssuesWidget(QWidget *parent) #if defined(Q_OS_MAC) _ui->_treeWidget->setMinimumWidth(400); #endif + + _reenableSorting.setInterval(5000); + connect(&_reenableSorting, &QTimer::timeout, this, + [this]() { _ui->_treeWidget->setSortingEnabled(true); }); + + _ui->_tooManyIssuesWarning->hide(); + connect(this, &IssuesWidget::issueCountUpdated, this, + [this](int count) { _ui->_tooManyIssuesWarning->setVisible(count >= maxIssueCount); }); } IssuesWidget::~IssuesWidget() @@ -140,7 +154,7 @@ void IssuesWidget::cleanItems(const QString &folder) int itemCnt = _ui->_treeWidget->topLevelItemCount(); for (int cnt = itemCnt - 1; cnt >= 0; cnt--) { QTreeWidgetItem *item = _ui->_treeWidget->topLevelItem(cnt); - QString itemFolder = item->data(2, Qt::UserRole).toString(); + QString itemFolder = ProtocolItem::folderName(item); if (itemFolder == folder) { delete item; } @@ -157,11 +171,17 @@ void IssuesWidget::addItem(QTreeWidgetItem *item) if (!item) return; - int insertLoc = 0; + int count = _ui->_treeWidget->topLevelItemCount(); + if (count >= maxIssueCount) + return; + + _ui->_treeWidget->setSortingEnabled(false); + _reenableSorting.start(); // Insert item specific errors behind the others + int insertLoc = 0; if (!item->text(1).isEmpty()) { - for (int i = 0; i < _ui->_treeWidget->topLevelItemCount(); ++i) { + for (int i = 0; i < count; ++i) { if (_ui->_treeWidget->topLevelItem(i)->text(1).isEmpty()) { insertLoc = i + 1; } else { @@ -177,11 +197,8 @@ void IssuesWidget::addItem(QTreeWidgetItem *item) void IssuesWidget::slotOpenFile(QTreeWidgetItem *item, int) { - QString folderName = item->data(2, Qt::UserRole).toString(); QString fileName = item->text(1); - - Folder *folder = FolderMan::instance()->folder(folderName); - if (folder) { + if (Folder *folder = ProtocolItem::folder(item)) { // folder->path() always comes back with trailing path QString fullPath = folder->path() + fileName; if (QFile(fullPath).exists()) { @@ -200,7 +217,7 @@ void IssuesWidget::slotProgressInfo(const QString &folder, const ProgressInfo &p void IssuesWidget::slotItemCompleted(const QString &folder, const SyncFileItemPtr &item) { - if (!item->hasErrorStatus()) + if (!item->showInIssuesTab()) return; QTreeWidgetItem *line = ProtocolItem::create(folder, *item); if (!line) @@ -268,13 +285,13 @@ bool IssuesWidget::shouldBeVisible(QTreeWidgetItem *item, AccountState *filterAc const QString &filterFolderAlias) const { bool visible = true; - auto status = item->data(3, Qt::UserRole); + auto status = ProtocolItem::status(item); visible &= (_ui->showIgnores->isChecked() || status != SyncFileItem::FileIgnored); visible &= (_ui->showWarnings->isChecked() || (status != SyncFileItem::SoftError && status != SyncFileItem::Restoration)); - auto folderalias = item->data(2, Qt::UserRole).toString(); + auto folderalias = ProtocolItem::folderName(item); if (filterAccount) { auto folder = FolderMan::instance()->folder(folderalias); visible &= folder && folder->accountState() == filterAccount; @@ -423,7 +440,7 @@ void IssuesWidget::addErrorWidget(QTreeWidgetItem *item, const QString &message, auto button = new QPushButton("Retry all uploads", widget); button->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding); - auto folderAlias = item->data(2, Qt::UserRole).toString(); + auto folderAlias = ProtocolItem::folderName(item); connect(button, &QPushButton::clicked, this, [this, folderAlias]() { retryInsufficentRemoteStorageErrors(folderAlias); }); layout->addWidget(button); diff --git a/src/gui/issueswidget.h b/src/gui/issueswidget.h index 9f295b816..443fc9451 100644 --- a/src/gui/issueswidget.h +++ b/src/gui/issueswidget.h @@ -18,6 +18,7 @@ #include #include #include +#include #include "progressdispatcher.h" #include "owncloudgui.h" @@ -85,6 +86,9 @@ private: /// Wipes all insufficient remote storgage blacklist entries void retryInsufficentRemoteStorageErrors(const QString &folderAlias); + /// Each insert disables sorting, this timer reenables it + QTimer _reenableSorting; + Ui::IssuesWidget *_ui; }; } diff --git a/src/gui/issueswidget.ui b/src/gui/issueswidget.ui index 2d1161e78..73acf809a 100644 --- a/src/gui/issueswidget.ui +++ b/src/gui/issueswidget.ui @@ -127,6 +127,13 @@ + + + + There were too many issues. Not all will be visible here. + + + diff --git a/src/gui/networksettings.cpp b/src/gui/networksettings.cpp index a981d80c5..6305dc3a3 100644 --- a/src/gui/networksettings.cpp +++ b/src/gui/networksettings.cpp @@ -20,8 +20,11 @@ #include "application.h" #include "configfile.h" #include "folderman.h" +#include "accountmanager.h" #include +#include +#include namespace OCC { @@ -73,6 +76,10 @@ NetworkSettings::NetworkSettings(QWidget *parent) 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(); } NetworkSettings::~NetworkSettings() @@ -150,6 +157,7 @@ void NetworkSettings::saveProxySettings() { ConfigFile cfgFile; + checkEmptyProxyHost(); if (_ui->noProxyRadioButton->isChecked()) { cfgFile.setProxyType(QNetworkProxy::NoProxy); } else if (_ui->systemProxyRadioButton->isChecked()) { @@ -169,7 +177,11 @@ void NetworkSettings::saveProxySettings() // ...and set the folders dirty, they refresh their proxy next time they // start the sync. - FolderMan::instance()->setDirtyProxy(true); + FolderMan::instance()->setDirtyProxy(); + + for (auto account : AccountManager::instance()->accounts()) { + account->freshConnectionAttempt(); + } } void NetworkSettings::saveBWLimitSettings() @@ -196,4 +208,13 @@ void NetworkSettings::saveBWLimitSettings() FolderMan::instance()->setDirtyNetworkLimits(); } +void NetworkSettings::checkEmptyProxyHost() +{ + if (_ui->hostLineEdit->isEnabled() && _ui->hostLineEdit->text().isEmpty()) { + _ui->hostLineEdit->setStyleSheet("border: 1px solid red"); + } else { + _ui->hostLineEdit->setStyleSheet(QString()); + } +} + } // namespace OCC diff --git a/src/gui/networksettings.h b/src/gui/networksettings.h index 34419a261..7201d1109 100644 --- a/src/gui/networksettings.h +++ b/src/gui/networksettings.h @@ -41,6 +41,9 @@ private slots: void saveProxySettings(); void saveBWLimitSettings(); + /// Red marking of host field if empty and enabled + void checkEmptyProxyHost(); + private: void loadProxySettings(); void loadBWLimitSettings(); diff --git a/src/gui/protocolwidget.cpp b/src/gui/protocolwidget.cpp index 7324a1026..8a8dc5d8a 100644 --- a/src/gui/protocolwidget.cpp +++ b/src/gui/protocolwidget.cpp @@ -43,6 +43,56 @@ QString ProtocolItem::timeString(QDateTime dt, QLocale::FormatType format) return loc.toString(dt, dtFormat); } +QString ProtocolItem::folderName(const QTreeWidgetItem *item) +{ + return item->data(2, Qt::UserRole).toString(); +} + +void ProtocolItem::setFolderName(QTreeWidgetItem *item, const QString &folderName) +{ + item->setData(2, Qt::UserRole, folderName); +} + +QString ProtocolItem::filePath(const QTreeWidgetItem *item) +{ + return item->toolTip(1); +} + +void ProtocolItem::setFilePath(QTreeWidgetItem *item, const QString &filePath) +{ + item->setToolTip(1, filePath); +} + +QDateTime ProtocolItem::timestamp(const QTreeWidgetItem *item) +{ + return item->data(0, Qt::UserRole).toDateTime(); +} + +void ProtocolItem::setTimestamp(QTreeWidgetItem *item, const QDateTime ×tamp) +{ + item->setData(0, Qt::UserRole, timestamp); +} + +SyncFileItem::Status ProtocolItem::status(const QTreeWidgetItem *item) +{ + return static_cast(item->data(3, Qt::UserRole).toInt()); +} + +void ProtocolItem::setStatus(QTreeWidgetItem *item, SyncFileItem::Status status) +{ + item->setData(3, Qt::UserRole, status); +} + +quint64 ProtocolItem::size(const QTreeWidgetItem *item) +{ + return item->data(4, Qt::UserRole).toULongLong(); +} + +void ProtocolItem::setSize(QTreeWidgetItem *item, quint64 size) +{ + item->setData(4, Qt::UserRole, size); +} + ProtocolItem *ProtocolItem::create(const QString &folder, const SyncFileItem &item) { auto f = FolderMan::instance()->folder(folder); @@ -84,13 +134,14 @@ ProtocolItem *ProtocolItem::create(const QString &folder, const SyncFileItem &it // Warning: The data and tooltips on the columns define an implicit // interface and can only be changed with care. twitem->setData(0, Qt::SizeHintRole, QSize(0, ActivityItemDelegate::rowHeight())); - twitem->setData(0, Qt::UserRole, timestamp); twitem->setIcon(0, icon); twitem->setToolTip(0, longTimeStr); - twitem->setToolTip(1, item._file); - twitem->setData(2, Qt::UserRole, folder); twitem->setToolTip(3, message); - twitem->setData(3, Qt::UserRole, item._status); + setTimestamp(twitem, timestamp); + setFilePath(twitem, item._file); // also sets toolTip(1) + setFolderName(twitem, folder); + setStatus(twitem, item._status); + setSize(twitem, item._size); return twitem; } @@ -100,22 +151,22 @@ SyncJournalFileRecord ProtocolItem::syncJournalRecord(QTreeWidgetItem *item) auto f = folder(item); if (!f) return rec; - f->journalDb()->getFileRecord(item->toolTip(1), &rec); + f->journalDb()->getFileRecord(filePath(item), &rec); return rec; } Folder *ProtocolItem::folder(QTreeWidgetItem *item) { - return FolderMan::instance()->folder(item->data(2, Qt::UserRole).toString()); + return FolderMan::instance()->folder(folderName(item)); } void ProtocolItem::openContextMenu(QPoint globalPos, QTreeWidgetItem *item, QWidget *parent) { - auto f = ProtocolItem::folder(item); + auto f = folder(item); if (!f) return; AccountPtr account = f->accountState()->account(); - auto rec = ProtocolItem::syncJournalRecord(item); + auto rec = syncJournalRecord(item); // rec might not be valid auto menu = new QMenu(parent); @@ -145,14 +196,16 @@ void ProtocolItem::openContextMenu(QPoint globalPos, QTreeWidgetItem *item, QWid bool ProtocolItem::operator<(const QTreeWidgetItem &other) const { int column = treeWidget()->sortColumn(); - if (column != 0) { - return QTreeWidgetItem::operator<(other); + if (column == 0) { + // Items with empty "File" column are larger than others, + // otherwise sort by time (this uses lexicographic ordering) + return std::forward_as_tuple(text(1).isEmpty(), timestamp(this)) + < std::forward_as_tuple(other.text(1).isEmpty(), timestamp(&other)); + } else if (column == 4) { + return size(this) < size(&other); } - // Items with empty "File" column are larger than others, - // otherwise sort by time (this uses lexicographic ordering) - return std::forward_as_tuple(text(1).isEmpty(), data(0, Qt::UserRole).toDateTime()) - < std::forward_as_tuple(other.text(1).isEmpty(), other.data(0, Qt::UserRole).toDateTime()); + return QTreeWidgetItem::operator<(other); } ProtocolWidget::ProtocolWidget(QWidget *parent) @@ -243,11 +296,8 @@ void ProtocolWidget::slotItemContextMenu(const QPoint &pos) void ProtocolWidget::slotOpenFile(QTreeWidgetItem *item, int) { - QString folderName = item->data(2, Qt::UserRole).toString(); QString fileName = item->text(1); - - Folder *folder = FolderMan::instance()->folder(folderName); - if (folder) { + if (Folder *folder = ProtocolItem::folder(item)) { // folder->path() always comes back with trailing path QString fullPath = folder->path() + fileName; if (QFile(fullPath).exists()) { @@ -258,7 +308,7 @@ void ProtocolWidget::slotOpenFile(QTreeWidgetItem *item, int) void ProtocolWidget::slotItemCompleted(const QString &folder, const SyncFileItemPtr &item) { - if (item->hasErrorStatus()) + if (!item->showInProtocolTab()) return; QTreeWidgetItem *line = ProtocolItem::create(folder, *item); if (line) { diff --git a/src/gui/protocolwidget.h b/src/gui/protocolwidget.h index 147ba4d1f..d8612634d 100644 --- a/src/gui/protocolwidget.h +++ b/src/gui/protocolwidget.h @@ -49,6 +49,18 @@ public: static ProtocolItem *create(const QString &folder, const SyncFileItem &item); static QString timeString(QDateTime dt, QLocale::FormatType format = QLocale::NarrowFormat); + // accessors for extra data stored in the item + static QString folderName(const QTreeWidgetItem *item); + static void setFolderName(QTreeWidgetItem *item, const QString &folderName); + static QString filePath(const QTreeWidgetItem *item); + static void setFilePath(QTreeWidgetItem *item, const QString &filePath); + static QDateTime timestamp(const QTreeWidgetItem *item); + static void setTimestamp(QTreeWidgetItem *item, const QDateTime ×tamp); + static SyncFileItem::Status status(const QTreeWidgetItem *item); + static void setStatus(QTreeWidgetItem *item, SyncFileItem::Status status); + static quint64 size(const QTreeWidgetItem *item); + static void setSize(QTreeWidgetItem *item, quint64 size); + static SyncJournalFileRecord syncJournalRecord(QTreeWidgetItem *item); static Folder *folder(QTreeWidgetItem *item); diff --git a/src/gui/sharelinkwidget.cpp b/src/gui/sharelinkwidget.cpp index 7838e04b5..0d938bd3d 100644 --- a/src/gui/sharelinkwidget.cpp +++ b/src/gui/sharelinkwidget.cpp @@ -58,7 +58,9 @@ ShareLinkWidget::ShareLinkWidget(AccountPtr account, //Is this a file or folder? QFileInfo fi(localPath); _isFile = fi.isFile(); - _ui->nameLineEdit->setText(tr("%1 link").arg(fi.fileName())); + + // Note: the share name cannot be longer than 64 characters + _ui->nameLineEdit->setText(tr("Public link")); // the following progress indicator widgets are added to layouts which makes them // automatically deleted once the dialog dies. @@ -222,11 +224,10 @@ void ShareLinkWidget::slotSharesFetched(const QList> &shar // 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(), SIGNAL(expireDateSet()), SLOT(slotExpireSet())); - connect(share.data(), SIGNAL(publicUploadSet()), SLOT(slotPermissionsSet())); - connect(share.data(), SIGNAL(passwordSet()), SLOT(slotPasswordSet())); - connect(share.data(), SIGNAL(passwordSetError(int, QString)), SLOT(slotPasswordSetError(int, QString))); connect(share.data(), &Share::permissionsSet, this, &ShareLinkWidget::slotPermissionsSet); + connect(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(); diff --git a/src/gui/socketapi.cpp b/src/gui/socketapi.cpp index 0cdc9f0de..f4e7a85e9 100644 --- a/src/gui/socketapi.cpp +++ b/src/gui/socketapi.cpp @@ -48,15 +48,13 @@ #include -#include - #include // 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.0" +#define MIRALL_SOCKET_API_VERSION "1.1" static inline QString removeTrailingSlash(QString path) { @@ -537,7 +535,7 @@ void SocketApi::emailPrivateLink(const QString &link) const 0); } -void SocketApi::command_GET_STRINGS(const QString &, SocketListener *listener) +void SocketApi::command_GET_STRINGS(const QString &argument, SocketListener *listener) { static std::array, 5> strings { { { "SHARE_MENU_TITLE", tr("Share...") }, @@ -547,11 +545,37 @@ void SocketApi::command_GET_STRINGS(const QString &, SocketListener *listener) } }; listener->sendMessage(QString("GET_STRINGS:BEGIN")); for (auto key_value : strings) { - listener->sendMessage(QString("STRING:%1:%2").arg(key_value.first, key_value.second)); + if (argument.isEmpty() || argument == QLatin1String(key_value.first)) { + listener->sendMessage(QString("STRING:%1:%2").arg(key_value.first, key_value.second)); + } } listener->sendMessage(QString("GET_STRINGS:END")); } +void SocketApi::command_GET_MENU_ITEMS(const QString &argument, OCC::SocketListener *listener) +{ + listener->sendMessage(QString("GET_MENU_ITEMS:BEGIN")); + bool hasSeveralFiles = argument.contains(QLatin1Char('\x1e')); // Record Separator + Folder *syncFolder = hasSeveralFiles ? nullptr : FolderMan::instance()->folderForPath(argument); + if (syncFolder) { + QString systemPath = QDir::cleanPath(argument); + if (systemPath.endsWith(QLatin1Char('/'))) { + systemPath.truncate(systemPath.length() - 1); + } + QString relativePath = systemPath.mid(syncFolder->cleanPath().length() + 1); + + SyncJournalFileRecord rec; + if (syncFolder->accountState()->isConnected() && syncFolder->journalDb()->getFileRecord(relativePath, &rec) && rec.isValid()) { + // If the file is on the DB, it is on the server + // TODO: check if sharing is allowed + listener->sendMessage(QLatin1String("MENU_ITEM:SHARE::") + tr("Share...")); + listener->sendMessage(QLatin1String("MENU_ITEM:COPY_PRIVATE_LINK::") + tr("Copy private link to clipboard")); + listener->sendMessage(QLatin1String("MENU_ITEM:EMAIL_PRIVATE_LINK::") + tr("Send private link by email...")); + } + } + listener->sendMessage(QString("GET_MENU_ITEMS:END")); +} + QString SocketApi::buildRegisterPathMessage(const QString &path) { QFileInfo fi(path); diff --git a/src/gui/socketapi.h b/src/gui/socketapi.h index e50a68fb7..04bb418e9 100644 --- a/src/gui/socketapi.h +++ b/src/gui/socketapi.h @@ -86,6 +86,15 @@ private: /** Sends translated/branded strings that may be useful to the integration */ Q_INVOKABLE void command_GET_STRINGS(const QString &argument, SocketListener *listener); + /** Send the list of menu item. (added in version 1.1) + * argument is a list of files for which the menu should be shown, separated by '\x1e' + * Reply with GET_MENU_ITEMS:BEGIN + * followed by several MENU_ITEM:[Action]:[flag]:[Text] + * If flag contains 'd', the menu should be disabled + * and ends with GET_MENU_ITEMS:END + */ + Q_INVOKABLE void command_GET_MENU_ITEMS(const QString &argument, SocketListener *listener); + QString buildRegisterPathMessage(const QString &path); QSet _registeredAliases; diff --git a/src/gui/updater/updater.cpp b/src/gui/updater/updater.cpp index 6249ebc3e..868595a42 100644 --- a/src/gui/updater/updater.cpp +++ b/src/gui/updater/updater.cpp @@ -13,6 +13,7 @@ */ #include +#include #include #include "updater/updater.h" @@ -39,7 +40,7 @@ Updater *Updater::instance() return _instance; } -QUrl Updater::addQueryParams(const QUrl &url) +QUrlQuery Updater::getQueryParams() { QUrlQuery query; Theme *theme = Theme::instance(); @@ -64,7 +65,8 @@ QUrl Updater::addQueryParams(const QUrl &url) QString suffix = QString::fromLatin1(MIRALL_STRINGIFY(MIRALL_VERSION_SUFFIX)); query.addQueryItem(QLatin1String("versionsuffix"), suffix); - if (suffix.startsWith("nightly") + if (suffix.startsWith("daily") + || suffix.startsWith("nightly") || suffix.startsWith("alpha") || suffix.startsWith("rc") || suffix.startsWith("beta")) { @@ -73,9 +75,7 @@ QUrl Updater::addQueryParams(const QUrl &url) // to beta channel } - QUrl paramUrl = url; - paramUrl.setQuery(query); - return paramUrl; + return query; } @@ -107,9 +107,16 @@ Updater *Updater::create() qCWarning(lcUpdater) << "Not a valid updater URL, will not do update check"; return 0; } - updateBaseUrl = addQueryParams(updateBaseUrl); + + auto urlQuery = getQueryParams(); + +#if defined(Q_OS_MAC) && defined(HAVE_SPARKLE) + urlQuery.addQueryItem(QLatin1String("sparkle"), QLatin1String("true")); +#endif + + updateBaseUrl.setQuery(urlQuery); + #if defined(Q_OS_MAC) && defined(HAVE_SPARKLE) - updateBaseUrl.addQueryItem(QLatin1String("sparkle"), QLatin1String("true")); return new SparkleUpdater(updateBaseUrl.toString()); #elif defined(Q_OS_WIN32) // the best we can do is notify about updates diff --git a/src/gui/updater/updater.h b/src/gui/updater/updater.h index 77f322638..04d18ac85 100644 --- a/src/gui/updater/updater.h +++ b/src/gui/updater/updater.h @@ -19,6 +19,7 @@ #include class QUrl; +class QUrlQuery; namespace OCC { @@ -50,7 +51,7 @@ protected: private: static QString getSystemInfo(); - static QUrl addQueryParams(const QUrl &url); + static QUrlQuery getQueryParams(); static Updater *create(); static Updater *_instance; }; diff --git a/src/libsync/CMakeLists.txt b/src/libsync/CMakeLists.txt index 390723044..b58cf9a95 100644 --- a/src/libsync/CMakeLists.txt +++ b/src/libsync/CMakeLists.txt @@ -1,11 +1,6 @@ project(libsync) set(CMAKE_AUTOMOC TRUE) -include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) -# csync is required. -include_directories(${CMAKE_SOURCE_DIR}/src/csync - ${CMAKE_BINARY_DIR}/src/csync - ) if ( APPLE ) list(APPEND OS_SPECIFIC_LINK_LIBRARIES @@ -94,23 +89,25 @@ IF (NOT APPLE) ) ENDIF(NOT APPLE) -list(APPEND libsync_LINK_TARGETS - ${QT_LIBRARIES} +add_library(${synclib_NAME} SHARED ${libsync_SRCS}) +target_link_libraries(${synclib_NAME} ocsync ${OS_SPECIFIC_LINK_LIBRARIES} + Qt5::Core Qt5::Network ) -if(QTKEYCHAIN_FOUND OR QT5KEYCHAIN_FOUND) - list(APPEND libsync_LINK_TARGETS ${QTKEYCHAIN_LIBRARY}) - include_directories(${QTKEYCHAIN_INCLUDE_DIR}) +if (NOT TOKEN_AUTH_ONLY) + find_package(Qt5 REQUIRED COMPONENTS Widgets) + target_link_libraries(${synclib_NAME} Qt5::Widgets ${QTKEYCHAIN_LIBRARY}) + target_include_directories(${synclib_NAME} PRIVATE ${QTKEYCHAIN_INCLUDE_DIR}) endif() if(INOTIFY_FOUND) - include_directories(${INOTIFY_INCLUDE_DIR}) + target_include_directories(${synclib_NAME} PRIVATE ${INOTIFY_INCLUDE_DIR}) link_directories(${INOTIFY_LIBRARY_DIR}) + target_link_libraries(${synclib_NAME} ${INOTIFY_LIBRARY} ) endif() -add_library(${synclib_NAME} SHARED ${libsync_SRCS} ${syncMoc}) GENERATE_EXPORT_HEADER( ${synclib_NAME} BASE_NAME ${synclib_NAME} EXPORT_MACRO_NAME OWNCLOUDSYNC_EXPORT @@ -118,11 +115,8 @@ GENERATE_EXPORT_HEADER( ${synclib_NAME} STATIC_DEFINE OWNCLOUD_BUILT_AS_STATIC ) -if(TOKEN_AUTH_ONLY) - qt5_use_modules(${synclib_NAME} Network) -else() - qt5_use_modules(${synclib_NAME} Widgets Network) -endif() +target_include_directories(${synclib_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) + set_target_properties( ${synclib_NAME} PROPERTIES VERSION ${MIRALL_VERSION} @@ -132,24 +126,6 @@ set_target_properties( ${synclib_NAME} PROPERTIES set_target_properties( ${synclib_NAME} PROPERTIES INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${LIB_INSTALL_DIR}/${APPLICATION_EXECUTABLE};${CMAKE_INSTALL_RPATH}" ) -target_link_libraries(${synclib_NAME} ${libsync_LINK_TARGETS} ) - -if(INOTIFY_FOUND) - target_link_libraries(${synclib_NAME} ${INOTIFY_LIBRARY} ) -endif() - -if(BUILD_LIBRARIES_ONLY) - #add_library(${synclib_NAME}_static STATIC ${libsync_SRCS} ${syncMoc}) - #qt5_use_modules(${synclib_NAME}_static Widgets Network Xml Sql) - - #set_target_properties( ${synclib_NAME}_static PROPERTIES - # VERSION ${MIRALL_VERSION} - # SOVERSION ${MIRALL_SOVERSION} - #) - - #target_link_libraries(${synclib_NAME}_static ${libsync_LINK_TARGETS} ) -endif() - if(NOT BUILD_OWNCLOUD_OSX_BUNDLE) install(TARGETS ${synclib_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} diff --git a/src/libsync/capabilities.cpp b/src/libsync/capabilities.cpp index 333410373..08c1e45ff 100644 --- a/src/libsync/capabilities.cpp +++ b/src/libsync/capabilities.cpp @@ -15,6 +15,7 @@ #include "capabilities.h" #include +#include namespace OCC { @@ -148,4 +149,14 @@ QString Capabilities::invalidFilenameRegex() const { return _capabilities["dav"].toMap()["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["uploadConflictFiles"].toBool(); +} } diff --git a/src/libsync/capabilities.h b/src/libsync/capabilities.h index f9f6614a4..63a59d6c0 100644 --- a/src/libsync/capabilities.h +++ b/src/libsync/capabilities.h @@ -116,6 +116,11 @@ public: */ QString invalidFilenameRegex() const; + /** + * Whether conflict files should remain local (default) or should be uploaded. + */ + bool uploadConflictFiles() const; + private: QVariantMap _capabilities; }; diff --git a/src/libsync/configfile.cpp b/src/libsync/configfile.cpp index 8aff35b3b..295fafc17 100644 --- a/src/libsync/configfile.cpp +++ b/src/libsync/configfile.cpp @@ -306,6 +306,19 @@ QString ConfigFile::excludeFileFromSystem() 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("etc") && d.cd(Theme::instance()->appName())) { + QFileInfo inMountDir(d, exclFile); + if (inMountDir.exists()) { + fi = inMountDir; + } + }; + } } } #endif diff --git a/src/libsync/discoveryphase.cpp b/src/libsync/discoveryphase.cpp index 4657e0488..bd74d439c 100644 --- a/src/libsync/discoveryphase.cpp +++ b/src/libsync/discoveryphase.cpp @@ -15,7 +15,6 @@ #include "discoveryphase.h" #include "account.h" -#include "theme.h" #include "common/asserts.h" #include "common/checksums.h" @@ -310,9 +309,9 @@ static std::unique_ptr propertyMapToFileStat(const QMaptype = CSYNC_FTW_TYPE_DIR; + file_stat->type = ItemTypeDirectory; } else { - file_stat->type = CSYNC_FTW_TYPE_FILE; + file_stat->type = ItemTypeFile; } } else if (property == "getlastmodified") { file_stat->modtime = oc_httpdate_parse(value.toUtf8()); @@ -698,8 +697,6 @@ void DiscoveryJob::start() _csync_ctx->callbacks.remote_closedir_hook = remote_vio_closedir_hook; _csync_ctx->callbacks.vio_userdata = this; - csync_set_log_callback(_log_callback); - csync_set_log_level(_log_level); _lastUpdateProgressCallbackCall.invalidate(); int ret = csync_update(_csync_ctx); diff --git a/src/libsync/discoveryphase.h b/src/libsync/discoveryphase.h index 222f4a999..b5e743d33 100644 --- a/src/libsync/discoveryphase.h +++ b/src/libsync/discoveryphase.h @@ -157,8 +157,6 @@ class DiscoveryJob : public QObject Q_OBJECT friend class DiscoveryMainThread; CSYNC *_csync_ctx; - csync_log_callback _log_callback; - int _log_level; QElapsedTimer _lastUpdateProgressCallbackCall; /** @@ -191,12 +189,7 @@ public: : QObject(parent) , _csync_ctx(ctx) { - // We need to forward the log property as csync uses thread local - // and updates run in another thread - _log_callback = csync_get_log_callback(); - _log_level = csync_get_log_level(); } - QStringList _selectiveSyncBlackList; QStringList _selectiveSyncWhiteList; SyncOptions _syncOptions; diff --git a/src/libsync/filesystem.cpp b/src/libsync/filesystem.cpp index 69bbbe3f1..648b6ebf3 100644 --- a/src/libsync/filesystem.cpp +++ b/src/libsync/filesystem.cpp @@ -23,7 +23,6 @@ extern "C" int c_utimes(const char *, const struct timeval *); #include "csync.h" #include "vio/csync_vio_local.h" -#include "std/c_path.h" #include "std/c_string.h" #include "std/c_utf8.h" diff --git a/src/libsync/logger.cpp b/src/libsync/logger.cpp index 31afbbe8c..cfba000ed 100644 --- a/src/libsync/logger.cpp +++ b/src/libsync/logger.cpp @@ -19,13 +19,8 @@ #include #include -#include "csync.h" -#include "csync_log.h" - namespace OCC { -Q_LOGGING_CATEGORY(lcCsync, "sync.csync", QtInfoMsg) - static void mirallLogCatcher(QtMsgType type, const QMessageLogContext &ctx, const QString &message) { auto logger = Logger::instance(); @@ -34,26 +29,6 @@ static void mirallLogCatcher(QtMsgType type, const QMessageLogContext &ctx, cons } } -static void csyncLogCatcher(int verbosity, - const char *function, - const char *buffer) -{ - if (verbosity <= CSYNC_LOG_PRIORITY_FATAL) { - QMessageLogger(0, 0, function, lcCsync().categoryName()).fatal("%s", buffer); - } else if (verbosity <= CSYNC_LOG_PRIORITY_CRIT) { - if (lcCsync().isCriticalEnabled()) - QMessageLogger(0, 0, function, lcCsync().categoryName()).critical("%s", buffer); - } else if (verbosity <= CSYNC_LOG_PRIORITY_WARN) { - if (lcCsync().isWarningEnabled()) - QMessageLogger(0, 0, function, lcCsync().categoryName()).warning("%s", buffer); - } else if (verbosity <= CSYNC_LOG_PRIORITY_INFO) { - if (lcCsync().isInfoEnabled()) - QMessageLogger(0, 0, function, lcCsync().categoryName()).info("%s", buffer); - } else if (verbosity <= CSYNC_LOG_PRIORITY_NOTSET) { - if (lcCsync().isDebugEnabled()) - QMessageLogger(0, 0, function, lcCsync().categoryName()).debug("%s", buffer); - } -} Logger *Logger::instance() { @@ -74,12 +49,7 @@ Logger::Logger(QObject *parent) qInstallMessageHandler(mirallLogCatcher); #else Q_UNUSED(mirallLogCatcher) - // Always get logging from csync in that case. - csync_set_log_level(11); #endif - - // Setup CSYNC logging to forward to our own logger - csync_set_log_callback(csyncLogCatcher); } Logger::~Logger() @@ -112,11 +82,6 @@ void Logger::log(Log log) msg = log.timeStamp.toString(QLatin1String("MM-dd hh:mm:ss:zzz")) + QLatin1Char(' '); } - if (log.source == Log::CSync) { - // msg += "csync - "; - } else { - // msg += "ownCloud - "; - } msg += QString().sprintf("%p ", (void *)QThread::currentThread()); msg += log.message; // _logs.append(log); @@ -151,7 +116,6 @@ void Logger::doLog(const QString &msg) void Logger::mirallLog(const QString &message) { Log log_; - log_.source = Log::Occ; log_.timeStamp = QDateTime::currentDateTimeUtc(); log_.message = message; @@ -161,18 +125,12 @@ void Logger::mirallLog(const QString &message) void Logger::setLogWindowActivated(bool activated) { QMutexLocker locker(&_mutex); - - csync_set_log_level(11); - _logWindowActivated = activated; } void Logger::setLogFile(const QString &name) { QMutexLocker locker(&_mutex); - - csync_set_log_level(11); - if (_logstream) { _logstream.reset(0); _logFile.close(); @@ -219,7 +177,7 @@ void Logger::setLogFlush(bool flush) void Logger::setLogDebug(bool debug) { - QLoggingCategory::setFilterRules(debug ? QStringLiteral("qt.*=true\n*.debug=true") : QString()); + QLoggingCategory::setFilterRules(debug ? QStringLiteral("sync.*.debug=true\ngui.*.debug=true") : QString()); _logDebug = debug; } diff --git a/src/libsync/logger.h b/src/libsync/logger.h index bf73009a9..9d6ef6b26 100644 --- a/src/libsync/logger.h +++ b/src/libsync/logger.h @@ -30,13 +30,7 @@ namespace OCC { struct Log { - typedef enum { - Occ, - CSync - } Source; - QDateTime timeStamp; - Source source; QString message; }; diff --git a/src/libsync/networkjobs.cpp b/src/libsync/networkjobs.cpp index f94403966..193002a74 100644 --- a/src/libsync/networkjobs.cpp +++ b/src/libsync/networkjobs.cpp @@ -630,7 +630,11 @@ bool PropfindJob::finished() AvatarJob::AvatarJob(AccountPtr account, const QString &userId, int size, QObject *parent) : AbstractNetworkJob(account, QString(), parent) { - _avatarUrl = Utility::concatUrlPath(account->url(), QString("remote.php/dav/avatars/%1/%2.png").arg(userId, QString::number(size))); + if (account->serverVersionInt() >= Account::makeServerVersion(10, 0, 0)) { + _avatarUrl = Utility::concatUrlPath(account->url(), QString("remote.php/dav/avatars/%1/%2.png").arg(userId, QString::number(size))); + } else { + _avatarUrl = Utility::concatUrlPath(account->url(), QString("index.php/avatar/%1/%2").arg(userId, QString::number(size))); + } } void AvatarJob::start() diff --git a/src/libsync/owncloudpropagator.cpp b/src/libsync/owncloudpropagator.cpp index e1d081141..e9e4df3ea 100644 --- a/src/libsync/owncloudpropagator.cpp +++ b/src/libsync/owncloudpropagator.cpp @@ -22,6 +22,7 @@ #include "propagateremotemove.h" #include "propagateremotemkdir.h" #include "propagatorjobs.h" +#include "filesystem.h" #include "common/utility.h" #include "account.h" #include "common/asserts.h" @@ -368,8 +369,10 @@ PropagateItemJob *OwncloudPropagator::createJob(const SyncFileItemPtr &item) return new PropagateRemoteDelete(this, item); case CSYNC_INSTRUCTION_NEW: case CSYNC_INSTRUCTION_TYPE_CHANGE: + case CSYNC_INSTRUCTION_CONFLICT: if (item->isDirectory()) { - if (item->_direction == SyncFileItem::Down) { + // CONFLICT has _direction == None + if (item->_direction != SyncFileItem::Up) { auto job = new PropagateLocalMkdir(this, item); job->setDeleteExistingFile(deleteExisting); return job; @@ -380,7 +383,6 @@ PropagateItemJob *OwncloudPropagator::createJob(const SyncFileItemPtr &item) } } //fall through case CSYNC_INSTRUCTION_SYNC: - case CSYNC_INSTRUCTION_CONFLICT: if (item->_direction != SyncFileItem::Up) { auto job = new PropagateDownloadFile(this, item); job->setDeleteExistingFolder(deleteExisting); @@ -431,6 +433,7 @@ void OwncloudPropagator::start(const SyncFileItemVector &items) directories.push(qMakePair(QString(), _rootJob.data())); QVector directoriesToRemove; QString removedDirectory; + QString maybeConflictDirectory; foreach (const SyncFileItemPtr &item, items) { if (!removedDirectory.isEmpty() && item->_file.startsWith(removedDirectory)) { // this is an item in a directory which is going to be removed. @@ -464,6 +467,20 @@ void OwncloudPropagator::start(const SyncFileItemVector &items) } } + // If a CONFLICT item contains files these 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; + continue; + } else { + maybeConflictDirectory.clear(); + } + } + while (!item->destination().startsWith(directories.top().first)) { directories.pop(); } @@ -513,6 +530,12 @@ void OwncloudPropagator::start(const SyncFileItemVector &items) } else { directories.top().second->appendTask(item); } + + 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 + "/"; + } } } @@ -702,6 +725,72 @@ OwncloudPropagator::DiskSpaceResult OwncloudPropagator::diskSpaceCheck() const return DiskSpaceOk; } +bool OwncloudPropagator::createConflict(const SyncFileItemPtr &item, + PropagatorCompositeJob *composite, QString *error) +{ + QString fn = getFilePath(item->_file); + + QString renameError; + auto conflictModTime = FileSystem::getModTime(fn); + QString conflictFileName = Utility::makeConflictFileName( + item->_file, Utility::qDateTimeFromTime_t(conflictModTime)); + QString conflictFilePath = getFilePath(conflictFileName); + + emit touchedFile(fn); + emit touchedFile(conflictFilePath); + + if (!FileSystem::rename(fn, conflictFilePath, &renameError)) { + // If the rename fails, don't replace it. + + // If the file is locked, we want to retry this sync when it + // becomes available again. + if (FileSystem::isFileLocked(fn)) { + emit seenLockedFile(fn); + } + + if (error) + *error = renameError; + return false; + } + qCInfo(lcPropagator) << "Created conflict file" << fn << "->" << conflictFileName; + + // Create a new conflict record. To get the base etag, we need to read it from the db. + ConflictRecord conflictRecord; + conflictRecord.path = conflictFileName.toUtf8(); + conflictRecord.baseModtime = item->_previousModtime; + + SyncJournalFileRecord baseRecord; + if (_journal->getFileRecord(item->_originalFile, &baseRecord) && baseRecord.isValid()) { + conflictRecord.baseEtag = baseRecord._etag; + conflictRecord.baseFileId = baseRecord._fileId; + } else { + // We might very well end up with no fileid/etag for new/new conflicts + } + + _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; +} + // ================================================================================ PropagatorJob::PropagatorJob(OwncloudPropagator *propagator) @@ -739,6 +828,12 @@ void PropagatorCompositeJob::slotSubJobAbortFinished() } } +void PropagatorCompositeJob::appendJob(PropagatorJob *job) +{ + job->setAssociatedComposite(this); + _jobsToDo.append(job); +} + bool PropagatorCompositeJob::scheduleSelfOrChild() { if (_state == Finished) { @@ -767,13 +862,8 @@ bool PropagatorCompositeJob::scheduleSelfOrChild() } // Now it's our turn, check if we have something left to do. - if (!_jobsToDo.isEmpty()) { - PropagatorJob *nextJob = _jobsToDo.first(); - _jobsToDo.remove(0); - _runningJobs.append(nextJob); - return possiblyRunNextJob(nextJob); - } - while (!_tasksToDo.isEmpty()) { + // First, convert a task to a job if necessary + while (_jobsToDo.isEmpty() && !_tasksToDo.isEmpty()) { SyncFileItemPtr nextTask = _tasksToDo.first(); _tasksToDo.remove(0); PropagatorJob *job = propagator()->createJob(nextTask); @@ -781,9 +871,15 @@ bool PropagatorCompositeJob::scheduleSelfOrChild() qCWarning(lcDirectory) << "Useless task found for file" << nextTask->destination() << "instruction" << nextTask->_instruction; continue; } - - _runningJobs.append(job); - return possiblyRunNextJob(job); + appendJob(job); + break; + } + // Then run the next job + if (!_jobsToDo.isEmpty()) { + PropagatorJob *nextJob = _jobsToDo.first(); + _jobsToDo.remove(0); + _runningJobs.append(nextJob); + return possiblyRunNextJob(nextJob); } // If neither us or our children had stuff left to do we could hang. Make sure @@ -851,6 +947,7 @@ PropagateDirectory::PropagateDirectory(OwncloudPropagator *propagator, const Syn { if (_firstJob) { connect(_firstJob.data(), &PropagatorJob::finished, this, &PropagateDirectory::slotFirstJobFinished); + _firstJob->setAssociatedComposite(&_subJobs); } connect(&_subJobs, &PropagatorJob::finished, this, &PropagateDirectory::slotSubJobsFinished); } @@ -894,7 +991,9 @@ void PropagateDirectory::slotFirstJobFinished(SyncFileItem::Status status) { _firstJob.take()->deleteLater(); - if (status != SyncFileItem::Success && status != SyncFileItem::Restoration) { + if (status != SyncFileItem::Success + && status != SyncFileItem::Restoration + && status != SyncFileItem::Conflict) { if (_state != Finished) { // Synchronously abort abort(AbortType::Synchronous); diff --git a/src/libsync/owncloudpropagator.h b/src/libsync/owncloudpropagator.h index 062b3825e..88bb2f1f7 100644 --- a/src/libsync/owncloudpropagator.h +++ b/src/libsync/owncloudpropagator.h @@ -49,6 +49,7 @@ qint64 freeSpaceLimit(); class SyncJournalDb; class OwncloudPropagator; +class PropagatorCompositeJob; /** * @brief the base class of propagator jobs @@ -102,6 +103,14 @@ public: */ 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; } + public slots: /* * Asynchronous abort requires emit of abortFinished() signal, @@ -128,6 +137,16 @@ signals: 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; }; /* @@ -210,14 +229,12 @@ public: virtual ~PropagatorCompositeJob() { - qDeleteAll(_jobsToDo); - qDeleteAll(_runningJobs); + // Don't delete jobs in _jobsToDo and _runningJobs: they have parents + // that will be responsible for cleanup. Deleting them here would risk + // deleting something that has already been deleted by a shared parent. } - void appendJob(PropagatorJob *job) - { - _jobsToDo.append(job); - } + void appendJob(PropagatorJob *job); void appendTask(const SyncFileItemPtr &item) { _tasksToDo.append(item); @@ -439,7 +456,10 @@ public: QString getFilePath(const QString &tmp_file_name) const; + /** Creates the job for an item. + */ PropagateItemJob *createJob(const SyncFileItemPtr &item); + void scheduleNextJob(); void reportProgress(const SyncFileItem &, quint64 bytes); @@ -477,6 +497,18 @@ public: */ DiskSpaceResult diskSpaceCheck() const; + /** 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); + private slots: void abortTimeout() @@ -497,6 +529,7 @@ private slots: void scheduleNextJobImpl(); signals: + void newItem(const SyncFileItemPtr &); void itemCompleted(const SyncFileItemPtr &); void progress(const SyncFileItem &, quint64 bytes); void finished(bool success); diff --git a/src/libsync/propagatedownload.cpp b/src/libsync/propagatedownload.cpp index 2efc537ea..0601e9fba 100644 --- a/src/libsync/propagatedownload.cpp +++ b/src/libsync/propagatedownload.cpp @@ -525,7 +525,7 @@ void PropagateDownloadFile::slotGetFinished() { propagator()->_activeJobList.removeOne(this); - GETFileJob *job = qobject_cast(sender()); + GETFileJob *job = _job; ASSERT(job); QNetworkReply::NetworkError err = job->reply()->error(); @@ -639,6 +639,26 @@ void PropagateDownloadFile::slotGetFinished() return; } + // Did the file come with conflict headers? If so, store them now! + // If we download conflict files but the server doesn't send conflict + // headers, the record will be established by SyncEngine::conflictRecordMaintenance. + // (we can't reliably determine the file id of the base file here, + // it might still be downloaded in a parallel job and not exist in + // the database yet!) + if (job->reply()->rawHeader("OC-Conflict") == "1") { + _conflictRecord.path = _item->_file.toUtf8(); + _conflictRecord.baseFileId = job->reply()->rawHeader("OC-ConflictBaseFileId"); + _conflictRecord.baseEtag = _job->reply()->rawHeader("OC-ConflictBaseEtag"); + + auto mtimeHeader = _job->reply()->rawHeader("OC-ConflictBaseMtime"); + if (!mtimeHeader.isEmpty()) + _conflictRecord.baseModtime = mtimeHeader.toLongLong(); + + // We don't set it yet. That will only be done when the download finished + // successfully, much further down. Here we just grab the headers because the + // job will be deleted later. + } + // 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() // as this is (still) also correct. @@ -677,14 +697,9 @@ void PropagateDownloadFile::deleteExistingFolder() // on error, just try to move it away... } - QString conflictDir = FileSystem::makeConflictFileName( - existingDir, Utility::qDateTimeFromTime_t(FileSystem::getModTime(existingDir))); - - emit propagator()->touchedFile(existingDir); - emit propagator()->touchedFile(conflictDir); - QString renameError; - if (!FileSystem::rename(existingDir, conflictDir, &renameError)) { - done(SyncFileItem::NormalError, renameError); + QString error; + if (!propagator()->createConflict(_item, _associatedComposite, &error)) { + done(SyncFileItem::NormalError, error); } } @@ -799,27 +814,14 @@ void PropagateDownloadFile::downloadFinished() return; } - // In case of conflict, make a backup of the old file - // Ignore conflicts where both files are binary equal bool isConflict = _item->_instruction == CSYNC_INSTRUCTION_CONFLICT - && !FileSystem::fileEquals(fn, _tmpFile.fileName()); + && (QFileInfo(fn).isDir() || !FileSystem::fileEquals(fn, _tmpFile.fileName())); if (isConflict) { - QString renameError; - QString conflictFileName = FileSystem::makeConflictFileName( - fn, Utility::qDateTimeFromTime_t(FileSystem::getModTime(fn))); - if (!FileSystem::rename(fn, conflictFileName, &renameError)) { - // If the rename fails, don't replace it. - - // If the file is locked, we want to retry this sync when it - // becomes available again. - if (FileSystem::isFileLocked(fn)) { - emit propagator()->seenLockedFile(fn); - } - - done(SyncFileItem::SoftError, renameError); + QString error; + if (!propagator()->createConflict(_item, _associatedComposite, &error)) { + done(SyncFileItem::SoftError, error); return; } - qCInfo(lcPropagateDownload) << "Created conflict file" << fn << "->" << conflictFileName; } FileSystem::setModTime(_tmpFile.fileName(), _item->_modtime); @@ -886,6 +888,11 @@ void PropagateDownloadFile::downloadFinished() // Get up to date information for the journal. _item->_size = FileSystem::getSize(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()) + propagator()->_journal->setConflictRecord(_conflictRecord); + updateMetadata(isConflict); } diff --git a/src/libsync/propagatedownload.h b/src/libsync/propagatedownload.h index 4b8988b65..632cb0c79 100644 --- a/src/libsync/propagatedownload.h +++ b/src/libsync/propagatedownload.h @@ -202,6 +202,7 @@ private: QPointer _job; QFile _tmpFile; bool _deleteExisting; + ConflictRecord _conflictRecord; QElapsedTimer _stopwatch; }; diff --git a/src/libsync/propagateupload.cpp b/src/libsync/propagateupload.cpp index 6de6316af..4579139ce 100644 --- a/src/libsync/propagateupload.cpp +++ b/src/libsync/propagateupload.cpp @@ -214,10 +214,6 @@ void PropagateUploadFileCommon::slotComputeContentChecksum() // change during the checksum calculation _item->_modtime = FileSystem::getModTime(filePath); -#ifdef WITH_TESTING - _stopWatch.start(); -#endif - QByteArray checksumType = contentChecksumType(); // Maybe the discovery already computed the checksum? @@ -243,11 +239,6 @@ void PropagateUploadFileCommon::slotComputeTransmissionChecksum(const QByteArray { _item->_checksumHeader = makeChecksumHeader(contentChecksumType, contentChecksum); -#ifdef WITH_TESTING - _stopWatch.addLapTime(QLatin1String("ContentChecksum")); - _stopWatch.start(); -#endif - // Reuse the content checksum as the transmission checksum if possible const auto supportedTransmissionChecksums = propagator()->account()->capabilities().supportedChecksumTypes(); @@ -291,10 +282,6 @@ void PropagateUploadFileCommon::slotStartUpload(const QByteArray &transmissionCh done(SyncFileItem::SoftError, tr("File Removed")); return; } -#ifdef WITH_TESTING - _stopWatch.addLapTime(QLatin1String("TransmissionChecksum")); -#endif - 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. @@ -605,6 +592,19 @@ QMap PropagateUploadFileCommon::headers() // csync_owncloud.c's owncloud_file_id always strips the quotes. headers["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["OC-Conflict"] = "1"; + if (!conflictRecord.baseFileId.isEmpty()) + headers["OC-ConflictBaseFileId"] = conflictRecord.baseFileId; + if (conflictRecord.baseModtime != -1) + headers["OC-ConflictBaseMtime"] = QByteArray::number(conflictRecord.baseModtime); + if (!conflictRecord.baseEtag.isEmpty()) + headers["OC-ConflictBaseEtag"] = conflictRecord.baseEtag; + } + return headers; } diff --git a/src/libsync/propagateupload.h b/src/libsync/propagateupload.h index c156190b5..a3ebe25bb 100644 --- a/src/libsync/propagateupload.h +++ b/src/libsync/propagateupload.h @@ -211,12 +211,6 @@ protected: bool _finished BITFIELD(1); /// Tells that all the jobs have been finished bool _deleteExisting BITFIELD(1); quint64 _abortCount; /// Keep track of number of aborted items - -// measure the performance of checksum calc and upload -#ifdef WITH_TESTING - Utility::StopWatch _stopWatch; -#endif - QByteArray _transmissionChecksumHeader; public: diff --git a/src/libsync/propagateuploadng.cpp b/src/libsync/propagateuploadng.cpp index fa16a4abe..2586664ca 100644 --- a/src/libsync/propagateuploadng.cpp +++ b/src/libsync/propagateuploadng.cpp @@ -230,6 +230,7 @@ void PropagateUploadFileNG::startNewUpload() pi._valid = true; pi._transferid = _transferId; pi._modtime = _item->_modtime; + pi._contentChecksum = _item->_checksumHeader; propagator()->_journal->setUploadInfo(_item->_file, pi); propagator()->_journal->commit("Upload info"); QMap headers; @@ -466,17 +467,6 @@ void PropagateUploadFileNG::slotMoveJobFinished() return; } _item->_responseTimeStamp = job->responseTimestamp(); - -#ifdef WITH_TESTING - // performance logging - quint64 duration = _stopWatch.stop(); - qCDebug(lcPropagateUpload) << "*==* duration UPLOAD" << _item->_size - << _stopWatch.durationOfLap(QLatin1String("ContentChecksum")) - << _stopWatch.durationOfLap(QLatin1String("TransmissionChecksum")) - << duration; - // The job might stay alive for the whole sync, release this tiny bit of memory. - _stopWatch.reset(); -#endif finalize(); } diff --git a/src/libsync/propagateuploadv1.cpp b/src/libsync/propagateuploadv1.cpp index 8ebdd3786..ca0a61ccb 100644 --- a/src/libsync/propagateuploadv1.cpp +++ b/src/libsync/propagateuploadv1.cpp @@ -43,10 +43,24 @@ void PropagateUploadFileV1::doStartUpload() const SyncJournalDb::UploadInfo progressInfo = propagator()->_journal->getUploadInfo(_item->_file); - if (progressInfo._valid && progressInfo._modtime == _item->_modtime) { + if (progressInfo._valid && progressInfo._modtime == _item->_modtime + && (progressInfo._contentChecksum == _item->_checksumHeader || progressInfo._contentChecksum.isEmpty() || _item->_checksumHeader.isEmpty())) { _startChunk = progressInfo._chunk; _transferId = progressInfo._transferid; qCInfo(lcPropagateUpload) << _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 = _transferId; + pi._modtime = _item->_modtime; + pi._errorCount = 0; + pi._contentChecksum = _item->_checksumHeader; + propagator()->_journal->setUploadInfo(_item->_file, pi); + propagator()->_journal->commit("Upload info"); } _currentChunk = 0; @@ -274,6 +288,7 @@ void PropagateUploadFileV1::slotPutFinished() pi._transferid = _transferId; pi._modtime = _item->_modtime; pi._errorCount = 0; // successful chunk upload resets + pi._contentChecksum = _item->_checksumHeader; propagator()->_journal->setUploadInfo(_item->_file, pi); propagator()->_journal->commit("Upload info"); startNextChunk(); @@ -303,17 +318,6 @@ void PropagateUploadFileV1::slotPutFinished() done(SyncFileItem::SoftError, "Server does not support X-OC-MTime"); } -#ifdef WITH_TESTING - // performance logging - quint64 duration = _stopWatch.stop(); - qCDebug(lcPropagateUpload) << "*==* duration UPLOAD" << _item->_size - << _stopWatch.durationOfLap(QLatin1String("ContentChecksum")) - << _stopWatch.durationOfLap(QLatin1String("TransmissionChecksum")) - << duration; - // The job might stay alive for the whole sync, release this tiny bit of memory. - _stopWatch.reset(); -#endif - finalize(); } diff --git a/src/libsync/propagatorjobs.cpp b/src/libsync/propagatorjobs.cpp index 49ecfe156..66b934c06 100644 --- a/src/libsync/propagatorjobs.cpp +++ b/src/libsync/propagatorjobs.cpp @@ -151,13 +151,21 @@ void PropagateLocalMkdir::start() // When turning something that used to be a file into a directory // we need to delete the file first. QFileInfo fi(newDirStr); - if (_deleteExistingFile && fi.exists() && fi.isFile()) { - QString removeError; - if (!FileSystem::remove(newDirStr, &removeError)) { - done(SyncFileItem::NormalError, - tr("could not delete file %1, error: %2") - .arg(newDirStr, removeError)); - return; + if (fi.exists() && fi.isFile()) { + if (_deleteExistingFile) { + QString removeError; + if (!FileSystem::remove(newDirStr, &removeError)) { + done(SyncFileItem::NormalError, + tr("could not delete file %1, error: %2") + .arg(newDirStr, removeError)); + return; + } + } else if (_item->_instruction == CSYNC_INSTRUCTION_CONFLICT) { + QString error; + if (!propagator()->createConflict(_item, _associatedComposite, &error)) { + done(SyncFileItem::SoftError, error); + return; + } } } @@ -186,7 +194,10 @@ void PropagateLocalMkdir::start() } propagator()->_journal->commit("localMkdir"); - done(SyncFileItem::Success); + auto resultStatus = _item->_instruction == CSYNC_INSTRUCTION_CONFLICT + ? SyncFileItem::Conflict + : SyncFileItem::Success; + done(resultStatus); } void PropagateLocalMkdir::setDeleteExistingFile(bool enabled) diff --git a/src/libsync/syncengine.cpp b/src/libsync/syncengine.cpp index 6dc571c72..cd0cf6ccd 100644 --- a/src/libsync/syncengine.cpp +++ b/src/libsync/syncengine.cpp @@ -278,7 +278,7 @@ void SyncEngine::deleteStaleDownloadInfos(const SyncFileItemVector &syncItems) QSet download_file_paths; foreach (const SyncFileItemPtr &it, syncItems) { if (it->_direction == SyncFileItem::Down - && it->_type == SyncFileItem::File) { + && it->_type == ItemTypeFile) { download_file_paths.insert(it->_file); } } @@ -299,7 +299,7 @@ void SyncEngine::deleteStaleUploadInfos(const SyncFileItemVector &syncItems) QSet upload_file_paths; foreach (const SyncFileItemPtr &it, syncItems) { if (it->_direction == SyncFileItem::Up - && it->_type == SyncFileItem::File) { + && it->_type == ItemTypeFile) { upload_file_paths.insert(it->_file); } } @@ -329,6 +329,45 @@ void SyncEngine::deleteStaleErrorBlacklistEntries(const SyncFileItemVector &sync _journal->deleteStaleErrorBlacklistEntries(blacklist_file_paths); } +void SyncEngine::conflictRecordMaintenance() +{ + // Remove stale conflict entries from the database + // by checking which files still exist and removing the + // missing ones. + auto conflictRecordPaths = _journal->conflictRecordPaths(); + for (const auto &path : conflictRecordPaths) { + auto fsPath = _propagator->getFilePath(QString::fromUtf8(path)); + if (!QFileInfo(fsPath).exists()) { + _journal->deleteConflictRecord(path); + } + } + + // Did the sync see any conflict files that don't yet have records? + // 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 : _seenFiles) { + if (!Utility::isConflictFile(path)) + continue; + + auto bapath = path.toUtf8(); + if (!conflictRecordPaths.contains(bapath)) { + ConflictRecord record; + record.path = bapath; + + // Determine fileid of target file + auto basePath = Utility::conflictFileBaseName(bapath); + SyncJournalFileRecord baseRecord; + if (_journal->getFileRecord(basePath, &baseRecord) && baseRecord.isValid()) { + record.baseFileId = baseRecord._fileId; + } + + _journal->setConflictRecord(record); + } + } +} + int SyncEngine::treewalkLocal(csync_file_stat_t *file, csync_file_stat_t *other, void *data) { return static_cast(data)->treewalkFile(file, other, false); @@ -393,6 +432,7 @@ int SyncEngine::treewalkFile(csync_file_stat_t *file, csync_file_stat_t *other, item->_modtime = file->modtime; item->_size = file->size; item->_checksumHeader = file->checksumHeader; + item->_type = file->type; } else { if (instruction != CSYNC_INSTRUCTION_NONE) { qCWarning(lcEngine) << "ERROR: Instruction" << item->_instruction << "vs" << instruction << "for" << fileUtf8; @@ -483,7 +523,7 @@ int SyncEngine::treewalkFile(csync_file_stat_t *file, csync_file_stat_t *other, break; case CSYNC_STATUS_INDIVIDUAL_IS_CONFLICT_FILE: item->_status = SyncFileItem::Conflict; - if (Utility::shouldUploadConflictFiles()) { + if (account()->capabilities().uploadConflictFiles()) { // 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! @@ -495,9 +535,6 @@ int SyncEngine::treewalkFile(csync_file_stat_t *file, csync_file_stat_t *other, item->_errorString = tr("Conflict: Server version downloaded, local copy renamed and not uploaded."); } break; - case CYSNC_STATUS_FILE_LOCKED_OR_OPEN: - item->_errorString = QLatin1String("File locked"); // don't translate, internal use! - break; case CSYNC_STATUS_INDIVIDUAL_STAT_FAILED: item->_errorString = tr("Stat failed."); break; @@ -529,7 +566,7 @@ int SyncEngine::treewalkFile(csync_file_stat_t *file, csync_file_stat_t *other, item->_errorString = tr("Filename encoding is not valid"); } - bool isDirectory = file->type == CSYNC_FTW_TYPE_DIR; + bool isDirectory = file->type == ItemTypeDirectory; if (!file->etag.isEmpty()) { item->_etag = file->etag; @@ -540,20 +577,6 @@ int SyncEngine::treewalkFile(csync_file_stat_t *file, csync_file_stat_t *other, item->_inode = file->inode; } - switch (file->type) { - case CSYNC_FTW_TYPE_DIR: - item->_type = SyncFileItem::Directory; - break; - case CSYNC_FTW_TYPE_FILE: - item->_type = SyncFileItem::File; - break; - case CSYNC_FTW_TYPE_SLINK: - item->_type = SyncFileItem::SoftLink; - break; - default: - item->_type = SyncFileItem::UnknownType; - } - SyncFileItem::Direction dir = SyncFileItem::None; int re = 0; @@ -564,7 +587,7 @@ int SyncEngine::treewalkFile(csync_file_stat_t *file, csync_file_stat_t *other, _hasNoneFiles = true; } // Put none-instruction conflict files into the syncfileitem list - if (Utility::shouldUploadConflictFiles() + if (account()->capabilities().uploadConflictFiles() && file->error_status == CSYNC_STATUS_INDIVIDUAL_IS_CONFLICT_FILE && item->_instruction == CSYNC_INSTRUCTION_IGNORE) { break; @@ -680,8 +703,6 @@ int SyncEngine::treewalkFile(csync_file_stat_t *file, csync_file_stat_t *other, checkErrorBlacklisting(*item); } - _progressInfo->adjustTotalsForFile(*item); - _needsUpdate = true; if (other) { @@ -689,6 +710,7 @@ int SyncEngine::treewalkFile(csync_file_stat_t *file, csync_file_stat_t *other, item->_previousSize = other->size; } + slotNewItem(item); _syncItemMap.insert(key, item); return re; } @@ -817,6 +839,9 @@ void SyncEngine::startSync() // database creation error! } + _csync_ctx->upload_conflict_files = _account->capabilities().uploadConflictFiles(); + _excludedFiles->setExcludeConflictFiles(!_account->capabilities().uploadConflictFiles()); + _csync_ctx->read_remote_from_db = true; _lastLocalDiscoveryStyle = _csync_ctx->local_discovery_style; @@ -906,6 +931,11 @@ void SyncEngine::slotRootEtagReceived(const QString &e) } } +void SyncEngine::slotNewItem(const SyncFileItemPtr &item) +{ + _progressInfo->adjustTotalsForFile(*item); +} + void SyncEngine::slotDiscoveryJobFinished(int discoveryResult) { if (discoveryResult < 0) { @@ -1063,6 +1093,7 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult) 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); @@ -1128,11 +1159,12 @@ void SyncEngine::slotFinished(bool success) _journal->setDataFingerprint(_discoveryMainThread->_dataFingerprint); } - // emit the treewalk results. if (!_journal->postSyncCleanup(_seenFiles, _temporarilyUnavailablePaths)) { qCDebug(lcEngine) << "Cleaning of synced "; } + conflictRecordMaintenance(); + _journal->commit("All Finished.", false); // Send final progress information even if no diff --git a/src/libsync/syncengine.h b/src/libsync/syncengine.h index 8be936e38..5ae335b42 100644 --- a/src/libsync/syncengine.h +++ b/src/libsync/syncengine.h @@ -161,6 +161,15 @@ signals: private slots: void slotFolderDiscovered(bool local, const QString &folder); void slotRootEtagReceived(const QString &); + + /** Called when a SyncFileItem gets accepted for a sync. + * + * Mostly done in initial creation inside treewalkFile but + * can also be called via the propagator for items that are + * created during propagation. + */ + void slotNewItem(const SyncFileItemPtr &item); + void slotItemCompleted(const SyncFileItemPtr &item); void slotFinished(bool success); void slotProgress(const SyncFileItem &item, quint64 curent); @@ -200,6 +209,9 @@ private: // Removes stale error blacklist entries from the journal. void deleteStaleErrorBlacklistEntries(const SyncFileItemVector &syncItems); + // Removes stale and adds missing conflict records after sync + void conflictRecordMaintenance(); + // cleanup and emit the finished signal void finalize(bool success); diff --git a/src/libsync/syncfileitem.cpp b/src/libsync/syncfileitem.cpp index bfea07163..832feefde 100644 --- a/src/libsync/syncfileitem.cpp +++ b/src/libsync/syncfileitem.cpp @@ -58,7 +58,7 @@ SyncFileItemPtr SyncFileItem::fromSyncJournalFileRecord(const SyncJournalFileRec item->_file = rec._path; item->_inode = rec._inode; item->_modtime = rec._modtime; - item->_type = static_cast(rec._type); + item->_type = rec._type; item->_etag = rec._etag; item->_fileId = rec._fileId; item->_size = rec._fileSize; diff --git a/src/libsync/syncfileitem.h b/src/libsync/syncfileitem.h index dd364ae43..4272bf9e5 100644 --- a/src/libsync/syncfileitem.h +++ b/src/libsync/syncfileitem.h @@ -42,13 +42,6 @@ public: Down }; - enum Type { - UnknownType = 0, - File = CSYNC_FTW_TYPE_FILE, - Directory = CSYNC_FTW_TYPE_DIR, - SoftLink = CSYNC_FTW_TYPE_SLINK - }; - enum Status { // stored in 4 bits NoStatus, @@ -101,7 +94,7 @@ public: SyncFileItem() - : _type(UnknownType) + : _type(ItemTypeSkip) , _direction(None) , _serverHasIgnoredFiles(false) , _hasBlacklistEntry(false) @@ -173,29 +166,42 @@ public: bool isDirectory() const { - return _type == SyncFileItem::Directory; + return _type == ItemTypeDirectory; } /** * True if the item had any kind of error. - * - * Used for deciding whether an item belongs to the protocol or the - * issues list on the activity page and for checking whether an - * item should be announced in the notification message. */ bool hasErrorStatus() const { return _status == SyncFileItem::SoftError || _status == SyncFileItem::NormalError || _status == SyncFileItem::FatalError - || _status == SyncFileItem::Conflict || !_errorString.isEmpty(); } + /** + * Whether this item should appear on the issues tab. + */ + bool showInIssuesTab() const + { + return hasErrorStatus() || _status == SyncFileItem::Conflict; + } + + /** + * Whether this item should appear on the protocol tab. + */ + bool showInProtocolTab() const + { + return !showInIssuesTab() + // Don't show conflicts that were resolved as "not a conflict after all" + && !(_instruction == CSYNC_INSTRUCTION_CONFLICT && _status == SyncFileItem::Success); + } + // Variables useful for everybody QString _file; QString _renameTarget; - Type _type BITFIELD(3); + ItemType _type BITFIELD(3); Direction _direction BITFIELD(3); bool _serverHasIgnoredFiles BITFIELD(1); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b62a6ee01..a2879a42b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,5 +1,5 @@ -include_directories(${QT_INCLUDES} - ${CMAKE_SOURCE_DIR}/src +find_package(SQLite3 3.8.0 REQUIRED) +include_directories(${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/3rdparty/qtokenizer ${CMAKE_SOURCE_DIR}/src/csync ${CMAKE_SOURCE_DIR}/src/csync/std @@ -9,13 +9,9 @@ include_directories(${QT_INCLUDES} ${CMAKE_BINARY_DIR}/src/libsync ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} + ${SQLITE3_INCLUDE_DIR} ) -include_directories() - -include(QtVersionAbstraction) -setup_qt() - include(owncloud_add_test.cmake) owncloud_add_test(OwncloudPropagator "") @@ -47,6 +43,7 @@ owncloud_add_test(FileSystem "") owncloud_add_test(Utility "") owncloud_add_test(SyncEngine "syncenginetestutils.h") owncloud_add_test(SyncMove "syncenginetestutils.h") +owncloud_add_test(SyncConflict "syncenginetestutils.h") owncloud_add_test(SyncFileStatusTracker "syncenginetestutils.h") owncloud_add_test(ChunkingNg "syncenginetestutils.h") owncloud_add_test(UploadReset "syncenginetestutils.h") diff --git a/test/csync/CMakeLists.txt b/test/csync/CMakeLists.txt index 5cb0590e4..a7ec9f133 100644 --- a/test/csync/CMakeLists.txt +++ b/test/csync/CMakeLists.txt @@ -1,11 +1,9 @@ -project(tests C) +project(tests) set(TORTURE_LIBRARY torture) include_directories( ${CMAKE_CURRENT_SOURCE_DIR} - ${CSYNC_PUBLIC_INCLUDE_DIRS} - ${CSTDLIB_PUBLIC_INCLUDE_DIRS} ${CMAKE_BINARY_DIR} ${CMOCKA_INCLUDE_DIR} ) @@ -13,24 +11,19 @@ include_directories( include_directories(${CHECK_INCLUDE_DIRS}) # create test library add_library(${TORTURE_LIBRARY} STATIC torture.c cmdline.c) -target_link_libraries(${TORTURE_LIBRARY} ${CMOCKA_LIBRARIES} ${CSYNC_LIBRARY} ${CSTDLIB_LIBRARY}) +target_link_libraries(${TORTURE_LIBRARY} ${CMOCKA_LIBRARIES} ${CSYNC_LIBRARY}) -set(TEST_TARGET_LIBRARIES ${TORTURE_LIBRARY}) +set(TEST_TARGET_LIBRARIES ${TORTURE_LIBRARY} Qt5::Core ocsync) # create tests # std add_cmocka_test(check_std_c_alloc std_tests/check_std_c_alloc.c ${TEST_TARGET_LIBRARIES}) add_cmocka_test(check_std_c_jhash std_tests/check_std_c_jhash.c ${TEST_TARGET_LIBRARIES}) -add_cmocka_test(check_std_c_path std_tests/check_std_c_path.c ${TEST_TARGET_LIBRARIES}) add_cmocka_test(check_std_c_str std_tests/check_std_c_str.c ${TEST_TARGET_LIBRARIES}) -add_cmocka_test(check_std_c_time std_tests/check_std_c_time.c ${TEST_TARGET_LIBRARIES}) + # csync tests -# This will be rewritten soon anyway. -#add_cmocka_test(check_logger log_tests/check_log.cpp ${TEST_TARGET_LIBRARIES}) - -add_cmocka_test(check_csync_log csync_tests/check_csync_log.cpp ${TEST_TARGET_LIBRARIES}) add_cmocka_test(check_csync_exclude csync_tests/check_csync_exclude.cpp ${TEST_TARGET_LIBRARIES}) add_cmocka_test(check_csync_util csync_tests/check_csync_util.cpp ${TEST_TARGET_LIBRARIES}) add_cmocka_test(check_csync_misc csync_tests/check_csync_misc.cpp ${TEST_TARGET_LIBRARIES}) diff --git a/test/csync/csync_tests/check_csync_exclude.cpp b/test/csync/csync_tests/check_csync_exclude.cpp index b01f4cdc8..f719aa4aa 100644 --- a/test/csync/csync_tests/check_csync_exclude.cpp +++ b/test/csync/csync_tests/check_csync_exclude.cpp @@ -40,6 +40,7 @@ static int setup(void **state) { csync = new CSYNC("/tmp/check_csync1", new OCC::SyncJournalDb("")); excludedFiles = new ExcludedFiles; + excludedFiles->setWildcardsMatchSlash(false); csync->exclude_traversal_fn = excludedFiles->csyncTraversalMatchFun(); *state = csync; @@ -51,6 +52,7 @@ static int setup_init(void **state) { csync = new CSYNC("/tmp/check_csync1", new OCC::SyncJournalDb("")); excludedFiles = new ExcludedFiles; + excludedFiles->setWildcardsMatchSlash(false); csync->exclude_traversal_fn = excludedFiles->csyncTraversalMatchFun(); excludedFiles->addExcludeFilePath(EXCLUDE_LIST_FILE); @@ -90,22 +92,22 @@ static int teardown(void **state) { static int check_file_full(const char *path) { - return excludedFiles->fullPatternMatch(path, CSYNC_FTW_TYPE_FILE); + return excludedFiles->fullPatternMatch(path, ItemTypeFile); } static int check_dir_full(const char *path) { - return excludedFiles->fullPatternMatch(path, CSYNC_FTW_TYPE_DIR); + return excludedFiles->fullPatternMatch(path, ItemTypeDirectory); } static int check_file_traversal(const char *path) { - return excludedFiles->traversalPatternMatch(path, CSYNC_FTW_TYPE_FILE); + return excludedFiles->traversalPatternMatch(path, ItemTypeFile); } static int check_dir_traversal(const char *path) { - return excludedFiles->traversalPatternMatch(path, CSYNC_FTW_TYPE_DIR); + return excludedFiles->traversalPatternMatch(path, ItemTypeDirectory); } static void check_csync_exclude_add(void **) @@ -116,10 +118,13 @@ static void check_csync_exclude_add(void **) assert_true(excludedFiles->_allExcludes.contains("/tmp/check_csync1/*")); assert_true(excludedFiles->_fullRegexFile.pattern().contains("csync1")); - assert_false(excludedFiles->_bnameActivationRegexFile.pattern().contains("csync1")); + assert_true(excludedFiles->_fullTraversalRegexFile.pattern().contains("csync1")); + assert_false(excludedFiles->_bnameTraversalRegexFile.pattern().contains("csync1")); excludedFiles->addManualExclude("foo"); - assert_true(excludedFiles->_bnameActivationRegexFile.pattern().contains("foo")); + assert_true(excludedFiles->_bnameTraversalRegexFile.pattern().contains("foo")); + assert_true(excludedFiles->_fullRegexFile.pattern().contains("foo")); + assert_false(excludedFiles->_fullTraversalRegexFile.pattern().contains("foo")); } static void check_csync_excluded(void **) @@ -429,7 +434,101 @@ static void check_csync_pathes(void **) assert_int_equal(check_dir_full("/excludepath/withsubdir/foo"), CSYNC_FILE_EXCLUDE_LIST); } -static void check_csync_is_windows_reserved_word(void **) { +static void check_csync_wildcards(void **) +{ + 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->setWildcardsMatchSlash(false); + + assert_int_equal(check_file_traversal("a/fooXYZbar"), CSYNC_FILE_EXCLUDE_LIST); + assert_int_equal(check_file_traversal("a/fooX/Zbar"), CSYNC_NOT_EXCLUDED); + + assert_int_equal(check_file_traversal("b/fooXYZbarABC"), CSYNC_FILE_EXCLUDE_LIST); + assert_int_equal(check_file_traversal("b/fooX/ZbarABC"), CSYNC_NOT_EXCLUDED); + + assert_int_equal(check_file_traversal("c/fooXbar"), CSYNC_FILE_EXCLUDE_LIST); + assert_int_equal(check_file_traversal("c/foo/bar"), CSYNC_NOT_EXCLUDED); + + assert_int_equal(check_file_traversal("d/fooXbarABC"), CSYNC_FILE_EXCLUDE_LIST); + assert_int_equal(check_file_traversal("d/foo/barABC"), CSYNC_NOT_EXCLUDED); + + assert_int_equal(check_file_traversal("e/fooXbarA"), CSYNC_FILE_EXCLUDE_LIST); + assert_int_equal(check_file_traversal("e/foo/barA"), CSYNC_NOT_EXCLUDED); + + assert_int_equal(check_file_traversal("g/barABC"), CSYNC_FILE_EXCLUDE_LIST); + assert_int_equal(check_file_traversal("g/XbarABC"), CSYNC_NOT_EXCLUDED); + + assert_int_equal(check_file_traversal("h/barZ"), CSYNC_FILE_EXCLUDE_LIST); + assert_int_equal(check_file_traversal("h/XbarZ"), CSYNC_NOT_EXCLUDED); + + excludedFiles->setWildcardsMatchSlash(true); + + assert_int_equal(check_file_traversal("a/fooX/Zbar"), CSYNC_FILE_EXCLUDE_LIST); + assert_int_equal(check_file_traversal("b/fooX/ZbarABC"), CSYNC_FILE_EXCLUDE_LIST); + assert_int_equal(check_file_traversal("c/foo/bar"), CSYNC_FILE_EXCLUDE_LIST); + assert_int_equal(check_file_traversal("d/foo/barABC"), CSYNC_FILE_EXCLUDE_LIST); + assert_int_equal(check_file_traversal("e/foo/barA"), CSYNC_FILE_EXCLUDE_LIST); +} + +static void check_csync_regex_translation(void **) +{ + QByteArray storage; + auto translate = [&storage](const char *pattern) { + storage = convertToRegexpSyntax(pattern, false).toUtf8(); + return storage.constData(); + }; + + assert_string_equal(translate(""), ""); + assert_string_equal(translate("abc"), "abc"); + assert_string_equal(translate("a*c"), "a[^/]*c"); + assert_string_equal(translate("a?c"), "a[^/]c"); + assert_string_equal(translate("a[xyz]c"), "a[xyz]c"); + assert_string_equal(translate("a[xyzc"), "a\\[xyzc"); + assert_string_equal(translate("a[!xyz]c"), "a[^xyz]c"); + assert_string_equal(translate("a\\*b\\?c\\[d\\\\e"), "a\\*b\\?c\\[d\\\\e"); + assert_string_equal(translate("a.c"), "a\\.c"); + assert_string_equal(translate("?𠜎?"), "[^/]\\𠜎[^/]"); // 𠜎 is 4-byte utf8 +} + +static void check_csync_bname_trigger(void **) +{ + bool wildcardsMatchSlash = false; + QByteArray storage; + auto translate = [&storage, &wildcardsMatchSlash](const char *pattern) { + storage = extractBnameTrigger(pattern, wildcardsMatchSlash).toUtf8(); + return storage.constData(); + }; + + assert_string_equal(translate(""), ""); + assert_string_equal(translate("a/b/"), ""); + assert_string_equal(translate("a/b/c"), "c"); + assert_string_equal(translate("c"), "c"); + assert_string_equal(translate("a/foo*"), "foo*"); + assert_string_equal(translate("a/abc*foo*"), "abc*foo*"); + + wildcardsMatchSlash = true; + + assert_string_equal(translate(""), ""); + assert_string_equal(translate("a/b/"), ""); + assert_string_equal(translate("a/b/c"), "c"); + assert_string_equal(translate("c"), "c"); + assert_string_equal(translate("*"), "*"); + assert_string_equal(translate("a/foo*"), "foo*"); + assert_string_equal(translate("a/abc?foo*"), "*foo*"); + assert_string_equal(translate("a/abc*foo*"), "*foo*"); + assert_string_equal(translate("a/abc?foo?"), "*foo?"); + assert_string_equal(translate("a/abc*foo?*"), "*foo?*"); + assert_string_equal(translate("a/abc*/foo*"), "foo*"); +} + +static void check_csync_is_windows_reserved_word(void **) +{ assert_true(csync_is_windows_reserved_word("CON")); assert_true(csync_is_windows_reserved_word("con")); assert_true(csync_is_windows_reserved_word("CON.")); @@ -512,19 +611,17 @@ static void check_csync_exclude_expand_escapes(void **state) { (void)state; - const char *str = csync_exclude_expand_escapes( - "keep \\' \\\" \\? \\\\ \\a \\b \\f \\n \\r \\t \\v \\z \\#"); - assert_true(0 == strcmp( - str, "keep ' \" ? \\\\ \a \b \f \n \r \t \v \\z #")); - SAFE_FREE(str); + QByteArray line = "keep \\' \\\" \\? \\\\ \\a \\b \\f \\n \\r \\t \\v \\z \\#"; + csync_exclude_expand_escapes(line); + assert_true(0 == strcmp(line.constData(), "keep ' \" ? \\\\ \a \b \f \n \r \t \v \\z #")); - str = csync_exclude_expand_escapes(""); - assert_true(0 == strcmp(str, "")); - SAFE_FREE(str); + line = ""; + csync_exclude_expand_escapes(line); + assert_true(0 == strcmp(line.constData(), "")); - str = csync_exclude_expand_escapes("\\"); - assert_true(0 == strcmp(str, "\\")); - SAFE_FREE(str); + line = "\\"; + csync_exclude_expand_escapes(line); + assert_true(0 == strcmp(line.constData(), "\\")); } }; // class ExcludedFilesTest @@ -539,6 +636,9 @@ int torture_run_tests(void) cmocka_unit_test_setup_teardown(T::check_csync_excluded_traversal, T::setup_init, T::teardown), cmocka_unit_test_setup_teardown(T::check_csync_dir_only, T::setup, T::teardown), cmocka_unit_test_setup_teardown(T::check_csync_pathes, T::setup_init, T::teardown), + cmocka_unit_test_setup_teardown(T::check_csync_wildcards, T::setup, T::teardown), + cmocka_unit_test_setup_teardown(T::check_csync_regex_translation, T::setup, T::teardown), + cmocka_unit_test_setup_teardown(T::check_csync_bname_trigger, T::setup, T::teardown), cmocka_unit_test_setup_teardown(T::check_csync_is_windows_reserved_word, T::setup_init, T::teardown), cmocka_unit_test_setup_teardown(T::check_csync_excluded_performance, T::setup_init, T::teardown), cmocka_unit_test(T::check_csync_exclude_expand_escapes), diff --git a/test/csync/csync_tests/check_csync_log.cpp b/test/csync/csync_tests/check_csync_log.cpp deleted file mode 100644 index 9c767b7c2..000000000 --- a/test/csync/csync_tests/check_csync_log.cpp +++ /dev/null @@ -1,140 +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 -#include - -#include "csync.h" -#include "csync_log.cpp" -#include "c_private.h" -#include "std/c_utf8.h" - -#include "torture.h" - -static int setup(void **state) { - int rc; - - rc = system("mkdir -p /tmp/check_csync1"); - assert_int_equal(rc, 0); - - *state = NULL; - - return 0; -} - -static int teardown(void **state) { - int rc; - - rc = system("rm -rf /tmp/check_csync"); - assert_int_equal(rc, 0); - - rc = system("rm -rf /tmp/check_csync1"); - assert_int_equal(rc, 0); - - *state = NULL; - - return 0; -} - -static void check_log_callback(int verbosity, - const char *function, - const char *buffer) -{ - int rc; - - assert_true(verbosity >= 0); - assert_non_null(function); - assert_false(function[0] == '\0'); - assert_non_null(buffer); - assert_false(buffer[0] == '\0'); - - rc = system("touch /tmp/check_csync1/cb_called"); - assert_int_equal(rc, 0); -} - -static void check_set_log_level(void **state) -{ - int rc; - - (void) state; - - rc = csync_set_log_level(-5); - assert_int_equal(rc, -1); - - rc = csync_set_log_level(5); - assert_int_equal(rc, 0); - - rc = csync_get_log_level(); - assert_int_equal(rc, 5); -} - -static void check_set_auth_callback(void **state) -{ - csync_log_callback log_fn; - int rc; - - (void) state; - - rc = csync_set_log_callback(NULL); - assert_int_equal(rc, -1); - - rc = csync_set_log_callback(check_log_callback); - assert_int_equal(rc, 0); - - log_fn = csync_get_log_callback(); - assert_non_null(log_fn); - assert_true(log_fn == &check_log_callback); -} - -static void check_logging(void **state) -{ - int rc; - csync_stat_t sb; - mbchar_t *path; - path = c_utf8_path_to_locale("/tmp/check_csync1/cb_called"); - - (void) state; /* unused */ - - assert_non_null(path); - - rc = csync_set_log_level(1); - assert_int_equal(rc, 0); - - rc = csync_set_log_callback(check_log_callback); - assert_int_equal(rc, 0); - - csync_log(1, __func__, "rc = %d", rc); - - rc = _tstat(path, &sb); - - c_free_locale_string(path); - - assert_int_equal(rc, 0); -} - -int torture_run_tests(void) -{ - const struct CMUnitTest tests[] = { - cmocka_unit_test(check_set_log_level), - cmocka_unit_test(check_set_auth_callback), - cmocka_unit_test_setup_teardown(check_logging, setup, teardown), - }; - - return cmocka_run_group_tests(tests, NULL, NULL); -} diff --git a/test/csync/csync_tests/check_csync_update.cpp b/test/csync/csync_tests/check_csync_update.cpp index db6819907..504ea2096 100644 --- a/test/csync/csync_tests/check_csync_update.cpp +++ b/test/csync/csync_tests/check_csync_update.cpp @@ -18,6 +18,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "csync_update.cpp" +#include #include "torture.h" @@ -184,7 +185,7 @@ static std::unique_ptr create_fstat(const char *name, fs->path = "file.txt"; } - fs->type = CSYNC_FTW_TYPE_FILE; + fs->type = ItemTypeFile; if (inode == 0) { fs->inode = 619070; diff --git a/test/csync/encoding_tests/check_encoding.cpp b/test/csync/encoding_tests/check_encoding.cpp index d2459b8d2..372005e93 100644 --- a/test/csync/encoding_tests/check_encoding.cpp +++ b/test/csync/encoding_tests/check_encoding.cpp @@ -19,7 +19,6 @@ */ #include #include "c_string.h" -#include "c_path.h" #include "c_utf8.h" #include "common/filesystembase.h" #include "torture.h" diff --git a/test/csync/log_tests/check_log.cpp b/test/csync/log_tests/check_log.cpp deleted file mode 100644 index f5f961f72..000000000 --- a/test/csync/log_tests/check_log.cpp +++ /dev/null @@ -1,98 +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 - -#include "support.h" - -#include "config_csync.h" -#include "csync_log.h" - -static void setup(void) { - csync_log_init(); -} - -static void teardown(void) { - csync_log_fini(); -} - -START_TEST (log_create) -{ - fail_unless((csync_log_init() == 0), NULL); - fail_unless((csync_log_fini() == 0), NULL); -} -END_TEST - -START_TEST (log_load) -{ - char buf[256]; - snprintf(buf, (size_t) 256 - 1, "%s/%s", SOURCEDIR, "config/ocsync_log.conf"); - fail_unless(csync_log_load(buf) == 0); -} -END_TEST - -START_TEST (log_prio) -{ - CSYNC_LOG(CSYNC_LOG_PRIORITY_FATAL, "log %s", "test"); - CSYNC_LOG(CSYNC_LOG_PRIORITY_FATAL, "log %s", "test"); - CSYNC_LOG(CSYNC_LOG_PRIORITY_ALERT, "log %s", "test"); - CSYNC_LOG(CSYNC_LOG_PRIORITY_CRIT, "log %s", "test"); - CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "log %s", "test"); - CSYNC_LOG(CSYNC_LOG_PRIORITY_WARN, "log %s", "test"); - CSYNC_LOG(CSYNC_LOG_PRIORITY_NOTICE, "log %s", "test"); - CSYNC_LOG(CSYNC_LOG_PRIORITY_INFO, "log %s", "test"); - CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "log %s", "test"); - CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "log %s", "test"); - CSYNC_LOG(CSYNC_LOG_PRIORITY_NOTSET, "log %s", "test"); - CSYNC_LOG(CSYNC_LOG_PRIORITY_UNKNOWN, "log %s", "test"); -} -END_TEST - -START_TEST (log_null) -{ - char *z = NULL; - CSYNC_LOG(CSYNC_LOG_PRIORITY_UNKNOWN, "log %s", z); -} -END_TEST - -static Suite *log_suite(void) { - Suite *s = suite_create("Logger"); - - create_case(s, "log_create", log_create); - create_case_fixture(s, "log_load", log_load, setup, teardown); - create_case_fixture(s, "log_prio", log_prio, setup, teardown); - create_case_fixture(s, "log_null", log_null, setup, teardown); - - return s; -} - -int main(void) { - int nf; - - Suite *s = log_suite(); - - SRunner *sr; - sr = srunner_create(s); - srunner_run_all(sr, CK_VERBOSE); - nf = srunner_ntests_failed(sr); - srunner_free(sr); - - return (nf == 0) ? EXIT_SUCCESS : EXIT_FAILURE; -} - diff --git a/test/csync/std_tests/check_std_c_path.c b/test/csync/std_tests/check_std_c_path.c deleted file mode 100644 index 2513d3fa2..000000000 --- a/test/csync/std_tests/check_std_c_path.c +++ /dev/null @@ -1,185 +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 -#include -#include -#include - -#include "std/c_path.h" - -#include "torture.h" - -static void check_c_basename(void **state) -{ - char *bname; - - (void) state; /* unused */ - - bname = c_basename("/usr/lib"); - assert_string_equal(bname, "lib"); - free(bname); - - bname = c_basename("/usr//"); - assert_string_equal(bname, "usr"); - free(bname); - - bname = c_basename("usr"); - assert_string_equal(bname, "usr"); - free(bname); - - bname = c_basename("///"); - assert_string_equal(bname, "/"); - free(bname); - - bname = c_basename("/"); - assert_string_equal(bname, "/"); - free(bname); - - bname = c_basename("."); - assert_string_equal(bname, "."); - free(bname); - - bname = c_basename(".."); - assert_string_equal(bname, ".."); - free(bname); - - bname = c_basename(""); - assert_string_equal(bname, "."); - free(bname); - - bname = c_basename(NULL); - assert_string_equal(bname, "."); - free(bname); -} - -static void check_c_basename_uri(void **state) -{ - char *bname = NULL; - - (void) state; /* unused */ - - bname = c_basename("smb://server/share/dir/"); - assert_string_equal(bname, "dir"); - free(bname); -} - -static void check_c_dirname(void **state) -{ - char *dname; - - (void) state; /* unused */ - - dname = c_dirname("/usr/lib"); - assert_string_equal(dname, "/usr"); - free(dname); - - dname = c_dirname("/usr//"); - assert_string_equal(dname, "/"); - free(dname); - - dname = c_dirname("usr"); - assert_string_equal(dname, "."); - free(dname); - - dname = c_dirname("/"); - assert_string_equal(dname, "/"); - free(dname); - - dname = c_dirname("///"); - assert_string_equal(dname, "/"); - free(dname); - - dname = c_dirname("."); - assert_string_equal(dname, "."); - free(dname); - - dname = c_dirname(".."); - assert_string_equal(dname, "."); - free(dname); - - dname = c_dirname(NULL); - assert_string_equal(dname, "."); - free(dname); -} - -static void check_c_dirname_uri(void **state) -{ - char *dname; - - (void) state; /* unused */ - - dname = c_dirname("smb://server/share/dir"); - assert_string_equal(dname, "smb://server/share"); - free(dname); -} - -static void check_c_parse_uri(void **state) -{ - const char *test_scheme = "git+ssh"; - const char *test_user = "gladiac"; - const char *test_passwd = "secret"; - const char *test_host = "git.csync.org"; - const char *test_path = "/srv/git/csync.git"; - - char *scheme = NULL; - char *user = NULL; - char *passwd = NULL; - char *host = NULL; - unsigned int port; - char *path = NULL; - char uri[1024] = {0}; - int rc; - - (void) state; /* unused */ - - rc = snprintf(uri, sizeof(uri), "%s://%s:%s@%s:22%s", - test_scheme, test_user, test_passwd, test_host, test_path); - assert_true(rc); - - rc = c_parse_uri(uri, &scheme, &user, &passwd, &host, &port, &path); - assert_int_equal(rc, 0); - - assert_string_equal(test_scheme, scheme); - assert_string_equal(test_user, user); - assert_string_equal(test_passwd, passwd); - assert_string_equal(test_host, host); - assert_int_equal(port, 22); - assert_string_equal(test_path, path); - - free(scheme); - free(user); - free(passwd); - free(host); - free(path); -} - -int torture_run_tests(void) -{ - const struct CMUnitTest tests[] = { - cmocka_unit_test(check_c_basename), - cmocka_unit_test(check_c_basename_uri), - cmocka_unit_test(check_c_dirname), - cmocka_unit_test(check_c_dirname_uri), - cmocka_unit_test(check_c_parse_uri), - }; - - return cmocka_run_group_tests(tests, NULL, NULL); -} - diff --git a/test/csync/std_tests/check_std_c_str.c b/test/csync/std_tests/check_std_c_str.c index 20921b100..e815958c2 100644 --- a/test/csync/std_tests/check_std_c_str.c +++ b/test/csync/std_tests/check_std_c_str.c @@ -49,70 +49,6 @@ static void check_c_streq_null(void **state) assert_false(c_streq(NULL, NULL)); } -static void check_c_strlist_new(void **state) -{ - c_strlist_t *strlist = NULL; - - (void) state; /* unused */ - - strlist = c_strlist_new(42); - assert_non_null(strlist); - assert_int_equal(strlist->size, 42); - assert_int_equal(strlist->count, 0); - - c_strlist_destroy(strlist); -} - -static void check_c_strlist_add(void **state) -{ - int rc; - size_t i = 0; - c_strlist_t *strlist = NULL; - - (void) state; /* unused */ - - strlist = c_strlist_new(42); - assert_non_null(strlist); - assert_int_equal(strlist->size, 42); - assert_int_equal(strlist->count, 0); - - for (i = 0; i < strlist->size; i++) { - rc = c_strlist_add(strlist, (char *) "foobar"); - assert_int_equal(rc, 0); - } - - assert_int_equal(strlist->count, 42); - assert_string_equal(strlist->vector[0], "foobar"); - assert_string_equal(strlist->vector[41], "foobar"); - - c_strlist_destroy(strlist); -} - -static void check_c_strlist_expand(void **state) -{ - c_strlist_t *strlist; - size_t i = 0; - int rc; - - (void) state; /* unused */ - - strlist = c_strlist_new(42); - assert_non_null(strlist); - assert_int_equal(strlist->size, 42); - assert_int_equal(strlist->count, 0); - - strlist = c_strlist_expand(strlist, 84); - assert_non_null(strlist); - assert_int_equal(strlist->size, 84); - - for (i = 0; i < strlist->size; i++) { - rc = c_strlist_add(strlist, (char *) "foobar"); - assert_int_equal(rc, 0); - } - - c_strlist_destroy(strlist); -} - int torture_run_tests(void) @@ -121,9 +57,6 @@ int torture_run_tests(void) cmocka_unit_test(check_c_streq_equal), cmocka_unit_test(check_c_streq_not_equal), cmocka_unit_test(check_c_streq_null), - cmocka_unit_test(check_c_strlist_new), - cmocka_unit_test(check_c_strlist_add), - cmocka_unit_test(check_c_strlist_expand), }; return cmocka_run_group_tests(tests, NULL, NULL); diff --git a/test/csync/std_tests/check_std_c_time.c b/test/csync/std_tests/check_std_c_time.c deleted file mode 100644 index db8e72ff6..000000000 --- a/test/csync/std_tests/check_std_c_time.c +++ /dev/null @@ -1,101 +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 - -#include "csync_time.h" -#include "std/c_time.h" -#include - -#include "torture.h" - -static void check_c_tspecdiff(void **state) -{ - struct timespec start, finish, diff; - - (void) state; /* unused */ - - csync_gettime(&start); - csync_gettime(&finish); - - diff = c_tspecdiff(finish, start); - - assert_int_equal(diff.tv_sec, 0); - assert_true(diff.tv_nsec >= 0); -} - -static void check_c_tspecdiff_five(void **state) -{ - struct timespec start, finish, diff; - - (void) state; /* unused */ - - csync_gettime(&start); - sleep(5); - csync_gettime(&finish); - - diff = c_tspecdiff(finish, start); - - assert_int_equal(diff.tv_sec, 5); - assert_true(diff.tv_nsec > 0); -} - -static void check_c_secdiff(void **state) -{ - struct timespec start, finish; - double diff; - - (void) state; /* unused */ - - csync_gettime(&start); - csync_gettime(&finish); - - diff = c_secdiff(finish, start); - - assert_true(diff >= 0.00 && diff < 1.00); -} - -static void check_c_secdiff_three(void **state) -{ - struct timespec start, finish; - double diff; - - (void) state; /* unused */ - - csync_gettime(&start); - sleep(3); - csync_gettime(&finish); - - diff = c_secdiff(finish, start); - - assert_true(diff > 3.00 && diff < 4.00); -} - -int torture_run_tests(void) -{ - const struct CMUnitTest tests[] = { - cmocka_unit_test(check_c_tspecdiff), - cmocka_unit_test(check_c_tspecdiff_five), - cmocka_unit_test(check_c_secdiff), - cmocka_unit_test(check_c_secdiff_three), - }; - - return cmocka_run_group_tests(tests, NULL, NULL); -} - diff --git a/test/csync/vio_tests/check_vio_ext.cpp b/test/csync/vio_tests/check_vio_ext.cpp index f86ce9591..f0e772956 100644 --- a/test/csync/vio_tests/check_vio_ext.cpp +++ b/test/csync/vio_tests/check_vio_ext.cpp @@ -218,7 +218,7 @@ static void traverse_dir(void **state, const char *dir, int *cnt) continue; } - is_dir = (dirent->type == CSYNC_FTW_TYPE_DIR) ? 1:0; + is_dir = (dirent->type == ItemTypeDirectory) ? 1:0; assert_int_not_equal( asprintf( &subdir, "%s/%s", dir, dirent->path.constData() ), -1 ); diff --git a/test/owncloud_add_test.cmake b/test/owncloud_add_test.cmake index 7e7cc5057..ee2cfd200 100644 --- a/test/owncloud_add_test.cmake +++ b/test/owncloud_add_test.cmake @@ -1,17 +1,17 @@ +find_package(Qt5 COMPONENTS Core Test Xml Network REQUIRED) + macro(owncloud_add_test test_class additional_cpp) set(CMAKE_AUTOMOC TRUE) set(OWNCLOUD_TEST_CLASS ${test_class}) string(TOLOWER "${OWNCLOUD_TEST_CLASS}" OWNCLOUD_TEST_CLASS_LOWERCASE) add_executable(${OWNCLOUD_TEST_CLASS}Test test${OWNCLOUD_TEST_CLASS_LOWERCASE}.cpp ${additional_cpp}) - qt5_use_modules(${OWNCLOUD_TEST_CLASS}Test Test Sql Xml Network) set_target_properties(${OWNCLOUD_TEST_CLASS}Test PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${BIN_OUTPUT_DIRECTORY}) target_link_libraries(${OWNCLOUD_TEST_CLASS}Test updater ${APPLICATION_EXECUTABLE}sync - ${QT_QTTEST_LIBRARY} - ${QT_QTCORE_LIBRARY} + Qt5::Core Qt5::Test Qt5::Xml Qt5::Network ) add_definitions(-DOWNCLOUD_TEST) @@ -25,14 +25,12 @@ macro(owncloud_add_benchmark test_class additional_cpp) string(TOLOWER "${OWNCLOUD_TEST_CLASS}" OWNCLOUD_TEST_CLASS_LOWERCASE) add_executable(${OWNCLOUD_TEST_CLASS}Bench benchmarks/bench${OWNCLOUD_TEST_CLASS_LOWERCASE}.cpp ${additional_cpp}) - qt5_use_modules(${OWNCLOUD_TEST_CLASS}Bench Test Sql Xml Network) set_target_properties(${OWNCLOUD_TEST_CLASS}Bench PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${BIN_OUTPUT_DIRECTORY}) target_link_libraries(${OWNCLOUD_TEST_CLASS}Bench updater ${APPLICATION_EXECUTABLE}sync - ${QT_QTTEST_LIBRARY} - ${QT_QTCORE_LIBRARY} + Qt5::Core Qt5::Test Qt5::Xml Qt5::Network ) add_definitions(-DOWNCLOUD_TEST) diff --git a/test/syncenginetestutils.h b/test/syncenginetestutils.h index c9f4e088a..f7cf4eb46 100644 --- a/test/syncenginetestutils.h +++ b/test/syncenginetestutils.h @@ -441,7 +441,8 @@ public: QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection); } - Q_INVOKABLE void respond() { + Q_INVOKABLE virtual void respond() + { emit uploadProgress(fileInfo->size, fileInfo->size); setRawHeader("OC-ETag", fileInfo->etag.toLatin1()); setRawHeader("ETag", fileInfo->etag.toLatin1()); @@ -451,7 +452,11 @@ public: emit finished(); } - void abort() override { } + void abort() override + { + setError(OperationCanceledError, "abort"); + emit finished(); + } qint64 readData(char *, qint64) override { return 0; } }; @@ -615,7 +620,7 @@ class FakeChunkMoveReply : public QNetworkReply public: FakeChunkMoveReply(FileInfo &uploadsFileInfo, FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, - quint64 delayMs, QObject *parent) + QObject *parent) : QNetworkReply{ parent } { setRequest(request); @@ -675,10 +680,11 @@ public: fileInfo->lastModified = OCC::Utility::qDateTimeFromTime_t(request.rawHeader("X-OC-Mtime").toLongLong()); remoteRootFileInfo.find(fileName, /*invalidate_etags=*/true); - QTimer::singleShot(delayMs, this, &FakeChunkMoveReply::respond); + QTimer::singleShot(0, this, &FakeChunkMoveReply::respond); } - Q_INVOKABLE void respond() { + Q_INVOKABLE virtual void respond() + { setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 201); setRawHeader("OC-ETag", fileInfo->etag.toLatin1()); setRawHeader("ETag", fileInfo->etag.toLatin1()); @@ -694,7 +700,12 @@ public: emit finished(); } - void abort() override { } + void abort() override + { + setError(OperationCanceledError, "abort"); + emit finished(); + } + qint64 readData(char *, qint64) override { return 0; } }; @@ -744,10 +755,32 @@ public: qint64 readData(char *, qint64) override { return 0; } }; +// A delayed reply +template +class DelayedReply : public OriginalReply +{ +public: + template + explicit DelayedReply(quint64 delayMS, Args &&... args) + : OriginalReply(std::forward(args)...) + , _delayMs(delayMS) + { + } + quint64 _delayMs; + + void respond() override + { + QTimer::singleShot(_delayMs, static_cast(this), [this] { + // Explicit call to bases's respond(); + this->OriginalReply::respond(); + }); + } +}; + class FakeQNAM : public QNetworkAccessManager { public: - using Override = std::function; + using Override = std::function; private: FileInfo _remoteRootFileInfo; @@ -770,7 +803,7 @@ protected: QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData = 0) { if (_override) { - if (auto reply = _override(op, request)) + if (auto reply = _override(op, request, outgoingData)) return reply; } const QString fileName = getFilePathFromUrl(request.url()); @@ -796,7 +829,7 @@ protected: else if (verb == QLatin1String("MOVE") && !isUpload) return new FakeMoveReply{info, op, request, this}; else if (verb == QLatin1String("MOVE") && isUpload) - return new FakeChunkMoveReply{ info, _remoteRootFileInfo, op, request, 0, this }; + return new FakeChunkMoveReply{ info, _remoteRootFileInfo, op, request, this }; else { qDebug() << verb << outgoingData; Q_UNREACHABLE(); diff --git a/test/testchunkingng.cpp b/test/testchunkingng.cpp index 78003b295..ce7880537 100644 --- a/test/testchunkingng.cpp +++ b/test/testchunkingng.cpp @@ -79,7 +79,7 @@ private slots: // Add a fake file to make sure it gets deleted fakeFolder.uploadState().children.first().insert("10000", size); - fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request) -> QNetworkReply * { + 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").toULongLong() >= uploadedSize); @@ -109,11 +109,11 @@ private slots: QByteArray moveChecksumHeader; int nGET = 0; int responseDelay = 10000; // bigger than abort-wait timeout - fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request) -> QNetworkReply * { + fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * { if (request.attribute(QNetworkRequest::CustomVerbAttribute) == "MOVE") { QTimer::singleShot(50, parent, [&]() { fakeFolder.syncEngine().abort(); }); moveChecksumHeader = request.rawHeader("OC-Checksum"); - return new FakeChunkMoveReply(fakeFolder.uploadState(), fakeFolder.remoteModifier(), op, request, responseDelay, parent); + return new DelayedReply(responseDelay, fakeFolder.uploadState(), fakeFolder.remoteModifier(), op, request, parent); } else if (op == QNetworkAccessManager::GetOperation) { nGET++; } @@ -161,17 +161,6 @@ private slots: QVERIFY(!moveChecksumHeader.isEmpty()); fakeFolder.remoteModifier().find("A/a0")->checksums = moveChecksumHeader; - // This time it's a real conflict, we have a remote checksum! - connection = connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToPropagate, [&](SyncFileItemVector &items) { - SyncFileItemPtr a0; - for (auto &item : items) { - if (item->_file == "A/a0") - a0 = item; - } - - QVERIFY(a0); - QCOMPARE(a0->_instruction, CSYNC_INSTRUCTION_CONFLICT); - }); QVERIFY(fakeFolder.syncOnce()); disconnect(connection); QCOMPARE(nGET, 0); // no new download, just a metadata update! @@ -203,11 +192,11 @@ private slots: QByteArray moveChecksumHeader; int nGET = 0; int responseDelay = 2000; // smaller than abort-wait timeout - fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request) -> QNetworkReply * { + fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * { if (request.attribute(QNetworkRequest::CustomVerbAttribute) == "MOVE") { QTimer::singleShot(50, parent, [&]() { fakeFolder.syncEngine().abort(); }); moveChecksumHeader = request.rawHeader("OC-Checksum"); - return new FakeChunkMoveReply(fakeFolder.uploadState(), fakeFolder.remoteModifier(), op, request, responseDelay, parent); + return new DelayedReply(responseDelay, fakeFolder.uploadState(), fakeFolder.remoteModifier(), op, request, parent); } else if (op == QNetworkAccessManager::GetOperation) { nGET++; } @@ -378,6 +367,65 @@ private slots: 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 ? 150 * 1000 * 1000 : 300; + + // Make the MOVE never reply, but trigger a client-abort and apply the change remotely + QByteArray checksumHeader; + int nGET = 0; + QScopedValueRollback setHttpTimeout(AbstractNetworkJob::httpTimeout, 1); + int responseDelay = AbstractNetworkJob::httpTimeout * 1000 * 1000; // 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 && 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); + } }; QTEST_GUILESS_MAIN(TestChunkingNG) diff --git a/test/testexcludedfiles.cpp b/test/testexcludedfiles.cpp index b743ffd81..5043768ec 100644 --- a/test/testexcludedfiles.cpp +++ b/test/testexcludedfiles.cpp @@ -11,7 +11,7 @@ using namespace OCC; -#define EXCLUDE_LIST_FILE SOURCEDIR"/../../sync-exclude.lst" +#define EXCLUDE_LIST_FILE SOURCEDIR "/../../sync-exclude.lst" class TestExcludedFiles: public QObject { diff --git a/test/testfolderman.cpp b/test/testfolderman.cpp index 39c657a05..15f044ba1 100644 --- a/test/testfolderman.cpp +++ b/test/testfolderman.cpp @@ -107,7 +107,7 @@ private slots: QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/sub/ownCloud1/folder").isNull()); QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/sub/ownCloud1/folder/f").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")); @@ -129,7 +129,6 @@ private slots: QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/link4").isNull()); QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/link3/folder").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()); @@ -140,12 +139,13 @@ private slots: QVERIFY(folderman->checkPathValidityForNewFolder(dirPath + "/link1/subfolder").isNull()); QVERIFY(folderman->checkPathValidityForNewFolder(dirPath + "/link2/free/subfolder").isNull()); - // Invalid paths - QVERIFY(!folderman->checkPathValidityForNewFolder("").isNull()); - // Should not have the rights QVERIFY(!folderman->checkPathValidityForNewFolder("/").isNull()); QVERIFY(!folderman->checkPathValidityForNewFolder("/usr/bin/somefolder").isNull()); +#endif + + // Invalid paths + QVERIFY(!folderman->checkPathValidityForNewFolder("").isNull()); } void testFindGoodPathForNewSyncFolder() diff --git a/test/testoauth.cpp b/test/testoauth.cpp index 76dbb3bc5..49eb3ce6c 100644 --- a/test/testoauth.cpp +++ b/test/testoauth.cpp @@ -111,7 +111,7 @@ public: account->setUrl(sOAuthTestServer); account->setCredentials(new FakeCredentials{fakeQnam}); fakeQnam->setParent(this); - fakeQnam->setOverride([this] (QNetworkAccessManager::Operation op, const QNetworkRequest &req) { + fakeQnam->setOverride([this](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *) { return this->tokenReply(op, req); }); diff --git a/test/testsyncconflict.cpp b/test/testsyncconflict.cpp new file mode 100644 index 000000000..daf7eab0d --- /dev/null +++ b/test/testsyncconflict.cpp @@ -0,0 +1,553 @@ +/* + * 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 "syncenginetestutils.h" +#include + +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 itemSuccessful(const QSignalSpy &spy, const QString &path, const csync_instructions_e instr) +{ + auto item = findItem(spy, path); + return item->_status == SyncFileItem::Success && item->_instruction == instr; +} + +bool itemConflict(const QSignalSpy &spy, const QString &path) +{ + auto item = findItem(spy, path); + return item->_status == SyncFileItem::Conflict && item->_instruction == CSYNC_INSTRUCTION_CONFLICT; +} + +bool itemSuccessfulMove(const QSignalSpy &spy, const QString &path) +{ + return itemSuccessful(spy, path, CSYNC_INSTRUCTION_RENAME); +} + +QStringList findConflicts(const FileInfo &dir) +{ + QStringList conflicts; + for (const auto &item : dir.children) { + if (item.name.contains("conflict")) { + conflicts.append(item.path()); + } + } + return conflicts; +} + +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 : base->children) { + if (item.name.startsWith(pathComponents.fileName()) && item.name.contains("_conflict")) { + local.remove(item.path()); + return true; + } + } + return false; +} + +class TestSyncConflict : public QObject +{ + Q_OBJECT + +private slots: + void testNoUpload() + { + FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + 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()); + QVERIFY(expectAndWipeConflict(fakeFolder.localModifier(), fakeFolder.currentLocalState(), "A/a1")); + QVERIFY(expectAndWipeConflict(fakeFolder.localModifier(), fakeFolder.currentLocalState(), "A/a2")); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + } + + void testUploadAfterDownload() + { + FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; + fakeFolder.syncEngine().account()->setCapabilities({ { "uploadConflictFiles", true } }); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + QMap conflictMap; + fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * { + if (op == QNetworkAccessManager::PutOperation) { + auto baseFileId = request.rawHeader("OC-ConflictBaseFileId"); + if (!baseFileId.isEmpty()) { + auto components = request.url().toString().split('/'); + QString conflictFile = components.mid(components.size() - 2).join('/'); + conflictMap[baseFileId] = conflictFile; + } + } + 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::conflictFileBaseName(conflictMap[a1FileId].toUtf8()), QByteArray("A/a1")); + + 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 } }); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + QMap conflictMap; + fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * { + if (op == QNetworkAccessManager::PutOperation) { + auto baseFileId = request.rawHeader("OC-ConflictBaseFileId"); + if (!baseFileId.isEmpty()) { + auto components = request.url().toString().split('/'); + QString conflictFile = components.mid(components.size() - 2).join('/'); + conflictMap[baseFileId] = conflictFile; + } + } + 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_conflict-me-1234"); + fakeFolder.localModifier().insert(conflictName, 64, 'L'); + ConflictRecord conflictRecord; + conflictRecord.path = conflictName.toUtf8(); + conflictRecord.baseFileId = a1FileId; + 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_conflict-1234"); + QVERIFY(fakeFolder.syncOnce()); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + auto conflictRecord = fakeFolder.syncJournal().conflictRecord("A/a1_conflict-1234"); + QVERIFY(conflictRecord.isValid()); + QCOMPARE(conflictRecord.baseFileId, fakeFolder.remoteModifier().find("A/a1")->fileId); + + // 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"); + 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")); + } + + // 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 } }); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + // Make conflict records + ConflictRecord conflictRecord; + conflictRecord.path = "A/a1"; + fakeFolder.syncJournal().setConflictRecord(conflictRecord); + conflictRecord.path = "A/a2"; + fakeFolder.syncJournal().setConflictRecord(conflictRecord); + + // A nothing-to-sync keeps them alive + QVERIFY(fakeFolder.syncOnce()); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + 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()); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + 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 } }); + 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()); + + 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(); + } + + // A nothing-to-sync keeps them alive + 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.syncOnce()); + QVERIFY(fakeFolder.syncJournal().conflictRecord(a1conflict).isValid()); + QVERIFY(!fakeFolder.syncJournal().conflictRecord(a2conflict).isValid()); + } + + void testConflictFileBaseName_data() + { + QTest::addColumn("input"); + QTest::addColumn("output"); + + QTest::newRow("") + << "a/b/foo" + << ""; + QTest::newRow("") + << "a/b/foo.txt" + << ""; + QTest::newRow("") + << "a/b/foo_conflict" + << ""; + QTest::newRow("") + << "a/b/foo_conflict.txt" + << ""; + + QTest::newRow("") + << "a/b/foo_conflict-123.txt" + << "a/b/foo.txt"; + QTest::newRow("") + << "a/b/foo_conflict-foo-123.txt" + << "a/b/foo.txt"; + + QTest::newRow("") + << "a/b/foo_conflict-123" + << "a/b/foo"; + QTest::newRow("") + << "a/b/foo_conflict-foo-123" + << "a/b/foo"; + + // double conflict files + QTest::newRow("") + << "a/b/foo_conflict-123_conflict-456.txt" + << "a/b/foo_conflict-123.txt"; + QTest::newRow("") + << "a/b/foo_conflict-foo-123_conflict-bar-456.txt" + << "a/b/foo_conflict-foo-123.txt"; + } + + void testConflictFileBaseName() + { + QFETCH(QString, input); + QFETCH(QString, output); + QCOMPARE(Utility::conflictFileBaseName(input.toUtf8()), output.toUtf8()); + } + + void testLocalDirRemoteFileConflict() + { + FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; + fakeFolder.syncEngine().account()->setCapabilities({ { "uploadConflictFiles", true } }); + QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); + + auto cleanup = [&]() { + completeSpy.clear(); + }; + 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); + + // 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"); + + // 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); + + QVERIFY(fakeFolder.syncOnce()); + + auto conflicts = findConflicts(fakeFolder.currentLocalState()); + conflicts += findConflicts(fakeFolder.currentLocalState().children["A"]); + QCOMPARE(conflicts.size(), 3); + std::sort(conflicts.begin(), conflicts.end()); + + 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")); + QCOMPARE(conflicts[2].toUtf8(), conflictRecords[2]); + QVERIFY(QFileInfo(fakeFolder.localPath() + conflicts[2]).isDir()); + QVERIFY(QFile::exists(fakeFolder.localPath() + conflicts[2] + "/foo")); + + // 2) + QVERIFY(itemConflict(completeSpy, "A/a1")); + QCOMPARE(fakeFolder.currentLocalState().find("A/a1")->size, 5); + QVERIFY(conflicts[0].contains("A/a1")); + QCOMPARE(conflicts[0].toUtf8(), conflictRecords[0]); + QVERIFY(QFileInfo(fakeFolder.localPath() + conflicts[0]).isDir()); + QVERIFY(QFile::exists(fakeFolder.localPath() + conflicts[0] + "/bar")); + + // 3) + QVERIFY(itemConflict(completeSpy, "B")); + QCOMPARE(fakeFolder.currentLocalState().find("B")->size, 31); + QVERIFY(conflicts[1].contains("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()); + } + + void testLocalFileRemoteDirConflict() + { + FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; + fakeFolder.syncEngine().account()->setCapabilities({ { "uploadConflictFiles", true } }); + QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); + + // 1) a NEW/NEW conflict + fakeFolder.remoteModifier().mkdir("Z"); + fakeFolder.remoteModifier().mkdir("Z/subdir"); + fakeFolder.remoteModifier().insert("Z/foo"); + fakeFolder.localModifier().insert("Z"); + + // 2) local dir becomes file: remote dir adds file + fakeFolder.localModifier().remove("A"); + fakeFolder.localModifier().insert("A", 63); + fakeFolder.remoteModifier().insert("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()); + auto conflicts = findConflicts(fakeFolder.currentLocalState()); + conflicts += findConflicts(fakeFolder.currentLocalState().children["B"]); + QCOMPARE(conflicts.size(), 3); + std::sort(conflicts.begin(), conflicts.end()); + + 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")); + QCOMPARE(conflicts[2].toUtf8(), conflictRecords[2]); + + // 2) + QVERIFY(itemConflict(completeSpy, "A")); + QVERIFY(conflicts[0].contains("A")); + QCOMPARE(conflicts[0].toUtf8(), conflictRecords[0]); + + // 3) + QVERIFY(itemConflict(completeSpy, "B/b1")); + QVERIFY(conflicts[1].contains("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() }; + QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); + + // 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"); + + // 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"); + + QVERIFY(fakeFolder.syncOnce()); + + QVERIFY(itemSuccessful(completeSpy, "A", CSYNC_INSTRUCTION_TYPE_CHANGE)); + QVERIFY(itemConflict(completeSpy, "B")); + + auto conflicts = findConflicts(fakeFolder.currentLocalState()); + std::sort(conflicts.begin(), conflicts.end()); + QVERIFY(conflicts.size() == 2); + QVERIFY(conflicts[0].contains("A_conflict")); + QVERIFY(conflicts[1].contains("B_conflict")); + for (auto conflict : conflicts) + QDir(fakeFolder.localPath() + conflict).removeRecursively(); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + // Currently a1 and b1 don't get moved, but redownloaded + } + + void testTypeChange() + { + FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; + QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); + + // dir becomes file + fakeFolder.remoteModifier().remove("A"); + fakeFolder.remoteModifier().insert("A"); + fakeFolder.localModifier().remove("B"); + fakeFolder.localModifier().insert("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"); + + QVERIFY(fakeFolder.syncOnce()); + + 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)); + + // A becomes a conflict because we don't delete folders with files + // inside of them! + auto conflicts = findConflicts(fakeFolder.currentLocalState()); + QVERIFY(conflicts.size() == 1); + QVERIFY(conflicts[0].contains("A_conflict")); + for (auto conflict : conflicts) + QDir(fakeFolder.localPath() + conflict).removeRecursively(); + + QVERIFY(fakeFolder.syncEngine().isAnotherSyncNeeded() == ImmediateFollowUp); + QVERIFY(fakeFolder.syncOnce()); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + } +}; + +QTEST_GUILESS_MAIN(TestSyncConflict) +#include "testsyncconflict.moc" diff --git a/test/testsyncengine.cpp b/test/testsyncengine.cpp index e5dccd939..ff291cc39 100644 --- a/test/testsyncengine.cpp +++ b/test/testsyncengine.cpp @@ -301,7 +301,7 @@ private slots: FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; int nGET = 0; - fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &) { + fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &, QIODevice *) { if (op == QNetworkAccessManager::GetOperation) ++nGET; return nullptr; @@ -431,7 +431,7 @@ private slots: int remoteQuota = 1000; int n507 = 0, nPUT = 0; auto parent = new QObject; - fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request) -> QNetworkReply * { + fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * { if (op == QNetworkAccessManager::PutOperation) { nPUT++; if (request.rawHeader("OC-Total-Length").toInt() > remoteQuota) { @@ -472,7 +472,7 @@ private slots: QByteArray checksumValue; QByteArray contentMd5Value; - fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request) -> QNetworkReply * { + 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()) @@ -536,6 +536,7 @@ private slots: { FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; +#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"); @@ -547,6 +548,7 @@ private slots: fakeFolder.localModifier().insert("B/\\:?*\"<>|.txt"); QVERIFY(fakeFolder.syncOnce()); QVERIFY(!fakeFolder.currentRemoteState().find("B/\\:?*\"<>|.txt")); +#endif // We can override that by setting the capability fakeFolder.syncEngine().account()->setCapabilities({ { "dav", QVariantMap{ { "invalidFilenameRegex", "" } } } }); @@ -620,6 +622,56 @@ private slots: QVERIFY(localFileExists("A/.hidden")); QVERIFY(fakeFolder.currentRemoteState().find("B/.hidden")); } + + void testNoLocalEncoding() + { + auto utf8Locale = QTextCodec::codecForLocale(); + if (utf8Locale->mibEnum() != 106) { + QSKIP("Test only works for UTF8 locale"); + } + + 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")); + + // Try again with a locale that can represent ö but not 𠜎 (4-byte utf8). + QTextCodec::setCodecForLocale(QTextCodec::codecForName("ISO-8859-15")); + QVERIFY(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 + QTextCodec::setCodecForLocale(QTextCodec::codecForName("ASCII")); + QVERIFY(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")); + + QTextCodec::setCodecForLocale(utf8Locale); + } }; QTEST_GUILESS_MAIN(TestSyncEngine) diff --git a/test/testsyncjournaldb.cpp b/test/testsyncjournaldb.cpp index cb86a1655..389330ffa 100644 --- a/test/testsyncjournaldb.cpp +++ b/test/testsyncjournaldb.cpp @@ -54,7 +54,7 @@ private slots: // signed int being cast to uint64 either (like uint64::max would be) record._inode = std::numeric_limits::max() + 12ull; record._modtime = dropMsecs(QDateTime::currentDateTime()); - record._type = 5; + record._type = ItemTypeDirectory; record._etag = "789789"; record._fileId = "abcd"; record._remotePerm = RemotePermissions("RW"); @@ -76,7 +76,7 @@ private slots: record._modtime = dropMsecs(QDateTime::currentDateTime().addDays(1)); // try a value that only fits uint64, not int64 record._inode = std::numeric_limits::max() - std::numeric_limits::max() - 1; - record._type = 7; + record._type = ItemTypeFile; record._etag = "789FFF"; record._fileId = "efg"; record._remotePerm = RemotePermissions("NV"); @@ -183,6 +183,28 @@ private slots: QCOMPARE(record.numericFileId(), QByteArray("123456789")); } + void testConflictRecord() + { + ConflictRecord record; + record.path = "abc"; + record.baseFileId = "def"; + record.baseModtime = 1234; + record.baseEtag = "ghi"; + + QVERIFY(!_db.conflictRecord(record.path).isValid()); + + _db.setConflictRecord(record); + auto newRecord = _db.conflictRecord(record.path); + QVERIFY(newRecord.isValid()); + QCOMPARE(newRecord.path, record.path); + QCOMPARE(newRecord.baseFileId, record.baseFileId); + QCOMPARE(newRecord.baseModtime, record.baseModtime); + QCOMPARE(newRecord.baseEtag, record.baseEtag); + + _db.deleteConflictRecord(record.path); + QVERIFY(!_db.conflictRecord(record.path).isValid()); + } + private: SyncJournalDb _db; }; diff --git a/test/testsyncmove.cpp b/test/testsyncmove.cpp index 5049b019a..67d3c22be 100644 --- a/test/testsyncmove.cpp +++ b/test/testsyncmove.cpp @@ -158,7 +158,7 @@ private slots: int nPUT = 0; int nDELETE = 0; - fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &) { + fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &, QIODevice *) { if (op == QNetworkAccessManager::PutOperation) ++nPUT; if (op == QNetworkAccessManager::DeleteOperation) @@ -268,7 +268,7 @@ private slots: QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); int nGET = 0; - fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &) { + fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &, QIODevice *) { if (op == QNetworkAccessManager::GetOperation) ++nGET; return nullptr; @@ -313,7 +313,7 @@ private slots: int nPUT = 0; int nMOVE = 0; int nDELETE = 0; - fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &req) { + fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *) { if (op == QNetworkAccessManager::GetOperation) ++nGET; if (op == QNetworkAccessManager::PutOperation) diff --git a/test/testxmlparse.cpp b/test/testxmlparse.cpp index 9ec675f47..6c0945554 100644 --- a/test/testxmlparse.cpp +++ b/test/testxmlparse.cpp @@ -238,6 +238,40 @@ private slots: QVERIFY(_subdirs.size() == 0); } + 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 + + + LsColXMLParser parser; + + connect( &parser, SIGNAL(directoryListingSubfolders(const QStringList&)), + this, SLOT(slotDirectoryListingSubFolders(const QStringList&)) ); + connect( &parser, SIGNAL(directoryListingIterated(const QString&, const QMap&)), + this, SLOT(slotDirectoryListingIterated(const QString&, const QMap&)) ); + connect( &parser, SIGNAL(finishedWithoutError()), + this, SLOT(slotFinishedSuccessfully()) ); + + QHash sizes; + QVERIFY(!parser.parse( testXml, &sizes, "/oc/remote.php/webdav/sharefolder" )); + QVERIFY(!_success); + } + void testParserBogfusHref1() { const QByteArray testXml = "" "" diff --git a/theme.qrc b/theme.qrc index 4f4357e61..1be58f01e 100644 --- a/theme.qrc +++ b/theme.qrc @@ -1,12 +1,12 @@ - theme/colored/owncloud-icon-512.png - theme/colored/owncloud-icon-256.png - theme/colored/owncloud-icon-128.png - theme/colored/owncloud-icon-22.png - theme/colored/owncloud-icon-32.png - theme/colored/owncloud-icon-48.png - theme/colored/owncloud-icon-64.png + theme/colored/512-Nextcloud-icon.png + theme/colored/256-Nextcloud-icon.png + theme/colored/128-Nextcloud-icon.png + theme/colored/22-Nextcloud-icon.png + theme/colored/32-Nextcloud-icon.png + theme/colored/48-Nextcloud-icon.png + theme/colored/64-Nextcloud-icon.png theme/colored/state-error-32.png theme/colored/state-error-64.png theme/colored/state-error-128.png diff --git a/theme/colored/1024-Nextcloud-icon.png b/theme/colored/1024-Nextcloud-icon.png new file mode 100644 index 000000000..b8c89ee03 Binary files /dev/null and b/theme/colored/1024-Nextcloud-icon.png differ diff --git a/theme/colored/Nextcloud-icon-128.png b/theme/colored/128-Nextcloud-icon.png similarity index 100% rename from theme/colored/Nextcloud-icon-128.png rename to theme/colored/128-Nextcloud-icon.png diff --git a/theme/colored/Nextcloud-icon-16.png b/theme/colored/16-Nextcloud-icon.png similarity index 100% rename from theme/colored/Nextcloud-icon-16.png rename to theme/colored/16-Nextcloud-icon.png diff --git a/theme/colored/nextcloud-icon-16.png b/theme/colored/16-Nextcloud-sidebar.png similarity index 100% rename from theme/colored/nextcloud-icon-16.png rename to theme/colored/16-Nextcloud-sidebar.png diff --git a/theme/colored/18-Nextcloud-sidebar.png b/theme/colored/18-Nextcloud-sidebar.png new file mode 100644 index 000000000..017f0204a Binary files /dev/null and b/theme/colored/18-Nextcloud-sidebar.png differ diff --git a/theme/colored/Nextcloud-icon-22.png b/theme/colored/22-Nextcloud-icon.png similarity index 100% rename from theme/colored/Nextcloud-icon-22.png rename to theme/colored/22-Nextcloud-icon.png diff --git a/theme/colored/Nextcloud-icon-256.png b/theme/colored/256-Nextcloud-icon.png similarity index 100% rename from theme/colored/Nextcloud-icon-256.png rename to theme/colored/256-Nextcloud-icon.png diff --git a/theme/colored/Nextcloud-icon-32.png b/theme/colored/32-Nextcloud-icon.png similarity index 100% rename from theme/colored/Nextcloud-icon-32.png rename to theme/colored/32-Nextcloud-icon.png diff --git a/theme/colored/nextcloud-icon-32.png b/theme/colored/32-Nextcloud-sidebar.png similarity index 100% rename from theme/colored/nextcloud-icon-32.png rename to theme/colored/32-Nextcloud-sidebar.png diff --git a/theme/colored/36-Nextcloud-sidebar.png b/theme/colored/36-Nextcloud-sidebar.png new file mode 100644 index 000000000..2fccf344b Binary files /dev/null and b/theme/colored/36-Nextcloud-sidebar.png differ diff --git a/theme/colored/Nextcloud-icon-48.png b/theme/colored/48-Nextcloud-icon.png similarity index 100% rename from theme/colored/Nextcloud-icon-48.png rename to theme/colored/48-Nextcloud-icon.png diff --git a/theme/colored/Nextcloud-icon-512.png b/theme/colored/512-Nextcloud-icon.png similarity index 100% rename from theme/colored/Nextcloud-icon-512.png rename to theme/colored/512-Nextcloud-icon.png diff --git a/theme/colored/Nextcloud-icon-64.png b/theme/colored/64-Nextcloud-icon.png similarity index 100% rename from theme/colored/Nextcloud-icon-64.png rename to theme/colored/64-Nextcloud-icon.png diff --git a/theme/colored/nextcloud-icon-64.png b/theme/colored/64-Nextcloud-sidebar.png similarity index 100% rename from theme/colored/nextcloud-icon-64.png rename to theme/colored/64-Nextcloud-sidebar.png diff --git a/theme/colored/nextcloud-icon-128.png b/theme/colored/nextcloud-icon-128.png deleted file mode 100644 index c97f36807..000000000 Binary files a/theme/colored/nextcloud-icon-128.png and /dev/null differ diff --git a/theme/colored/nextcloud-icon-22.png b/theme/colored/nextcloud-icon-22.png deleted file mode 100644 index d12e6826e..000000000 Binary files a/theme/colored/nextcloud-icon-22.png and /dev/null differ diff --git a/theme/colored/nextcloud-icon-256.png b/theme/colored/nextcloud-icon-256.png deleted file mode 100644 index 11b0833ca..000000000 Binary files a/theme/colored/nextcloud-icon-256.png and /dev/null differ diff --git a/theme/colored/nextcloud-icon-48.png b/theme/colored/nextcloud-icon-48.png deleted file mode 100644 index a1a9625f9..000000000 Binary files a/theme/colored/nextcloud-icon-48.png and /dev/null differ diff --git a/theme/colored/nextcloud-icon-512.png b/theme/colored/nextcloud-icon-512.png deleted file mode 100644 index 9f40c76cf..000000000 Binary files a/theme/colored/nextcloud-icon-512.png and /dev/null differ diff --git a/theme/colored/owncloud-icon-1024.png b/theme/colored/owncloud-icon-1024.png deleted file mode 100644 index e32847c1c..000000000 Binary files a/theme/colored/owncloud-icon-1024.png and /dev/null differ diff --git a/theme/colored/owncloud-icon-128.png b/theme/colored/owncloud-icon-128.png deleted file mode 100644 index f5f127a05..000000000 Binary files a/theme/colored/owncloud-icon-128.png and /dev/null differ diff --git a/theme/colored/owncloud-icon-16.png b/theme/colored/owncloud-icon-16.png deleted file mode 100644 index 5c01c4fe9..000000000 Binary files a/theme/colored/owncloud-icon-16.png and /dev/null differ diff --git a/theme/colored/owncloud-icon-22.png b/theme/colored/owncloud-icon-22.png deleted file mode 100644 index be8a913e3..000000000 Binary files a/theme/colored/owncloud-icon-22.png and /dev/null differ diff --git a/theme/colored/owncloud-icon-256.png b/theme/colored/owncloud-icon-256.png deleted file mode 100644 index ec0ecdb1a..000000000 Binary files a/theme/colored/owncloud-icon-256.png and /dev/null differ diff --git a/theme/colored/owncloud-icon-32.png b/theme/colored/owncloud-icon-32.png deleted file mode 100644 index 721c04e48..000000000 Binary files a/theme/colored/owncloud-icon-32.png and /dev/null differ diff --git a/theme/colored/owncloud-icon-48.png b/theme/colored/owncloud-icon-48.png deleted file mode 100644 index e696d98ab..000000000 Binary files a/theme/colored/owncloud-icon-48.png and /dev/null differ diff --git a/theme/colored/owncloud-icon-512.png b/theme/colored/owncloud-icon-512.png deleted file mode 100644 index 723d58382..000000000 Binary files a/theme/colored/owncloud-icon-512.png and /dev/null differ diff --git a/theme/colored/owncloud-icon-64.png b/theme/colored/owncloud-icon-64.png deleted file mode 100644 index 5c6f17f0c..000000000 Binary files a/theme/colored/owncloud-icon-64.png and /dev/null differ diff --git a/theme/colored/owncloud-icon.svg b/theme/colored/owncloud-icon.svg deleted file mode 100644 index cb01fd8c4..000000000 --- a/theme/colored/owncloud-icon.svg +++ /dev/null @@ -1,1400 +0,0 @@ - - - - - - - - - - - -]> - - - - - - - - - - - - - - - - - - - - - - eJzUvfuSHcmRp/cEeIfSH2PGNhNrMyLvXJnMcJ2lRA5pbHJEabTWBnYXm9hBA21oNCny6eU/jzzn -ZPrnBwXuGY1mCesmWeXIa1y+DI+I7x/+p19/+dOn37z/w8NP+/vu7sk//MPzDw+vP77/8LM7/+nd -z9++/fGHjx/0o5/85ou7Mt93FvT058tXW+A/P3z44c37dz+7K8t95798pb/9k6df/+nNd1/c/eQL -+8lv33x8+2A/e/+Xd2+/fv/jNz998/X7d/c//PnbL04ntCO8eP3RYoodY70v413pf9ZN+vXrd39+ -/cMPb/6mX0790tvPnr3/8d03b959++z9/2M/LPN4t/T9XandfLeM1QL+y5vfPPyQRt3PS9fVta7T -Yj+wvzLdr/afaR7n0f96ue+Wrk5r309zV+xYL95//eN3D+8+/vrD+68ffvjh+fu37z/88LO75399 -/e7ul6+/td+8vvs/H96+ff+Xu2dvX3/9r0/s6YxfvXrz9sEexHevP9qd6LE8/XmpXz378c3bb/7p -x+/+8PBBD0w/7b/yI/7uBzuUHVX/Wz+ev/r5d/aTLx8+frRbsPPpyf7mH5/tr8J+6H9+8i+/tif0 -YCf78K8P7/7rF9thP7z/Xj/54XTzdbyf5/X8n9Pzmu2RtL/x24fvvn9rb2F7YGu5t7816N/7/7PF -2v153Fr0m2Eq4eDD3Okv1mE4H//yJB/+/ObhLz+7+6f37x7a43r64eOX7RUPQ9e1f7ff/ObHtw8f -fvfuzUfdh360tgf2y/ffPLzdfuR//9Xb1/6c/E+5/LsF/Pb1h28fPlqpeP/2x49eXpfTGex9/OL1 -Xx8+XE7wq+8f3v32/T/7NZZSp/tq9z5P9yox010dJj28oburk59mWOwdD+czl8u/2wl0OB3sdJbZ -XuWv7eX+6sObb9+8+9l2jfNX//jhzTeXFz7Xu6X9y2/j3q5X/7FXUTsrv5/7k/Y+5qmMSxnq5/yk -PQN7nB8/PrzbnomV3ee/3BXG7v6XX9pdvHz3zfP33+md/qBq+WC3YwX07ftv2+/O/9t/Y3/9x+/b -E/H//5W9/l9/ePNOx3zyT/6b5atfv/3RfvWPH97/+P3P3/3x/ZOftGbonx++trbGSsg3d7/6w3+z -/2Mtyz+9/vpP3z787eHN13969/Dx4Ye7Z1a57Ofn2PvXb77/4pOHtZv88HDXfml/0//v6b8f/9sv -Hv5oNfzy19tP/8v/Xfv57duHdz/9v3784fXHv333/psf39pvW/TjR/3129fvXn+485+fDro/1N3P -3318+PD6Xz+++fPdd6/tPBakv/Pw8fefcXArgN/b83nz59NtH0/0qYDdrx4/0Zdfq/h8uHv24ccf -/nT32/fv355P8eX3D3978/rt92/e/fDwNjyk7a/539Jf+g90ol+//vgna+Ef3n3zw/kMT999+1bl -78d33+J9/8KeUwv/rGPbxbz71bt2Et7KL60TfviQ3ok1fe3v/X338u96vvPfyc6lavs/4Jmev377 -9s23H15//6c3X2cn+9+33//xzUN6zv3f/3vK+1+/+8P7t29++O5SzHc/+fXrDx/ffP324cu/2vv7 -nMamFYS7l9+8sTbzSpPwyZgv//L649d/+sWbP3yymOvZ/vHNu2+son7545uPD+eD65moA/n24YeH -j3/7aE2yNZJ+I6e/8OX5uONX1qfse42f/vRJuXv2bten/OOH19+8se7IYPN37969/s76i2+3H33x -hD+yjrXePfvmyb88+V+eDC+Gl8OrsRvLWMd+HMZxnAwKl3Edn47Pxufji/Hl+GrqpjLVqZ+GaZym -aZ6WaZ2eTs/sz/PpxfRyejV3c5nr3D+ZB4PKeV7mdX5qf57Nz+cX88v51VKWuvTLsIyL/e1lXpZl -XZ4uz5bny4vl5fJq7Vbrv9d+HezPuE6rHWC1PvrJ+nR9tj5fX9ifl+urp93T8rQ+7Z8O9md8Otk5 -lqfr06dPn9mf509fPH355H+12yqLsdvT8sz/eW5/XpZXtavF/6m1r0Md61SXOts/a7Uj1mf1eX3h -/7ysL/vO/pS++j99Pzzpx14Yvvo/T/tn/fP+hf156f+8GrrBEGLoh8H/GQd7UMM8LHaRq/3zbHh+ -es6nZ/3EHnd72Pr3/oHrHz329tCL/dsffLst3c4zv6UXuqXttjq7Jd1WuzH9M/mNtZtb/QZPt+c3 -2Hf1VX3Vd0/O9zj4DY7+79n+LOcbfer/frH9eanb7V9uN1y32+7tlnXb4xO779Hv3960P4HVn8Bq -z0BP4fn2LF4Mr+x52BPx2+oe/085/6mHP/35z3D+Mz7p7CF3k/+Ztz/L9sfKWffU/zzzP8/tzwv7 -89L+vOpe2Rnaf+yBlr4M/md8UvxVlNnL1bqVrefnF/HSX8ar84vo/baevXz24tnzZ8+saK7Plmez -VZfRHkP/rNor7J6+evrSyutzK7dPrQQvVpInK9GDPe9qZbxbX1l5f2El/5nVgNWqwvzEasVoz7O3 -N1rWbnll9eaF1Z9nVpMWq1GT1a3BXly1utbNr6zWvbDa98xq4Wr1cZ71uTfYy61WVbvp1fTyidXd -51aHn1ptXqxWT1a7BytsVcVufGU1/4UXyKdWOO2TaZz8tl6M9v76F1aOXpQX3fNXz18+f/H8+XO7 -0+dPn6/Pl+fz88mKsN52b+WtPO+evfJHER/G9jienJ9HfCLxmcSnsj2X82PZHsyT85PRs3m6ezrx -+WxPyG/L3mH34pX9efnixYvnutoXT63RWV5YzItpu+n+fNOn235+vu1w40905+d7f76790tROBeG -XXHIbt8fwJPDE7iUjMszuBQOLx52W8PdP3z17IO19N39tNRqr7ibxnGdx96+gPq1WFO0zl3trV75 -T7pVBXwqix2hn+0npbfCYo3Tuqhh03dTbwVoHNaurFa5FvsAvB+W2o91HeyD21o9+267H/1X1rB4 -qPU25W66G+3T0L5y7YLsI+qr/zgX9OyHJzpRWZZhmO0rpiv2aasBGWtFa7XObFpLp59Y2zCXydrH -qZR+Wf37dB5Gu8TOnnoZrQ7pu3J36m70r9FwOeez7x7G/0/nt3t/9uJMGSeO+PTnat9+swGSMcev -37/919d3L95/PEGTQQd/dlfvu34o8zYGdT+uds9l0ZtZy91c9r/VKMr+13pEd0/937//S/s/D/Zf -P/r/LNsYxO//6v/3f7P/+d/sh3+xl3v3y7t/+a/d3Tftb/7G/uuTF/GdBRwvJEb8AhHxUj2k/n0R -2VneHW8zP6mhg732uV15etJzxGed9L/35g9n0ZX/J/u2ePHma405vf7w17uf6Ue//+UvfvfzF3dW -YL5///av375/N31Vvvri7j8bFP+8dBrMsl9/cfc/W+x095/se98w+5//8asv//T6+4ff/vX7h/ar -/2z/+KP5ldcbayqWdbQey5r4aV69ybDGfZ3sqoya9AP7Va+hocnq1rz2Xm1WrxzniqGK1B/+owPN -h6C7378+F7d6Km7/A72WP37ua1luei3nElvuXvgPLgOEpT3CqdgrsbZo7QZrzDpvofrJrtZaPv8A -GP25KWxdraeeDO7qrHbdOjVrHOtqXGmkWu/8SIuOVNQk9v5i7b6HqatT+4lC7CnMVia6brLewR5U -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 -NzbTZy/+TD63x34PS/EO3A/WD/aPnhxsF5qGTv7De/tu4JlVnV7+A7/nJAf7ViGnt5uH/MeXY9ey -ZY1v8cY2vn7BPd37seWGzr5umPebZ1z4gfu+tnenxQYu/cndl3eNoXC7s+iq4f3J5s6uFPHm/k4y -Nq4aDce/9EQGne7N+V/yn7P+pTCn9uK9jRdzZxX/1uy8VozdK7cevtj8e/z0eO9o1/Uo6Uc2fe6+ -IP0OANn7m/rbKrma+W5eHG85jj3KPppWaT7fDv7xYHfH+CRu7RxsjWUCw7V/1nyBfwfPkrH/i7KJ -giDVy2yGoF3sG/cP8g56cXffd8Cy32bMrtdfLv4m117Hfe7tYp+rZm8+dLqddP4mmLiRd1o1WxO0 -mZJ45oOGyfInrdkKreXzsmt/Hmyt+sLY3NvbDF9z/hj4BdckTYIPq/K6SXHxm9W/kN5AjWtPv+8e -brmX8T2s+zqZ68f2MDzQiSZj90VMx1JQk0KH7Xsw+UweHWw93P9ysCilW6ugyzLa2j36sSmgGNTm -rAUoJj/8+uP76paMnw++fFnd8hyd9NmlyWUv41QntwowvPjxdOy6HT9Wn5YX1OF01X23AqP7buCf -Xdu4lelqmMq62j8PVw8Otw/C7qeYYPvHqiv6MaBfpUmp++Do23hSmZ0bbLm3fJRPHJbe1s5x+W25 -BNNk7uMfMvVxcDjjYtN0Wq1VkmRifA5I56rEdHW8L5P3OzNSfTne3654O3GapDVK39CMqul/Z3N/ -/+Co/JNy7+LHwY7dMpbOa6uiyCfZs2ZYpQjGPCW3kX4S2z/+le79vHPg2oCdxfVn95/f6o4WP6w8 -fPFssS3W0auda51Wq/fhYvXN+csefNndq2hJ3O3J9KSfncz6Quse5WN0Y7+qRtaXeLxMV6AL60pf -9edTlmP8+ceT7GAPXdG/bbqmMO15unZrsOdazb2K9kLaGD+M2NqcTGs3XUdpjZrxTfrfcm1qsv2m -tImY7KweuTFHgU14B5LoYPJ19VvFdfYmhe+7rH3fGU93v+5vzmjat137sfqPfMDSHprF4hL5LX2u -oL8c1SqT6dFe8tEcHu6kNbEYW1D1hbuOcuz7tJxMSrqBrV3p92q/0UlhlWK1E5X+ws74i+BMoWBq -dIx52ZS/Wffd720erlZdNP0C/q7+AtJKdHiQTu5YfUSGPlsHx/uFLrXkS/hWGObO+hSOqvobSRa0 -GUYa+UR24o3j/harOk33qvfHXze5Udx+HbXfWfEhql9Z8jradkMlKaeyujOr4mbA74BxewYw+nRh -S19Wgw8K8/fRYFD95gq1wCQylzB5L7WrevIC9yvr65f9I0csW9MK6JG2vC3joM2j2pU6qHf2RyFT -mq67KbSGdZr0+APOy9/s5P2L395PH8qCBUlzmARS1B5/VBelf+t+ki654N3jyfa3xZeT8e6WG9i5 -17e7v+uahxlP6S6S5zPsdcq+m6BdmFGVDyuo0L1eebmCrJWQIF/Kzl51Jxe/okllBQ4r+4Rr1Egc -gO3Myp40CoNOWX9/MJE5l81qos7Hp9N4VDazmS5Wd4mCr6zuxRFYNLI7vZAC6lT4wx15PXvVn6l/ -364N25/RKuzsHU6+HGTLFKVvRwb7u274OyPZVOYHq0Fsun24t/2v6jSOAQ6/HUz+bY3Db6VJCyNx -zBLdkWWVO8myyvPiskq4qUAm5GU3wS2T17PZJLWYU2/Ox/9qxZRPeCcyl2PfiprwKX+0wm0nc0Pq -vovzWnGKiomt4u1F+ZT0xu4/x3sb48mX8fYREprThbf29l6MBSvzba6YK8kjRsNoQrnROMpQ5rfS -ny2syb8X/0XmX269uPPw4TC6O5baI1e9fCP6Y3D55uuttdba5adXL9/8dtSVv3V6P/92rZv94Lfs -b/4HP3Vvvjy6fffL6P73B2efX9+8+6X17kb2087l68/735Yudh9cX7q6duH5wvLS5Rvff166+Ovv -o6Ur33bdj/74srp0+fjai6UrT9/eXbraetpprV1/t+Kzj5buXPytN+1Mn7qbu/u9d/PZHze6t4fd -Yf/3/o/ff776x/rB4E23tZP/tPXg8/jOwvJkcuP61q0rh78++uXxaHpj+ODnN6vrB7/3Xt+bfPi9 -dff39Xcv16/fur7dvnRrsN9aezZ+dfnGu4+d1qON53daD/6I1jp/LD141r5y4dvr0htZWK5xK5PJ -zaXj2388PX5660F/+9m18/u7l+5HR09/cffw+sUva0et9Wsfn909GP3x4uot97uPfrR2zr+7m2V6 -6N/L/b0bowfn/vTXdre8dS97B+d9yhuHF38+9L+d5Lz5/Hbytz8e3fS/tro2jf5yf3txiCS+xKbD -9f2XnZXRu3PuBtr7kvez/A1OPky/Dle7T4bHl2/cXzqfl4S77PB+981PZ7/420xuPr65J9PJ5Kfp -75OPP208a631Xqy4Z5G7zbN1v9v+0Hv77ckVM9OPX28/KM203/lzbcnKVN7+T9Otyae1y298tsj0 -19sXz7463HtiZTo9+6l/uyzTBzd+G+y/9pkuLONZexffXv5p+9lTM9Mz6596pZm21m8//sXKdGHZ -Zds/e25/cvG6XcC9d59a619+fWFnen/p5/PtR59fmpnef9B56TOVb4wF3Pn9xss/fKbBJxe/1TeT -D8c3nkimF/lOV9513++1L7pMewd5pi6X+K0+aaUFvHHhgso0in58PizL9PPk47f9lyWZ/rLZH6yf -a/v3Usw2zvTTzT+el2X64Fx35acPdqY/L32cnv02fq4ydbnE2f525+KNv879eGJlevmnvWs3SjLt -nz371/HvAzvT3rv3rfXHP2+4XKxnPbP+Y7Tc+/Xub1amrfWD3UelmZ5/+mx8X2UquSTZjlv3fzv7 -u13Av75pLX9fe/3CZTo41JXmwn05y8dn+u7qiso0+uv7i+dJO9a+9+H7evCsv99oPXk46kqml/Ck -D77/NejdvtmzMm09+fFlrDL1uSTZDp/e+7RVlumn1rO9Zy/sTB9f+P3xqiNiM9MXT7sDVyvLnvXJ -/d7jXkmm73utlw9fXCzJ9PjoxZP77wd5pgvLhWxft452SzN9Of517VtZpg9brz9duGFn+mR0YWH5 -9crSrzfNZ31z9fX50kzf3Fr56WxZprut369c/5Rn6p6lkO2vD8d/Lm1fWjEz/bh15mlppt+v7V14 -VJLph5vuS/706VbPftZnl88cXnJ10sz0j+7bldJMz7z7dGktz1R6sWIDsTSZrG98l0wvo9I86/xy -+czw522X6bW/dJt03N5/nWT6fXRJZbqw/NfnwY+Jz7Zz4cbKg7Cn2bh87eDonmR6hTX15drys92n -z12mv0z1k9779bCVZHp087J7L2FTuNw69zmuNJ2PR9ceha3Sb617916tS6arKtPJ8R/LSxfGK4N3 -LtP1Y5WptPxrV9/E2d5sP76qMj3z8tHL5z7T7s1XT54UM+0c7S917hxtS6YtPOnr7oc/3979+ZLL -9NHSwrIu4Mnk3uW0V934oX661Bk9/FL+0+72w6vWT5Ne7PKN6wd7pb/tmt7zk9Kftu79vHMl/ekL -dMtPfnnwPmth7r9GA//k+cvPFT/d+WO7/KdPl/a+5j9FibWe9s6tlv/2s+Pvf5b/9MXr0cj6aVJi -rRff7t0u/+2Xzw+elf50cnSlk3Zq99+h9r5+NfgrL7EPuqK1Xn+9c1z+0zfnNs5V/LT/YSX/KUvs -zYNvd8t/+/fo/eXyn378vvLM+mlaYp9Wem/Lf/vT10/j0p+67v3GjfKf9l5f3awosfb11S+vyn96 -e9Trlf/01xvdg4oSaz/769pPpb997fzhwefSny6dv3wnSn/6eYISWzr39Mb39Odbuu1b6rTu/ij9 -6eWfO0/uVvz0p+freYnFPz8sjg17VzwkPi8Zmj3drxyaPbj+0g3t7txee/RmYfnu2fGjF3dvXn7x -cnh16fyx+9v9DTdUvHBn/e2H9R3XG5y563/RXeLsBWNgfnZt9/rWRfcJnLnneoObz4N2c3Kmc+H6 -xtWkxKZnX7/bKDzrjbPdC34gGQ93rt15/y5vrNeeDvcvuNH522M/2HGF8eVnK1PXG1xrq34/yTZh -1NalpyWZusHO4PnFjyWZvvtgZbqwHD/rmfXDrvWsyXDn7O+vSjO98HBr9Wua6f29YqY/L130mWb9 -fu/FuWIBb0SdQqY758+fzTOdXhiczz/TbpBp9O3iys33eypTofGkgD8WnzXM9Mz65mpppn7sUJJp -/6yMHP7I+/3wWXvvPldkev9cvzxTGTmoTD2NJ5+SEMlmWabPyzMd/vr4dXmmwiNBC+OyPSc/v5L9 -7WoCW5eur9VK173TstLJswQpW39cunarxhU9qOWthUxu9eLfYMW9gNmeG6P7b+90LtxsPZSC6eqp -uBtX7l0p/HHn6qU72VDftUndc7/Jvz3Pa5Mr1MjVl8u3D6a/xnfh/nZXhv/3fM6qaXLZv9pw/3le -xrvHl9IMYoBOMijczcb5wzTJi1+SsVje7LUeRcvL+R83Dld2Xia87a+TV1yX2N3oL3/FSYL5Rn/D -t9e+je8uyx+Ok7PSuWRN1bkneH+3vXnuwoOsAIslf+PGveXkjytPD0pvScb78U2tTytv6mz7yqv2 -FfnjfXG+JrmvZ4Uiv7ly77Eq8qTEioXu/9h8ezcfk1vP1z330+Mns55P/pCX42dISt/ghTP5Gyx9 -f1I/3xSHYXw+P6e08aPGG6zx/lrjP8evre9zYbmssKouNv3+Zq6PPW5hrM/9oH3v4/R+ky8r/a5+ -Wl1YPp2S92hUWu4Ly/OV/Jc7q+/nbxmkfzEan3sfblwKm557uunJnr5G05OPxKW83w== - - - XZkEBZjdctj0jDdWz8dQxrK7JyPxh+puwlr5+fiCa9ufXsnKzlypcIk/3et8PL792G62S2plPJvH -D+3d1TPNH+3mtd+eJv1LRVH/cfG8r0MlN3LlMKth5U/l6/7j88nbNz73ex/uXm38QD6Xd4fFLz2r -hkELc17+eF76mlpf2mfelRbLwnLdgqnqpW+vJB/N9adr6YPHE0L2l3zYuf376NEJL6Zr3eaZ+5d9 -rUtnFN3f15vUu+LdbHWO1ZvMmD99l2u7Z69d8X8kraBfazA+i80zn8/Mfp3+j2w+OZ7Gxtexjlaw -eLFfWuOjV2u4r63uxZJ+f/PM007pB7v27eDydVJYxSuJOTFrCGMeuz+Lwmo3PTv325du/7muuiU/ -z1+PGQvv9/vwqLpbUqWYrSXx/bqb/3R8KvR0X3IJW2oW1qVix1t+S+MzZbfkWv7sfurclIV06S09 -e57VWf3+MroI3uAspKv9/kaF/qXWG6wqrNdXqz+GhbkutjrXl5VfKlxHTi62dsKLGZdqnWaJtU+z -xDqnWWLdJiWWdMvJh3YlafmDgevXB62dC5v3GtNx5/bbnUK7Ebdj5UMJux4c3VyqjddltfLrg7CP -P3mtPLp5btY3hoGyX801S+douXqYtVCjdM7PVTpW2bhnGW9+flVzsGCCoXuWzzPq8UKdW5lniGfe -yMKy3MqMqlvnRhRbhjeSseWsMplRU/XIcPPM0f28gypQ3wM/dT3PmPyymuIudHk3V+5OXAV/f6dI -Sp2P069H82RQNqnx50NraJ0xjNV8lFWQj9O/Svt485aS1tK6qVNqABwpLNca79dpAD4erZyfb7xf -Xujdm69fPq3fYqe3xP7l4/TbjJqsv9oSkn/oFwRTtmz6UbW+XPjrbXXLsBAOfErG5O7rP5pzWjF4 -f8VRkhRW79QKq1jBZ/WVKCxVxduPpnkV93fTPTc6PFbjqkILM8+83o9HYRUvmSFJZxLSOaXwQ+ue -u7ZyrnLQWHdQ8cjze40ZkhpTsj8ehfyuH21BP1zJo/VWmkxv+NnRH490tzz/vI/7Do7DIWXhgRaW -rbdlP9Dor27pMLp63qfwjT3yS8MN531csXy5tvJ75VyfngMJRvRh6VTM17ApkFyCuYYQpte4BnT4 -OETpGRl4lC6pLzdfXT7bsOwOH+fdYPxeeD91O8Jrv12o8UUs1OkI3aNda15fDh+HfeCMVqnkRpbb -pU+1sFz9pWe3/Djv+eZ/oPxLdt+O6vRO0ABc+20p7O26ZTw2q2D6NV63YtkCj4WlE7KsXTq1WFaW -quLdts+Lr9v96/z1znpwaYIvqxHfCea8JTahV93yxb1dPHNV3d/Jxa7VKMB4vtXCjkKbLBe7ftSw -XXn7wi9SzG6Tq+fi5a3N3eVlraW6TjiQPMGSiVwlq38p9Z30OlUrFflVFpZnXYc93/z9XrLCq1cI -57pYOC0uy7wrijdlVuHP8Wp1o1F3Ck0u1bIulYxe5xp3ycUuVSz6lKzxlUCE7AStVZRF/DRbtLRW -SufBFs3926m0aPJept8br+LdwZaKoN1IZq5qErxc7IkenNRo0cz+RS62sdS0RXuVt2hN6r57azVb -tOq6L9dp3qLJzuHmTOHv5jTWXv11whYtWHr5LV/FsViguAc+fmVVk2TB0qE943ZYrCp/XLxszY2/ -fX1aC7D33x3mw+gyGp9JZvI6zZbRamQXlmc0s+5i5culdRvZtE12FzvpoLFQme99+GAOe+K117rV -+TU2NdVrFPQ39roJ2gdXKW6AiAn2ZNepP6AunR3116nYejQLyYNLebo4tblqeT72hbI7/ZT43rVj -a81HfHdcQ/K2ugMr7wuDfbDJxervp6icVpRLbZ0N52BP8qG9sfh+/l7MvcvGU1rJ2z+F3tDdTWVf -WLcXk+vU43vrKtlI3F/n5L2hfmHV88nz94ZXObn19q3qC4urovP2hp8nFZPKxa1TVwo7h0uf/61M -1D0oeSXFoswrZPnq2x2X7VY1UdYmXXepb2frtPx1are72PfqOXTsta4osTpVvGpwmX/Jnye16nnV -6qlUhVVr6jrfDTVzO134eVVVUlSu+L1UdkxbU3ZMW9O5uiXpK0s7pjur5VORdXauheP9rak1DWhV -r9mfyrtT3G3rLqZ2X1UOnouR9fbF2o+fnM4Myda01nxyjhjchZi9yTVr823ZmngJYsh+gnLSD76I -4i3Zqwn+pmp+EVV7VZNKEXPyk4E9sgji5QZf3n8ZpIeo3H5yb/Lp+uc8hq46gk7asdOIoauOoPNn -d5xCDJ2VaR5Bl832NIyhq46gSyNSm8bQZf2LGUGXz5A0i6GrjqDLowWbxdBVR9CVRAvOHUN3uTKC -Lo8WbBZDF75THceWntzSNIauOoKuEGHXKIbOAMw7leuVJ4qhq46g88xfuSG5PBLo+uHsQW+4B748 -Eujd1fsl44D6t+Try53qYf3ZoIEvDw+clu5yyfb11YwV0DO9VeU0Y6b3ru7tT/zqXpyZsU+pbjkd -hB29sc/s6cFCzWCwF/vzlVN4qcL45S4mrU7+fGpzXxj/kj5hvef7UB0mWril6sjH8wczN/fVuqU6 -M1cV5VQzZK5yn1LND/beh4elITLFncMzAqhmrYHN3AqSEey7q0uNp43TR3unp77MXWozHm3+rSDY -CSnBbpdPITAxqHonnOmVYLc5lm3K4l7fXTmsYP56Cw5SLDrOotZApOxiM0a5C8vVFwu3YF0iRGyu -zxqT12yT161h71yD3nyMvLk+V5BpRYjpVucvVY/DNnm+sb0rrIq17OI0SXGnStXEYY/z99+Hk1Kc -mHPFyj3V2/Ilv7pRYFkc3yzsqh/HZ+wwDsf79ePAZsS/yIe7kt9S6SrPzv1wr2PQTc4XWjg6W7EH -Pn959WKjwhlMYw987eBCPYNZ8/3ZcXyzYmLm+Bj8KQhBv9/oYlUhMvEe+PoXm7Grfr4Sq4yZmfO+ -rq38fmolVnkUwuwSUxO/Rzf+UludOrfffpqBjTJzVQOwj24c163sZfOWM+Plqi8hF5hxkoj0lRtv -2/6PGbX79tu/ymt39VKHXhV9UH+wZ6PYA6t50LPWsy4x82iUfIBQoKcgZmR2qNzMN3RjWhknHo55 -7GohIW6z6r5ZGCoqrfYgbr4YueK+vrCDKyMXeaDqI0/U4ErNjYccaax2/vlwVlRsOMws/ZIfzjm9 -Uy88zowYCoerpRFR986eGo99PFqeEdRaY3Ez5rGHc07vlIfZXb5g3VJhB1HNcrp7VGN6R30C5szV -w3lnZKqC2WRGJhhX1vwueUuzKrPqxSpuar4ZmdLd6Tosbr5yCmdkuudG39f02R2PZu3mqzcj0z13 -7Yy5Y2euXWo/Hp1wRoZ74F3NOX86MzLu0doXGu8g+vGo7oxMPLIoDUObsddx9ozMo2Qf7CmEoc2c -kVmoFZ/XdEZmYVkKJpo/PKekdOzgHP0l1ws1v7ZxpONQb75aLt9Lk514MBuWHc2dPeHHUGhhHjee -1EkTHz5WUzrG3HjNSR33aCt1H02+5LIwtJqb9qrDxwr7LprE1dXZCrwwO66u8blhh49lD/zJ9+vl -BVPdWGfRtTMnQaV0BiesvTFbqq16K8ZK6osmHV1wMlgWEXfa8XB6nr/2nrq54uFmnD92SvFwUveb -Vr7Z8XB1d6g2i4fL9/UVI+IaPZVR/04ULTh3PFzZysjpxsPFsW86Iu604+HUHOyJCa86Hi48JaDQ -d51qPFw4a623Wp4sHo6rvqXxYr80PZVAgo9SjiiLF6u/J/JVjT2RC6oIS/dEvqq1J3Jm3X990Di8 -XhqhyzV3Qs68TumxFvVXeOPrzHOUSfndZBB/8kguuU7FuDnYkrkwuxpKcF35hNjscAJXCcMT2h17 -sRq6f5u7GpqzPXdq7GqoFRFVgvPZidP1q+HrubYm+0pYFWPVoBrmpe0rYTMalz78ilUN1VVmnQbs -rzN/NcToNb5O42ror1I2lq5/1o2/Tq3ocII9Wxh/sfJl15nbyMNZ6Qv+SGBtAZAgryczDpeotfp0 -/8NfpxKR2j5Tvqdj7ojU9plaQaT1IlLbZ3qNhy6utMdm0M18EalvTiki9c2pRKS+OaWI1DenFJH6 -pv4x0LOmtO4sLPMY6GDDwuwdS6oaqmOgk1zcv76q7sBqVsOKUDi/G2q+aJ0ThML5ErML8xRD4XyJ -fatxplWjULia85YNQ+FKx5WnGgpX1u+fbihc8fTsejHaJwmFW6i7L/eOOka4oingifBGj3xntdmJ -8OFDFqIFT7qLtnCxyj2BAcPM2BUo0WuVBxnOwzB3Vk86Pck3+Xm11mlaVVsXJP6wossrfBELdW+q -5hdhdw7pDHxef8UaZ37OyTvwgszp16Wr+799Xrry+tO9pau3up+Wrj4a3RPN+h3527ulK9/+fCl/ -/LJ0eXv10cLy0pW7z+/IHyLkHJ3LXuh5dcvJ34K4s8m0vTwp1tkgQql3pnNtVNyhGoSAnTlfFey2 -erX4vQSZXv658/U3K9PEji1a7fdlwW6/VwW7/VkVYffl1xfBKk8YAiZa7e2yTHeqYrFuvyhkmsVi -ZT6+b+O8UdQhYKKazp5UB7u9K83UFe/1qgi71vqo9WtJpv2z557/6HwqC3abEWE37VnPmgS7Xfnj -dWmm4pL9VhZLuFoZYbf+5EJpppPpqxtLpZku/fLx6svgnY5HyTy//3v+Ii7e+X3nh0pppvt0/GN/ -Zjpv+/107+2zmSmjb8l3l3SdEnT04ZYC0XSWZuWH6k7lWdaPyy1JNbbchgRrrBC9v6vPVDlpHI2M -wNZLpp2Kaxa1pF+zPF0lt6T3XN3VEHziULH1Wac0NzLJWbNLC6dhkqs5C3d39h7qmuX00yVrwFW+ -r+8EErmyWbhLpePK2RK5+s93ddY58LWfb6ZroP5N1d5Zqm5JzSrM9sfVv6XCXJBtljS3u9aMppsx -03tK0XQ15mFOIZrOiqUL98CfRjSd9WgLy6cdTccbuXu53nrlPNF0J458bLyM7U9uOdVoOqtY5FlO -N5pu1gkhpxNNV6dHbh5Nl99SPm6unLU+UTSdNc8S74M9zWg6K5bO2tvTLJrOAsMgNuFUoumsWDrz -XIVG0XRW05q3MKcVTWfdku9fTjWaroKUTjGazoJTPw9zqtF01vubbZiaN5rOutT8K7yzoumsS1XE -V54wmq6kxE45mu7USmwmGFaX2GlE01nLKMY58A2j6axYOsOR2jCazoqls8/sahJNF9btOJZuRnzl -CaLprAuU708+aTRdUCKf19R88mlF01lIU1r3TxxNFxRGsnBa5xSakxrn8sIwTjhsGE1n7+6Qm4pO -UA3DAWDn43RrNRwAPswHgFmtPGkg3f5Sjc9wYea2/ZV7Myt7kS7M2RzfJp+qr27WPMzp+OqsWKyS -VVF3Uxcal1MckSlYUbMdm/EJfKvhe615U7WbAt6StsvVrs6zbikggNmnnFWUU+3KXA== - - - fVan3NST3oe5m8xwPHT9iPYfCaqatf+ozsxcUXN34t1Qc2nuki+5DMkfzb8fsbSFmSW6a6y5K/gr -ZwXSNTgTPN05XCm6a6y5Y2s5K8LwJJo7NaNoi+5QMPNq7iq/5Frbn+po7k56LtxjHRt/knOuHJnO -mGqsO6Du3nw1uFAnVrQGdD+u3gBcL5bHPdrFJrOo8SrP4xPuPVSBdKVBRdm4sqag7mR7f4t9pcTQ -1YmAnRlfiE2HMxypZQAt8r454olomAr5V9rIC4gn2vw842NQHV35fssXc9W70hiqU9wN5S52eruh -Nn6csLfTkY8zNjbXi3y8qIN7MatQ8zqX5robO/qp5U1Hp/FUV2rt7Jp9nTpuslo7u+RiMwLuKneu -HYZ74f64eJE939uXp3gqoLvYqRBecl9BY1bPNFFalDvnO+WenDLfa2UoAw9jlki8a9XLe3okXrqH -+vVB44iS2DIz46nrR2xOv5uNx8kMhtPvtTxdM+p+fvJxg9CBVzlMNHIMvaobyFC9Wu2v0/iUgLd+ -VqG5rdrfTbla6xf5xuYIZZD9FK3S8fcJVngvGDsrJBLvTnUB1p2BDxx3DQK7Cs9s74ioXQ1rGu5q -easbG+7yyEfbcTdn5GMJ089vyjtJPJFlymteDbXhrokpr77hrnpu3F/sFAx3ue/Vdtw1DqzNo5+e -1zgeY2ZgVxXmzHXuqOjkToQ51rmjcrHmgbUf/sr3ODWKfLxaI7B21jlX/jqNZ7P8Vdzbb97+yt1U -bnRaqH2duvHt1pRWOKfkdXkN49uLC9ESFiWVUOXi/vVUqqF7qvdH5e3YjDAmFOW1lTobvdKz02dH -hDXZvIgSO62jzd2l8mH7SeYtwxLrn9a40r3L13UGqaVBTH5+zNsTa4Ux1bwl/UUUZxVOEON6++27 -qwoMsTXDOrWpXozr1rSu7rFOj/yuse6xyGPuMb9XD4vq6x71no2yGcVaMa633x7VmIuuxTDvGuse -s32wEurZPMZVfHRlXZ6Oep4d4+puaR7doy8qc59SjuyyvfbJ1MgvQT8JU1q69Pintg/Rk7C+F0tX -Pu+8XLr08lZf/rbhY/sWlpeuPv4ctdbefh8kXc/1g+/F20tnl8LYqZXyeLjhxlqrWJ6fJ2f8DtXM -w3bu4LDY1QUetpWtc8+/l2nuKo1zn66G82MqDO92/1lJpv2z5/a7P30uC8L7pDKN2+TMw7YUlWd6 -//7kXZapDsI7+1f/+ENZaNrHheWK2L/ffil6BMPQtMs/7b0qC8KLvl365c2lwzxT9yzBs5bH/rkC -/toqzbS1/vFBWbihRKUtf197vVUW+7dZken91rXSTCfTr4/O5pnG3rdiGN6Hs/1XZQV8pepJH55X -71Sq5lWffRyNfjUNwjzeKU1ZTPfs7LhOujPPri+F8/wlKSfHn78vF7pJeWZAZ1px3e9eWVFdpx8+ -lbqfnj03TnSokhBsnP+hVoPyudPmFrPD6i0exgxJefzPL7UtX9YtZSeDbZyfucu9XrSVNQI5mZll -4/yM7aVqOlfeflmgzqVbG3W2oM18dRfOnFpUWuWO7fmi0i7PVU75pejkche7ckrPl+3Sqrl7sOL5 -VmvcUsHBXXVT5QL4WreUrCZUzxPPWU7cpZWvWD17ZZsmVHP17sq+bq5mzzHXdKa8u1IeGF17Dvbe -qUUjfbpnDX+NEw5rDIDfXT0z16OZqzz3TuEk2ndXDvN5rRPPj0l8Wp1x84xzre+dxsyyxAC6AUKl -jbFewdSOF5s5apGLBUFF8x3yk80oZlGzclpcyCHrjX0HaQuzeeZt6aE8c42R163JYOs04Bqr3hJw -V75nDhMB6VnQJbMq66WH4dedsstqZfIaL3MDwc79macu1OIReZbvw3JP77yBZO+PLB4pOlPqhwLO -OnphjhirucQiFbd0dPNR6b7x2TQe3lLQ3utAwIU5iPL76NwJQzlBSu7m96tFPHO8P6zvN7rYDK/x -2cL5Y7MvNsuiU3opHY98H71Ko4ecHcszx8VmrFzOV2KzfDpzldjYbCXMcOFyOgxiAJP+5YRRgHVj -AO1zSEoucWKjXkZ9J4oCrBsDeNL55PmMeiZb1o4CrBsDOMNiNiMKsO4bMmNFa0cBlnQEiAGsjLKZ -GQU4+7MvOdV8rijAujGANcfIJatv1TGAyd1k+/qeK37KSuKUpHxlRrbTlfLVnYVrJuVbWK5Dc02l -fIGR7T8m5ateSTwtKZ8/467UgHdaUj697+I/I+WzbFmnL+Uz/JUNpHzqltTJLSE9x/czIzRYef3S -CKxTORuq1Os3ZyTXKZ0NFVr9mp0NlXv9muy2re/1q4y6q+myme31a3w2VC2v38yzoU7F61dt9Vso -vOkmXr/qLYvZjruGXr9qYA92EDXw+mX5mVY/7Xo+qdevej+Tj385Ba9ftdWv4bxl5vWrfqpZBpC6 -Xr/qB/K5nILXr9rqVzu+cobXr3rHmY5JbHJwdrnVrxAx1MjrV4woodXvhFFp8PpVzy+Un0A1n9ev -kqPu+Ld/Cl6/6rWU/1vela4lkjTrK+Ae3FAQKGpfUBHZxHbf9wUBlRZBAad7vh/n2s8bmVVFsYgF -2J8z59jPMFAVlZURGXtmZI5yHt/kNe+TnuvnaWWAyRvvPL5Rz9UcVDMyzrl+Q7qEEPZzbenvXL/h -p/oNqXz0XyX2s/LJCceBEarEhp3rN/xUv0HjMs65fsMWXNMpzP11D+Oc6zdI4jtLTyatSvM3MeyZ -35/oXL/hE10ezT/RuX4dag/y38c5j89v3cOQzNWY5/oNbOULzrGqdJ3rN7yVYdUclRHO9fOx2vYL -zvXrEsO+U/1sf+yLxPCj3ZVHrkoba33GwIqhMc71+7hffTvpjSOG9rl+k3njfs/1+7Tq+UvO9Rt+ -qt+I5/GNGUt3ebD95/qNdhRfb1M9O+pMfq5fbxVu96l+AysfxzjXb3w9Nsq5fsPdHL676eTn+rn9 -Gniq32i7Nn18rt9oda/jnus3/FS/Sc7j8x9aDz+Pb/JieH6qX+DDuYjRap6Gn+rna+90H+f6DT/V -b1AGfpxz/Yaf6jfQio1xrt/wU/0+2efKf83T0FP9PPvCTXSun8+85YTn+n04kv07T45R8+Sc6/cl -qwc/PddvuGNIeuwrzvUb7hh65vgmOtevj5Rdp/r1zYyMea7f8Bmbnurasc/1G74euKe6duxz/QZQ -zBhCsTHP9RtuzQMfl8QmRznXb3iXOlWck53r12scurcgdt5y3Xr8MK2Y4oL72SamYbF/YcPZJUvs -flwxNHzhfXfZYr4npZXx5rDugrWec97JaXPXEHPFbBMh7CFMUttrdOjZE4lS7VvBjE3Pv+dO06fT -uHb42qmxqmSazRU5nTy+Pg9NB+uGOr2QEPPTi43D/Wk5cbgdWaol0pHkSjMVOd58WhBzO6+KmLdy -KTGf3syJ69H3A3HLSF+KW5cXJXF7sSWIh0sLmnh4u0q+5fHP+7J4slB/Ek+2pDfx5PXHnHi6ep8V -r3efN8XrtnAm3m7E6uLd4kFQvFu+CTebO7GFZmvtWm+2GvMbzfdE+7I1+5p+EJQt892u7Hxs7CX1 -hent43RQNgLBUHGhsjd7cnGUnKs34/l5Wb3dWbg7TJgzRz9KschRfm8hub9c0SNuKeBCoXKVixlb -8z8xJJE8lb3FppvV63hwt7p9wNz9gZleb31puFqzpiM1db/rCEg6T1INn0eWl2OJgcRixAC68+Ld -dmi/B9NAsAfXyFJJxdPialLMH53mxfXZxl6zZZ6WWSWpWxx6FFmJx9hZlDO8JjGX+xlvtq4aS3Rt -tm9nYy4lHe8qufLq8Z25O2HnW7cs75xMt5/IR+M6/05nY57x4zOj22eF6ZgZvQ0E7XJbOZ+ZjoZT -WSq83fRAxtaUAnWvo1XnuTdTaadMNlZrL42t1trm6elNJBsNvudDhY0NxJ8vV/nb8OUmJPpc4z7M -HGkYBNx3szEefCa1qxb9jNtKWCssuN9CdIJfjXJBdKhOYZFX/cJeR+ln1P6ZEQT6KbjxvlaIR3In -ZwXqz5WyevKazlSLcUmMJ9WQ29FrsRwKJZwbicXODSl3k1h2bqSinRswb1tJdoN4LJkXPLeu329S -zjNbYueG9/XriTBhGva+eT0V6dDO++b1fCwQ7NxiHjWubsW5ZYiv74q0G1xEmUsENeC8w2uipfub -YDG1tDXdxLX9CCt6lO5fJVoGth9zx29OXkiGyA/eF1iFHYBKekamCyKtMWlKpcIu+2k3W7q4lFgo -KMbP85H49uGzgrtHfAzksJ4oOy89EvhbxPCcKcq1w2AuaoVvAsHUkngy54033CMbOR/0GXX3XNij -+KAWB7Xn5MY/bVH0tCjFZ1vLkeOlpp48VnfWjOtyiNkAOXxxOJ0rPb9ZwPlSdsfiLhBU5paX7zhr -h9/Lzw7qp9EO98vZwgrx5anASZS92JC4AGRLu5r9rXYs29/eL2+YkZFz4eKtPTMi5+RHGtpTjTdx -e5GiFi8EOnKpId/W4NGwp2/fD+y278JnT05vLmRPb+6Wyj/dG6orHvdduNydTmdIXywpq0ftg3RN -eFxc2ys9bGU3N6YPO3bKLdXNuNHrvCc0cY2oEnzfqLCXMqm8jjM0lHn5WGW9VeaXLjX7W7ZYdbp3 -rdhwJy9rvb3ZzRxd57O1mdLawdHVQu4+ljolzbdMe6zHnVmeePtVz+fkbbPHxnf75fNdZvnkbbhZ -TurBzHv2Il85CwSzs+X3XOb6x2wh17xcvtVq96uNXPP6ZDNvzR1X1kr7d1sw4G8nqXg7vUWn2G5B -2maynHZQAK6C+/FidxjkZ3SAPfvBdETEzfWhZ0RvgXg65NGWroLDTwOaQZNCbHdIfKzA3GjGInsV -Wjzk1KbI6c1mZy0Vs8+upUrFC3hFJ00u3ZU9Y5l7Ow/HicuBrvbDTerKljD3g25I0YUn2uZ6M+yx -AVGrzXDRNiNul6/RqQ30MZmJUR9D5CQ+p2sz9bwUvTEqDh9swsMzTgqZu+e1co8apRHapoE3QBuT -834gKK0fh0xwzOMu6UOumqT1R5WpRy6GUmEuFc/+WG6ITIMydYzY4HHNaftYpmtEu0h9kP/XWdMb -rHV8RlEMCUuuB7vTldpk51a39enwzoVFh1Kzk6nTZGx3+M/w8k6C7OzGdCy+cDAd2Z+Zp3Or5fwh -Gd4kfZS5sYbPFZ2OTt8t0EYYJ33GeG8lAj/j3N75gTxYd8MMJkOpXPEu95yqv8XvUicLB+ewL0dL -P0PZjUv9x9rhe2gmtbyaF1yef2PNMu6PPwUb0e4gzfdLA8FMNT/fxBvP3lKNQvM8fxstzK7tPi5v -rR1l1bnsw37rgPm3renrhaxYDtaz8HQv4Atp23XSlqs+3kxrR/vevZ99mH+dTSiZ7D1QL92OgG69 -ifYO1f4oyd54RF7gWzzYmzls10n4Yh6rIray9fTuXgrqStPuRnozw+XK6g0a/b5ZLg== - - - 3S6mlq6ebrPCwV0z87BSvx9EclaNPpjoylpzOjvmcI/NY8nj28VcT4Bra8v1zQjTlvbmGPnbCIuN -mCQqaVMxA0F9NrF1kErkKtX0cyi0nyvsVaXU21p8LbvxUm3BZEhpx+fPVmEIDtoklQuCWlh7GTTS -g6jN9NjY9O6j9mpin8cByff56b7Zar+oj4l4IOig/icZDbZyOOqrS8l5r4EuvsV6e/O5GoW2ZBcW -F2aXp6Nvr1ukSLdo46BN3Ig1SXkeUmADXbuYjiLmid1QUAjVWt+/omdFAl7l4aE31vLSobNzy5cK -ew/zuftb+lYzJ5XUyebKRv62fTSdu9N3n3wwXyDIhX3MgfeJLnhsMMLjs9wAdAPBDxEmRtvrSke0 -+spYfL4Z2nKEQYYyO5nL9quZzxAfxmPjqpl+pR7oj+M/Veuj23CHx0ax4j4MSg/dEVf+CaelB90h -PPaF9hOx2OcIf8po6YXhbw5MPsgD5WpiHhvDW+MaZpBe4TFdZ4ZsUEyXSQuijEjtfGct+iqmELjt -5LOrEe3HWrSRWECw175eK+h3sC+ph6PXJYR497sI9m7msu/Fvd1UvNmeS9/tyId5a1ZLIvY7KLOg -cOn9MRlNC2rzRF5YXVzvxAtshY2zhVd8jiReYLkXFh4yu39nRpL6bCsXqV48rR2dnDaXKlbzGfhP -PxlvhYOtXLG4IOQuyjNPjPKeMQg2rjPkH8hy+lza6MLZ+2ZWydX3bufNc3PwJN6uaAxA/szNlT93 -orVj71PmvJSt6f0MYeelp3JWEJZPodGvpdTSj+cDvy9lu891OxGjOo4+vCeWT/7QcfwqUxYI+rXd -jvfkpbdfahOPjUvvr7FiX2dEu+df/oC7xLQc47Ev8B+Gm2+KkkbVsH8yEsfrjT1PaEY5LL/M/kGU -NCLle19/2YOzc+rf11qVQjKRufzhiNnKQaCP6CxHS3PwPQrHO0u3+mgkrve2LRgPPYWP5TTMxOwO -+7m8HGsvw36sU4rwcCsVb69uYvQL2rEAg3NyiAtiPjHfyCays2Vd/CP2ZWyFOyhc7VG3geAfCFf7 -3uxdP+Zf1b9mMunnhfpVVjAWWj4MK3BxTet/1744CE9k2brQDQSHIwwN8zo3cTIGHuxogwy1HqvH -zNqQvMDoPLa0OZe9yq6vrzxDryhvY7pQPedY/SFvhs1Y+RevMbktEJxYsH1wWyA4kniNKVx2Bn5y -bTIRj42kTT4UrvH02KjCRTVWI4jXmMIVCI4eodDqhnPvCqIBqxai2QOZEno705GLUIpyfTU1TysU -ftCFBVqmcE05vB2aOVmi5N7udPjuwbQXOdyWlenYD+t8pNTfCHn+CdIy/d7Fn0jL/Jk8f69bNWKe -30ek+gdiZJ9eNOnkr/MoPwpi+nfU+QNTS4VAcMxU5Egc7zM/Bo/6C/Jj40wnjvDmj/NjE4US/8Uc -bCd8svNjA+I3nh/jq4DGyI/VX5v5o1X1jAUkGH1r5t34wljFkfKFUKY6fRlmAg4N87mIdxbWRZar -+ilfx5M+K7b98rkzLnxl0EKlnXkyris+ddsba4e+vZxeHs0fjqth1vK3z7XF/Fqt+NMHd7NvfHXk -9GrlZPy5pPHQ5Tz2cjbdiB+NZ0v8oBsIDkK4+X63ZYyrxwagy3jsC8Z3OLqkLb9ifIejyzK9XzC+ -w0eXzuL8ivEdPrpj5fpGRtf2xyYe3+Ho9vljY47vcHQp4htvfO0MV312+MK5Tp4/9OLtLVu/7C6E -54Us9oOLXZZGfetZc99XkOYYI7ZHRNfhIMybm8vdaG5M52TEPYsFWjGP6zdfMjc93WTXptuhltvA -XvdZNjxoXFxn7pLjNje6MGCWdjm23XFt5ZB1rnSw5+e7BNNzrt9dmu85/YeaSMc9TUj5s3XJrUgg -mggvxG0hNlbxavKY9q3Za8Sr0USShWbOtZM359p5k3yY3Xqks7RTWT3Z2eyyvoQ1P6vImrYN5tLL -Afnl03TjjC9H7z+rR+Zrsu0Z3ve7FTnSdW5P0RFdSWAdZTfCodXLWld9hBbmY6AYu8/24nj5XfKY -76XS7rYY363M0DyyGu4684dqKlyKEevm+HkzzfT5pcvT7JCmK6k4o67z3niZT3p1SXDDSGDn+iLL -8uO+S4QLLxHQHy8Rns1XPqrvdynFQ4Lg8vSdQwI13iEBOzLKG+/zs+KGE2E18VxnRCBe7OZPe2NP -z0v5kTsYEI8eG0qElbbD/e/THT6YVluX8z74gDiZ7WjGWDsd3nSJULgWZn4eO0QQYh/yAdUDLjhU -TEiDGInx2NAm2O4yw5r4rIHWc9jJkIzZxMPC26LTwBB5YOPyURPSTGSyPiRC0Y5UjtdERoj5QIM1 -wL2+/ia2VGEyNI4T8Q5L2Vm40Zjq4SYljsSVgWCvfoouPIVGl25vA2I93NOATTH/TVjtxck0jBTN -BCMDm/A5GlJ0KxId1EAg2NWEtxd9TbBzwYaPRnF5p9OAclmTTO9KSFoUPhFTSdHHlfiHDbDVtp83 -8ZoRPyXlEJOHt8TmNiTu8M2sF9JOE/rs4mVTefO8aj39Frbh8j+lDlxQ3cneeeG2ZnoZZBCPDcNq -/Tg0htbp4rH1G2Egi/jm8/VHdSCDBIJ+JV5af02MyyAOj1FdwERoFGL5j7WOPx4r6FvSJH3AWwqr -B/In0tZMxiW3iYLaFo6XPX24vwl+onWYfRlGifvHSGSyPrxK0Y5FHms0SnNGzAdHDLHIUim2Ioyp -uOwG9Ex8MosslVY3Vr0/C7sp78/947Wu0S9dXKY9DmZ4Tkh4f8bUJe9PPbHs/bmaWvH+LOST3p/7 -W6teeZHDFwcp7/3S2Zr3Z+0mbUeOmhRinm6nBIpR0YxNl9TsauT2jC2GyFz/sPhyZFac5Kl8vJKv -W+UWL8peXLtai3gnq05DrIDIU2/kqaDcrdTtcqez55h8WyhH2fpyXih33XqKseh1lhcGKavHMwJr -1tl8Ct9ofwL0Rw3zUqrcVaqBn4kI+4lw/OCN9Zb/vG5VWF3rQifqIlfkkSof7YImb0fhIdy5Nxa9 -NzLBe/dG1HvjWHKLLDcF743HlQfPW0TPLdgctx5wJ2xXRG3NuBWiOxEPMFR5yb0R8954TVC1/U68 -U5W2I/KqUegsndehrh5Pv4vxvVSnkvaW7cqAa3lGmEUQsJJm40epExKVLROhyW4TILtxaiXKjuqg -7OjescSeUeasF4Qpe5eKW3MaiSRnl2dZlZwdHp9vxTrVmc5bWAL5IpaNF5fnE4/1heP8ck792beC -iLYN2O/ba0zestCzesZHO8NboX1IPAnt8A9z3zLX86mFylF2o2wfpAoMziResQj8vGWb4drtrFOC -WlRdJIt4S9uy+fx+1+bkKEhogGHvj+N2qe79mSjdb22E8e1SsmsN72/kpLUhY1zui4rzrayyBuxB -fD5gPB1ju33g51nc2UUz/nwp2u0830huf+47pdG4UVY8N26mlVu7unT7LWjj8vyie2aUKzfhh8yT -nt9N18xZs6t+n+caFi4zTnWi88G3uHK2hxDj7bNONXbJZov2jSitS6kQvhUl51uZF/JCwxCkLcTt -n2pff7T2SX4xnFwyTmIz2/m77aDFi1rnb1uG+GM9QlWF17pd1Hgm8hnV7gmOBbvmHeNiLrBaQ0d8 -9ho2Fc+eI05Z/E0LhJkR4tWZvXnO9mJIaDkVmwI/XN4WKegkXu1JOokoBq0EH+YnpX8K0U5xKJ45 -IdEtsE04UrQ7usDYlISddostxD/YF7gg9lT4BtxbVIfaTr71qjCmXKC9Xu0uJ1NRXt7JaZzciIva -/eZqn/aCZ21XZCZ3JW/NO88A8vFdX1l08IPOIuHjJgFoxLi6YlXr0FRxpqnc0tEKsx9o4KDDrjc0 -k+ioK2eNeOt/kgFDFMUpS1SUqfjBe63S3G1WH6v1qWhgKRBf25Ck43q5kW9WKkeV3+1so/T+Uqm3 -pxJT8bXDzMaGqWUrpUa5MhXl09SGJ1XDkbDZ2rv9TNeKB5Y+TmcfrPXnwuzBSjH7IJ4ne9dDLLwe -vdGihwIvewrSEodLXi4aKrSi9POcZ3PYgBTnpvPSeqacFh9up+lE5aYuFVLLPz7aWbG3I4Hgx10J -K4UVu2g1+bzMS7OiT9UnWn8hTEfeE4dUepWdjonbMk9iflSQzYV+oask21514qMku6cge6VxF9HT -d9unmXROPsrCyzA3vaXbeWv2kHLfbs23vRNBSIo8G3T49vmNw9bb9Y41c6XqDqavxpyFqLOHxbld -Ph1vh3ia57ZtCzgE1/kmhVge29bSngptKobWVrj3AIm9pHLujOA8lo87pqHgMe10fNnC060rfz1F -166F3/RaeMTzroXf9Fr4rrrrePdbXjOP7i3J6xWk32hbjh2v84KA0nVedrzOC6I813mhWmzodYq5 -RKd0G+ETGfcdiWVWKY6hn/vethFXUHy9bxd2w8VP2EYiut3g2gA+O7nZ+3F7S4vVDSLbvsSeIAd5 -/5g1q3hciMJt096ZBA4y6ZKjmNcAr6aWRtoSoXertt5NEcizXh55k4Xe9jyLR9LknK90pFZr781V -1q8vzMe1w/e5au6ivEcW4Ejq8K93t5LzG8X1PFTv9hWrRqnjVJ7G7F0s9jOkX0/jDuOfou37a0rH -n0qOcTtlVfAN+qa431TeQG7phXb3ubDbuy3lFSYvF+7+Lxei+03ybmAh37g7RFwo3hvZ5znHrfip -Obhc6D17hgQ+3jXEu2dI56N3z5Brj9FS5sO7RNRrfJyf0tzMtcS+8Y0uZC/k9qNK19QBe5js5+9O -zjPZiBnO53PbJ6bjy7xoYuU+obPoxI0mLj7fFsiZNZLT55FchG965WyP1bVA3VhIzDdk1dmlYjG7 -VtBLuxFLvYqlNhOFLbZzBTS//LZ72tGwjt50FbPj5kzi5FBhpT83ZxInp3fj5I/dnEmcHLYtkC83 -ZxInhza68Ofm9Ds5ncU/3q3T0Ie9TBezDzi8rnvMD7qnWlPeqVatke+dap3JrM24U60nnYksd/G7 -3Ap7m4hWcz1NRDJzq/zIjg+mWrXCzExydX3QXK0z5T6Tzwc7TYiVGS3e08TZ2d6a20Q11OWPrNd4 -soGxQO5qKUb710gw39koy4LaW06yq8SSAruKcbklm3PVclTTbbuL8rML3plHe5uw60EzoWwetGea -ensh0pkGZGkldxow4k4CUlrp+oNJwEh90CQgfEuf04D2OfVjTQI6yVe2kTMnQrZ16BLhykuE98td -lwhn3SSIeEhw2VTuHRKwlJuzQOkpnLko3zhEEGJdRKANdD6ZCV2c85BgNfPW8G5RwKRykikKfh7b -5zPiJC8OJ3QTIR6ZbEbcPirHmd7pnlH22UQ48tGU9KAGWFq0t4nFkZoY0ECkRx7YMqiRJCIcHb0P -PcUi4Y8n532iIQwS686CCz9oxMfsQ0fz23tsTsBU0ocN+F2kEA0Pl+7hss0SY9HFSQ== - - - mqAGJtcw0egHTfjliGgsEPTRRFcfepsQfAxnIKik9kMhu4ni6m53E+MylduAGOksTB6zCckPHUgy -IPsfNCE7TeizHfFqzaxvZrrg4osduLm6srRnw+Vf5B4ei3/CIp9iFf9E69gUG9pEbEI+jwseHhtP -4uN+GCQQHNqEOCkakofHxkRD9tuHDo/19kL5nJQFdWU7E+ENNJOa0tWA7GEpWORxKCFHR+oDLbXr -68WkTCULPRwx2CIPGw3Zj3PTo7Z6LLIs+uDLQX1gkTbC0VSL5ei8YXTyoBoI5o9WExe5JgJNxNA7 -hUzx8cdPmvnR5PTF8aqdZKFsHouxyDEcPenIJys+STtGJk06smmEz9KOEycdMS6fpx0nTjpiXD5P -O06cdOTL0j9JO06cdAwEx0wSjpR0dLad893iWElH2oLi07TjxElHnu35JO0YmTTpGAj6SDtOnHT0 -ysuHacfIpElHkv0P047yltFbBMF1xMp5iAtS7mqNrcRYdKh4+GqD0IZ+tp5SQ+wttqZylcsCy7PR -Ol6yd4UwkyFaUst+4uO8IdgLLlLT1/a6iowQZefOgKmWN+LVs7bAk3fVaCJiZ0eVuaXcj0GHIrG1 -8D3zyXTj2E4EJI2wZzNwrjeTKxGnw5tRZzYheswW/G12snBlr9JPboluJrBHhenuLrWLLn5hvrzF -zgRmmGIOM+/idgYXNjgQZUd/8r1yob0krr06bXdUmLuR/P8kA0swIBuSdJurl70zl4FgEFcOK+33 -VwLQbtOVx2p9q/h3pRmQpvg/Ef/o07CmJNmckjUNPzS6unUfCDHYKSk8tVUPiFNrCFBv42vNdrZa -alcb9WLz76kEXTrb3jreyE4lpvgDt3hgaSqELom3gMatMM2ZMrgdmkBlDw1o5MObaO33S62O27Fi -u92s3r+3Ky270bVms9gHVXqq1srNSp3DyFPxjXq7c5c+2n+/VvjdULXUqMdq1centtD66zE8FT/E -K+qP3fB/FWvvzgOtRrn6iv8S5UapXnypfPAIvxXlPet/5lOS/FmsRUE1BUWQppqWaeo+sK7Wn1ul -4msl8Vel2UI3/WDd/8w3Yx1/arxU4u+tSjPe+FUv1Rrv5Xip0azEqy+PcWIE4bXuhwVcxCq/XxvN -duyhWqv4ZYWPn/1m4piWYMq6Ko3EDTYSf5dfq+Mgz5/7FyP+e0zEf/8TEH9qt18T8fivX7+EX4rQ -aD7GJcuy4qIcl+VYs/wQa/1dbxd/x+qtWR+koeutBB7zQw8P8D+DCK/vzRojQbkUr9QqZEFbcUmQ -4r4xL5f8I06w/wy8S81KsV39q1JqvLw06i1GglHGuzQC1qV/DNaOPRZajfdmqfIArCtCvdKOZ4+y -7s2YKJTbZd+UcB7zT4/OE/8MqngUgSyKYtyfP2Tj8iHsIMQJ+J+Ds6OcOfOju63XYqnSijvXfRPh -kwcGUKLzRHRyjKWhGH+OxOHJ+u3RU7WV4/rPDxZ9j7Dr+I8iBvx39ou+VALvgeNPw4fb30rmllov -/1Wt/FJkBRdyt4OjCaCabjRqvAMZ0mGVcvrvjRcyrpXmlzHXPzVo8VIJw3Rcr9La1E+dl/Jw2G6P -pfztIiqNgJrjX/2q1suNX7GX4u/qS/U/lZEw/riN7/ZQRXF8UjxVKLidgA5OA98dsvonwUO1jdFr -PlbrsftGu914GQH7Ac9+M+LtJiE28vA/vldx+/6+8Xucsfc+/e8Z+F7WHQv1zrPfjHhMmUAD/j0B -6n9/O+rjjPlr8bHSgOdWbY+Fe9fj34y+PCb6racixm9c7J2nvxl5N408MglK7028pB2rUQvjUKGn -ge/2f2RzAt33q1puP02gBOzn/z2KwGO5myO6PP2PfjPasyL78488yW+pUWuMwvaeZ/5VLk7rqfGL -+SetEZD1PvTdcj3CuD42IYaNWqVZrJc+IVEXuj3P/SuluFZ5GFOI+ZP/SqTbjdfxcGYPfrffogqG -KOnGWMZ7LGe99P1u+kjiTBpoLHnuefDfp7CbI6WfOo989+jqY/Dyfxoj5Rx6nvtujP0jfN9olivN -0YOunue+G2FFMEXDHGukS2PFmqV/ANIjaK3G/c9KqT2O2up78puxfv3EyHixfq9X26N4mDb8d0cO -OvsbVYZHjR26nor6xuqfOqHy++/P7ZOXaPxR39TqvOkbcSQMFUkbJbf0L5swGtErqRdf/6q2qve1 -CpGmVq1XWo16bRSF/nET30yJh2KtNQIpyo12e6S5MueB7443/KNYeXmlaf0qW+DnF03vQ/8q3gYa -4MlRRtR94l+Fpy18I+DpPuHfaP2xBRed2baRgqPon+yTu1TZXWPwD+nYl65OsaZC4amzU744ZfEd -/9/FfyLiAPYnSZYsWrJlr5E/+xs3f+DLT1z4NSWJU9tTl9fiVJlWuBzg4zEgSaYlqKapa4alSYo+ -ZWqSIJqirFuKohuiNPVCMIYgibqpi5Iumko/DCA0QTJlzTQ1w5CNKVO1BEnTLMuSTEXXtEEQumAY -qmSZmiJLojZVCgyA0QTDklXJVA20ZPFWNNHSRFNSZAs9UVXBxJ+Ip0QVjwBCF0Rd1C3RNCxTZhCS -rsmyaaqKblpTGY6PrOkK2tRUXWfv0VRLMizRMCSVvccSRFmSACRJosp6q8pA2ULbiq4CwhIFel4X -LUWzjH4IwseSBEvTNEMVFU0f3IosmLplSLquqhg3gpAlRQEJ0BmTemIpAuisSsAHn6yvIKllmZaC -EVMZPoAxMDiipBC5WSuSYoL8ADE0mbdiWKohW7IiG/w9KohCYyqqvCf9EH3j0wfTN8rA2DREVUO7 -siUN5JRP+K0UeHA4E/GtrqmKoYmGrrC3gQi6Zpmgj6RxzpQFHUOvy6qu6UY/DCAkQBgWiAw+In4w -AYGhtSxVMiRpEIQumLgHhrAk2eQjOQBGVUXwiiIasma3YsBNNSzDFDWTQRAfmqZlyKqhcAgwjIgx -UTCgnLvBF2hV1tF9zpnAR1c08LaFh/lIok0wGejDe6sIGphZx1MmUZh6QuTDuIhsqE0VXQNFJciF -ofQBMGxUQQO1cVGT9MFN6LIs6Rhq9I0BKBZeaYHLMB4cwhDxk96kWTaECgjR0nWTo4JGwKaKasqK -zjuq0UJtSK/KWZtBABcLLK/x1+iqZinUb+KegRCMKS2VVITlINMNYwigJnoFSQMVeSumSiRVDJ3U -F1hAkmUF0g6EDVs3iTqJsgqEGABEEsoOL5MVyUHHALVAV0M2NQZjaSoYSVaAkP0aheiF8ZbVDr+i -FdPiSoUztGqC7IbcB5DxwfNbrnRYGGaImgGNpYgSQxvd0RRTh4oBli8cBtyG1mRV1PtBOIAIKYAy -MlQbQgFGJrQW9A+HACOAuA5SgCDdaWqgrixz9QMYwIOkkqExEIiKZpgyhlm2XwNBIg0HC8L7KoKb -gS2MlaoOguADjVGFtOgS1z79MIqhKooliaQQ7VagPNEoKMP1ugW9D9Y10Zr9HkkVMfIyqMoAdEg6 -PWFInG0tTZBlQwO10ZDFQJjZs1Twj8V0KWRSBmeLGBnZ5BZG0nEF/8lMRgEBkmuqic6ZJOkKrImm -GNCTIpGVvccQoM9xFWyqcxgFmhgv0SHx7D3QVDIZdAyPpTMIDXxNVgo2SeMQBuTFwC1FVjgECAb7 -hsu2aTAFzaC3wWwa3KTifRAxExbPdDpLNIVKFp0RBpdAAEVdsmk2lNM6Gtu2ieiDyboD8iumQiZd -JvtnuxK6ZhIC0JRqPwwTI0tXoNJlE/1kSIH3oEYxqIquD4BQLAF6x4TpVzGQrorrhoH2MaCywA4g -MmuF9UQjpS2SqCkQPQw6kFBJyqf60OkFsHlSwUiTBTLJkxjQCHhdFqGO8C7NcDoCc05YG9YgiAHo -9MLYRAE29GpHlGVVJb/I0vRBhP1kdDyG17YgkDbZdjksGnoTDgMsLR9HGHq8mNCWdKUfRgJuEGHw -KbxSTWY9JrMLXQf6mwPuS5Bd0lWQADgJDOseEBAGLAiqEf9YjFdEQQW2pgx1rRDpTAGOCIZYJ7Xg -GHfItEx5cuJ/BcxLQo4HMPaWY3RFMq/gME1TWCvgb0OHWjY1nStuUVUtEaIqwdhTR0QVfIW+wuey -7aEEh4AYSie/tBfCZknZ0C0iAcb2g1ZUvAh6U5N1G0IhxQFmUmQbQmMMBz1IPgRBSNAzMNSgkuZY -KjCOjIbJ/2MwzEvRdShCptQJwoCiJD9D5hwHbwP8JKk6hnQwBPGkTD2HM+vg0wWDEVR08sJBA5Xp -JxJC9BVmGS4y6VKZ5llNsrpUuMYtrwEHATKjqVzF4WnwFO13bNqGFwoZQwc/CfrIYupWIf9Rh1ev -OUSBnYHXgN8m51fwByQQupQ6y423AUkXFfj3XAlaOvSbAVfCcO07PBeMuUzuNWdoEW43dAZ6Yjsj -MAv4B2/d6rC8SM9gUKY+FBwXIuNDuDwWHt61iJGGqjZtCyGBEjC9oBjI+cLVuyKL5KTA85D7YZjm -gEBYMPAaA5BBK50cSGhgpgeh/xHCkBdt6DIfJLwPsRJiG1gs11jBOCCO0anamoYanAzFiiHRmLTT -a8ghNICnQhAISnWFAgFFUbmC6oGwWQpuNuhuOFquG4QcNVWh0FbhnikZK4vsG0RCs9W6SAAy7CD3 -AWDS4ctCvBG5EIQGCiFQYn6EonB04PCCPKQqDKZYwFIwkNCQiLFMnXcWFpwkAi6jyfQ6/HoyxxY6 -y2yvPTrkQxPZeiGAjyxC+SCGgMBD16gDWpFFukIeF3wMiobQWyg3uLfQ7vB+BkFAOYH54RVAAnCB -vacHBsOD0ZIha1C4cED6e+LwiUVYa8rU59zWMQ/QyoqkQouSl2FwhoFkQQtR+ZXB+NIgppIpqAWv -S/0wEvcURfgr4BFIr8JHz+CjLkvQ6BBRhWwbswQap40CdKAdFHxO9b+FrK9FbqcOva/bRqirr70Q -TI+J8PhAJwiApOmDWoERQSxHnqTG2B/8gqfguCOygxtsR3/gBSAHhlIYBJiLNKZMuQku+RICEzjG -6I2s8rEGSUBeUbUsrshgA8lZRVxuUTQLCJCAnBYYWUsdBKELGH2wCiyEaCpOrNoLo8L/I5cctLZj -YoVCF5gJzXaOCA+4wbop8dBMIhOoEEPJosbMJvQFsYIFK2dwfETiEMQJEC9LZjB4rUT6H6NpUx8m -EzyEcbM9GwT98DvJMTMGQdheADxrvElzvYBeGMRYlHOAmIqyPcpwZSASpshiMzLo5CtRjCoZHAK+ -EZABc8s2P5K8WxS4mFwzUysw+KQXVZbzIRiZRbSwRpb9HhARCgpDaHO+yGJemUZ6AL/1QmR8yI9r -AaDYMf4kHhhN6hEkw5Qw2PAhEIayGI9gNLCmbCBQUI1+GAZB4Ro4QCT7CQC43joFNAb5rAxAJqeM -/HuDv8aAPqHUhSFqzAIwGPJaYbh1CpEBozN3WiTr7bRiYPBxFXENg4DLoRPDWGynjg== - - - AQDQZOApSYULwxVZLwjYjnIxIIGqIebjqk6BcSdDpFq2UoAmJHsgMwBZgMpSaDWXpHJJtCjARTQJ -eyVzZOA9w9WD/qSojcGgk2RV4Jnzl1gSOWWMAOw+OAVtIGwno+2ODEJ5DK7cD8GQgWVXoVFM8t+U -ga1Af+EP9o7ifa4RYCzhutnBWT+EzrwHsCC8O5W/phcEpgsuIyXTFI3ZGN4PiIoJf8HlEHJlEPlq -g7Dpgcj44EXi2o4fDPkn8WVMoIGR8JwGt9fJ2eE6pJEyWmY/DNemJskzYCjCJqcdPE58YTBm64cY -rAe7YVTodUODL0muPNOmvT2B5qd0ggUXHB7n1AB8eiCcrDUGRYfWkhV1YCsYAYgbwhK4z5yfVJ0l -eSWeOdKhvxD2GMDAsH0WPAxdBp/XMh03mGlx4nVZZRjDZsMeUj7RdNJc5NtB17BMMagGzxytmOQM -OOk00upwoHi0ahJdZYWEHbbB9U+hHGAb4ADaMCr0LqyfTJEez3NJCDYQeDiqoHf8PuGCUiB9H5Cm -Qsd1Nlsz9dgslqtspkMURJFoJMHVpyTEFB19MkWBq04yDewkEUKEK7LG5jhi7BvMMgDhryLoJ1mg -tD0Yeyr9GKAW7T+D/J2pmAS6QCGiexI0HlxPRe5IkERp184zUEwQA6KuQVGV/QdXnuWtIYki3aZk -09mL+yqwG9wAhNlTMWg/4kIZJhL+J3za0d5lsrGGqBOjUgqJfDx9Kl36r6GV/lq0NLfXtjCJxCKU -iNZNQossXzodGFxz7f09Fd9ptA8qJVrPU2YTZL5YIn5QKda2i+1m9TfN3oUyaxvrNvMdPTSaL/yW -OxO3Vm7cV27XNiyq4j5s/12r3HY64ZmXo/2ozsrE1fIkXN3Z2kq0mZeIoduTfouf16KXqs1SrQI2 -+GAzq38kBQ17hpWmPQ+fiq+VI3/Ljr55jbz/tZaNer1SajeasdJ7869i+71ZGWnvnYHPf/Nyh1ar -hH/sA/9Kzqf9x3/aMDZcmD1W4rDsLn2MsnGZ04WPHhq8dZnnqW+m2dBtvGqNx8b/5228rJFE6v/C -/l1jYTz5xl3/tzZr+S+Y3a0034gyVy+zEt9YLBAM7hUfK0fNIqSnGXhsFf+qTBXr9Ua72K5QdSL6 -UGlBYVemqB6EruARBzwYzO3mA/8LKtOkrA== - - - diff --git a/theme/colored/owncloud-sidebar-16.png b/theme/colored/owncloud-sidebar-16.png deleted file mode 100644 index 5c01c4fe9..000000000 Binary files a/theme/colored/owncloud-sidebar-16.png and /dev/null differ diff --git a/theme/colored/owncloud-sidebar-18.png b/theme/colored/owncloud-sidebar-18.png deleted file mode 100644 index bff617574..000000000 Binary files a/theme/colored/owncloud-sidebar-18.png and /dev/null differ diff --git a/theme/colored/owncloud-sidebar-32.png b/theme/colored/owncloud-sidebar-32.png deleted file mode 100644 index 721c04e48..000000000 Binary files a/theme/colored/owncloud-sidebar-32.png and /dev/null differ diff --git a/theme/colored/owncloud-sidebar-36.png b/theme/colored/owncloud-sidebar-36.png deleted file mode 100644 index 27e415d31..000000000 Binary files a/theme/colored/owncloud-sidebar-36.png and /dev/null differ diff --git a/theme/colored/owncloud-sidebar-64.png b/theme/colored/owncloud-sidebar-64.png deleted file mode 100644 index 5c6f17f0c..000000000 Binary files a/theme/colored/owncloud-sidebar-64.png and /dev/null differ diff --git a/translations/client_ca.ts b/translations/client_ca.ts index b7c4490e9..be35103fe 100644 --- a/translations/client_ca.ts +++ b/translations/client_ca.ts @@ -412,44 +412,44 @@ 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 @@ -1408,7 +1408,7 @@ Els elements que poden ser eliminats s'eliminaran si impedeixen que una car - + Folder Carpeta @@ -1423,27 +1423,32 @@ Els elements que poden ser eliminats s'eliminaran si impedeixen que una car - + + There were too many issues. Not all will be visible here. + + + + Copy the issues list to the clipboard. - + Copy Copiar - + Time Hora - + File Fitxer - + Issue @@ -2497,6 +2502,14 @@ No és aconsellada usar-la. S'ha produit un error en carregar la llista de subcarpetes. + + OCC::ServerNotificationHandler + + + Dismiss + + + OCC::SettingsDialog @@ -2675,99 +2688,95 @@ No és aconsellada usar-la. - - + + P&assword protect Protegit amb contr&asenya - + Password Protected Protegit amb contrasenya - + 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ó. - - %1 link - - - - + Link shares have been disabled - + Create public link share - - + + Delete Esborra - + Open link in browser - + Copy link to clipboard - + Copy link to clipboard (direct download) - + Send link by email Envia l'enllaç per correu electrònic - + Send link by email (direct download) - + 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 - + Delete link share - + Public sh&aring requires a password La comp&artició pública requereix una contrasenya - + Please Set Password Establiu la contrasenya diff --git a/translations/client_cs.ts b/translations/client_cs.ts index 6c73f1bae..50131a190 100644 --- a/translations/client_cs.ts +++ b/translations/client_cs.ts @@ -412,44 +412,44 @@ 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) - + The server activity list has been copied to the clipboard. Výpis aktivity serveru byl zkopírován do schránky. - + The sync activity list has been copied to the clipboard. Výpis aktivity synchronizace byl zkopírován do schránky. - + The list of unsynced items has been copied to the clipboard. Seznam nesynchronizovaných položek byl zkopírován do schránky. - + Copied to clipboard Zkopírováno do schránky @@ -1411,7 +1411,7 @@ Položky u kterých je povoleno smazání budou vymazány, pokud by bránily ods - + Folder Adresář @@ -1426,27 +1426,32 @@ Položky u kterých je povoleno smazání budou vymazány, pokud by bránily ods Ukázat ignorované soubory - + + There were too many issues. Not all will be visible here. + + + + Copy the issues list to the clipboard. - + Copy Kopie - + Time Čas - + File Soubor - + Issue Problém @@ -2500,6 +2505,14 @@ Nedoporučuje se jí používat. Došlo k chybě v průběhu načítání seznamu podadresářů. + + OCC::ServerNotificationHandler + + + Dismiss + Zamítnout + + OCC::SettingsDialog @@ -2678,99 +2691,95 @@ Nedoporučuje se jí používat. Kdokoliv, kdo má odkaz, může přistupovat k tomuto souboru/složce - - + + P&assword protect Ch&ránit heslem - + Password Protected Chráněno 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í. - - %1 link - - - - + Link shares have been disabled - + Create public link share - - + + Delete Smazat - + Open link in browser Otevřít odkaz v prohlížeči - + Copy link to clipboard - + Copy link to clipboard (direct download) - + Send link by email Poslat odkaz emailem - + Send link by email (direct download) - + 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šit - + + Public link Veřejný odkaz - + Delete link share - + Public sh&aring requires a password Veřejné s&dílení vyžaduje heslo - + Please Set Password Nastavte prosím heslo diff --git a/translations/client_de.ts b/translations/client_de.ts index 40ef6dd31..6064178cd 100644 --- a/translations/client_de.ts +++ b/translations/client_de.ts @@ -412,44 +412,44 @@ OCC::ActivitySettings - - + + Server Activity Serveraktivität - + Sync Protocol Synchronisationsprotokoll - + Not Synced nicht synchronisiert - + Not Synced (%1) %1 is the number of not synced files. nicht synchronisiert (%1) - + The server activity list has been copied to the clipboard. Die Server-Aktivitätsliste wurde in die Zwischenablage kopiert. - + The sync activity list has been copied to the clipboard. Die Synchronisationsliste wurde in die Zwischenablage kopiert. - + The list of unsynced items has been copied to the clipboard. Die Liste der unsynchronisierten Dateien wurde in die Zwischenablage kopiert. - + Copied to clipboard In die Zwischenablage kopiert @@ -1413,7 +1413,7 @@ Objekte, bei denen Löschen erlaubt ist, werden gelöscht, wenn sie die Löschun - + Folder Ordner @@ -1428,27 +1428,32 @@ Objekte, bei denen Löschen erlaubt ist, werden gelöscht, wenn sie die Löschun Ignorierte Dateien anzeigen - + + There were too many issues. Not all will be visible here. + Es gab zu viele Probleme. Nicht alle können hier dargestellt werden. + + + Copy the issues list to the clipboard. Liste der Fehler in die Zwischenablage kopieren. - + Copy Kopieren - + Time Zeit - + File Datei - + Issue Fehler @@ -2501,6 +2506,14 @@ Es ist nicht ratsam, diese zu benutzen. Ein Fehler ist aufgetreten, während die Liste der Unterordner geladen wurde. + + OCC::ServerNotificationHandler + + + Dismiss + Ausblenden + + OCC::SettingsDialog @@ -2679,99 +2692,95 @@ Es ist nicht ratsam, diese zu benutzen. Jeder mit dem Link hat Zugriff auf die Datei/Ordner - - + + P&assword protect Passwort geschützt - + Password Protected 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. - - %1 link - %1 - Link - - - + 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 Zwischenablage kopieren - + Copy link to clipboard (direct download) Link in die Zwischenablage kopieren (direkter download) - + Send link by email Link als E-Mail verschicken - + Send link by email (direct download) Link per Email senden (direkter download) - + 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öchte Sie wirklich den öffentlichen Link <i>%1 </i>löschen?<p>Hinweis: Dies kann nicht rückgängig gemacht werden.</p> - + Cancel Abbrechen - + + Public link Öffentlicher Link - + Delete link share Öffentlichen Link löschen - + Public sh&aring requires a password Öffentliches Teilen erfordert ein P&asswort - + Please Set Password Bitte wählen Sie ein Passwort: diff --git a/translations/client_el.ts b/translations/client_el.ts index c4ed10508..f8d25d012 100644 --- a/translations/client_el.ts +++ b/translations/client_el.ts @@ -412,44 +412,44 @@ 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 Αντιγράφηκε στο πρόχειρο @@ -1413,7 +1413,7 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + Folder Φάκελος @@ -1428,27 +1428,32 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + + There were too many issues. Not all will be visible here. + + + + Copy the issues list to the clipboard. - + Copy Αντιγραφή - + Time Ώρα - + File Αρχείο - + Issue @@ -2502,6 +2507,14 @@ It is not advisable to use it. Παρουσιάστηκε σφάλμα κατά την φόρτωση της λίστας των υπο-φακέλων + + OCC::ServerNotificationHandler + + + Dismiss + Απόρριψη + + OCC::SettingsDialog @@ -2680,99 +2693,95 @@ It is not advisable to use it. Οποιοσδήποτε με τη σύνδεση έχει πρόσβαση στο αρχείο / φάκελο - - + + P&assword protect Π&ροστασία με κωδικό - + Password Protected Προστατευμένο με κωδικό πρόσβασης - + The file can not be shared because it was shared without sharing permission. Το αρχείο δεν μπορεί να διαμοιραστεί γιατί διαμοιράστηκε χωρίς δικαιώματα διαμοιρασμού. - - %1 link - %1 σλυνδεσμος - - - + 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) - + 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 Παρακαλούμε ορίστε Κωδικό diff --git a/translations/client_en.ts b/translations/client_en.ts index b5288470d..cec124faa 100644 --- a/translations/client_en.ts +++ b/translations/client_en.ts @@ -414,44 +414,44 @@ 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 @@ -1434,7 +1434,7 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + Folder @@ -1449,27 +1449,32 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + + There were too many issues. Not all will be visible here. + + + + Copy the issues list to the clipboard. - + Copy - + Time - + File - + Issue @@ -2521,6 +2526,14 @@ It is not advisable to use it. + + OCC::ServerNotificationHandler + + + Dismiss + + + OCC::SettingsDialog @@ -2699,99 +2712,95 @@ It is not advisable to use it. - - + + P&assword protect - + Password Protected - + The file can not be shared because it was shared without sharing permission. - - %1 link - - - - + 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) - + 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 diff --git a/translations/client_es.ts b/translations/client_es.ts index f5588ea94..1d26e031f 100644 --- a/translations/client_es.ts +++ b/translations/client_es.ts @@ -283,7 +283,7 @@ 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 almacenes externos: + Hay carpetas que no fueron sincronizadas porque son demasiado grandes o residen en almacenamiento externo: @@ -412,44 +412,44 @@ OCC::ActivitySettings - - + + Server Activity Actividad del 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. La lista de la actividad del servidor se ha copiado en el portapapeles. - + The sync activity list has been copied to the clipboard. La lista de actividades de sincronización se ha copiado en el portapapeles. - + The list of unsynced items has been copied to the clipboard. La lista de elementos sin sincronizar, ha sido copiada al portapapeles. - + Copied to clipboard Copiado al portapapeles @@ -1244,7 +1244,7 @@ Si continua con la sincronización todos los archivos serán remplazados por su Ask for confirmation before synchronizing external storages - Preguntar si se desea sincronizar carpetas de almacenes externos + Preguntar si se desea sincronizar carpetas de almacenamiento externo @@ -1413,7 +1413,7 @@ Los elementos cuya eliminación está permitida serán eliminados si impiden que - + Folder Carpeta @@ -1428,27 +1428,32 @@ Los elementos cuya eliminación está permitida serán eliminados si impiden que Mostrar archivos ignorados - + + There were too many issues. Not all will be visible here. + + + + Copy the issues list to the clipboard. Copiar la lista de problemas al portapapeles. - + Copy Copiar - + Time Hora - + File Archivo - + Issue Problema @@ -2501,6 +2506,14 @@ No se recomienda usarla. Ha ocurrido un error mientras cargaba la lista de carpetas. + + OCC::ServerNotificationHandler + + + Dismiss + Descartar + + OCC::SettingsDialog @@ -2679,99 +2692,95 @@ No se recomienda usarla. Quienquiera que posea el vínculo tendrá acceso al archivo/carpeta - - + + P&assword protect Protegido por contr&aseña - + Password Protected Protegido con contraseña - + 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. - - %1 link - %1 Enlace - - - + Link shares have been disabled Se ha deshabilitado la compartición de enlaces - + Create public link share Crear un enlace público compartido - - + + Delete Eliminar - + Open link in browser Abrir enlace en el explorador - + Copy link to clipboard Copiar enlace al portapapeles - + Copy link to clipboard (direct download) Copiar enlace al portapapeles (descarga directa) - + Send link by email Enviar enlace por e-mail - + Send link by email (direct download) Enviar enlace por e-mail (descarga directa) - + Confirm Link Share Deletion Confirmar eliminación de enlace de recurso compartido - + <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> - + Cancel Cancelar - + + Public link Enlace Publico - + Delete link share Borrar el enlace compartido - + Public sh&aring requires a password Compartir public&amente requiere contraseña - + Please Set Password Por favor establece una contraseña @@ -3454,7 +3463,7 @@ No se recomienda usarla. <p>Version %1. For more information please visit <a href='%2'>%3</a>.</p> - <p>Versión %1. Para obtener más información, visita<a href='%2'>%3</a>.</p> + <p>Versión %1. Para obtener más información, visita <a href='%2'>%3</a>.</p> @@ -3635,7 +3644,7 @@ No se recomienda usarla. Unpause all folders - Despausar todas las carpetas + Reanudar todas las carpetas @@ -3645,12 +3654,12 @@ No se recomienda usarla. Unpause all synchronization - Despausar todas las sincronizaciones + Reanudar toda la sincronización Unpause synchronization - Despausar la sincronización + Reanudar la sincronización @@ -3774,7 +3783,7 @@ No se recomienda usarla. Ask for confirmation before synchronizing e&xternal storages - Preguntar si se desea sincronizar carpetas de almacenes e&xternos + Preguntar si se desea sincronizar carpetas de almacenamiento e&xterno diff --git a/translations/client_es_AR.ts b/translations/client_es_AR.ts index 5663831ad..d4aea5cb5 100644 --- a/translations/client_es_AR.ts +++ b/translations/client_es_AR.ts @@ -412,44 +412,44 @@ 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 @@ -1402,7 +1402,7 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + Folder Carpeta @@ -1417,27 +1417,32 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + + There were too many issues. Not all will be visible here. + + + + Copy the issues list to the clipboard. - + Copy Copiar - + Time Hora - + File Archivo - + Issue @@ -2489,6 +2494,14 @@ It is not advisable to use it. + + OCC::ServerNotificationHandler + + + Dismiss + + + OCC::SettingsDialog @@ -2667,99 +2680,95 @@ It is not advisable to use it. - - + + P&assword protect - + Password Protected - + The file can not be shared because it was shared without sharing permission. - - %1 link - - - - + 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) - + 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 diff --git a/translations/client_et.ts b/translations/client_et.ts index 8830831a6..e477a5ecb 100644 --- a/translations/client_et.ts +++ b/translations/client_et.ts @@ -412,44 +412,44 @@ OCC::ActivitySettings - - + + Server Activity Serveri aktiivsus - + Sync Protocol Sünkroniseerimisprotokoll - + Not Synced Pole sünkroonitud - + 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 Kopeeritud lõikepuhvrisse @@ -1402,7 +1402,7 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + Folder Kaust @@ -1417,27 +1417,32 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + + There were too many issues. Not all will be visible here. + + + + Copy the issues list to the clipboard. - + Copy Kopeeri - + Time Aeg - + File Fail - + Issue @@ -2490,6 +2495,14 @@ Selle kasutamine pole soovitatav. + + OCC::ServerNotificationHandler + + + Dismiss + Jäta vahele + + OCC::SettingsDialog @@ -2635,7 +2648,7 @@ Selle kasutamine pole soovitatav. &Create new - + &Loo uus @@ -2668,99 +2681,95 @@ Selle kasutamine pole soovitatav. - - + + P&assword protect P&arooliga kaitstud - + Password Protected Parooliga kaitstud - + The file can not be shared because it was shared without sharing permission. - - %1 link - - - - + Link shares have been disabled - + Create public link share - - + + Delete Kustuta - + Open link in browser - + Copy link to clipboard - + Copy link to clipboard (direct download) - + Send link by email Saada link e-postiga - + Send link by email (direct download) - + 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 Loobu - + + Public link - + Avalik link - + Delete link share - + Public sh&aring requires a password Avalik j&agamine nõuab parooli - + Please Set Password Palun määra parool diff --git a/translations/client_eu.ts b/translations/client_eu.ts index cf37af90a..10eea4ab2 100644 --- a/translations/client_eu.ts +++ b/translations/client_eu.ts @@ -412,44 +412,44 @@ 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 @@ -1404,7 +1404,7 @@ Ezabatzeko baimena duten itemak ezabatuko dira hauek karpeta bat ezabatzea uzten - + Folder Karpeta @@ -1419,27 +1419,32 @@ Ezabatzeko baimena duten itemak ezabatuko dira hauek karpeta bat ezabatzea uzten - + + There were too many issues. Not all will be visible here. + + + + Copy the issues list to the clipboard. - + Copy Kopiatu - + Time Noiz - + File Fitxategia - + Issue @@ -2492,6 +2497,14 @@ Ez da gomendagarria erabltzea. + + OCC::ServerNotificationHandler + + + Dismiss + + + OCC::SettingsDialog @@ -2670,99 +2683,95 @@ Ez da gomendagarria erabltzea. Esteka duen edonork atzitu dezake fitxategi/karpeta - - + + P&assword protect - + Password Protected - + The file can not be shared because it was shared without sharing permission. - - %1 link - - - - + 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) - + 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 diff --git a/translations/client_fa.ts b/translations/client_fa.ts index cdc94a553..8c9d53594 100644 --- a/translations/client_fa.ts +++ b/translations/client_fa.ts @@ -78,17 +78,17 @@ Connection timed out - + زمان ارتباط تمام شد Unknown error: network reply was deleted - + خطای ناشناخته: پاسخ شبکه پاک شد Server replied "%1 %2" to "%3 %4" - + سرور "1% 2%" به "3% 4%" پاسخ داد @@ -101,7 +101,7 @@ ... - + ... @@ -111,7 +111,7 @@ Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - + پوشه های بررسی نشده از سیستم فایل محلی شما <b>حذف</b> خواهد شد و دیگر در این کامپیوتر همگام سازی نخواهد شد. @@ -121,7 +121,7 @@ Synchronize none - + همگام سازی هیچ @@ -153,7 +153,7 @@ Add new - + اضافه کردن جدید @@ -178,32 +178,32 @@ 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 - + حذف اتصال همگام سازی پوشه @@ -218,17 +218,17 @@ %1 in use - + 1% در استفاده %1 as <i>%2</i> - + 1% به عنوان <i>2%</i> The server version %1 is old and unsupported! Proceed at your own risk. - + نسخه سرور 1% قدیمی است و پشتیبانی نشده است! مسئولیت با خود شماست. @@ -243,17 +243,17 @@ 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. - + دریافت مجوز از مرورگر. <a href='%1'>اینجا کلیک کنید</a> تا مرورگر دوباره باز شود. @@ -263,7 +263,7 @@ No connection to %1 at %2. - + اتصال به 1% در 2% وجود ندارد. @@ -273,17 +273,17 @@ 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: - + پوشه‌هایی وجود دارند که همگام سازی نشده اند زیرا آن ها بسیار بزرگ یا مخازن خارجی هستند: @@ -293,7 +293,7 @@ <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> @@ -325,22 +325,22 @@ <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. - + در حال حاضر هیچ اطلاعات کاربرد ذخیره سازی در دسترس نیست. @@ -358,7 +358,7 @@ Disconnected - + قطع شده @@ -373,7 +373,7 @@ Maintenance mode - + حالت تعمیر @@ -383,17 +383,17 @@ Configuration error - + خطای پیکربندی Asking Credentials - + درخواست مجوزها Unknown account state - + وضعیت حساب ناشناخته @@ -401,55 +401,55 @@ %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. - + همگام سازی نشده (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 کپی به کلیپ بورد @@ -481,37 +481,37 @@ 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. - + شما n% هشدار جدید از 2% دریافت کردید. You received %n new notification(s) from %1 and %2. - + شما n% هشدار جدید از 1% و 2% دریافت کردید. You received new notifications from %1, %2 and other accounts. - + شما هشدارهای جدیدی از 1%، 2% و سایر حساب ها دریافت کردید. %1 Notifications - Action Required - + 1% هشدارها - عمل لازم @@ -519,37 +519,37 @@ SSL client certificate authentication - + تأیید اعتبار گواهی‌نامه مشتری SSL This server probably requires a SSL client certificate. - + سرور احتمالا نیاز به گواهی‌نامه مشتری SSL دارد. Certificate & Key (pkcs12) : - + گواهی‌نامه و کلید (pkcs12) : Browse... - + مرور... Certificate password : - + گواهی‌نامه رمز عبور : Select a certificate - + انتخاب یک گواهی‌نامه Certificate files (*.p12 *.pfx) - + گواهی‌نامه فایل های (p12 *.pfx.*) @@ -557,12 +557,12 @@ Error accessing the configuration file - + خطای دسترسی به پرونده پیکربندی There was an error while accessing the configuration file at %1. - + خطایی هنگام دسترسی به پرونده پیکربندی در 1% وحود دارد. @@ -575,12 +575,12 @@ Authentication Required - + نیازمند تاییدیه Enter username and password for '%1' at %2. - + نام کاربری و رمز عبور را برای '1%' در 2% وارد کنید. @@ -606,32 +606,32 @@ 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 - + مدارک ارائه شده صحیح نیستند @@ -652,7 +652,7 @@ %1 should be a folder but is not. - + 1% باید یک پوشه باشد اما نیست. @@ -691,47 +691,47 @@ %1 and %n other file(s) have been removed. - + 1% و n% پرونده های دیگر حذف شده اند. %1 and %n other file(s) have been downloaded. - + 1% و n% پرونده های دیگر دانلود شده اند. %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% سایر پرونده ها به دلیل خطاها نمی توانند همگام سازی شوند. برای جزییات log را مشاهده کنید. %1 could not be synced due to an error. See the log for details. - + 1% به دلیل خطاها نمی تواند همگام سازی شود. برای جزییات log را مشاهده کنید. @@ -741,24 +741,26 @@ 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. - + اگر می خواهید این را دانلود کنید لطفا به تنظیمات بروید تا آن را انتخاب کنید. @@ -766,14 +768,19 @@ 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%' در سرور حذف شد. این حذف ها با پوشه همگام ساز محلی شما هماهنگ خواهند شد، ساخت چنین پرونده هایی در دسترس نیستند مگر اینکه حق بازگرداندن داشته باشید. +آیا شما اطمینان دارید که می خواهید آن اعمال را با سرور همگام نمایید؟ +اگر این یک اتفاق بوده و شما تصمیم دارید پرونده هایتان را نگه دارید، آن ها از سرور مجددا همگام سازی خواهند شد. @@ -795,22 +802,24 @@ If this was an accident and you decide to keep your files, they will be re-synce 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? - + این همگام سازی پرونده ها را به زمان قبلی در پوشه همگام ساز 1% مجددا تنظیم می کند. +این ممکن است به این دلیل باشد که یک پشتیبان در سرور بازسازی شده است. +ادامه همگام سازی به طور معمول سبب خواهد شد که تمام پرونده های شما توسط یک فایل قدیمی تر در یک وضعیت جدیدتر بازنویسی شوند. آیا شما می خواهید پرونده های اخیر محلیتان را به عنوان پرونده های ناسازگار نگهداری کنید؟ Backup detected - + پشتیبان شناسایی شد Normal Synchronisation - + همگام سازی معمول Keep Local Files as Conflict - + پرونده های محلی را به عنوان ناسازگار نگه دارید @@ -823,7 +832,7 @@ Continuing the sync as normal will cause all your files to be overwritten by an An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - + یک مجله همگام قدیمی '1%' پیدا شد، اما حذف نمی شود. لطفا مطمئن شوید که هیچ برنامه ای در حال حاضر از آن استفاده نمی کند. @@ -863,7 +872,7 @@ Continuing the sync as normal will cause all your files to be overwritten by an Last Sync was successful, but with warnings on individual files. - + اخرین همگام سازی موفق بود، اما با هشدارهایی در پرونده های مجزا همراه بود. @@ -893,7 +902,7 @@ Continuing the sync as normal will cause all your files to be overwritten by an The selected path is not a folder! - + مسیر انتخاب شده یک پوشه نیست! @@ -903,27 +912,27 @@ Continuing the sync as normal will cause all your files to be overwritten by an The local folder %1 contains a symbolic link. The link target contains an already synced folder Please pick another one! - + پوشه محلی 1% شامل یک پیوند نمادین می باشد. پیوند مقصد در حال حاضر شامل پوشه ای همگام سازی شده است، لطفا یکی دیگر را انتخاب کنید! 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% از قبل یک پوشه استفاده شده در یک اتصال همگام سازی پوشه دارد. لطفا یکی دیگر را انتخاب کنید! The local folder %1 is a symbolic link. The link target is already contained in a folder used in a folder sync connection. Please pick another one! - + پوشه محلی 1% شامل یک پیوند نمادین می باشد. پیوند مقصد در حال حاضر در یک پوشه استفاده شده در اتصال همگام پوشه موجود است. لطفا یکی دیگر را انتخاب کنید! @@ -931,7 +940,7 @@ Continuing the sync as normal will cause all your files to be overwritten by an Add Folder Sync Connection - + افزودن اتصال همگام سازی پوشه @@ -949,7 +958,7 @@ Continuing the sync as normal will cause all your files to be overwritten by an You need to be connected to add a folder - + شما باید متصل باشید تا پوشه ای را اضافه کنید @@ -966,7 +975,7 @@ Continuing the sync as normal will cause all your files to be overwritten by an Error while loading the list of folders from the server. - + خطای هنگام بارگذاری لیست پوشه‌ها از سرور. @@ -976,28 +985,28 @@ Continuing the sync as normal will cause all your files to be overwritten by an Fetching folder list from server... - + آوردن لیست پوشه از سرور... There are unresolved conflicts. Click for details. - + ناسازگاری های حل نشده ای وجود دارد. برای جزییات کلیک نمایید. Checking for changes in '%1' - + بررسی تغییرات در '1%' Reconciling changes - + تطبیق تغییرات , '%1' Build a list of file names - + ، '1%' @@ -1055,13 +1064,13 @@ Continuing the sync as normal will cause all your files to be overwritten by an %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% @@ -1089,12 +1098,12 @@ Continuing the sync as normal will cause all your files to be overwritten by an Add Folder Sync Connection - + اضافه کردن اتصال همگام سازی پوشه Add Sync Connection - + افزودن اتصال همگام سازی @@ -1102,12 +1111,12 @@ Continuing the sync as normal will cause all your files to be overwritten by an Click to select a local folder to sync. - + کلیک کنید تا یک پوشه را برای همگام سازی انتخاب نمایید. Enter the path to the local folder. - + مسیر را به پوشه محلی وارد کنید. @@ -1120,12 +1129,12 @@ Continuing the sync as normal will cause all your files to be overwritten by an Create Remote Folder - + ایجاد پوشه از راه دور Enter the name of the new folder to be created below '%1': - + نام پوشه جدید را برای ایجاد شدن در زیر '1%' وارد نمایید: @@ -1135,22 +1144,22 @@ Continuing the sync as normal will cause all your files to be overwritten by an 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 - + این را برای همگام سازی کل حساب انتخاب کنید @@ -1160,7 +1169,7 @@ Continuing the sync as normal will cause all your files to be overwritten by an You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - + شما از قبل <i>1%</i> را همگام سازی کرده اید، که یک پوشه والد از <i>2%</i> است. @@ -1181,17 +1190,17 @@ Continuing the sync as normal will cause all your files to be overwritten by an 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 - + سرور محدوده محتوای اشتباهی برگرداند. @@ -1224,7 +1233,7 @@ Continuing the sync as normal will cause all your files to be overwritten by an Ask for confirmation before synchronizing folders larger than - + درخواست تایید قبل از همگام سازی پوشه های بزرگتر @@ -1235,32 +1244,32 @@ Continuing the sync as normal will cause all your files to be overwritten by an Ask for confirmation before synchronizing external storages - + درخواست تایید قبل از همگام سازی مخازن خارجی &Launch on System Startup - + راه انداختن در سیستم راه اندازی Show &Desktop Notifications - + نمایش هشدارهای دسکتاپ Use &Monochrome Icons - + استفاده از آیکون های تک رنگ Edit &Ignored Files - + ویرایش پرونده های رد شده S&how crash reporter - + نمایش گزارشگر برخورد @@ -1276,7 +1285,7 @@ Continuing the sync as normal will cause all your files to be overwritten by an &Restart && Update - + راه اندازی مجدد و به روز رسانی @@ -1284,7 +1293,7 @@ Continuing the sync as normal will cause all your files to be overwritten by an Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - + لطفا رمز عبور 1% را وارد کنید: <br><br> کاربر: 2% <br> حساب: 3% <br> @@ -1299,7 +1308,7 @@ Continuing the sync as normal will cause all your files to be overwritten by an <a href="%1">Click here</a> to request an app password from the web interface. - + <a href="%1">اینجا کلیک کنید</a> تا رمز عبور یک برنامه را از رابط وب درخواست کنید. @@ -1307,22 +1316,22 @@ Continuing the sync as normal will cause all your files to be overwritten by an Ignored Files Editor - + ویرایشگر پرونده های رد شده Global Ignore Settings - + تنظیمات رد کردن سراسری Sync hidden files - + همگام سازی پرونده های مخفی Files Ignored by Patterns - + پرونده ها توسط الگوها رد شده اند @@ -1337,7 +1346,7 @@ Continuing the sync as normal will cause all your files to be overwritten by an Allow Deletion - + اجازه حذف @@ -1349,7 +1358,8 @@ Continuing the sync as normal will cause all your files to be overwritten by an 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. - + پرونده ها یا پوشه‌های مطابق با یک الگو همگام نخواهند شد. +مواردی که در آن حذف کردن مجاز است، اگر از حذف یک پوشه جلوگیری کنند حذف خواهند شد. این برای فرا داده ها مفید است. @@ -1364,17 +1374,17 @@ Items where deletion is allowed will be deleted if they prevent a directory from 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%' ارائه شده است و در این دیدگاه قابل تغییر نیست. @@ -1387,7 +1397,7 @@ Items where deletion is allowed will be deleted if they prevent a directory from List of issues - + فهرست موضوعات @@ -1398,48 +1408,53 @@ Items where deletion is allowed will be deleted if they prevent a directory from <no filter> - + <no filter> - + Folder پوشه Show warnings - + نمایش هشدارها Show ignored files + نمایش پرونده های رد شده + + + + There were too many issues. Not all will be visible here. - + Copy the issues list to the clipboard. - + کپی کردن فهرست موضوعات در کلیپ بورد. - + Copy کپی کردن - + Time زمان - + File فایل - + Issue - + موضوع @@ -1447,7 +1462,7 @@ Items where deletion is allowed will be deleted if they prevent a directory from Log Output - + خروجی Log @@ -1462,7 +1477,7 @@ Items where deletion is allowed will be deleted if they prevent a directory from &Capture debug messages - + ضبط پیام های رفع خطا @@ -1472,7 +1487,7 @@ Items where deletion is allowed will be deleted if they prevent a directory from Clear the log display. - + صفحه نمایش log را پاک کنید. @@ -1482,7 +1497,7 @@ Items where deletion is allowed will be deleted if they prevent a directory from Save the log file to a file on disk for debugging. - + پرونده log را برای رفع اشکال در یک پرونده از دیسک ذخیره کنید. @@ -1497,7 +1512,7 @@ Items where deletion is allowed will be deleted if they prevent a directory from Could not write to log file %1 - + نمی توان در فایل لاگ 1% نوشت @@ -1510,7 +1525,7 @@ Items where deletion is allowed will be deleted if they prevent a directory from <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/>خروجی log <b>نمی تواند</b> ذخیره شود!</nobr> @@ -1523,7 +1538,7 @@ Items where deletion is allowed will be deleted if they prevent a directory from <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> @@ -1581,7 +1596,7 @@ Items where deletion is allowed will be deleted if they prevent a directory from Proxy server requires authentication - + سرور پروکسی نیازمند تایید است @@ -1610,7 +1625,7 @@ Items where deletion is allowed will be deleted if they prevent a directory from Limit to 3/4 of estimated bandwidth - + محدود به 3/4 پهنای باند تخمین زده شده @@ -1659,19 +1674,19 @@ Items where deletion is allowed will be deleted if they prevent a directory from 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% انتخاب شد @@ -1679,32 +1694,32 @@ Items where deletion is allowed will be deleted if they prevent a directory from 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> The reply from the server did not contain all expected fields - + پاسخ سرور شامل تمامی زمینه های مورد انتظار نبود <h1>Login Error</h1><p>%1</p> - + <h1>خطای ورود</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>کاربر نادرست</h1><p> شما با کاربر<em>1%</em> وارد شدید، اما باید با کاربر <em>2%</em> وارد می شدید. <br> لطفا در برگه دیگری از 3% خارج شده، سپس <a href='%4'>اینجا را کلیک کنید</a> و به عنوان کاربر 2% وارد شوید</p> @@ -1712,53 +1727,53 @@ Items where deletion is allowed will be deleted if they prevent a directory from 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. - + 1% نسخه 2% موجود است. برنامه را مجددا راه اندازی کنید تا به روز رسانی شروع شود. New %1 version %2 available. Please use the system's update tool to install it. - + 1% جدید نسخه 2% موجود است. لطفا از ابزار به روز رسانی سیستم استفاده کنید تا آن را نصب نمایید. Checking update server... - + بررسی سرور به روز رسانی... Update status is unknown: Did not check for new updates. - + وضعیت به روز رسانی نامشخص است: به روز رسانی های جدید را بررسی نکردید. No updates available. Your installation is at the latest version. - + به روز رسانی موجود نیست. نصب شما آخرین نسخه است. @@ -1776,7 +1791,7 @@ for additional privileges during the process. Setup local folder options - + راه اندازی گزینه های پوشه محلی @@ -1796,7 +1811,7 @@ for additional privileges during the process. <p><small><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</small></p> - + <p><small><strong>هشدار:</strong> پوشه محلی خالی نیست. یک دقت انتخاب کنید!</small></p> @@ -1820,7 +1835,7 @@ for additional privileges during the process. <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> @@ -1835,12 +1850,12 @@ for additional privileges during the process. 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> @@ -1848,7 +1863,7 @@ for additional privileges during the process. &Email - + پست الکترونیکی @@ -1858,7 +1873,7 @@ for additional privileges during the process. Enter user credentials - + وارد کردن گواهی نامه کاربر @@ -1871,7 +1886,7 @@ for additional privileges during the process. Login in your browser - + در مرورگر خود وارد شوید @@ -1890,7 +1905,8 @@ for additional privileges during the process. This url is NOT secure as it is not encrypted. It is not advisable to use it. - + این آدرس امن نیست زیرا رمزگذاری شده نیست. +استفاده از آن توصیه نمی شود. @@ -1918,7 +1934,7 @@ It is not advisable to use it. Timeout while trying to connect to %1 at %2. - + هنگام تلاش برای اتصال به 1% در 2% زمان به پایان رسید. @@ -1928,17 +1944,17 @@ It is not advisable to use it. 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> تا با مرورگر خود به سرویس دسترسی پیدا کنید. @@ -1948,7 +1964,7 @@ It is not advisable to use it. The server reported the following error: - + سرور خطای زیر را گزارش کرد: @@ -1958,7 +1974,7 @@ It is not advisable to use it. Creating local sync folder %1... - + ساخت پوشه همگام سازی محلی 1%... @@ -1978,7 +1994,7 @@ It is not advisable to use it. No remote folder specified! - + هیچ پوشه از راه دوری مشخص نشده است! @@ -2004,7 +2020,7 @@ It is not advisable to use it. The folder creation resulted in HTTP error code %1 - + ایجاد پوشه به خطای HTTP کد 1% منجر شد @@ -2045,7 +2061,7 @@ It is not advisable to use it. 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. - + نمی توانید پوشه را حذف کنید یا پشتیبان بگیرید زیرا پوشه یا یک پرونده در آن در برنامه دیگری باز است. لطفا پوشه یا پرونده را ببندید و مجددا تلاش کنید یا تنظیم را لغو کنید. @@ -2063,7 +2079,7 @@ It is not advisable to use it. Skip folders configuration - + از پیکربندی پوشه‌ها بگذرید @@ -2071,7 +2087,7 @@ It is not advisable to use it. Everything set up! - + همه چیز تنظیم شده است! @@ -2089,7 +2105,7 @@ It is not advisable to use it. Invalid JSON reply from the poll URL - + پاسخ JSON نامعتبر از آدرس نظرسنجی @@ -2105,12 +2121,12 @@ It is not advisable to use it. 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 - + دانلود فضای دیسک محلی آزاد تحت محدودیت را کاهش می دهد @@ -2130,17 +2146,17 @@ It is not advisable to use it. 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 - + پرونده از زمان کشف تغییر کرده است. @@ -2153,12 +2169,12 @@ It is not advisable to use it. ; Restoration Failed: %1 - + ؛ بازگردانی شکست خورد: 1% A file or folder was removed from a read only share, but restoring failed: %1 - + یک پرونده یا پوشه از یک اشتراک فقط خواندنی حذف شد، اما بازگردانی شکست خورد: 1% @@ -2166,7 +2182,7 @@ It is not advisable to use it. could not delete file %1, error: %2 - + نمی توان پرونده 1% را حذف کرد: خطای 2% @@ -2194,12 +2210,12 @@ It is not advisable to use it. Could not remove folder '%1' - + پوشه '1%' حذف نمی شود Could not remove %1 because of a local file name clash - + 1% بخاطر یک پرونده محلی به نام برخورد حذف نمی شود @@ -2207,7 +2223,7 @@ It is not advisable to use it. File %1 can not be renamed to %2 because of a local file name clash - + پرونده 1% بخاطر یک پرونده محلی به نام برخورد نتغییر نام داده نمی شود @@ -2221,12 +2237,12 @@ It is not advisable to use it. The file has been removed from a read only share. It was restored. - + پرونده از یک استراک فقط خواندنی حذف شد. این بازگردانی شد. Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - + کد HTTP اشتباه توسط سرور برگردانده شد. 204 انتظار می رفت، اما "1% 2%" دریافت شد. @@ -2234,7 +2250,7 @@ It is not advisable to use it. Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - + کد HTTP اشتباه توسط سرور برگردانده شد. 201 انتظار می رفت، اما "1% 2%" دریافت شد. @@ -2247,22 +2263,22 @@ It is not advisable to use it. This folder must not be renamed. It is renamed back to its original name. - + این پوشه نباید تغییر نام داده شود. نام آن به نام اصلی خود تغییر داده شده است. This folder must not be renamed. Please name it back to Shared. - + این پوشه نباید تغییر نام داده شود. لطفا نام آن را اشتراک برگردانید. The file was renamed but is part of a read only share. The original file was restored. - + این پرونده تغییر نام داده شده است اما بخشی از یک اشتراک فقط خواندنی است. پرونده اصلی برگردانده شده است. Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - + کد HTTP اشتباه توسط سرور برگردانده شد. 201 انتظار می رفت، اما "1% 2%" دریافت شد. @@ -2276,7 +2292,7 @@ It is not advisable to use it. File %1 cannot be uploaded because another file with the same name, differing only in case, exists - + پرونده 1% بارگذاری نمی شود زیرا پرونده دیگری با نام مشابه، که تنها در وضعیت متفاوت است، وجود دارد @@ -2286,7 +2302,7 @@ It is not advisable to use it. Local file changed during syncing. It will be resumed. - + پرونده محلی در طول همگام سازی تغییر کرد. این ادامه خواهد یافت. @@ -2297,7 +2313,7 @@ It is not advisable to use it. Upload of %1 exceeds the quota for the folder - + بارگذاری از 1% بیش از سهمیه برای پوشه است @@ -2320,17 +2336,17 @@ It is not advisable to use it. Unexpected return code from server (%1) - + کد بازگشت غیر منتظره از سرور (1%) Missing File ID from server - + فاقد شناسه پرونده از سرور Missing ETag from server - + فاقد ETag از سرور @@ -2338,12 +2354,12 @@ It is not advisable to use it. The file was edited locally but is part of a read only share. It is restored and your edit is in the conflict file. - + پرونده به طور محلی ویرایش شده اما بخشی از یک اشتراک فقط خواندنی است. این بازسازی شده و ویرایش شما در پرونده ناسازگاری است. Poll URL missing - + فاقد آدرس نظرسنجی @@ -2358,7 +2374,7 @@ It is not advisable to use it. The server did not acknowledge the last chunk. (No e-tag was present) - + سرور آخرین تکه را تایید نکرد. (برچسب الکترونیکی وجود نداشت) @@ -2401,7 +2417,7 @@ It is not advisable to use it. Local sync protocol - + پروتکل همگام سازی محلی @@ -2411,7 +2427,7 @@ It is not advisable to use it. Copy the activity list to the clipboard. - + فهرست فعالیت را در کلیپ بورد کپی کنید. @@ -2465,7 +2481,7 @@ It is not advisable to use it. Deselect remote folders you do not wish to synchronize. - + پوشه‌های از راه دور را که نمی خواهید همگام سازی کنید، انتخاب نکنید. @@ -2481,12 +2497,20 @@ It is not advisable to use it. No subfolders currently on the server. - + هیچ زیر پوشه ای در حال حاضر در سرور وجود ندارد. An error occurred while loading the list of sub folders. - + هنگام بارگذاری فهرست زیر پوشه ها خطایی روی داد. + + + + OCC::ServerNotificationHandler + + + Dismiss + پنهان کن @@ -2561,7 +2585,7 @@ It is not advisable to use it. Dialog - + گفتگو @@ -2586,27 +2610,27 @@ It is not advisable to use it. 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 - + پیوندهای عمومی @@ -2614,7 +2638,7 @@ It is not advisable to use it. Share NewDocument.odt - + اشتراک NewDocument.odt @@ -2629,12 +2653,12 @@ It is not advisable to use it. Enter a name to create a new public link... - + یک نام وارد کنید تا پیوند عمومی جدیدی بسازید... &Create new - + ایجاد جدید @@ -2649,12 +2673,12 @@ It is not advisable to use it. Link properties: - + خواص پیوند: Show file listing - + نمایش فهرست نویسی پرونده @@ -2664,102 +2688,98 @@ It is not advisable to use it. Anyone with the link has access to the file/folder - + هر کسی با پیوند به پرونده/پوشه دسترسی دارد - - + + P&assword protect - + رمز عبور محافظت می شود - + Password Protected محافظت شده توسط رمزعبور - + The file can not be shared because it was shared without sharing permission. - + پرونده نمی تواند به اشتراک گذاشته شود زیرا بدون مجوز به اشتراک گذاشته شده است. - - %1 link - - - - + Link shares have been disabled - + اشتراک های پیوند غیر فعال شده اند - + Create public link share - + اشتراک لینک عمومی بسازید - - + + Delete حذف - - - Open link in browser - - - - - Copy link to clipboard - - - Copy link to clipboard (direct download) - + Open link in browser + لینک را در مرورگر باز کنید + Copy link to clipboard + لینک را در کلیپ بورد کپی کنید + + + + Copy link to clipboard (direct download) + لینک را در کلیپ بورد کپی کنید (دانلود مستقیم) + + + Send link by email لینک را توسط ایمیل بفرست. - + Send link by email (direct download) - + لینک را با پست الکترونیکی ارسال کنید (دانلود مستقیم) - + 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 - + حذف پیوند اشتراک - + Public sh&aring requires a password - + اشتراک عمومی نیازمند رمز عبور است - + Please Set Password لطفا رمزعبور را تعیین کنید @@ -2769,7 +2789,7 @@ It is not advisable to use it. Share NewDocument.odt - + اشتراک NewDocument.odt @@ -2779,22 +2799,22 @@ It is not advisable to use it. <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>شما می تونید مردم را <a href="private link menu"><span style=" text-decoration: underline; color:#0000ff;"> با دادن لینک خصوصی به آن ها</span></a> به این پرونده یا پوشه مشترک هدایت کنید. </p></body></html> The item is not shared with any users or groups - + این مورد با هیچ کاربر یا گروهی به اشتراک گذاشته نشده است Open link in browser - + لینک را در مرورگر باز کنید Copy link to clipboard - + لینک را در کلیپ بورد کپی کنید @@ -2804,12 +2824,12 @@ It is not advisable to use it. No results for '%1' - + هیچ نتیجه ای برای '1%' وجود ندارد I shared something with you - + من چیزی را با شما به اشتراک گذاشتم @@ -2837,7 +2857,7 @@ It is not advisable to use it. ... - + ... @@ -2865,7 +2885,7 @@ It is not advisable to use it. You must sign in as user %1 - + شما باید به عنوان کاربر 1% وارد شوید @@ -2873,7 +2893,7 @@ It is not advisable to use it. %1 - Authenticate - + 1% - تایید اعتبار @@ -2883,12 +2903,12 @@ It is not advisable to use it. Reauthentication required - + تایید اعتبار مجدد مورد نیاز است Your session has expired. You need to re-login to continue to use the client. - + جلسه شما منقضی شده است. برای ادامه استفاده از سرویس گیرنده، مجددا باید وارد شوید. @@ -2902,22 +2922,22 @@ It is not advisable to use it. I shared something with you - + من چیزی را با شما به اشتراک گذاشتم Share... - + اشتراک گذاری... Copy private link to clipboard - + لینک خصوصی را در کلیپ بورد کپی کنید Send private link by email... - + لینک خصوصی را پست الکترونیکی ارسال کنید... @@ -2935,7 +2955,7 @@ It is not advisable to use it. Subject Alternative Names: - + نام های جایگزین موضوع: @@ -2985,7 +3005,7 @@ It is not advisable to use it. <h3>Fingerprints</h3> - + <h3>اثر انگشت</h3> @@ -3000,7 +3020,7 @@ It is not advisable to use it. <p><b>Note:</b> This certificate was manually approved</p> - + <p><b>توجه: </b>این گواهی‌نامه به طور دستی تایید شده است</p> @@ -3016,12 +3036,12 @@ It is not advisable to use it. This connection is encrypted using %1 bit %2. - + این اتصال با استفاده از 1% بیت 2% رمزگذاری شده است. No support for SSL session tickets/identifiers - + پشتیبانی برای جلسه SSL برچسب ها/شناسه ها وجود ندارد @@ -3032,7 +3052,8 @@ It is not advisable to use it. This connection is NOT secure as it is not encrypted. - + این اتصال امن نیست زیرا رمزگذاری نشده است. + @@ -3123,7 +3144,7 @@ It is not advisable to use it. CSync failed to load the journal file. The journal file is corrupted. - + CSync در بارگذاری پرونده مجله شکست خورد. پرونده مجله از بین رفته است. @@ -3133,7 +3154,7 @@ It is not advisable to use it. CSync fatal parameter error. - + خطای پارامتری وخیم CSync. @@ -3148,7 +3169,7 @@ It is not advisable to use it. CSync could not authenticate at the proxy. - + CSync نمی تواند پروکسی را تصدیق کند. @@ -3168,7 +3189,7 @@ It is not advisable to use it. A network connection timeout happened. - + وقفه اتصال شبکه روی داده است. @@ -3178,7 +3199,7 @@ It is not advisable to use it. The mounted folder is temporarily not available on the server - + پوشه نصب شده به طور موقت در سرور موجود نیست @@ -3193,53 +3214,53 @@ It is not advisable to use it. %1 (skipped due to earlier error, trying again in %2) - + 1% (به علت خطای قبلی از بین رفته است، دوباره در 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. - + فضای دیسک کم است: دانلودهایی که فضای آزاد را به کمتر از 1% کاهش می دهند رد می شوند. There is insufficient space available on the server for some uploads. - + برای بعضی از بارگذاری ها در سرور فضای کافی موجود نیست. @@ -3259,22 +3280,22 @@ It is not advisable to use it. CSync failed to access - + CSync برای دسترسی شکست خورد CSync failed to load or create the journal file. Make sure you have read and write permissions in the local sync folder. - + CSync در بارگذاری یا ساخت پرونده مجله شکست خورد. اطمینان حااصل کنید که شما اجازه خواندن و نوشتن در پوشه همگام سازی محلی را دارید. CSync failed due to unhandled permission denied. - + CSync به علت رد شدن مجوز نادرست شکست خورد. CSync tried to create a folder that already exists. - + CSync سعی کرد پوشه ای بسازد که از قبل موجود بود. @@ -3284,7 +3305,7 @@ It is not advisable to use it. Access is forbidden - + دسترسی ممنوع است @@ -3294,32 +3315,32 @@ It is not advisable to use it. 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%' هستند در این سیستم پرونده پشتیبانی نشده اند. The file name is a reserved name on this file system. - + نام پرونده یک نام رزرو شده در این سیستم پرونده است. Filename contains trailing spaces. - + نام پرونده شامل دنباله فضای خالی است. @@ -3329,12 +3350,12 @@ It is not advisable to use it. The filename cannot be encoded on your file system. - + نام پرونده در سیستم پرونده شما رمزگذاری نمی شود. Unresolved conflict. - + ناسازگاری حل نشده. @@ -3354,17 +3375,17 @@ It is not advisable to use it. Unable to read the blacklist from the local database - + نمی توان لیست سیاه را از پایگاه داده محلی خواند Unable to read from the sync journal. - + نمی توان از مجله همگام ساز خواند. Cannot open the sync journal - + نمی توان مجله همگام ساز را باز کرد @@ -3375,7 +3396,7 @@ It is not advisable to use it. Ignored because of the "choose what to sync" blacklist - + به علت لیست سیاه "انتخاب کنید چه چیزی همگام سازی شود" رد شد @@ -3406,7 +3427,7 @@ It is not advisable to use it. Move not allowed because %1 is read-only - + انتقال مجاز نیست زیرا 1% فقط خواندنی است @@ -3424,7 +3445,7 @@ It is not advisable to use it. Synchronisation Log - + Log همگام سازی @@ -3450,7 +3471,7 @@ It is not advisable to use it. <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 مجوز عمومی سراسری (GPL) نسخه 2.0 است. <br/> 2% و لوگوی 2% علائم تجاری 1% در ایالات متحده، سایر کشورها، یا هر دو را ثبت نام کرده اند. </p> @@ -3458,17 +3479,17 @@ It is not advisable to use it. 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. - + پرونده دانلود شده با checksum مطابق نیست، این ادامه خواهد یافت. @@ -3515,7 +3536,7 @@ It is not advisable to use it. Checking for changes in '%1' - + بررسی تغییرات در '1%' @@ -3530,7 +3551,7 @@ It is not advisable to use it. Open %1 in browser - + 1% را در مرورگر باز کنید @@ -3565,27 +3586,27 @@ It is not advisable to use it. Unsupported Server Version - + نسخه سرور پشتیبانی نشده The server on account %1 runs an old and unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - + سرور روی حساب 1% یک نسخه قدیمی و پشتیبانی نشده 2% را اجرا کرده است. استفاده از این مشتری با نسخه های سرور پشتیبانی نشده بررسی نشده و خطرناک است. مسئولیت با خود شماست. Disconnected - + قطع شده Disconnected from some accounts - + قطع شده از برخی حساب ها Disconnected from accounts: - + قطع شده از حساب ها: @@ -3600,68 +3621,68 @@ It is not advisable to use it. Account synchronization is disabled - + همگام سازی حساب غیر فعال است Synchronization is paused - + همگام سازی متوقف شده است 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... - + حساب جدید... @@ -3682,7 +3703,7 @@ It is not advisable to use it. Syncing %1 of %2 - + همگام سازی 1% از 2% @@ -3710,7 +3731,7 @@ It is not advisable to use it. <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>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> - + <p>نسخه 2%. برای اطلاعات بیشتر https://%4 را مشاهده کنید.</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 و دیگران. </small></p><p> Copyright ownCloud GmbH</p><p>تحت مجوز GNU مجوز عمومی سراسری (GPL) نسخه 2.0 <br/> ownCloud و لوگوی ownCloud علائم تجاری ownCloud GmbH در ایالت متحده، سایر کشورها، یا هر دو را ثبت کرده اند. </p> @@ -3739,17 +3760,17 @@ It is not advisable to use it. <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 - + درخواست تایید پیش از همگام سازی پوشه‌های بزرگتر از @@ -3760,7 +3781,7 @@ It is not advisable to use it. Ask for confirmation before synchronizing e&xternal storages - + درخواست تایید پیش از همگام سازی مخازن خارجی @@ -3785,7 +3806,7 @@ It is not advisable to use it. S&ync everything from server - + همگام سازی همه چیز از سرور @@ -3821,17 +3842,17 @@ It is not advisable to use it. Please switch to your browser to proceed. - + لطفا برای ادامه به مرورگر خود سوئیچ کنید. An error occured while connecting. Please try again. - + هنگام اتصال خطایی روی داد. لطفا دوباره امتحان کنید. Re-open Browser - + مرورگر را دوباره باز کنید @@ -3850,7 +3871,7 @@ It is not advisable to use it. Ser&ver Address - + آدرس سرور @@ -3892,37 +3913,37 @@ It is not advisable to use it. 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 - + چند وقت پیش @@ -3956,32 +3977,32 @@ It is not advisable to use it. %n year(s) - + n% سال %n month(s) - + n% ماه %n day(s) - + n% روز %n hour(s) - + n% ساعت %n minute(s) - + n% دقیقه %n second(s) - + n% ثانیه @@ -4007,7 +4028,7 @@ It is not advisable to use it. <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> Git روی 3%، 4% با استفاده از Qt 5%، 6% </small></p> @@ -4025,7 +4046,7 @@ It is not advisable to use it. Server version downloaded, copied changed local file into conflict file - + نسخه سرور دانلود شد، پرونده محلی تغییر یافته به پرونده ناسازگار کپی شد @@ -4055,7 +4076,7 @@ It is not advisable to use it. Updated local metadata - + فرا داده محلی به روز رسانی شده @@ -4097,7 +4118,7 @@ It is not advisable to use it. updating local metadata - + فرا داده محلی به روز رسانی شده @@ -4105,7 +4126,7 @@ It is not advisable to use it. Status undefined - + وضعیت نامشخص است @@ -4158,22 +4179,22 @@ It is not advisable to use it. 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? - + در راه اندازی پست الکترونیکی مشتری برای ساخت یک پیام جدید خطایی وجود دارد. شاید پست الکترونیکی مشتری پیش فرض پیکربندی نشده است؟ \ No newline at end of file diff --git a/translations/client_fi.ts b/translations/client_fi.ts index 1d1138c10..e05f18be5 100644 --- a/translations/client_fi.ts +++ b/translations/client_fi.ts @@ -412,44 +412,44 @@ 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 @@ -1404,7 +1404,7 @@ Kohteet, joiden poisto on sallittu, poistetaan, jos ne estävät kansion poistam - + Folder Kansio @@ -1419,27 +1419,32 @@ Kohteet, joiden poisto on sallittu, poistetaan, jos ne estävät kansion poistam - + + There were too many issues. Not all will be visible here. + + + + Copy the issues list to the clipboard. - + Copy Kopioi - + Time Aika - + File Tiedosto - + Issue @@ -2492,6 +2497,14 @@ Osoitteen käyttäminen ei ole suositeltavaa. Alikansioluetteloa ladatessa tapahtui virhe. + + OCC::ServerNotificationHandler + + + Dismiss + Hylkää + + OCC::SettingsDialog @@ -2670,99 +2683,95 @@ Osoitteen käyttäminen ei ole suositeltavaa. - - + + P&assword protect &Suojaa salasanalla - + Password Protected Salasanasuojattu - + The file can not be shared because it was shared without sharing permission. - - %1 link - - - - + Link shares have been disabled - + Create public link share - - + + Delete Poista - + Open link in browser - + Copy link to clipboard - + Copy link to clipboard (direct download) - + Send link by email Lähetä linkki sähköpostitse - + Send link by email (direct download) - + 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 - + Delete link share - + Public sh&aring requires a password &Julkinen jakaminen vaatii salasanan - + Please Set Password Aseta salasana diff --git a/translations/client_fr.ts b/translations/client_fr.ts index c436a0661..09b80e47a 100644 --- a/translations/client_fr.ts +++ b/translations/client_fr.ts @@ -412,44 +412,44 @@ OCC::ActivitySettings - - + + Server Activity Activité serveur - + Sync Protocol Activité de synchronisation - + Not Synced Fichiers non synchronisés - + Not Synced (%1) %1 is the number of not synced files. Non Synchronisé (%1) - + The server activity list has been copied to the clipboard. L'historique des opérations sur le serveur a été copié dans le presse-papier. - + The sync activity list has been copied to the clipboard. L'historique des opérations locales a été copié dans le presse-papier. - + The list of unsynced items has been copied to the clipboard. La liste des éléments non synchronisés a été copiée dans le presse-papier. - + Copied to clipboard Copié dans le presse-papier @@ -1414,7 +1414,7 @@ L'option "Autoriser suppression" permet de ne pas bloquer la supp - + Folder Dossier @@ -1429,27 +1429,32 @@ L'option "Autoriser suppression" permet de ne pas bloquer la supp Voir les fichiers ignorés - + + There were too many issues. Not all will be visible here. + De trop nombreuses questions se posent. Toutes n'apparaissent pas ici. + + + Copy the issues list to the clipboard. Copier la liste des anomalies dans le presse-papier. - + Copy Copier - + Time Heure - + File Fichier - + Issue Incident @@ -1730,8 +1735,8 @@ L'option "Autoriser suppression" permet de ne pas bloquer la supp A new update for %1 is about to be installed. The updater may ask for additional privileges during the process. - Une nouvelle mise à jour de %1 est sur le point d'être installée. -L'assistant peut demander des privilèges additionnels durant le processus. + 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. @@ -2503,6 +2508,14 @@ Il est déconseillé de l'utiliser. Une erreur est survenue lors du chargement de la liste des sous-dossiers. + + OCC::ServerNotificationHandler + + + Dismiss + Ignorer + + OCC::SettingsDialog @@ -2681,100 +2694,96 @@ Il est déconseillé de l'utiliser. Quiconque dispose du lien a accès aux fichiers/dossiers - - + + P&assword protect Protéger par mot de p&asse - + Password Protected Protégé par mot de passe - + 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. - - %1 link - lien %1 - - - + Link shares have been disabled Les partages par lien ont été désactivés - + Create public link share Créer une lien de partage public - - + + Delete Supprimer - + Open link in browser Ouvrir le lien dans le navigateur - + Copy link to clipboard Copier le lien vers le presse-papier - + Copy link to clipboard (direct download) Copier le lien vers le presse-papier (téléchargement direct) - + Send link by email Envoyer le lien par email - + Send link by email (direct download) Envoyer le lien par courriel (téléchargement direct) - + Confirm Link Share Deletion Confirmer la suppression du partage par lien - + <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> - + Cancel Anuler - + + Public link Lien public - + Delete link share Supprimer le partage par lien - + Public sh&aring requires a password Le p&artage public nécessite un mot de passe - + Please Set Password Veuillez choisir un mot de passe diff --git a/translations/client_gl.ts b/translations/client_gl.ts index bd0fbd54a..0315e96a1 100644 --- a/translations/client_gl.ts +++ b/translations/client_gl.ts @@ -412,44 +412,44 @@ 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) - + The server activity list has been copied to the clipboard. A lista de actividade do servidor copiouse no portapapeis. - + The sync activity list has been copied to the clipboard. A lista de actividade de sincronización foi copiada ao portapapeis. - + The list of unsynced items has been copied to the clipboard. A lista de elementos non sincronizados foi copiada ao portapapeis. - + Copied to clipboard Copiado no portapapeis. @@ -1402,7 +1402,7 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + Folder Cartafol @@ -1417,27 +1417,32 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + + There were too many issues. Not all will be visible here. + + + + Copy the issues list to the clipboard. - + Copy Copiar - + Time Hora - + File Ficheiro - + Issue @@ -2491,6 +2496,14 @@ Recomendámoslle que non o use. + + OCC::ServerNotificationHandler + + + Dismiss + Desbotar + + OCC::SettingsDialog @@ -2669,99 +2682,95 @@ Recomendámoslle que non o use. - - + + P&assword protect Contr&asinal de protección - + Password Protected Protexido con contrasinal - + 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. - - %1 link - - - - + Link shares have been disabled - + Create public link share - - + + Delete Eliminar - + Open link in browser - + Copy link to clipboard - + Copy link to clipboard (direct download) - + Send link by email Enviar a ligazón por correo - + Send link by email (direct download) - + 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 A comp&artición pública precisa de contrasinal - + Please Set Password Estabeleza o contrasinal diff --git a/translations/client_hu.ts b/translations/client_hu.ts index decde9b8c..30e0428f8 100644 --- a/translations/client_hu.ts +++ b/translations/client_hu.ts @@ -412,44 +412,44 @@ 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. - + Copied to clipboard Másolva a vágólapra @@ -1402,7 +1402,7 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + Folder Mappa @@ -1417,27 +1417,32 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + + There were too many issues. Not all will be visible here. + + + + Copy the issues list to the clipboard. - + Copy Másolás - + Time Idő - + File Fájl - + Issue @@ -2489,6 +2494,14 @@ It is not advisable to use it. + + OCC::ServerNotificationHandler + + + Dismiss + Elutasít + + OCC::SettingsDialog @@ -2667,99 +2680,95 @@ It is not advisable to use it. Bárki a hivatkozással hozzáférhet a fájlhoz/mappához - - + + P&assword protect J&elszóval védve - + Password Protected Jelszóval védett - + 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. - - %1 link - - - - + Link shares have been disabled - + Create public link share - - + + Delete Törlés - + Open link in browser - + Copy link to clipboard - + Copy link to clipboard (direct download) - + Send link by email Link küldése e-mail-ben - + Send link by email (direct download) - + 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 Mégsem - + + Public link - + Delete link share - + Public sh&aring requires a password Nyilvános me&gosztáshoz szükség van jelszóra - + Please Set Password Kérjük, állíts be egy jelszót diff --git a/translations/client_it.ts b/translations/client_it.ts index 50cd825f9..2da0e159c 100644 --- a/translations/client_it.ts +++ b/translations/client_it.ts @@ -412,44 +412,44 @@ OCC::ActivitySettings - - + + Server Activity Attività del server - + Sync Protocol Protocollo di sincronizzazione - + Not Synced Non sincronizzata - + Not Synced (%1) %1 is the number of not synced files. Non sincronizzata (%1) - + The server activity list has been copied to the clipboard. L'elenco di attività del server è stato copiato negli appunti. - + The sync activity list has been copied to the clipboard. L'elenco di attività di sincronizzazione è stato copiato negli appunti. - + The list of unsynced items has been copied to the clipboard. L'elenco di elementi non sincronizzati è stato copiato negli appunti. - + Copied to clipboard Copiato negli appunti @@ -1409,7 +1409,7 @@ Gli elementi per i quali è consentita l'eliminazione, saranno eliminati se - + Folder Cartella @@ -1424,27 +1424,32 @@ Gli elementi per i quali è consentita l'eliminazione, saranno eliminati se Mostra i file ignorati - + + There were too many issues. Not all will be visible here. + + + + Copy the issues list to the clipboard. Copia la lista dei problemi negli appunti. - + Copy Copia - + Time Ora - + File File - + Issue Problema @@ -2497,6 +2502,14 @@ Non è consigliabile utilizzarlo. Si è verificato un errore durante il caricamento dell'elenco delle sottocartelle. + + OCC::ServerNotificationHandler + + + Dismiss + Annulla + + OCC::SettingsDialog @@ -2675,99 +2688,95 @@ Non è consigliabile utilizzarlo. Chiunque sia in possesso del collegamento ha accesso al file/cartella - - + + P&assword protect Proteggi con p&assword - + Password Protected Protetta da password - + 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. - - %1 link - - - - + Link shares have been disabled La condivisione tramite link è stata disabilitata - + Create public link share Crea collegamento pubblico - - + + Delete Elimina - + Open link in browser Apri collegamento nel browser - + Copy link to clipboard Copia link negli appunti - + Copy link to clipboard (direct download) - + Send link by email Invia collegamento tramite email - + Send link by email (direct download) - + 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 Annulla - + + Public link Collegamento pubblico - + Delete link share - + Public sh&aring requires a password La condivisione pubblic&a richiede una password - + Please Set Password imposta la password diff --git a/translations/client_ja.ts b/translations/client_ja.ts index 4d509b624..8c3166546 100644 --- a/translations/client_ja.ts +++ b/translations/client_ja.ts @@ -412,44 +412,44 @@ 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 クリップボードにコピー @@ -1396,7 +1396,7 @@ Items where deletion is allowed will be deleted if they prevent a directory from List of issues - 課題一覧 + 問題一覧 @@ -1411,44 +1411,49 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + Folder フォルダー Show warnings - + 警告を表示 Show ignored files + 除外ファイルを表示 + + + + There were too many issues. Not all will be visible here. - + Copy the issues list to the clipboard. - + Copy コピー - + Time 時刻 - + File ファイル - + Issue - 課題 + 問題 @@ -2498,6 +2503,14 @@ It is not advisable to use it. サーバーからフォルダーのリスト取得時にエラーが発生しました。 + + OCC::ServerNotificationHandler + + + Dismiss + 閉じる + + OCC::SettingsDialog @@ -2673,102 +2686,98 @@ It is not advisable to use it. Anyone with the link has access to the file/folder - リンクを知っている人はファイル/フォルダにアクセスできます + リンクを知っている人はファイル/フォルダーにアクセスできます - - + + P&assword protect パスワード保護(&A) - + Password Protected パスワード保護 - + The file can not be shared because it was shared without sharing permission. 再共有権限がない共有ため、このファイルは共有できません。 - - %1 link - - - - + 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) - + 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 共有するにはパスワードが必要(&A) - + Please Set Password パスワードを入力してください diff --git a/translations/client_nb_NO.ts b/translations/client_nb_NO.ts index 42c272b95..46fa85f73 100644 --- a/translations/client_nb_NO.ts +++ b/translations/client_nb_NO.ts @@ -412,44 +412,44 @@ 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 @@ -1412,7 +1412,7 @@ Elementer hvor sletting er tillatt, vil bli slettet hvis de forhindrer fjerning - + Folder Mappe @@ -1427,27 +1427,32 @@ Elementer hvor sletting er tillatt, vil bli slettet hvis de forhindrer fjerning - + + There were too many issues. Not all will be visible here. + + + + Copy the issues list to the clipboard. - + Copy Kopier - + Time Tid - + File Fil - + Issue @@ -2501,6 +2506,14 @@ Det er ikke tilrådelig å bruke den. Det oppstod en feil ved lasting av liten med undermapper. + + OCC::ServerNotificationHandler + + + Dismiss + Forkast + + OCC::SettingsDialog @@ -2679,99 +2692,95 @@ Det er ikke tilrådelig å bruke den. Alle med linken har tilgang til filen/mappen - - + + P&assword protect P&assordbeskyttelse - + Password Protected Passordbeskyttet - + 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. - - %1 link - - - - + Link shares have been disabled - + Create public link share - - + + Delete Slett - + Open link in browser - + Copy link to clipboard - + Copy link to clipboard (direct download) - + Send link by email Send link i e-post - + Send link by email (direct download) - + 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 Offentlig lenke - + Delete link share - + Public sh&aring requires a password Offentlig d&eling krever et passord - + Please Set Password Sett passord diff --git a/translations/client_nl.ts b/translations/client_nl.ts index 7aff8a8c8..e543cecfe 100644 --- a/translations/client_nl.ts +++ b/translations/client_nl.ts @@ -88,7 +88,7 @@ Server replied "%1 %2" to "%3 %4" - + Server antwoordde "%1 %2" naar "%3 %4" @@ -258,7 +258,7 @@ Connecting to %1... - + Verbinden met %1... @@ -373,7 +373,7 @@ Maintenance mode - + Onderhoudsmodus @@ -412,44 +412,44 @@ OCC::ActivitySettings - - + + Server Activity Serveractiviteit - + Sync Protocol Synchronisatiegeschiedenis - + Not Synced Niet gesynchroniseerd - + Not Synced (%1) %1 is the number of not synced files. Niet gesynchroniseerd (%1) - + The server activity list has been copied to the clipboard. De server activiteitenlijst is gekopieerd naar het klembord. - + The sync activity list has been copied to the clipboard. De sync activiteitenlijst is gekopieerd naar het klembord. - + The list of unsynced items has been copied to the clipboard. De lijst met niet gesyncte objecten is gekopieerd naar het klembord. - + Copied to clipboard Gekopieerd naar het klembord @@ -1402,7 +1402,7 @@ Onderdelen die gewist mogen worden worden verwijderd als ze voorkomen dat een ma List of issues - + Lijst met problemen @@ -1413,48 +1413,53 @@ Onderdelen die gewist mogen worden worden verwijderd als ze voorkomen dat een ma <no filter> - + <no filter> - + Folder Map Show warnings - + Tonen waarschuwingen Show ignored files + Tonen genegeerde bestanden + + + + There were too many issues. Not all will be visible here. - + Copy the issues list to the clipboard. - + Copy Kopiëren - + Time Tijd - + File Bestand - + Issue - + Probleem @@ -1694,7 +1699,7 @@ Onderdelen die gewist mogen worden worden verwijderd als ze voorkomen dat een ma Error returned from the server: <em>%1</em> - + Fout gemeld door de server: <em>%1</em> @@ -1714,7 +1719,7 @@ Onderdelen die gewist mogen worden worden verwijderd als ze voorkomen dat een ma <h1>Login Error</h1><p>%1</p> - + <h1>Inlog fout<p>%1</p> @@ -2506,6 +2511,14 @@ We adviseren deze site niet te gebruiken. Er trad een fout op bij het laden van de lijst met submappen. + + OCC::ServerNotificationHandler + + + Dismiss + Terzijde leggen + + OCC::SettingsDialog @@ -2666,7 +2679,7 @@ We adviseren deze site niet te gebruiken. Link properties: - + Link eigenschappen: @@ -2684,99 +2697,95 @@ We adviseren deze site niet te gebruiken. Iedereen met de link heeft toegang tot het bestand of de map - - + + P&assword protect &Wachtwoord beveiligd - + Password Protected Wachtwoord beveiligd - + 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. - - %1 link - - - - + Link shares have been disabled - + Create public link share - - + + Delete Verwijderen - - - Open link in browser - - - - - Copy link to clipboard - - + Open link in browser + Openen link in browser + + + + Copy link to clipboard + Kopiëren link naar klembord + + + Copy link to clipboard (direct download) - + Send link by email Versturen links via e-mail - + Send link by email (direct download) - + 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 Annuleren - + + Public link Openbare Link - + Delete link share - + Public sh&aring requires a password Openbaar de&len vereist een wachtwoord - + Please Set Password Stel uw wachtwoord in @@ -2806,12 +2815,12 @@ We adviseren deze site niet te gebruiken. Open link in browser - + Openen link in browser Copy link to clipboard - + Kopiëren link naar klembord @@ -2826,7 +2835,7 @@ We adviseren deze site niet te gebruiken. I shared something with you - + Ik deelde iets met u @@ -2919,17 +2928,17 @@ We adviseren deze site niet te gebruiken. I shared something with you - + Ik deelde iets met u Share... - + Delen... Copy private link to clipboard - + Kopiëren privé-link naar klembord diff --git a/translations/client_pl.ts b/translations/client_pl.ts index 515acd5b2..5d1ce36e5 100644 --- a/translations/client_pl.ts +++ b/translations/client_pl.ts @@ -412,44 +412,44 @@ OCC::ActivitySettings - - + + Server Activity Aktywność serwera - + Sync Protocol Protokół synchronizacji - + Not Synced Niezsynchronizowany - + Not Synced (%1) %1 is the number of not synced files. Niezsynchronizowany (%1) - + The server activity list has been copied to the clipboard. Log aktywności serwera został skopiowany do schowka. - + The sync activity list has been copied to the clipboard. Przebieg synchronizacji został skopiowany do schowka. - + The list of unsynced items has been copied to the clipboard. Lista niezsynchronizowanych elementów została skopiowana do schowka - + Copied to clipboard Skopiuj do schowka @@ -1409,7 +1409,7 @@ Pozycje, dla których usuwanie jest dozwolone zostaną usunięte, jeżeli uprawn - + Folder Folder @@ -1424,27 +1424,32 @@ Pozycje, dla których usuwanie jest dozwolone zostaną usunięte, jeżeli uprawn - + + There were too many issues. Not all will be visible here. + + + + Copy the issues list to the clipboard. - + Copy Kopiuj - + Time Czas - + File Plik - + Issue @@ -2498,6 +2503,14 @@ Niezalecane jest jego użycie. Wystąpił błąd podczas wczytywania listy podfolderów + + OCC::ServerNotificationHandler + + + Dismiss + Anuluj + + OCC::SettingsDialog @@ -2676,99 +2689,95 @@ Niezalecane jest jego użycie. Każdy posiadający link ma dostęp do pliku/katalogu. - - + + P&assword protect - + Password Protected Zabezpieczone hasłem - + The file can not be shared because it was shared without sharing permission. - - %1 link - - - - + Link shares have been disabled - + Create public link share - - + + Delete Usuń - + Open link in browser - + Copy link to clipboard - + Copy link to clipboard (direct download) - + Send link by email Wyślij link mailem - + Send link by email (direct download) - + 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 Anuluj - + + Public link - + Delete link share - + Public sh&aring requires a password - + Please Set Password Proszę podać hasło diff --git a/translations/client_pt.ts b/translations/client_pt.ts index fad3f1776..9bceb1e06 100644 --- a/translations/client_pt.ts +++ b/translations/client_pt.ts @@ -412,44 +412,44 @@ OCC::ActivitySettings - - + + Server Activity Atividade do Servidor - + Sync Protocol Protocolo de Sincronização - + Not Synced Não Sincronizado - + Not Synced (%1) %1 is the number of not synced files. Não Sincronizado (%1) - + The server activity list has been copied to the clipboard. A lista de atividades do servidor foi copiada para a área de transferência. - + 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. - + 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. - + Copied to clipboard Copiado para a área de transferência @@ -1413,7 +1413,7 @@ Os itens onde é permitido a eliminação serão eliminados se estes impedirem a - + Folder Pasta @@ -1428,27 +1428,32 @@ Os itens onde é permitido a eliminação serão eliminados se estes impedirem a Mostrar ficheiros ignorados - + + There were too many issues. Not all will be visible here. + + + + 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 @@ -2502,6 +2507,14 @@ Não é aconselhada a sua utilização. Ocorreu um erro ao carregar a lista das sub pastas. + + OCC::ServerNotificationHandler + + + Dismiss + Rejeitar + + OCC::SettingsDialog @@ -2680,99 +2693,95 @@ Não é aconselhada a sua utilização. Qualquer pessoa com a hiperligação terá acesso ao ficheiro/pasta - - + + P&assword protect Protegida por senha - + Password Protected Protegido com Senha - + 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. - - %1 link - - - - + Link shares have been disabled - + Create public link share - - + + Delete Eliminar - + Open link in browser - + Copy link to clipboard - + Copy link to clipboard (direct download) - + Send link by email Enviar hiperligação por e-mail - + Send link by email (direct download) - + 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 Hiperligação Pública - + Delete link share - + Public sh&aring requires a password A pa&rtilha pública requer uma palavra-passe: - + Please Set Password Por favor, Definir Senha diff --git a/translations/client_pt_BR.ts b/translations/client_pt_BR.ts index 6dbcd008f..886cec6a2 100644 --- a/translations/client_pt_BR.ts +++ b/translations/client_pt_BR.ts @@ -412,44 +412,44 @@ OCC::ActivitySettings - - + + Server Activity Atividade do Servidor - + Sync Protocol Protocolo de Sincronização - + Not Synced Não Sincronizado - + Not Synced (%1) %1 is the number of not synced files. Não sincronizada (%1) - + The server activity list has been copied to the clipboard. A lista de atividades do servidor tem sido copiados para o clipboard. - + The sync activity list has been copied to the clipboard. A lista de atividades do servidor foi copiada para a área de transferência. - + 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. - + Copied to clipboard Copiado para área de transferência @@ -1412,7 +1412,7 @@ Itens onde a eliminação é permitida serão excluídos se eles evitarem que um - + Folder Pasta @@ -1427,27 +1427,32 @@ Itens onde a eliminação é permitida serão excluídos se eles evitarem que um Mostrar arquivos ignorados - + + There were too many issues. Not all will be visible here. + Havia muitos problemas. Nem todos serão visíveis aqui. + + + Copy the issues list to the clipboard. Copie a lista de problemas para a área de transferência. - + Copy Copiar - + Time Horário - + File Arquivo - + Issue Problemas @@ -2499,6 +2504,14 @@ It is not advisable to use it. Ocorreu um erro enquanto carregava a lista de subpastas. + + OCC::ServerNotificationHandler + + + Dismiss + Dispensar + + OCC::SettingsDialog @@ -2677,99 +2690,95 @@ It is not advisable to use it. Qualquer pessoa com o link tem acesso ao arquivo/pasta - - + + P&assword protect S&enha de proteção - + Password Protected Protegido por Senha - + 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. - - %1 link - %1 linque - - - + Link shares have been disabled Os compartilhamentos de linque foram desativados - + Create public link share Criar linque de compartilhamento público - - + + Delete Excluir - + Open link in browser Abrir linque no navegador - + Copy link to clipboard Copiar o linque para a área de transferência - + Copy link to clipboard (direct download) Copiar o linque para a área de transferência (download direto) - + Send link by email Enviar linque por e-mail - + Send link by email (direct download) Enviar linque por e-mail (download direto) - + Confirm Link Share Deletion Confirmar o Link de Eliminação de Compartilhamento - + <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> - + Cancel Cancelar - + + Public link Linque público - + Delete link share Excluir linque de compartilhamento - + Public sh&aring requires a password Comp&artilhamento público requer uma senha - + Please Set Password Por favor, Definir senha diff --git a/translations/client_ru.ts b/translations/client_ru.ts index ba0d719f6..266b36c5d 100644 --- a/translations/client_ru.ts +++ b/translations/client_ru.ts @@ -412,44 +412,44 @@ 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 Скопировано в буфер обмена @@ -1410,7 +1410,7 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + Folder Папка @@ -1425,27 +1425,32 @@ Items where deletion is allowed will be deleted if they prevent a directory from Показать игнорируемые файлы - + + There were too many issues. Not all will be visible here. + Было слишком много проблем. Не все будут видны здесь. + + + Copy the issues list to the clipboard. Скопировать список проблем в буфер обмена. - + Copy Копировать - + Time Время - + File Файл - + Issue Проблема @@ -2499,6 +2504,14 @@ It is not advisable to use it. Произошла ошибка во время загрузки списка подпапок. + + OCC::ServerNotificationHandler + + + Dismiss + Убрать + + OCC::SettingsDialog @@ -2677,99 +2690,95 @@ It is not advisable to use it. Каждый, у кого есть эта ссылка, имеет доступ к файлу/каталогу - - + + P&assword protect Защитить паролем - + Password Protected Защищено Паролем - + The file can not be shared because it was shared without sharing permission. Невозможно предоставить общий доступ к файлу: нет разрешения на предоставление общего доступа. - - %1 link - Ссылка на %1 - - - + 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) Отправить ссылку по почте (прямое скачивание) - + 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 Удалить доступ по ссылке - + Public sh&aring requires a password Публичные ссылки требуют пароля - + Please Set Password Пожалуйста, установите пароль diff --git a/translations/client_sk.ts b/translations/client_sk.ts index 1db2d935f..ac2ed2044 100644 --- a/translations/client_sk.ts +++ b/translations/client_sk.ts @@ -412,44 +412,44 @@ 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 @@ -1402,7 +1402,7 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + Folder Priečinok @@ -1417,27 +1417,32 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + + There were too many issues. Not all will be visible here. + + + + Copy the issues list to the clipboard. - + Copy Kopírovať - + Time Čas - + File Súbor - + Issue @@ -2491,6 +2496,14 @@ Nie je vhodné ju používať. + + OCC::ServerNotificationHandler + + + Dismiss + Odmietnuť + + OCC::SettingsDialog @@ -2669,99 +2682,95 @@ Nie je vhodné ju používať. - - + + P&assword protect Ch&rániť heslom - + Password Protected Chránené 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. - - %1 link - - - - + 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) - + 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 diff --git a/translations/client_sl.ts b/translations/client_sl.ts index 8911d5ab7..52657ec18 100644 --- a/translations/client_sl.ts +++ b/translations/client_sl.ts @@ -412,44 +412,44 @@ 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 @@ -1413,7 +1413,7 @@ Predmeti na mestu, kjer je brisanje dovoljeno, bodo izbisani, v kolikor zaradi n - + Folder Mapa @@ -1428,27 +1428,32 @@ Predmeti na mestu, kjer je brisanje dovoljeno, bodo izbisani, v kolikor zaradi n Pokaži prezrte datoteke - + + There were too many issues. Not all will be visible here. + + + + Copy the issues list to the clipboard. Kopiraj seznam napak v odložišče. - + Copy Kopiraj - + Time Čas - + File Datoteka - + Issue Napaka @@ -2502,6 +2507,14 @@ Uporaba ni priporočljiva. Prišlo je do napake med nalaganjem seznama podrejenih map. + + OCC::ServerNotificationHandler + + + Dismiss + Opusti + + OCC::SettingsDialog @@ -2680,99 +2693,95 @@ Uporaba ni priporočljiva. Vsak, ki ima povezavo ima dostop do datoteke ali mape - - + + P&assword protect &Zaščiti z geslom - + Password Protected Zaščiteno 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. - - %1 link - Povezava %1 - - - + 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) - + 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 - + Public sh&aring requires a password Javna omogočanje &souporabe zahteva geslo - + Please Set Password Določite geslo diff --git a/translations/client_sr.ts b/translations/client_sr.ts index 2f2b30b57..81442142b 100644 --- a/translations/client_sr.ts +++ b/translations/client_sr.ts @@ -412,44 +412,44 @@ 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 Копирано у клипборд @@ -1402,7 +1402,7 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + Folder фасцикла @@ -1417,27 +1417,32 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + + There were too many issues. Not all will be visible here. + + + + Copy the issues list to the clipboard. - + Copy Копирај - + Time време - + File фајл - + Issue @@ -2491,6 +2496,14 @@ It is not advisable to use it. + + OCC::ServerNotificationHandler + + + Dismiss + Откажи + + OCC::SettingsDialog @@ -2669,99 +2682,95 @@ It is not advisable to use it. - - + + P&assword protect &Заштићено лозинком - + Password Protected Заштићено лозинком - + The file can not be shared because it was shared without sharing permission. Фајл се не може делити јер је подељен без дозволе за поновно дељење. - - %1 link - - - - + 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) - + 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 Поставите лозинку diff --git a/translations/client_sv.ts b/translations/client_sv.ts index 9d7462f6a..6381d2f22 100644 --- a/translations/client_sv.ts +++ b/translations/client_sv.ts @@ -412,44 +412,44 @@ 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 @@ -1408,7 +1408,7 @@ Objekt som tillåter radering kommer tas bort om de förhindrar en mapp att tas - + Folder Mapp @@ -1423,27 +1423,32 @@ Objekt som tillåter radering kommer tas bort om de förhindrar en mapp att tas Visa ignorerade filer - + + There were too many issues. Not all will be visible here. + + + + Copy the issues list to the clipboard. Kopiera fellistan till klippbordet. - + Copy Kopiera - + Time Tid - + File Fil - + Issue Fel @@ -2497,6 +2502,14 @@ Det är inte lämpligt använda den. Ett fel uppstod när listan för submappar laddades. + + OCC::ServerNotificationHandler + + + Dismiss + Avfärda + + OCC::SettingsDialog @@ -2675,99 +2688,95 @@ Det är inte lämpligt använda den. Vem som helst med länken kan komma åt filen eller mappen - - + + P&assword protect L&ösenordsskydda - + Password Protected Lösenordsskyddad - + The file can not be shared because it was shared without sharing permission. Filen kan inte delas eftersom den delades utan delningsrättigheter. - - %1 link - %1 länk - - - + 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) - + 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 - + Public sh&aring requires a password Publik d&elning kräver lösenord - + Please Set Password Var vänlig sätt lösenord diff --git a/translations/client_th.ts b/translations/client_th.ts index de8224547..30d42c15e 100644 --- a/translations/client_th.ts +++ b/translations/client_th.ts @@ -412,44 +412,44 @@ 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 คัดลอกไปยังคลิปบอร์ด @@ -1415,7 +1415,7 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + Folder แฟ้มเอกสาร @@ -1430,27 +1430,32 @@ Items where deletion is allowed will be deleted if they prevent a directory from แสดงไฟล์ที่ถูกเพิกเฉย - + + There were too many issues. Not all will be visible here. + มีปัญหามากเกินไป ทั้งหมดจะไม่ปรากฏที่นี่ + + + Copy the issues list to the clipboard. คัดลอกรายการปัญหาไปยังคลิปบอร์ด - + Copy คัดลอก - + Time เวลา - + File ไฟล์ - + Issue ปัญหา @@ -2503,6 +2508,14 @@ It is not advisable to use it. เกิดข้อผิดพลาดขณะโหลดรายชื่อของโฟลเดอร์ย่อย + + OCC::ServerNotificationHandler + + + Dismiss + ยกเลิก + + OCC::SettingsDialog @@ -2681,99 +2694,95 @@ It is not advisable to use it. ทุกคนที่มีลิงค์สามารถเข้าถึงไฟล์หรือโฟลเดอร์ได้ - - + + P&assword protect ป้องกันด้วยรหัสผ่าน - + Password Protected รหัสผ่านถูกป้องกันแล้ว - + The file can not be shared because it was shared without sharing permission. ไม่สามารถแชร์ไฟล์เพราะไม่ได้รับอนุญาต - - %1 link - %1 ลิงค์ - - - + 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) ส่งลิงค์ทางอีเมล (ดาวน์โหลดโดยตรง) - + 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 ลบลิงค์ที่แชร์ - + Public sh&aring requires a password การแชร์สาธารณะจำเป็นต้องมีรหัสผ่าน - + Please Set Password กรุณาตั้งรหัสผ่าน diff --git a/translations/client_tr.ts b/translations/client_tr.ts index fce128ea4..4e17dc223 100644 --- a/translations/client_tr.ts +++ b/translations/client_tr.ts @@ -412,44 +412,44 @@ 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) - + The server activity list has been copied to the clipboard. Sunucu etkinlik listesi panoya kopyalandı. - + The sync activity list has been copied to the clipboard. Eşitleme etkinlik listesi panoya kopyalandı. - + The list of unsynced items has been copied to the clipboard. Eşitlenmemiş ögelerin listesi panoya kopyalandı. - + Copied to clipboard Panoya kopyalandı @@ -1404,7 +1404,7 @@ Bir dizinin silinmesine engel oluyorsa silmeye izin verilen yerlerdeki ögeler s - + Folder Klasör @@ -1419,27 +1419,32 @@ Bir dizinin silinmesine engel oluyorsa silmeye izin verilen yerlerdeki ögeler s - + + There were too many issues. Not all will be visible here. + + + + Copy the issues list to the clipboard. - + Copy Kopyala - + Time Zaman - + File Dosya - + Issue @@ -2492,6 +2497,14 @@ Kullanmanız önerilmez. Alt klasör listesi alınırken bir hata oluştu. + + OCC::ServerNotificationHandler + + + Dismiss + İptal et + + OCC::SettingsDialog @@ -2670,99 +2683,95 @@ Kullanmanız önerilmez. Dosya/klasör linkine sahip Herkes erişebilir - - + + P&assword protect &Parola koruması - + Password Protected Parola Korumalı - + 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ış. - - %1 link - - - - + Link shares have been disabled - + Create public link share - - + + Delete Sil - + Open link in browser - + Copy link to clipboard - + Copy link to clipboard (direct download) - + Send link by email Bağlantıyı e-posta ile gönder - + Send link by email (direct download) - + 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 İptal - + + Public link - + Delete link share - + Public sh&aring requires a password Herkese &açık paylaşım için parola gerekir - + Please Set Password Lütfen Parola Atayın diff --git a/translations/client_uk.ts b/translations/client_uk.ts index fe97e9135..15068e19d 100644 --- a/translations/client_uk.ts +++ b/translations/client_uk.ts @@ -412,44 +412,44 @@ 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 Скопійовано в буфер обміну @@ -1402,7 +1402,7 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + Folder Тека @@ -1417,27 +1417,32 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + + There were too many issues. Not all will be visible here. + + + + Copy the issues list to the clipboard. - + Copy Копіювати - + Time Час - + File Файл - + Issue @@ -2490,6 +2495,14 @@ It is not advisable to use it. + + OCC::ServerNotificationHandler + + + Dismiss + Припинити + + OCC::SettingsDialog @@ -2668,99 +2681,95 @@ It is not advisable to use it. Будь хто з цім посиланням має доступ до файлу/теки - - + + P&assword protect &Захистити паролем - + Password Protected Захищено паролем - + The file can not be shared because it was shared without sharing permission. Цей файл неможливо поширити, бо ним поділилися без права на поширення. - - %1 link - - - - + 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) - + 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 diff --git a/translations/client_zh_CN.ts b/translations/client_zh_CN.ts index 962e0b0a0..531d9c5f0 100644 --- a/translations/client_zh_CN.ts +++ b/translations/client_zh_CN.ts @@ -412,44 +412,44 @@ 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 复制到剪贴板 @@ -1413,7 +1413,7 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + Folder 文件夹 @@ -1428,27 +1428,32 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + + There were too many issues. Not all will be visible here. + + + + Copy the issues list to the clipboard. - + Copy 复制 - + Time 时间 - + File 文件 - + Issue @@ -1770,7 +1775,7 @@ for additional privileges during the process. No updates available. Your installation is at the latest version. - 亲,你使用的已经是最新的版本了 + 没有可用更新。您的安装已为最新版本。 @@ -2501,6 +2506,14 @@ It is not advisable to use it. 载入子文件夹列表时发生错误。 + + OCC::ServerNotificationHandler + + + Dismiss + 忽略 + + OCC::SettingsDialog @@ -2679,99 +2692,95 @@ It is not advisable to use it. 任何查看此链接的人都可以访问文件、文件夹 - - + + P&assword protect 密码保护(&a) - + Password Protected 密码保护 - + The file can not be shared because it was shared without sharing permission. 未分配共享权限,无法共享文件。 - - %1 link - %1 的链接 - - - + 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) - + 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 请设置密码 diff --git a/translations/client_zh_TW.ts b/translations/client_zh_TW.ts index cdc167a94..632ab7560 100644 --- a/translations/client_zh_TW.ts +++ b/translations/client_zh_TW.ts @@ -412,44 +412,44 @@ 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 複製至剪貼簿中 @@ -1404,7 +1404,7 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + Folder 資料夾 @@ -1419,27 +1419,32 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + + There were too many issues. Not all will be visible here. + + + + Copy the issues list to the clipboard. - + Copy 複製 - + Time 時間 - + File 檔案 - + Issue @@ -2493,6 +2498,14 @@ It is not advisable to use it. + + OCC::ServerNotificationHandler + + + Dismiss + + + OCC::SettingsDialog @@ -2671,99 +2684,95 @@ It is not advisable to use it. - - + + P&assword protect &密碼保護 - + Password Protected 密碼保護 - + The file can not be shared because it was shared without sharing permission. 這個檔案無法被分享,並沒有分享此檔案的權限。 - - %1 link - - - - + 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) - + 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 請設定密碼