diff --git a/CMakeLists.txt b/CMakeLists.txt index 20a0de8be7..116dca4cc7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -153,8 +153,6 @@ if(APPLE) set( SOCKETAPI_TEAM_IDENTIFIER_PREFIX "" CACHE STRING "SocketApi prefix (including a following dot) that must match the codesign key's TeamIdentifier/Organizational Unit" ) endif() -find_package(OpenSSL 1.0.0 REQUIRED) - if(APPLE) find_package(Sparkle) endif(APPLE) @@ -178,9 +176,11 @@ endif() find_package(ZLIB) -configure_file(config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) +if (NOT DEFINED APPLICATION_ICON_NAME) + set(APPLICATION_ICON_NAME ${APPLICATION_SHORTNAME}) +endif() -configure_file(test/test_journal.db "${CMAKE_BINARY_DIR}/test/test_journal.db" COPYONLY) +configure_file(config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) include(OwnCloudCPack.cmake) diff --git a/ChangeLog b/ChangeLog index 98f393db46..0e475fa594 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,17 +2,22 @@ ChangeLog ========= version 2.4.0 (2017-0X-XX) +* OAuth2 authentication support * Sharing: Add support for multiple public link shares (#5655) * Sharing: Add option to copy/email direct links (#5627) * Sharing: Show warning that links are public +* Sharing: Many UI improvements * Wizards: Never propose an existing folder for syncing (#5597) +* Wizard: Don't show last page anymore, go to settings directly (#5726) * Settings Dialog: Display the user server avatar * Selective Sync: Open sub folder context menu (#5596) +* Selective Sync: Skip excluded folders when reading db +* Selective Sync: SelectiveSync: Remove local files of unselected folder despite other modified files (#5783) * Exclude list: remove .htaccess * Detect maintenance mode (#4485) * Discovery: Increase the MAX_DEPTH and show deep folders as ignored * Downloads: Remove empty temporary if disk space full (#5746) -* Wizard: Don't show not-so-useful result page (#5726) +* Downloads: Re-trigger folder discovery on 404 * When creating explorer favorite use more specific windows functions (#5690) * Added owncloudcmd bandwidth limit parameter (#5707) * AccountSettings: Sync with clean discovery on Ctrl-F6 (#5666) @@ -22,6 +27,9 @@ version 2.4.0 (2017-0X-XX) * Switch 3rdparty/json usage to Qt5's QJson (#5710) * Reduce memory usage * Documentation improvements +* Logging improvements (with Qt logging categories), new --logdebug parameter +* Harmonize source code style with clang-format +* Crash fixes version 2.3.2 (2017-05-08) * Fix more crashes (thanks to everyone submitting to our crash reporter!) diff --git a/Jenkinsfile b/Jenkinsfile index 01101cc3a8..f8d6f9dd72 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,5 +1,12 @@ #!groovy +// +// We now run the tests in Debug mode so that ASSERTs are triggered. +// Ideally we should run the tests in both Debug and Release so we catch +// all possible error combinations. +// See also the top comment in syncenginetestutils.h +// + node('CLIENT') { stage 'Checkout' checkout scm @@ -9,17 +16,17 @@ node('CLIENT') { sh '''rm -rf build mkdir build cd build - cmake -DUNIT_TESTING=1 -DBUILD_WITH_QT4=OFF .. + cmake -DCMAKE_BUILD_TYPE="Debug" -DUNIT_TESTING=1 -DWITH_TESTING=1 -DBUILD_WITH_QT4=OFF .. make -j4 - ctest --output-on-failure''' + ctest -V --output-on-failure''' stage 'Qt5 - clang' sh '''rm -rf build mkdir build cd build - cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DUNIT_TESTING=1 -DBUILD_WITH_QT4=OFF .. + cmake -DCMAKE_BUILD_TYPE="Debug" -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DUNIT_TESTING=1 -DWITH_TESTING=1 -DBUILD_WITH_QT4=OFF .. make -j4 - ctest --output-on-failure''' + ctest -V --output-on-failure''' stage 'Win32' @@ -37,6 +44,8 @@ node('CLIENT') { ctest . ''' } + + // Stage 'macOS' TODO } diff --git a/OWNCLOUD.cmake b/OWNCLOUD.cmake index 4022212e62..1a818a4d05 100644 --- a/OWNCLOUD.cmake +++ b/OWNCLOUD.cmake @@ -3,6 +3,7 @@ set( APPLICATION_EXECUTABLE "owncloud" ) set( APPLICATION_DOMAIN "owncloud.com" ) set( APPLICATION_VENDOR "ownCloud" ) set( APPLICATION_UPDATE_URL "https://updates.owncloud.com/client/" CACHE string "URL for updater" ) +set( APPLICATION_ICON_NAME "owncloud" ) set( THEME_CLASS "ownCloudTheme" ) set( APPLICATION_REV_DOMAIN "com.owncloud.desktopclient" ) diff --git a/binary b/binary index 741b49156b..1818b48380 160000 --- a/binary +++ b/binary @@ -1 +1 @@ -Subproject commit 741b49156bb7b55347c20228d6ff9d4aa493cd91 +Subproject commit 1818b48380f4fc50d482b980e5ec0d347f896a70 diff --git a/cmake/modules/FindOpenSSLCross.cmake b/cmake/modules/FindOpenSSLCross.cmake deleted file mode 100644 index 0191714355..0000000000 --- a/cmake/modules/FindOpenSSLCross.cmake +++ /dev/null @@ -1,306 +0,0 @@ -# - Try to find the OpenSSL encryption library -# Once done this will define -# -# OPENSSL_ROOT_DIR - Set this variable to the root installation of OpenSSL -# -# Read-Only variables: -# OPENSSL_FOUND - system has the OpenSSL library -# OPENSSL_INCLUDE_DIR - the OpenSSL include directory -# OPENSSL_LIBRARIES - The libraries needed to use OpenSSL -# OPENSSL_VERSION - This is set to $major.$minor.$revision$path (eg. 0.9.8s) - -#============================================================================= -# Copyright 2006-2009 Kitware, Inc. -# Copyright 2006 Alexander Neundorf -# Copyright 2009-2011 Mathieu Malaterre -# -# Distributed under the OSI-approved BSD License (the "License"); -# see accompanying file Copyright.txt for details. -# -# This software is distributed WITHOUT ANY WARRANTY; without even the -# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the License for more information. -#============================================================================= -# (To distribute this file outside of CMake, substitute the full -# License text for the above reference.) - -if (UNIX) - find_package(PkgConfig QUIET) - pkg_check_modules(_OPENSSL QUIET openssl) -endif (UNIX) - -# http://www.slproweb.com/products/Win32OpenSSL.html -SET(_OPENSSL_ROOT_HINTS - $ENV{OPENSSL_ROOT_DIR} - ${OPENSSL_ROOT_DIR} - "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (32-bit)_is1;Inno Setup: App Path]" - "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (64-bit)_is1;Inno Setup: App Path]" - ) -SET(_OPENSSL_ROOT_PATHS - "$ENV{PROGRAMFILES}/OpenSSL" - "$ENV{PROGRAMFILES}/OpenSSL-Win32" - "$ENV{PROGRAMFILES}/OpenSSL-Win64" - "C:/OpenSSL/" - "C:/OpenSSL-Win32/" - "C:/OpenSSL-Win64/" - ) -SET(_OPENSSL_ROOT_HINTS_AND_PATHS - HINTS ${_OPENSSL_ROOT_HINTS} - PATHS ${_OPENSSL_ROOT_PATHS} - ) - -FIND_PATH(OPENSSL_INCLUDE_DIR - NAMES - openssl/ssl.h - HINTS - ${_OPENSSL_INCLUDEDIR} - ${_OPENSSL_ROOT_HINTS_AND_PATHS} - PATH_SUFFIXES - include -) - -IF(WIN32 AND NOT CYGWIN) - # MINGW should go here too - IF(MSVC) - # /MD and /MDd are the standard values - if someone wants to use - # others, the libnames have to change here too - # use also ssl and ssleay32 in debug as fallback for openssl < 0.9.8b - # TODO: handle /MT and static lib - # In Visual C++ naming convention each of these four kinds of Windows libraries has it's standard suffix: - # * MD for dynamic-release - # * MDd for dynamic-debug - # * MT for static-release - # * MTd for static-debug - - # Implementation details: - # We are using the libraries located in the VC subdir instead of the parent directory eventhough : - # libeay32MD.lib is identical to ../libeay32.lib, and - # ssleay32MD.lib is identical to ../ssleay32.lib - FIND_LIBRARY(LIB_EAY_DEBUG - NAMES - libeay32MDd - libeay32 - ${_OPENSSL_ROOT_HINTS_AND_PATHS} - PATH_SUFFIXES - "lib" - "VC" - "lib/VC" - ) - - FIND_LIBRARY(LIB_EAY_RELEASE - NAMES - libeay32MD - libeay32 - ${_OPENSSL_ROOT_HINTS_AND_PATHS} - PATH_SUFFIXES - "lib" - "VC" - "lib/VC" - ) - - FIND_LIBRARY(SSL_EAY_DEBUG - NAMES - ssleay32MDd - ssleay32 - ssl - ${_OPENSSL_ROOT_HINTS_AND_PATHS} - PATH_SUFFIXES - "lib" - "VC" - "lib/VC" - ) - - FIND_LIBRARY(SSL_EAY_RELEASE - NAMES - ssleay32MD - ssleay32 - ssl - ${_OPENSSL_ROOT_HINTS_AND_PATHS} - PATH_SUFFIXES - "lib" - "VC" - "lib/VC" - ) - - if( CMAKE_CONFIGURATION_TYPES OR CMAKE_BUILD_TYPE ) - set( OPENSSL_LIBRARIES - optimized ${SSL_EAY_RELEASE} debug ${SSL_EAY_DEBUG} - optimized ${LIB_EAY_RELEASE} debug ${LIB_EAY_DEBUG} - ) - else() - set( OPENSSL_LIBRARIES ${SSL_EAY_RELEASE} ${LIB_EAY_RELEASE} ) - endif() - MARK_AS_ADVANCED(SSL_EAY_DEBUG SSL_EAY_RELEASE) - MARK_AS_ADVANCED(LIB_EAY_DEBUG LIB_EAY_RELEASE) - ELSEIF(MINGW) - # same player, for MingW - FIND_LIBRARY(LIB_EAY - NAMES - libeay32 - crypto - ${_OPENSSL_ROOT_HINTS_AND_PATHS} - PATH_SUFFIXES - "lib" - "lib/MinGW" - ) - - FIND_LIBRARY(SSL_EAY - NAMES - ssleay32 - ssl - ${_OPENSSL_ROOT_HINTS_AND_PATHS} - PATH_SUFFIXES - "lib" - "lib/MinGW" - ) - - MARK_AS_ADVANCED(SSL_EAY LIB_EAY) - set( OPENSSL_LIBRARIES ${SSL_EAY} ${LIB_EAY} ) - ELSE(MSVC) - # Not sure what to pick for -say- intel, let's use the toplevel ones and hope someone report issues: - FIND_LIBRARY(LIB_EAY - NAMES - libeay32 - HINTS - ${_OPENSSL_LIBDIR} - ${_OPENSSL_ROOT_HINTS_AND_PATHS} - PATH_SUFFIXES - lib - ) - - FIND_LIBRARY(SSL_EAY - NAMES - ssleay32 - HINTS - ${_OPENSSL_LIBDIR} - ${_OPENSSL_ROOT_HINTS_AND_PATHS} - PATH_SUFFIXES - lib - ) - - MARK_AS_ADVANCED(SSL_EAY LIB_EAY) - set( OPENSSL_LIBRARIES ${SSL_EAY} ${LIB_EAY} ) - ENDIF(MSVC) -ELSE(WIN32 AND NOT CYGWIN) - - FIND_LIBRARY(OPENSSL_SSL_LIBRARY - NAMES - ssl - ssleay32 - ssleay32MD - HINTS - ${_OPENSSL_LIBDIR} - ${_OPENSSL_ROOT_HINTS_AND_PATHS} - PATH_SUFFIXES - lib - ) - - FIND_LIBRARY(OPENSSL_CRYPTO_LIBRARY - NAMES - crypto - HINTS - ${_OPENSSL_LIBDIR} - ${_OPENSSL_ROOT_HINTS_AND_PATHS} - PATH_SUFFIXES - lib - ) - - MARK_AS_ADVANCED(OPENSSL_CRYPTO_LIBRARY OPENSSL_SSL_LIBRARY) - - # compat defines - SET(OPENSSL_SSL_LIBRARIES ${OPENSSL_SSL_LIBRARY}) - SET(OPENSSL_CRYPTO_LIBRARIES ${OPENSSL_CRYPTO_LIBRARY}) - - SET(OPENSSL_LIBRARIES ${OPENSSL_SSL_LIBRARY} ${OPENSSL_CRYPTO_LIBRARY}) - -ENDIF(WIN32 AND NOT CYGWIN) - -function(from_hex HEX DEC) - string(TOUPPER "${HEX}" HEX) - set(_res 0) - string(LENGTH "${HEX}" _strlen) - - while (_strlen GREATER 0) - math(EXPR _res "${_res} * 16") - string(SUBSTRING "${HEX}" 0 1 NIBBLE) - string(SUBSTRING "${HEX}" 1 -1 HEX) - if (NIBBLE STREQUAL "A") - math(EXPR _res "${_res} + 10") - elseif (NIBBLE STREQUAL "B") - math(EXPR _res "${_res} + 11") - elseif (NIBBLE STREQUAL "C") - math(EXPR _res "${_res} + 12") - elseif (NIBBLE STREQUAL "D") - math(EXPR _res "${_res} + 13") - elseif (NIBBLE STREQUAL "E") - math(EXPR _res "${_res} + 14") - elseif (NIBBLE STREQUAL "F") - math(EXPR _res "${_res} + 15") - else() - math(EXPR _res "${_res} + ${NIBBLE}") - endif() - - string(LENGTH "${HEX}" _strlen) - endwhile() - - set(${DEC} ${_res} PARENT_SCOPE) -endfunction(from_hex) - -if (OPENSSL_INCLUDE_DIR) - if (_OPENSSL_VERSION) - set(OPENSSL_VERSION "${_OPENSSL_VERSION}") - elseif(OPENSSL_INCLUDE_DIR AND EXISTS "${OPENSSL_INCLUDE_DIR}/openssl/opensslv.h") - file(STRINGS "${OPENSSL_INCLUDE_DIR}/openssl/opensslv.h" openssl_version_str - REGEX "^#define[\t ]+OPENSSL_VERSION_NUMBER[\t ]+0x([0-9a-fA-F])+.*") - - # The version number is encoded as 0xMNNFFPPS: major minor fix patch status - # The status gives if this is a developer or prerelease and is ignored here. - # Major, minor, and fix directly translate into the version numbers shown in - # the string. The patch field translates to the single character suffix that - # indicates the bug fix state, which 00 -> nothing, 01 -> a, 02 -> b and so - # on. - - string(REGEX REPLACE "^.*OPENSSL_VERSION_NUMBER[\t ]+0x([0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F]).*$" - "\\1;\\2;\\3;\\4;\\5" OPENSSL_VERSION_LIST "${openssl_version_str}") - list(GET OPENSSL_VERSION_LIST 0 OPENSSL_VERSION_MAJOR) - list(GET OPENSSL_VERSION_LIST 1 OPENSSL_VERSION_MINOR) - from_hex("${OPENSSL_VERSION_MINOR}" OPENSSL_VERSION_MINOR) - list(GET OPENSSL_VERSION_LIST 2 OPENSSL_VERSION_FIX) - from_hex("${OPENSSL_VERSION_FIX}" OPENSSL_VERSION_FIX) - list(GET OPENSSL_VERSION_LIST 3 OPENSSL_VERSION_PATCH) - - if (NOT OPENSSL_VERSION_PATCH STREQUAL "00") - from_hex("${OPENSSL_VERSION_PATCH}" _tmp) - # 96 is the ASCII code of 'a' minus 1 - math(EXPR OPENSSL_VERSION_PATCH_ASCII "${_tmp} + 96") - unset(_tmp) - # Once anyone knows how OpenSSL would call the patch versions beyond 'z' - # this should be updated to handle that, too. This has not happened yet - # so it is simply ignored here for now. - string(ASCII "${OPENSSL_VERSION_PATCH_ASCII}" OPENSSL_VERSION_PATCH_STRING) - endif (NOT OPENSSL_VERSION_PATCH STREQUAL "00") - - set(OPENSSL_VERSION "${OPENSSL_VERSION_MAJOR}.${OPENSSL_VERSION_MINOR}.${OPENSSL_VERSION_FIX}${OPENSSL_VERSION_PATCH_STRING}") - endif (_OPENSSL_VERSION) -endif (OPENSSL_INCLUDE_DIR) - -include(FindPackageHandleStandardArgs) - -if (OPENSSL_VERSION) - find_package_handle_standard_args(OpenSSL - REQUIRED_VARS - OPENSSL_LIBRARIES - OPENSSL_INCLUDE_DIR - VERSION_VAR - OPENSSL_VERSION - FAIL_MESSAGE - "Could NOT find OpenSSL, try to set the path to OpenSSL root folder in the system variable OPENSSL_ROOT_DIR" - ) -else (OPENSSL_VERSION) - find_package_handle_standard_args(OpenSSL "Could NOT find OpenSSL, try to set the path to OpenSSL root folder in the system variable OPENSSL_ROOT_DIR" - OPENSSL_LIBRARIES - OPENSSL_INCLUDE_DIR - ) -endif (OPENSSL_VERSION) - -MARK_AS_ADVANCED(OPENSSL_INCLUDE_DIR OPENSSL_LIBRARIES) diff --git a/cmake/modules/FindQt5Keychain.cmake b/cmake/modules/FindQt5Keychain.cmake index 6848763fe7..4aff32d6fb 100644 --- a/cmake/modules/FindQt5Keychain.cmake +++ b/cmake/modules/FindQt5Keychain.cmake @@ -13,7 +13,9 @@ # so that it doesn't pull a different Qt version. For that reason # first look in the Qt lib directory for QtKeychain. get_target_property(_QTCORE_LIB_PATH Qt5::Core IMPORTED_LOCATION_RELEASE) -get_filename_component(QT_LIB_DIR "${_QTCORE_LIB_PATH}" DIRECTORY) + +# Use PATH here because Debian 7.0 has CMake 2.8.9 and DIRECTORY is only available from 2.8.12+ +get_filename_component(QT_LIB_DIR "${_QTCORE_LIB_PATH}" PATH) find_path(QTKEYCHAIN_INCLUDE_DIR NAMES diff --git a/cmake/modules/FindQtKeychain.cmake b/cmake/modules/FindQtKeychain.cmake deleted file mode 100644 index 6ef1cf7eb2..0000000000 --- a/cmake/modules/FindQtKeychain.cmake +++ /dev/null @@ -1,39 +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. - -# - Try to find QtKeychain -# Once done this will define -# QTKEYCHAIN_FOUND - System has QtKeychain -# QTKEYCHAIN_INCLUDE_DIRS - The QtKeychain include directories -# QTKEYCHAIN_LIBRARIES - The libraries needed to use QtKeychain -# QTKEYCHAIN_DEFINITIONS - Compiler switches required for using LibXml2 - -find_path(QTKEYCHAIN_INCLUDE_DIR - NAMES - keychain.h - PATH_SUFFIXES - qtkeychain - ) - - -find_library(QTKEYCHAIN_LIBRARY - NAMES - qtkeychain - libqtkeychain - PATHS - /usr/lib - /usr/lib/${CMAKE_ARCH_TRIPLET} - /usr/local/lib - /opt/local/lib - ${CMAKE_LIBRARY_PATH} - ${CMAKE_INSTALL_PREFIX}/lib - ) - -include(FindPackageHandleStandardArgs) -# handle the QUIETLY and REQUIRED arguments and set QTKEYCHAIN_FOUND to TRUE -# if all listed variables are TRUE -find_package_handle_standard_args(QtKeychain DEFAULT_MSG - QTKEYCHAIN_LIBRARY QTKEYCHAIN_INCLUDE_DIR) - -mark_as_advanced(QTKEYCHAIN_INCLUDE_DIR QTKEYCHAIN_LIBRARY) diff --git a/config.h.in b/config.h.in index 1e8f8ecdc7..0cc0ab9cd0 100644 --- a/config.h.in +++ b/config.h.in @@ -17,6 +17,7 @@ #cmakedefine APPLICATION_SHORTNAME "@APPLICATION_SHORTNAME@" #cmakedefine APPLICATION_EXECUTABLE "@APPLICATION_EXECUTABLE@" #cmakedefine APPLICATION_UPDATE_URL "@APPLICATION_UPDATE_URL@" +#cmakedefine APPLICATION_ICON_NAME "@APPLICATION_ICON_NAME@" #cmakedefine ZLIB_FOUND @ZLIB_FOUND@ diff --git a/csync/src/csync.c b/csync/src/csync.c index abac02efee..9af6f0fcf8 100644 --- a/csync/src/csync.c +++ b/csync/src/csync.c @@ -376,8 +376,7 @@ static int _csync_treewalk_visitor(void *obj, void *data) { trav.error_status = cur->error_status; trav.has_ignored_files = cur->has_ignored_files; - trav.checksum = cur->checksum; - trav.checksumTypeId = cur->checksumTypeId; + trav.checksumHeader = cur->checksumHeader; if( other_node ) { csync_file_stat_t *other_stat = (csync_file_stat_t*)other_node->data; @@ -670,7 +669,7 @@ void csync_file_stat_free(csync_file_stat_t *st) SAFE_FREE(st->directDownloadCookies); SAFE_FREE(st->etag); SAFE_FREE(st->destpath); - SAFE_FREE(st->checksum); + SAFE_FREE(st->checksumHeader); SAFE_FREE(st); } } diff --git a/csync/src/csync.h b/csync/src/csync.h index c8664a3784..949c9efd23 100644 --- a/csync/src/csync.h +++ b/csync/src/csync.h @@ -223,6 +223,10 @@ struct csync_vio_file_stat_s { enum csync_vio_file_flags_e flags; char *original_name; // only set if locale conversion fails + + // For remote file stats: the highest quality checksum the server provided + // in the "SHA1:324315da2143" form. + char *checksumHeader; }; csync_vio_file_stat_t OCSYNC_EXPORT *csync_vio_file_stat_new(void); @@ -262,8 +266,7 @@ struct csync_tree_walk_file_s { char *directDownloadUrl; char *directDownloadCookies; - const char *checksum; - uint32_t checksumTypeId; + const char *checksumHeader; struct { int64_t size; @@ -304,8 +307,8 @@ typedef int (*csync_vio_stat_hook) (csync_vio_handle_t *dhhandle, void *userdata); /* Compute the checksum of the given \a checksumTypeId for \a path. */ -typedef const char* (*csync_checksum_hook) ( - const char *path, uint32_t checksumTypeId, void *userdata); +typedef const char *(*csync_checksum_hook)( + const char *path, const char *otherChecksumHeader, void *userdata); /** * @brief Allocate a csync context. diff --git a/csync/src/csync_exclude.c b/csync/src/csync_exclude.c index e125b33462..88dd61e750 100644 --- a/csync/src/csync_exclude.c +++ b/csync/src/csync_exclude.c @@ -235,6 +235,11 @@ static CSYNC_EXCLUDE_TYPE _csync_excluded_common(c_strlist_t *excludes, const ch match = CSYNC_FILE_SILENTLY_EXCLUDED; goto out; } + rc = csync_fnmatch(".sync_*.db*", bname, 0); + if (rc == 0) { + match = CSYNC_FILE_SILENTLY_EXCLUDED; + goto out; + } rc = csync_fnmatch(".csync_journal.db*", bname, 0); if (rc == 0) { match = CSYNC_FILE_SILENTLY_EXCLUDED; diff --git a/csync/src/csync_private.h b/csync/src/csync_private.h index a10b09f6f3..11775f345d 100644 --- a/csync/src/csync_private.h +++ b/csync/src/csync_private.h @@ -191,8 +191,11 @@ struct csync_file_stat_s { char *directDownloadCookies; char remotePerm[REMOTE_PERM_BUF_SIZE+1]; - const char *checksum; - uint32_t checksumTypeId; + // In the local tree, this can hold a checksum and its type if it is + // computed during discovery for some reason. + // In the remote tree, this will have the server checksum, if available. + // In both cases, the format is "SHA1:baff". + const char *checksumHeader; CSYNC_STATUS error_status; diff --git a/csync/src/csync_reconcile.c b/csync/src/csync_reconcile.c index 9d716a80c2..59156e800a 100644 --- a/csync/src/csync_reconcile.c +++ b/csync/src/csync_reconcile.c @@ -65,6 +65,19 @@ static c_rbnode_t *_csync_check_ignored(c_rbtree_t *tree, const char *path, int } } +/* Returns true if we're reasonably certain that hash equality + * for the header means content equality. + * + * Cryptographic safety is not required - this is mainly + * intended to rule out checksums like Adler32 that are not intended for + * hashing and have high likelihood of collision with particular inputs. + */ +static bool _csync_is_collision_safe_hash(const char *checksum_header) +{ + return strncmp(checksum_header, "SHA1:", 5) == 0 + || strncmp(checksum_header, "MD5:", 4) == 0; +} + /** * The main function in the reconcile pass. * @@ -272,11 +285,31 @@ static int _csync_merge_algorithm_visitor(void *obj, void *data) { // Folders of the same path are always considered equals is_conflict = false; } else { + // If the size or mtime is different, it's definitely a conflict. is_conflict = ((other->size != cur->size) || (other->modtime != cur->modtime)); - // FIXME: do a binary comparision of the file here because of the following - // edge case: - // The files could still have different content, even though the mtime - // and size are the same. + + // It could be a conflict even if size and mtime match! + // + // In older client versions we always treated these cases as a + // non-conflict. This behavior is preserved in case the server + // doesn't provide a suitable content hash. + // + // When it does have one, however, we do create a job, but the job + // will compare hashes and avoid the download if they are equal. + const char *remoteChecksumHeader = + (ctx->current == REMOTE_REPLICA ? cur->checksumHeader : other->checksumHeader); + if (remoteChecksumHeader) { + is_conflict |= _csync_is_collision_safe_hash(remoteChecksumHeader); + } + + // SO: If there is no checksum, we can have !is_conflict here + // even though the files have different content! This is an + // intentional tradeoff. Downloading and comparing files would + // be technically correct in this situation but leads to too + // much waste. + // In particular this kind of NEW/NEW situation with identical + // sizes and mtimes pops up when the local database is lost for + // whatever reason. } if (ctx->current == REMOTE_REPLICA) { // If the files are considered equal, only update the DB with the etag from remote diff --git a/csync/src/csync_statedb.c b/csync/src/csync_statedb.c index 9b87985a8d..056ca48dd6 100644 --- a/csync/src/csync_statedb.c +++ b/csync/src/csync_statedb.c @@ -225,7 +225,12 @@ int csync_statedb_close(CSYNC *ctx) { return rc; } -#define METADATA_COLUMNS "phash, pathlen, path, inode, uid, gid, mode, modtime, type, md5, fileid, remotePerm, filesize, ignoredChildrenRemote, contentChecksum, contentChecksumTypeId" +#define METADATA_QUERY \ + "phash, pathlen, path, inode, uid, gid, mode, modtime, type, md5, fileid, remotePerm, " \ + "filesize, ignoredChildrenRemote, " \ + "contentchecksumtype.name || ':' || contentChecksum " \ + "FROM metadata " \ + "LEFT JOIN checksumtype as contentchecksumtype ON metadata.contentChecksumTypeId == contentchecksumtype.id" // This funciton parses a line from the metadata table into the given csync_file_stat // structure which it is also allocating. @@ -287,9 +292,8 @@ static int _csync_file_stat_from_metadata_table( csync_file_stat_t **st, sqlite3 if(column_count > 13) { (*st)->has_ignored_files = sqlite3_column_int(stmt, 13); } - if(column_count > 15 && sqlite3_column_int(stmt, 15)) { - (*st)->checksum = c_strdup( (char*) sqlite3_column_text(stmt, 14)); - (*st)->checksumTypeId = sqlite3_column_int(stmt, 15); + if (column_count > 14 && sqlite3_column_text(stmt, 14)) { + (*st)->checksumHeader = c_strdup((char *)sqlite3_column_text(stmt, 14)); } } @@ -313,7 +317,7 @@ csync_file_stat_t *csync_statedb_get_stat_by_hash(CSYNC *ctx, } if( ctx->statedb.by_hash_stmt == NULL ) { - const char *hash_query = "SELECT " METADATA_COLUMNS " FROM metadata WHERE phash=?1"; + const char *hash_query = "SELECT " METADATA_QUERY " WHERE phash=?1"; SQLITE_BUSY_HANDLED(sqlite3_prepare_v2(ctx->statedb.db, hash_query, strlen(hash_query), &ctx->statedb.by_hash_stmt, NULL)); ctx->statedb.lastReturnValue = rc; @@ -356,7 +360,7 @@ csync_file_stat_t *csync_statedb_get_stat_by_file_id(CSYNC *ctx, } if( ctx->statedb.by_fileid_stmt == NULL ) { - const char *query = "SELECT " METADATA_COLUMNS " FROM metadata WHERE fileid=?1"; + const char *query = "SELECT " METADATA_QUERY " WHERE fileid=?1"; SQLITE_BUSY_HANDLED(sqlite3_prepare_v2(ctx->statedb.db, query, strlen(query), &ctx->statedb.by_fileid_stmt, NULL)); ctx->statedb.lastReturnValue = rc; @@ -396,7 +400,7 @@ csync_file_stat_t *csync_statedb_get_stat_by_inode(CSYNC *ctx, } if( ctx->statedb.by_inode_stmt == NULL ) { - const char *inode_query = "SELECT " METADATA_COLUMNS " FROM metadata WHERE inode=?1"; + const char *inode_query = "SELECT " METADATA_QUERY " WHERE inode=?1"; SQLITE_BUSY_HANDLED(sqlite3_prepare_v2(ctx->statedb.db, inode_query, strlen(inode_query), &ctx->statedb.by_inode_stmt, NULL)); ctx->statedb.lastReturnValue = rc; @@ -439,7 +443,7 @@ int csync_statedb_get_below_path( CSYNC *ctx, const char *path ) { * In other words, anything that is between path+'/' and path+'0', * (because '0' follows '/' in ascii) */ - const char *below_path_query = "SELECT " METADATA_COLUMNS " FROM metadata WHERE path > (?||'/') AND path < (?||'0') ORDER BY path||'/' ASC"; + const char *below_path_query = "SELECT " METADATA_QUERY " WHERE path > (?||'/') AND path < (?||'0') ORDER BY path||'/' ASC"; SQLITE_BUSY_HANDLED(sqlite3_prepare_v2(ctx->statedb.db, below_path_query, -1, &stmt, NULL)); ctx->statedb.lastReturnValue = rc; if( rc != SQLITE_OK ) { diff --git a/csync/src/csync_update.c b/csync/src/csync_update.c index 1951529127..d7587765c3 100644 --- a/csync/src/csync_update.c +++ b/csync/src/csync_update.c @@ -287,16 +287,15 @@ static int _csync_detect_update(CSYNC *ctx, const char *file, // Checksum comparison at this stage is only enabled for .eml files, // check #4754 #4755 bool isEmlFile = csync_fnmatch("*.eml", file, FNM_CASEFOLD) == 0; - if (isEmlFile && fs->size == tmp->size && tmp->checksumTypeId) { + if (isEmlFile && fs->size == tmp->size && tmp->checksumHeader) { if (ctx->callbacks.checksum_hook) { - st->checksum = ctx->callbacks.checksum_hook( - file, tmp->checksumTypeId, - ctx->callbacks.checksum_userdata); + st->checksumHeader = ctx->callbacks.checksum_hook( + file, tmp->checksumHeader, + ctx->callbacks.checksum_userdata); } bool checksumIdentical = false; - if (st->checksum) { - st->checksumTypeId = tmp->checksumTypeId; - checksumIdentical = strncmp(st->checksum, tmp->checksum, 1000) == 0; + if (st->checksumHeader) { + checksumIdentical = strncmp(st->checksumHeader, tmp->checksumHeader, 1000) == 0; } if (checksumIdentical) { CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "NOTE: Checksums are identical, file did not actually change: %s", path); @@ -381,15 +380,14 @@ static int _csync_detect_update(CSYNC *ctx, const char *file, // Verify the checksum where possible - if (isRename && tmp->checksumTypeId && ctx->callbacks.checksum_hook - && fs->type == CSYNC_VIO_FILE_TYPE_REGULAR) { - st->checksum = ctx->callbacks.checksum_hook( - file, tmp->checksumTypeId, - ctx->callbacks.checksum_userdata); - if (st->checksum) { - CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "checking checksum of potential rename %s %s <-> %s", path, st->checksum, tmp->checksum); - st->checksumTypeId = tmp->checksumTypeId; - isRename = strncmp(st->checksum, tmp->checksum, 1000) == 0; + if (isRename && tmp->checksumHeader && ctx->callbacks.checksum_hook + && fs->type == CSYNC_VIO_FILE_TYPE_REGULAR) { + st->checksumHeader = ctx->callbacks.checksum_hook( + file, tmp->checksumHeader, + ctx->callbacks.checksum_userdata); + if (st->checksumHeader) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "checking checksum of potential rename %s %s <-> %s", path, st->checksumHeader, tmp->checksumHeader); + isRename = strncmp(st->checksumHeader, tmp->checksumHeader, 1000) == 0; } } @@ -506,6 +504,11 @@ out: strncpy(st->remotePerm, fs->remotePerm, REMOTE_PERM_BUF_SIZE); } + // For the remote: propagate the discovered checksum + if (fs->checksumHeader && ctx->current == REMOTE_REPLICA) { + st->checksumHeader = c_strdup(fs->checksumHeader); + } + st->phash = h; st->pathlen = len; memcpy(st->path, (len ? path : ""), len + 1); diff --git a/csync/src/vio/csync_vio_file_stat.c b/csync/src/vio/csync_vio_file_stat.c index bc935d3759..8ebb64f337 100644 --- a/csync/src/vio/csync_vio_file_stat.c +++ b/csync/src/vio/csync_vio_file_stat.c @@ -40,6 +40,9 @@ csync_vio_file_stat_t* csync_vio_file_stat_copy(csync_vio_file_stat_t *file_stat if (file_stat_cpy->directDownloadUrl) { file_stat_cpy->directDownloadUrl = c_strdup(file_stat_cpy->directDownloadUrl); } + if (file_stat_cpy->checksumHeader) { + file_stat_cpy->checksumHeader = c_strdup(file_stat_cpy->checksumHeader); + } file_stat_cpy->name = c_strdup(file_stat_cpy->name); return file_stat_cpy; } @@ -57,6 +60,7 @@ void csync_vio_file_stat_destroy(csync_vio_file_stat_t *file_stat) { SAFE_FREE(file_stat->directDownloadCookies); SAFE_FREE(file_stat->name); SAFE_FREE(file_stat->original_name); + SAFE_FREE(file_stat->checksumHeader); SAFE_FREE(file_stat); } diff --git a/csync/tests/csync_tests/check_csync_exclude.c b/csync/tests/csync_tests/check_csync_exclude.c index e7af73c67f..bd4813c5ed 100644 --- a/csync/tests/csync_tests/check_csync_exclude.c +++ b/csync/tests/csync_tests/check_csync_exclude.c @@ -157,7 +157,16 @@ static void check_csync_excluded(void **state) assert_int_equal(rc, CSYNC_FILE_SILENTLY_EXCLUDED); rc = csync_excluded_no_ctx(csync->excludes, "subdir/._sync_5bdd60bdfcfa.db", CSYNC_FTW_TYPE_FILE); assert_int_equal(rc, CSYNC_FILE_SILENTLY_EXCLUDED); - + + rc = csync_excluded_no_ctx(csync->excludes, ".sync_5bdd60bdfcfa.db", CSYNC_FTW_TYPE_FILE); + assert_int_equal(rc, CSYNC_FILE_SILENTLY_EXCLUDED); + rc = csync_excluded_no_ctx(csync->excludes, ".sync_5bdd60bdfcfa.db.ctmp", CSYNC_FTW_TYPE_FILE); + assert_int_equal(rc, CSYNC_FILE_SILENTLY_EXCLUDED); + rc = csync_excluded_no_ctx(csync->excludes, ".sync_5bdd60bdfcfa.db-shm", CSYNC_FTW_TYPE_FILE); + assert_int_equal(rc, CSYNC_FILE_SILENTLY_EXCLUDED); + rc = csync_excluded_no_ctx(csync->excludes, "subdir/.sync_5bdd60bdfcfa.db", CSYNC_FTW_TYPE_FILE); + assert_int_equal(rc, CSYNC_FILE_SILENTLY_EXCLUDED); + /* pattern ]*.directory - ignore and remove */ rc = csync_excluded_no_ctx(csync->excludes, "my.~directory", CSYNC_FTW_TYPE_FILE); diff --git a/doc/building.rst b/doc/building.rst index 1e3a927bb5..7cc6da6987 100644 --- a/doc/building.rst +++ b/doc/building.rst @@ -120,7 +120,6 @@ follow `Windows Installer Build (Cross-Compile)`_ instead. * Make sure that you have CMake_ and Git_. * Download the Qt_ MinGW package. You will use the MinGW version bundled with it. - * Download an `OpenSSL Windows Build`_ (the non-"Light" version) 2. Get the QtKeychain_ sources as well as the latest versions of the ownCloud client from Git as follows:: @@ -130,11 +129,10 @@ follow `Windows Installer Build (Cross-Compile)`_ instead. 3. Open the Qt MinGW shortcut console from the Start Menu -4. Make sure that OpenSSL's ``bin`` directory as well as your qtkeychain source - directories are in your PATH. This will allow CMake to find the library and - headers, as well as allow the ownCloud client to find the DLLs at runtime:: +4. Make sure that your qtkeychain source directories are in your PATH. This will + allow CMake to find the library and headers, as well as allow the ownCloud + client to find the DLLs at runtime:: - set PATH=C:\\bin;%PATH% set PATH=C:\;%PATH% 5. Build qtkeychain **directly in the source directory** so that the DLL is built @@ -251,7 +249,7 @@ To build the most up-to-date version of the client: .. note:: qtkeychain must be compiled with the same prefix e.g ``CMAKE_INSTALL_PREFIX=/Users/path/to/client/install/ .`` - .. note:: Example:: ``cmake -DCMAKE_PREFIX_PATH=/usr/local/opt/qt5 -DCMAKE_INSTALL_PREFIX=/Users/path/to/client/install/ -D_OPENSSL_LIBDIR=/usr/local/opt/openssl/lib/ -D_OPENSSL_INCLUDEDIR=/usr/local/opt/openssl/include/ -D_OPENSSL_VERSION=1.0.2a -DOPENSSL_INCLUDE_DIR=/usr/local/opt/openssl/include/ -DNO_SHIBBOLETH=1`` + .. note:: Example:: ``cmake -DCMAKE_PREFIX_PATH=/usr/local/opt/qt5 -DCMAKE_INSTALL_PREFIX=/Users/path/to/client/install/ -DNO_SHIBBOLETH=1`` 4. Call ``make``. @@ -279,7 +277,6 @@ The following are known cmake parameters: .. _Git: http://git-scm.com .. _MacPorts: http://www.macports.org .. _Homebrew: http://mxcl.github.com/homebrew/ -.. _OpenSSL Windows Build: http://slproweb.com/products/Win32OpenSSL.html .. _Qt: http://www.qt.io/download .. _Microsoft Authenticode: https://msdn.microsoft.com/en-us/library/ie/ms537361%28v=vs.85%29.aspx .. _QtKeychain: https://github.com/frankosterfeld/qtkeychain diff --git a/mirall.desktop.in b/mirall.desktop.in index 0d56e2e891..b3a21737b9 100644 --- a/mirall.desktop.in +++ b/mirall.desktop.in @@ -9,6 +9,45 @@ Icon=@APPLICATION_EXECUTABLE@ Keywords=@APPLICATION_NAME@;syncing;file;sharing; X-GNOME-Autostart-Delay=3 +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + # Translations Comment[oc]=@APPLICATION_NAME@ sincronizacion del client GenericName[oc]=Dorsièr de Sincronizacion @@ -149,6 +188,10 @@ GenericName[zh_CN]=文件夹同步 Name[zh_CN]=@APPLICATION_NAME@ 桌面同步客户端 Icon[zh_CN]=@APPLICATION_EXECUTABLE@ GenericName[zh_TW]=資料夾同步 +Comment[es_AR]=Cliente de sincronización para escritorio @APPLICATION_NAME@ +GenericName[es_AR]=Sincronización de directorio +Name[es_AR]=Cliente de sincronización para escritorio @APPLICATION_NAME@ +Icon[es_AR]=@APPLICATION_EXECUTABLE@ Comment[lt_LT]=@APPLICATION_NAME@ darbalaukio sinchronizavimo programa GenericName[lt_LT]=Katalogo sinchnorizacija Name[lt_LT]=@APPLICATION_NAME@ darbalaukio programa diff --git a/shell_integration/windows/OCUtil/RemotePathChecker.cpp b/shell_integration/windows/OCUtil/RemotePathChecker.cpp index cf8b72bbdd..4a13c1cd56 100644 --- a/shell_integration/windows/OCUtil/RemotePathChecker.cpp +++ b/shell_integration/windows/OCUtil/RemotePathChecker.cpp @@ -172,9 +172,10 @@ void RemotePathChecker::workerThreadLoop() RemotePathChecker::RemotePathChecker() - : _watchedDirectories(make_shared>()) + : _stop(false) + , _watchedDirectories(make_shared>()) , _connected(false) - , _newQueries(CreateEvent(NULL, true, true, NULL)) + , _newQueries(CreateEvent(NULL, FALSE, FALSE, NULL)) , _thread([this]{ this->workerThreadLoop(); }) { } diff --git a/shell_integration/windows/OCUtil/Version.h b/shell_integration/windows/OCUtil/Version.h index 0b1db7a6b7..4010a60a10 100644 --- a/shell_integration/windows/OCUtil/Version.h +++ b/shell_integration/windows/OCUtil/Version.h @@ -2,7 +2,7 @@ // This is the number that will end up in the version window of the DLLs. // Increment this version before committing a new build if you are today's shell_integration build master. -#define OCEXT_BUILD_NUM 44 +#define OCEXT_BUILD_NUM 45 #define STRINGIZE2(s) #s #define STRINGIZE(s) STRINGIZE2(s) diff --git a/src/cmd/cmd.cpp b/src/cmd/cmd.cpp index 0535875cd4..5baeb8a8be 100644 --- a/src/cmd/cmd.cpp +++ b/src/cmd/cmd.cpp @@ -188,6 +188,7 @@ void help() std::cout << " --downlimit [n] Limit the download speed of files to n KB/s" << std::endl; std::cout << " -h Sync hidden files,do not ignore them" << std::endl; std::cout << " --version, -v Display version and exit" << std::endl; + std::cout << " --debug More verbose logging" << std::endl; std::cout << "" << std::endl; exit(0); } @@ -495,7 +496,7 @@ restart_sync: } Cmd cmd; - QString dbPath = options.source_dir + SyncJournalDb::makeDbName(credentialFreeUrl, folder, user); + QString dbPath = options.source_dir + SyncJournalDb::makeDbName(options.source_dir, credentialFreeUrl, folder, user); SyncJournalDb db(dbPath); if (!selectiveSyncList.empty()) { diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 5ebf0effca..643bb932cd 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -35,6 +35,7 @@ set(client_UI wizard/owncloudadvancedsetuppage.ui wizard/owncloudconnectionmethoddialog.ui wizard/owncloudhttpcredspage.ui + wizard/owncloudoauthcredspage.ui wizard/owncloudsetupnocredspage.ui wizard/owncloudwizardresultpage.ui ) @@ -93,11 +94,13 @@ set(client_SRCS servernotificationhandler.cpp creds/credentialsfactory.cpp creds/httpcredentialsgui.cpp + creds/oauth.cpp wizard/postfixlineedit.cpp wizard/abstractcredswizardpage.cpp wizard/owncloudadvancedsetuppage.cpp wizard/owncloudconnectionmethoddialog.cpp wizard/owncloudhttpcredspage.cpp + wizard/owncloudoauthcredspage.cpp wizard/owncloudsetuppage.cpp wizard/owncloudwizardcommon.cpp wizard/owncloudwizard.cpp @@ -173,7 +176,6 @@ set(3rdparty_INC ) include_directories(${3rdparty_INC}) -include_directories(${OPENSSL_INCLUDE_DIR}) # csync is required. include_directories(${CMAKE_SOURCE_DIR}/csync/src @@ -221,15 +223,13 @@ endif() include( AddAppIconMacro ) set(ownCloud_old ${ownCloud}) -# set an icon_app_name. For historical reasons we can not use the -# application_shortname for ownCloud but must rather set it manually. -if ( EXISTS ${OEM_THEME_DIR}/OEM.cmake ) - set(ICON_APP_NAME ${APPLICATION_SHORTNAME}) -else() - set(ICON_APP_NAME "owncloud") +# For historical reasons we can not use the application_shortname +# for ownCloud but must rather set it manually. +if (NOT DEFINED APPLICATION_ICON_NAME) + set(APPLICATION_ICON_NAME ${APPLICATION_SHORTNAME}) endif() -kde4_add_app_icon( ownCloud "${theme_dir}/colored/${ICON_APP_NAME}-icon*.png") +kde4_add_app_icon( ownCloud "${theme_dir}/colored/${APPLICATION_ICON_NAME}-icon*.png") list(APPEND final_src ${ownCloud}) set(ownCloud ${ownCloud_old}) @@ -243,11 +243,11 @@ endif() if(NOT BUILD_OWNCLOUD_OSX_BUNDLE) if(NOT WIN32) - file( GLOB _icons "${theme_dir}/colored/${ICON_APP_NAME}-icon-*.png" ) + file( GLOB _icons "${theme_dir}/colored/${APPLICATION_ICON_NAME}-icon-*.png" ) foreach( _file ${_icons} ) - string( REPLACE "${theme_dir}/colored/${ICON_APP_NAME}-icon-" "" _res ${_file} ) + string( REPLACE "${theme_dir}/colored/${APPLICATION_ICON_NAME}-icon-" "" _res ${_file} ) string( REPLACE ".png" "" _res ${_res} ) - install( FILES ${_file} RENAME ${ICON_APP_NAME}.png DESTINATION ${DATADIR}/icons/hicolor/${_res}x${_res}/apps ) + install( FILES ${_file} RENAME ${APPLICATION_ICON_NAME}.png DESTINATION ${DATADIR}/icons/hicolor/${_res}x${_res}/apps ) endforeach( _file ) endif(NOT WIN32) diff --git a/src/gui/accountsettings.cpp b/src/gui/accountsettings.cpp index 9eb3a53d25..2f866502ad 100644 --- a/src/gui/accountsettings.cpp +++ b/src/gui/accountsettings.cpp @@ -202,6 +202,7 @@ void AccountSettings::slotOpenAccountWizard() void AccountSettings::slotToggleSignInState() { if (_accountState->isSignedOut()) { + _accountState->account()->resetRejectedCertificates(); _accountState->signIn(); } else { _accountState->signOutByUi(); diff --git a/src/gui/application.cpp b/src/gui/application.cpp index 4991aac56c..40bbfb1d7e 100644 --- a/src/gui/application.cpp +++ b/src/gui/application.cpp @@ -53,8 +53,6 @@ #include #include -#include - class QSocket; namespace OCC { @@ -438,6 +436,7 @@ void Application::parseOptions(const QStringList &options) showHint("Path for confdir not specified"); } } else if (option == QLatin1String("--debug")) { + _logDebug = true; _debugMode = true; } else if (option == QLatin1String("--version")) { _versionOnly = true; diff --git a/src/gui/creds/httpcredentialsgui.cpp b/src/gui/creds/httpcredentialsgui.cpp index 50d2a2367c..9c629ce654 100644 --- a/src/gui/creds/httpcredentialsgui.cpp +++ b/src/gui/creds/httpcredentialsgui.cpp @@ -15,9 +15,14 @@ #include #include +#include +#include +#include +#include #include "creds/httpcredentialsgui.h" #include "theme.h" #include "account.h" +#include using namespace QKeychain; @@ -25,11 +30,61 @@ namespace OCC { void HttpCredentialsGui::askFromUser() { - // The rest of the code assumes that this will be done asynchronously - QMetaObject::invokeMethod(this, "askFromUserAsync", Qt::QueuedConnection); + _password = QString(); // So our QNAM does not add any auth + + // First, we will send a call to the webdav endpoint to check what kind of auth we need. + auto reply = _account->sendRequest("GET", _account->davUrl()); + QTimer::singleShot(30 * 1000, reply, &QNetworkReply::abort); + QObject::connect(reply, &QNetworkReply::finished, this, [this, reply] { + reply->deleteLater(); + if (reply->rawHeader("WWW-Authenticate").contains("Bearer ")) { + // OAuth + _asyncAuth.reset(new OAuth(_account, this)); + connect(_asyncAuth.data(), &OAuth::result, + this, &HttpCredentialsGui::asyncAuthResult); + _asyncAuth->start(); + } else if (reply->error() == QNetworkReply::AuthenticationRequiredError) { + // Show the dialog + // We will re-enter the event loop, so better wait the next iteration + QMetaObject::invokeMethod(this, "showDialog", Qt::QueuedConnection); + } else { + // Network error? + emit asked(); + } + }); } -void HttpCredentialsGui::askFromUserAsync() +void HttpCredentialsGui::asyncAuthResult(OAuth::Result r, const QString &user, + const QString &token, const QString &refreshToken) +{ + switch (r) { + case OAuth::NotSupported: + // We will re-enter the event loop, so better wait the next iteration + QMetaObject::invokeMethod(this, "showDialog", Qt::QueuedConnection); + _asyncAuth.reset(0); + return; + case OAuth::Error: + _asyncAuth.reset(0); + emit asked(); + return; + case OAuth::LoggedIn: + break; + } + + if (_user != user) { + QMessageBox::warning(nullptr, tr("Login Error"), tr("You must sign in as user %1").arg(_user)); + _asyncAuth->openBrowser(); + return; + } + _password = token; + _refreshToken = refreshToken; + _ready = true; + persist(); + _asyncAuth.reset(0); + emit asked(); +} + +void HttpCredentialsGui::showDialog() { QString msg = tr("Please enter %1 password:
" "
" @@ -87,6 +142,4 @@ QString HttpCredentialsGui::requestAppPasswordText(const Account *account) return tr("Click here to request an app password from the web interface.") .arg(account->url().toString() + path); } - - } // namespace OCC diff --git a/src/gui/creds/httpcredentialsgui.h b/src/gui/creds/httpcredentialsgui.h index 235fd523ea..0eaeeee812 100644 --- a/src/gui/creds/httpcredentialsgui.h +++ b/src/gui/creds/httpcredentialsgui.h @@ -15,6 +15,9 @@ #pragma once #include "creds/httpcredentials.h" +#include "creds/oauth.h" +#include +#include namespace OCC { @@ -34,10 +37,26 @@ public: : HttpCredentials(user, password, certificate, key) { } - void askFromUser() Q_DECL_OVERRIDE; - Q_INVOKABLE void askFromUserAsync(); + HttpCredentialsGui(const QString &user, const QString &password, const QString &refreshToken, + const QSslCertificate &certificate, const QSslKey &key) + : HttpCredentials(user, password, certificate, key) + { + _refreshToken = refreshToken; + } + + /** + * This will query the server and either uses OAuth via _asyncAuth->start() + * or call showDialog to ask the password + */ + Q_INVOKABLE void askFromUser() Q_DECL_OVERRIDE; static QString requestAppPasswordText(const Account *account); +private slots: + void asyncAuthResult(OAuth::Result, const QString &user, const QString &accessToken, const QString &refreshToken); + void showDialog(); + +private: + QScopedPointer> _asyncAuth; }; } // namespace OCC diff --git a/src/gui/creds/oauth.cpp b/src/gui/creds/oauth.cpp new file mode 100644 index 0000000000..38e279d293 --- /dev/null +++ b/src/gui/creds/oauth.cpp @@ -0,0 +1,123 @@ +/* + * Copyright (C) by Olivier Goffart + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include +#include +#include +#include "account.h" +#include "creds/oauth.h" +#include +#include +#include "theme.h" + +namespace OCC { + +Q_LOGGING_CATEGORY(lcOauth, "sync.credentials.oauth", QtInfoMsg) + +OAuth::~OAuth() +{ +} + +static void httpReplyAndClose(QTcpSocket *socket, const char *code, const char *html) +{ + socket->write("HTTP/1.1 "); + socket->write(code); + socket->write("\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: "); + socket->write(QByteArray::number(qstrlen(html))); + socket->write("\r\n\r\n"); + socket->write(html); + socket->disconnectFromHost(); +} + +void OAuth::start() +{ + // Listen on the socket to get a port which will be used in the redirect_uri + if (!_server.listen(QHostAddress::LocalHost)) { + emit result(NotSupported, QString()); + return; + } + + if (!openBrowser()) + return; + + QObject::connect(&_server, &QTcpServer::newConnection, this, [this] { + while (QTcpSocket *socket = _server.nextPendingConnection()) { + QObject::connect(socket, &QTcpSocket::disconnected, socket, &QTcpSocket::deleteLater); + QObject::connect(socket, &QIODevice::readyRead, this, [this, socket] { + QByteArray peek = socket->peek(qMin(socket->bytesAvailable(), 4000LL)); //The code should always be within the first 4K + if (peek.indexOf('\n') < 0) + return; // wait until we find a \n + QRegExp rx("^GET /\\?code=([a-zA-Z0-9]+)[& ]"); // Match a /?code=... URL + if (rx.indexIn(peek) != 0) { + httpReplyAndClose(socket, "404 Not Found", "404 Not Found

404 Not Found

"); + return; + } + + // TODO: add redirect to the page on the server + httpReplyAndClose(socket, "200 OK", "

Login Successful

You can close this window.

"); + + QString code = rx.cap(1); // The 'code' is the first capture of the regexp + + QUrl requestToken(_account->url().toString() + + QLatin1String("/index.php/apps/oauth2/api/v1/token?grant_type=authorization_code&code=") + + code + + QLatin1String("&redirect_uri=http://localhost:") + QString::number(_server.serverPort())); + requestToken.setUserName(Theme::instance()->oauthClientId()); + requestToken.setPassword(Theme::instance()->oauthClientSecret()); + QNetworkRequest req; + req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + auto reply = _account->sendRequest("POST", requestToken, req); + QTimer::singleShot(30 * 1000, reply, &QNetworkReply::abort); + QObject::connect(reply, &QNetworkReply::finished, this, [this, reply] { + auto jsonData = reply->readAll(); + QJsonParseError jsonParseError; + QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object(); + QString accessToken = json["access_token"].toString(); + QString refreshToken = json["refresh_token"].toString(); + QString user = json["user_id"].toString(); + + if (reply->error() != QNetworkReply::NoError || jsonParseError.error != QJsonParseError::NoError + || json.isEmpty() || refreshToken.isEmpty() || accessToken.isEmpty() + || json["token_type"].toString() != QLatin1String("Bearer")) { + qCWarning(lcOauth) << "Error when getting the accessToken" << reply->error() << json << jsonParseError.errorString(); + emit result(Error); + return; + } + emit result(LoggedIn, user, accessToken, refreshToken); + }); + }); + } + }); + QTimer::singleShot(5 * 60 * 1000, this, [this] { result(Error); }); +} + + +bool OAuth::openBrowser() +{ + Q_ASSERT(_server.isListening()); + auto url = QUrl(_account->url().toString() + + QLatin1String("/index.php/apps/oauth2/authorize?response_type=code&client_id=") + + Theme::instance()->oauthClientId() + + QLatin1String("&redirect_uri=http://localhost:") + QString::number(_server.serverPort())); + + + if (!QDesktopServices::openUrl(url)) { + // We cannot open the browser, then we claim we don't support OAuth. + emit result(NotSupported, QString()); + return false; + } + return true; +} + +} // namespace OCC diff --git a/src/gui/creds/oauth.h b/src/gui/creds/oauth.h new file mode 100644 index 0000000000..fe7fd1c402 --- /dev/null +++ b/src/gui/creds/oauth.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) by Olivier Goffart + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#pragma once +#include +#include + +namespace OCC { + +/** + * Job that do the authorization grant and fetch the access token + * + * Normal workflow: + * + * --> start() + * | + * +----> openBrowser() open the browser to the login page, redirects to http://localhost:xxx + * | + * +----> _server starts listening on a TCP port waiting for an HTTP request with a 'code' + * | + * v + * request the access_token and the refresh_token via 'apps/oauth2/api/v1/token' + * | + * v + * emit result(...) + * + */ +class OAuth : public QObject +{ + Q_OBJECT +public: + OAuth(Account *account, QObject *parent) + : QObject(parent) + , _account(account) + { + } + ~OAuth(); + + enum Result { NotSupported, + LoggedIn, + Error }; + Q_ENUM(Result); + void start(); + bool openBrowser(); + +signals: + /** + * The state has changed. + * when logged in, token has the value of the token. + */ + void result(OAuth::Result result, const QString &user = QString(), const QString &token = QString(), const QString &refreshToken = QString()); + +private: + Account *_account; + QTcpServer _server; +}; + + +} // namespace OCC diff --git a/src/gui/folder.cpp b/src/gui/folder.cpp index 7c00949527..e00166262e 100644 --- a/src/gui/folder.cpp +++ b/src/gui/folder.cpp @@ -1034,7 +1034,7 @@ QString FolderDefinition::absoluteJournalPath() const QString FolderDefinition::defaultJournalPath(AccountPtr account) { - return SyncJournalDb::makeDbName(account->url(), targetPath, account->credentials()->user()); + return SyncJournalDb::makeDbName(localPath, account->url(), targetPath, account->credentials()->user()); } } // namespace OCC diff --git a/src/gui/folderman.cpp b/src/gui/folderman.cpp index 6c9fd71cfc..ce5376b2ba 100644 --- a/src/gui/folderman.cpp +++ b/src/gui/folderman.cpp @@ -239,11 +239,22 @@ void FolderMan::setupFoldersHelper(QSettings &settings, AccountStatePtr account, foreach (const auto &folderAlias, settings.childGroups()) { FolderDefinition folderDefinition; if (FolderDefinition::load(settings, folderAlias, &folderDefinition)) { + auto defaultJournalPath = folderDefinition.defaultJournalPath(account->account()); + // Migration: Old settings don't have journalPath if (folderDefinition.journalPath.isEmpty()) { - folderDefinition.journalPath = folderDefinition.defaultJournalPath(account->account()); + folderDefinition.journalPath = defaultJournalPath; } - folderDefinition.defaultJournalPath(account->account()); + + // Migration: ._ files sometimes don't work + // So if the configured journalPath is the default one ("._sync_*.db") + // but the current default doesn't have the underscore, switch to the + // new default. See SyncJournalDb::makeDbName(). + if (folderDefinition.journalPath.startsWith("._sync_") + && defaultJournalPath.startsWith(".sync_")) { + folderDefinition.journalPath = defaultJournalPath; + } + // Migration: If an old db is found, move it to the new name. if (backwardsCompatible) { SyncJournalDb::maybeMigrateDb(folderDefinition.localPath, folderDefinition.absoluteJournalPath()); diff --git a/src/gui/folderstatusmodel.cpp b/src/gui/folderstatusmodel.cpp index 037285b530..b0f83e9c8e 100644 --- a/src/gui/folderstatusmodel.cpp +++ b/src/gui/folderstatusmodel.cpp @@ -614,6 +614,8 @@ void FolderStatusModel::slotUpdateDirectories(const QStringList &list) if (!parentInfo) { return; } + ASSERT(parentInfo->_fetching); // we should only get a result if we were doing a fetch + ASSERT(parentInfo->_subs.isEmpty()); if (parentInfo->hasLabel()) { beginRemoveRows(idx, 0, 0); @@ -712,9 +714,11 @@ void FolderStatusModel::slotUpdateDirectories(const QStringList &list) newSubs.append(newInfo); } - beginInsertRows(idx, 0, newSubs.size() - 1); - parentInfo->_subs = std::move(newSubs); - endInsertRows(); + if (!newSubs.isEmpty()) { + beginInsertRows(idx, 0, newSubs.size() - 1); + parentInfo->_subs = std::move(newSubs); + endInsertRows(); + } for (auto it = undecidedIndexes.begin(); it != undecidedIndexes.end(); ++it) { suggestExpand(idx.child(*it, 0)); diff --git a/src/gui/folderwatcher_linux.cpp b/src/gui/folderwatcher_linux.cpp index b505b8dd0b..5d2d4e7fd4 100644 --- a/src/gui/folderwatcher_linux.cpp +++ b/src/gui/folderwatcher_linux.cpp @@ -160,7 +160,10 @@ void FolderWatcherPrivate::slotReceivedNotification(int fd) // Fire event for the path that was changed. if (event->len > 0 && event->wd > -1) { QByteArray fileName(event->name); - if (fileName.startsWith("._sync_") || fileName.startsWith(".csync_journal.db") || fileName.startsWith(".owncloudsync.log")) { + if (fileName.startsWith("._sync_") + || fileName.startsWith(".csync_journal.db") + || fileName.startsWith(".owncloudsync.log") + || fileName.startsWith(".sync_")) { } else { const QString p = _watches[event->wd] + '/' + fileName; _parent->changeDetected(p); diff --git a/src/gui/generalsettings.ui b/src/gui/generalsettings.ui index 5a035a5922..20f2c86d37 100644 --- a/src/gui/generalsettings.ui +++ b/src/gui/generalsettings.ui @@ -152,6 +152,12 @@ + + + 0 + 0 + + About diff --git a/src/gui/ignorelisteditor.cpp b/src/gui/ignorelisteditor.cpp index 83fd39ffd9..64c39c48fe 100644 --- a/src/gui/ignorelisteditor.cpp +++ b/src/gui/ignorelisteditor.cpp @@ -48,6 +48,9 @@ IgnoreListEditor::IgnoreListEditor(QWidget *parent) "and cannot be modified in this view.") .arg(QDir::toNativeSeparators(cfgFile.excludeFile(ConfigFile::SystemScope))); + addPattern(".csync_journal.db*", /*deletable=*/false, /*readonly=*/true); + addPattern("._sync_*.db*", /*deletable=*/false, /*readonly=*/true); + addPattern(".sync_*.db*", /*deletable=*/false, /*readonly=*/true); readIgnoreFile(cfgFile.excludeFile(ConfigFile::SystemScope), true); readIgnoreFile(cfgFile.excludeFile(ConfigFile::UserScope), false); diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index 25418c62ab..684689dae7 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -871,6 +871,7 @@ void ownCloudGui::slotDisplayIdle() void ownCloudGui::slotLogin() { if (auto account = qvariant_cast(sender()->property(propertyAccountC))) { + account->account()->resetRejectedCertificates(); account->signIn(); } else { auto list = AccountManager::instance()->accounts(); diff --git a/src/gui/owncloudsetupwizard.cpp b/src/gui/owncloudsetupwizard.cpp index 9a7359f3ef..f124fa265b 100644 --- a/src/gui/owncloudsetupwizard.cpp +++ b/src/gui/owncloudsetupwizard.cpp @@ -346,7 +346,7 @@ void OwncloudSetupWizard::slotAuthError() } _ocWizard->show(); - if (_ocWizard->currentId() == WizardCommon::Page_ShibbolethCreds) { + if (_ocWizard->currentId() == WizardCommon::Page_ShibbolethCreds || _ocWizard->currentId() == WizardCommon::Page_OAuthCreds) { _ocWizard->back(); } _ocWizard->displayError(errorMsg, _ocWizard->currentId() == WizardCommon::Page_ServerSetup && checkDowngradeAdvised(reply)); @@ -625,7 +625,11 @@ bool DetermineAuthTypeJob::finished() redirection.clear(); } if ((reply()->error() == QNetworkReply::AuthenticationRequiredError) || redirection.isEmpty()) { - emit authType(WizardCommon::HttpCreds); + if (reply()->rawHeader("WWW-Authenticate").contains("Bearer ")) { + emit authType(WizardCommon::OAuth); + } else { + emit authType(WizardCommon::HttpCreds); + } } else if (redirection.toString().endsWith(account()->davPath())) { // do a new run _redirects++; diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index af6e933ccb..a380be35d6 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -14,6 +14,7 @@ #include "systray.h" #include "theme.h" +#include "config.h" #ifdef USE_FDO_NOTIFICATIONS #include @@ -30,8 +31,8 @@ namespace OCC { void Systray::showMessage(const QString &title, const QString &message, MessageIcon icon, int millisecondsTimeoutHint) { #ifdef USE_FDO_NOTIFICATIONS - if (QDBusInterface(NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE).isValid()) { - QList args = QList() << "owncloud" << quint32(0) << "owncloud" + if(QDBusInterface(NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE).isValid()) { + QList args = QList() << APPLICATION_NAME << quint32(0) << APPLICATION_ICON_NAME << title << message << QStringList() << QVariantMap() << qint32(-1); QDBusMessage method = QDBusMessage::createMethodCall(NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE, "Notify"); method.setArguments(args); diff --git a/src/gui/wizard/owncloudoauthcredspage.cpp b/src/gui/wizard/owncloudoauthcredspage.cpp new file mode 100644 index 0000000000..a4bf5988e7 --- /dev/null +++ b/src/gui/wizard/owncloudoauthcredspage.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (C) by Olivier Goffart + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include + +#include "wizard/owncloudoauthcredspage.h" +#include "theme.h" +#include "account.h" +#include "cookiejar.h" +#include "wizard/owncloudwizardcommon.h" +#include "wizard/owncloudwizard.h" +#include "creds/httpcredentialsgui.h" +#include "creds/credentialsfactory.h" + +namespace OCC { + +OwncloudOAuthCredsPage::OwncloudOAuthCredsPage() + : AbstractCredentialsWizardPage() +{ + _ui.setupUi(this); + + Theme *theme = Theme::instance(); + _ui.topLabel->hide(); + _ui.bottomLabel->hide(); + QVariant variant = theme->customMedia(Theme::oCSetupTop); + WizardCommon::setupCustomMedia(variant, _ui.topLabel); + variant = theme->customMedia(Theme::oCSetupBottom); + WizardCommon::setupCustomMedia(variant, _ui.bottomLabel); + + WizardCommon::initErrorLabel(_ui.errorLabel); + + setTitle(WizardCommon::titleTemplate().arg(tr("Connect to %1").arg(Theme::instance()->appNameGUI()))); + setSubTitle(WizardCommon::subTitleTemplate().arg(tr("Login in your browser"))); + + connect(_ui.openLinkButton, &QCommandLinkButton::clicked, [this] { + _ui.errorLabel->hide(); + if (_asyncAuth) + _asyncAuth->openBrowser(); + }); +} + +void OwncloudOAuthCredsPage::initializePage() +{ + OwncloudWizard *ocWizard = qobject_cast(wizard()); + Q_ASSERT(ocWizard); + ocWizard->account()->setCredentials(CredentialsFactory::create("http")); + _asyncAuth.reset(new OAuth(ocWizard->account().data(), this)); + connect(_asyncAuth.data(), &OAuth::result, this, &OwncloudOAuthCredsPage::asyncAuthResult, Qt::QueuedConnection); + _asyncAuth->start(); + wizard()->hide(); +} + +void OCC::OwncloudOAuthCredsPage::cleanupPage() +{ + // The next or back button was activated, show the wizard again + wizard()->show(); + _asyncAuth.reset(); +} + +void OwncloudOAuthCredsPage::asyncAuthResult(OAuth::Result r, const QString &user, + const QString &token, const QString &refreshToken) +{ + switch (r) { + case OAuth::NotSupported: { + /* OAuth not supported (can't open browser), fallback to HTTP credentials */ + OwncloudWizard *ocWizard = qobject_cast(wizard()); + ocWizard->back(); + ocWizard->setAuthType(WizardCommon::HttpCreds); + break; + } + case OAuth::Error: + /* Error while getting the access token. (Timeout, or the server did not accept our client credentials */ + _ui.errorLabel->show(); + wizard()->show(); + break; + case OAuth::LoggedIn: { + _token = token; + _user = user; + _refreshToken = refreshToken; + OwncloudWizard *ocWizard = qobject_cast(wizard()); + Q_ASSERT(ocWizard); + emit connectToOCUrl(ocWizard->account()->url().toString()); + break; + } + } +} + +int OwncloudOAuthCredsPage::nextId() const +{ + return WizardCommon::Page_AdvancedSetup; +} + +void OwncloudOAuthCredsPage::setConnected() +{ + wizard()->show(); +} + +AbstractCredentials *OwncloudOAuthCredsPage::getCredentials() const +{ + OwncloudWizard *ocWizard = qobject_cast(wizard()); + Q_ASSERT(ocWizard); + return new HttpCredentialsGui(_user, _token, _refreshToken, + ocWizard->_clientSslCertificate, ocWizard->_clientSslKey); +} + +bool OwncloudOAuthCredsPage::isComplete() const +{ + return false; /* We can never go forward manually */ +} + +} // namespace OCC diff --git a/src/gui/wizard/owncloudoauthcredspage.h b/src/gui/wizard/owncloudoauthcredspage.h new file mode 100644 index 0000000000..f51a1896a0 --- /dev/null +++ b/src/gui/wizard/owncloudoauthcredspage.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) by Olivier Goffart + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include "wizard/abstractcredswizardpage.h" +#include "accountfwd.h" +#include "creds/oauth.h" + +#include "ui_owncloudoauthcredspage.h" + + +namespace OCC { + + +class OwncloudOAuthCredsPage : public AbstractCredentialsWizardPage +{ + Q_OBJECT +public: + OwncloudOAuthCredsPage(); + + AbstractCredentials *getCredentials() const Q_DECL_OVERRIDE; + + void initializePage() Q_DECL_OVERRIDE; + void cleanupPage() override; + int nextId() const Q_DECL_OVERRIDE; + void setConnected(); + bool isComplete() const override; + +public Q_SLOTS: + void asyncAuthResult(OAuth::Result, const QString &user, const QString &token, + const QString &reniewToken); + +signals: + void connectToOCUrl(const QString &); + +public: + QString _user; + QString _token; + QString _refreshToken; + QScopedPointer _asyncAuth; + Ui_OwncloudOAuthCredsPage _ui; +}; + +} // namespace OCC diff --git a/src/gui/wizard/owncloudoauthcredspage.ui b/src/gui/wizard/owncloudoauthcredspage.ui new file mode 100644 index 0000000000..7b20b6cc45 --- /dev/null +++ b/src/gui/wizard/owncloudoauthcredspage.ui @@ -0,0 +1,87 @@ + + + OwncloudOAuthCredsPage + + + + 0 + 0 + 424 + 373 + + + + Form + + + + + + TextLabel + + + Qt::RichText + + + Qt::AlignCenter + + + true + + + + + + + Please switch to your browser to proceed. + + + true + + + + + + + An error occured while connecting. Please try again. + + + Qt::PlainText + + + + + + + Re-open Browser + + + + + + + Qt::Vertical + + + + 20 + 127 + + + + + + + + TextLabel + + + Qt::RichText + + + + + + + + diff --git a/src/gui/wizard/owncloudsetuppage.cpp b/src/gui/wizard/owncloudsetuppage.cpp index 39710e8923..271c943a2c 100644 --- a/src/gui/wizard/owncloudsetuppage.cpp +++ b/src/gui/wizard/owncloudsetuppage.cpp @@ -203,6 +203,8 @@ int OwncloudSetupPage::nextId() const { if (_authType == WizardCommon::HttpCreds) { return WizardCommon::Page_HttpCreds; + } else if (_authType == WizardCommon::OAuth) { + return WizardCommon::Page_OAuthCreds; } else { return WizardCommon::Page_ShibbolethCreds; } diff --git a/src/gui/wizard/owncloudwizard.cpp b/src/gui/wizard/owncloudwizard.cpp index 84068ddd58..9fb20ced65 100644 --- a/src/gui/wizard/owncloudwizard.cpp +++ b/src/gui/wizard/owncloudwizard.cpp @@ -20,6 +20,7 @@ #include "wizard/owncloudwizard.h" #include "wizard/owncloudsetuppage.h" #include "wizard/owncloudhttpcredspage.h" +#include "wizard/owncloudoauthcredspage.h" #ifndef NO_SHIBBOLETH #include "wizard/owncloudshibbolethcredspage.h" #endif @@ -42,12 +43,11 @@ OwncloudWizard::OwncloudWizard(QWidget *parent) , _account(0) , _setupPage(new OwncloudSetupPage(this)) , _httpCredsPage(new OwncloudHttpCredsPage(this)) - , + , _browserCredsPage(new OwncloudOAuthCredsPage) #ifndef NO_SHIBBOLETH - _shibbolethCredsPage(new OwncloudShibbolethCredsPage) - , + , _shibbolethCredsPage(new OwncloudShibbolethCredsPage) #endif - _advancedSetupPage(new OwncloudAdvancedSetupPage) + , _advancedSetupPage(new OwncloudAdvancedSetupPage) , _resultPage(new OwncloudWizardResultPage) , _credentialsPage(0) , _setupLog() @@ -55,6 +55,7 @@ OwncloudWizard::OwncloudWizard(QWidget *parent) setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); setPage(WizardCommon::Page_ServerSetup, _setupPage); setPage(WizardCommon::Page_HttpCreds, _httpCredsPage); + setPage(WizardCommon::Page_OAuthCreds, _browserCredsPage); #ifndef NO_SHIBBOLETH setPage(WizardCommon::Page_ShibbolethCreds, _shibbolethCredsPage); #endif @@ -70,6 +71,7 @@ OwncloudWizard::OwncloudWizard(QWidget *parent) connect(this, SIGNAL(currentIdChanged(int)), SLOT(slotCurrentPageChanged(int))); connect(_setupPage, SIGNAL(determineAuthType(QString)), SIGNAL(determineAuthType(QString))); connect(_httpCredsPage, SIGNAL(connectToOCUrl(QString)), SIGNAL(connectToOCUrl(QString))); + connect(_browserCredsPage, SIGNAL(connectToOCUrl(QString)), SIGNAL(connectToOCUrl(QString))); #ifndef NO_SHIBBOLETH connect(_shibbolethCredsPage, SIGNAL(connectToOCUrl(QString)), SIGNAL(connectToOCUrl(QString))); #endif @@ -142,6 +144,10 @@ void OwncloudWizard::successfulStep() _httpCredsPage->setConnected(); break; + case WizardCommon::Page_OAuthCreds: + _browserCredsPage->setConnected(); + break; + #ifndef NO_SHIBBOLETH case WizardCommon::Page_ShibbolethCreds: _shibbolethCredsPage->setConnected(); @@ -169,7 +175,9 @@ void OwncloudWizard::setAuthType(WizardCommon::AuthType type) _credentialsPage = _shibbolethCredsPage; } else #endif - { + if (type == WizardCommon::OAuth) { + _credentialsPage = _browserCredsPage; + } else { _credentialsPage = _httpCredsPage; } next(); @@ -193,6 +201,11 @@ void OwncloudWizard::slotCurrentPageChanged(int id) } setOption(QWizard::HaveCustomButton1, id == WizardCommon::Page_AdvancedSetup); + if (id == WizardCommon::Page_AdvancedSetup && _credentialsPage == _browserCredsPage) { + // For OAuth, disable the back button in the Page_AdvancedSetup because we don't want + // to re-open the browser. + button(QWizard::BackButton)->setEnabled(false); + } } void OwncloudWizard::displayError(const QString &msg, bool retryHTTPonly) diff --git a/src/gui/wizard/owncloudwizard.h b/src/gui/wizard/owncloudwizard.h index 4b9097f74e..78f5bb444f 100644 --- a/src/gui/wizard/owncloudwizard.h +++ b/src/gui/wizard/owncloudwizard.h @@ -30,6 +30,7 @@ Q_DECLARE_LOGGING_CATEGORY(lcWizard) class OwncloudSetupPage; class OwncloudHttpCredsPage; +class OwncloudOAuthCredsPage; #ifndef NO_SHIBBOLETH class OwncloudShibbolethCredsPage; #endif @@ -94,6 +95,7 @@ private: AccountPtr _account; OwncloudSetupPage *_setupPage; OwncloudHttpCredsPage *_httpCredsPage; + OwncloudOAuthCredsPage *_browserCredsPage; #ifndef NO_SHIBBOLETH OwncloudShibbolethCredsPage *_shibbolethCredsPage; #endif @@ -102,6 +104,8 @@ private: AbstractCredentialsWizardPage *_credentialsPage; QStringList _setupLog; + + friend class OwncloudSetupWizard; }; } // namespace OCC diff --git a/src/gui/wizard/owncloudwizardcommon.h b/src/gui/wizard/owncloudwizardcommon.h index bf6248c2c6..eaad007046 100644 --- a/src/gui/wizard/owncloudwizardcommon.h +++ b/src/gui/wizard/owncloudwizardcommon.h @@ -30,7 +30,8 @@ namespace WizardCommon { enum AuthType { HttpCreds, - Shibboleth + Shibboleth, + OAuth }; enum SyncMode { @@ -42,6 +43,7 @@ namespace WizardCommon { Page_ServerSetup, Page_HttpCreds, Page_ShibbolethCreds, + Page_OAuthCreds, Page_AdvancedSetup, Page_Result }; diff --git a/src/libsync/CMakeLists.txt b/src/libsync/CMakeLists.txt index a2bd91322e..65d68823d1 100644 --- a/src/libsync/CMakeLists.txt +++ b/src/libsync/CMakeLists.txt @@ -9,7 +9,6 @@ include_directories(${CMAKE_SOURCE_DIR}/csync/src ${CMAKE_BINARY_DIR}/csync ${CMAKE_BINARY_DIR}/csync/src ) -include_directories(${OPENSSL_INCLUDE_DIR}) if ( APPLE ) list(APPEND OS_SPECIFIC_LINK_LIBRARIES @@ -108,7 +107,6 @@ list(APPEND libsync_LINK_TARGETS ${QT_LIBRARIES} ocsync ${OS_SPECIFIC_LINK_LIBRARIES} - ${OPENSSL_LIBRARIES} ) if(QTKEYCHAIN_FOUND OR QT5KEYCHAIN_FOUND) diff --git a/src/libsync/abstractnetworkjob.cpp b/src/libsync/abstractnetworkjob.cpp index 25fe28ce38..71984a66ab 100644 --- a/src/libsync/abstractnetworkjob.cpp +++ b/src/libsync/abstractnetworkjob.cpp @@ -152,10 +152,12 @@ void AbstractNetworkJob::slotFinished() } if (_reply->error() != QNetworkReply::NoError) { - qCWarning(lcNetworkJob) << _reply->error() << errorString() - << _reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); - if (_reply->error() == QNetworkReply::ProxyAuthenticationRequiredError) { - qCWarning(lcNetworkJob) << _reply->rawHeader("Proxy-Authenticate"); + if (!_ignoreCredentialFailure || _reply->error() != QNetworkReply::AuthenticationRequiredError) { + qCWarning(lcNetworkJob) << _reply->error() << errorString() + << _reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); + if (_reply->error() == QNetworkReply::ProxyAuthenticationRequiredError) { + qCWarning(lcNetworkJob) << _reply->rawHeader("Proxy-Authenticate"); + } } emit networkError(_reply); } diff --git a/src/libsync/checksums.cpp b/src/libsync/checksums.cpp index 00818442fb..eaf36379cc 100644 --- a/src/libsync/checksums.cpp +++ b/src/libsync/checksums.cpp @@ -81,6 +81,8 @@ Q_LOGGING_CATEGORY(lcChecksums, "sync.checksums", QtInfoMsg) QByteArray makeChecksumHeader(const QByteArray &checksumType, const QByteArray &checksum) { + if (checksumType.isEmpty() || checksum.isEmpty()) + return QByteArray(); QByteArray header = checksumType; header.append(':'); header.append(checksum); @@ -105,6 +107,16 @@ bool parseChecksumHeader(const QByteArray &header, QByteArray *type, QByteArray return true; } + +QByteArray parseChecksumHeaderType(const QByteArray &header) +{ + const auto idx = header.indexOf(':'); + if (idx < 0) { + return QByteArray(); + } + return header.left(idx); +} + bool uploadChecksumEnabled() { static bool enabled = qgetenv("OWNCLOUD_DISABLE_CHECKSUM_UPLOAD").isEmpty(); @@ -214,39 +226,27 @@ void ValidateChecksumHeader::slotChecksumCalculated(const QByteArray &checksumTy emit validated(checksumType, checksum); } -CSyncChecksumHook::CSyncChecksumHook(SyncJournalDb *journal) - : _journal(journal) +CSyncChecksumHook::CSyncChecksumHook() { } -const char *CSyncChecksumHook::hook(const char *path, uint32_t checksumTypeId, void *this_obj) +const char *CSyncChecksumHook::hook(const char *path, const char *otherChecksumHeader, void * /*this_obj*/) { - CSyncChecksumHook *checksumHook = static_cast(this_obj); - QByteArray checksum = checksumHook->compute(QString::fromUtf8(path), checksumTypeId); + QByteArray type = parseChecksumHeaderType(QByteArray(otherChecksumHeader)); + if (type.isEmpty()) + return NULL; + + QByteArray checksum = ComputeChecksum::computeNow(path, type); if (checksum.isNull()) { + qCWarning(lcChecksums) << "Failed to compute checksum" << type << "for" << path; return NULL; } - char *result = (char *)malloc(checksum.size() + 1); - memcpy(result, checksum.constData(), checksum.size()); - result[checksum.size()] = 0; + QByteArray checksumHeader = makeChecksumHeader(type, checksum); + char *result = (char *)malloc(checksumHeader.size() + 1); + memcpy(result, checksumHeader.constData(), checksumHeader.size()); + result[checksumHeader.size()] = 0; return result; } -QByteArray CSyncChecksumHook::compute(const QString &path, int checksumTypeId) -{ - QByteArray checksumType = _journal->getChecksumType(checksumTypeId); - if (checksumType.isEmpty()) { - qCWarning(lcChecksums) << "Checksum type" << checksumTypeId << "not found"; - return QByteArray(); - } - - QByteArray checksum = ComputeChecksum::computeNow(path, checksumType); - if (checksum.isNull()) { - qCWarning(lcChecksums) << "Failed to compute checksum" << checksumType << "for" << path; - return QByteArray(); - } - - return checksum; -} } diff --git a/src/libsync/checksums.h b/src/libsync/checksums.h index 5d88b0bcb6..a7259f7ecc 100644 --- a/src/libsync/checksums.h +++ b/src/libsync/checksums.h @@ -31,6 +31,9 @@ QByteArray makeChecksumHeader(const QByteArray &checksumType, const QByteArray & /// Parses a checksum header bool parseChecksumHeader(const QByteArray &header, QByteArray *type, QByteArray *checksum); +/// Convenience for getting the type from a checksum header, null if none +QByteArray parseChecksumHeaderType(const QByteArray &header); + /// Checks OWNCLOUD_DISABLE_CHECKSUM_UPLOAD bool uploadChecksumEnabled(); @@ -119,20 +122,15 @@ class OWNCLOUDSYNC_EXPORT CSyncChecksumHook : public QObject { Q_OBJECT public: - explicit CSyncChecksumHook(SyncJournalDb *journal); + explicit CSyncChecksumHook(); /** - * Returns the checksum value for \a path for the given \a checksumTypeId. + * Returns the checksum value for \a path that is comparable to \a otherChecksum. * * Called from csync, where a instance of CSyncChecksumHook has * to be set as userdata. * The return value will be owned by csync. */ - static const char *hook(const char *path, uint32_t checksumTypeId, void *this_obj); - - QByteArray compute(const QString &path, int checksumTypeId); - -private: - SyncJournalDb *_journal; + static const char *hook(const char *path, const char *otherChecksumHeader, void *this_obj); }; } diff --git a/src/libsync/connectionvalidator.cpp b/src/libsync/connectionvalidator.cpp index 5816655be7..0b5897f1ff 100644 --- a/src/libsync/connectionvalidator.cpp +++ b/src/libsync/connectionvalidator.cpp @@ -139,7 +139,9 @@ void ConnectionValidator::slotStatusFound(const QUrl &url, const QJsonObject &in return; } - if (info["maintenance"].toBool()) { + // Check for maintenance mode: Servers send "true", so go through QVariant + // to parse it correctly. + if (info["maintenance"].toVariant().toBool()) { reportResult(MaintenanceMode); return; } diff --git a/src/libsync/creds/httpcredentials.cpp b/src/libsync/creds/httpcredentials.cpp index f5e3cc5820..ce37ce93ee 100644 --- a/src/libsync/creds/httpcredentials.cpp +++ b/src/libsync/creds/httpcredentials.cpp @@ -18,6 +18,8 @@ #include #include #include +#include +#include #include @@ -37,6 +39,7 @@ Q_LOGGING_CATEGORY(lcHttpCredentials, "sync.credentials.http", QtInfoMsg) namespace { const char userC[] = "user"; + const char isOAuthC[] = "oauth"; const char clientCertificatePEMC[] = "_clientCertificatePEM"; const char clientKeyPEMC[] = "_clientKeyPEM"; const char authenticationFailedC[] = "owncloud-authentication-failed"; @@ -54,9 +57,20 @@ public: protected: QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData) Q_DECL_OVERRIDE { - QByteArray credHash = QByteArray(_cred->user().toUtf8() + ":" + _cred->password().toUtf8()).toBase64(); QNetworkRequest req(request); - req.setRawHeader(QByteArray("Authorization"), QByteArray("Basic ") + credHash); + if (!_cred->password().isEmpty()) { + if (_cred->isUsingOAuth()) { + req.setRawHeader("Authorization", "Bearer " + _cred->password().toUtf8()); + } else { + QByteArray credHash = QByteArray(_cred->user().toUtf8() + ":" + _cred->password().toUtf8()).toBase64(); + req.setRawHeader("Authorization", "Basic " + credHash); + } + } else if (!request.url().password().isEmpty()) { + // Typically the requests to get or refresh the OAuth access token. The client + // credentials are put in the URL from the code making the request. + QByteArray credHash = request.url().userInfo().toUtf8().toBase64(); + req.setRawHeader("Authorization", "Basic " + credHash); + } if (!_cred->_clientSslKey.isNull() && !_cred->_clientSslCertificate.isNull()) { // SSL configuration @@ -149,6 +163,13 @@ void HttpCredentials::fetchFromKeychain() // User must be fetched from config file fetchUser(); + if (!_ready && !_refreshToken.isEmpty()) { + // This happens if the credentials are still loaded from the keychain, bur we are called + // here because the auth is invalid, so this means we simply need to refresh the credentials + refreshAccessToken(); + return; + } + const QString kck = keychainKey(_account->url().toString(), _user); if (_ready) { @@ -160,7 +181,6 @@ void HttpCredentials::fetchFromKeychain() addSettingsToJob(_account, job); job->setInsecureFallback(false); job->setKey(kck); - qCDebug(lcHttpCredentials) << "-------- ----->" << _clientSslCertificate << _clientSslKey; connect(job, SIGNAL(finished(QKeychain::Job *)), SLOT(slotReadClientCertPEMJobDone(QKeychain::Job *))); job->start(); @@ -236,7 +256,13 @@ bool HttpCredentials::stillValid(QNetworkReply *reply) void HttpCredentials::slotReadJobDone(QKeychain::Job *incomingJob) { QKeychain::ReadPasswordJob *job = static_cast(incomingJob); - _password = job->textData(); + + bool isOauth = _account->credentialSetting(QLatin1String(isOAuthC)).toBool(); + if (isOauth) { + _refreshToken = job->textData(); + } else { + _password = job->textData(); + } if (_user.isEmpty()) { qCWarning(lcHttpCredentials) << "Strange: User is empty!"; @@ -244,7 +270,9 @@ void HttpCredentials::slotReadJobDone(QKeychain::Job *incomingJob) QKeychain::Error error = job->error(); - if (!_password.isEmpty() && error == NoError) { + if (!_refreshToken.isEmpty() && error == NoError) { + refreshAccessToken(); + } else if (!_password.isEmpty() && error == NoError) { // All cool, the keychain did not come back with error. // Still, the password can be empty which indicates a problem and // the password dialog has to be opened. @@ -262,6 +290,41 @@ void HttpCredentials::slotReadJobDone(QKeychain::Job *incomingJob) } } +void HttpCredentials::refreshAccessToken() +{ + QUrl requestToken(_account->url().toString() + + QLatin1String("/index.php/apps/oauth2/api/v1/token?grant_type=refresh_token&refresh_token=") + + _refreshToken); + requestToken.setUserName(Theme::instance()->oauthClientId()); + requestToken.setPassword(Theme::instance()->oauthClientSecret()); + QNetworkRequest req; + req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + auto reply = _account->sendRequest("POST", requestToken, req); + QTimer::singleShot(30 * 1000, reply, &QNetworkReply::abort); + QObject::connect(reply, &QNetworkReply::finished, this, [this, reply] { + reply->deleteLater(); + auto jsonData = reply->readAll(); + QJsonParseError jsonParseError; + QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object(); + QString accessToken = json["access_token"].toString(); + if (reply->error() != QNetworkReply::NoError || jsonParseError.error != QJsonParseError::NoError || json.isEmpty()) { + // Network error maybe? + qCWarning(lcHttpCredentials) << "Error while refreshing the token" << reply->errorString() << jsonData << jsonParseError.errorString(); + } else if (accessToken.isEmpty()) { + // The token is no longer valid. + qCDebug(lcHttpCredentials) << "Expired refresh token. Logging out"; + _refreshToken.clear(); + } else { + _ready = true; + _password = accessToken; + _refreshToken = json["refresh_token"].toString(); + persist(); + } + emit fetched(); + }); +} + + void HttpCredentials::invalidateToken() { if (!_password.isEmpty()) { @@ -279,6 +342,12 @@ void HttpCredentials::invalidateToken() return; } + if (!_refreshToken.isEmpty()) { + // Only invalidate the access_token (_password) but keep the _refreshToken in the keychain + // (when coming from forgetSensitiveData, the _refreshToken is cleared) + return; + } + DeletePasswordJob *job = new DeletePasswordJob(Theme::instance()->appName()); addSettingsToJob(_account, job); job->setInsecureFallback(true); @@ -315,6 +384,9 @@ void HttpCredentials::clearQNAMCache() void HttpCredentials::forgetSensitiveData() { + // need to be done before invalidateToken, so it actually deletes the refresh_token from the keychain + _refreshToken.clear(); + invalidateToken(); _previousPassword.clear(); } @@ -327,6 +399,7 @@ void HttpCredentials::persist() } _account->setCredentialSetting(QLatin1String(userC), _user); + _account->setCredentialSetting(QLatin1String(isOAuthC), isUsingOAuth()); // write cert WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName()); @@ -359,7 +432,7 @@ void HttpCredentials::slotWriteClientKeyPEMJobDone(Job *incomingJob) job->setInsecureFallback(false); connect(job, SIGNAL(finished(QKeychain::Job *)), SLOT(slotWriteJobDone(QKeychain::Job *))); job->setKey(keychainKey(_account->url().toString(), _user)); - job->setTextData(_password); + job->setTextData(isUsingOAuth() ? _refreshToken : _password); job->start(); } @@ -378,6 +451,8 @@ void HttpCredentials::slotWriteJobDone(QKeychain::Job *job) void HttpCredentials::slotAuthentication(QNetworkReply *reply, QAuthenticator *authenticator) { + if (!_ready) + return; Q_UNUSED(authenticator) // Because of issue #4326, we need to set the login and password manually at every requests // Thus, if we reach this signal, those credentials were invalid and we terminate. diff --git a/src/libsync/creds/httpcredentials.h b/src/libsync/creds/httpcredentials.h index 14118e924b..45b01c5ee5 100644 --- a/src/libsync/creds/httpcredentials.h +++ b/src/libsync/creds/httpcredentials.h @@ -32,6 +32,43 @@ class ReadPasswordJob; namespace OCC { +/* + The authentication system is this way because of Shibboleth. + There used to be two different ways to authenticate: Shibboleth and HTTP Basic Auth. + AbstractCredentials can be inherited from both ShibbolethCrendentials and HttpCredentials. + + HttpCredentials is then split in HttpCredentials and HttpCredentialsGui. + + This class handle both HTTP Basic Auth and OAuth. But anything that needs GUI to ask the user + is in HttpCredentialsGui. + + The authentication mechanism looks like this. + + 1) First, AccountState will attempt to load the certificate from the keychain + + ----> fetchFromKeychain ------------------------> shortcut to refreshAccessToken if the cached + | } information is still valid + v } + slotReadClientCertPEMJobDone } There are first 3 QtKeychain jobs to fetch + | } the TLS client keys, if any, and the password + v } (or refresh token + slotReadClientKeyPEMJobDone } + | } + v + slotReadJobDone + | | + | +-------> emit fetched() if OAuth is not used + | + v + refreshAccessToken() + | + v + emit fetched() + + 2) If the credentials is still not valid when fetched() is emitted, the ui, will call askFromUser() + which is implemented in HttpCredentialsGui + + */ class OWNCLOUDSYNC_EXPORT HttpCredentials : public AbstractCredentials { Q_OBJECT @@ -48,15 +85,21 @@ public: bool stillValid(QNetworkReply *reply) Q_DECL_OVERRIDE; void persist() Q_DECL_OVERRIDE; QString user() const Q_DECL_OVERRIDE; + // the password or token QString password() const; void invalidateToken() Q_DECL_OVERRIDE; void forgetSensitiveData() Q_DECL_OVERRIDE; QString fetchUser(); virtual bool sslIsTrusted() { return false; } + void refreshAccessToken(); + // To fetch the user name as early as possible void setAccount(Account *account) Q_DECL_OVERRIDE; + // Whether we are using OAuth + bool isUsingOAuth() const { return !_refreshToken.isNull(); } + private Q_SLOTS: void slotAuthentication(QNetworkReply *, QAuthenticator *); @@ -71,7 +114,8 @@ private Q_SLOTS: protected: QString _user; - QString _password; + QString _password; // user's password, or access_token for OAuth + QString _refreshToken; // OAuth _refreshToken, set if OAuth is used. QString _previousPassword; QString _fetchErrorString; @@ -80,6 +124,7 @@ protected: QSslCertificate _clientSslCertificate; }; + } // namespace OCC #endif diff --git a/src/libsync/discoveryphase.cpp b/src/libsync/discoveryphase.cpp index c8f13784be..16b98faeb2 100644 --- a/src/libsync/discoveryphase.cpp +++ b/src/libsync/discoveryphase.cpp @@ -272,7 +272,8 @@ void DiscoverySingleDirectoryJob::start() << "http://owncloud.org/ns:id" << "http://owncloud.org/ns:downloadURL" << "http://owncloud.org/ns:dDC" - << "http://owncloud.org/ns:permissions"; + << "http://owncloud.org/ns:permissions" + << "http://owncloud.org/ns:checksums"; if (_isRootPath) props << "http://owncloud.org/ns:data-fingerprint"; @@ -294,6 +295,28 @@ void DiscoverySingleDirectoryJob::abort() } } +/** + * Returns the highest-quality checksum in a 'checksums' + * property retrieved from the server. + * + * Example: "ADLER32:1231 SHA1:ab124124 MD5:2131affa21" + * -> "SHA1:ab124124" + */ +static QByteArray findBestChecksum(const QByteArray &checksums) +{ + int i = 0; + // The order of the searches here defines the preference ordering. + if (-1 != (i = checksums.indexOf("SHA1:")) + || -1 != (i = checksums.indexOf("MD5:")) + || -1 != (i = checksums.indexOf("Adler32:"))) { + // Now i is the start of the best checksum + // Grab it until the next space or end of string. + auto checksum = checksums.mid(i); + return checksum.mid(0, checksum.indexOf(" ")); + } + return QByteArray(); +} + static csync_vio_file_stat_t *propertyMapToFileStat(const QMap &map) { csync_vio_file_stat_t *file_stat = csync_vio_file_stat_new(); @@ -343,6 +366,11 @@ static csync_vio_file_stat_t *propertyMapToFileStat(const QMap } else { qCWarning(lcDiscovery) << "permissions too large" << v; } + } else if (property == "checksums") { + QByteArray checksum = findBestChecksum(value.toUtf8()); + if (!checksum.isEmpty()) { + file_stat->checksumHeader = strdup(checksum.constData()); + } } } diff --git a/src/libsync/ownsql.cpp b/src/libsync/ownsql.cpp index 6db35fda5b..69dae78477 100644 --- a/src/libsync/ownsql.cpp +++ b/src/libsync/ownsql.cpp @@ -62,6 +62,12 @@ bool SqlDatabase::openHelper(const QString &filename, int sqliteFlags) if (_errId != SQLITE_OK) { qCWarning(lcSql) << "Error:" << _error << "for" << filename; + if (_errId == SQLITE_CANTOPEN) { + qCWarning(lcSql) << "CANTOPEN extended errcode: " << sqlite3_extended_errcode(_db); +#if SQLITE_VERSION_NUMBER >= 3012000 + qCWarning(lcSql) << "CANTOPEN system errno: " << sqlite3_system_errno(_db); +#endif + } close(); return false; } diff --git a/src/libsync/propagatedownload.cpp b/src/libsync/propagatedownload.cpp index f20651a456..8cc87ccac9 100644 --- a/src/libsync/propagatedownload.cpp +++ b/src/libsync/propagatedownload.cpp @@ -344,6 +344,41 @@ void PropagateDownloadFile::start() } } + // If we have a conflict where size and mtime are identical, + // compare the remote checksum to the local one. + // Maybe it's not a real conflict and no download is necessary! + if (_item->_instruction == CSYNC_INSTRUCTION_CONFLICT + && _item->_size == _item->log._other_size + && _item->_modtime == _item->log._other_modtime + && !_item->_checksumHeader.isEmpty()) { + qCDebug(lcPropagateDownload) << _item->_file << "may not need download, computing checksum"; + auto computeChecksum = new ComputeChecksum(this); + computeChecksum->setChecksumType(parseChecksumHeaderType(_item->_checksumHeader)); + connect(computeChecksum, SIGNAL(done(QByteArray, QByteArray)), + SLOT(conflictChecksumComputed(QByteArray, QByteArray))); + computeChecksum->start(propagator()->getFilePath(_item->_file)); + return; + } + + startDownload(); +} + +void PropagateDownloadFile::conflictChecksumComputed(const QByteArray &checksumType, const QByteArray &checksum) +{ + if (makeChecksumHeader(checksumType, checksum) == _item->_checksumHeader) { + qCDebug(lcPropagateDownload) << _item->_file << "remote and local checksum match"; + // No download necessary, just update metadata + updateMetadata(/*isConflict=*/false); + return; + } + startDownload(); +} + +void PropagateDownloadFile::startDownload() +{ + if (propagator()->_abortRequested.fetchAndAddRelaxed(0)) + return; + // do a klaas' case clash check. if (propagator()->localFileNameClash(_item->_file)) { done(SyncFileItem::NormalError, tr("File %1 can not be downloaded because of a local file name clash!").arg(QDir::toNativeSeparators(_item->_file))); @@ -513,6 +548,11 @@ void PropagateDownloadFile::slotGetFinished() } else if (fileNotFound) { job->setErrorString(tr("File was deleted from server")); job->setErrorStatus(SyncFileItem::SoftError); + + // As a precaution against bugs that cause our database and the + // reality on the server to diverge, rediscover this folder on the + // next sync run. + propagator()->_journal->avoidReadFromDbOnNextSync(_item->_file); } SyncFileItem::Status status = job->errorStatus(); @@ -667,7 +707,7 @@ namespace { // Anonymous namespace for the recall feature continue; } - qCInfo(lcPropagateDownload) << "Recalling" << localRecalledFile << "Checksum:" << record._contentChecksumType << record._contentChecksum; + qCInfo(lcPropagateDownload) << "Recalling" << localRecalledFile << "Checksum:" << record._checksumHeader; QString targetPath = makeRecallFileName(recalledFile); @@ -712,8 +752,7 @@ void PropagateDownloadFile::transmissionChecksumValidated(const QByteArray &chec void PropagateDownloadFile::contentChecksumComputed(const QByteArray &checksumType, const QByteArray &checksum) { - _item->_contentChecksum = checksum; - _item->_contentChecksumType = checksumType; + _item->_checksumHeader = makeChecksumHeader(checksumType, checksum); downloadFinished(); } @@ -822,6 +861,13 @@ void PropagateDownloadFile::downloadFinished() // Get up to date information for the journal. _item->_size = FileSystem::getSize(fn); + updateMetadata(isConflict); +} + +void PropagateDownloadFile::updateMetadata(bool isConflict) +{ + QString fn = propagator()->getFilePath(_item->_file); + if (!propagator()->_journal->setFileRecord(SyncJournalFileRecord(*_item, fn))) { done(SyncFileItem::FatalError, tr("Error writing metadata to the database")); return; diff --git a/src/libsync/propagatedownload.h b/src/libsync/propagatedownload.h index 69962131b5..26361541c2 100644 --- a/src/libsync/propagatedownload.h +++ b/src/libsync/propagatedownload.h @@ -106,6 +106,40 @@ private slots: /** * @brief The PropagateDownloadFile class * @ingroup libsync + * + * This is the flow: + +\code{.unparsed} + start() + | + | deleteExistingFolder() if enabled + | + +--> mtime and size identical? + | then compute the local checksum + | done?-> conflictChecksumComputed() + | | + | checksum differs? | + +-> startDownload() <--------------------------+ + | | + +-> run a GETFileJob | checksum identical? + | + done?-> slotGetFinished() | + | | + +-> validate checksum header | + | + done?-> transmissionChecksumValidated() | + | | + +-> compute the content checksum | + | + done?-> contentChecksumComputed() | + | | + +-> downloadFinished() | + | | + +------------------+ | + | | + +-> updateMetadata() <-------------------------+ + +\endcode */ class PropagateDownloadFile : public PropagateItemJob { @@ -136,11 +170,22 @@ public: void setDeleteExistingFolder(bool enabled); private slots: + /// Called when ComputeChecksum on the local file finishes, + /// maybe the local and remote checksums are identical? + void conflictChecksumComputed(const QByteArray &checksumType, const QByteArray &checksum); + /// Called to start downloading the remote file + void startDownload(); + /// Called when the GETFileJob finishes void slotGetFinished(); - void abort() Q_DECL_OVERRIDE; + /// Called when the download's checksum header was validated void transmissionChecksumValidated(const QByteArray &checksumType, const QByteArray &checksum); + /// Called when the download's checksum computation is done void contentChecksumComputed(const QByteArray &checksumType, const QByteArray &checksum); void downloadFinished(); + /// Called when it's time to update the db metadata + void updateMetadata(bool isConflict); + + void abort() Q_DECL_OVERRIDE; void slotDownloadProgress(qint64, qint64); void slotChecksumFail(const QString &errMsg); diff --git a/src/libsync/propagateremotemove.cpp b/src/libsync/propagateremotemove.cpp index 7f4f96c33c..69979b1a97 100644 --- a/src/libsync/propagateremotemove.cpp +++ b/src/libsync/propagateremotemove.cpp @@ -173,8 +173,7 @@ void PropagateRemoteMove::finalize() SyncJournalFileRecord record(*_item, propagator()->getFilePath(_item->_renameTarget)); record._path = _item->_renameTarget; if (oldRecord.isValid()) { - record._contentChecksum = oldRecord._contentChecksum; - record._contentChecksumType = oldRecord._contentChecksumType; + record._checksumHeader = oldRecord._checksumHeader; if (record._fileSize != oldRecord._fileSize) { qCWarning(lcPropagateRemoteMove) << "File sizes differ on server vs sync journal: " << record._fileSize << oldRecord._fileSize; diff --git a/src/libsync/propagateupload.cpp b/src/libsync/propagateupload.cpp index 93849dff73..f3b0e8336b 100644 --- a/src/libsync/propagateupload.cpp +++ b/src/libsync/propagateupload.cpp @@ -231,9 +231,10 @@ void PropagateUploadFileCommon::slotComputeContentChecksum() QByteArray checksumType = contentChecksumType(); // Maybe the discovery already computed the checksum? - if (_item->_contentChecksumType == checksumType - && !_item->_contentChecksum.isEmpty()) { - slotComputeTransmissionChecksum(checksumType, _item->_contentChecksum); + QByteArray existingChecksumType, existingChecksum; + parseChecksumHeader(_item->_checksumHeader, &existingChecksumType, &existingChecksum); + if (existingChecksumType == checksumType) { + slotComputeTransmissionChecksum(checksumType, existingChecksum); return; } @@ -250,8 +251,7 @@ void PropagateUploadFileCommon::slotComputeContentChecksum() void PropagateUploadFileCommon::slotComputeTransmissionChecksum(const QByteArray &contentChecksumType, const QByteArray &contentChecksum) { - _item->_contentChecksum = contentChecksum; - _item->_contentChecksumType = contentChecksumType; + _item->_checksumHeader = makeChecksumHeader(contentChecksumType, contentChecksum); #ifdef WITH_TESTING _stopWatch.addLapTime(QLatin1String("ContentChecksum")); @@ -288,13 +288,11 @@ void PropagateUploadFileCommon::slotStartUpload(const QByteArray &transmissionCh // When we start chunks, we will add it again, once for every chunks. propagator()->_activeJobList.removeOne(this); - _transmissionChecksum = transmissionChecksum; - _transmissionChecksumType = transmissionChecksumType; + _transmissionChecksumHeader = makeChecksumHeader(transmissionChecksumType, transmissionChecksum); - if (_item->_contentChecksum.isEmpty() && _item->_contentChecksumType.isEmpty()) { - // If the _contentChecksum was not set, reuse the transmission checksum as the content checksum. - _item->_contentChecksum = transmissionChecksum; - _item->_contentChecksumType = transmissionChecksumType; + // If no checksum header was not set, reuse the transmission checksum as the content checksum. + if (_item->_checksumHeader.isEmpty()) { + _item->_checksumHeader = _transmissionChecksumHeader; } const QString fullFilePath = propagator()->getFilePath(_item->_file); diff --git a/src/libsync/propagateupload.h b/src/libsync/propagateupload.h index e9130d9340..5e16b5ff98 100644 --- a/src/libsync/propagateupload.h +++ b/src/libsync/propagateupload.h @@ -226,8 +226,7 @@ protected: Utility::StopWatch _stopWatch; #endif - QByteArray _transmissionChecksum; - QByteArray _transmissionChecksumType; + QByteArray _transmissionChecksumHeader; public: PropagateUploadFileCommon(OwncloudPropagator *propagator, const SyncFileItemPtr &item) diff --git a/src/libsync/propagateuploadng.cpp b/src/libsync/propagateuploadng.cpp index d6c213389d..45768102da 100644 --- a/src/libsync/propagateuploadng.cpp +++ b/src/libsync/propagateuploadng.cpp @@ -283,9 +283,8 @@ void PropagateUploadFileNG::startNextChunk() if (!ifMatch.isEmpty()) { headers["If"] = "<" + destination.toUtf8() + "> ([" + ifMatch + "])"; } - if (!_transmissionChecksumType.isEmpty()) { - headers[checkSumHeaderC] = makeChecksumHeader( - _transmissionChecksumType, _transmissionChecksum); + if (!_transmissionChecksumHeader.isEmpty()) { + headers[checkSumHeaderC] = _transmissionChecksumHeader; } headers["OC-Total-Length"] = QByteArray::number(fileSize); diff --git a/src/libsync/propagateuploadv1.cpp b/src/libsync/propagateuploadv1.cpp index f8a88020dd..b48b433ab7 100644 --- a/src/libsync/propagateuploadv1.cpp +++ b/src/libsync/propagateuploadv1.cpp @@ -103,9 +103,8 @@ void PropagateUploadFileV1::startNextChunk() } qCDebug(lcPropagateUpload) << _chunkCount << isFinalChunk << chunkStart << currentChunkSize; - if (isFinalChunk && !_transmissionChecksumType.isEmpty()) { - headers[checkSumHeaderC] = makeChecksumHeader( - _transmissionChecksumType, _transmissionChecksum); + if (isFinalChunk && !_transmissionChecksumHeader.isEmpty()) { + headers[checkSumHeaderC] = _transmissionChecksumHeader; } const QString fileName = propagator()->getFilePath(_item->_file); diff --git a/src/libsync/propagatorjobs.cpp b/src/libsync/propagatorjobs.cpp index bda94fa11f..5cf17b26ac 100644 --- a/src/libsync/propagatorjobs.cpp +++ b/src/libsync/propagatorjobs.cpp @@ -241,8 +241,7 @@ void PropagateLocalRename::start() SyncJournalFileRecord record(*_item, targetFile); record._path = _item->_renameTarget; if (oldRecord.isValid()) { - record._contentChecksum = oldRecord._contentChecksum; - record._contentChecksumType = oldRecord._contentChecksumType; + record._checksumHeader = oldRecord._checksumHeader; } if (!_item->_isDirectory) { // Directories are saved at the end diff --git a/src/libsync/syncengine.cpp b/src/libsync/syncengine.cpp index 12d6c879cd..faf003fbf6 100644 --- a/src/libsync/syncengine.cpp +++ b/src/libsync/syncengine.cpp @@ -52,7 +52,7 @@ namespace OCC { -Q_LOGGING_CATEGORY(lcEngine, "sync.engine") +Q_LOGGING_CATEGORY(lcEngine, "sync.engine", QtInfoMsg) bool SyncEngine::s_anySyncRunning = false; @@ -73,7 +73,6 @@ SyncEngine::SyncEngine(AccountPtr account, const QString &localPath, , _backInTimeFiles(0) , _uploadLimit(0) , _downloadLimit(0) - , _checksum_hook(journal) , _anotherSyncNeeded(NoFollowUpSync) { qRegisterMetaType("SyncFileItem"); @@ -426,9 +425,12 @@ int SyncEngine::treewalkFile(TREE_WALK_FILE *file, bool remote) } // Sometimes the discovery computes checksums for local files - if (!remote && file->checksum && file->checksumTypeId) { - item->_contentChecksum = QByteArray(file->checksum); - item->_contentChecksumType = _journal->getChecksumType(file->checksumTypeId); + if (!remote && file->checksumHeader) { + item->_checksumHeader = QByteArray(file->checksumHeader); + } + // For conflicts, store the remote checksum there + if (remote && item->_instruction == CSYNC_INSTRUCTION_CONFLICT && file->checksumHeader) { + item->_checksumHeader = QByteArray(file->checksumHeader); } // record the seen files to be able to clean the journal later @@ -1159,6 +1161,7 @@ void SyncEngine::checkForPermission(SyncFileItemVector &syncItems) bool selectiveListOk; auto selectiveSyncBlackList = _journal->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &selectiveListOk); std::sort(selectiveSyncBlackList.begin(), selectiveSyncBlackList.end()); + SyncFileItemPtr needle; for (SyncFileItemVector::iterator it = syncItems.begin(); it != syncItems.end(); ++it) { if ((*it)->_direction != SyncFileItem::Up) { @@ -1177,8 +1180,41 @@ void SyncEngine::checkForPermission(SyncFileItemVector &syncItems) (*it)->_errorString = tr("Ignored because of the \"choose what to sync\" blacklist"); if ((*it)->_isDirectory) { + auto it_base = it; for (SyncFileItemVector::iterator it_next = it + 1; it_next != syncItems.end() && (*it_next)->_file.startsWith(path); ++it_next) { it = it_next; + // We want to ignore almost all instructions for items inside selective-sync excluded folders. + //The exception are DOWN/REMOVE actions that remove local files and folders that are + //guaranteed to be up-to-date with their server copies. + if ((*it)->_direction == SyncFileItem::Down && (*it)->_instruction == CSYNC_INSTRUCTION_REMOVE) { + // We need to keep the "delete" items. So we need to un-ignore parent directories + QString parentDir = (*it)->_file; + do { + parentDir = QFileInfo(parentDir).path(); + if (parentDir.isEmpty() || !parentDir.startsWith((*it_base)->destination())) { + break; + } + // Find the parent directory in the syncItems vector. Since the vector + // is sorted we can use a lower_bound, but we need a fake + // SyncFileItemPtr needle to compare against + if (!needle) + needle = SyncFileItemPtr::create(); + needle->_file = parentDir; + auto parent_it = std::lower_bound(it_base, it, needle); + if (parent_it == syncItems.end() || (*parent_it)->destination() != parentDir) { + break; + } + ASSERT((*parent_it)->_isDirectory); + if ((*parent_it)->_instruction != CSYNC_INSTRUCTION_IGNORE) { + break; // already changed + } + (*parent_it)->_instruction = CSYNC_INSTRUCTION_UPDATE_METADATA; + (*parent_it)->_status = SyncFileItem::NoStatus; + (*parent_it)->_errorString.clear(); + + } while (true); + continue; + } (*it)->_instruction = CSYNC_INSTRUCTION_IGNORE; (*it)->_status = SyncFileItem::FileIgnored; (*it)->_errorString = tr("Ignored because of the \"choose what to sync\" blacklist"); diff --git a/src/libsync/syncfileitem.h b/src/libsync/syncfileitem.h index 6273dd5c96..3eefac5cee 100644 --- a/src/libsync/syncfileitem.h +++ b/src/libsync/syncfileitem.h @@ -190,8 +190,13 @@ public: quint64 _inode; QByteArray _fileId; QByteArray _remotePerm; - QByteArray _contentChecksum; - QByteArray _contentChecksumType; + + // When is this set, and is it the local or the remote checksum? + // - if mtime or size changed locally for *.eml files (local checksum) + // - for potential renames of local files (local checksum) + // - for conflicts (remote checksum) (what about eval_rename/new reconcile?) + QByteArray _checksumHeader; + QString _directDownloadUrl; QString _directDownloadCookies; diff --git a/src/libsync/syncjournaldb.cpp b/src/libsync/syncjournaldb.cpp index c6b67fd067..b6bcff4d0d 100644 --- a/src/libsync/syncjournaldb.cpp +++ b/src/libsync/syncjournaldb.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include "ownsql.h" @@ -28,6 +29,7 @@ #include "version.h" #include "filesystem.h" #include "asserts.h" +#include "checksums.h" #include "../../csync/src/std/c_jhash.h" @@ -42,7 +44,8 @@ SyncJournalDb::SyncJournalDb(const QString &dbFilePath, QObject *parent) { } -QString SyncJournalDb::makeDbName(const QUrl &remoteUrl, +QString SyncJournalDb::makeDbName(const QString &localPath, + const QUrl &remoteUrl, const QString &remotePath, const QString &user) { @@ -54,6 +57,42 @@ QString SyncJournalDb::makeDbName(const QUrl &remoteUrl, journalPath.append(ba.left(6).toHex()); journalPath.append(".db"); + // If the journal doesn't exist and we can't create a file + // at that location, try again with a journal name that doesn't + // have the ._ prefix. + // + // The disadvantage of that filename is that it will only be ignored + // by client versions >2.3.2. + // + // See #5633: "._*" is often forbidden on samba shared folders. + + // If it exists already, the path is clearly usable + QFile file(QDir(localPath).filePath(journalPath)); + if (file.exists()) { + return journalPath; + } + + // Try to create a file there + if (file.open(QIODevice::ReadWrite)) { + // Ok, all good. + file.close(); + file.remove(); + return journalPath; + } + + // Can we create it if we drop the underscore? + QString alternateJournalPath = journalPath.mid(2).prepend("."); + QFile file2(QDir(localPath).filePath(alternateJournalPath)); + if (file2.open(QIODevice::ReadWrite)) { + // The alternative worked, use it + qCInfo(lcDb) << "Using alternate database path" << alternateJournalPath; + file2.close(); + file2.remove(); + return alternateJournalPath; + } + + // Neither worked, just keep the original and throw errors later + qCWarning(lcDb) << "Could not find a writable database path" << file.fileName(); return journalPath; } @@ -448,7 +487,7 @@ bool SyncJournalDb::checkConnect() _getFileRecordQuery.reset(new SqlQuery(_db)); if (_getFileRecordQuery->prepare( "SELECT path, inode, uid, gid, mode, modtime, type, md5, fileid, remotePerm, filesize," - " ignoredChildrenRemote, contentChecksum, contentchecksumtype.name" + " ignoredChildrenRemote, contentchecksumtype.name || ':' || contentChecksum" " FROM metadata" " LEFT JOIN checksumtype as contentchecksumtype ON metadata.contentChecksumTypeId == contentchecksumtype.id" " WHERE phash=?1")) { @@ -832,7 +871,7 @@ bool SyncJournalDb::setFileRecord(const SyncJournalFileRecord &_record) qCInfo(lcDb) << "Updating file record for path:" << record._path << "inode:" << record._inode << "modtime:" << record._modtime << "type:" << record._type << "etag:" << record._etag << "fileId:" << record._fileId << "remotePerm:" << record._remotePerm - << "fileSize:" << record._fileSize << "checksum:" << record._contentChecksum << record._contentChecksumType; + << "fileSize:" << record._fileSize << "checksum:" << record._checksumHeader; qlonglong phash = getPHash(record._path); if (checkConnect()) { @@ -848,7 +887,9 @@ bool SyncJournalDb::setFileRecord(const SyncJournalFileRecord &_record) QString remotePerm(record._remotePerm); if (remotePerm.isEmpty()) remotePerm = QString(); // have NULL in DB (vs empty) - int contentChecksumTypeId = mapChecksumType(record._contentChecksumType); + QByteArray checksumType, checksum; + parseChecksumHeader(record._checksumHeader, &checksumType, &checksum); + int contentChecksumTypeId = mapChecksumType(checksumType); _setFileRecordQuery->reset_and_clear_bindings(); _setFileRecordQuery->bindValue(1, QString::number(phash)); _setFileRecordQuery->bindValue(2, plen); @@ -864,7 +905,7 @@ bool SyncJournalDb::setFileRecord(const SyncJournalFileRecord &_record) _setFileRecordQuery->bindValue(12, remotePerm); _setFileRecordQuery->bindValue(13, record._fileSize); _setFileRecordQuery->bindValue(14, record._serverHasIgnoredFiles ? 1 : 0); - _setFileRecordQuery->bindValue(15, record._contentChecksum); + _setFileRecordQuery->bindValue(15, checksum); _setFileRecordQuery->bindValue(16, contentChecksumTypeId); if (!_setFileRecordQuery->exec()) { @@ -943,10 +984,7 @@ SyncJournalFileRecord SyncJournalDb::getFileRecord(const QString &filename) rec._remotePerm = _getFileRecordQuery->baValue(9); rec._fileSize = _getFileRecordQuery->int64Value(10); rec._serverHasIgnoredFiles = (_getFileRecordQuery->intValue(11) > 0); - rec._contentChecksum = _getFileRecordQuery->baValue(12); - if (!_getFileRecordQuery->nullValue(13)) { - rec._contentChecksumType = _getFileRecordQuery->baValue(13); - } + rec._checksumHeader = _getFileRecordQuery->baValue(12); _getFileRecordQuery->reset_and_clear_bindings(); } else { int errId = _getFileRecordQuery->errorId(); diff --git a/src/libsync/syncjournaldb.h b/src/libsync/syncjournaldb.h index f51d5b9c1e..3f6c89e435 100644 --- a/src/libsync/syncjournaldb.h +++ b/src/libsync/syncjournaldb.h @@ -41,7 +41,8 @@ public: virtual ~SyncJournalDb(); /// Create a journal path for a specific configuration - static QString makeDbName(const QUrl &remoteUrl, + static QString makeDbName(const QString &localPath, + const QUrl &remoteUrl, const QString &remotePath, const QString &user); diff --git a/src/libsync/syncjournalfilerecord.cpp b/src/libsync/syncjournalfilerecord.cpp index 6937789145..b2c65931d4 100644 --- a/src/libsync/syncjournalfilerecord.cpp +++ b/src/libsync/syncjournalfilerecord.cpp @@ -47,8 +47,7 @@ SyncJournalFileRecord::SyncJournalFileRecord(const SyncFileItem &item, const QSt , _fileSize(item._size) , _remotePerm(item._remotePerm) , _serverHasIgnoredFiles(item._serverHasIgnoredFiles) - , _contentChecksum(item._contentChecksum) - , _contentChecksumType(item._contentChecksumType) + , _checksumHeader(item._checksumHeader) { // use the "old" inode coming with the item for the case where the // filesystem stat fails. That can happen if the the file was removed @@ -106,8 +105,7 @@ SyncFileItem SyncJournalFileRecord::toSyncFileItem() item._size = _fileSize; item._remotePerm = _remotePerm; item._serverHasIgnoredFiles = _serverHasIgnoredFiles; - item._contentChecksum = _contentChecksum; - item._contentChecksumType = _contentChecksumType; + item._checksumHeader = _checksumHeader; return item; } @@ -130,7 +128,6 @@ bool operator==(const SyncJournalFileRecord &lhs, && lhs._fileSize == rhs._fileSize && lhs._remotePerm == rhs._remotePerm && lhs._serverHasIgnoredFiles == rhs._serverHasIgnoredFiles - && lhs._contentChecksum == rhs._contentChecksum - && lhs._contentChecksumType == rhs._contentChecksumType; + && lhs._checksumHeader == rhs._checksumHeader; } } diff --git a/src/libsync/syncjournalfilerecord.h b/src/libsync/syncjournalfilerecord.h index f96e85b3ba..c7579683b9 100644 --- a/src/libsync/syncjournalfilerecord.h +++ b/src/libsync/syncjournalfilerecord.h @@ -57,8 +57,7 @@ public: qint64 _fileSize; QByteArray _remotePerm; bool _serverHasIgnoredFiles; - QByteArray _contentChecksum; - QByteArray _contentChecksumType; + QByteArray _checksumHeader; }; bool OWNCLOUDSYNC_EXPORT diff --git a/src/libsync/theme.cpp b/src/libsync/theme.cpp index 9dd1c88a95..1d1391b1fc 100644 --- a/src/libsync/theme.cpp +++ b/src/libsync/theme.cpp @@ -12,8 +12,6 @@ * for more details. */ -#include - #include "theme.h" #include "version.h" #include "config.h" @@ -308,11 +306,7 @@ QString Theme::gitSHA1() const .arg(__DATE__) .arg(__TIME__) .arg(QString::fromAscii(qVersion())) -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) .arg(QSslSocket::sslLibraryVersionString()); -#else - .arg(QCoreApplication::translate("ownCloudTheme::about()", "built with %1").arg(QString::fromAscii(OPENSSL_VERSION_TEXT))); -#endif #endif return devString; } @@ -503,5 +497,15 @@ QString Theme::quotaBaseFolder() const return QLatin1String("/"); } +QString Theme::oauthClientId() const +{ + return "xdXOt13JKxym1B1QcEncf2XDkLAexMBFwiT9j6EfhhHFJhs2KM9jbjTmf8JBXE69"; +} + +QString Theme::oauthClientSecret() const +{ + return "UBntmLjC2yYCeHwsyj73Uwo9TAaecAetRwMw0xYcvNL9yRdLSUi0hUAHfvCHFeFh"; +} + } // end namespace client diff --git a/src/libsync/theme.h b/src/libsync/theme.h index cc51251df5..bb5c858ae6 100644 --- a/src/libsync/theme.h +++ b/src/libsync/theme.h @@ -320,6 +320,13 @@ public: */ virtual QString quotaBaseFolder() const; + /** + * The OAuth client_id, secret pair. + * Note that client that change these value cannot connect to un-branded owncloud servers. + */ + virtual QString oauthClientId() const; + virtual QString oauthClientSecret() const; + protected: #ifndef TOKEN_AUTH_ONLY diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c9726d584b..ac0f1dca1e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -59,3 +59,4 @@ list(APPEND FolderMan_SRC ${FolderWatcher_SRC}) list(APPEND FolderMan_SRC stub.cpp ) owncloud_add_test(FolderMan "${FolderMan_SRC}") +configure_file(test_journal.db "${PROJECT_BINARY_DIR}/bin/test_journal.db" COPYONLY) diff --git a/test/syncenginetestutils.h b/test/syncenginetestutils.h index 64b0632840..997e5054e5 100644 --- a/test/syncenginetestutils.h +++ b/test/syncenginetestutils.h @@ -17,6 +17,12 @@ #include #include +/* + * TODO: In theory we should use QVERIFY instead of Q_ASSERT for testing, but this + * only works when directly called from a QTest :-( + */ + + static const QUrl sRootUrl("owncloud://somehost/owncloud/remote.php/webdav/"); static const QUrl sRootUrl2("owncloud://somehost/owncloud/remote.php/dav/files/admin/"); static const QUrl sUploadUrl("owncloud://somehost/owncloud/remote.php/dav/uploads/admin/"); @@ -282,6 +288,7 @@ public: QDateTime lastModified = QDateTime::currentDateTime().addDays(-7); QString etag = generateEtag(); QByteArray fileId = generateFileId(); + QByteArray checksums; qint64 size = 0; char contentChar = 'W'; @@ -346,12 +353,13 @@ public: xml.writeEmptyElement(davUri, QStringLiteral("resourcetype")); auto gmtDate = fileInfo.lastModified.toTimeZone(QTimeZone(0)); - auto stringDate = gmtDate.toString("ddd, dd MMM yyyy HH:mm:ss 'GMT'"); + auto stringDate = QLocale::c().toString(gmtDate, "ddd, dd MMM yyyy HH:mm:ss 'GMT'"); xml.writeTextElement(davUri, QStringLiteral("getlastmodified"), stringDate); xml.writeTextElement(davUri, QStringLiteral("getcontentlength"), QString::number(fileInfo.size)); xml.writeTextElement(davUri, QStringLiteral("getetag"), fileInfo.etag); xml.writeTextElement(ocUri, QStringLiteral("permissions"), fileInfo.isShared ? QStringLiteral("SRDNVCKW") : QStringLiteral("RDNVCKW")); xml.writeTextElement(ocUri, QStringLiteral("id"), fileInfo.fileId); + xml.writeTextElement(ocUri, QStringLiteral("checksums"), fileInfo.checksums); xml.writeEndElement(); // prop xml.writeTextElement(davUri, QStringLiteral("status"), "HTTP/1.1 200 OK"); xml.writeEndElement(); // propstat @@ -704,10 +712,17 @@ public: class FakeQNAM : public QNetworkAccessManager { +public: + using Override = std::function; + +private: FileInfo _remoteRootFileInfo; FileInfo _uploadFileInfo; // maps a path to an HTTP error QHash _errorPaths; + // monitor requests and optionally provide custom replies + Override _override; + public: FakeQNAM(FileInfo initialRoot) : _remoteRootFileInfo{std::move(initialRoot)} { } FileInfo ¤tRemoteState() { return _remoteRootFileInfo; } @@ -715,6 +730,8 @@ public: QHash &errorPaths() { return _errorPaths; } + void setOverride(const Override &override) { _override = override; } + protected: QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData = 0) { @@ -726,8 +743,13 @@ protected: bool isUpload = request.url().path().startsWith(sUploadUrl.path()); FileInfo &info = isUpload ? _uploadFileInfo : _remoteRootFileInfo; + if (_override) { + if (auto reply = _override(op, request)) + return reply; + } + auto verb = request.attribute(QNetworkRequest::CustomVerbAttribute); - if (verb == QLatin1String("PROPFIND")) + if (verb == "PROPFIND") // Ignore outgoingData always returning somethign good enough, works for now. return new FakePropfindReply{info, op, request, this}; else if (verb == QLatin1String("GET") || op == QNetworkAccessManager::GetOperation) @@ -823,6 +845,7 @@ public: void clear() { _qnam->errorPaths().clear(); } }; ErrorList serverErrorPaths() { return {_fakeQnam}; } + void setServerOverride(const FakeQNAM::Override &override) { _fakeQnam->setOverride(override); } QString localPath() const { // SyncEngine wants a trailing slash diff --git a/test/test_journal.db b/test/test_journal.db index 2b58c07cb8..7a95c44555 100644 Binary files a/test/test_journal.db and b/test/test_journal.db differ diff --git a/test/testcsyncsqlite.cpp b/test/testcsyncsqlite.cpp index 9d526afeef..09dc1528a4 100644 --- a/test/testcsyncsqlite.cpp +++ b/test/testcsyncsqlite.cpp @@ -21,10 +21,11 @@ private slots: memset(&_ctx, 0, sizeof(CSYNC)); - _ctx.statedb.file = c_strdup("./test_journal.db"); + QString db = QCoreApplication::applicationDirPath() + "/test_journal.db"; + _ctx.statedb.file = c_strdup(db.toLocal8Bit()); rc = csync_statedb_load((CSYNC*)(&_ctx), _ctx.statedb.file, &(_ctx.statedb.db)); - Q_ASSERT(rc == 0); + QVERIFY(rc == 0); } void testFullResult() { @@ -85,5 +86,5 @@ private slots: }; -QTEST_APPLESS_MAIN(TestCSyncSqlite) +QTEST_GUILESS_MAIN(TestCSyncSqlite) #include "testcsyncsqlite.moc" diff --git a/test/testsyncengine.cpp b/test/testsyncengine.cpp index 242d5e438b..1f48931da1 100644 --- a/test/testsyncengine.cpp +++ b/test/testsyncengine.cpp @@ -220,7 +220,18 @@ private slots: FileInfo { QStringLiteral("parentFolder"), { FileInfo{ QStringLiteral("subFolder"), { { QStringLiteral("fileA.txt"), 400 }, - { QStringLiteral("fileB.txt"), 400, 'o' } + { QStringLiteral("fileB.txt"), 400, 'o' }, + FileInfo { QStringLiteral("subsubFolder"), { + { QStringLiteral("fileC.txt"), 400 }, + { QStringLiteral("fileD.txt"), 400, 'o' } + }}, + FileInfo{ QStringLiteral("anotherFolder"), { + FileInfo { QStringLiteral("emptyFolder"), { } }, + FileInfo { QStringLiteral("subsubFolder"), { + { QStringLiteral("fileE.txt"), 400 }, + { QStringLiteral("fileF.txt"), 400, 'o' } + }} + }} }} }} }}}; @@ -233,9 +244,11 @@ private slots: {"parentFolder/subFolder/"}); fakeFolder.syncEngine().journal()->avoidReadFromDbOnNextSync("parentFolder/subFolder/"); - // But touch a local file before the next sync, such that the local folder + // But touch local file before the next sync, such that the local folder // can't be removed fakeFolder.localModifier().setContents("parentFolder/subFolder/fileB.txt", 'n'); + fakeFolder.localModifier().setContents("parentFolder/subFolder/subsubFolder/fileD.txt", 'n'); + fakeFolder.localModifier().setContents("parentFolder/subFolder/anotherFolder/subsubFolder/fileF.txt", 'n'); // Several follow-up syncs don't change the state at all, // in particular the remote state doesn't change and fileB.txt @@ -250,8 +263,13 @@ private slots: // The local state should still have subFolderA auto local = fakeFolder.currentLocalState(); QVERIFY(local.find("parentFolder/subFolder")); - QVERIFY(local.find("parentFolder/subFolder/fileA.txt")); + QVERIFY(!local.find("parentFolder/subFolder/fileA.txt")); QVERIFY(local.find("parentFolder/subFolder/fileB.txt")); + QVERIFY(!local.find("parentFolder/subFolder/subsubFolder/fileC.txt")); + QVERIFY(local.find("parentFolder/subFolder/subsubFolder/fileD.txt")); + QVERIFY(!local.find("parentFolder/subFolder/anotherFolder/subsubFolder/fileE.txt")); + QVERIFY(local.find("parentFolder/subFolder/anotherFolder/subsubFolder/fileF.txt")); + QVERIFY(!local.find("parentFolder/subFolder/anotherFolder/emptyFolder")); } } } @@ -303,6 +321,70 @@ private slots: } } + void testFakeConflict() + { + FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; + + int nGET = 0; + fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &) { + if (op == QNetworkAccessManager::GetOperation) + ++nGET; + return nullptr; + }); + + // For directly editing the remote checksum + FileInfo &remoteInfo = dynamic_cast(fakeFolder.remoteModifier()); + + // Base mtime with no ms content (filesystem is seconds only) + auto mtime = QDateTime::currentDateTime().addDays(-4); + mtime.setMSecsSinceEpoch(mtime.toMSecsSinceEpoch() / 1000 * 1000); + + // Conflict: Same content, mtime, but no server checksum + // -> ignored in reconcile + fakeFolder.localModifier().setContents("A/a1", 'C'); + fakeFolder.localModifier().setModTime("A/a1", mtime); + fakeFolder.remoteModifier().setContents("A/a1", 'C'); + fakeFolder.remoteModifier().setModTime("A/a1", mtime); + QVERIFY(fakeFolder.syncOnce()); + QCOMPARE(nGET, 0); + + // Conflict: Same content, mtime, but weak server checksum + // -> ignored in reconcile + mtime = mtime.addDays(1); + fakeFolder.localModifier().setContents("A/a1", 'D'); + fakeFolder.localModifier().setModTime("A/a1", mtime); + fakeFolder.remoteModifier().setContents("A/a1", 'D'); + fakeFolder.remoteModifier().setModTime("A/a1", mtime); + remoteInfo.find("A/a1")->checksums = "Adler32:bad"; + QVERIFY(fakeFolder.syncOnce()); + QCOMPARE(nGET, 0); + + // Conflict: Same content, mtime, but server checksum differs + // -> downloaded + mtime = mtime.addDays(1); + fakeFolder.localModifier().setContents("A/a1", 'W'); + fakeFolder.localModifier().setModTime("A/a1", mtime); + fakeFolder.remoteModifier().setContents("A/a1", 'W'); + fakeFolder.remoteModifier().setModTime("A/a1", mtime); + remoteInfo.find("A/a1")->checksums = "SHA1:bad"; + QVERIFY(fakeFolder.syncOnce()); + QCOMPARE(nGET, 1); + + // Conflict: Same content, mtime, matching checksums + // -> PropagateDownload, but it skips the download + mtime = mtime.addDays(1); + fakeFolder.localModifier().setContents("A/a1", 'C'); + fakeFolder.localModifier().setModTime("A/a1", mtime); + fakeFolder.remoteModifier().setContents("A/a1", 'C'); + fakeFolder.remoteModifier().setModTime("A/a1", mtime); + remoteInfo.find("A/a1")->checksums = "SHA1:56900fb1d337cf7237ff766276b9c1e8ce507427"; + QVERIFY(fakeFolder.syncOnce()); + QCOMPARE(nGET, 1); + + // Extra sync reads from db, no difference + QVERIFY(fakeFolder.syncOnce()); + QCOMPARE(nGET, 1); + } }; QTEST_GUILESS_MAIN(TestSyncEngine) diff --git a/test/testsyncjournaldb.cpp b/test/testsyncjournaldb.cpp index fa6021acc4..ec673077dc 100644 --- a/test/testsyncjournaldb.cpp +++ b/test/testsyncjournaldb.cpp @@ -53,17 +53,15 @@ private slots: record._fileId = "abcd"; record._remotePerm = "744"; record._fileSize = 213089055; - record._contentChecksum = "mychecksum"; - record._contentChecksumType = "MD5"; + record._checksumHeader = "MD5:mychecksum"; QVERIFY(_db.setFileRecord(record)); SyncJournalFileRecord storedRecord = _db.getFileRecord("foo"); QVERIFY(storedRecord == record); // Update checksum - record._contentChecksum = "newchecksum"; - record._contentChecksumType = "Adler32"; - _db.updateFileRecordChecksum("foo", record._contentChecksum, record._contentChecksumType); + record._checksumHeader = "Adler32:newchecksum"; + _db.updateFileRecordChecksum("foo", "newchecksum", "Adler32"); storedRecord = _db.getFileRecord("foo"); QVERIFY(storedRecord == record); @@ -91,16 +89,14 @@ private slots: SyncJournalFileRecord record; record._path = "foo-checksum"; record._remotePerm = "744"; - record._contentChecksum = "mychecksum"; - record._contentChecksumType = "MD5"; + record._checksumHeader = "MD5:mychecksum"; record._modtime = QDateTime::currentDateTimeUtc(); QVERIFY(_db.setFileRecord(record)); SyncJournalFileRecord storedRecord = _db.getFileRecord("foo-checksum"); QVERIFY(storedRecord._path == record._path); QVERIFY(storedRecord._remotePerm == record._remotePerm); - QVERIFY(storedRecord._contentChecksum == record._contentChecksum); - QVERIFY(storedRecord._contentChecksumType == record._contentChecksumType); + QVERIFY(storedRecord._checksumHeader == record._checksumHeader); // qDebug()<< "OOOOO " << storedRecord._modtime.toTime_t() << record._modtime.toTime_t(); diff --git a/translations/client_ca.ts b/translations/client_ca.ts index ae14fb3c10..dce4d38608 100644 --- a/translations/client_ca.ts +++ b/translations/client_ca.ts @@ -807,112 +807,112 @@ Continuing the sync as normal will cause all your files to be overwritten by an OCC::FolderMan - + Could not reset folder state No es pot restablir l'estat de la carpeta - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. S'ha trobat un diari de sincronització antic '%1', però no s'ha pogut eliminar. Assegureu-vos que no hi ha cap aplicació que actualment en faci ús. - + (backup) (copia de seguretat) - + (backup %1) (copia de seguretat %1) - + Undefined State. Estat indefinit. - + Waiting to start syncing. S'està esperant per començar a sincronitzar. - + Preparing for sync. S'està preparant per la sincronització. - + Sync is running. S'està sincronitzant. - + Last Sync was successful. La darrera sincronització va ser correcta. - + Last Sync was successful, but with warnings on individual files. La última sincronització ha estat un èxit, però amb avisos en fitxers individuals. - + Setup Error. Error de configuració. - + User Abort. Cancel·la usuari. - + Sync is paused. La sincronització està en pausa. - + %1 (Sync is paused) %1 (Sync està pausat) - + No valid folder selected! No s'ha seleccionat cap directori vàlid! - + The selected path is not a folder! La ruta seleccionada no és un directori! - + You have no permission to write to the selected folder! No teniu permisos per escriure en la carpeta seleccionada! - + The local folder %1 contains a symbolic link. The link target contains an already synced folder Please pick another one! - + There is already a sync from the server to this local folder. Please pick another local folder! - + The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - + The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - + 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! @@ -1255,17 +1255,17 @@ Continuing the sync as normal will cause all your files to be overwritten by an - + About Quant a - + Updates Actualitzacions - + &Restart && Update &Reiniciar && Actualitzar @@ -1345,22 +1345,22 @@ Items where deletion is allowed will be deleted if they prevent a directory from Els elements que poden ser eliminats s'eliminaran si impedeixen que una carpeta sigui eliminada. Això és útil per les metadades. - + Could not open file No s'ha pogut obrir el fitxer - + Cannot write changes to '%1'. No es poden desar els canvis a '%1'. - + Add Ignore Pattern Afegeix una plantilla per ignorar - + Add a new ignore pattern: Afegeix una nova plantilla d'ignorats: @@ -2018,27 +2018,27 @@ No és aconsellada usar-la. El fitxer s'ha esborrat del servidor - + The file could not be downloaded completely. No es pot descarregar el fitxer completament. - + The downloaded file is empty despite the server announced it should have been %1. - + File %1 cannot be saved because of a local file name clash! - + File has changed since discovery El fitxer ha canviat des de que es va descobrir - + Error writing metadata to the database Error en escriure les metadades a la base de dades @@ -2549,6 +2549,11 @@ No és aconsellada usar-la. Allow editing Permetre edició + + + Anyone with the link has access to the file/folder + + P&assword protect diff --git a/translations/client_cs.ts b/translations/client_cs.ts index 7461a4e5ab..3c2eb0519f 100644 --- a/translations/client_cs.ts +++ b/translations/client_cs.ts @@ -810,112 +810,112 @@ Pokračováním v synchronizaci způsobí přepsání všech vašich souborů st OCC::FolderMan - + Could not reset folder state Nelze obnovit stav adresáře - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. Byl nalezen starý záznam synchronizace '%1', ale nebylo možné jej odebrat. Ujistěte se, že není aktuálně používán jinou aplikací. - + (backup) (záloha) - + (backup %1) (záloha %1) - + Undefined State. Nedefinovaný stav. - + Waiting to start syncing. Čeká na spuštění synchronizace. - + Preparing for sync. Příprava na synchronizaci. - + Sync is running. Synchronizace probíhá. - + Last Sync was successful. Poslední synchronizace byla úspěšná. - + Last Sync was successful, but with warnings on individual files. Poslední synchronizace byla úspěšná, ale s varováním u některých souborů - + Setup Error. Chyba nastavení. - + User Abort. Zrušení uživatelem. - + Sync is paused. Synchronizace pozastavena. - + %1 (Sync is paused) %1 (Synchronizace je pozastavena) - + No valid folder selected! Nebyl vybrán platný adresář! - + The selected path is not a folder! Vybraná cesta nevede do adresáře! - + You have no permission to write to the selected folder! Nemáte oprávnění pro zápis do zvoleného adresáře! - + The local folder %1 contains a symbolic link. The link target contains an already synced folder Please pick another one! Místní složka %1 obsahuje symbolický odkaz. Cílový odkaz obsahuje již synchronizované složky. Vyberte si prosím jinou! - + There is already a sync from the server to this local folder. Please pick another local folder! Ze serveru se do tohoto umístění již synchronizuje. Prosím zvolte jinou místní složku! - + The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! Místní adresář %1 již obsahuje podadresář použitý pro synchronizaci odesílání. Zvolte prosím jiný! - + The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! Místní adresář %1 je již obsažen ve adresáři použitém pro synchronizaci. Vyberte prosím jiný! - + 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! Místní adresář %1 je symbolickým obsahem. Cíl odkazu je již obsažen v adresáři použitém pro synchronizaci. Vyberte prosím jiný! @@ -1258,17 +1258,17 @@ Pokračováním v synchronizaci způsobí přepsání všech vašich souborů st - + About O aplikaci - + Updates Aktualizace - + &Restart && Update &Restart && aktualizace @@ -1348,22 +1348,22 @@ Items where deletion is allowed will be deleted if they prevent a directory from Položky u kterých je povoleno smazání budou vymazány, pokud by bránily odstranění adresáře. Toto je užitečné pro metadata. - + Could not open file Nepodařilo se otevřít soubor - + Cannot write changes to '%1'. Nelze zapsat změny do '%1'. - + Add Ignore Pattern Přidat masku ignorovaných - + Add a new ignore pattern: Přidat novou masku ignorovaných souborů: @@ -2021,27 +2021,27 @@ Nedoporučuje se jí používat. Soubor byl smazán ze serveru - + The file could not be downloaded completely. Soubor nemohl být kompletně stažen. - + The downloaded file is empty despite the server announced it should have been %1. Stažený soubor je prázdný, přestože server oznámil, že měl být %1. - + File %1 cannot be saved because of a local file name clash! Soubor %1 nemohl být uložen z důvodu kolize názvu se souborem v místním systému! - + File has changed since discovery Soubor se mezitím změnil - + Error writing metadata to the database Chyba zápisu metadat do databáze @@ -2552,6 +2552,11 @@ Nedoporučuje se jí používat. Allow editing Povolit úpravy + + + Anyone with the link has access to the file/folder + Kdokoliv, kdo má odkaz, může přistupovat k tomuto souboru/složce + P&assword protect diff --git a/translations/client_de.ts b/translations/client_de.ts index 1139e88318..5b3b13befd 100644 --- a/translations/client_de.ts +++ b/translations/client_de.ts @@ -812,112 +812,112 @@ Wenn diese Synchronisation fortgesetzt wird, werden Dateien eventuell von älter OCC::FolderMan - + Could not reset folder state Konnte Ordner-Zustand nicht zurücksetzen - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. Ein altes Synchronisations-Journal '%1' wurde gefunden, konnte jedoch nicht entfernt werden. Bitte stellen Sie sicher, dass keine Anwendung es verwendet. - + (backup) (Sicherung) - + (backup %1) (Sicherung %1) - + Undefined State. Undefinierter Zustand. - + Waiting to start syncing. Wartet auf Beginn der Synchronistation - + Preparing for sync. Synchronisation wird vorbereitet. - + Sync is running. Synchronisation läuft. - + Last Sync was successful. Die letzte Synchronisation war erfolgreich. - + Last Sync was successful, but with warnings on individual files. Letzte Synchronisation war erfolgreich, aber mit Warnungen für einzelne Dateien. - + Setup Error. Installationsfehler. - + User Abort. Benutzer-Abbruch - + Sync is paused. Synchronisation wurde angehalten. - + %1 (Sync is paused) %1 (Synchronisation ist pausiert) - + No valid folder selected! Kein gültige Ordner gewählt! - + The selected path is not a folder! Der gewählte Pfad ist kein Ordner! - + You have no permission to write to the selected folder! Sie haben keine Schreibberechtigung für den ausgewählten Ordner! - + The local folder %1 contains a symbolic link. The link target contains an already synced folder Please pick another one! Der lokale Ordner %1 beinhaltet einen symbolischer Link. Das Ziel des Links beinhaltet bereits einen synchronisierten Ordner. Bitte wählen Sie einen anderen lokalen Ordner aus! - + There is already a sync from the server to this local folder. Please pick another local folder! Es exisitiert bereits eine Synchronisation vom Server zu diesem lokalen Ordner. Bitte wählen Sie ein anderes lokales Verzeichnis! - + The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! Der lokale Ordner %1 liegt innerhalb eines synchronisierten Ordners. Bitte wählen Sie einen anderen aus! - + The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! Der lokale Ordner %1 liegt in einem Ordner, der bereits synchronisiert wird. Bitte wählen Sie einen anderen aus! - + 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! Der lokale Ordner %1 ist ein symbolischer Link. Das Ziel des Links liegt in einem Ordner, der schon synchronisiert wird. Bitte wählen Sie einen anderen aus! @@ -1260,17 +1260,17 @@ Wenn diese Synchronisation fortgesetzt wird, werden Dateien eventuell von älter - + About Über - + Updates Updates - + &Restart && Update &Neustarten && aktualisieren @@ -1350,22 +1350,22 @@ Items where deletion is allowed will be deleted if they prevent a directory from Objekte, bei denen Löschen erlaubt ist, werden gelöscht, wenn sie die Löschung eines Ordners verhindern würden. Das ist für Metadaten nützlich. - + Could not open file Datei konnte nicht geöffnet werden - + Cannot write changes to '%1'. Konnte Änderungen nicht in '%1' schreiben. - + Add Ignore Pattern Ignoriermuster hinzufügen - + Add a new ignore pattern: Neues Ignoriermuster hinzufügen: @@ -2022,27 +2022,27 @@ Es ist nicht ratsam, diese zu benutzen. Die Datei wurde vom Server gelöscht - + The file could not be downloaded completely. Die Datei konnte nicht vollständig herunter geladen werden. - + The downloaded file is empty despite the server announced it should have been %1. Die heruntergeladene Datei ist leer, obwohl der Server %1 als Größe übermittelt hat. - + File %1 cannot be saved because of a local file name clash! Die Datei %1 kann aufgrund eines Konfliktes mit dem lokalen Dateinamen nicht gespeichert geladen werden! - + File has changed since discovery Datei ist seit der Entdeckung geändert worden - + Error writing metadata to the database Fehler beim Schreiben der Metadaten in die Datenbank @@ -2553,6 +2553,11 @@ Es ist nicht ratsam, diese zu benutzen. Allow editing Bearbeitung erlauben + + + Anyone with the link has access to the file/folder + Jeder mit dem Link hat Zugriff auf die Datei/Ordner + P&assword protect diff --git a/translations/client_el.ts b/translations/client_el.ts index 0b2a80c493..bac0620b61 100644 --- a/translations/client_el.ts +++ b/translations/client_el.ts @@ -812,112 +812,112 @@ Continuing the sync as normal will cause all your files to be overwritten by an OCC::FolderMan - + Could not reset folder state Δεν ήταν δυνατό να επαναφερθεί η κατάσταση του φακέλου - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. Βρέθηκε ένα παλαιότερο αρχείο συγχρονισμού '%1', αλλά δεν μπόρεσε να αφαιρεθεί. Παρακαλώ βεβαιωθείτε ότι καμμία εφαρμογή δεν το χρησιμοποιεί αυτή τη στιγμή. - + (backup) (αντίγραφο ασφαλείας) - + (backup %1) (αντίγραοφ ασφαλέιας %1) - + Undefined State. Απροσδιόριστη Κατάσταση. - + Waiting to start syncing. Αναμονή έναρξης συγχρονισμού. - + Preparing for sync. Προετοιμασία για συγχρονισμό. - + Sync is running. Ο συγχρονισμός εκτελείται. - + Last Sync was successful. Ο τελευταίος συγχρονισμός ήταν επιτυχής. - + Last Sync was successful, but with warnings on individual files. Ο τελευταίος συγχρονισμός ήταν επιτυχής, αλλά υπήρχαν προειδοποιήσεις σε συγκεκριμένα αρχεία. - + Setup Error. Σφάλμα Ρύθμισης. - + User Abort. Ματαίωση από Χρήστη. - + Sync is paused. Παύση συγχρονισμού. - + %1 (Sync is paused) %1 (Παύση συγχρονισμού) - + No valid folder selected! Δεν επιλέχθηκε έγκυρος φάκελος! - + The selected path is not a folder! Η επιλεγμένη διαδρομή δεν είναι φάκελος! - + You have no permission to write to the selected folder! Δεν έχετε δικαιώματα εγγραφής στον επιλεγμένο φάκελο! - + 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 είναι συμβολικός σύνδεσμος. Ο σύνδεσμος που παραπέμπει περιέχεται ήδη σε φάκελο που βρίσκεται σε συγχρονισμό. Παρακαλώ επιλέξτε άλλον! @@ -1260,17 +1260,17 @@ Continuing the sync as normal will cause all your files to be overwritten by an - + About Σχετικά - + Updates Ενημερώσεις - + &Restart && Update &Επανεκκίνηση && Ενημέρωση @@ -1350,22 +1350,22 @@ Items where deletion is allowed will be deleted if they prevent a directory from Τα στοιχεία όπου επιτρέπεται η διαγραφή θα διαγράφονται εάν εμποδίζουν την αφαίρεση ενός φακέλου αρχείων. Αυτό είναι χρήσιμο για μετα-δεδομένα. - + Could not open file Αδυναμία ανοίγματος αρχείου - + Cannot write changes to '%1'. Αδυναμία εγγραφής αλλαγών στο '%1'. - + Add Ignore Pattern Προσθήκη Προτύπου Αγνόησης - + Add a new ignore pattern: Προσθήκη νέου προτύπου αγνόησης: @@ -2023,27 +2023,27 @@ It is not advisable to use it. Το αρχείο διαγράφηκε από τον διακομιστή - + The file could not be downloaded completely. Η λήψη του αρχείου δεν ολοκληρώθηκε. - + The downloaded file is empty despite the server announced it should have been %1. Το ληφθέν αρχείο είναι άδειο, παρόλο που ο διακομιστής ανακοίνωσε ότι θα έπρεπε να ήταν% 1. - + File %1 cannot be saved because of a local file name clash! Το αρχείο %1 δεν είναι δυνατό να αποθηκευτεί λόγω διένεξης με το όνομα ενός τοπικού ονόματος αρχείου! - + File has changed since discovery Το αρχείο έχει αλλάξει από όταν ανακαλύφθηκε - + Error writing metadata to the database Σφάλμα εγγραφής μεταδεδομένων στην βάση δεδομένων @@ -2554,6 +2554,11 @@ It is not advisable to use it. Allow editing Επιτρέπεται η επεξεργασία + + + Anyone with the link has access to the file/folder + Οποιοσδήποτε με τη σύνδεση έχει πρόσβαση στο αρχείο / φάκελο + P&assword protect diff --git a/translations/client_en.ts b/translations/client_en.ts index 08c2ce4638..b6a2146eae 100644 --- a/translations/client_en.ts +++ b/translations/client_en.ts @@ -832,112 +832,112 @@ Continuing the sync as normal will cause all your files to be overwritten by an OCC::FolderMan - + Could not reset folder state - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - + (backup) - + (backup %1) - + Undefined State. - + Waiting to start syncing. - + Preparing for sync. - + Sync is running. - + Last Sync was successful. - + Last Sync was successful, but with warnings on individual files. - + Setup Error. - + User Abort. - + Sync is paused. - + %1 (Sync is paused) - + No valid folder selected! - + The selected path is not a folder! - + You have no permission to write to the selected folder! - + The local folder %1 contains a symbolic link. The link target contains an already synced folder Please pick another one! - + There is already a sync from the server to this local folder. Please pick another local folder! - + The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - + The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - + 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! @@ -1283,17 +1283,17 @@ Continuing the sync as normal will cause all your files to be overwritten by an - + About - + Updates - + &Restart && Update @@ -1371,22 +1371,22 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + Could not open file - + Cannot write changes to '%1'. - + Add Ignore Pattern - + Add a new ignore pattern: @@ -2042,27 +2042,27 @@ It is not advisable to use it. - + The file could not be downloaded completely. - + The downloaded file is empty despite the server announced it should have been %1. - + File %1 cannot be saved because of a local file name clash! - + File has changed since discovery - + Error writing metadata to the database @@ -2573,6 +2573,11 @@ It is not advisable to use it. Allow editing + + + Anyone with the link has access to the file/folder + + P&assword protect diff --git a/translations/client_es.ts b/translations/client_es.ts index e503887672..97b7e559ba 100644 --- a/translations/client_es.ts +++ b/translations/client_es.ts @@ -812,112 +812,112 @@ Si continua con la sincronización todos los archivos serán remplazados por su OCC::FolderMan - + Could not reset folder state No se ha podido restablecer el estado de la carpeta - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. Se ha encontrado un antiguo registro de sincronización '%1'; pero no se ha podido eliminar. Por favor, asegúrese de que ninguna aplicación la esté utilizando. - + (backup) (copia de seguridad) - + (backup %1) (copia de seguridad %1) - + Undefined State. Estado no definido. - + Waiting to start syncing. Esperando para comenzar la sincronización. - + Preparing for sync. Preparándose para sincronizar. - + Sync is running. Sincronización en funcionamiento. - + Last Sync was successful. La última sincronización se ha realizado con éxito. - + Last Sync was successful, but with warnings on individual files. La última sincronización salió bien; pero hay advertencias para archivos individuales. - + Setup Error. Error de configuración. - + User Abort. Interrumpido por el usuario. - + Sync is paused. La sincronización está en pausa. - + %1 (Sync is paused) %1 (Sincronización en pausa) - + No valid folder selected! ¡La carpeta seleccionada no es válida! - + The selected path is not a folder! ¡La ruta seleccionada no es un directorio! - + You have no permission to write to the selected folder! ¡Usted no tiene permiso para escribir en la carpeta seleccionada! - + The local folder %1 contains a symbolic link. The link target contains an already synced folder Please pick another one! El directorio local %1 es un enlace simbólico. El objetivo del enlace ya contiene un directorio usado en una conexión de sincronización de directorios. Por favor, elija otro. - + There is already a sync from the server to this local folder. Please pick another local folder! Ya existe una tarea de sincronización entre el servidor y esta carpeta. Por favor elija otra carpeta local. - + The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! El directorio local %1 ya contiene un directorio usado en una conexión de sincronización de directorios. Por favor, elija otro. - + The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! El directorio local %1 está dentro de un directorio usado en una conexión de sincronización de directorios. Por favor, elija otro. - + 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! El directorio local %1 es un enlace simbólico. El objetivo está incluido en un directorio usado en una conexión de sincronización de directorios. Por favor, elija otro. @@ -1260,17 +1260,17 @@ Si continua con la sincronización todos los archivos serán remplazados por su - + About Acerca de - + Updates Actualizaciones - + &Restart && Update &Reiniciar && Actualizar @@ -1350,22 +1350,22 @@ Items where deletion is allowed will be deleted if they prevent a directory from Los elementos cuya eliminación está permitida serán eliminados si impiden que un directorio sea eliminado. Esto es útil para sus metadatos. - + Could not open file No se ha podido abrir el archivo - + Cannot write changes to '%1'. No se pueden guardar cambios en '%1'. - + Add Ignore Pattern Añadir patrón para ignorar - + Add a new ignore pattern: Añadir nuevo patrón para ignorar: @@ -2022,27 +2022,27 @@ No se recomienda usarla. Se ha eliminado el archivo del servidor - + The file could not be downloaded completely. No se ha podido descargar el archivo completamente. - + The downloaded file is empty despite the server announced it should have been %1. El archivo descargado está vacio aunque el servidor dice que deberia haber sido %1. - + File %1 cannot be saved because of a local file name clash! ¡El fichero %1 no puede guardarse debido a un conflicto con el nombre de otro fichero local! - + File has changed since discovery El archivo ha cambiado desde que fue descubierto - + Error writing metadata to the database Error al escribir los metadatos en la base de datos @@ -2553,6 +2553,11 @@ No se recomienda usarla. Allow editing Permitir edición + + + Anyone with the link has access to the file/folder + Quienquiera que posea el vínculo tendrá acceso al archivo/carpeta + P&assword protect diff --git a/translations/client_es_AR.ts b/translations/client_es_AR.ts index 1d4ded8ce0..f0839b5d42 100644 --- a/translations/client_es_AR.ts +++ b/translations/client_es_AR.ts @@ -803,112 +803,112 @@ Continuing the sync as normal will cause all your files to be overwritten by an OCC::FolderMan - + Could not reset folder state No se pudo - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. Una antigua sincronización con journaling '%1' fue encontrada, pero no se pudo eliminar. Por favor, asegurate que ninguna aplicación la está utilizando. - + (backup) - + (backup %1) - + Undefined State. Estado no definido. - + Waiting to start syncing. - + Preparing for sync. Preparando la sincronización. - + Sync is running. Sincronización en funcionamiento. - + Last Sync was successful. La última sincronización fue exitosa. - + Last Sync was successful, but with warnings on individual files. El último Sync fue exitoso, pero hubo advertencias en archivos individuales. - + Setup Error. Error de configuración. - + User Abort. Interrumpir. - + Sync is paused. La sincronización está en pausa. - + %1 (Sync is paused) %1 (Sincronización en pausa) - + No valid folder selected! - + The selected path is not a folder! - + You have no permission to write to the selected folder! ¡No tenés permisos para escribir el directorio seleccionado! - + The local folder %1 contains a symbolic link. The link target contains an already synced folder Please pick another one! - + There is already a sync from the server to this local folder. Please pick another local folder! - + The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - + The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - + 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! @@ -1251,17 +1251,17 @@ Continuing the sync as normal will cause all your files to be overwritten by an - + About Acerca de - + Updates Actualizaciones - + &Restart && Update @@ -1339,22 +1339,22 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + Could not open file No se pudo abrir el archivo - + Cannot write changes to '%1'. No se pueden guardar cambios en '%1'. - + Add Ignore Pattern Agregar patrón a ignorar - + Add a new ignore pattern: Añadir nuevo patrón a ignorar: @@ -2010,27 +2010,27 @@ It is not advisable to use it. - + The file could not be downloaded completely. - + The downloaded file is empty despite the server announced it should have been %1. - + File %1 cannot be saved because of a local file name clash! - + File has changed since discovery - + Error writing metadata to the database @@ -2541,6 +2541,11 @@ It is not advisable to use it. Allow editing + + + Anyone with the link has access to the file/folder + + P&assword protect diff --git a/translations/client_et.ts b/translations/client_et.ts index 7e9505063f..1e194a6058 100644 --- a/translations/client_et.ts +++ b/translations/client_et.ts @@ -803,112 +803,112 @@ Continuing the sync as normal will cause all your files to be overwritten by an OCC::FolderMan - + Could not reset folder state Ei suutnud tühistada kataloogi staatust - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. Leiti vana sünkroniseeringu zurnaal '%1', kuid selle eemaldamine ebaõnnenstus. Palun veendu, et seda kasutaks ükski programm. - + (backup) (varukoopia) - + (backup %1) (varukoopia %1) - + Undefined State. Määramata staatus. - + Waiting to start syncing. Oodatakse sünkroonimise alustamist. - + Preparing for sync. Valmistun sünkroniseerima. - + Sync is running. Sünkroniseerimine on käimas. - + Last Sync was successful. Viimane sünkroniseerimine oli edukas. - + Last Sync was successful, but with warnings on individual files. Viimane sünkroniseering oli edukas, kuid mõned failid põhjustasid tõrkeid. - + Setup Error. Seadistamise viga. - + User Abort. Kasutaja tühistamine. - + Sync is paused. Sünkroniseerimine on peatatud. - + %1 (Sync is paused) %1 (Sünkroniseerimine on peatatud) - + No valid folder selected! Sobilikku kausta pole valitud! - + The selected path is not a folder! Valitud asukoht pole kaust! - + You have no permission to write to the selected folder! Sul puuduvad õigused valitud kataloogi kirjutamiseks! - + The local folder %1 contains a symbolic link. The link target contains an already synced folder Please pick another one! - + There is already a sync from the server to this local folder. Please pick another local folder! - + The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - + The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - + 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! @@ -1251,17 +1251,17 @@ Continuing the sync as normal will cause all your files to be overwritten by an - + About Info - + Updates Uuendused - + &Restart && Update &Taaskäivita && Uuenda @@ -1339,22 +1339,22 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + Could not open file Ei suutunud avada faili - + Cannot write changes to '%1'. Ei saa kirjutada muudatusi '%1'. - + Add Ignore Pattern Lisa ignoreerimise muster - + Add a new ignore pattern: Lisa uus ignoreerimise muster: @@ -2011,27 +2011,27 @@ Selle kasutamine pole soovitatav. Fail on serverist kustutatud - + The file could not be downloaded completely. Faili täielik allalaadimine ebaõnnestus. - + The downloaded file is empty despite the server announced it should have been %1. - + File %1 cannot be saved because of a local file name clash! Faili %1 ei saa salvestada kuna on nime konflikt kohaliku failiga! - + File has changed since discovery Faili on pärast avastamist muudetud - + Error writing metadata to the database @@ -2542,6 +2542,11 @@ Selle kasutamine pole soovitatav. Allow editing Luba muutmine + + + Anyone with the link has access to the file/folder + + P&assword protect diff --git a/translations/client_eu.ts b/translations/client_eu.ts index 9a9292c64b..73e5d44a24 100644 --- a/translations/client_eu.ts +++ b/translations/client_eu.ts @@ -803,112 +803,112 @@ Continuing the sync as normal will cause all your files to be overwritten by an OCC::FolderMan - + Could not reset folder state Ezin izan da karpetaren egoera berrezarri - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. Aurkitu da '%1' sinkronizazio erregistro zaharra, baina ezin da ezabatu. Ziurtatu aplikaziorik ez dela erabiltzen ari. - + (backup) - + (backup %1) - + Undefined State. Definitu gabeko egoera. - + Waiting to start syncing. Itxoiten sinkronizazioa hasteko. - + Preparing for sync. Sinkronizazioa prestatzen. - + Sync is running. Sinkronizazioa martxan da. - + Last Sync was successful. Azkeneko sinkronizazioa ongi burutu zen. - + Last Sync was successful, but with warnings on individual files. Azkenengo sinkronizazioa ongi burutu zen, baina banakako fitxategi batzuetan abisuak egon dira. - + Setup Error. Konfigurazio errorea. - + User Abort. Erabiltzaileak bertan behera utzi. - + Sync is paused. Sinkronizazioa pausatuta dago. - + %1 (Sync is paused) %1 (Sinkronizazioa pausatuta dago) - + No valid folder selected! Ez da karpeta egokirik hautatu! - + The selected path is not a folder! Hautatutako bidea ez da karpeta bat! - + You have no permission to write to the selected folder! Ez daukazu hautatutako karpetan idazteko baimenik! - + The local folder %1 contains a symbolic link. The link target contains an already synced folder Please pick another one! - + There is already a sync from the server to this local folder. Please pick another local folder! - + The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - + The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - + 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! @@ -1251,17 +1251,17 @@ Continuing the sync as normal will cause all your files to be overwritten by an - + About Honi buruz - + Updates Eguneraketak - + &Restart && Update Be&rrabiarazi eta Eguneratu @@ -1341,22 +1341,22 @@ Items where deletion is allowed will be deleted if they prevent a directory from Ezabatzeko baimena duten itemak ezabatuko dira hauek karpeta bat ezabatzea uzten ez badute. Hau meta datuentzat interesgarria da. - + Could not open file Ezin izan da fitxategia ireki - + Cannot write changes to '%1'. Ezin izan dira aldaketa idatzi hemen '%1'. - + Add Ignore Pattern Gehitu Baztertzeko Eredua - + Add a new ignore pattern: Gehitu baztertzeko eredu berria: @@ -2013,27 +2013,27 @@ Ez da gomendagarria erabltzea. Fitxategia zerbitzaritik ezabatua izan da - + The file could not be downloaded completely. - + The downloaded file is empty despite the server announced it should have been %1. - + File %1 cannot be saved because of a local file name clash! - + File has changed since discovery - + Error writing metadata to the database @@ -2544,6 +2544,11 @@ Ez da gomendagarria erabltzea. Allow editing Baimendu editatzea + + + Anyone with the link has access to the file/folder + + P&assword protect diff --git a/translations/client_fa.ts b/translations/client_fa.ts index 7af17d9f01..cafd7cfadc 100644 --- a/translations/client_fa.ts +++ b/translations/client_fa.ts @@ -803,112 +803,112 @@ Continuing the sync as normal will cause all your files to be overwritten by an OCC::FolderMan - + Could not reset folder state نمی تواند حالت پوشه را تنظیم مجدد کند - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - + (backup) (backup) - + (backup %1) (پشتیبان %1) - + Undefined State. موقعیت تعریف نشده - + Waiting to start syncing. - + Preparing for sync. آماده سازی برای همگام سازی کردن. - + Sync is running. همگام سازی در حال اجراست - + Last Sync was successful. آخرین همگام سازی موفقیت آمیز بود - + Last Sync was successful, but with warnings on individual files. - + Setup Error. خطا در پیکر بندی. - + User Abort. خارج کردن کاربر. - + Sync is paused. همگام سازی فعلا متوقف شده است - + %1 (Sync is paused) %1 (همگام‌سازی موقتا متوقف شده است) - + No valid folder selected! هیچ پوشه‌ی معتبری انتخاب نشده است! - + The selected path is not a folder! - + You have no permission to write to the selected folder! شما اجازه نوشتن در پوشه های انتخاب شده را ندارید! - + The local folder %1 contains a symbolic link. The link target contains an already synced folder Please pick another one! - + There is already a sync from the server to this local folder. Please pick another local folder! - + The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - + The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - + 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! @@ -1251,17 +1251,17 @@ Continuing the sync as normal will cause all your files to be overwritten by an - + About درباره - + Updates به روز رسانی ها - + &Restart && Update @@ -1339,22 +1339,22 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + Could not open file امکان باز کردن فایل وجود ندارد - + Cannot write changes to '%1'. - + Add Ignore Pattern - + Add a new ignore pattern: @@ -2010,27 +2010,27 @@ It is not advisable to use it. فایل از روی سرور حذف شد - + The file could not be downloaded completely. - + The downloaded file is empty despite the server announced it should have been %1. - + File %1 cannot be saved because of a local file name clash! - + File has changed since discovery - + Error writing metadata to the database @@ -2541,6 +2541,11 @@ It is not advisable to use it. Allow editing اجازه‌ی ویرایش + + + Anyone with the link has access to the file/folder + + P&assword protect diff --git a/translations/client_fi.ts b/translations/client_fi.ts index 41641256e7..08064209e8 100644 --- a/translations/client_fi.ts +++ b/translations/client_fi.ts @@ -803,112 +803,112 @@ Continuing the sync as normal will cause all your files to be overwritten by an OCC::FolderMan - + Could not reset folder state Kansion tilaa ei voitu alustaa - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - + (backup) (varmuuskopio) - + (backup %1) (varmuuskopio %1) - + Undefined State. Määrittelemätön tila. - + Waiting to start syncing. Odotetaan synkronoinnin aloitusta. - + Preparing for sync. Valmistellaan synkronointia. - + Sync is running. Synkronointi on meneillään. - + Last Sync was successful. Viimeisin synkronointi suoritettiin onnistuneesti. - + Last Sync was successful, but with warnings on individual files. Viimeisin synkronointi onnistui, mutta yksittäisten tiedostojen kanssa ilmeni varoituksia. - + Setup Error. Asetusvirhe. - + User Abort. - + Sync is paused. Synkronointi on keskeytetty. - + %1 (Sync is paused) %1 (Synkronointi on keskeytetty) - + No valid folder selected! Kelvollista kansiota ei ole valittu! - + The selected path is not a folder! Valittu polku ei ole kansio! - + You have no permission to write to the selected folder! Sinulla ei ole kirjoitusoikeutta valittuun kansioon! - + The local folder %1 contains a symbolic link. The link target contains an already synced folder Please pick another one! - + There is already a sync from the server to this local folder. Please pick another local folder! - + The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! Paikallinen kansio %1 sisältää kansion, jota käytetään kansion synkronointiyhteydessä. Valitse toinen kansio! - + The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - + 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! @@ -1251,17 +1251,17 @@ Continuing the sync as normal will cause all your files to be overwritten by an - + About Tietoja - + Updates Päivitykset - + &Restart && Update &Käynnistä uudelleen && päivitä @@ -1341,22 +1341,22 @@ Items where deletion is allowed will be deleted if they prevent a directory from Kohteet, joiden poisto on sallittu, poistetaan, jos ne estävät kansion poistamisen. Tämä on hyödyllistä metatietojen osalta. - + Could not open file Tiedoston avaaminen ei onnistunut - + Cannot write changes to '%1'. Muutoksien kirjoittaminen kohteeseen '%1' epäonnistui. - + Add Ignore Pattern Lisää ohituskaava - + Add a new ignore pattern: Lisää uusi ohituskaava: @@ -2013,27 +2013,27 @@ Osoitteen käyttäminen ei ole suositeltavaa. Tiedosto poistettiin palvelimelta - + The file could not be downloaded completely. Tiedostoa ei voitu ladata täysin. - + The downloaded file is empty despite the server announced it should have been %1. - + File %1 cannot be saved because of a local file name clash! - + File has changed since discovery Tiedosto on muuttunut löytymisen jälkeen - + Error writing metadata to the database Virhe kirjoittaessa metadataa tietokantaan @@ -2544,6 +2544,11 @@ Osoitteen käyttäminen ei ole suositeltavaa. Allow editing Salli muokkaus + + + Anyone with the link has access to the file/folder + + P&assword protect diff --git a/translations/client_fr.ts b/translations/client_fr.ts index 9fda7d3017..c570e49b41 100644 --- a/translations/client_fr.ts +++ b/translations/client_fr.ts @@ -813,112 +813,112 @@ Continuer la synchronisation comme d'habitude fera en sorte que tous les fi OCC::FolderMan - + Could not reset folder state Impossible de réinitialiser l'état du dossier - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. Un ancien fichier journal '%1' a été trouvé, mais ne peut être supprimé. Veuillez vous assurer qu’aucune application ne l'utilise en ce moment. - + (backup) (sauvegarde) - + (backup %1) (sauvegarde %1) - + Undefined State. Statut indéfini. - + Waiting to start syncing. En attente de synchronisation. - + Preparing for sync. Préparation de la synchronisation. - + Sync is running. Synchronisation en cours - + Last Sync was successful. Synchronisation terminée avec succès - + Last Sync was successful, but with warnings on individual files. Synchronisation terminée avec des avertissements pour certains fichiers - + Setup Error. Erreur d'installation. - + User Abort. Abandon par l'utilisateur. - + Sync is paused. La synchronisation est en pause. - + %1 (Sync is paused) %1 (Synchronisation en pause) - + No valid folder selected! Aucun dossier valable sélectionné ! - + The selected path is not a folder! Le chemin sélectionné n'est pas un dossier ! - + You have no permission to write to the selected folder! Vous n'avez pas la permission d'écrire dans le dossier sélectionné ! - + The local folder %1 contains a symbolic link. The link target contains an already synced folder Please pick another one! Le dossier local %1 contient un lien symbolique. La cible du lien contient un dossier déjà synchronisé. Veuillez en choisir un autre ! - + There is already a sync from the server to this local folder. Please pick another local folder! Il y a déjà une synchronisation depuis le serveur vers ce dossier local. Merci de choisir un autre dossier local ! - + The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! Le dossier local %1 contient un dossier déjà utilisé pour une synchronisation de dossiers. Veuillez en choisir un autre ! - + The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! Le dossier local %1 se trouve dans un dossier déjà configuré pour une synchronisation de dossier. Veuillez en choisir un autre ! - + 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! Le dossier local %1 est un lien symbolique. Le dossier vers lequel le lien pointe est inclus dans un dossier déjà configuré pour une synchronisation de dossier. Veuillez en choisir un autre ! @@ -1261,17 +1261,17 @@ Continuer la synchronisation comme d'habitude fera en sorte que tous les fi - + About À propos - + Updates Mises à jour - + &Restart && Update &Redémarrer && Mettre à jour @@ -1351,22 +1351,22 @@ Items where deletion is allowed will be deleted if they prevent a directory from L'option "Autoriser suppression" permet de ne pas bloquer la suppression d'un dossier. C'est utile pour les fichiers de méta-données. - + Could not open file Impossible d'ouvrir le fichier - + Cannot write changes to '%1'. Impossible d'écrire les modifications sur '%1'. - + Add Ignore Pattern Ajouter un motif d'exclusion - + Add a new ignore pattern: Ajoutez un nouveau motif d'exclusion : @@ -2024,27 +2024,27 @@ Il est déconseillé de l'utiliser. Le fichier a été supprimé du serveur - + The file could not be downloaded completely. Le fichier n'a pas pu être téléchargé intégralement. - + The downloaded file is empty despite the server announced it should have been %1. Le fichier téléchargé est vide bien que le serveur indique que sa taille devrait être de %1. - + File %1 cannot be saved because of a local file name clash! Le fichier %1 n'a pas pu être sauvegardé en raison d'un conflit sur le nom du fichier local ! - + File has changed since discovery Le fichier a changé depuis sa découverte - + Error writing metadata to the database Erreur à l'écriture des métadonnées dans la base de données @@ -2555,6 +2555,11 @@ Il est déconseillé de l'utiliser. Allow editing Permettre la modification + + + Anyone with the link has access to the file/folder + Quiconque dispose du lien a accès aux fichiers/dossiers + P&assword protect diff --git a/translations/client_gl.ts b/translations/client_gl.ts index c3c15bcd45..84643b388b 100644 --- a/translations/client_gl.ts +++ b/translations/client_gl.ts @@ -803,112 +803,112 @@ Continuing the sync as normal will cause all your files to be overwritten by an OCC::FolderMan - + Could not reset folder state Non foi posíbel restabelecer o estado do cartafol - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. Atopouse un rexistro de sincronización antigo en «%1» máis non pode ser retirado. Asegúrese de que non o está a usar ningunha aplicación. - + (backup) (copia de seguranza) - + (backup %1) (copia de seguranza %1) - + Undefined State. Estado sen definir. - + Waiting to start syncing. - + Preparing for sync. Preparando para sincronizar. - + Sync is running. Estase sincronizando. - + Last Sync was successful. A última sincronización fíxose correctamente. - + Last Sync was successful, but with warnings on individual files. A última sincronización fíxose correctamente, mais con algún aviso en ficheiros individuais. - + Setup Error. Erro de configuración. - + User Abort. Interrompido polo usuario. - + Sync is paused. Sincronización en pausa. - + %1 (Sync is paused) %1 (sincronización en pausa) - + No valid folder selected! Non seleccionou ningún cartafol correcto! - + The selected path is not a folder! - + You have no permission to write to the selected folder! Vostede non ten permiso para escribir neste cartafol! - + The local folder %1 contains a symbolic link. The link target contains an already synced folder Please pick another one! - + There is already a sync from the server to this local folder. Please pick another local folder! - + The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - + The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - + 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! @@ -1251,17 +1251,17 @@ Continuing the sync as normal will cause all your files to be overwritten by an - + About Sobre - + Updates Actualizacións - + &Restart && Update &Reiniciar e actualizar @@ -1339,22 +1339,22 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + Could not open file Non foi posíbel abrir o ficheiro - + Cannot write changes to '%1'. Non é posíbel escribir os cambios en «%1». - + Add Ignore Pattern Engadir o patrón a ignorar - + Add a new ignore pattern: Engadir un novo patrón a ignorar: @@ -2012,27 +2012,27 @@ Recomendámoslle que non o use. O ficheiro vai seren eliminado do servidor - + The file could not be downloaded completely. Non foi posíbel descargar completamente o ficheiro. - + The downloaded file is empty despite the server announced it should have been %1. - + File %1 cannot be saved because of a local file name clash! Non foi posíbel gardar o ficheiro %1 por mor dunha colisión co nome dun ficheiro local! - + File has changed since discovery O ficheiro cambiou após seren atopado - + Error writing metadata to the database @@ -2543,6 +2543,11 @@ Recomendámoslle que non o use. Allow editing Permitir a edición + + + Anyone with the link has access to the file/folder + + P&assword protect diff --git a/translations/client_hu.ts b/translations/client_hu.ts index 3794b8b4b0..192694b89b 100644 --- a/translations/client_hu.ts +++ b/translations/client_hu.ts @@ -803,112 +803,112 @@ Continuing the sync as normal will cause all your files to be overwritten by an OCC::FolderMan - + Could not reset folder state - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - + (backup) (biztonsági mentés) - + (backup %1) (biztonsági mentés: %1) - + Undefined State. Ismeretlen állapot. - + Waiting to start syncing. Várakozás a szinkronizálás elindítására. - + Preparing for sync. Előkészítés szinkronizációhoz. - + Sync is running. Szinkronizálás fut. - + Last Sync was successful. Legutolsó szinkronizálás sikeres volt. - + Last Sync was successful, but with warnings on individual files. Az utolsó szinkronizáció sikeresen lefutott, de néhány figyelmeztetés van. - + Setup Error. Beállítás hiba. - + User Abort. Felhasználó megszakította. - + Sync is paused. Szinkronizálás megállítva. - + %1 (Sync is paused) %1 (szinkronizálás megállítva) - + No valid folder selected! Nincs érvényes könyvtár kiválasztva! - + The selected path is not a folder! A kiválasztott elérési út nem könyvtár! - + You have no permission to write to the selected folder! Nincs joga a kiválasztott könyvtár írásához! - + The local folder %1 contains a symbolic link. The link target contains an already synced folder Please pick another one! - + There is already a sync from the server to this local folder. Please pick another local folder! - + The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - + The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - + 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! @@ -1251,17 +1251,17 @@ Continuing the sync as normal will cause all your files to be overwritten by an - + About Rólunk - + Updates Frissítések - + &Restart && Update Új&raindítás és frissítés @@ -1339,22 +1339,22 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + Could not open file Nem sikerült a fájl megnyitása - + Cannot write changes to '%1'. - + Add Ignore Pattern - + Add a new ignore pattern: @@ -2010,27 +2010,27 @@ It is not advisable to use it. A fájl törlésre került a szerverről - + The file could not be downloaded completely. - + The downloaded file is empty despite the server announced it should have been %1. - + File %1 cannot be saved because of a local file name clash! - + File has changed since discovery - + Error writing metadata to the database @@ -2541,6 +2541,11 @@ It is not advisable to use it. Allow editing Szerkesztés engedélyezése + + + Anyone with the link has access to the file/folder + + P&assword protect diff --git a/translations/client_it.ts b/translations/client_it.ts index 7ffe6a9106..fc949e14a5 100644 --- a/translations/client_it.ts +++ b/translations/client_it.ts @@ -541,7 +541,7 @@ There was an error while accessing the configuration file at %1. - + Si è verificato un errore durante l'accesso al file di configurazione su %1. @@ -808,112 +808,112 @@ Se continui normalmente la sincronizzazione provocherai la sovrascrittura di tut OCC::FolderMan - + Could not reset folder state Impossibile ripristinare lo stato della cartella - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. È stato trovato un vecchio registro di sincronizzazione '%1', ma non può essere rimosso. Assicurati che nessuna applicazione lo stia utilizzando. - + (backup) (copia di sicurezza) - + (backup %1) (copia di sicurezza %1) - + Undefined State. Stato non definito. - + Waiting to start syncing. In attesa di iniziare la sincronizzazione. - + Preparing for sync. Preparazione della sincronizzazione. - + Sync is running. La sincronizzazione è in corso. - + Last Sync was successful. L'ultima sincronizzazione è stata completata correttamente. - + Last Sync was successful, but with warnings on individual files. Ultima sincronizzazione avvenuta, ma con avvisi relativi a singoli file. - + Setup Error. Errore di configurazione. - + User Abort. Interrotto dall'utente. - + Sync is paused. La sincronizzazione è sospesa. - + %1 (Sync is paused) %1 (La sincronizzazione è sospesa) - + No valid folder selected! Nessuna cartella valida selezionata! - + The selected path is not a folder! Il percorso selezionato non è una cartella! - + You have no permission to write to the selected folder! Non hai i permessi di scrittura per la cartella selezionata! - + The local folder %1 contains a symbolic link. The link target contains an already synced folder Please pick another one! La cartella locale %1 contiene un collegamento simbolico. La destinazione del collegamento contiene una cartella già sincronizzata. Selezionane un'altra! - + There is already a sync from the server to this local folder. Please pick another local folder! Esiste già una sincronizzazione dal server a questa cartella locale. Seleziona un'altra cartella locale! - + The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! La cartella locale %1 contiene già una cartella utilizzata in una connessione di sincronizzazione delle cartelle. Selezionane un'altra! - + The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! La cartella locale %1 è già contenuta in una cartella utilizzata in una connessione di sincronizzazione delle cartelle. Selezionane un'altra! - + 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! La cartella locale %1 è un collegamento simbolico. La destinazione del collegamento è già contenuta in una cartella utilizzata in una connessione di sincronizzazione delle cartelle. Selezionane un'altra! @@ -1256,17 +1256,17 @@ Se continui normalmente la sincronizzazione provocherai la sovrascrittura di tut - + About Informazioni - + Updates Aggiornamenti - + &Restart && Update &Riavvia e aggiorna @@ -1346,22 +1346,22 @@ Items where deletion is allowed will be deleted if they prevent a directory from Gli elementi per i quali è consentita l'eliminazione, saranno eliminati se impediscono la rimozione di una cartella. Utile per i metadati. - + Could not open file Impossibile aprire il file - + Cannot write changes to '%1'. Impossibile scrivere le modifiche in '%1'. - + Add Ignore Pattern Aggiungi modello Ignora - + Add a new ignore pattern: Aggiungi un nuovo modello di esclusione: @@ -2018,27 +2018,27 @@ Non è consigliabile utilizzarlo. Il file è stato eliminato dal server - + The file could not be downloaded completely. Il file non può essere scaricato completamente. - + The downloaded file is empty despite the server announced it should have been %1. Il file scaricato è vuoto nonostante il server indicasse una dimensione di %1. - + File %1 cannot be saved because of a local file name clash! Il file %1 non può essere salvato a causa di un conflitto con un file locale. - + File has changed since discovery Il file è stato modificato dal suo rilevamento - + Error writing metadata to the database Errore durante la scrittura dei metadati nel database @@ -2176,7 +2176,7 @@ Non è consigliabile utilizzarlo. File %1 cannot be uploaded because another file with the same name, differing only in case, exists - + Il file %1 non può essere caricato poiché esiste un altro file con lo stesso nome, ma con differenze tra maiuscole e minuscole @@ -2369,7 +2369,7 @@ Non è consigliabile utilizzarlo. Deselect remote folders you do not wish to synchronize. - + Deseleziona le cartelle remote che non desideri sincronizzare. @@ -2549,6 +2549,11 @@ Non è consigliabile utilizzarlo. Allow editing Consenti la modifica + + + Anyone with the link has access to the file/folder + Chiunque sia in possesso del collegamento ha accesso al file/cartella + P&assword protect @@ -3074,12 +3079,12 @@ Non è consigliabile utilizzarlo. File names ending with a period are not supported on this file system. - + I nomi del file che terminano con un punto non sono supportati su questo file system. File names containing the character '%1' are not supported on this file system. - + I nomi del file che contengono il carattere '%1' non sono supportati su questo file system. @@ -3488,7 +3493,7 @@ Non è consigliabile utilizzarlo. Ask for confirmation before synchroni&zing folders larger than - + Chiedi conferma prima di sincroni&zzare cartelle più grandi di @@ -3499,7 +3504,7 @@ Non è consigliabile utilizzarlo. Ask for confirmation before synchronizing e&xternal storages - + Chiedi conferma prima di sincroni&zzare archiviazioni esterne @@ -3795,7 +3800,7 @@ Non è consigliabile utilizzarlo. built with %1 - + creato con %1 diff --git a/translations/client_ja.ts b/translations/client_ja.ts index f014ac26b0..d750ca1f24 100644 --- a/translations/client_ja.ts +++ b/translations/client_ja.ts @@ -810,112 +810,112 @@ Continuing the sync as normal will cause all your files to be overwritten by an OCC::FolderMan - + Could not reset folder state フォルダーの状態をリセットできませんでした - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. 古い同期ジャーナル '%1' が見つかりましたが、削除できませんでした。それを現在使用しているアプリケーションが存在しないか確認してください。 - + (backup) (バックアップ) - + (backup %1) (%1をバックアップ) - + Undefined State. 未定義の状態。 - + Waiting to start syncing. 同期開始を待機中 - + Preparing for sync. 同期の準備中。 - + Sync is running. 同期を実行中です。 - + Last Sync was successful. 最後の同期は成功しました。 - + Last Sync was successful, but with warnings on individual files. 最新の同期は成功しました。しかし、一部のファイルに問題がありました。 - + Setup Error. 設定エラー。 - + User Abort. ユーザーによる中止。 - + Sync is paused. 同期を一時停止しました。 - + %1 (Sync is paused) %1 (同期を一時停止) - + No valid folder selected! 有効なフォルダーが選択されていません! - + The selected path is not a folder! 指定のパスは、フォルダーではありません! - + You have no permission to write to the selected folder! 選択されたフォルダーに書き込み権限がありません - + 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 には同期フォルダーとして利用されているフォルダーがあります。他のフォルダーを選択してください! @@ -1258,17 +1258,17 @@ Continuing the sync as normal will cause all your files to be overwritten by an - + About バージョン情報 - + Updates アップデート - + &Restart && Update 再起動してアップデート(&R) @@ -1348,22 +1348,22 @@ Items where deletion is allowed will be deleted if they prevent a directory from パターンによってディレクトリを削除から除外する場合は,パターンに含まれた項目も削除されます。例えばメタデータファイルに有用です。 - + Could not open file ファイルが開けませんでした - + Cannot write changes to '%1'. '%1'を更新できません。 - + Add Ignore Pattern 除外するファイルパターンを追加 - + Add a new ignore pattern: 除外するファイルパターンを新しく追加: @@ -2019,27 +2019,27 @@ It is not advisable to use it. ファイルはサーバーから削除されました - + The file could not be downloaded completely. このファイルのダウンロードは完了しませんでした - + The downloaded file is empty despite the server announced it should have been %1. サーバーが通知しているファイルは %1 であるべきですが、ダウンロードファイルは空でした。 - + File %1 cannot be saved because of a local file name clash! %1 はローカルファイル名が衝突しているため保存できません! - + File has changed since discovery ファイルは発見以降に変更されました - + Error writing metadata to the database メタデータのデータベースへの書き込みに失敗 @@ -2550,6 +2550,11 @@ It is not advisable to use it. Allow editing 編集を許可 + + + Anyone with the link has access to the file/folder + + P&assword protect diff --git a/translations/client_nb_NO.ts b/translations/client_nb_NO.ts index b47dd8462b..76bd6db10e 100644 --- a/translations/client_nb_NO.ts +++ b/translations/client_nb_NO.ts @@ -811,112 +811,112 @@ Hvis synkroniseringen fortsetter som normalt, vil alle filene dine bli overskrev OCC::FolderMan - + Could not reset folder state Klarte ikke å tilbakestille mappetilstand - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. En gammel synkroniseringsjournal '%1' ble funnet men kunne ikke fjernes. Pass på at ingen applikasjoner bruker den. - + (backup) (sikkerhetskopi) - + (backup %1) (sikkerhetskopi %1) - + Undefined State. Udefinert tilstand. - + Waiting to start syncing. Venter på å starte synkronisering. - + Preparing for sync. Forbereder synkronisering. - + Sync is running. Synkronisering kjører. - + Last Sync was successful. Siste synkronisering var vellykket. - + Last Sync was successful, but with warnings on individual files. Siste synkronisering var vellykket, men med advarsler på enkelte filer. - + Setup Error. Feil med oppsett. - + User Abort. Brukeravbrudd. - + Sync is paused. Synkronisering er satt på pause. - + %1 (Sync is paused) %1 (Synkronisering er satt på pause) - + No valid folder selected! Ingen gyldig mappe valgt! - + The selected path is not a folder! Den valgte stien er ikke en mappe! - + You have no permission to write to the selected folder! Du har ikke skrivetilgang til den valgte mappen! - + The local folder %1 contains a symbolic link. The link target contains an already synced folder Please pick another one! Den lokale mappen %1 inneholder en symbolsk lenke. Målet for lenken inneholder en mappe som allerede er synkronisert. Velg en annen mappe! - + There is already a sync from the server to this local folder. Please pick another local folder! Det er allerede en synkronisering fra serveren til denne lokale mappen. Velg en annen mappe! - + The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! Den lokale mappen %1 inneholder allerede en mappe brukt i en mappe-synkronisering. Velg en annen! - + The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! Den lokale mappen %1 er allerede en undermappe av en mappe brukt i en mappe-synkronisering. Velg en annen! - + 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! Den lokale mappen %1 er en symbolsk lenke. Målet for lenken er allerede en undermappe av en mappe brukt i en mappe-synkronisering. Velg en annen! @@ -1259,17 +1259,17 @@ Hvis synkroniseringen fortsetter som normalt, vil alle filene dine bli overskrev - + About Om - + Updates Oppdateringer - + &Restart && Update &Omstart && Oppdater @@ -1349,22 +1349,22 @@ Items where deletion is allowed will be deleted if they prevent a directory from Elementer hvor sletting er tillatt, vil bli slettet hvis de forhindrer fjerning av en mappe. Dette er hendig for metadata. - + Could not open file Klarte ikke å åpne fil - + Cannot write changes to '%1'. Kan ikke skrive endringer til '%1'. - + Add Ignore Pattern Nytt mønster - + Add a new ignore pattern: Legg til ignoreringsmønster: @@ -2022,27 +2022,27 @@ Det er ikke tilrådelig å bruke den. Filen ble slettet fra serveren - + The file could not be downloaded completely. Hele filen kunne ikke lastes ned. - + The downloaded file is empty despite the server announced it should have been %1. Nedlastet fil er tom, selv om serveren annonserte at den skulle være %1. - + File %1 cannot be saved because of a local file name clash! Fil %1 kan ikke lagres på grunn av lokal konflikt med filnavn. - + File has changed since discovery Filen er endret siden den ble oppdaget - + Error writing metadata to the database Feil ved skriving av metadata til databasen @@ -2553,6 +2553,11 @@ Det er ikke tilrådelig å bruke den. Allow editing Tillat redigering + + + Anyone with the link has access to the file/folder + + P&assword protect diff --git a/translations/client_nl.ts b/translations/client_nl.ts index 36082de3a2..d2e03afd6e 100644 --- a/translations/client_nl.ts +++ b/translations/client_nl.ts @@ -811,112 +811,112 @@ Doorgaan met deze synchronisatie overschrijft al uw bestanden door een eerdere v OCC::FolderMan - + Could not reset folder state Kan de beginstaat van de map niet terugzetten - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. Een oud synchronisatieverslag '%1' is gevonden maar kan niet worden verwijderd. Zorg ervoor dat geen applicatie dit bestand gebruikt. - + (backup) (backup) - + (backup %1) (backup %1) - + Undefined State. Ongedefiniëerde staat - + Waiting to start syncing. In afwachting van synchronisatie. - + Preparing for sync. Synchronisatie wordt voorbereid - + Sync is running. Bezig met synchroniseren. - + Last Sync was successful. Laatste synchronisatie was geslaagd. - + Last Sync was successful, but with warnings on individual files. Laatste synchronisatie geslaagd, maar met waarschuwingen over individuele bestanden. - + Setup Error. Installatiefout. - + User Abort. Afgebroken door gebruiker. - + Sync is paused. Synchronisatie gepauzeerd. - + %1 (Sync is paused) %1 (Synchronisatie onderbroken) - + No valid folder selected! Geen geldige map geselecteerd! - + The selected path is not a folder! Het geselecteerde pad is geen map! - + You have no permission to write to the selected folder! U heeft geen permissie om te schrijven naar de geselecteerde map! - + The local folder %1 contains a symbolic link. The link target contains an already synced folder Please pick another one! Lokale map %1 bevat een symbolische link. De doellink bevat een map die al is gesynchroniseerd. Kies een andere! - + There is already a sync from the server to this local folder. Please pick another local folder! Er wordt vanaf de server al naar deze lokale map gesynchroniseerd. Kies een andere lokale map! - + The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! Lokale map %1 bevat al een map die wordt gebruikt voor een mapsync verbinding. Kies een andere! - + The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! Lokale map %1 zit al in een map die wordt gebruikt voor een mapsync verbinding. Kies een andere! - + 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! Lokale map %1 is een symbolische link. De doellink zit al in een map die in een mapsync verbinding wordt gebruikt. Kies een andere! @@ -1260,17 +1260,17 @@ Doorgaan met deze synchronisatie overschrijft al uw bestanden door een eerdere v - + About Over - + Updates Updates - + &Restart && Update &Herstarten en &Bijwerken @@ -1354,22 +1354,22 @@ Items where deletion is allowed will be deleted if they prevent a directory from Onderdelen die gewist mogen worden worden verwijderd als ze voorkomen dat een map verdwijnt. Dit is nuttig voor metadata. - + Could not open file Kon het bestand niet openen - + Cannot write changes to '%1'. Er kunnen geen wijzigingen worden geschreven naar %1 - + Add Ignore Pattern Toevoegen negeerpatroon - + Add a new ignore pattern: Voeg nieuw negeerpatroon toe: @@ -2027,27 +2027,27 @@ We adviseren deze site niet te gebruiken. Bestand was verwijderd van de server - + The file could not be downloaded completely. Het bestand kon niet volledig worden gedownload. - + The downloaded file is empty despite the server announced it should have been %1. Het gedownloade bestand is leeg, hoewel de server meldde dat het %1 zou moeten zijn. - + File %1 cannot be saved because of a local file name clash! Bestand %1 kan niet worden opgeslagen wegens een lokaal bestandsnaam conflict! - + File has changed since discovery Het bestand is gewijzigd sinds het is gevonden - + Error writing metadata to the database Fout bij schrijven van Metadata naar de database @@ -2558,6 +2558,11 @@ We adviseren deze site niet te gebruiken. Allow editing Toestaan bewerken + + + Anyone with the link has access to the file/folder + Iedereen met de link heeft toegang tot het bestand of de map + P&assword protect diff --git a/translations/client_pl.ts b/translations/client_pl.ts index 7744aa861d..e773f7c385 100644 --- a/translations/client_pl.ts +++ b/translations/client_pl.ts @@ -805,112 +805,112 @@ Continuing the sync as normal will cause all your files to be overwritten by an OCC::FolderMan - + Could not reset folder state Nie udało się zresetować stanu folderu - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. Stary sync journal '%1' został znaleziony, lecz nie mógł być usunięty. Proszę się upewnić, że żaden program go obecnie nie używa. - + (backup) (kopia zapasowa) - + (backup %1) (kopia zapasowa %1) - + Undefined State. Niezdefiniowany stan - + Waiting to start syncing. Poczekaj na rozpoczęcie synchronizacji. - + Preparing for sync. Przygotowuję do synchronizacji - + Sync is running. Synchronizacja w toku - + Last Sync was successful. Ostatnia synchronizacja zakończona powodzeniem. - + Last Sync was successful, but with warnings on individual files. Ostatnia synchronizacja udana, ale istnieją ostrzeżenia z pojedynczymi plikami. - + Setup Error. Błąd ustawień. - + User Abort. Użytkownik anulował. - + Sync is paused. Synchronizacja wstrzymana - + %1 (Sync is paused) %1 (Synchronizacja jest zatrzymana) - + No valid folder selected! Nie wybrano poprawnego folderu. - + The selected path is not a folder! Wybrana ścieżka nie jest katalogiem! - + You have no permission to write to the selected folder! Nie masz uprawnień, aby zapisywać w tym katalogu! - + The local folder %1 contains a symbolic link. The link target contains an already synced folder Please pick another one! - + 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! Lokalny folder %1 już zawiera folder użyty na potrzeby synchronizacji. Proszę wybrać inny folder lokalny. - + The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! Lokalny folder %1 już zawiera folder użyty na potrzeby synchronizacji. Proszę wybrać inny folder lokalny. - + 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! @@ -1253,17 +1253,17 @@ Continuing the sync as normal will cause all your files to be overwritten by an - + About O aplikacji - + Updates Aktualizacje - + &Restart && Update &Zrestartuj && Aktualizuj @@ -1343,22 +1343,22 @@ Items where deletion is allowed will be deleted if they prevent a directory from Pozycje, dla których usuwanie jest dozwolone zostaną usunięte, jeżeli uprawnienia katalogu dopuszczają usuwanie. - + Could not open file Nie można otworzyć plików - + Cannot write changes to '%1'. Nie mogę zapisać zmian do '%1'. - + Add Ignore Pattern Dodaj ignorowany - + Add a new ignore pattern: Dodaj nowy ignorowany: @@ -2016,27 +2016,27 @@ Niezalecane jest jego użycie. Plik został usunięty z serwera - + The file could not be downloaded completely. Ten plik nie mógł być całkowicie pobrany. - + The downloaded file is empty despite the server announced it should have been %1. - + File %1 cannot be saved because of a local file name clash! - + File has changed since discovery - + Error writing metadata to the database Błąd podczas zapisu metadanych do bazy @@ -2547,6 +2547,11 @@ Niezalecane jest jego użycie. Allow editing Pozwól na edycję + + + Anyone with the link has access to the file/folder + Każdy posiadający link ma dostęp do pliku/katalogu. + P&assword protect diff --git a/translations/client_pt.ts b/translations/client_pt.ts index 4cc868d776..5bdf1dd69a 100644 --- a/translations/client_pt.ts +++ b/translations/client_pt.ts @@ -812,112 +812,112 @@ Continuando a sincronização fará com que todos os seus ficheiros sejam substi OCC::FolderMan - + Could not reset folder state Não foi possível reiniciar o estado da pasta - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. Foi encontrado um 'journal' de sincronização, mas não foi possível removê-lo. Por favor, certifique-se que nenhuma aplicação o está a utilizar. - + (backup) (cópia de segurança) - + (backup %1) (cópia de segurança %1) - + Undefined State. Estado indefinido. - + Waiting to start syncing. A aguardar para iniciar a sincronização. - + Preparing for sync. A preparar para sincronizar. - + Sync is running. A sincronização está em execução. - + Last Sync was successful. A última sincronização foi bem sucedida. - + Last Sync was successful, but with warnings on individual files. A última sincronização foi bem sucedida, mas com avisos nos ficheiros individuais. - + Setup Error. Erro de instalação. - + User Abort. Abortado pelo utilizador. - + Sync is paused. A sincronização está pausada. - + %1 (Sync is paused) %1 (A sincronização está pausada) - + No valid folder selected! Não foi selecionada nenhuma pasta válida! - + The selected path is not a folder! O caminho selecionado não é uma pasta! - + You have no permission to write to the selected folder! Não tem permissão para gravar para a pasta selecionada! - + The local folder %1 contains a symbolic link. The link target contains an already synced folder Please pick another one! A pasta local %1 contém hiperligação simbólica. O destino da hiperligação já contém uma pasta sincronizada. Por favor, escolha outra! - + There is already a sync from the server to this local folder. Please pick another local folder! Já existe uma sincronização do servidor para esta pasta local. Por favor escolha outra pasta local! - + The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! A pasta local %1 já contém uma pasta utilizada numa ligação de sincronização de pasta. Por favor, escolha outra! - + The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! A pasta local %1 já contém uma pasta usada numa ligação de sincronização de pasta. Por favor, escolha outra! - + 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! A pasta local %1 é uma hiperligação simbólica. A hiperligação de destino já contém uma pasta usada numa ligação de sincronização de pasta. Por favor, escolha outra! @@ -1260,17 +1260,17 @@ Continuando a sincronização fará com que todos os seus ficheiros sejam substi - + About Sobre - + Updates Atualizações - + &Restart && Update &Reiniciar e Atualizar @@ -1350,22 +1350,22 @@ Items where deletion is allowed will be deleted if they prevent a directory from Os itens onde é permitido a eliminação serão eliminados se estes impedirem a remoção de uma diretoria. Isto é útil para os metadados. - + Could not open file Não foi possível abrir o ficheiro - + Cannot write changes to '%1'. Não foi possível gravar as alterações para '%1' - + Add Ignore Pattern Adicione Padrão de ignorar - + Add a new ignore pattern: Adicione um novo padrão de ignorar: @@ -2023,27 +2023,27 @@ Não é aconselhada a sua utilização. O ficheiro foi eliminado do servidor - + The file could not be downloaded completely. Não foi possível transferir o ficheiro na totalidade. - + The downloaded file is empty despite the server announced it should have been %1. O ficheiro transferido está vazio, apesar do servidor indicar que este deveria ter %1. - + File %1 cannot be saved because of a local file name clash! Ficheiro %1 não pode ser guardado devido à existência de um ficheiro local com o mesmo nome. - + File has changed since discovery O ficheiro alterou-se desde a sua descoberta - + Error writing metadata to the database Erro ao gravar os metadados para a base de dados @@ -2554,6 +2554,11 @@ Não é aconselhada a sua utilização. Allow editing Permitir edição + + + Anyone with the link has access to the file/folder + Qualquer pessoa com a hiperligação terá acesso ao ficheiro/pasta + P&assword protect diff --git a/translations/client_pt_BR.ts b/translations/client_pt_BR.ts index 52864f4aeb..9bb8a78516 100644 --- a/translations/client_pt_BR.ts +++ b/translations/client_pt_BR.ts @@ -810,112 +810,112 @@ Continuar a sincronização como normal fará com que todos os seus arquivos sej OCC::FolderMan - + Could not reset folder state Não foi possível redefinir o estado da pasta - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. Uma velha revista de sincronização '%1' foi encontrada, mas não pôde ser removida. Por favor, certifique-se de que nenhuma aplicação está a usá-la. - + (backup) (backup) - + (backup %1) (backup %1) - + Undefined State. Estado indefinido. - + Waiting to start syncing. À espera do início da sincronização. - + Preparing for sync. Preparando para sincronização. - + Sync is running. A sincronização está ocorrendo. - + Last Sync was successful. A última sincronização foi feita com sucesso. - + Last Sync was successful, but with warnings on individual files. A última sincronização foi executada com sucesso, mas com advertências em arquivos individuais. - + Setup Error. Erro de Configuração. - + User Abort. Usuário Abortou. - + Sync is paused. Sincronização pausada. - + %1 (Sync is paused) %1 (Pausa na Sincronização) - + No valid folder selected! Nenhuma pasta válida selecionada! - + The selected path is not a folder! O caminho selecionado não é uma pasta! - + You have no permission to write to the selected folder! Voce não tem permissão para escrita na pasta selecionada! - + The local folder %1 contains a symbolic link. The link target contains an already synced folder Please pick another one! A pasta local %1 contém um link simbólico. O destino do link contém uma pasta já sincronizados. Por favor, escolha uma outra! - + There is already a sync from the server to this local folder. Please pick another local folder! Já existe uma sincronização do servidor para esta pasta local. Por favor, escolha uma outra pasta local! - + The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! A pasta local %1 já contém uma pasta utilizada numa ligação de sincronização de pasta. Por favor, escolha outra! - + The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! A pasta local %1 já está contida em uma pasta usada em uma conexão de sincronização de pastas. Por favor, escolha outra! - + 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! A pasta local %1 é um link simbólico. O destino do link já está contido em uma pasta usada em uma conexão de sincronização de pastas. Por favor, escolha outra! @@ -1091,7 +1091,7 @@ Continuar a sincronização como normal fará com que todos os seus arquivos sej Click to select a local folder to sync. - Click para selecionar uma pasta local para sincronização. + Clique para selecionar uma pasta local para sincronização. @@ -1258,17 +1258,17 @@ Continuar a sincronização como normal fará com que todos os seus arquivos sej - + About Sobre - + Updates Atualizações - + &Restart && Update &Reiniciar && Atualização @@ -1294,7 +1294,7 @@ Continuar a sincronização como normal fará com que todos os seus arquivos sej <a href="%1">Click here</a> to request an app password from the web interface. - <a href="%1">Click aqui</a> para solicitar uma senha de aplicativo na interface da web. + <a href="%1">Clique aqui</a> para solicitar uma senha de aplicativo na interface da web. @@ -1349,22 +1349,22 @@ Items where deletion is allowed will be deleted if they prevent a directory from Itens onde a eliminação é permitida serão excluídos se eles evitarem que um diretório seja removido. Isso é útil para metadados. - + Could not open file Não foi possível abrir o arquivo - + Cannot write changes to '%1'. Não é possível gravar as alterações em '%1'. - + Add Ignore Pattern Adicionar Ignorar Padrão - + Add a new ignore pattern: Adicionar um novo padrão ignorar: @@ -1468,7 +1468,7 @@ Itens onde a eliminação é permitida serão excluídos se eles evitarem que um Skip this time - Pular desta vêz + Pular desta vez @@ -2020,27 +2020,27 @@ It is not advisable to use it. O arquivo foi eliminado do servidor - + The file could not be downloaded completely. O arquivo não pode ser baixado completamente. - + The downloaded file is empty despite the server announced it should have been %1. O arquivo baixado está vazio apesar do servidor anunciou que deveria ter %1. - + File %1 cannot be saved because of a local file name clash! O arquivo %1 não pode ser salvo devido a um confronto com um nome de arquivo local! - + File has changed since discovery Arquivo foi alterado desde a descoberta - + Error writing metadata to the database Ocorreu um erro ao escrever metadados ao banco de dados @@ -2551,6 +2551,11 @@ It is not advisable to use it. Allow editing Permitir edição + + + Anyone with the link has access to the file/folder + Qualquer pessoa com o link tem acesso ao arquivo/pasta + P&assword protect diff --git a/translations/client_ru.ts b/translations/client_ru.ts index 485ac8dfac..75d0152827 100644 --- a/translations/client_ru.ts +++ b/translations/client_ru.ts @@ -810,112 +810,112 @@ Continuing the sync as normal will cause all your files to be overwritten by an OCC::FolderMan - + Could not reset folder state Невозможно сбросить состояние каталога - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. Найден старый журнал синхронизации '%1', и он не может быть удалён. Убедитесь что он не открыт в другом приложении. - + (backup) (резервная копия) - + (backup %1) (резервная копия %1) - + Undefined State. Неопределенное состояние. - + Waiting to start syncing. Ожидание запуска синхронизации. - + Preparing for sync. Подготовка к синхронизации. - + Sync is running. Идет синхронизация. - + Last Sync was successful. Последняя синхронизация прошла успешно. - + Last Sync was successful, but with warnings on individual files. Последняя синхронизация прошла успешно, но были предупреждения для некоторых файлов. - + Setup Error. Ошибка установки. - + User Abort. Отмена пользователем. - + Sync is paused. Синхронизация приостановлена. - + %1 (Sync is paused) %! (синхронизация приостановлена) - + No valid folder selected! Не выбран валидный каталог! - + The selected path is not a folder! Выбранный путь не является каталогом! - + You have no permission to write to the selected folder! У вас недостаточно прав для записи в выбранный каталог! - + 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 является символьной ссылкой. Эта ссылка указывает на путь, находящийся внутри директории, уже используемой для синхронизации. Пожалуйста укажите другую! @@ -1258,17 +1258,17 @@ Continuing the sync as normal will cause all your files to be overwritten by an - + About О программе - + Updates Обновления - + &Restart && Update &Перезапуск и обновление @@ -1347,22 +1347,22 @@ Items where deletion is allowed will be deleted if they prevent a directory from Элементы, где это разрешается, будут удалены, в случае если они помешают удалению папки. Используется для метаданных. - + Could not open file Невозможно открыть файл - + Cannot write changes to '%1'. Невозможно записать изменения в '%1'. - + Add Ignore Pattern Добавить шаблон игнорирования - + Add a new ignore pattern: Добавить новый шаблон игнорирования: @@ -2020,27 +2020,27 @@ It is not advisable to use it. Файл был удален с сервера - + The file could not be downloaded completely. Невозможно полностью загрузить файл. - + The downloaded file is empty despite the server announced it should have been %1. Скачанный файл пуст, хотя сервер заявил, что он должен быть %1. - + File %1 cannot be saved because of a local file name clash! Файл %1 не может быть сохранён из-за локального конфликта имен! - + File has changed since discovery После обнаружения файл был изменен - + Error writing metadata to the database Ошибка записи метаданных в базу данных @@ -2551,6 +2551,11 @@ It is not advisable to use it. Allow editing Разрешить редактирование + + + Anyone with the link has access to the file/folder + Каждый, у кого есть эта ссылка, имеет доступ к файлу/каталогу + P&assword protect diff --git a/translations/client_sk.ts b/translations/client_sk.ts index 736b1fce2a..e27219054b 100644 --- a/translations/client_sk.ts +++ b/translations/client_sk.ts @@ -803,112 +803,112 @@ Continuing the sync as normal will cause all your files to be overwritten by an OCC::FolderMan - + Could not reset folder state Nemožno resetovať stav priečinka - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. Starý synchronizačný žurnál '%1' nájdený, avšak neodstrániteľný. Prosím uistite sa, že žiadna aplikácia ho práve nevyužíva. - + (backup) (záloha) - + (backup %1) (záloha %1) - + Undefined State. Nedefinovaný stav. - + Waiting to start syncing. Čaká sa na začiatok synchronizácie - + Preparing for sync. Príprava na synchronizáciu. - + Sync is running. Synchronizácia prebieha. - + Last Sync was successful. Posledná synchronizácia sa úspešne skončila. - + Last Sync was successful, but with warnings on individual files. Posledná synchronizácia bola úspešná, ale s varovaniami pre individuálne súbory. - + Setup Error. Chyba pri inštalácii. - + User Abort. Zrušené používateľom. - + Sync is paused. Synchronizácia je pozastavená. - + %1 (Sync is paused) %1 (Synchronizácia je pozastavená) - + No valid folder selected! Nebol zvolený platný priečinok. - + The selected path is not a folder! Zvolená cesta nie je priečinok. - + You have no permission to write to the selected folder! Nemáte oprávnenia pre zápis do daného priečinka! - + The local folder %1 contains a symbolic link. The link target contains an already synced folder Please pick another one! - + There is already a sync from the server to this local folder. Please pick another local folder! - + The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - + The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - + 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! @@ -1251,17 +1251,17 @@ Continuing the sync as normal will cause all your files to be overwritten by an - + About O - + Updates Aktualizácie - + &Restart && Update &Restart && aktualizácia @@ -1339,22 +1339,22 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + Could not open file Nemožno otvoriť súbor - + Cannot write changes to '%1'. Nemožno zapísať zmeny do '%1'. - + Add Ignore Pattern Pridať vzor ignorovaného súboru - + Add a new ignore pattern: Pridať nový vzor ignorovaného súboru: @@ -2012,27 +2012,27 @@ Nie je vhodné ju používať. Súbor bol vymazaný zo servera - + The file could not be downloaded completely. Súbor sa nedá stiahnuť úplne. - + The downloaded file is empty despite the server announced it should have been %1. - + File %1 cannot be saved because of a local file name clash! Súbor %1 nie je možné uložiť, pretože jeho názov koliduje s názvom lokálneho súboru! - + File has changed since discovery Súbor sa medzitým zmenil - + Error writing metadata to the database Chyba pri zápise metadát do databázy @@ -2543,6 +2543,11 @@ Nie je vhodné ju používať. Allow editing Povoliť úpravy + + + Anyone with the link has access to the file/folder + + P&assword protect diff --git a/translations/client_sl.ts b/translations/client_sl.ts index 46da52970a..eaec8fcec7 100644 --- a/translations/client_sl.ts +++ b/translations/client_sl.ts @@ -812,112 +812,112 @@ Z nadaljevanjem usklajevanja bodo vse trenutne datoteke prepisane s starejšimi OCC::FolderMan - + Could not reset folder state Ni mogoče ponastaviti stanja mape - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. Obstaja starejši dnevnik usklajevanja '%1', vendar ga ni mogoče odstraniti. Preverite, ali je datoteka v uporabi. - + (backup) (varnostna kopija) - + (backup %1) (varnostna kopija %1) - + Undefined State. Nedoločeno stanje. - + Waiting to start syncing. Čakanje začetek usklajevanja - + Preparing for sync. Poteka priprava za usklajevanje. - + Sync is running. Usklajevanje je v teku. - + Last Sync was successful. Zadnje usklajevanje je bilo uspešno končano. - + Last Sync was successful, but with warnings on individual files. Zadnje usklajevanje je bilo sicer uspešno, vendar z opozorili za posamezne datoteke. - + Setup Error. Napaka nastavitve. - + User Abort. Uporabniška prekinitev. - + Sync is paused. Usklajevanje je začasno v premoru. - + %1 (Sync is paused) %1 (usklajevanje je v premoru) - + No valid folder selected! Ni izbrane veljavne mape! - + The selected path is not a folder! Izbrana pot ni mapa! - + You have no permission to write to the selected folder! Ni ustreznih dovoljenj za pisanje v izbrano mapo! - + The local folder %1 contains a symbolic link. The link target contains an already synced folder Please pick another one! Krajevna mapa %1 vsebuje simbolno povezavo. Ciljno mesto povezave že vključuje mapo, ki se usklajuje. Izbrati je treba drugo. - + There is already a sync from the server to this local folder. Please pick another local folder! Za to krajevno pot je že ustvarjeno mesto za usklajevanje. Izbrati je treba drugo. - + The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! Krajevna mapa %1 že vključuje mapo, ki je določena za usklajevanje. Izbrati je treba drugo. - + The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! Krajevna mapa %1 je že v določena za usklajevanje. Izbrati je treba drugo. - + 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! Krajevna mapa %1 je simbolna povezava. Cilj povezave že vsebuje mapo, ki je uporabljena pri povezavi usklajevanja mape. Izberite drugo. @@ -1260,17 +1260,17 @@ Z nadaljevanjem usklajevanja bodo vse trenutne datoteke prepisane s starejšimi - + About O programu... - + Updates Posodobitve - + &Restart && Update &Ponovno zaženi && posodobi @@ -1350,22 +1350,22 @@ Items where deletion is allowed will be deleted if they prevent a directory from Predmeti na mestu, kjer je brisanje dovoljeno, bodo izbisani, v kolikor zaradi njih brisanje mape ni mogoče. Možnost je uporabna pri metapodatkih. - + Could not open file Datoteke ni mogoče odpreti - + Cannot write changes to '%1'. Ni mogoče zapisati sprememb v '%1'. - + Add Ignore Pattern Dodaj vzorec za izpuščanje - + Add a new ignore pattern: Dodaj nov vzorec za izpuščanje: @@ -2023,27 +2023,27 @@ Uporaba ni priporočljiva. Datoteka je izbrisana s strežnika - + The file could not be downloaded completely. Datoteke ni mogoče prejeti v celoti. - + The downloaded file is empty despite the server announced it should have been %1. Prejeta datoteka je prazna, čeprav je na strežniku velikosti %1. - + File %1 cannot be saved because of a local file name clash! Datoteke %1 ni mogoče shraniti zaradi neskladja z imenom obstoječe datoteke! - + File has changed since discovery Datoteka je bila spremenjena po usklajevanju seznama datotek - + Error writing metadata to the database Napaka zapisovanja metapodatkov v podatkovno zbirko @@ -2554,6 +2554,11 @@ Uporaba ni priporočljiva. Allow editing Dovoli urejanje + + + Anyone with the link has access to the file/folder + Vsak, ki ima povezavo ima dostop do datoteke ali mape + P&assword protect diff --git a/translations/client_sr.ts b/translations/client_sr.ts index e769bac05f..6ab459366c 100644 --- a/translations/client_sr.ts +++ b/translations/client_sr.ts @@ -803,112 +803,112 @@ Continuing the sync as normal will cause all your files to be overwritten by an OCC::FolderMan - + Could not reset folder state Не могу да ресетујем стање фасцикле - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. Пронађен је стари журнал синхронизације „%1“ али се не може уклонити. Проверите да га нека апликација тренутно не користи. - + (backup) (резерва) - + (backup %1) (резерва %1) - + Undefined State. Неодређено стање. - + Waiting to start syncing. - + Preparing for sync. Припремам синхронизацију. - + Sync is running. Синхронизација у току. - + Last Sync was successful. Последња синхронизација је била успешна. - + Last Sync was successful, but with warnings on individual files. Последња синхронизација је била успешна али са упозорењима за поједине фајлове. - + Setup Error. Грешка подешавања. - + User Abort. Корисник прекинуо. - + Sync is paused. Синхронизација је паузирана. - + %1 (Sync is paused) %1 (синхронизација паузирана) - + No valid folder selected! - + The selected path is not a folder! - + You have no permission to write to the selected folder! Немате дозволе за упис у изабрану фасциклу! - + The local folder %1 contains a symbolic link. The link target contains an already synced folder Please pick another one! - + There is already a sync from the server to this local folder. Please pick another local folder! - + The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - + The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - + 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! @@ -1251,17 +1251,17 @@ Continuing the sync as normal will cause all your files to be overwritten by an - + About О програму - + Updates Ажурирања - + &Restart && Update &Поново покрени и ажурирај @@ -1339,22 +1339,22 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + Could not open file Не могу да отворим фајл - + Cannot write changes to '%1'. Не могу да упишем измене у „%1“ - + Add Ignore Pattern Додавање шаблона за игнорисање - + Add a new ignore pattern: Додајте нови шаблон за игнорисање: @@ -2012,27 +2012,27 @@ It is not advisable to use it. Фајл је обрисан са сервера - + The file could not be downloaded completely. Фајл није могао бити преузет у потпуности. - + The downloaded file is empty despite the server announced it should have been %1. - + File %1 cannot be saved because of a local file name clash! Фајл %1 се не може сачувати јер се судара са називом локалног фајла! - + File has changed since discovery Фајл је измењен у међувремену - + Error writing metadata to the database @@ -2543,6 +2543,11 @@ It is not advisable to use it. Allow editing уређивање + + + Anyone with the link has access to the file/folder + + P&assword protect diff --git a/translations/client_sv.ts b/translations/client_sv.ts index a8fdd3a0d4..804934345f 100644 --- a/translations/client_sv.ts +++ b/translations/client_sv.ts @@ -805,112 +805,112 @@ Om du fortsätter synkningen kommer alla dina filer återställas med en äldre OCC::FolderMan - + Could not reset folder state Kunde inte återställa mappens skick - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. En gammal synkroniseringsjournal '%1' hittades, men kunde inte raderas. Vänligen se till att inga program för tillfället använder den. - + (backup) (säkerhetskopia) - + (backup %1) (säkerhetkopia %1) - + Undefined State. Okänt tillstånd. - + Waiting to start syncing. Väntar på att starta synkronisering. - + Preparing for sync. Förbereder synkronisering - + Sync is running. Synkronisering pågår. - + Last Sync was successful. Senaste synkronisering lyckades. - + Last Sync was successful, but with warnings on individual files. Senaste synkning lyckades, men det finns varningar för vissa filer! - + Setup Error. Inställningsfel. - + User Abort. Användare Avbryt - + Sync is paused. Synkronisering är pausad. - + %1 (Sync is paused) %1 (Synk är stoppad) - + No valid folder selected! Ingen giltig mapp markerad! - + The selected path is not a folder! Den markerade sökvägen är inte en mapp! - + You have no permission to write to the selected folder! Du har inga skrivrättigheter till den valda mappen! - + The local folder %1 contains a symbolic link. The link target contains an already synced folder Please pick another one! - + 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! Den lokala mappen %1 innehåller redan en mapp som synkas. Var god välj en annan! - + The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! Den lokala mappen %1 finns redan inuti en mapp som synkas. Var god välj en annan! - + 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! Den lokala mappen %1 är en symbolisk länk. Länkmålet finns redan inuti en mapp som synkas. Var god välj en annan! @@ -1253,17 +1253,17 @@ Om du fortsätter synkningen kommer alla dina filer återställas med en äldre - + About Om - + Updates Uppdateringar - + &Restart && Update %Starta om && Uppdatera @@ -1343,22 +1343,22 @@ Items where deletion is allowed will be deleted if they prevent a directory from Objekt som tillåter radering kommer tas bort om de förhindrar en mapp att tas bort. Det är användbart för meta-data. - + Could not open file Kunde inte öppna fil - + Cannot write changes to '%1'. Kan inte skriva förändringar till '%1'. - + Add Ignore Pattern Lägg till synk-filter - + Add a new ignore pattern: Lägg till ett nytt synk-filter: @@ -2016,27 +2016,27 @@ Det är inte lämpligt använda den. Filen har tagits bort från servern - + The file could not be downloaded completely. Filen kunde inte laddas ner fullständigt. - + The downloaded file is empty despite the server announced it should have been %1. Den nedladdade filen är tom men servern sa att den skulle vara %1. - + File %1 cannot be saved because of a local file name clash! Fil %1 kan inte sparas eftersom namnet krockar med en lokal fil! - + File has changed since discovery Filen har ändrats sedan upptäckten - + Error writing metadata to the database Fel vid skrivning av metadata till databasen @@ -2547,6 +2547,11 @@ Det är inte lämpligt använda den. Allow editing Tillåt redigering + + + Anyone with the link has access to the file/folder + + P&assword protect diff --git a/translations/client_th.ts b/translations/client_th.ts index f208b64901..376816964a 100644 --- a/translations/client_th.ts +++ b/translations/client_th.ts @@ -813,112 +813,112 @@ Continuing the sync as normal will cause all your files to be overwritten by an OCC::FolderMan - + Could not reset folder state ไม่สามารถรีเซ็ตสถานะโฟลเดอร์ - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. บนบันทึกการประสานข้อมูลเก่า '%1' แต่ไม่สามารถลบออกได้ กรุณาตรวจสอบให้แน่ใจว่าไม่มีแอพฯ หรือการทำงานใดๆที่ใช้มันอยู่ - + (backup) (สำรองข้อมูล) - + (backup %1) (สำรองข้อมูล %1) - + Undefined State. สถานะที่ยังไม่ได้ถูกกำหนด - + Waiting to start syncing. กำลังรอเริ่มต้นการประสานข้อมูล - + Preparing for sync. กำลังเตรียมการประสานข้อมูล - + Sync is running. การประสานข้อมูลกำลังทำงาน - + Last Sync was successful. ประสานข้อมูลครั้งล่าสุดเสร็จเรียบร้อยแล้ว - + Last Sync was successful, but with warnings on individual files. การประสานข้อมูลสำเร็จ แต่มีคำเตือนในแต่ละไฟล์ - + Setup Error. เกิดข้อผิดพลาดในการติดตั้ง - + User Abort. ยกเลิกผู้ใช้ - + Sync is paused. การประสานข้อมูลถูกหยุดไว้ชั่วคราว - + %1 (Sync is paused) %1 (การประสานข้อมูลถูกหยุดชั่วคราว) - + No valid folder selected! เลือกโฟลเดอร์ไม่ถูกต้อง! - + The selected path is not a folder! เส้นทางที่เลือกไม่ใช่โฟลเดอร์! - + You have no permission to write to the selected folder! คุณมีสิทธิ์ที่จะเขียนโฟลเดอร์ที่เลือกนี้! - + 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 เป็นการเชื่อมโยงสัญลักษณ์ เป้าหมายของลิงค์มีเนื้อหาที่ถูกใช้ไปแล้วในโฟลเดอร์ที่ประสานข้อมูล กรุณาเลือกอีกอันหนึ่ง! @@ -1262,17 +1262,17 @@ Continuing the sync as normal will cause all your files to be overwritten by an - + About เกี่ยวกับเรา - + Updates อัพเดท - + &Restart && Update และเริ่มต้นใหม่ && อัพเดท @@ -1352,22 +1352,22 @@ Items where deletion is allowed will be deleted if they prevent a directory from รายการที่ลบจะถูกอนุญาตให้ลบถ้าพวกเขาป้องกันไม่ให้ไดเรกทอรีถูกลบออก นี้จะเป็นประโยชน์สำหรับข้อมูล meta - + Could not open file ไม่สามารถเปิดไฟล์ - + Cannot write changes to '%1'. ไม่สามารถเขียนเปลี่ยนเป็น '%1' - + Add Ignore Pattern เพิ่มการละเว้นรูปแบบ - + Add a new ignore pattern: เพิ่มการละเว้นรูปแบบใหม่: @@ -2024,27 +2024,27 @@ It is not advisable to use it. ไฟล์ถูกลบออกจากเซิร์ฟเวอร์ - + The file could not be downloaded completely. ดาวน์โหลดไฟล์ไม่สำเร็จ - + The downloaded file is empty despite the server announced it should have been %1. ไฟล์ที่ดาวน์โหลดว่างเปล่าแม้ว่าเซิร์ฟเวอร์ประกาศว่าควรจะเป็น %1 - + File %1 cannot be saved because of a local file name clash! ไฟล์ %1 ไม่สามารถบันทึกได้เพราะชื่อไฟล์ต้นทางเหมือนกัน! - + File has changed since discovery ไฟล์มีการเปลี่ยนแปลงตั้งแต่ถูกพบ - + Error writing metadata to the database ข้อผิดพลาดในการเขียนข้อมูลเมตาไปยังฐานข้อมูล @@ -2555,6 +2555,11 @@ It is not advisable to use it. Allow editing อนุญาตให้แก้ไข + + + Anyone with the link has access to the file/folder + ทุกคนที่มีลิงก์สามารถเข้าถึงไฟล์หรือโฟลเดอร์ได้ + P&assword protect diff --git a/translations/client_tr.ts b/translations/client_tr.ts index 0ff1ef5e7c..99944a8804 100644 --- a/translations/client_tr.ts +++ b/translations/client_tr.ts @@ -248,17 +248,17 @@ There are folders that were not synchronized because they are too big: - + Çok büyük oldukları için eşitlenmeyen klasörler var: There are folders that were not synchronized because they are external storages: - + Harici depolama diskinde oldukları için eşitlenmeyen klasörler var: There are folders that were not synchronized because they are too big or external storages: - + Çok büyük oldukları için ya da harici depolama alanında oldukları için eşitlenmeyen klasörler var: @@ -546,7 +546,7 @@ Quit ownCloud - + ownCloud'dan çık @@ -803,112 +803,112 @@ Continuing the sync as normal will cause all your files to be overwritten by an OCC::FolderMan - + Could not reset folder state Klasör durumu sıfırılanamadı - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. Eski eşitleme günlüğü '%1' bulundu ancak kaldırılamadı. Başka bir uygulama tarafından kullanılmadığından emin olun. - + (backup) (yedek) - + (backup %1) (yedek %1) - + Undefined State. Tanımlanmamış Durum. - + Waiting to start syncing. Eşitlemenin başlanması bekleniyor. - + Preparing for sync. Eşitleme için hazırlanıyor. - + Sync is running. Eşitleme çalışıyor. - + Last Sync was successful. Son Eşitleme başarılı oldu. - + Last Sync was successful, but with warnings on individual files. Son eşitleme başarılıydı, ancak tekil dosyalarda uyarılar vardı. - + Setup Error. Kurulum Hatası. - + User Abort. Kullanıcı İptal Etti. - + Sync is paused. Eşitleme duraklatıldı. - + %1 (Sync is paused) %1 (Eşitleme duraklatıldı) - + No valid folder selected! Geçerli klasör seçilmedi! - + The selected path is not a folder! Seçilen yol bir klasör değil! - + You have no permission to write to the selected folder! Seçilen klasöre yazma izniniz yok! - + The local folder %1 contains a symbolic link. The link target contains an already synced folder Please pick another one! - + 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 yerel klasörü zaten bir eşitleme klasörü içermektedir. Lütfen farklı bir seçim yapın! - + The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! %1 yerel klasörü zaten bir eşitleme klasörü içindedir. Lütfen farklı bir seçim yapın! - + 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 yerel klasörü sembolik bağlantıdır. Bu bağlantının işaretlediği klasör zaten yapılandırılmış bir klasör içindedir. Lütfen farklı bir seçim yapın! @@ -1211,7 +1211,7 @@ Continuing the sync as normal will cause all your files to be overwritten by an Ask for confirmation before synchronizing folders larger than - + senkronizasyon dosyaları @@ -1251,17 +1251,17 @@ Continuing the sync as normal will cause all your files to be overwritten by an - + About Hakkında - + Updates Güncellemeler - + &Restart && Update &Yeniden Başlat ve Güncelle @@ -1341,22 +1341,22 @@ Items where deletion is allowed will be deleted if they prevent a directory from Bir dizinin silinmesine engel oluyorsa silmeye izin verilen yerlerdeki ögeler silinecektir. Bu ham veriler için kullanışlıdır. - + Could not open file Dosya açılamadı - + Cannot write changes to '%1'. Değişiklikler '%1' üzerine yazılamıyor. - + Add Ignore Pattern Yoksayma Deseni Ekle - + Add a new ignore pattern: Yeni bir yoksayma deseni ekle: @@ -2013,27 +2013,27 @@ Kullanmanız önerilmez. Dosya sunucudan silindi - + The file could not be downloaded completely. Dosya tamamıyla indirilemedi. - + The downloaded file is empty despite the server announced it should have been %1. Sunucu boyutunu %1 olarak duyurmasına rağmen indirilen dosya boş. - + File %1 cannot be saved because of a local file name clash! Yerel bir dosya ismi ile çakıştığından, %1 dosyası kaydedilemedi! - + File has changed since discovery Dosya, bulunduğundan itibaren değişmiş - + Error writing metadata to the database Veritabanına üstveri yazma hatası @@ -2544,6 +2544,11 @@ Kullanmanız önerilmez. Allow editing Düzenlemeye izin ver + + + Anyone with the link has access to the file/folder + Dosya/klasör linkine sahip Herkes erişebilir + P&assword protect @@ -2578,7 +2583,7 @@ Kullanmanız önerilmez. There was an error when launching the email client to create a new message. Maybe no default email client is configured? - + Yeni mesaj oluşturmak için eposta istemcisini çalıştırıken bir hata oluştu. Belki varsayılan eposta istemcisi ayarlanmamıştır? @@ -3079,7 +3084,7 @@ Kullanmanız önerilmez. The file name is a reserved name on this file system. - + Dosya adı bu dosya sisteminde ayrılmış bir addır. @@ -3416,7 +3421,7 @@ Kullanmanız önerilmez. Syncing %1 of %2 - + %2 nin %1 i eşitleniyor diff --git a/translations/client_uk.ts b/translations/client_uk.ts index 5d45d854fb..5f4a399af1 100644 --- a/translations/client_uk.ts +++ b/translations/client_uk.ts @@ -803,112 +803,112 @@ Continuing the sync as normal will cause all your files to be overwritten by an OCC::FolderMan - + Could not reset folder state Не вдалося скинути стан теки - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. Знайдено старий журнал синхронізації '%1', його неможливо видалити. Будь ласка, впевніться що він не відкритий в іншій програмі. - + (backup) (Резервна копія) - + (backup %1) (Резервна копія %1) - + Undefined State. Невизначений стан. - + Waiting to start syncing. Очікування початку синхронізації. - + Preparing for sync. Підготовка до синхронізації - + Sync is running. Синхронізація запущена. - + Last Sync was successful. Остання синхронізація була успішною. - + Last Sync was successful, but with warnings on individual files. Остання синхронізація пройшла вдало, але були зауваження про деякі файли. - + Setup Error. Помилка встановлення. - + User Abort. Скасовано користувачем. - + Sync is paused. Синхронізація призупинена. - + %1 (Sync is paused) %1 (Синхронізація призупинена) - + No valid folder selected! - + The selected path is not a folder! - + You have no permission to write to the selected folder! У вас немає прав на запис в цю теку! - + The local folder %1 contains a symbolic link. The link target contains an already synced folder Please pick another one! - + There is already a sync from the server to this local folder. Please pick another local folder! - + The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - + The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - + 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! @@ -1251,17 +1251,17 @@ Continuing the sync as normal will cause all your files to be overwritten by an - + About Про - + Updates Оновлення - + &Restart && Update &Перезавантаження && Оновлення @@ -1339,22 +1339,22 @@ Items where deletion is allowed will be deleted if they prevent a directory from - + Could not open file Не вдалося відкрити файл - + Cannot write changes to '%1'. Неможливо запиасати зміни до '%1'. - + Add Ignore Pattern Додати шаблон ігнорування - + Add a new ignore pattern: Додати новий шаблон ігнорування: @@ -2011,27 +2011,27 @@ It is not advisable to use it. Файл видалено з сервера - + The file could not be downloaded completely. Файл не може бути завантажений повністю. - + The downloaded file is empty despite the server announced it should have been %1. - + File %1 cannot be saved because of a local file name clash! Файл %1 не збережено через локальний конфлікт назви файлу! - + File has changed since discovery Файл змінився з моменту знаходження - + Error writing metadata to the database @@ -2542,6 +2542,11 @@ It is not advisable to use it. Allow editing Дозволити редагування + + + Anyone with the link has access to the file/folder + Будь хто з цім посиланням має доступ до файлу/теки + P&assword protect diff --git a/translations/client_zh_CN.ts b/translations/client_zh_CN.ts index 95abd501e2..57fbe2b663 100644 --- a/translations/client_zh_CN.ts +++ b/translations/client_zh_CN.ts @@ -163,12 +163,12 @@ Force sync now - + 强制同步 Restart sync - + 重新开始同步 @@ -536,7 +536,7 @@ Error accessing the configuration file - + 访问配置文件时发生错误 @@ -546,7 +546,7 @@ Quit ownCloud - + 退出 ownCloud @@ -803,112 +803,112 @@ Continuing the sync as normal will cause all your files to be overwritten by an OCC::FolderMan - + Could not reset folder state 不能重置文件夹状态 - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. 一个旧的同步日志 '%1' 被找到,但是不能被移除。请确定没有应用程序正在使用它。 - + (backup) (备份) - + (backup %1) (备份 %1) - + Undefined State. 未知状态。 - + Waiting to start syncing. 等待启动同步。 - + Preparing for sync. 准备同步。 - + Sync is running. 同步正在运行。 - + Last Sync was successful. 最后一次同步成功。 - + Last Sync was successful, but with warnings on individual files. 上次同步已成功,不过一些文件出现了警告。 - + Setup Error. 安装失败 - + User Abort. 用户撤销。 - + Sync is paused. 同步已暂停。 - + %1 (Sync is paused) %1 (同步已暂停) - + No valid folder selected! 没有选择有效的文件夹! - + The selected path is not a folder! 选择的路径不是一个文件夹! - + You have no permission to write to the selected folder! 你没有写入所选文件夹的权限! - + The local folder %1 contains a symbolic link. The link target contains an already synced folder Please pick another one! - + 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 是一个符号连接,连接指向的是正在使用的同步文件夹,请选择另一个! @@ -1251,17 +1251,17 @@ Continuing the sync as normal will cause all your files to be overwritten by an - + About 关于 - + Updates 更新 - + &Restart && Update 重启并更新 (&R) @@ -1341,22 +1341,22 @@ Items where deletion is allowed will be deleted if they prevent a directory from 如果选中的项目正在阻止文件夹的删除,它们也会被删除。这对于元数据很有用。 - + Could not open file 不能打开文件 - + Cannot write changes to '%1'. 无法向 %1 中写入修改。 - + Add Ignore Pattern 增加忽略模式 - + Add a new ignore pattern: 增加新的忽略模式: @@ -2013,27 +2013,27 @@ It is not advisable to use it. 已从服务器删除文件 - + The file could not be downloaded completely. 文件无法完整下载。 - + The downloaded file is empty despite the server announced it should have been %1. 虽然服务器宣称已完成 %1,但实际下载文件为空。 - + File %1 cannot be saved because of a local file name clash! 由于本地文件名冲突,文件 %1 无法保存。 - + File has changed since discovery 自从发现文件以来,它已经被改变了 - + Error writing metadata to the database 向数据库写入元数据错误 @@ -2544,6 +2544,11 @@ It is not advisable to use it. Allow editing 允许编辑 + + + Anyone with the link has access to the file/folder + + P&assword protect @@ -2573,7 +2578,7 @@ It is not advisable to use it. Could not open email client - + 无法打开邮件客户端 @@ -3074,7 +3079,7 @@ It is not advisable to use it. File names containing the character '%1' are not supported on this file system. - + 此文件系统不支持包含字符 '%1' 的文件名。 @@ -3084,7 +3089,7 @@ It is not advisable to use it. Filename contains trailing spaces. - + 文件名尾部含有空格 diff --git a/translations/client_zh_TW.ts b/translations/client_zh_TW.ts index 5d4863008a..460511e108 100644 --- a/translations/client_zh_TW.ts +++ b/translations/client_zh_TW.ts @@ -803,112 +803,112 @@ Continuing the sync as normal will cause all your files to be overwritten by an OCC::FolderMan - + Could not reset folder state 無法重置資料夾狀態 - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. 發現較舊的同步處理日誌'%1',但無法移除。請確認沒有應用程式正在使用它。 - + (backup) (備份) - + (backup %1) (備份 %1) - + Undefined State. 未知狀態 - + Waiting to start syncing. 正在等待同步開始 - + Preparing for sync. 正在準備同步。 - + Sync is running. 同步執行中 - + Last Sync was successful. 最後一次同步成功 - + Last Sync was successful, but with warnings on individual files. 最新一次的同步已經成功,但是有部份檔案有問題 - + Setup Error. 安裝失敗 - + User Abort. 使用者中斷。 - + Sync is paused. 同步已暫停 - + %1 (Sync is paused) %1 (同步暫停) - + No valid folder selected! 沒有選擇有效的資料夾 - + The selected path is not a folder! 所選的路徑並非資料夾! - + You have no permission to write to the selected folder! 您沒有權限來寫入被選取的資料夾! - + The local folder %1 contains a symbolic link. The link target contains an already synced folder Please pick another one! - + 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 是一個捷徑,此捷徑的目標是被包含在一個已經被資料夾同步功能使用的資料夾,請選擇其他資料夾! @@ -1251,17 +1251,17 @@ Continuing the sync as normal will cause all your files to be overwritten by an - + About 關於 - + Updates 更新 - + &Restart && Update 重新啟動並更新 (&R) @@ -1341,22 +1341,22 @@ Items where deletion is allowed will be deleted if they prevent a directory from 當資料夾被移除時,會根據清單裡的允許刪除選項來避免那些檔案會被移除。而這對元資料是有用的。 - + Could not open file 無法開啟檔案 - + Cannot write changes to '%1'. %1 無法寫入變更。 - + Add Ignore Pattern 增加忽略格式 - + Add a new ignore pattern: 增加一個新的忽略格式: @@ -2014,27 +2014,27 @@ It is not advisable to use it. 檔案已從伺服器被刪除 - + The file could not be downloaded completely. 檔案下載無法完成。 - + The downloaded file is empty despite the server announced it should have been %1. - + File %1 cannot be saved because of a local file name clash! 檔案 %1 無法存檔,因為本地端的檔案名稱已毀損! - + File has changed since discovery 尋找的過程中檔案已經被更改 - + Error writing metadata to the database 寫入後設資料(metadata) 時發生錯誤 @@ -2545,6 +2545,11 @@ It is not advisable to use it. Allow editing 允許編輯 + + + Anyone with the link has access to the file/folder + + P&assword protect