diff --git a/.github/issue_template.md b/.github/issue_template.md index 834d89e543..7681eab04a 100644 --- a/.github/issue_template.md +++ b/.github/issue_template.md @@ -2,7 +2,7 @@ Please try to only report a bug if it happens with the latest version The latest version can be seen by checking the ChangeLog: https://owncloud.org/changelog/desktop/ -For support try: https://central.owncloud.org/c/help/desktop-file-sync +For support try: https://central.owncloud.org/c/desktop-client ---> diff --git a/.gitmodules b/.gitmodules index 9a68e4ebe3..ab18390aa5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "doc/ocdoc"] - path = doc/ocdoc - url = https://github.com/nextcloud/documentation [submodule "src/3rdparty/qtmacgoodies"] path = src/3rdparty/qtmacgoodies url = https://github.com/guruz/qtmacgoodies.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 116dca4cc7..38193d0068 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -191,6 +191,24 @@ add_definitions( -D__USE_MINGW_ANSI_STDIO=1 ) add_definitions( -DNOMINMAX ) endif( WIN32 ) +include(QtVersionAbstraction) +setup_qt() +if (${Qt5Core_VERSION_MAJOR} EQUAL "5") + if (${Qt5Core_VERSION_MINOR} EQUAL "6" OR ${Qt5Core_VERSION_MINOR} GREATER 6) + else() + message(STATUS "If possible compile me with Qt 5.6 or higher.") + endif() + if (${Qt5Core_VERSION_MINOR} EQUAL "9" OR ${Qt5Core_VERSION_MINOR} GREATER 9) + else() + message(STATUS "For HTTP2 use Qt 5.9.2 or higher.") + endif() +endif() +message("Qt ${Qt5Core_VERSION} at ${Qt5Core_INCLUDE_DIRS}") + +if (APPLE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") +endif() + # Handle Translations, pick all client_* files from trans directory. file( GLOB TRANS_FILES ${CMAKE_SOURCE_DIR}/translations/client_*.ts) set(TRANSLATIONS ${TRANS_FILES}) diff --git a/ChangeLog b/ChangeLog index 0e475fa594..8ba22d5df2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -4,32 +4,62 @@ 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: Add option to copy/email direct links (#5627, #5023) +* Sharing: Add option "show file listing" (#5837) * Sharing: Show warning that links are public * Sharing: Many UI improvements +* Sharing: Make "can edit" partially checked sometimes #5642 * 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) +* Selective Sync: Remove local files of unselected folder despite other modified files (#5783) +* Exclude list: remove .htaccess form list of excluded files +* Sync Issues: More functional error view including filters and conflicts #5516 * Discovery: Increase the MAX_DEPTH and show deep folders as ignored * Downloads: Remove empty temporary if disk space full (#5746) * Downloads: Re-trigger folder discovery on 404 * When creating explorer favorite use more specific windows functions (#5690) +* AccountSettings: Triggering log in re-ask about previously rejected certificates #5819 * Added owncloudcmd bandwidth limit parameter (#5707) * AccountSettings: Sync with clean discovery on Ctrl-F6 (#5666) -* Dynamic size of chunks in chunked uploads for improved big file upload performance -* ShareDialog: Make "can edit" partially checked sometimes #5642 -* Require Qt5 -* Switch 3rdparty/json usage to Qt5's QJson (#5710) +* Sync: Dynamic sizing of chunks in chunked uploads for improved big file upload performance +* Sync: Introduce overall errors that are not tied to a file #5746 +* Sync: Better messaging for 507 Insufficient Storage #5537 +* Sync: Create conflicts by comparing the hash of files with identical mtime/size #5589 +* Sync: Blacklist: Don't let errors become warnings #5516 +* macOS: Finder sidebar icon #296 * Reduce memory usage +* Fix at least one memory leak * Documentation improvements * Logging improvements (with Qt logging categories), new --logdebug parameter * Harmonize source code style with clang-format * Crash fixes +* Test improvements +* Windows Overlays: Potential hang fixes +* Small UI layout fixes +* Maintenance Mode: Detect maintenance mode (#4485) +* Maintenance Mode: Add a 1 to 5 min reconnection delay #5872 +* HTTP: Send a unique X-Request-ID with each request #5853 +* HTTP: Support HTTP2 when built and running with Qt 5.9.x (Official packages still on Qt 5.6.x) +* owncloudcmd: Don't start if connection or auth fails #5692 +* Overlays: Consider also the "shared by me" as shared (#4788) +* Switch 3rdparty/json usage to Qt5's QJson (#5710) +* OpenSSL: Don't require directly, only via Qt +* Remove iconv dependency, use Qt for file system locale encoding/decoding (emoji filename support on macOS) + +version 2.3.3 (2017-07-XX) +* Chunking NG: Don't use old chunking on new DAV endpoint (#5855) +* Selective Sync: Skip excluded folders when reading DB, don't let them show errors (#5772) +* Settings: Make window bigger so Qt version is always visible (#5760) +* Share links: Show warning that public link shares are public (#5786) +* Downloads: Re-trigger folder discovery on HTTP 404 (#5799) +* Overlay Icons: Fix potential hangs on Windows +* SyncJournalDB: Don't use ._ as filename pattern if that does not work because of SMB storage settings (#5844) +* SyncJournalDB: Log reason for sqlite3 opening errors +* Switch Linux build also to Qt 5.6.2 (#5470) +* Stopped maintaining Qt 4 buildability version 2.3.2 (2017-05-08) * Fix more crashes (thanks to everyone submitting to our crash reporter!) diff --git a/admin/osx/macosx.pkgproj b/admin/osx/macosx.pkgproj index 9b9b891bc8..927ca92dfb 100644 --- a/admin/osx/macosx.pkgproj +++ b/admin/osx/macosx.pkgproj @@ -506,536 +506,6 @@ UUID 7D7219B7-1897-48C3-8533-842BDEC46F71 - - PACKAGE_FILES - - DEFAULT_INSTALL_LOCATION - / - HIERARCHY - - CHILDREN - - - CHILDREN - - - CHILDREN - - GID - 80 - PATH - Utilities - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - GID - 80 - PATH - Applications - PATH_TYPE - 0 - PERMISSIONS - 509 - TYPE - 1 - UID - 0 - - - CHILDREN - - - CHILDREN - - GID - 80 - PATH - Application Support - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Documentation - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Filesystems - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Frameworks - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Input Methods - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Internet Plug-Ins - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - LaunchAgents - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - LaunchDaemons - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - PreferencePanes - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Preferences - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 80 - PATH - Printers - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - PrivilegedHelperTools - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - QuickLook - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - QuickTime - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Screen Savers - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - - CHILDREN - - - CHILDREN - - GID - 0 - PATH - Library/ScriptingAdditions/SyncStateFinder.osax/Contents - PATH_TYPE - 3 - PERMISSIONS - 493 - TYPE - 3 - UID - 0 - - - GID - 0 - PATH - SyncStateFinder.osax - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 2 - UID - 0 - - - GID - 0 - PATH - ScriptingAdditions - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 2 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Scripts - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Services - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Widgets - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - GID - 0 - PATH - Library - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - - CHILDREN - - - CHILDREN - - GID - 0 - PATH - Extensions - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - GID - 0 - PATH - Library - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - GID - 0 - PATH - System - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - - CHILDREN - - GID - 0 - PATH - Shared - PATH_TYPE - 0 - PERMISSIONS - 1023 - TYPE - 1 - UID - 0 - - - GID - 80 - PATH - Users - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - GID - 0 - PATH - / - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - PAYLOAD_TYPE - 0 - VERSION - 2 - - PACKAGE_SCRIPTS - - POSTINSTALL_PATH - - PREINSTALL_PATH - - RESOURCES - - - PACKAGE_SETTINGS - - AUTHENTICATION - 1 - CONCLUSION_ACTION - 0 - IDENTIFIER - com.ownCloud.finderPlugin - LOCATION - 0 - NAME - Legacy Finder Plugin (OS X 10.9 or older) - OVERWRITE_PERMISSIONS - - VERSION - @MIRALL_VERSION_FULL@ - - TYPE - 0 - UUID - 39F61FCD-6EAA-4F3A-81C6-25E3F667DFB5 - PROJECT @@ -1103,49 +573,6 @@ UUID 9647ADC0-BD53-4D7D-A561-73D383AACDE1 - - DESCRIPTION - - OPTIONS - - HIDDEN - - STATE - 1 - - PACKAGE_UUID - 39F61FCD-6EAA-4F3A-81C6-25E3F667DFB5 - REQUIREMENTS - - - BEHAVIOR - 1 - DICTIONARY - - IC_REQUIREMENT_JAVASCRIPT_FUNCTION - olderOsx - IC_REQUIREMENT_JAVASCRIPT_PARAMETERS - - - IDENTIFIER - fr.whitebox.Packages.requirement.javascript - MESSAGE - - NAME - JavaScript - STATE - - - - TITLE - - TOOLTIP - - TYPE - 0 - UUID - 1D2C47E0-5FD3-4623-B934-1347C66782D0 - REMOVED @@ -1450,18 +877,6 @@ @CMAKE_INSTALL_DIR@ - SHARED_GLOBAL_DATA - - IC_REQUIREMENT_JAVASCRIPT_SHARED_SOURCE_CODE - - function olderOsx() { - if(system.compareVersions(system.version.ProductVersion, '10.10') == -1) { - return true; - } - return false; - } - - TYPE 0 VERSION diff --git a/admin/osx/pre_install.sh.cmake b/admin/osx/pre_install.sh.cmake index 094a283dee..cd909e29ad 100644 --- a/admin/osx/pre_install.sh.cmake +++ b/admin/osx/pre_install.sh.cmake @@ -3,7 +3,4 @@ # kill the old version. see issue #2044 killall @APPLICATION_EXECUTABLE@ -# Unload the Finder plugin. see issue #2105 -killall Finder - exit 0 diff --git a/admin/win/docker/Dockerfile b/admin/win/docker/Dockerfile index 8e40a4991e..1629b97602 100644 --- a/admin/win/docker/Dockerfile +++ b/admin/win/docker/Dockerfile @@ -8,7 +8,7 @@ ENV HOME /root ENV REFRESHED_AT 20160421 RUN zypper --non-interactive --gpg-auto-import-keys refresh -RUN zypper --non-interactive --gpg-auto-import-keys ar http://download.opensuse.org/repositories/windows:/mingw/openSUSE_42.1/windows:mingw.repo +RUN zypper --non-interactive --gpg-auto-import-keys ar http://download.opensuse.org/repositories/windows:/mingw/openSUSE_Leap_42.1/windows:mingw.repo RUN zypper --non-interactive --gpg-auto-import-keys ar http://download.opensuse.org/repositories/isv:ownCloud:toolchains:mingw:win32:2.3/openSUSE_Leap_42.1/isv:ownCloud:toolchains:mingw:win32:2.3.repo RUN zypper --non-interactive --gpg-auto-import-keys install cmake make mingw32-cross-binutils mingw32-cross-cpp mingw32-cross-gcc \ mingw32-cross-gcc-c++ mingw32-cross-pkg-config mingw32-filesystem \ diff --git a/admin/win/nsi/l10n/Portuguese.nsh b/admin/win/nsi/l10n/Portuguese.nsh index 7d32b7c14e..6b2abd2d4f 100644 --- a/admin/win/nsi/l10n/Portuguese.nsh +++ b/admin/win/nsi/l10n/Portuguese.nsh @@ -18,7 +18,7 @@ StrCpy $SEC_APPLICATION_DETAILS "A instalar o essencial de ${APPLICATION_NAME}." StrCpy $OPTION_SECTION_SC_SHELL_EXT_SECTION "Integração para Windows Explorer" StrCpy $OPTION_SECTION_SC_SHELL_EXT_DetailPrint "A instalar integração para Windows Explorer" StrCpy $OPTION_SECTION_SC_START_MENU_SECTION "Atalho do progama no Menu Iniciar" -StrCpy $OPTION_SECTION_SC_START_MENU_DetailPrint "A adicionar o atalho de ${APPLICATION_NAME} ao Menu Inicial." +StrCpy $OPTION_SECTION_SC_START_MENU_DetailPrint "A adicionar o atalho de ${APPLICATION_NAME} no Menu Iniciar." StrCpy $OPTION_SECTION_SC_DESKTOP_SECTION "Atalho da área de trabalho" StrCpy $OPTION_SECTION_SC_DESKTOP_DetailPrint "A criar atalhos na área de trabalho" StrCpy $OPTION_SECTION_SC_QUICK_LAUNCH_SECTION "Atalho de início rápido" diff --git a/binary b/binary index 1818b48380..3425fab2c6 160000 --- a/binary +++ b/binary @@ -1 +1 @@ -Subproject commit 1818b48380f4fc50d482b980e5ec0d347f896a70 +Subproject commit 3425fab2c66118ffae3e3b16751e636ca71ee450 diff --git a/cmake/modules/AddAppIconMacro.cmake b/cmake/modules/AddAppIconMacro.cmake index 3106b1bfae..dc833bd9d9 100644 --- a/cmake/modules/AddAppIconMacro.cmake +++ b/cmake/modules/AddAppIconMacro.cmake @@ -70,45 +70,64 @@ macro (KDE4_ADD_APP_ICON appsources pattern) endif(PNG2ICO_EXECUTABLE AND WINDRES_EXECUTABLE) endif(WIN32) if (APPLE) - # first convert image to a tiff using the Mac OS X "sips" utility, - # then use tiff2icns to convert to an icon - find_program(SIPS_EXECUTABLE NAMES sips) - find_program(TIFF2ICNS_EXECUTABLE NAMES tiff2icns) - if (SIPS_EXECUTABLE AND TIFF2ICNS_EXECUTABLE) - file(GLOB_RECURSE files "${pattern}") - # we can only test for the 128-icon like that - we don't use patterns anymore - foreach (it ${files}) - if (it MATCHES ".*128.*" ) - set (_icon ${it}) - endif (it MATCHES ".*128.*") - endforeach (it) + file(GLOB_RECURSE files "${pattern}") + file(MAKE_DIRECTORY ${appsources}.iconset) - if (_icon) - - # first, get the basename of our app icon - add_custom_command(OUTPUT ${_outfilename}.icns ${outfilename}.tiff - COMMAND ${SIPS_EXECUTABLE} -s format tiff ${_icon} --out ${outfilename}.tiff - COMMAND ${TIFF2ICNS_EXECUTABLE} ${outfilename}.tiff ${_outfilename}.icns - DEPENDS ${_icon} - ) + # List from: + # https://developer.apple.com/library/content/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Optimizing/Optimizing.html#//apple_ref/doc/uid/TP40012302-CH7-SW4 + foreach (it ${files}) + if (it MATCHES ".*icon-16.*") + configure_file(${it} ${appsources}.iconset/icon_16x16.png COPYONLY) + elseif (it MATCHES ".*icon-32.*") + configure_file(${it} ${appsources}.iconset/icon_16x16@2x.png COPYONLY) + configure_file(${it} ${appsources}.iconset/icon_32x32.png COPYONLY) + elseif (it MATCHES ".*icon-64.*") + configure_file(${it} ${appsources}.iconset/icon_32x32@2x.png COPYONLY) + elseif (it MATCHES ".*icon-128.*") + configure_file(${it} ${appsources}.iconset/icon_128x128.png COPYONLY) + elseif (it MATCHES ".*icon-256.*") + configure_file(${it} ${appsources}.iconset/icon_128x128@2x.png COPYONLY) + configure_file(${it} ${appsources}.iconset/icon_256x256.png COPYONLY) + elseif (it MATCHES ".*icon-512.*") + configure_file(${it} ${appsources}.iconset/icon_256x256@2x.png COPYONLY) + configure_file(${it} ${appsources}.iconset/icon_512x512.png COPYONLY) + elseif (it MATCHES ".*icon-1024.*") + configure_file(${it} ${appsources}.iconset/icon_512x512@2x.png COPYONLY) + endif() + endforeach (it) - # This will register the icon into the bundle - set(MACOSX_BUNDLE_ICON_FILE ${appsources}.icns) + # Copy the sidebar icons in the main app bundle for the FinderSync extension to pick. + # https://developer.apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/Finder.html#//apple_ref/doc/uid/TP40014214-CH15-SW15 + foreach (it ${files}) + if (it MATCHES ".*sidebar-16.*") + configure_file(${it} ${appsources}.iconset/sidebar_16x16.png COPYONLY) + elseif (it MATCHES ".*sidebar-18.*") + configure_file(${it} ${appsources}.iconset/sidebar_18x18.png COPYONLY) + elseif (it MATCHES ".*sidebar-32.*") + configure_file(${it} ${appsources}.iconset/sidebar_16x16@2x.png COPYONLY) + configure_file(${it} ${appsources}.iconset/sidebar_32x32.png COPYONLY) + elseif (it MATCHES ".*sidebar-36.*") + configure_file(${it} ${appsources}.iconset/sidebar_18x18@2x.png COPYONLY) + elseif (it MATCHES ".*sidebar-64.*") + configure_file(${it} ${appsources}.iconset/sidebar_32x32@2x.png COPYONLY) + endif() + endforeach (it) - # Append the icns file to the sources list so it will be a dependency to the - # main target - list(APPEND ${appsources} ${_outfilename}.icns) + add_custom_command(OUTPUT ${_outfilename}.icns + COMMAND echo === Building bundle icns with iconset: + COMMAND ls -1 ${appsources}.iconset + COMMAND iconutil -c icns -o ${_outfilename}.icns ${appsources}.iconset + DEPENDS ${files} + ) - # Install the icon into the Resources dir in the bundle - set_source_files_properties(${_outfilename}.icns PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + # This will register the icon into the bundle + set(MACOSX_BUNDLE_ICON_FILE ${appsources}.icns) - else(_icon) - # TODO - try to scale a non-128 icon...? Try to convert an SVG on the fly? - message(STATUS "Unable to find an 128x128 icon that matches pattern ${pattern} for variable ${appsources} - application will not have an application icon!") - endif(_icon) + # Append the icns file to the sources list so it will be a dependency to the + # main target + list(APPEND ${appsources} ${_outfilename}.icns) - else(SIPS_EXECUTABLE AND TIFF2ICNS_EXECUTABLE) - message(STATUS "Unable to find the sips and tiff2icns utilities - application will not have an application icon!") - endif(SIPS_EXECUTABLE AND TIFF2ICNS_EXECUTABLE) + # Install the icon into the Resources dir in the bundle + set_source_files_properties(${_outfilename}.icns PROPERTIES MACOSX_PACKAGE_LOCATION Resources) endif(APPLE) endmacro (KDE4_ADD_APP_ICON) diff --git a/cmake/modules/FindIconv.cmake b/cmake/modules/FindIconv.cmake deleted file mode 100644 index 46d99f0581..0000000000 --- a/cmake/modules/FindIconv.cmake +++ /dev/null @@ -1,82 +0,0 @@ -# - Try to find Iconv -# Once done this will define -# -# ICONV_FOUND - system has Iconv -# ICONV_INCLUDE_DIRS - the Iconv include directory -# ICONV_LIBRARIES - Link these to use Iconv -# ICONV_DEFINITIONS - Compiler switches required for using Iconv -# -# Copyright (c) 2013 Andreas Schneider -# -# Redistribution and use is allowed according to the terms of the New -# BSD license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. -# - -include(CheckIncludeFile) -include(CheckFunctionExists) -include(CheckLibraryExists) -include(CheckPrototypeDefinition) - -find_path(ICONV_INCLUDE_DIR - NAMES - iconv.h sys/iconv.h -) - -set(CMAKE_REQUIRED_INCLUDES ${ICONV_INCLUDE_DIR}) -check_include_file(iconv.h HAVE_ICONV_H) -check_include_file(sys/iconv.h HAVE_SYS_ICONV_H) -set(CMAKE_REQUIRED_INCLUDES) - -find_library(ICONV_LIBRARY - NAMES - iconv - libiconv - PATHS -) - -if (ICONV_LIBRARY) - get_filename_component(_ICONV_NAME ${ICONV_LIBRARY} NAME) - get_filename_component(_ICONV_PATH ${ICONV_LIBRARY} PATH) - check_library_exists(${_ICONV_NAME} iconv ${_ICONV_PATH} HAVE_ICONV) -else() - check_function_exists(iconv HAVE_ICONV) -endif() - -if (HAVE_ICONV_H OR HAVE_SYS_ICONV_H) - if (HAVE_ICONV_H) - set(_ICONV_PROTO_INCLUDE "iconv.h") - endif (HAVE_ICONV_H) - if (HAVE_SYS_ICONV_H) - set(_ICONV_PROTO_INCLUDE "sys/iconv.h") - endif (HAVE_SYS_ICONV_H) - - set(CMAKE_REQUIRED_INCLUDES ${ICONV_INCLUDE_DIR}) - check_prototype_definition(iconv - "size_t iconv(iconv_t cd, const char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft)" - "-1" - ${_ICONV_PROTO_INCLUDE} - HAVE_ICONV_CONST) - set(CMAKE_REQUIRED_INCLUDES) -endif (HAVE_ICONV_H OR HAVE_SYS_ICONV_H) - -set(ICONV_INCLUDE_DIRS - ${ICONV_INCLUDE_DIR} -) - -if (ICONV_LIBRARY) - set(ICONV_LIBRARIES - ${ICONV_LIBRARIES} - ${ICONV_LIBRARY} - ) -endif (ICONV_LIBRARY) - -include(FindPackageHandleStandardArgs) -if (ICONV_LIBRARIES) - find_package_handle_standard_args(Iconv DEFAULT_MSG ICONV_LIBRARIES ICONV_INCLUDE_DIRS) -else() - find_package_handle_standard_args(Iconv DEFAULT_MSG ICONV_INCLUDE_DIRS) -endif() - -# show the ICONV_INCLUDE_DIRS and ICONV_LIBRARIES variables only in the advanced view -mark_as_advanced(ICONV_INCLUDE_DIRS ICONV_LIBRARIES) diff --git a/csync/CMakeLists.txt b/csync/CMakeLists.txt index 2d4b94f2aa..d5be43a29a 100644 --- a/csync/CMakeLists.txt +++ b/csync/CMakeLists.txt @@ -18,10 +18,6 @@ include(DefineInstallationPaths) include(MacroAddPlugin) include(MacroCopyFile) -if (NOT WIN32) - find_package(Iconv) -endif (NOT WIN32) - find_package(SQLite3 3.8.0 REQUIRED) include(ConfigureChecks.cmake) diff --git a/csync/DefineOptions.cmake b/csync/DefineOptions.cmake index 9ba14800d4..485e183221 100644 --- a/csync/DefineOptions.cmake +++ b/csync/DefineOptions.cmake @@ -1,5 +1,2 @@ -if ( NOT WIN32 ) - option(WITH_ICONV "Build csync with iconv support" ON) -endif() option(UNIT_TESTING "Build with unit tests" OFF) option(MEM_NULL_TESTS "Enable NULL memory testing" OFF) diff --git a/csync/config_csync.h.cmake b/csync/config_csync.h.cmake index 155329bbd7..f5c9f09b2f 100644 --- a/csync/config_csync.h.cmake +++ b/csync/config_csync.h.cmake @@ -9,19 +9,14 @@ #cmakedefine HAVE_CLOCK_GETTIME #cmakedefine WITH_LOG4C 1 -#cmakedefine WITH_ICONV 1 #cmakedefine HAVE_ARGP_H 1 -#cmakedefine HAVE_ICONV_H 1 -#cmakedefine HAVE_SYS_ICONV_H 1 #cmakedefine HAVE_TIMEGM 1 #cmakedefine HAVE_STRERROR_R 1 #cmakedefine HAVE_UTIMES 1 #cmakedefine HAVE_LSTAT 1 #cmakedefine HAVE_FNMATCH 1 -#cmakedefine HAVE_ICONV 1 -#cmakedefine HAVE_ICONV_CONST 1 #cmakedefine HAVE___MINGW_ASPRINTF 1 #cmakedefine HAVE_ASPRINTF 1 diff --git a/csync/src/CMakeLists.txt b/csync/src/CMakeLists.txt index cb07377e90..8bce5f418d 100644 --- a/csync/src/CMakeLists.txt +++ b/csync/src/CMakeLists.txt @@ -28,11 +28,6 @@ set(CSYNC_LINK_LIBRARIES ${SQLITE3_LIBRARIES} ) -if(HAVE_ICONV AND WITH_ICONV) - list(APPEND CSYNC_PRIVATE_INCLUDE_DIRS ${ICONV_INCLUDE_DIR}) - list(APPEND CSYNC_LINK_LIBRARIES ${ICONV_LIBRARIES}) -endif() - # Specific option for builds tied to servers that do not support renaming extensions set(NO_RENAME_EXTENSION 0 CACHE BOOL "Do not issue rename if the extension changes") if(NO_RENAME_EXTENSION) diff --git a/csync/src/csync.c b/csync/src/csync.c index 9af6f0fcf8..1f75ac3bdb 100644 --- a/csync/src/csync.c +++ b/csync/src/csync.c @@ -33,13 +33,6 @@ #include #include -#ifdef HAVE_ICONV_H -#include -#endif -#ifdef HAVE_SYS_ICONV_H -#include -#endif - #include "c_lib.h" #include "csync_private.h" #include "csync_exclude.h" @@ -569,10 +562,6 @@ int csync_destroy(CSYNC *ctx) { SAFE_FREE(ctx->local.uri); SAFE_FREE(ctx->error_string); -#ifdef WITH_ICONV - c_close_iconv(); -#endif - SAFE_FREE(ctx); return rc; @@ -626,19 +615,6 @@ const char *csync_get_status_string(CSYNC *ctx) return csync_vio_get_status_string(ctx); } -#ifdef WITH_ICONV -int csync_set_iconv_codec(const char *from) -{ - c_close_iconv(); - - if (from != NULL) { - c_setup_iconv(from); - } - - return 0; -} -#endif - void csync_request_abort(CSYNC *ctx) { if (ctx != NULL) { diff --git a/csync/src/csync.h b/csync/src/csync.h index 949c9efd23..daf778c247 100644 --- a/csync/src/csync.h +++ b/csync/src/csync.h @@ -100,7 +100,8 @@ enum csync_status_codes_e { CSYNC_STATUS_INVALID_CHARACTERS, CSYNC_STATUS_INDIVIDUAL_STAT_FAILED, CSYNC_STATUS_FORBIDDEN, - CSYNC_STATUS_INDIVIDUAL_TOO_DEEP + CSYNC_STATUS_INDIVIDUAL_TOO_DEEP, + CSYNC_STATUS_INDIVIDUAL_IS_CONFLICT_FILE }; typedef enum csync_status_codes_e CSYNC_STATUS; @@ -479,17 +480,6 @@ int OCSYNC_EXPORT csync_walk_remote_tree(CSYNC *ctx, csync_treewalk_visit_func * */ const char OCSYNC_EXPORT *csync_get_status_string(CSYNC *ctx); -#ifdef WITH_ICONV -/** - * @brief Set iconv source codec for filenames. - * - * @param from Source codec. - * - * @return 0 on success, or an iconv error number. - */ -int OCSYNC_EXPORT csync_set_iconv_codec(const char *from); -#endif - /** * @brief Aborts the current sync run as soon as possible. Can be called from another thread. * diff --git a/csync/src/csync_exclude.c b/csync/src/csync_exclude.c index 88dd61e750..fea6625044 100644 --- a/csync/src/csync_exclude.c +++ b/csync/src/csync_exclude.c @@ -293,6 +293,13 @@ static CSYNC_EXCLUDE_TYPE _csync_excluded_common(c_strlist_t *excludes, const ch } #endif + /* We create a desktop.ini on Windows for the sidebar icon, make sure we don't sync them. */ + rc = csync_fnmatch("Desktop.ini", bname, 0); + if (rc == 0) { + match = CSYNC_FILE_SILENTLY_EXCLUDED; + goto out; + } + rc = csync_fnmatch(".owncloudsync.log*", bname, 0); if (rc == 0) { match = CSYNC_FILE_SILENTLY_EXCLUDED; @@ -302,7 +309,7 @@ static CSYNC_EXCLUDE_TYPE _csync_excluded_common(c_strlist_t *excludes, const ch /* Always ignore conflict files, not only via the exclude list */ rc = csync_fnmatch("*_conflict-*", bname, 0); if (rc == 0) { - match = CSYNC_FILE_SILENTLY_EXCLUDED; + match = CSYNC_FILE_EXCLUDE_CONFLICT; goto out; } @@ -313,7 +320,7 @@ static CSYNC_EXCLUDE_TYPE _csync_excluded_common(c_strlist_t *excludes, const ch } rc = csync_fnmatch(conflict, path, 0); if (rc == 0) { - match = CSYNC_FILE_SILENTLY_EXCLUDED; + match = CSYNC_FILE_EXCLUDE_CONFLICT; SAFE_FREE(conflict); goto out; } diff --git a/csync/src/csync_exclude.h b/csync/src/csync_exclude.h index ae49bbd8d1..722d27cace 100644 --- a/csync/src/csync_exclude.h +++ b/csync/src/csync_exclude.h @@ -32,7 +32,8 @@ enum csync_exclude_type_e { CSYNC_FILE_EXCLUDE_TRAILING_SPACE, CSYNC_FILE_EXCLUDE_LONG_FILENAME, CSYNC_FILE_EXCLUDE_HIDDEN, - CSYNC_FILE_EXCLUDE_STAT_FAILED + CSYNC_FILE_EXCLUDE_STAT_FAILED, + CSYNC_FILE_EXCLUDE_CONFLICT }; typedef enum csync_exclude_type_e CSYNC_EXCLUDE_TYPE; diff --git a/csync/src/csync_private.h b/csync/src/csync_private.h index 11775f345d..2edc26019d 100644 --- a/csync/src/csync_private.h +++ b/csync/src/csync_private.h @@ -42,17 +42,6 @@ #include "csync.h" #include "csync_misc.h" -#ifdef WITH_ICONV -#include -#endif - -#ifdef HAVE_ICONV_H -#include -#endif -#ifdef HAVE_SYS_ICONV_H -#include -#endif - #include "csync_macros.h" /** @@ -130,12 +119,6 @@ struct csync_s { } remote; -#if defined(HAVE_ICONV) && defined(WITH_ICONV) - struct { - iconv_t iconv_cd; - } options; -#endif - /* replica we are currently walking */ enum csync_replica_e current; diff --git a/csync/src/csync_update.c b/csync/src/csync_update.c index d7587765c3..1a069442e7 100644 --- a/csync/src/csync_update.c +++ b/csync/src/csync_update.c @@ -469,6 +469,8 @@ out: st->error_status = CSYNC_STATUS_INDIVIDUAL_EXCLUDE_HIDDEN; } else if (excluded == CSYNC_FILE_EXCLUDE_STAT_FAILED) { st->error_status = CSYNC_STATUS_INDIVIDUAL_STAT_FAILED; + } else if (excluded == CSYNC_FILE_EXCLUDE_CONFLICT) { + st->error_status = CSYNC_STATUS_INDIVIDUAL_IS_CONFLICT_FILE; } } } diff --git a/csync/src/std/CMakeLists.txt b/csync/src/std/CMakeLists.txt index 91b8843a85..519cada86e 100644 --- a/csync/src/std/CMakeLists.txt +++ b/csync/src/std/CMakeLists.txt @@ -1,14 +1,10 @@ -project(cstdlib C) +project(cstdlib) set(CSTDLIB_PUBLIC_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR} CACHE INTERNAL "cstdlib public include directories" ) -set(CSTDLIB_PRIVATE_INCLUDE_DIRS - ${ICONV_INCLUDE_DIR} -) - set(CSTDLIB_LIBRARY cstdlib CACHE INTERNAL "cstdlib library" @@ -24,6 +20,7 @@ set(cstdlib_SRCS c_rbtree.c c_string.c c_time.c + c_utf8.cc ) if(NOT HAVE_ASPRINTF AND NOT HAVE___MINGW_ASPRINTF) @@ -34,12 +31,12 @@ endif() include_directories( ${CSTDLIB_PUBLIC_INCLUDE_DIRS} - ${CSTDLIB_PRIVATE_INCLUDE_DIRS} ) add_library(${CSTDLIB_LIBRARY} STATIC ${cstdlib_SRCS}) if(NOT WIN32) add_definitions( -fPIC ) + qt5_use_modules(${CSTDLIB_LIBRARY} Core) endif() if(NOT HAVE_FNMATCH AND WIN32) # needed for PathMatchSpec for our fnmatch replacement diff --git a/csync/src/std/c_private.h b/csync/src/std/c_private.h index 09c1ea36b6..26bba70db5 100644 --- a/csync/src/std/c_private.h +++ b/csync/src/std/c_private.h @@ -159,13 +159,6 @@ typedef char mbchar_t; #define _tgetcwd getcwd #endif -#ifdef WITH_ICONV -/** @internal */ -int c_setup_iconv(const char* to); -/** @internal */ -int c_close_iconv(void); -#endif - /* FIXME: Implement TLS for OS X */ #if defined(__GNUC__) && !defined(__APPLE__) # define CSYNC_THREAD __thread diff --git a/csync/src/std/c_string.c b/csync/src/std/c_string.c index e21d4374e9..8929f24586 100644 --- a/csync/src/std/c_string.c +++ b/csync/src/std/c_string.c @@ -40,97 +40,6 @@ #include #endif -#if defined(HAVE_ICONV) && defined(WITH_ICONV) -# ifdef HAVE_ICONV_H -# include -# endif -# ifdef HAVE_SYS_ICONV_H -# include -# endif - -typedef struct { - iconv_t to; - iconv_t from; -} iconv_conversions; - -CSYNC_THREAD iconv_conversions _iconvs = { NULL, NULL }; - -int c_setup_iconv(const char* to) { - _iconvs.to = iconv_open(to, "UTF-8"); - _iconvs.from = iconv_open("UTF-8", to); - - if (_iconvs.to == (iconv_t)-1 || _iconvs.from == (iconv_t)-1) - return -1; - - return 0; -} - -int c_close_iconv() { - int ret_to = 0; - int ret_from = 0; - if( _iconvs.to != (iconv_t) NULL ) { - ret_to = iconv_close(_iconvs.to); - } - if( _iconvs.from != (iconv_t) NULL ) { - ret_from = iconv_close(_iconvs.from); - } - - if (ret_to == -1 || ret_from == -1) - return -1; - - _iconvs.to = (iconv_t) 0; - _iconvs.from = (iconv_t) 0; - - return 0; -} - -enum iconv_direction { iconv_from_native, iconv_to_native }; - -static char *c_iconv(const char* str, enum iconv_direction dir) -{ -#ifdef HAVE_ICONV_CONST - const char *in = str; -#else - char *in = discard_const(str); -#endif - size_t size; - size_t outsize; - char *out; - char *out_in; - size_t ret; - - if (str == NULL) { - return NULL; - } - - if(_iconvs.from == NULL && _iconvs.to == NULL) { -#ifdef __APPLE__ - c_setup_iconv("UTF-8-MAC"); -#else - return c_strdup(str); -#endif - } - - size = strlen(in); - outsize = size*2; - out = c_malloc(outsize); - out_in = out; - - if (dir == iconv_to_native) { - ret = iconv(_iconvs.to, &in, &size, &out, &outsize); - } else { - ret = iconv(_iconvs.from, &in, &size, &out, &outsize); - } - - if (ret == (size_t)-1) { - SAFE_FREE(out_in); - return NULL; - } - - return out_in; -} -#endif /* defined(HAVE_ICONV) && defined(WITH_ICONV) */ - int c_strncasecmp(const char *a, const char *b, size_t n) { #ifdef _WIN32 return _strnicmp(a, b, n); @@ -258,70 +167,3 @@ void c_strlist_destroy(c_strlist_t *strlist) { SAFE_FREE(strlist->vector); SAFE_FREE(strlist); } - -/* Convert a wide multibyte String to UTF8 */ -char* c_utf8_from_locale(const mbchar_t *wstr) -{ - char *dst = NULL; -#ifdef _WIN32 - char *mdst = NULL; - int size_needed; - size_t len; -#endif - - if (wstr == NULL) { - return NULL; - } - -#ifdef _WIN32 - len = wcslen(wstr); - /* Call once to get the required size. */ - size_needed = WideCharToMultiByte(CP_UTF8, 0, wstr, len, NULL, 0, NULL, NULL); - if (size_needed > 0) { - mdst = c_malloc(size_needed + 1); - - memset(mdst, 0, size_needed + 1); - WideCharToMultiByte(CP_UTF8, 0, wstr, len, mdst, size_needed, NULL, NULL); - dst = mdst; - } -#else -#ifdef WITH_ICONV - dst = c_iconv(wstr, iconv_from_native); -#else - dst = (char*) wstr; -#endif -#endif - return dst; -} - -/* Convert a an UTF8 string to multibyte */ -mbchar_t* c_utf8_string_to_locale(const char *str) -{ - mbchar_t *dst = NULL; -#ifdef _WIN32 - size_t len; - int size_needed; -#endif - - if (str == NULL ) { - return NULL; - } - -#ifdef _WIN32 - len = strlen(str); - size_needed = MultiByteToWideChar(CP_UTF8, 0, str, len, NULL, 0); - if (size_needed > 0) { - int size_char = (size_needed + 1) * sizeof(mbchar_t); - dst = c_malloc(size_char); - memset((void*)dst, 0, size_char); - MultiByteToWideChar(CP_UTF8, 0, str, -1, dst, size_needed); - } -#else -#ifdef WITH_ICONV - dst = c_iconv(str, iconv_to_native); -#else - dst = (_TCHAR*) str; -#endif -#endif - return dst; -} diff --git a/csync/src/std/c_string.h b/csync/src/std/c_string.h index 60bc010da4..304c598a23 100644 --- a/csync/src/std/c_string.h +++ b/csync/src/std/c_string.h @@ -194,8 +194,6 @@ void c_strlist_destroy(c_strlist_t *strlist); */ mbchar_t* c_utf8_string_to_locale(const char *wstr); -#if defined(_WIN32) || defined(WITH_ICONV) - /** * @brief Free buffer malloced by c_utf8_from_locale or c_utf8_to_locale(). * @@ -214,9 +212,7 @@ mbchar_t* c_utf8_string_to_locale(const char *wstr); * */ #define c_free_locale_string(x) SAFE_FREE(x) -#else -#define c_free_locale_string(x) (void)x -#endif + /** * }@ diff --git a/csync/src/std/c_utf8.cc b/csync/src/std/c_utf8.cc new file mode 100644 index 0000000000..4bd5d75ac1 --- /dev/null +++ b/csync/src/std/c_utf8.cc @@ -0,0 +1,104 @@ +/* + * cynapses libc functions + * + * Copyright (c) 2008-2013 by Andreas Schneider + * Copyright (c) 2012-2013 by Klaas Freitag + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config_csync.h" + +#ifdef _WIN32 +#include +#include +#include +#include +#include +#include +#include +#else +#include +#include +#endif + +extern "C" { +#include "c_alloc.h" +#include "c_string.h" + +/* Convert a locale String to UTF8 */ +char* c_utf8_from_locale(const mbchar_t *wstr) +{ + if (wstr == NULL) { + return NULL; + } + +#ifdef _WIN32 + char *dst = NULL; + char *mdst = NULL; + int size_needed; + size_t len; + len = wcslen(wstr); + /* Call once to get the required size. */ + size_needed = WideCharToMultiByte(CP_UTF8, 0, wstr, len, NULL, 0, NULL, NULL); + if (size_needed > 0) { + mdst = (char*)c_malloc(size_needed + 1); + + memset(mdst, 0, size_needed + 1); + WideCharToMultiByte(CP_UTF8, 0, wstr, len, mdst, size_needed, NULL, NULL); + dst = mdst; + } + return dst; +#else + QTextDecoder dec(QTextCodec::codecForLocale()); + QString s = dec.toUnicode(wstr, qstrlen(wstr)); + if (s.isEmpty() || dec.hasFailure()) { + /* Conversion error: since we can't report error from this function, just return the original + string. We take care of invalid utf-8 in SyncEngine::treewalkFile */ + return c_strdup(wstr); + } +#ifdef __APPLE__ + s = s.normalized(QString::NormalizationForm_C); +#endif + return c_strdup(std::move(s).toUtf8().constData()); +#endif +} + +/* Convert a an UTF8 string to locale */ +mbchar_t* c_utf8_string_to_locale(const char *str) +{ + if (str == NULL ) { + return NULL; + } +#ifdef _WIN32 + mbchar_t *dst = NULL; + size_t len; + int size_needed; + + len = strlen(str); + size_needed = MultiByteToWideChar(CP_UTF8, 0, str, len, NULL, 0); + if (size_needed > 0) { + int size_char = (size_needed + 1) * sizeof(mbchar_t); + dst = (mbchar_t*)c_malloc(size_char); + memset((void*)dst, 0, size_char); + MultiByteToWideChar(CP_UTF8, 0, str, -1, dst, size_needed); + } + return dst; +#else + return c_strdup(QFile::encodeName(QString::fromUtf8(str))); +#endif +} + +} diff --git a/csync/src/vio/csync_vio.c b/csync/src/vio/csync_vio.c index 3993af9a1d..909117836a 100644 --- a/csync/src/vio/csync_vio.c +++ b/csync/src/vio/csync_vio.c @@ -115,7 +115,7 @@ int csync_vio_stat(CSYNC *ctx, const char *uri, csync_vio_file_stat_t *buf) { case LOCAL_REPLICA: rc = csync_vio_local_stat(uri, buf); if (rc < 0) { - CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "Local stat failed, errno %d", errno); + CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "Local stat failed, errno %d for %s", errno, uri); } break; default: diff --git a/csync/tests/csync_tests/check_csync_exclude.c b/csync/tests/csync_tests/check_csync_exclude.c index bd4813c5ed..ea9ca0e2f6 100644 --- a/csync/tests/csync_tests/check_csync_exclude.c +++ b/csync/tests/csync_tests/check_csync_exclude.c @@ -113,9 +113,9 @@ static void check_csync_excluded(void **state) assert_int_equal(rc, CSYNC_NOT_EXCLUDED); rc = csync_excluded_no_ctx(csync->excludes, ".kde/share/config/kwin.eventsrc", CSYNC_FTW_TYPE_FILE); assert_int_equal(rc, CSYNC_NOT_EXCLUDED); - rc = csync_excluded_no_ctx(csync->excludes, ".htaccess/cache-maximegalon/cache1.txt", CSYNC_FTW_TYPE_FILE); + rc = csync_excluded_no_ctx(csync->excludes, ".directory/cache-maximegalon/cache1.txt", CSYNC_FTW_TYPE_FILE); assert_int_equal(rc, CSYNC_FILE_EXCLUDE_LIST); - rc = csync_excluded_no_ctx(csync->excludes, "mozilla/.htaccess", CSYNC_FTW_TYPE_DIR); + rc = csync_excluded_no_ctx(csync->excludes, "mozilla/.directory", CSYNC_FTW_TYPE_DIR); assert_int_equal(rc, CSYNC_FILE_EXCLUDE_LIST); /* diff --git a/csync/tests/csync_tests/check_csync_update.c b/csync/tests/csync_tests/check_csync_update.c index 6bd731947c..695616b2ca 100644 --- a/csync/tests/csync_tests/check_csync_update.c +++ b/csync/tests/csync_tests/check_csync_update.c @@ -52,6 +52,13 @@ static void statedb_create_metadata_table(sqlite3 *db) rc = sqlite3_exec(db, sql, NULL, NULL, NULL); //const char *msg = sqlite3_errmsg(db); assert_int_equal( rc, SQLITE_OK ); + + sql = "CREATE TABLE IF NOT EXISTS checksumtype(" + "id INTEGER PRIMARY KEY," + "name TEXT UNIQUE" + ");"; + rc = sqlite3_exec(db, sql, NULL, NULL, NULL); + assert_int_equal( rc, SQLITE_OK ); } } diff --git a/csync/tests/encoding_tests/check_encoding.c b/csync/tests/encoding_tests/check_encoding.c index bbfbe99c8d..eb761d318a 100644 --- a/csync/tests/encoding_tests/check_encoding.c +++ b/csync/tests/encoding_tests/check_encoding.c @@ -29,30 +29,12 @@ static void setup(void **state) { - int rc = 0; - (void) state; /* unused */ - -#ifdef HAVE_ICONV -#ifdef __APPLE__ - /* this test only works on apple because linux does not know the - * UTF-8-MAC encoding that we use here. */ - rc = c_setup_iconv("UTF-8-MAC"); -#endif -#endif - assert_int_equal(rc, 0); } static void teardown(void **state) { - int rc = 0; - (void) state; /* unused */ -#ifdef HAVE_ICONV - // this must never crash - rc = c_close_iconv(); -#endif - assert_int_equal(rc, 0); } static void check_iconv_to_native_normalization(void **state) diff --git a/csync/tests/ownCloud/ownCloud/Test.pm b/csync/tests/ownCloud/ownCloud/Test.pm index ee3d557294..251164e2c1 100644 --- a/csync/tests/ownCloud/ownCloud/Test.pm +++ b/csync/tests/ownCloud/ownCloud/Test.pm @@ -294,15 +294,12 @@ sub localCleanup($) system( "rm -rf $dir" ); } -# parameter: An optional full url to the owncloud sync dir. +# parameter: the expected return code sub csync( ;$ ) { - my ($aurl) = @_; + my $expected = $_[0] // 0; my $url = testDirUrl(); - if( $aurl ) { - $url = $aurl; - } if( $url =~ /^https:/ ) { $url =~ s#^https://##; # Remove the leading http:// $url = "ownclouds://$user:$passwd@". $url; @@ -317,7 +314,8 @@ sub csync( ;$ ) my $cmd = "LD_LIBRARY_PATH=$ld_libpath $csync $args $localDir $url"; print "Starting: $cmd\n"; - system( $cmd ) == 0 or die("CSync died!\n"); + my $result = system( $cmd ); + $result == ($expected << 8) or die("Wrong csync return code or crash! $result\n"); } # diff --git a/csync/tests/ownCloud/t4.pl b/csync/tests/ownCloud/t4.pl index e862abebb0..aeccd2a767 100755 --- a/csync/tests/ownCloud/t4.pl +++ b/csync/tests/ownCloud/t4.pl @@ -79,7 +79,8 @@ printInfo("Add a file in a read only directory"); system( "echo \"Hello World\" >> /tmp/kernelcrash.txt" ); put_to_dir( '/tmp/kernelcrash.txt', 'test_stat' ); -csync(); +# Sync failed, can't download file to readonly dir +csync(1); assert( ! -e localDir().'test_stat/kernelcrash' ); diff --git a/csync/tests/ownCloud/t7.pl b/csync/tests/ownCloud/t7.pl index 117b998fe8..6b68a06efa 100755 --- a/csync/tests/ownCloud/t7.pl +++ b/csync/tests/ownCloud/t7.pl @@ -102,10 +102,6 @@ system("echo '__modified' > ". localDir() . "normalDirectory_PERM_CKDNV_/canBeMo system("echo '__modified_' > ". localDir() . "readonlyDirectory_PERM_M_/canBeModified_PERM_W_.data"); #5. Create a new file in a read only folder -# (they should not be uploaded) -createLocalFile( localDir() . "readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data", 105 ); - -#6. Create a new file in a read only folder # (should be uploaded) createLocalFile( localDir() . "normalDirectory_PERM_CKDNV_/newFile_PERM_WDNV_.data", 106 ); @@ -113,7 +109,6 @@ createLocalFile( localDir() . "normalDirectory_PERM_CKDNV_/newFile_PERM_WDNV_.da csync(); assertCsyncJournalOk(localDir()); - #1. # File should be recovered assert( -e localDir(). 'normalDirectory_PERM_CKDNV_/cannotBeRemoved_PERM_WVN_.data' ); @@ -139,6 +134,23 @@ system("rm " . localDir().'readonlyDirectory_PERM_M_/canotBeModified_PERM_DVN__c #4. File should be updated, that's tested by assertLocalAndRemoteDir #5. +# the file should be in the server and local +assert( -e localDir() . "normalDirectory_PERM_CKDNV_/newFile_PERM_WDNV_.data" ); + +### Both side should still be the same +assertLocalAndRemoteDir( '', 0); + +# Next test + +#6. Create a new file in a read only folder +# (they should not be uploaded) +createLocalFile( localDir() . "readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data", 105 ); + +# error: can't upload to readonly +csync(1); +assertCsyncJournalOk(localDir()); + +#6. # The file should not exist on the remote # TODO: test that the file is NOT on the server # but still be there @@ -146,11 +158,6 @@ assert( -e localDir() . "readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data" ); # remove it so assertLocalAndRemoteDir succeed. unlink(localDir() . "readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data"); -#6. -# the file should be in the server and local -assert( -e localDir() . "normalDirectory_PERM_CKDNV_/newFile_PERM_WDNV_.data" ); - - ### Both side should still be the same assertLocalAndRemoteDir( '', 0); @@ -207,7 +214,8 @@ system("mv " . localDir().'readonlyDirectory_PERM_M_/subdir_PERM_CK_ ' . localDi #2. move a directory from read to read only (move the directory from previous step) system("mv " . localDir().'normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_ ' . localDir().'readonlyDirectory_PERM_M_/moved_PERM_CK_' ); -csync(); +# error: can't upload to readonly! +csync(1); assertCsyncJournalOk(localDir()); #1. diff --git a/csync/tests/ownCloud/t8.pl b/csync/tests/ownCloud/t8.pl index 1a0557d7fe..4628344da0 100755 --- a/csync/tests/ownCloud/t8.pl +++ b/csync/tests/ownCloud/t8.pl @@ -54,7 +54,8 @@ createLocalFile( $tmpdir . "test.dat", 170 ); createRemoteDir( "dir" ); glob_put( "$tmpdir/*", "dir" ); -csync(); +# can't download these +csync(1); # Check that only one of the two file was synced. # The one that exist here is undefined, the current implementation will take the @@ -76,7 +77,8 @@ printInfo( "Renaming one file to the same name as another one with different cas moveRemoteFile( 'dir/Hello.dat', 'dir/NORMAL.dat'); moveRemoteFile( 'dir/test.dat', 'dir/TEST.dat'); -csync(); +# can't download these +csync(1); # Hello -> NORMAL should not have do the move since the case conflict assert( -e localDir() . 'dir/Hello.dat' ); @@ -87,13 +89,16 @@ assert( -e localDir() . 'dir/Normal.dat' ); assert( -e localDir() . 'dir/TEST.dat' ); assert( !-e localDir() . 'dir/test.dat' ); +# undo the change that causes the sync fail +moveRemoteFile( 'dir/NORMAL.dat', 'dir/Hello.dat'); printInfo( "Another directory with the same name but different casing is created" ); createRemoteDir( "DIR" ); -glob_put( "$tmpdir/*", "DIR" ); +glob_put( "$tmpdir/HELLO.dat", "DIR" ); -csync(); +# can't download dirs +csync(1); assert( !-e localDir() . 'DIR' ); @@ -107,7 +112,6 @@ csync(); # now DIR was fetched assert( -e localDir() . 'DIR' ); assert( -e localDir() . 'DIR/HELLO.dat' ); -assert( !-e localDir() . 'DIR/Hello.dat' ); assert( !-e localDir() . 'dir' ); # dir/NORMAL.dat is still on the server @@ -125,7 +129,8 @@ createLocalFile( $tmpdir2 . "file.dat", 33 ); createRemoteDir( "parallel" ); glob_put( "$tmpdir2/*", "parallel" ); -csync(); +# again, can't download both +csync(1); # only one file must exist assert( (!-e localDir() . 'parallel/FILE.dat' ) or (!-e localDir() . 'parallel/file.dat') ); diff --git a/csync/tests/vio_tests/check_vio_ext.c b/csync/tests/vio_tests/check_vio_ext.c index f91328767e..c34e708c0a 100644 --- a/csync/tests/vio_tests/check_vio_ext.c +++ b/csync/tests/vio_tests/check_vio_ext.c @@ -434,7 +434,7 @@ static void check_readdir_bigunicode(void **state) assert_int_equal(rc, 0); SAFE_FREE(p); - const char *t1 = "goodone/ugly\xEF\xBB\xBF\x32" ".txt"; + const char *t1 = "goodone/ugly\xEF\xBB\xBF\x32" ".txt"; // file with encoding error asprintf( &p, "%s/%s", CSYNC_TEST_DIR, t1 ); rc = _tmkdir(p, MKDIR_MASK); SAFE_FREE(p); @@ -444,18 +444,10 @@ static void check_readdir_bigunicode(void **state) int files_cnt = 0; traverse_dir(state, CSYNC_TEST_DIR, &files_cnt); const char *expected_result = " C:/tmp/csync_test/goodone" -#ifndef __APPLE__ - // On Mac, iconv will not return some files with fancy unicode. - // Linux is not so picky about it and return everything and let the sync engine deal with it. " C:/tmp/csync_test/goodone/ugly\xEF\xBB\xBF\x32" ".txt" -#endif ; assert_string_equal( sv->result, expected_result); -#ifdef __APPLE__ - // Bad one is recognized though.. ! - assert_string_equal( sv->ignored_dir, CSYNC_TEST_DIR "/goodone/" "ugly\xEF\xBB\xBF\x32" ".txt"); -#endif assert_int_equal(files_cnt, 0); } diff --git a/doc/building.rst b/doc/building.rst index 7cc6da6987..5207b45f7a 100644 --- a/doc/building.rst +++ b/doc/building.rst @@ -30,10 +30,10 @@ you the exact sources from which the binary packages are built. These are hosted on the `ownCloud repository from OBS`_. Go to the `Index of repositories`_ to see all the Linux client repos. -1. At the `bottom of the page for each distribution +1. The source RPMs for CentOS, RHEL, Fedora, SLES, and openSUSE are at the `bottom of the page for each distribution `_ is a "Grab binary packages directly" section. - These contain source RPMs for CentOS, RHEL, Fedora, SLES, and openSUSE. + package=owncloud-client>` + the sources for DEB and Ubuntu based distributions are at e.g. http://download.opensuse.org/repositories/isv:/ownCloud:/desktop/Ubuntu_16.04/ To get the .deb source packages add the source repo for your Debian or Ubuntu version, like this example for Debian 8 @@ -120,6 +120,7 @@ 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:: @@ -129,10 +130,11 @@ follow `Windows Installer Build (Cross-Compile)`_ instead. 3. Open the Qt MinGW shortcut console from the Start Menu -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:: +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:: + set PATH=C:\\bin;%PATH% set PATH=C:\;%PATH% 5. Build qtkeychain **directly in the source directory** so that the DLL is built @@ -277,6 +279,7 @@ 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/doc/conf.py b/doc/conf.py index 61b0d63027..ce5bf4279c 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -48,9 +48,9 @@ copyright = u'2013-2016, The ownCloud developers' # built documents. # # The short X.Y version. -version = '2.3.0' +version = '2.4.0' # The full version, including alpha/beta/rc tags. -release = '2.3.0' +release = '2.4.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/doc/navigating.rst b/doc/navigating.rst index c136b07b44..b4cf521835 100644 --- a/doc/navigating.rst +++ b/doc/navigating.rst @@ -190,7 +190,9 @@ The Activity window contains the log of your recent activities, organized over three tabs: **Server Activities**, which includes new shares and files downloaded and deleted, **Sync Protocol**, which displays local activities such as which local folders your files went into, and **Not Synced** shows errors -such as files not synced. +such as files not synced. Double clicking an entry pointing to an existing +file in **Server Activities** or **Sync Protocol** will open the folder containing +the file and highlight it. .. figure:: images/client-8.png :alt: Activity windows logs all server and client activities. diff --git a/doc/ocdoc b/doc/ocdoc deleted file mode 160000 index 2fdd8b2833..0000000000 --- a/doc/ocdoc +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2fdd8b2833ea1db7da08a61afda665eb6ecaa017 diff --git a/doc/owncloud.1.rst b/doc/owncloud.1.rst deleted file mode 100644 index 27092f0b65..0000000000 --- a/doc/owncloud.1.rst +++ /dev/null @@ -1,34 +0,0 @@ -:orphan: - -owncloud(1) ------------ - -SYNOPSIS -======== -*owncloud* [`OPTIONS`...] - - -DESCRIPTION -=========== -The ownCloud Client is a file synchronization desktop utility. It synchronizes files on your local computer, tablet, or handheld device with an ownCloud Server. If you make a change to the files on one device, the change is propagated to all other synchronized devices using the desktop synchronization clients. - -Normally, you start the client by clicking on the desktop icon or by starting it from the client application menu. After starting, an ownCloud icon appears in the computer system tray or on your tablet or handheld device. - -Options -======= -.. include:: options.rst - -Config File -=========== -.. include:: conffile.rst - -BUGS -==== - -Please report bugs at https://github.com/owncloud/client/issues. - - -SEE ALSO -======== -:manpage:`owncloudcmd(1)` - diff --git a/doc/owncloudcmd.1.rst b/doc/owncloudcmd.1.rst deleted file mode 100644 index 573b0d1f12..0000000000 --- a/doc/owncloudcmd.1.rst +++ /dev/null @@ -1,98 +0,0 @@ -:orphan: - -owncloudcmd(1) --------------- - -SYNOPSIS -======== -*owncloudcmd* [`OPTIONS`...] sourcedir owncloudurl - -DESCRIPTION -=========== -owncloudcmd is the command line tool used for the ownCloud file synchronization -desktop utility. - -Contrary to the :manpage:`owncloud(1)` GUI client, `owncloudcmd` only performs -a single sync run and then exits. In so doing, `owncloudcmd` replaces the -`ocsync` binary used for the same purpose in earlier releases. - -A *sync run* synchronizes a single local directory using a WebDAV share on a -remote ownCloud server. - -To invoke the command line client, provide the local and the remote repository: -The first parameter is the local directory. The second parameter is -the server URL. - -.. note:: Prior to the 1.6 release of owncloudcmd, the tool only accepted - ``owncloud://`` or ``ownclouds://`` in place of ``http://`` and ``https://`` as - a scheme. See ``Examples`` for details. - -OPTIONS -======= -``--user``, ``-u`` ``[user]`` - Use ``user`` as the login name. - -``--password``, ``-p`` ``[password]`` - Use ``password`` as the password. - -``-n`` - Use ``netrc (5)`` for login. - -``--non-interactive`` - Do not prompt for questions. - -``--silent``, ``--s`` - Inhibits verbose log output. - -``--trust`` - Trust any SSL certificate, including invalid ones. - -``--httpproxy http://[user@pass:]:`` - Uses ``server`` as HTTP proxy. - -``--nonshib`` - Uses Non Shibboleth WebDAV Authentication - -``--davpath [path]`` - Overrides the WebDAV Path with ``path`` - -``--exclude [file]`` - Exclude list file - -``--unsyncedfolders [file]`` - File containing the list of un-synced folders (selective sync) - -``--max-sync-retries [n]`` - Retries maximum n times (defaults to 3) - -``-h`` - Sync hidden files,do not ignore them - -Example -======= -To synchronize the ownCloud directory ``Music`` to the local directory ``media/music`` -through a proxy listening on port ``8080`` on the gateway machine ``192.168.178.1``, -the command line would be:: - - $ owncloudcmd --httpproxy http://192.168.178.1:8080 \ - $HOME/media/music \ - https://server/owncloud/remote.php/webdav/Music - -``owncloudcmd`` will enquire user name and password, unless they have -been specified on the command line or ``-n`` (see `netrc(5)`) has been passed. - -Using the legacy scheme, it would be:: - - $ owncloudcmd --httpproxy http://192.168.178.1:8080 \ - $HOME/media/music \ - ownclouds://server/owncloud/remote.php/webdav/Music - - -BUGS -==== -Please report bugs at https://github.com/owncloud/client/issues. - -SEE ALSO -======== -:manpage:`owncloud(1)` - diff --git a/mirall.desktop.in b/mirall.desktop.in index b3a21737b9..55fb89e470 100644 --- a/mirall.desktop.in +++ b/mirall.desktop.in @@ -48,6 +48,66 @@ X-GNOME-Autostart-Delay=3 # Translations +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + +# Translations + + # Translations Comment[oc]=@APPLICATION_NAME@ sincronizacion del client GenericName[oc]=Dorsièr de Sincronizacion @@ -187,6 +247,7 @@ Comment[zh_CN]=@APPLICATION_NAME@ 桌面同步客户端 GenericName[zh_CN]=文件夹同步 Name[zh_CN]=@APPLICATION_NAME@ 桌面同步客户端 Icon[zh_CN]=@APPLICATION_EXECUTABLE@ +Comment[zh_HK]=桌面版同步客户端 GenericName[zh_TW]=資料夾同步 Comment[es_AR]=Cliente de sincronización para escritorio @APPLICATION_NAME@ GenericName[es_AR]=Sincronización de directorio diff --git a/shell_integration/MacOSX/CMakeLists.txt b/shell_integration/MacOSX/CMakeLists.txt index 2e594bfcef..06174af7c4 100644 --- a/shell_integration/MacOSX/CMakeLists.txt +++ b/shell_integration/MacOSX/CMakeLists.txt @@ -1,12 +1,4 @@ - if(APPLE) -add_custom_target( legacy_mac_overlayplugin ALL - xcodebuild -workspace ${CMAKE_SOURCE_DIR}/shell_integration/MacOSX/OwnCloud.xcworkspace - -scheme SyncStateFinder.osax -configuration Release SYMROOT=${CMAKE_CURRENT_BINARY_DIR} - OC_APPLICATION_REV_DOMAIN=${APPLICATION_REV_DOMAIN} - OC_SOCKETAPI_TEAM_IDENTIFIER_PREFIX=${SOCKETAPI_TEAM_IDENTIFIER_PREFIX} - COMMENT building Legacy Mac Overlay icons) - # Contrary to popular belief, this is called like this no matter what theme/OEM. set(OC_OEM_SHARE_ICNS "${CMAKE_BINARY_DIR}/src/gui/ownCloud.icns") @@ -25,12 +17,6 @@ add_custom_target( mac_overlayplugin ALL VERBATIM) add_dependencies(mac_overlayplugin ${APPLICATION_EXECUTABLE}) # for the ownCloud.icns to be generated - -# legacy -INSTALL( DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/Release/SyncStateFinder.osax/Contents - DESTINATION ${CMAKE_INSTALL_PREFIX}/Library/ScriptingAdditions/SyncStateFinder.osax/ ) - -# >= 10.10.x INSTALL(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/Release/FinderSyncExt.appex DESTINATION ${OWNCLOUD_OSX_BUNDLE}/Contents/PlugIns USE_SOURCE_PERMISSIONS) diff --git a/shell_integration/MacOSX/OwnCloud.xcworkspace/contents.xcworkspacedata b/shell_integration/MacOSX/OwnCloud.xcworkspace/contents.xcworkspacedata index 26fba3447d..ed88ebf479 100644 --- a/shell_integration/MacOSX/OwnCloud.xcworkspace/contents.xcworkspacedata +++ b/shell_integration/MacOSX/OwnCloud.xcworkspace/contents.xcworkspacedata @@ -4,10 +4,4 @@ - - - - diff --git a/shell_integration/MacOSX/OwnCloudFinder/ContentManager.h b/shell_integration/MacOSX/OwnCloudFinder/ContentManager.h deleted file mode 100644 index 82d3b52c48..0000000000 --- a/shell_integration/MacOSX/OwnCloudFinder/ContentManager.h +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved. - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 2.1 of the License, or (at your option) - * any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - * details. - */ - -#import - -@interface OwnCloudFinderContentManager : NSObject -{ - NSMutableDictionary* _fileNamesCache; - NSMutableDictionary* _oldFileNamesCache; - BOOL _fileIconsEnabled; - BOOL _hasChangedContent; - - NSNumber *_icnOk; - NSNumber *_icnSync; - NSNumber *_icnWarn; - NSNumber *_icnErr; - NSNumber *_icnOkSwm; - NSNumber *_icnSyncSwm; - NSNumber *_icnWarnSwm; - NSNumber *_icnErrSwm; -} - -+ (OwnCloudFinderContentManager*)sharedInstance; - -- (void)enableFileIcons:(BOOL)enable; -- (NSNumber*)iconByPath:(NSString*)path isDirectory:(BOOL)isDir; -- (void)removeAllIcons; -- (void)setIcons:(NSDictionary*)iconDictionary filterByFolder:(NSString*)filterFolder; -- (void)setResultForPath:(NSString*)path result:(NSString*)result; -- (void)clearFileNameCache; -- (void)reFetchFileNameCacheForPath:(NSString*)path; -- (void)repaintAllWindows; - -- (void)loadIconResources; - -@end \ No newline at end of file diff --git a/shell_integration/MacOSX/OwnCloudFinder/ContentManager.m b/shell_integration/MacOSX/OwnCloudFinder/ContentManager.m deleted file mode 100644 index 7fe564e59c..0000000000 --- a/shell_integration/MacOSX/OwnCloudFinder/ContentManager.m +++ /dev/null @@ -1,358 +0,0 @@ -/** - * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved. - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 2.1 of the License, or (at your option) - * any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - * details. - */ - -#import -#import -#import -#import "ContentManager.h" -#import "MenuManager.h" -#import "RequestManager.h" -#import "IconCache.h" - -static OwnCloudFinderContentManager* sharedInstance = nil; - -@implementation OwnCloudFinderContentManager -- init -{ - self = [super init]; - - if (self) - { - _fileNamesCache = [[NSMutableDictionary alloc] init]; - _oldFileNamesCache = [[NSMutableDictionary alloc] init]; - _fileIconsEnabled = TRUE; - _hasChangedContent = TRUE; - [self loadIconResources]; - } - - return self; -} - -- (void)dealloc -{ - [self removeAllIcons]; - [_fileNamesCache release]; - [_oldFileNamesCache release]; - sharedInstance = nil; - - [super dealloc]; -} - -+ (OwnCloudFinderContentManager*)sharedInstance -{ - @synchronized(self) - { - if (sharedInstance == nil) - { - sharedInstance = [[self alloc] init]; - } - } - - return sharedInstance; -} - -- (void)loadIconResources -{ - NSBundle *extBundle = [NSBundle bundleForClass:[self class]]; - - _icnOk = [[IconCache sharedInstance] registerIcon:[extBundle imageForResource:@"ok.icns"]]; - _icnSync = [[IconCache sharedInstance] registerIcon:[extBundle imageForResource:@"sync.icns"]]; - _icnWarn = [[IconCache sharedInstance] registerIcon:[extBundle imageForResource:@"warning.icns"]]; - _icnErr = [[IconCache sharedInstance] registerIcon:[extBundle imageForResource:@"error.icns"]]; - _icnOkSwm = [[IconCache sharedInstance] registerIcon:[extBundle imageForResource:@"ok_swm.icns"]]; - _icnSyncSwm = [[IconCache sharedInstance] registerIcon:[extBundle imageForResource:@"sync_swm.icns"]]; - _icnWarnSwm = [[IconCache sharedInstance] registerIcon:[extBundle imageForResource:@"warning_swm.icns"]]; - _icnErrSwm = [[IconCache sharedInstance] registerIcon:[extBundle imageForResource:@"error_swm.icns"]]; - - // NSLog(@"Icon ok: %@ identifier: %d from bundle %@", [extBundle imageForResource:@"ok.icns"], [_icnOk intValue], extBundle); -} - -- (void)enableFileIcons:(BOOL)enable -{ - _fileIconsEnabled = enable; - - [self repaintAllWindows]; -} - -- (void)setResultForPath:(NSString*)path result:(NSString*)result -{ - if (_icnOk == nil) { - // no icon resource path registered yet - return; - } - - NSNumber *res; - res = [NSNumber numberWithInt:0]; - - if( [result isEqualToString:@"OK"] ) { - res = _icnOk; - } else if( [result isEqualToString:@"SYNC"] || [result isEqualToString:@"NEW"] ) { - res = _icnSync; - } else if( [result isEqualToString:@"IGNORE"]) { - res = _icnWarn; - } else if( [result isEqualToString:@"ERROR"]) { - res = _icnErr; - } else if( [result isEqualToString:@"OK+SWM"] ) { - res = _icnOkSwm; - } else if( [result isEqualToString:@"SYNC+SWM"] || [result isEqualToString:@"NEW+SWM"] ) { - res = _icnSyncSwm; - } else if( [result isEqualToString:@"IGNORE+SWM"]) { - res = _icnWarnSwm; - } else if( [result isEqualToString:@"ERROR+SWM"]) { - res = _icnErrSwm; - }else if( [result isEqualToString:@"NOP"]) { - // Nothing. - } else { - NSLog(@"SyncState: Unknown status code %@", result); - } - - NSString* normalizedPath = [path decomposedStringWithCanonicalMapping]; - - if (![_fileNamesCache objectForKey:normalizedPath] || ![[_fileNamesCache objectForKey:normalizedPath] isEqualTo:res]) { - [_fileNamesCache setObject:res forKey:normalizedPath]; - //NSLog(@"SET value %d %@", [res intValue], normalizedPath); - _hasChangedContent = YES; - [self performSelector:@selector(repaintAllWindowsIfNeeded) withObject:0 afterDelay:1.0]; // 1 sec - } -} - -- (NSNumber*)iconByPath:(NSString*)path isDirectory:(BOOL)isDir -{ - //NSLog(@"%@ %@", NSStringFromSelector(_cmd), path); - if (!_fileIconsEnabled) - { - NSLog(@"SyncState: Icons are NOT ENABLED!"); - // return nil; - } - - if( path == nil ) { - NSNumber *res = [NSNumber numberWithInt:0]; - return res; - } - NSString* normalizedPath = [path decomposedStringWithCanonicalMapping]; - - if (![[OwnCloudFinderRequestManager sharedInstance] isRegisteredPath:normalizedPath isDirectory:isDir]) { - return [NSNumber numberWithInt:0]; - } - - NSNumber* result = [_fileNamesCache objectForKey:normalizedPath]; - // NSLog(@"XXXXXXX Asking for icon for path %@ = %d",normalizedPath, [result intValue]); - - if( result == nil ) { - result = [NSNumber numberWithInt:0]; - // Set 0 into the cache, meaning "don't have an icon, but already requested it" - [_fileNamesCache setObject:result forKey:normalizedPath]; - // start the async call - [[OwnCloudFinderRequestManager sharedInstance] askForIcon:normalizedPath isDirectory:isDir]; - } - if ([result intValue] == 0) { - // Show the old state while we wait for the new one - NSNumber* oldResult = [_oldFileNamesCache objectForKey:normalizedPath]; - if (oldResult) - result = oldResult; - } - // NSLog(@"iconByPath return value %d", [result intValue]); - - return result; -} - -// Clears the entries from the hash to make it call again home to the desktop client. -- (void)clearFileNameCache -{ - [_fileNamesCache release]; - _fileNamesCache = [[NSMutableDictionary alloc] init]; - [_oldFileNamesCache removeAllObjects]; -} - -- (void)reFetchFileNameCacheForPath:(NSString*)path -{ - //NSLog(@"%@", NSStringFromSelector(_cmd)); - - // We won't request the new state if it finds the path in _fileNamesCache - // Move all entries to _oldFileNamesCache so that they get re-requested, but - // still available while we refill the cache - [_oldFileNamesCache addEntriesFromDictionary:_fileNamesCache]; - [_fileNamesCache removeAllObjects]; - - [self repaintAllWindows]; -} - - -- (void)removeAllIcons -{ - [_fileNamesCache removeAllObjects]; - [_oldFileNamesCache removeAllObjects]; - - [self repaintAllWindows]; -} - -- (void)repaintAllWindowsIfNeeded -{ - if (!_hasChangedContent) { - //NSLog(@"%@ Repaint scheduled but not needed", NSStringFromSelector(_cmd)); - return; - } - - _hasChangedContent = NO; - [self repaintAllWindows]; -} - -- (void)repaintAllWindows -{ - //NSLog(@"%@", NSStringFromSelector(_cmd)); - NSArray* windows = [[NSApplication sharedApplication] windows]; - - for (int i = 0; i < [windows count]; i++) - { - NSWindow* window = [windows objectAtIndex:i]; - - if (![window isVisible]) - { - continue; - } - - MenuManager* menuManager = [MenuManager sharedInstance]; - OwnCloudFinderRequestManager* requestManager = [OwnCloudFinderRequestManager sharedInstance]; - - if ([[window className] isEqualToString:@"TBrowserWindow"]) - { - NSObject* browserWindowController = [window browserWindowController]; - - BOOL repaintWindow = YES; - - NSString* filterFolder = [requestManager filterFolder]; - - if (filterFolder) - { - repaintWindow = NO; - - struct TFENodeVector* targetPath; - - if ([browserWindowController respondsToSelector:@selector(targetPath)]) - { - // 10.7 & 10.8 - targetPath = [browserWindowController targetPath]; - } - else if ([browserWindowController respondsToSelector:@selector(activeContainer)]) - { - // 10.9 - targetPath = [[browserWindowController activeContainer] targetPath]; - } - else - { - NSLog(@"SyncState: refreshing icon badges failed"); - - return; - } - - NSArray* folderPaths = [menuManager pathsForNodes:targetPath]; - - for (NSString* folderPath in folderPaths) - { - if ([folderPath hasPrefix:filterFolder] || [filterFolder hasPrefix:folderPath]) - { - repaintWindow = YES; - - break; - } - } - } - - if (repaintWindow) - { - if ([browserWindowController respondsToSelector:@selector(browserViewController)]) - { - // 10.7 & 10.8 - NSObject* browserViewController = [browserWindowController browserViewController]; - - NSObject* browserView = [browserViewController browserView]; - - dispatch_async(dispatch_get_main_queue(), ^{[browserView setNeedsDisplay:YES];}); - } - else if ([browserWindowController respondsToSelector:@selector(activeBrowserViewController)]) - { - // 10.9 - NSObject* browserViewController = [browserWindowController activeBrowserViewController]; - - NSObject* browserView = [browserViewController browserView]; - - if ([browserView isKindOfClass:(id)objc_getClass("TListView")]) - { - // List or Coverflow View - [self setNeedsDisplayForListView:browserView]; - } - else - { - // Icon or Column View - dispatch_async(dispatch_get_main_queue(), ^{[browserView setNeedsDisplay:YES];}); - } - } - else - { - NSLog(@"SyncState: refreshing icon badges failed"); - - return; - } - } - } - } -} - -- (void)setIcons:(NSDictionary*)iconDictionary filterByFolder:(NSString*)filterFolder -{ - NSLog(@"%@", NSStringFromSelector(_cmd)); - for (NSString* path in iconDictionary) - { - if (filterFolder && ![path hasPrefix:filterFolder]) - { - continue; - } - - NSString* normalizedPath = [path decomposedStringWithCanonicalMapping]; - NSNumber* iconId = [iconDictionary objectForKey:path]; - - if ([iconId intValue] == -1) - { - [_fileNamesCache removeObjectForKey:normalizedPath]; - } - else - { - [_oldFileNamesCache removeObjectForKey:normalizedPath]; - [_fileNamesCache setObject:iconId forKey:normalizedPath]; - } - } - - [self repaintAllWindows]; -} - -- (void)setNeedsDisplayForListView:(NSView*)view -{ - NSArray* subviews = [view subviews]; - - for (int i = 0; i < [subviews count]; i++) - { - NSView* subview = [subviews objectAtIndex:i]; - - if ([subview isKindOfClass:(id)objc_getClass("TListRowView")]) - { - [self setNeedsDisplayForListView:subview]; - } - else if ([subview isKindOfClass:(id)objc_getClass("TListNameCellView")]) - { - dispatch_async(dispatch_get_main_queue(), ^{[subview setNeedsDisplay:YES];}); - } - } -} - -@end diff --git a/shell_integration/MacOSX/OwnCloudFinder/ContextMenuHandlers.h b/shell_integration/MacOSX/OwnCloudFinder/ContextMenuHandlers.h deleted file mode 100644 index dc604cd6b0..0000000000 --- a/shell_integration/MacOSX/OwnCloudFinder/ContextMenuHandlers.h +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved. - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 2.1 of the License, or (at your option) - * any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - * details. - */ - -#import - -@interface NSObject (ContextMenuHandlers) - -struct TFENodeVector; - -+ (void)OCContextMenuHandlers_handleContextMenuCommon:(unsigned int)arg1 nodes:(const struct TFENodeVector*)arg2 event:(id)arg3 view:(id)arg4 browserController:(id)arg5 addPlugIns:(BOOL)arg6; -+ (void)OCContextMenuHandlers_handleContextMenuCommon:(unsigned int)arg1 nodes:(const struct TFENodeVector*)arg2 event:(id)arg3 view:(id)arg4 windowController:(id)arg5 addPlugIns:(BOOL)arg6; -+ (void)OCContextMenuHandlers_addViewSpecificStuffToMenu:(id)arg1 browserViewController:(id)arg2 context:(unsigned int)arg3; - -- (void)OCContextMenuHandlers_configureWithNodes:(const struct TFENodeVector*)arg1 browserController:(id)arg2 container:(BOOL)arg3; -- (void)OCContextMenuHandlers_configureWithNodes:(const struct TFENodeVector*)arg1 windowController:(id)arg2 container:(BOOL)arg3; - -@end \ No newline at end of file diff --git a/shell_integration/MacOSX/OwnCloudFinder/ContextMenuHandlers.m b/shell_integration/MacOSX/OwnCloudFinder/ContextMenuHandlers.m deleted file mode 100644 index 4811cf058a..0000000000 --- a/shell_integration/MacOSX/OwnCloudFinder/ContextMenuHandlers.m +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved. - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 2.1 of the License, or (at your option) - * any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - * details. - */ - -#import "ContextMenuHandlers.h" -#import "MenuManager.h" - -@class TIconViewController; - -@implementation NSObject (ContextMenuHandlers) - -+ (void)OCContextMenuHandlers_addViewSpecificStuffToMenu:(id)arg1 browserViewController:(id)arg2 context:(unsigned int)arg3 // 10.7 & 10.8 -{ - [self OCContextMenuHandlers_addViewSpecificStuffToMenu:arg1 browserViewController:arg2 context:arg3]; - NSLog(@"OCContextMenuHandlers_addViewSpecificStuffToMenu 10.7/10.8 %@ %@ %d", arg1, arg2, arg3); - MenuManager* menuManager = [MenuManager sharedInstance]; - if (menuManager.menuItems.count > 0) - { - [menuManager addItemsToMenu:arg1 forFiles:menuManager.menuItems]; - [menuManager.menuItems removeAllObjects]; - } -} - -+ (void)OCContextMenuHandlers_addViewSpecificStuffToMenu:(id)arg1 clickedView:(id)arg2 browserViewController:(id)arg3 context:(unsigned int)arg4 // 10.9 -{ - [self OCContextMenuHandlers_addViewSpecificStuffToMenu:arg1 clickedView:arg2 browserViewController:arg3 context:arg4]; - NSLog(@"OCContextMenuHandlers_addViewSpecificStuffToMenu 10.9 %@ %@ %@ %d", arg1, arg2, arg3, arg4); - MenuManager* menuManager = [MenuManager sharedInstance]; - if (menuManager.menuItems.count > 0) - { - [menuManager addItemsToMenu:arg1 forFiles:menuManager.menuItems]; - [menuManager.menuItems removeAllObjects]; - } -} - -+ (void)OCContextMenuHandlers_handleContextMenuCommon:(unsigned int)arg1 nodes:(const struct TFENodeVector*)arg2 event:(id)arg3 view:(id)arg4 windowController:(id)arg5 addPlugIns:(BOOL)arg6 // 10.7 -{ - MenuManager* menuManager = [MenuManager sharedInstance]; - NSLog(@"ContextMenuHandlers_handleContextMenuCommon"); - menuManager.menuItems = (NSMutableArray*)[menuManager pathsForNodes:arg2]; - [self OCContextMenuHandlers_handleContextMenuCommon:arg1 nodes:arg2 event:arg3 view:arg4 windowController:arg5 addPlugIns:arg6]; -} - -+ (void)OCContextMenuHandlers_handleContextMenuCommon:(unsigned int)arg1 nodes:(const struct TFENodeVector*)arg2 event:(id)arg3 view:(id)arg4 browserController:(id)arg5 addPlugIns:(BOOL)arg6 // 10.8 -{ - MenuManager* menuManager = [MenuManager sharedInstance]; - NSLog(@"ContextMenuHandlers_handleContextMenuCommon"); - menuManager.menuItems = (NSMutableArray*)[menuManager pathsForNodes:arg2]; - [self OCContextMenuHandlers_handleContextMenuCommon:arg1 nodes:arg2 event:arg3 view:arg4 browserController:arg5 addPlugIns:arg6]; -} - -+ (void)OCContextMenuHandlers_handleContextMenuCommon:(unsigned int)arg1 nodes:(const struct TFENodeVector*)arg2 event:(id)arg3 clickedView:(id)arg4 browserViewController:(id)arg5 addPlugIns:(BOOL)arg6 // 10.9 -{ - MenuManager* menuManager = [MenuManager sharedInstance]; - NSLog(@"ContextMenuHandlers_handleContextMenuCommon"); - menuManager.menuItems = (NSMutableArray*)[menuManager pathsForNodes:arg2]; - [self OCContextMenuHandlers_handleContextMenuCommon:arg1 nodes:arg2 event:arg3 clickedView:arg4 browserViewController:arg5 addPlugIns:arg6]; -} - -- (void)OCContextMenuHandlers_configureWithNodes:(const struct TFENodeVector*)arg1 windowController:(id)arg2 container:(BOOL)arg3 // 10.7 -{ - [self OCContextMenuHandlers_configureWithNodes:arg1 windowController:arg2 container:arg3]; - NSLog(@"ContextMenuHandlers_configureWithNodes"); - TContextMenu* realSelf = (TContextMenu*)self; - MenuManager* menuManager = [MenuManager sharedInstance]; - NSArray* selectedItems = [menuManager pathsForNodes:arg1]; - [menuManager addItemsToMenu:realSelf forFiles:selectedItems]; -} - -- (void)OCContextMenuHandlers_configureWithNodes:(const struct TFENodeVector*)arg1 browserController:(id)arg2 container:(BOOL)arg3 // 10.8 -{ - [self OCContextMenuHandlers_configureWithNodes:arg1 browserController:arg2 container:arg3]; - NSLog(@"ContextMenuHandlers_configureWithNodes"); - TContextMenu* realSelf = (TContextMenu*)self; - MenuManager* menuManager = [MenuManager sharedInstance]; - - NSArray* selectedItems = [menuManager pathsForNodes:arg1]; - [menuManager addItemsToMenu:realSelf forFiles:selectedItems]; -} - -- (void)OCContextMenuHandlers_configureFromMenuNeedsUpdate:(id)arg1 clickedView:(id)arg2 container:(BOOL)arg3 event:(id)arg4 selectedNodes:(const struct TFENodeVector *)arg5 // 10.9 -{ - [self OCContextMenuHandlers_configureFromMenuNeedsUpdate:arg1 clickedView:arg2 container:arg3 event:arg4 selectedNodes:arg5]; // 10.8 - NSLog(@"ContextMenuHandlers_configureFromMenuNeedsUpdate"); - TContextMenu* realSelf = (TContextMenu*)self; - MenuManager* menuManager = [MenuManager sharedInstance]; - NSArray* selectedItems = [menuManager pathsForNodes:arg5]; - [menuManager addItemsToMenu:realSelf forFiles:selectedItems]; -} - -@end \ No newline at end of file diff --git a/shell_integration/MacOSX/OwnCloudFinder/ContextMenuHandlers.m.unc b/shell_integration/MacOSX/OwnCloudFinder/ContextMenuHandlers.m.unc deleted file mode 100644 index f060b1555d..0000000000 --- a/shell_integration/MacOSX/OwnCloudFinder/ContextMenuHandlers.m.unc +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved. - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 2.1 of the License, or (at your option) - * any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - * details. - */ - -#import "ContextMenuHandlers.h" -#import "Finder/Finder.h" -#import "MenuManager.h" - -@implementation NSObject (ContextMenuHandlers) - -+ (void) ContextMenuHandlers_handleContextMenuCommon:(unsigned int)arg1 nodes:(const struct TFENodeVector*)arg2 event:(id)arg3 view:(id)arg4 windowController:(id)arg5 addPlugIns:(BOOL)arg6 // Lion -{ - MenuManager* contextMenuUtils = [MenuManager sharedInstance]; - - contextMenuUtils.menuItems = (NSMutableArray*)[contextMenuUtils pathsForNodes:arg2]; - - [self ContextMenuHandlers_handleContextMenuCommon:arg1 nodes:arg2 event:arg3 view:arg4 windowController:arg5 addPlugIns:arg6]; -} - -+ (void) ContextMenuHandlers_handleContextMenuCommon:(unsigned int)arg1 nodes:(const struct TFENodeVector*)arg2 event:(id)arg3 view:(id)arg4 browserController:(id)arg5 addPlugIns:(BOOL)arg6 // Mountain Lion -{ - MenuManager* contextMenuUtils = [MenuManager sharedInstance]; - - contextMenuUtils.menuItems = (NSMutableArray*)[contextMenuUtils pathsForNodes:arg2]; - - [self ContextMenuHandlers_handleContextMenuCommon:arg1 nodes:arg2 event:arg3 view:arg4 browserController:arg5 addPlugIns:arg6]; -} - -+ (void) ContextMenuHandlers_addViewSpecificStuffToMenu:(id)arg1 browserViewController:(id)arg2 context:(unsigned int)arg3 -{ - [self ContextMenuHandlers_addViewSpecificStuffToMenu:arg1 browserViewController:arg2 context:arg3]; - - if ([MenuManager sharedInstance].menuItems.count > 0) - { - MenuManager* contextMenuUtils = [MenuManager sharedInstance]; - [contextMenuUtils addItemsToMenu:arg1 forPaths:contextMenuUtils.menuItems]; - [contextMenuUtils.menuItems removeAllObjects]; - } -} - -- (void) ContextMenuHandlers_configureWithNodes:(const struct TFENodeVector*)arg1 windowController:(id)arg2 container:(BOOL)arg3 // Lion -{ - [self ContextMenuHandlers_configureWithNodes:arg1 windowController:arg2 container:arg3]; - - TContextMenu* realSelf = (TContextMenu*)self; - MenuManager* contextMenuUtils = [MenuManager sharedInstance]; - - NSArray* selectedItems = [contextMenuUtils pathsForNodes:arg1]; - [contextMenuUtils addItemsToMenu:realSelf forPaths:selectedItems]; -} - -- (void) ContextMenuHandlers_configureWithNodes:(const struct TFENodeVector*)arg1 browserController:(id)arg2 container:(BOOL)arg3 // Mountain Lion -{ - [self ContextMenuHandlers_configureWithNodes:arg1 browserController:arg2 container:arg3]; - - TContextMenu* realSelf = (TContextMenu*)self; - MenuManager* contextMenuUtils = [MenuManager sharedInstance]; - - NSArray* selectedItems = [contextMenuUtils pathsForNodes:arg1]; - [contextMenuUtils addItemsToMenu:realSelf forPaths:selectedItems]; -} - - -@end \ No newline at end of file diff --git a/shell_integration/MacOSX/OwnCloudFinder/English.lproj/InfoPlist.strings b/shell_integration/MacOSX/OwnCloudFinder/English.lproj/InfoPlist.strings deleted file mode 100644 index 88f65cf6ea..0000000000 --- a/shell_integration/MacOSX/OwnCloudFinder/English.lproj/InfoPlist.strings +++ /dev/null @@ -1,2 +0,0 @@ -/* Localized versions of Info.plist keys */ - diff --git a/shell_integration/MacOSX/OwnCloudFinder/Finder/Finder.h b/shell_integration/MacOSX/OwnCloudFinder/Finder/Finder.h deleted file mode 100644 index 8322f7b2c5..0000000000 --- a/shell_integration/MacOSX/OwnCloudFinder/Finder/Finder.h +++ /dev/null @@ -1,1016 +0,0 @@ -// -// Finder.h -// -// Created by snow on 9/30/10. -// Copyright 2010 Canvastudio Les Nie. All rights reserved. -// - -#import -#import - -struct _NSPoint { - float x; - float y; -}; - -struct _NSSize { - float width; - float height; -}; - -struct _NSRect { - struct _NSPoint origin; - struct _NSSize size; -}; - -typedef struct { - struct TFENode *_M_start; - struct TFENode *_M_finish; - struct TFENode *_M_end_of_storage; -} _Vector_impl_6bc0f568; - -struct TFENodeVector { - _Vector_impl_6bc0f568 _M_impl; -}; - -@class TListViewController, TTableViewShrinkToFitController; - -@interface TListView : NSOutlineView -{ - TListViewController *_controller; - BOOL _itemHitOnMouseDown; - TTableViewShrinkToFitController *_stfController; -} - -- (void)dealloc; -- (void)setDelegate:(id)arg1; -- (BOOL)shouldDelayWindowOrderingForEvent:(id)arg1; -- (BOOL)acceptsFirstResponder; -- (id)columnWithStringIdentifier:(id)arg1; -- (struct CGRect)_dropHighlightBackgroundRectForRow:(long long)arg1; -- (void)drawRow:(long long)arg1 clipRect:(struct CGRect)arg2; -- (BOOL)clickedOnMoreButton:(id)arg1; -- (BOOL)handleUnicodeTextInput:(id)arg1; -- (BOOL)acceptsFirstMouse:(id)arg1; -- (unsigned long long)hitTestForEvent:(id)arg1 row:(long long)arg2; -- (id)menuForEvent:(id)arg1; -- (BOOL)_onlyDragOnContent; -- (BOOL)commonMouseDownAndEarlyReturn:(id)arg1 controller:(id)arg2; -- (void)commonPostMouseDown:(id)arg1 controller:(id)arg2; -- (BOOL)_typeSelectInterpretKeyEvent:(id)arg1; -- (void)mouseDown:(id)arg1; -- (void)drawRect:(struct CGRect)arg1; -- (BOOL)_wantsLiveResizeToUseCachedImage; -- (id)inputContext; -- (void)keyDown:(id)arg1; -- (void)expandItem:(id)arg1 expandChildren:(BOOL)arg2; -- (void)collapseItem:(id)arg1 collapseChildren:(BOOL)arg2; -- (void)selectRowIndexes:(id)arg1 byExtendingSelection:(BOOL)arg2; -- (void)editColumn:(long long)arg1 row:(long long)arg2 withEvent:(id)arg3 select:(BOOL)arg4; -- (id)preparedCellAtColumn:(long long)arg1 row:(long long)arg2; -- (void)startEditingWithNode:(const struct TFENode *)arg1; -- (void)stopEditing:(BOOL)arg1; -- (struct CGRect)maxSTFEditorFrameFromTitleFrame:(struct CGRect)arg1; -- (void)updateSTFEditorLocation; -- (BOOL)shrinkToFitTextViewAboutToOpen; -- (void)shrinkToFitTextViewEditingComplete:(id)arg1; -- (void)shrinkToFitTextViewAboutToClose; -- (id)stfEditorController; -@property(readonly, nonatomic) TListViewController *controller; // @synthesize controller=_controller; - -@end - -typedef struct { - unsigned int selected:1; - unsigned int focus:1; - unsigned int twoLines:1; - unsigned int label:3; -} CDStruct_b8373011; -typedef struct { - struct CGRect _field1; - unsigned long long _field2; - struct CGRect _field3[2]; - unsigned long long _field4; -} CDStruct_51b97681; - -@protocol IKImageProxy -- (void)bind; -- (void)unbind; -- (BOOL)isBinded; -- (int)proxyDataFormat; -- (id)proxyData; -- (void)disconnect; -- (void)connect:(id)fp8; -- (id)image; -- (id)thumbnailWithSize:(struct _NSSize)fp8 antialiased:(BOOL)fp16 qualityRequested:(int)fp20 qualityProduced:(int *)fp24; -- (BOOL)isVectorial; -- (struct _NSSize)proxySize; -- (void)lockForThreadedOperation; -- (void)unlockForThreadedOperation; -- (BOOL)isLockedForThreadedOperation; -@end - - -@interface IKImageWrapper : NSObject -{ - NSString *_path; - NSData *_dataRepresentation; - NSBitmapImageRep *_bitmapRepresentation; - id _imageProxy; - union { - struct CGImage *_cgImage; - CIImage *_ciImage; - struct CGImageSource *_cgImageSource; - NSImage *_nsImage; - } _volatileRep; - unsigned short _volatileRepresentation; - unsigned int _exifOrientation:3; - unsigned int _generatedWithIconServices:1; - unsigned int _underlyingDataAreVolatile:1; - unsigned int _isReference:1; - struct _NSSize _cachedSize; - NSDictionary *_info; -} - -+ (id)imageWithPath:(id)fp8; -+ (id)imageWithNSImage:(id)fp8; -+ (id)imageWithCGImage:(struct CGImage *)fp8; -+ (id)imageWithCGImageSource:(struct CGImageSource *)fp8; -+ (id)imageWithData:(id)fp8; -+ (id)imageWithPasteboard:(id)fp8; -+ (id)imageWithNSBitmapImageRep:(id)fp8; -+ (id)emptyImage; -+ (id)imageWithSize:(struct _NSSize)fp8; -+ (id)imageWithImageProxy:(id)fp8; -+ (id)imageWithObject:(id)fp8; -- (void)dealloc; -- (void)finalize; -- (id)initWithPath:(id)fp8; -- (id)initWithCGImage:(struct CGImage *)fp8; -- (id)initWithCGImageSource:(struct CGImageSource *)fp8; -- (id)initWithNSImage:(id)fp8; -- (id)initEmptyImage; -- (id)initWithPasteboard:(id)fp8; -- (id)initWithSize:(struct _NSSize)fp8; -- (id)initWithData:(id)fp8; -- (id)initWithNSBitmapImageRep:(id)fp8; -- (id)initWithImageProxy:(id)fp8; -- (id)initWithOpenGLID:(unsigned int)fp8 size:(struct _NSSize)fp12 offset:(struct _NSPoint)fp20 premultiplied:(BOOL)fp28 deleteWhenDone:(BOOL)fp32; -- (int)volatileRepresentation; -- (void)setVolatileRepresentation:(int)fp8; -- (void)releaseVolatileImageRep; -- (unsigned short)flags; -- (void)setFlags:(unsigned short)fp8; -- (BOOL)wasGeneratedWithIconServices; -- (void)setWasGeneratedWithIconServices:(BOOL)fp8; -- (BOOL)underlyingDataAreVolatile; -- (void)setUnderlyingDataAreVolatile:(BOOL)fp8; -- (struct CGImage *)_cgImage; -- (id)_nsImage; -- (struct CGImage *)cgImage; -- (id)nsImage:(BOOL)fp8; -- (id)nsImage; -- (struct CGImageSource *)cgImageSourceRef:(BOOL)fp8; -- (void)setCGImageSource:(struct CGImageSource *)fp8; -- (void)setCGImage:(struct CGImage *)fp8; -- (void)setNSImage:(id)fp8; -- (id)copy; -- (void)setIsReference:(BOOL)fp8; -- (void)integrateReferenceInstance:(id)fp8; -- (void)referenceWillDie; -- (id)referenceInstance; -- (id)_thumbnailWithSize:(struct _NSSize)fp8 antialiased:(BOOL)fp16 qualityRequested:(int)fp20 qualityProduced:(int *)fp24; -- (id)thumbnailWithSize:(struct _NSSize)fp8 antialiased:(BOOL)fp16 qualityRequested:(int)fp20 qualityProduced:(int *)fp24; -- (struct _NSSize)cachedSize; -- (id)_sizeOfNSImage:(id)fp8; -- (struct _NSSize)_size; -- (struct _NSSize)size; -- (void)setSize:(struct _NSSize)fp8; -- (void)setSizeWithoutSavingContent:(struct _NSSize)fp8; -- (BOOL)isVectorial; -- (BOOL)isValid; -- (BOOL)isEmpty; -- (BOOL)hasAlpha; -- (id)animatedGifsCache; -- (BOOL)isAnimatedGifs; -- (int)imageFrameCount; -- (int)loopCount; -- (float)nextFrameDelayAtIndex:(int)fp8; -- (struct CGImage *)imageAtFrameIndex:(int)fp8; -- (id)GIFRepresentation; -- (id)TIFFRepresentation; -- (id)TIFFRepresentationUsingCompression:(unsigned int)fp8 factor:(float)fp12; -- (id)IK_JPEGRepresentationWithCompressionFactor:(float)fp8; -- (id)imagePath; -- (id)_dataRepresentationFromBitmapRepresentation:(id)fp8; -- (id)_createBitmapImageRepFromCGRepresentation; -- (id)dataRepresentationFromCGRepresentationWithCompressionFactor:(float)fp8; -- (id)dataRepresentation; -- (id)imageProxy; -- (void)setImageProxy:(id)fp8; -- (void)setPath:(id)fp8; -- (void)setDataRepresentation:(id)fp8; -- (void)drawInRect:(NSRect)fp8 fromRect:(NSRect)fp24 alpha:(float)fp40; -- (void)lockFocus; -- (void)unlockFocus; -- (void)saveAsTIFFAtPath:(id)fp8; -- (void)saveAsJPGAtPath:(id)fp8; -- (id)writeToFileWithAutomaticFormat:(id)fp8; -- (BOOL)hasDataRepresentation; -- (BOOL)hasBitmapRepresentation; -- (id)bitmapRepresentation; -- (void)setBitmapRepresentation:(id)fp8; -- (struct CGContext *)cgContext; -- (void)mapIntoVRAM; -- (void)mapRepresentationIntoRAM:(int)fp8; -- (BOOL)mappedIntoRAM; -- (BOOL)mappedAndDecompressedIntoRAM; -- (BOOL)mappedIntoVRAM; -- (void)freeImageCache; -- (void)bindCGCache; -- (BOOL)hasCGCache; -- (BOOL)hasVolatileCache; -- (BOOL)hasRAMCache; -- (void)freeRAMCache; -- (void)freeVRAMCache; -- (void)freeCache; -- (BOOL)textureIsPacked; -- (unsigned int)openGLTextureID; -- (void)deleteTextureInCurrentContext; -- (void)setOpenGLTextureID:(unsigned int)fp8 withGLContext:(id)fp12; -- (unsigned int)generateNewGLTextureID; -- (struct _NSPoint)openGLTextureOffset; -- (void)setOpenGLTextureOffset:(struct _NSPoint)fp8; -- (BOOL)openGLTextureIsPremultiplied; -- (void)setOpenGLTextureIsPremultiplied:(BOOL)fp8; -- (void)setValue:(id)fp8 forKey:(id)fp12; -- (id)valueForKey:(id)fp8; -- (id)_tryToCreateCGImageRepFromNonCGFile:(id)fp8; -- (id)description; - -@end - - -@class IKMipmapItem; - -@interface IKMipmapImage : NSObject -{ - IKMipmapItem *_mipmaps[4]; - IKMipmapItem *_originalMipmap; - IKMipmapItem *_customMipmap; - unsigned long _version; - struct _NSSize _originalImageSizeCache; - unsigned int _originalImageIsInvalid:1; - unsigned int _dirty:1; - unsigned int _mark:1; - unsigned int _isReference:1; -} - -- (void)_mipmapCommonInit; -- (id)init; -- (id)initWithMipmapSizes:(id)fp8 VMUsagePolicy:(id)fp12; -- (void)_cleanUp; -- (void)dealloc; -- (void)finalize; -- (BOOL)isDirty; -- (void)setDirty:(BOOL)fp8; -- (void)setIsReference:(BOOL)fp8; -- (void)setOriginalImageIsInvalid:(BOOL)fp8; -- (unsigned long)version; -- (void)setVersion:(unsigned long)fp8; -- (BOOL)marked; -- (void)mark; -- (void)clearMark; -- (id)temporaryItem; -- (id)originalItem; -- (id)mipmapItemAtIndex:(int)fp8; -- (int)indexOfMipmapItem:(id)fp8; -- (int)highestMipmapItemIndex; -- (id)highestMipmapItem; -- (float)originalAspectRatio; -- (BOOL)originalImageIsInvalid; -- (void)checkAndMarkMipmapAsInvalid; -- (void)invalidateOriginalImageSizeCache; -- (struct _NSSize)originalImageSize; -- (void)setOriginalImageSizeCache:(struct _NSSize)fp8; -- (struct _NSSize)originalImageSizeCache; -- (id)image; -- (void)setImage:(id)fp8; -- (void)setImageWithoutInvalidate:(id)fp8; -- (id *)mipmapItems; -- (id)validMipmapItems; -- (BOOL)atLeastOneMipmapItemIsValid; -- (BOOL)allMipmapItemsAreValid; -- (BOOL)customMipmapIsValidAndMatchSize:(struct _NSSize)fp8 andQuality:(int)fp16; -- (BOOL)validateMipmap:(id)fp8 withModel:(id)fp12 withQuality:(int)fp16; -- (BOOL)validateMipmap:(id)fp8 withQuality:(int)fp12; -- (BOOL)validateMipmapAtIndex:(int)fp8 withQuality:(int)fp12; -- (int)bestMipmapIndexToValidateForSize:(int)fp8; -- (id)mipmapWithSize:(int)fp8; -- (void)setImage:(id)fp8 forMipmapSize:(int)fp12; -- (id)fastMipmapItemForSize:(int)fp8 forOpenGL:(BOOL)fp12 useMinimumQualityThreshold:(BOOL)fp16; -- (id)_fastMipmapItemForSize:(struct _NSSize)fp8 forOpenGL:(BOOL)fp16 useMinimumQualityThreshold:(BOOL)fp20; -- (id)fastMipmapItemForSize:(struct _NSSize)fp8 forGLRendering:(BOOL)fp16; -- (id)fastestMipmapItemForSize:(struct _NSSize)fp8 forGLRendering:(BOOL)fp16; -- (void)_cacheMipmapSize:(struct _NSSize)fp8 fromModel:(id)fp16; -- (id)nicestMipmapItemForSize:(struct _NSSize)fp8 forGLRendering:(BOOL)fp16 cacheIt:(BOOL)fp20; -- (BOOL)shouldUseOriginalImageToCacheNiceImageWithSize:(struct _NSSize)fp8; -- (id)niceMipmapItemForSize:(struct _NSSize)fp8 forGLRendering:(BOOL)fp16 cacheIt:(BOOL)fp20; -- (id)fastMipmapItemWithExactSize:(struct _NSSize)fp8; -- (id)nicestImageForSize:(struct _NSSize)fp8 forGLRendering:(BOOL)fp16 cacheIt:(BOOL)fp20; -- (id)niceImageForSize:(struct _NSSize)fp8 forGLRendering:(BOOL)fp16 cacheIt:(BOOL)fp20; -- (id)fastImageForSize:(struct _NSSize)fp8 forGLRendering:(BOOL)fp16; -- (id)fastestImageForSize:(struct _NSSize)fp8 forGLRendering:(BOOL)fp16; -- (id)lockMipmapAtIndex:(int)fp8; -- (void)unlockMipmapItem:(id)fp8; -- (BOOL)preloadMipmapsWithQuality:(int)fp8; -- (BOOL)containsMipmapItem:(id)fp8; -- (void)freeAllCaches; -- (void)freeTemporaryCache; -- (void)freeExpendedRepresentationCaches; -- (void)freeOriginalImageCache; -- (void)invalidateMipMaps; -- (void)setMipmapSizes:(id)fp8; -- (void)setMipmapVMUsagePolicy:(id)fp8; -- (id)referenceInstance; -- (void)integrateReferenceInstance:(id)fp8; -- (void)referenceWillDie; - -@end - - -@interface IKMipmapItem : NSObject -{ - IKMipmapImage *_parent; - IKImageWrapper *_image; - int _mipmapSize; - unsigned int _vmUsagePolicy:8; - unsigned int _thumbnailQuality:8; - unsigned int _isReference:1; -} - -- (id)init; -- (void)dealloc; -- (id)description; -- (id)parent; -- (void)setParent:(id)fp8; -- (BOOL)loaded; -- (void)mapIntoVRAM; -- (BOOL)unload; -- (BOOL)isValid; -- (int)thumbnailQuality; -- (void)setThumbnailQuality:(int)fp8; -- (id)__image; -- (id)image; -- (void)setImage:(id)fp8; -- (void)setMipmapSize:(int)fp8; -- (void)invalidate; -- (int)mipmapSize; -- (void)__setDictionaryRepresentation:(id)fp8; -- (BOOL)setAsMipmapOfImage:(id)fp8 withSize:(struct _NSSize)fp12 antialiased:(BOOL)fp20 quality:(int)fp24; -- (BOOL)setAsMipmapOfImage:(id)fp8 aspectRatio:(float)fp12 antialiased:(BOOL)fp16 quality:(int)fp20; -- (int)vmUsagePolicy; -- (void)setVmUsagePolicy:(int)fp8; -- (void)setIsReference:(BOOL)fp8; -- (void)setAsReferenceOf:(id)fp8; -- (void)integrateReferenceInstance:(id)fp8 replaceImage:(BOOL)fp12; -- (void)referenceWillDie; - -@end - - -@interface IKImageCell : NSObject -{ - id _parent; - id _cellSource; - id _proxy; - unsigned int _dataSourceIndex; - unsigned int _mipmapDBIndex; - IKMipmapImage *_mipmapImage; - unsigned int _datasourceIsVectorial:1; - float _alpha; - NSMutableDictionary *_properties; -} - -+ (id)_IKBuildImageWrapperForType:(id)fp8 withObject:(id)fp12 withOwner:(id)fp16; -- (id)init; -- (struct _NSRect)imageFrame; -- (void)invalidate; -- (void)validate; -- (void)mipmapImageChanged; -- (void)validateMipmapDBIndex; -- (id)mipmapDB; -- (unsigned int)mipmapDBIndex; -- (void)setCacheDBIndex:(unsigned int)fp8; -- (void)parentWillDie:(id)fp8; -- (void)dealloc; -- (void)finalize; -- (void)setParent:(id)fp8; -- (id)parent; -- (void)setDataSource:(id)fp8; -- (id)dataSource; -- (unsigned int)dataSourceIndex; -- (void)setDataSourceIndex:(unsigned int)fp8; -- (id)mipmapImage; -- (void)setMipmapImage:(id)fp8; -- (float)alpha; -- (void)setAlpha:(float)fp8; -- (BOOL)isAnIcon; -- (BOOL)_representationTypeCanBePlayed:(id)fp8; -- (void)removeObjectForKey:(id)fp8; -- (void)setObject:(id)fp8 forKey:(id)fp12; -- (id)objectForKey:(id)fp8; -- (void)checkMipmapVersion; -- (id)dataSourcePath; - -@end - -//@class IKImageWrapper; - -@interface TIconViewCell : IKImageBrowserCell -{ - IKImageWrapper *_titleImage; - BOOL _twoLine; - CDStruct_b8373011 _titleSettings; -} - -+ (struct CGSize)cellSizeForIconSize:(double)arg1 labelOnBottom:(BOOL)arg2 gridSpacing:(double)arg3 titleAttrs:(id)arg4 subTitleAttrs:(id)arg5; -- (id)init; -- (void)dealloc; -- (double)iconSize; -- (BOOL)labelOnBottom; -- (BOOL)showPreview; -- (BOOL)showItemInfo; -- (double)endCapWidth; -- (struct CGRect)frame; -- (struct CGRect)imageFrame; -- (double)titleOffset; -- (double)maxTitleWidth; -- (struct CGRect)titleFrame; -- (struct CGRect)subtitleFrame; -- (int)heightOfInfoSpace; -- (id)subString:(id)arg1 atIndex:(unsigned long long)arg2 attributes:(id)arg3 lineBreakMode:(unsigned long long)arg4; -- (struct CGRect)placeSubString:(id)arg1 atIndex:(unsigned long long)arg2 fromFrame:(struct CGRect)arg3 bounds:(struct CGRect)arg4 attributes:(id)arg5 lineBreakMode:(unsigned long long)arg6 position:(BOOL)arg7; -- (CDStruct_51b97681)calculateTextMetrics:(id)arg1 attributes:(id)arg2; -- (void)drawLabel:(struct CGContext *)arg1 fillRect:(struct CGRect)arg2 bounds:(struct CGRect)arg3 firstLine:(struct CGRect)arg4 secondLine:(struct CGRect)arg5 actualLineCount:(unsigned long long)arg6 selected:(BOOL)arg7 labelValue:(short)arg8 justification:(short)arg9 inset:(double)arg10 radius:(double)arg11; -- (id)constructTitleImage; -- (CDStruct_b8373011)currentTitleImageSettings; -- (BOOL)titleImageStillValid:(CDStruct_b8373011)arg1; -- (id)titleImage; -- (void)invalidate; -- (void)drawTitle; - -@end - -@protocol TShrinkToFitDelegateProtocol -- (BOOL)shrinkToFitTextViewAboutToOpen; -- (void)shrinkToFitTextViewEditingComplete:(id)arg1; -- (void)shrinkToFitTextViewAboutToClose; -@end - -@class TCocoaShrinkToFitController, TIconViewController; - -@interface TIconView : IKImageBrowserView -{ - TIconViewController *_controller; - TCocoaShrinkToFitController *_stfController; - BOOL _startEditingOnMouseUp; - BOOL _viewIsReloadingData; - BOOL _isDrawingInDragImage; - unsigned long long _editedCellIndex; - unsigned long long _selectionCountBeforeReloadingData; -} - -- (id)initWithFrame:(struct CGRect)arg1 controller:(id)arg2; -- (void)setDelegate:(id)arg1; -- (id)_viewIdentifier; -- (void)viewDidMoveToWindow; -- (BOOL)shouldPreserveVisibleRangeWhileZooming; -- (void)reloadData; -- (BOOL)isReloadingData; -- (unsigned long long)selectionCountBeforeReloadingData; -- (BOOL)respondsToSelector:(SEL)arg1; -- (void)draggingExited:(id)arg1; -- (void)dragImage:(id)arg1 at:(struct CGPoint)arg2 offset:(struct CGSize)arg3 event:(id)arg4 pasteboard:(id)arg5 source:(id)arg6 slideBack:(BOOL)arg7; -- (void)scrollSTFEditorIntoView; -- (void)updateSTFEditorLocation; -- (void)scrollWheel:(id)arg1; -- (void)browserDidScroll; -- (void)mouseDown:(id)arg1; -- (void)drawRect:(struct CGRect)arg1; -- (void)rightMouseDown:(id)arg1; -- (void)mouseDragged:(id)arg1; -- (void)mouseUp:(id)arg1; -- (BOOL)_typeSelectInterpretKeyEvent:(id)arg1; -- (id)inputContext; -- (void)keyDown:(id)arg1; -- (int)defaultHeightOfInfoSpaceWithCurrentViewOptions; -- (BOOL)isDragImageOpaque; -- (int)nextIndexInGridLayoutWithDirectionKey:(unsigned short)arg1 currentIndex:(long long)arg2; -- (void)startEditingWithNode:(const struct TFENode *)arg1 afterDelay:(BOOL)arg2; -- (void)stopEditing:(BOOL)arg1; -- (unsigned long long)editingIndex; -- (struct CGRect)maxSTFEditorFrameForCellAtIndex:(unsigned long long)arg1; -- (BOOL)editCellTitleAtIndex:(unsigned long long)arg1 withEvent:(id)arg2 select:(BOOL)arg3; -- (BOOL)shrinkToFitTextViewAboutToOpen; -- (void)shrinkToFitTextViewEditingComplete:(id)arg1; -- (void)shrinkToFitTextViewAboutToClose; -- (BOOL)hasFocus; -- (id)draggedImageWithEvent:(id)arg1 countBadge:(int)arg2 hotPoint:(struct CGPoint *)arg3; -@property(readonly, retain, nonatomic) TIconViewController *controller; // @synthesize controller=_controller; - -@end - -@class TPropertyIconController; - -@interface TIconImageView : NSImageView -{ - TPropertyIconController *_controller; -} - -- (id)initWithFrame:(struct CGRect)arg1; -- (id)initWithCoder:(id)arg1; -- (void)initCommon; -- (void)drawRect:(struct CGRect)arg1; -- (void)setImage:(id)arg1; -- (BOOL)canChangeIcon; -- (BOOL)validateCopy:(id)arg1; -- (void)copy:(id)arg1; -- (BOOL)validateCut:(id)arg1; -- (void)cut:(id)arg1; -- (BOOL)validateDelete:(id)arg1; -- (void)delete:(id)arg1; -- (BOOL)validatePaste:(id)arg1; -- (void)paste:(id)arg1; -- (BOOL)validateUndo:(id)arg1; -- (void)undo:(id)arg1; -- (BOOL)validateRedo:(id)arg1; -- (void)redo:(id)arg1; -- (BOOL)validateMenuItem:(id)arg1; -- (unsigned long long)draggingEntered:(id)arg1; -- (void)concludeDragOperation:(id)arg1; -- (void)draggingEnded:(id)arg1; -@property TPropertyIconController *controller; //@synthesize controller=_controller; - -@end - -@interface FINode : NSObject -{ - -} - -+ (id)nodeWithFENode:(const struct TFENode *)arg1; -+ (struct TFENode)asFENode:(id)arg1; -- (struct TFENode)feNode; -- (struct TFENode)feNodeFollowingAliasChainSynchronously; -- (struct TFENode)feNodeFollowingAliasChainAsyncWithTarget:(id)arg1 okToLogin:(BOOL)arg2 tryToFixIfBroken:(BOOL)arg3; -- (BOOL)nodeIs:(unsigned long long)arg1; -- (id)name; -- (id)fullPath; -- (id)kind; -- (id)kindWithoutPlatform; -- (id)copyMDAttribute:(struct __CFString *)arg1; -- (id)typeIdentifier; -- (short)labelValue; -- (id)icon; -- (BOOL)isDimmed; -- (id)modificationDate; -- (id)creationDate; -- (id)lastOpenedDate; -- (long long)fileSize; -- (id)size:(BOOL)arg1; -- (id)label; -- (id)version; -- (id)comments; -- (id)authorName; -- (id)serverUserName; -- (BOOL)supportsScreenSharing; -- (BOOL)supportsFileSharing; -- (int)serverConnectionState; -- (BOOL)isSharedServer; -- (BOOL)isODSNode; -- (BOOL)isMountedSharePoint; -- (BOOL)isIDiskNode; -- (BOOL)isVolume; -- (BOOL)volumeIsEjectableOrUnmountable; -- (void)connectToSharedServerAs; -- (void)askToUseODS; -- (void)disconnectShare; -- (void)launchScreenSharingApp; -- (id)url; -- (long long)fileSizeSync; -- (BOOL)isExtensionHidden; -- (BOOL)containsLocalizations; -- (BOOL)containsPlugins; -- (BOOL)isAlias; -- (BOOL)isMDQueryHit; -- (BOOL)isResolved; -- (BOOL)isApplication; -- (BOOL)isContainer; -- (BOOL)isPackage; -- (BOOL)isVirtual; -- (BOOL)isQueryHit; -- (unsigned long long)nodeIs64:(unsigned long long)arg1; - -@end - - -@interface TViewController : NSViewController -{ -} - -- (id)initWithCoder:(id)arg1; -- (id)initWithNibName:(id)arg1 bundle:(id)arg2; -- (void)initCommon; -- (void)loadView; - -@end - -@class IPropertyValueExtractor, NSObject, TLayoutBinder; - -@interface IPropertyValueController : TViewController -{ - NSObject *_value; - TLayoutBinder *_layoutBinder; - double _viewHeight; - IPropertyValueExtractor *_valueExtractor; - BOOL _shouldBeVisible; - BOOL _shouldBeEnabled; -} - -+ (id)propertyValueController; -+ (id)propertyValueControllerWithValueExtractor:(id)arg1; -- (id)initWithValueExtractor:(id)arg1; -- (void)initCommon; -- (void)dealloc; -- (id)defaultValue; -- (void)setView:(id)arg1; -@property(retain) IPropertyValueExtractor *valueExtractor; // @synthesize valueExtractor=_valueExtractor; -- (void)updateWithNodes:(const struct TFENodeVector *)arg1; -@property BOOL shouldBeVisible; // @synthesize shouldBeVisible=_shouldBeVisible; -- (id)extractValueFromNodes:(const struct TFENodeVector *)arg1; -- (BOOL)needsUpdateForProperty:(unsigned int)arg1; -- (BOOL)isApplicableToNodes:(const struct TFENodeVector *)arg1; -- (void)flush; -- (BOOL)canModifyNodes:(const struct TFENodeVector *)arg1; -- (BOOL)adjustSize:(BOOL)arg1; -- (void)handleNodesGoingAway:(const struct TFENodeVector *)arg1; -- (void)handleNodeMDAttributesChanged:(const struct TFENode *)arg1 attributes:(id)arg2 isDisplayAttributes:(BOOL)arg3; -@property BOOL shouldBeEnabled; // @synthesize shouldBeEnabled=_shouldBeEnabled; -@property(readonly, retain) TLayoutBinder *layoutBinder; // @synthesize layoutBinder=_layoutBinder; -@property(retain) NSObject *value; // @synthesize value=_value; - -@end - - -@interface TPropertyImageViewController : IPropertyValueController -{ -} - -@end - - - -@interface TPropertyIconController : TPropertyImageViewController -{ - struct TFENodeVector _nodes; - BOOL _nodesHaveSameIcon; - BOOL _nodesHaveCustomIcon; - BOOL _nodesCanChangeIcon; -} - -- (void)initCommon; -- (void)updateWithNodes:(const struct TFENodeVector *)arg1; -- (BOOL)canModifyNodes:(const struct TFENodeVector *)arg1; -- (BOOL)validateCopy:(id)arg1; -- (void)copy:(id)arg1; -- (BOOL)validateCut:(id)arg1; -- (void)cut:(id)arg1; -- (BOOL)validateDelete:(id)arg1; -- (void)delete:(id)arg1; -- (BOOL)validatePaste:(id)arg1; -- (void)paste:(id)arg1; -- (void)concludeDragOperation:(id)arg1; - -@end - -struct TFENode { - struct OpaqueNodeRef *fNodeRef; -}; - - -@class TViewOptionsWindowController; - -@interface TFileBasedImageView : NSImageView -{ - TViewOptionsWindowController *_controller; - struct TFENode _imageNode; -} - -@property struct TFENode *imageNode; // @dynamic imageNode; -- (void)mouseDown:(id)arg1; -- (BOOL)performDragOperation:(id)arg1; - -@end - -@interface TTextCell : NSTextFieldCell -{ - double _leftMargin; - double _rightMargin; - BOOL _drawGrayTextWhenDisabled; -} - -- (id)init; -- (id)initTextCell:(id)arg1; -- (id)initWithCoder:(id)arg1; -- (void)initializeTextCell; -- (struct CGSize)cellSizeForBounds:(struct CGRect)arg1; -- (struct CGRect)titleRectForBounds:(struct CGRect)arg1; -- (void)drawInteriorWithFrame:(struct CGRect)arg1 inView:(id)arg2; -- (void)drawWithExpansionFrame:(struct CGRect)arg1 inView:(id)arg2; -- (unsigned long long)hitTestForEvent:(id)arg1 inRect:(struct CGRect)arg2 ofView:(id)arg3; -@property BOOL drawGrayTextWhenDisabled; // @synthesize drawGrayTextWhenDisabled=_drawGrayTextWhenDisabled; -@property double rightMargin; // @synthesize rightMargin=_rightMargin; -@property double leftMargin; // @synthesize leftMargin=_leftMargin; - -@end - -struct TIconRef { - //struct TRef fIconRef; -}; - -@interface TIconAndTextCell : TTextCell -{ - struct TIconRef _icon; - struct CGSize _iconSize; - double _iconToTextSpacing; - BOOL _showIcon; -} - -- (void)initializeTextCell; -- (id)copyWithZone:(struct _NSZone *)arg1; -- (void)setIcon:(const struct TIconRef *)arg1; -@property(readonly) struct TIconRef *icon; -- (struct CGRect)titleRectForBounds:(struct CGRect)arg1; -- (struct CGRect)imageRectForBounds:(struct CGRect)arg1; -- (struct CGSize)cellSizeForBounds:(struct CGRect)arg1; -- (void)drawIconWithFrame:(struct CGRect)arg1; -- (void)drawInteriorWithFrame:(struct CGRect)arg1 inView:(id)arg2; -- (unsigned long long)hitTestForEvent:(id)arg1 inRect:(struct CGRect)arg2 ofView:(id)arg3; -@property BOOL showIcon; // @synthesize showIcon=_showIcon; -@property double iconToTextSpacing; // @synthesize iconToTextSpacing=_iconToTextSpacing; -@property struct CGSize iconSize; // @synthesize iconSize=_iconSize; - -@end - -@interface TNodeIconAndNameCell : TIconAndTextCell -{ - struct TFENode _node; -} - -- (id)copyWithZone:(struct _NSZone *)arg1; -- (const struct TFENode *)node; -- (void)setNode:(const struct TFENode *)arg1; -- (id)accessibilityAttributeNames; -- (id)accessibilityAttributeValue:(id)arg1; -- (BOOL)accessibilityIsAttributeSettable:(id)arg1; - -@end - -@class NSImage, NSView; - -@interface TListViewIconAndTextCell : TNodeIconAndNameCell -{ - NSImage *_thumbnail; - NSView *_view; -} - -- (void)initializeTextCell; -- (void)dealloc; -- (id)copyWithZone:(struct _NSZone *)arg1; -- (void)drawIconWithFrame:(struct CGRect)arg1; -- (id)controller; -- (id)accessibilityActionNames; -- (id)accessibilityActionDescription:(id)arg1; -- (void)accessibilityPerformAction:(id)arg1; -@property NSView *view; // @synthesize view=_view; -@property(retain) NSImage *thumbnail; // @synthesize thumbnail=_thumbnail; - -@end - -@interface IKImageFlowView : NSOpenGLView -{ - id _dataSource; - id _dragDestinationDelegate; - id _delegate; - void *_reserved; -} - -+ (id)pixelFormat; -+ (BOOL)flowViewIsSupportedByCurrentHardware; -+ (void)initialize; -+ (void)setImportAnimationStyle:(unsigned int)fp8; -- (void)_setDefaultTextAttributes; -- (void)_ikCommonInit; -- (id)initWithFrame:(struct _NSRect)fp8; -- (void)dealloc; -- (void)finalize; -- (void)setValue:(id)fp8 forUndefinedKey:(id)fp12; -- (id)valueForUndefinedKey:(id)fp8; -- (id)allocateNewCell; -- (void)dataSourceDidChange; -- (void)_reloadCellDataAtIndex:(int)fp8; -- (void)reloadCellDataAtIndex:(int)fp8; -- (void)reloadAllCellsData; -- (void)reloadData; -- (id)loadCellAtIndex:(int)fp8; -- (void)didStabilize; -- (BOOL)isAnimating; -- (void)setAnimationsMask:(unsigned int)fp8; -- (unsigned int)animationsMask; -- (void)_cellFinishedImportAnimation:(id)fp8; -- (BOOL)itemAtIndexIsLoaded:(unsigned int)fp8; -- (void)keyWindowChanged:(id)fp8; -- (void)setSelectedIndex:(unsigned int)fp8; -- (BOOL)hitTestWithImage:(id)fp8 x:(float)fp12 y:(float)fp16; -- (unsigned int)cellIndexAtLocation:(struct _NSPoint)fp8; -- (void)_adjustScroller; -- (void)resetCursorRects; -- (void)frameDidChange:(id)fp8; -- (void)invalidateLayout; -- (float)offset; -- (int)cellIndexAtPosition:(float)fp8; -- (int)heightOfInfoSpace; -- (int)countOfVisibleCellsOnEachSide; -- (struct _NSRange)rangeOfVisibleIndexes; -- (struct _NSRange)rangeOfVisibleIndexesAtSelection; -- (id)visibleCellIndexesAtSelection; -- (id)visibleCellIndexes; -- (void)flipCellsWithOldSelectedIndex:(unsigned int)fp8 newSelectedIndex:(unsigned int)fp12; -- (void)flowLayout:(struct _NSRange)fp8; -- (void)zoomOnSelectedLayerLayout:(struct _NSRange)fp8; -- (void)updateLayoutInRange:(struct _NSRange)fp8; -- (void)updateLayout; -- (struct _NSRect)titleFrame; -- (struct _NSRect)subtitleFrame; -- (struct _NSRect)splitterFrame; -- (double)_viewAspectRatio; -- (double)_zScreen; -- (struct _NSSize)imageRenderedSize; -- (struct _NSRect)selectedImageFrame; -- (double)_computeCameraDZ; -- (double)cameraDZ; -- (double)_computeCameraDY; -- (double)cameraDY; -- (float)convertPixelUnitTo3DUnit:(float)fp8; -- (double)alignOnPixelValue; -- (BOOL)updatesCGSurfaceOnDrawRect; -- (void)setUpdatesCGSurfaceOnDrawRect:(BOOL)fp8; -- (BOOL)showSplitter; -- (void)setShowSplitter:(BOOL)fp8; -- (id)delegate; -- (void)setDelegate:(id)fp8; -- (id)dataSource; -- (void)setDataSource:(id)fp8; -- (void)setZoomOnSelectedLayer:(BOOL)fp8; -- (BOOL)zoomOnSelectedLayer; -- (unsigned int)itemsCount; -- (id)cells; -- (unsigned int)selectedIndex; -- (unsigned int)focusedIndex; -- (id)backgroundColor; -- (void)_setBackgroundColorWithRed:(float)fp8 green:(float)fp12 blue:(float)fp16 alpha:(float)fp20; -- (BOOL)backgroundIsLight; -- (BOOL)backgroundIsBlack; -- (BOOL)_convertColor:(id)fp8 toRed:(float *)fp12 green:(float *)fp16 blue:(float *)fp20 alpha:(float *)fp24; -- (void)_getBackgroundRed:(float *)fp8 green:(float *)fp12 blue:(float *)fp16 alpha:(float *)fp20; -- (void)setBackgroundColor:(id)fp8; -- (id)cellBackgroundColor; -- (void)setCellBackgroundColor:(id)fp8; -- (id)cellBorderColor; -- (void)setCellBorderColor:(id)fp8; -- (float)imageAspectRatio; -- (void)setImageAspectRatio:(float)fp8; -- (float)scaleFactor; -- (id)cacheManager; -- (BOOL)cellsAlignOnBaseline; -- (void)setCellsAlignOnBaseline:(BOOL)fp8; -- (void)startInlinePreview; -- (void)stopInlinePreview; -- (void)inlinePreviewDidRenderImage:(void *)fp8; -- (id)thumbnailImageAtIndex:(int)fp8; -- (id)previewImageAtIndex:(int)fp8; -- (void)initRenderingContext; -- (void *)fogShader; -- (void)renewGState; -- (void)setHidden:(BOOL)fp8; -- (id)renderer; -- (void)_setAutoscalesBoundsToPixelUnits:(BOOL)fp8; -- (void)setCacheManager:(id)fp8; -- (id)imageFlowContext; -- (void)setImageFlowContext:(id)fp8; -- (void)__ikSetupGLContext:(id)fp8; -- (id)openGLContext; -- (void)setOpenGLContext:(id)fp8; -- (void)_cacheWasFlushed:(id)fp8; -- (float)fogAtLocation:(float)fp8; -- (struct _NSRect)clampedBounds; -- (struct _NSRect)clampedFrame; -- (void)drawVisibleCells:(struct _NSRect)fp8; -- (void)drawBackground; -- (void)drawTitle; -- (BOOL)installViewport; -- (void)setupGLState; -- (void)installPerspetiveViewportForPicking:(BOOL)fp8 location:(struct _NSPoint)fp12; -- (void)drawFocusRing; -- (BOOL)drawWithCurrentRendererInRect:(struct _NSRect)fp8; -- (void)__copyPixels:(void *)fp8 withSize:(struct _NSSize)fp12 toCurrentFocusedViewAtPoint:(struct _NSPoint)fp20; -- (void)__copyGLToCurrentFocusedView; -- (BOOL)_createPBuffer; -- (void)_deletePBUffer; -- (BOOL)_installPBuffer; -- (void)_copyPBufferToCGSurface; -- (void)drawRect:(struct _NSRect)fp8; - -@end - -@interface TFlowView : IKImageFlowView -{ -} - -- (id)_viewIdentifier; -- (BOOL)acceptsFirstMouse:(id)arg1; -- (BOOL)acceptsFirstResponder; -- (void)dragImage:(id)arg1 at:(struct CGPoint)arg2 offset:(struct CGSize)arg3 event:(id)arg4 pasteboard:(id)arg5 source:(id)arg6 slideBack:(BOOL)arg7; -- (BOOL)isDragImageOpaque; -- (void)mouseDown:(id)arg1; -- (void)reloadData; -- (void)resetCursorRects; -- (BOOL)shouldDelayWindowOrderingForEvent:(id)arg1; - -@end - -@interface TContextMenu : NSMenu -{ -} - -+ (id)contextMenuWithDelegate:(id)arg1; -+ (void)clearContextMenuState; -+ (BOOL)allowContextualMenuForEvent:(id)arg1; -+ (void)contextMenuClickedOnNodes:(const struct TFENodeVector *)arg1 event:(id)arg2 view:(id)arg3 windowController:(id)arg4; -+ (void)contextMenuClickedOnContainer:(const struct TFENode *)arg1 event:(id)arg2 view:(id)arg3 windowController:(id)arg4; -+ (void)populateActionMenu:(id)arg1 forWindowController:(id)arg2; -- (id)initWithTitle:(id)arg1; -- (id)initWithObject:(id)arg1 nodes:(const struct TFENodeVector *)arg2 event:(id)arg3; -- (id)initWithDelegate:(id)arg1; -- (void)dealloc; -- (void)configureWithNodes:(const struct TFENodeVector *)arg1 windowController:(id)arg2 container:(BOOL)arg3; -- (void)configureForSidebarWithNode:(const struct TFENode *)arg1 windowController:(id)arg2 constrained:(BOOL)arg3 data:(id)arg4; -- (void)configureForPathbarWithNode:(const struct TFENode *)arg1 windowController:(id)arg2; -- (void)menuDidCompleteInteraction:(id)arg1; - -@end - -@interface TContextMenu (Private) -+ (void)addViewSpecificStuffToMenu:(id)arg1 browserViewController:(id)arg2 context:(unsigned int)arg3; -+ (void)buildContextMenu:(id)arg1 forContext:(unsigned int)arg2 target:(id)arg3 maxItems:(unsigned long long)arg4 addServices:(BOOL)arg5; -+ (void)handleContextMenuCommon:(unsigned int)arg1 nodes:(const struct TFENodeVector *)arg2 event:(id)arg3 view:(id)arg4 windowController:(id)arg5 addPlugIns:(BOOL)arg6; -@end - - - -@interface TDimmableIconImageView : NSImageView -{ -} - -- (void)drawRect:(struct CGRect)arg1; - -@end - -@interface TListRowView : NSTableRowView -{ - struct TFENode _node; - TListViewController *_listViewController; -// struct TNSRef _selectionView; - _Bool _isDropTarget; -} - -@property(nonatomic) _Bool isDropTarget; // @synthesize isDropTarget=_isDropTarget; -@property(nonatomic) TListViewController *listViewController; // @synthesize listViewController=_listViewController; -@property(nonatomic) struct TFENode node; // @synthesize node=_node; -- (void)openNode; -- (void)setSelected:(BOOL)arg1; -- (void)updateCellSelectedStateAppearance; -- (void)layout; -- (void)setNeedsLayout:(BOOL)arg1; -- (struct CGRect)selectionFrame; -- (void)updateLayer; -- (_Bool)isRowAfterSelected; -- (_Bool)isRowBeforeSelected; -- (long long)selectionHighlightStyle; -- (void)forceDisclosureTriangleBackgroundStyle; -- (id)disclosureTriangleButton; -- (void)dealloc; -- (id)initWithFrame:(struct CGRect)arg1; - -@end \ No newline at end of file diff --git a/shell_integration/MacOSX/OwnCloudFinder/FinderHook.h b/shell_integration/MacOSX/OwnCloudFinder/FinderHook.h deleted file mode 100644 index 3e53b128fb..0000000000 --- a/shell_integration/MacOSX/OwnCloudFinder/FinderHook.h +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved. - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 2.1 of the License, or (at your option) - * any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - * details. - */ - -#import - -@interface FinderHook : NSObject - -+ (void)hookClassMethod:(SEL)oldSelector inClass:(NSString*)className toCallToTheNewMethod:(SEL)newSelector; -+ (void)hookMethod:(SEL)oldSelector inClass:(NSString*)className toCallToTheNewMethod:(SEL)newSelector; - -@end \ No newline at end of file diff --git a/shell_integration/MacOSX/OwnCloudFinder/FinderHook.m b/shell_integration/MacOSX/OwnCloudFinder/FinderHook.m deleted file mode 100644 index dfb6197e64..0000000000 --- a/shell_integration/MacOSX/OwnCloudFinder/FinderHook.m +++ /dev/null @@ -1,125 +0,0 @@ -/** - * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved. - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 2.1 of the License, or (at your option) - * any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - * details. - */ - -#import "ContentManager.h" -#import "FinderHook.h" -#import "IconCache.h" -#import "objc/objc-class.h" -#import "RequestManager.h" - -static BOOL installed = NO; - -@implementation FinderHook - -+ (void)hookClassMethod:(SEL)oldSelector inClass:(NSString*)className toCallToTheNewMethod:(SEL)newSelector -{ - Class hookedClass = NSClassFromString(className); - Method oldMethod = class_getClassMethod(hookedClass, oldSelector); - Method newMethod = class_getClassMethod(hookedClass, newSelector); - - method_exchangeImplementations(newMethod, oldMethod); -} - -+ (void)hookMethod:(SEL)oldSelector inClass:(NSString*)className toCallToTheNewMethod:(SEL)newSelector -{ - Class hookedClass = NSClassFromString(className); - Method oldMethod = class_getInstanceMethod(hookedClass, oldSelector); - Method newMethod = class_getInstanceMethod(hookedClass, newSelector); - - method_exchangeImplementations(newMethod, oldMethod); -} - -+ (void)install -{ - if (installed) - { - // NSLog(@"SyncStateFinder: already installed"); - - return; - } - - // NSLog(@"SyncStateFinder: installing SyncState Shell extension"); - - [OwnCloudFinderRequestManager sharedInstance]; - - // Icons - [self hookMethod:@selector(drawImage:) inClass:@"IKImageBrowserCell" toCallToTheNewMethod:@selector(OCIconOverlayHandlers_IKImageBrowserCell_drawImage:)]; // 10.7 & 10.8 & 10.9 (Icon View arrange by name) - - [self hookMethod:@selector(drawImage:) inClass:@"IKFinderReflectiveIconCell" toCallToTheNewMethod:@selector(OCIconOverlayHandlers_IKFinderReflectiveIconCell_drawImage:)]; // 10.7 & 10.8 & 10.9 (Icon View arrange by everything else) - - [self hookMethod:@selector(drawIconWithFrame:) inClass:@"TColumnCell" toCallToTheNewMethod:@selector(OCIconOverlayHandlers_drawIconWithFrame:)]; // 10.7 & 10.8 & 10.9 Column View - - [self hookMethod:@selector(drawRect:) inClass:@"TDimmableIconImageView" toCallToTheNewMethod:@selector(OCIconOverlayHandlers_drawRect:)]; // 10.9 (List and Coverflow Views) - - // Context Menus - [self hookClassMethod:@selector(addViewSpecificStuffToMenu:browserViewController:context:) inClass:@"TContextMenu" toCallToTheNewMethod:@selector(OCContextMenuHandlers_addViewSpecificStuffToMenu:browserViewController:context:)]; // 10.7 & 10.8 - - [self hookClassMethod:@selector(addViewSpecificStuffToMenu:clickedView:browserViewController:context:) inClass:@"TContextMenu" toCallToTheNewMethod:@selector(OCContextMenuHandlers_addViewSpecificStuffToMenu:clickedView:browserViewController:context:)]; // 10.9 - - [self hookClassMethod:@selector(handleContextMenuCommon:nodes:event:view:windowController:addPlugIns:) inClass:@"TContextMenu" toCallToTheNewMethod:@selector(OCContextMenuHandlers_handleContextMenuCommon:nodes:event:view:windowController:addPlugIns:)]; // 10.7 - - [self hookClassMethod:@selector(handleContextMenuCommon:nodes:event:view:browserController:addPlugIns:) inClass:@"TContextMenu" toCallToTheNewMethod:@selector(OCContextMenuHandlers_handleContextMenuCommon:nodes:event:view:browserController:addPlugIns:)]; // 10.8 - - [self hookClassMethod:@selector(handleContextMenuCommon:nodes:event:clickedView:browserViewController:addPlugIns:) inClass:@"TContextMenu" toCallToTheNewMethod:@selector(OCContextMenuHandlers_handleContextMenuCommon:nodes:event:clickedView:browserViewController:addPlugIns:)]; // 10.9 - - [self hookMethod:@selector(configureWithNodes:windowController:container:) inClass:@"TContextMenu" toCallToTheNewMethod:@selector(OCContextMenuHandlers_configureWithNodes:windowController:container:)]; // 10.7 - - [self hookMethod:@selector(configureWithNodes:browserController:container:) inClass:@"TContextMenu" toCallToTheNewMethod:@selector(OCContextMenuHandlers_configureWithNodes:browserController:container:)]; // 10.8 - - [self hookMethod:@selector(configureFromMenuNeedsUpdate:clickedView:container:event:selectedNodes:) inClass:@"TContextMenu" toCallToTheNewMethod:@selector(OCContextMenuHandlers_configureFromMenuNeedsUpdate:clickedView:container:event:selectedNodes:)]; // 10.9 - - installed = YES; - - // NSLog(@"SyncStateFinder: installed"); -} - -+ (void)uninstall -{ - if (!installed) - { - // NSLog(@"SyncStateFinder: not installed"); - - return; - } - - // NSLog(@"SyncStateFinder: uninstalling"); - - [[OwnCloudFinderContentManager sharedInstance] dealloc]; - - [[IconCache sharedInstance] dealloc]; - - [[OwnCloudFinderRequestManager sharedInstance] dealloc]; - - // Icons - [self hookMethod:@selector(OCIconOverlayHandlers_drawImage:) inClass:@"TIconViewCell" toCallToTheNewMethod:@selector(drawImage:)]; // 10.7 & 10.8 & 10.9 - - [self hookMethod:@selector(OCIconOverlayHandlers_drawIconWithFrame:) inClass:@"TListViewIconAndTextCell" toCallToTheNewMethod:@selector(drawIconWithFrame:)]; // 10.7 & 10.8 & 10.9 - - // Context Menus - [self hookClassMethod:@selector(OCContextMenuHandlers_addViewSpecificStuffToMenu:browserViewController:context:) inClass:@"TContextMenu" toCallToTheNewMethod:@selector(addViewSpecificStuffToMenu:browserViewController:context:)]; // 10.7 & 10.8 - - [self hookClassMethod:@selector(OCContextMenuHandlers_handleContextMenuCommon:nodes:event:view:windowController:addPlugIns:) inClass:@"TContextMenu" toCallToTheNewMethod:@selector(handleContextMenuCommon:nodes:event:view:windowController:addPlugIns:)]; // 10.7 - - [self hookMethod:@selector(OCContextMenuHandlers_configureWithNodes:windowController:container:) inClass:@"TContextMenu" toCallToTheNewMethod:@selector(configureWithNodes:windowController:container:)]; // 10.7 - - [self hookClassMethod:@selector(OCContextMenuHandlers_handleContextMenuCommon:nodes:event:view:browserController:addPlugIns:) inClass:@"TContextMenu" toCallToTheNewMethod:@selector(handleContextMenuCommon:nodes:event:view:browserController:addPlugIns:)]; // 10.8 - - [self hookMethod:@selector(OCContextMenuHandlers_configureWithNodes:browserController:container:) inClass:@"TContextMenu" toCallToTheNewMethod:@selector(configureWithNodes:browserController:container:)]; // 10.8 - - installed = NO; - - // NSLog(@"SyncStateFinder: uninstalled"); -} - -@end \ No newline at end of file diff --git a/shell_integration/MacOSX/OwnCloudFinder/FinishedIconCache.h b/shell_integration/MacOSX/OwnCloudFinder/FinishedIconCache.h deleted file mode 100644 index 031b1e3390..0000000000 --- a/shell_integration/MacOSX/OwnCloudFinder/FinishedIconCache.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// FinishedIconCache.h -// OwnCloudFinder -// -// Created by Markus Goetz on 01/10/14. -// -// - -#import -#import - -@interface FinishedIconCache : NSObject { - NSCache *_cache; - long long _hits; - long long _misses; -} - -+ (FinishedIconCache*)sharedInstance; - -- (NSImage*)getIcon:(NSString*)fileName overlayIconIndex:(int)idx width:(float)w height:(float)h; -- (void)registerIcon:(NSImage*)icon withFileName:(NSString*)fileName overlayIconIndex:(int)idx width:(float)w height:(float)h; - - -@end diff --git a/shell_integration/MacOSX/OwnCloudFinder/FinishedIconCache.m b/shell_integration/MacOSX/OwnCloudFinder/FinishedIconCache.m deleted file mode 100644 index 73c26f2d4e..0000000000 --- a/shell_integration/MacOSX/OwnCloudFinder/FinishedIconCache.m +++ /dev/null @@ -1,91 +0,0 @@ -// -// FinishedIconCache.m -// OwnCloudFinder -// -// Created by Markus Goetz on 01/10/14. -// -// - -#import "FinishedIconCache.h" - - -@interface FinishedIconCacheItem : NSObject -@property (nonatomic, strong) NSImage *icon; -@property (nonatomic) NSTimeInterval maxAge; -@end - -@implementation FinishedIconCacheItem -@synthesize icon; -@synthesize maxAge; -- (void)dealloc { - //NSLog(@"RELEASE %@ %@", self, self.icon); - if (self.icon) { - [self->icon release]; - } - [super dealloc]; -} -@end - -@implementation FinishedIconCache - -static FinishedIconCache* sharedInstance = nil; - -- init -{ - self = [super init]; - if (self) - { - _cache = [[NSCache alloc] init]; - _cache.totalCostLimit = (2880 * 1800); // mbp15 screen size - _hits = 0; - _misses = 0; - } - return self; -} - -- (void)dealloc -{ - [_cache dealloc]; - [super dealloc]; -} - -+ (FinishedIconCache*)sharedInstance -{ - @synchronized(self) - { - if (sharedInstance == nil) - { - sharedInstance = [[self alloc] init]; - } - } - return sharedInstance; -} - - -- (NSImage*)getIcon:(NSString*)fileName overlayIconIndex:(int)idx width:(float)w height:(float)h -{ - NSString *cacheKey = [NSString stringWithFormat:@"%@--%d--%f%f", fileName, idx, w,h]; - FinishedIconCacheItem *item = [_cache objectForKey:cacheKey]; - if (item) { - if (item.maxAge > [[NSDate date] timeIntervalSinceReferenceDate]) { - _hits++; - return item.icon; - } - } - _misses++; - return NULL; -} - -- (void)registerIcon:(NSImage*)icon withFileName:(NSString*)fileName overlayIconIndex:(int)idx width:(float)w height:(float)h -{ - NSString *cacheKey = [NSString stringWithFormat:@"%@--%d--%f%f", fileName, idx, w, h]; - FinishedIconCacheItem *item = [[FinishedIconCacheItem alloc] init]; - item.icon = icon; - // max age between 1 sec and 5 sec - item.maxAge = [[NSDate date] timeIntervalSinceReferenceDate] + 1.0 + 4.0*((double)arc4random() / 0x100000000); - [_cache setObject:item forKey:cacheKey cost:w*h]; - [item release]; - //NSLog(@"CACHE hit/miss ratio: %f", (float)_hits/(float)_misses); -} - -@end diff --git a/shell_integration/MacOSX/OwnCloudFinder/IconCache.h b/shell_integration/MacOSX/OwnCloudFinder/IconCache.h deleted file mode 100644 index 1b451d85c3..0000000000 --- a/shell_integration/MacOSX/OwnCloudFinder/IconCache.h +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved. - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 2.1 of the License, or (at your option) - * any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - * details. - */ - -#import - -@interface IconCache : NSObject { - int _currentIconId; - NSMutableDictionary* _iconIdDictionary; -} - -+ (IconCache*)sharedInstance; - -- (NSImage*)getIcon:(NSNumber*)iconId; -- (NSNumber*)registerIcon:(NSImage*)image; - -@end \ No newline at end of file diff --git a/shell_integration/MacOSX/OwnCloudFinder/IconCache.m b/shell_integration/MacOSX/OwnCloudFinder/IconCache.m deleted file mode 100644 index f8370b5f71..0000000000 --- a/shell_integration/MacOSX/OwnCloudFinder/IconCache.m +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved. - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 2.1 of the License, or (at your option) - * any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - * details. - */ - -#import "IconCache.h" - -@implementation IconCache - -static IconCache* sharedInstance = nil; - -- init -{ - self = [super init]; - - if (self) - { - _iconIdDictionary = [[NSMutableDictionary alloc] init]; - _currentIconId = 0; - } - - return self; -} - -- (void)dealloc -{ - [_iconIdDictionary release]; - sharedInstance = nil; - - [super dealloc]; -} - -+ (IconCache*)sharedInstance -{ - @synchronized(self) - { - if (sharedInstance == nil) - { - sharedInstance = [[self alloc] init]; - } - } - return sharedInstance; -} - -- (NSImage*)getIcon:(NSNumber*)iconId -{ - NSImage* image = [_iconIdDictionary objectForKey:iconId]; - - return image; -} - -- (NSNumber*)registerIcon:(NSImage*)image -{ - _currentIconId++; - - NSNumber* iconId = [NSNumber numberWithInt:_currentIconId]; - - [_iconIdDictionary setObject:image forKey:iconId]; - - return iconId; -} - -@end diff --git a/shell_integration/MacOSX/OwnCloudFinder/IconOverlayHandlers.h b/shell_integration/MacOSX/OwnCloudFinder/IconOverlayHandlers.h deleted file mode 100644 index 0657fa87fc..0000000000 --- a/shell_integration/MacOSX/OwnCloudFinder/IconOverlayHandlers.h +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved. - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 2.1 of the License, or (at your option) - * any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - * details. - */ - -#import - -@interface NSObject (IconOverlayHandlers) - -- (void)OCIconOverlayHandlers_drawIconWithFrame:(struct CGRect)arg1; -- (void)OCIconOverlayHandlers_drawImage:(id)arg1; -- (void)OCIconOverlayHandlers_drawRect:(struct CGRect)arg1; - -@end \ No newline at end of file diff --git a/shell_integration/MacOSX/OwnCloudFinder/IconOverlayHandlers.m b/shell_integration/MacOSX/OwnCloudFinder/IconOverlayHandlers.m deleted file mode 100644 index 7db375ea6b..0000000000 --- a/shell_integration/MacOSX/OwnCloudFinder/IconOverlayHandlers.m +++ /dev/null @@ -1,176 +0,0 @@ -/** - * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved. - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 2.1 of the License, or (at your option) - * any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - * details. - */ - -#import -#import "ContentManager.h" -#import "IconCache.h" -#import "FinishedIconCache.h" -#import "IconOverlayHandlers.h" -#import "Finder/Finder.h" - -@implementation NSObject (IconOverlayHandlers) - -- (void)OCIconOverlayHandlers_drawIconWithFrame:(struct CGRect)arg1 -{ - [self OCIconOverlayHandlers_drawIconWithFrame:arg1]; - - NSURL* url = [[NSClassFromString(@"FINode") nodeFromNodeRef:[(TIconAndTextCell*)self node]->fNodeRef] previewItemURL]; - - BOOL isDir; - if ([[NSFileManager defaultManager] fileExistsAtPath: [url path] isDirectory:&isDir] == NO) { - NSLog(@"ERROR: Could not determine file type of %@", [url path]); - isDir = NO; - } - - NSNumber* imageIndex = [[OwnCloudFinderContentManager sharedInstance] iconByPath:[url path] isDirectory:isDir]; - - //NSLog(@"1 The icon index is %d", [imageIndex intValue]); - if ([imageIndex intValue] > 0) - { - NSImage* image = [[IconCache sharedInstance] getIcon:imageIndex]; - - if (image != nil) - { - struct CGRect arg2 = [(TIconViewCell*)self imageRectForBounds:arg1]; - - [image drawInRect:NSMakeRect(arg2.origin.x, arg2.origin.y, arg2.size.width, arg2.size.height) fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0 respectFlipped:TRUE hints:nil]; - } - } -} - -- (void)OCIconOverlayHandlers_IKImageBrowserCell_drawImage:(id)arg1 -{ - IKImageWrapper*imageWrapper = [self OCIconOverlayHandlers_imageWrapper:arg1]; - - [self OCIconOverlayHandlers_IKImageBrowserCell_drawImage:imageWrapper]; -} - -- (void)OCIconOverlayHandlers_IKFinderReflectiveIconCell_drawImage:(id)arg1 -{ - IKImageWrapper*imageWrapper = [self OCIconOverlayHandlers_imageWrapper:arg1]; - - [self OCIconOverlayHandlers_IKFinderReflectiveIconCell_drawImage:imageWrapper]; -} - -- (IKImageWrapper*)OCIconOverlayHandlers_imageWrapper:(id)arg1 -{ - TIconViewCell* realSelf = (TIconViewCell*)self; - FINode* node = (FINode*)[realSelf representedItem]; - - NSURL* url = [node previewItemURL]; - - BOOL isDir; - if ([[NSFileManager defaultManager] fileExistsAtPath:[url path] isDirectory:&isDir] == NO) { - NSLog(@"ERROR: Could not determine file type of %@", [url path]); - isDir = NO; - } - - NSNumber* imageIndex = [[OwnCloudFinderContentManager sharedInstance] iconByPath:[url path] isDirectory:isDir]; - //NSLog(@"2 The icon index is %d %@ %@", [imageIndex intValue], [url path], isDir ? @"isDir" : @""); - - if ([imageIndex intValue] > 0) - { - NSImage* icon = [arg1 _nsImage]; - - // Use the short term icon cache that possibly has the finished icon - FinishedIconCache *finishedIconCache = [FinishedIconCache sharedInstance]; - NSImage *finishedImage = [finishedIconCache getIcon:[url path] overlayIconIndex:imageIndex width:[icon size].width height:[icon size].height]; - if (finishedImage) { - //NSLog(@"X Got finished image from cache %@ %@", finishedImage, [url path]); - return [[[IKImageWrapper alloc] initWithNSImage:finishedImage] autorelease];; - } else { - //NSLog(@"X Need to redraw %@", [url path]); - } - - NSImage* iconimage = [[IconCache sharedInstance] getIcon:[NSNumber numberWithInt:[imageIndex intValue]]]; - - if (iconimage != nil) - { - [icon lockFocus]; - - CGContextRef myContext = [[NSGraphicsContext currentContext] graphicsPort]; - - CGRect destRect = CGRectMake(0, 0, [icon size].width, [icon size].height); - - CGImageRef cgImage = [iconimage CGImageForProposedRect:&destRect - context:[NSGraphicsContext currentContext] - hints:nil]; - if (cgImage) { - CGContextDrawImage(myContext, destRect, cgImage); - //CGImageRelease(cgImage); // leak here? if we leave this code in, Finder crashes - // But actually i'm not seeing a leak in Activity Monitor.. maybe it is not really leaking? - } else { - NSLog(@"No image given!!!!!11 %@", [url path]); - } - - [icon unlockFocus]; - } - - // Insert into cache - [finishedIconCache registerIcon:icon withFileName:[url path] overlayIconIndex:imageIndex width:[icon size].width height:[icon size].height]; - - return [[[IKImageWrapper alloc] initWithNSImage:icon] autorelease]; - } - else - { - return arg1; - } -} - -- (void)OCIconOverlayHandlers_drawRect:(struct CGRect)arg1 -{ - [self OCIconOverlayHandlers_drawRect:arg1]; - - NSView* supersuperview = [[(NSView*)self superview] superview]; - - if ([supersuperview isKindOfClass:(id)objc_getClass("TListRowView")]) - { - TListRowView *listRowView = (TListRowView*) supersuperview; - FINode *fiNode; - - object_getInstanceVariable(listRowView, "_node", (void**)&fiNode); - - NSURL *url; - - if ([fiNode respondsToSelector:@selector(previewItemURL)]) - { - url = [fiNode previewItemURL]; - } - else { - return; - } - - BOOL isDir; - if ([[NSFileManager defaultManager] fileExistsAtPath:[url path] isDirectory: &isDir] == NO) { - NSLog(@"ERROR: Could not determine file type of %@", [url path]); - isDir = NO; - } - - NSNumber* imageIndex = [[OwnCloudFinderContentManager sharedInstance] iconByPath:[url path] isDirectory:isDir]; - //NSLog(@"3 The icon index is %d", [imageIndex intValue]); - - if ([imageIndex intValue] > 0) - { - NSImage* image = [[IconCache sharedInstance] getIcon:imageIndex]; - - if (image != nil) - { - [image drawInRect:NSMakeRect(arg1.origin.x, arg1.origin.y, arg1.size.width, arg1.size.height) fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0 respectFlipped:TRUE hints:nil]; - } - } - - } -} - -@end \ No newline at end of file diff --git a/shell_integration/MacOSX/OwnCloudFinder/Info.plist b/shell_integration/MacOSX/OwnCloudFinder/Info.plist deleted file mode 100644 index f64f59a8ce..0000000000 --- a/shell_integration/MacOSX/OwnCloudFinder/Info.plist +++ /dev/null @@ -1,48 +0,0 @@ - - - - - SocketApiPrefix - $(OC_SOCKETAPI_TEAM_IDENTIFIER_PREFIX)$(OC_APPLICATION_REV_DOMAIN) - CFBundleDevelopmentRegion - English - CFBundleExecutable - ${EXECUTABLE_NAME} - CFBundleIconFile - - CFBundleIdentifier - com.owncloud.finder - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - ${PRODUCT_NAME} - CFBundlePackageType - BNDL - CFBundleShortVersionString - 1.0 - CFBundleSignature - OWNC - CFBundleVersion - 1 - CFPlugInDynamicRegisterFunction - - CFPlugInDynamicRegistration - NO - CFPlugInFactories - - 00000000-0000-0000-0000-000000000000 - MyFactoryFunction - - CFPlugInTypes - - 00000000-0000-0000-0000-000000000000 - - 00000000-0000-0000-0000-000000000000 - - - CFPlugInUnloadFunction - - NSPrincipalClass - FinderHook - - diff --git a/shell_integration/MacOSX/OwnCloudFinder/MenuManager.h b/shell_integration/MacOSX/OwnCloudFinder/MenuManager.h deleted file mode 100644 index a89fd117df..0000000000 --- a/shell_integration/MacOSX/OwnCloudFinder/MenuManager.h +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved. - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 2.1 of the License, or (at your option) - * any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - * details. - */ - -#import - -@class TContextMenu; -struct TFENodeVector; - -@interface MenuManager : NSObject - -@property (nonatomic, strong) NSMutableArray* menuItems; - -+ (MenuManager*)sharedInstance; - -- (void)addItemsToMenu:(TContextMenu*)menu forFiles:(NSArray*)files; -- (NSArray*)pathsForNodes:(const struct TFENodeVector*)nodes; - -@end \ No newline at end of file diff --git a/shell_integration/MacOSX/OwnCloudFinder/MenuManager.m b/shell_integration/MacOSX/OwnCloudFinder/MenuManager.m deleted file mode 100644 index 1fd9e0a53b..0000000000 --- a/shell_integration/MacOSX/OwnCloudFinder/MenuManager.m +++ /dev/null @@ -1,188 +0,0 @@ -/** - * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved. - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 2.1 of the License, or (at your option) - * any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - * details. - */ - -#import "MenuManager.h" -#import "Finder/Finder.h" -#import "RequestManager.h" - -@implementation MenuManager - -static MenuManager* sharedInstance = nil; - -+ (MenuManager*)sharedInstance -{ - @synchronized(self) - { - if (sharedInstance == nil) - { - sharedInstance = [[self alloc] init]; - } - } - return sharedInstance; -} - -- init -{ - return [super init]; -} - -- (void)addChildrenSubMenuItems:(NSMenuItem*)parentMenuItem withChildren:(NSArray*)menuItemsDictionaries forFiles:(NSArray*)files -{ - NSMenu* menu = [[NSMenu alloc] init]; - - for (int i = 0; i < [menuItemsDictionaries count]; ++i) - { - NSDictionary* menuItemDictionary = [menuItemsDictionaries objectAtIndex:i]; - - NSString* submenuTitle = [menuItemDictionary objectForKey:@"title"]; - BOOL enabled = [[menuItemDictionary objectForKey:@"enabled"] boolValue]; - NSString* uuid = [menuItemDictionary objectForKey:@"uuid"]; - NSArray* childrenSubMenuItems = (NSArray*)[menuItemDictionary objectForKey:@"contextMenuItems"]; - - if ([submenuTitle isEqualToString:@"_SEPARATOR_"]) - { - [menu addItem:[NSMenuItem separatorItem]]; - } - else if (childrenSubMenuItems != nil && [childrenSubMenuItems count] != 0) - { - NSMenuItem* submenuItem = [menu addItemWithTitle:submenuTitle action:nil keyEquivalent:@""]; - - [self addChildrenSubMenuItems:submenuItem withChildren:childrenSubMenuItems forFiles:files]; - } - else - { - [self createActionMenuItemIn:menu withTitle:submenuTitle withIndex:i enabled:enabled withUuid:uuid forFiles:files]; - } - } - - [parentMenuItem setSubmenu:menu]; - - [menu release]; -} - -- (void)addItemsToMenu:(TContextMenu*)menu forFiles:(NSArray*)files -{ - OwnCloudFinderRequestManager *requestManager = [OwnCloudFinderRequestManager sharedInstance]; - NSString *shareItemTitle = [requestManager shareItemTitle]; - if (!shareItemTitle || shareItemTitle.length == 0) { - return; - } - - for (int i = 0; i < files.count; i++) { - NSString *fn = [files objectAtIndex:i]; - BOOL isDir = false; - if ([[NSFileManager defaultManager] fileExistsAtPath:fn isDirectory:&isDir]) { - if (![requestManager isRegisteredPath:fn isDirectory:isDir]) { - return; - } - } - } - - NSMutableArray* menuItemsArray = [[[NSMutableArray alloc] init] autorelease]; - NSMutableDictionary *firstEntry = [[[NSMutableDictionary alloc] init] autorelease]; - [firstEntry setValue:[NSNumber numberWithBool:YES] forKey:@"enabled"]; - [firstEntry setValue:shareItemTitle forKey:@"title"]; - [menuItemsArray addObject:firstEntry]; - - // Find the menu with a submenu which should be the share menu position - NSInteger menuIndex = MIN(4, menu.itemArray.count); - for (int i = menuIndex; i < menu.itemArray.count; i++) { - if ([[menu itemAtIndex:i] hasSubmenu]) { - menuIndex = i; - //NSLog(@"addItemsToMenu: menuIndex --> %lu (count=%lu)", menuIndex, (unsigned long)menu.itemArray.count); - break; - } - } - - for (int i = 0; i < [menuItemsArray count]; ++i) - { - NSDictionary* menuItemDictionary = [menuItemsArray objectAtIndex:i]; - - NSString* mainMenuTitle = [menuItemDictionary objectForKey:@"title"]; - - if ([mainMenuTitle isEqualToString:@""]) - { - continue; - } - - menuIndex++; - - BOOL enabled = [[menuItemDictionary objectForKey:@"enabled"] boolValue]; - NSString* uuid = [menuItemDictionary objectForKey:@"uuid"]; - NSArray* childrenSubMenuItems = (NSArray*)[menuItemDictionary objectForKey:@"contextMenuItems"]; - - if (childrenSubMenuItems != nil && [childrenSubMenuItems count] != 0) - { - NSMenuItem* mainMenuItem = [menu insertItemWithTitle:mainMenuTitle action:nil keyEquivalent:@"" atIndex:menuIndex]; - - [self addChildrenSubMenuItems:mainMenuItem withChildren:childrenSubMenuItems forFiles:files]; - } - else - { - [self createActionMenuItemIn:menu withTitle:mainMenuTitle withIndex:menuIndex enabled:enabled withUuid:uuid forFiles:files]; - } - } -} - -- (void)createActionMenuItemIn:(NSMenu*)menu withTitle:(NSString*)title withIndex:(NSInteger*)index enabled:(BOOL)enabled withUuid:(NSString*)uuid forFiles:(NSArray*)files -{ - NSMenuItem* mainMenuItem = [menu insertItemWithTitle:title action:@selector(menuItemClicked:) keyEquivalent:@"" atIndex:index]; - - if (enabled) - { - [mainMenuItem setTarget:self]; - } - - NSDictionary* menuActionDictionary = [[NSMutableDictionary alloc] init]; - [menuActionDictionary setValue:uuid forKey:@"uuid"]; - NSMutableArray* filesArray = [files copy]; - [menuActionDictionary setValue:filesArray forKey:@"files"]; - - [mainMenuItem setRepresentedObject:menuActionDictionary]; - - [filesArray release]; - [menuActionDictionary release]; -} - -- (void)menuItemClicked:(id)param -{ - [[OwnCloudFinderRequestManager sharedInstance] menuItemClicked:[param representedObject]]; -} - -- (NSArray*)pathsForNodes:(const struct TFENodeVector*)nodes -{ - struct TFENode* start = nodes->_M_impl._M_start; - struct TFENode* end = nodes->_M_impl._M_finish; - - int count = end - start; - - NSMutableArray* selectedItems = [[NSMutableArray alloc] initWithCapacity:count]; - struct TFENode* current; - - for (current = start; current < end; ++current) - { - FINode* node = (FINode*)[NSClassFromString(@"FINode") nodeFromNodeRef:current->fNodeRef]; - - NSString* path = [[node previewItemURL] path]; - - if (path) - { - [selectedItems addObject:path]; - } - } - - return [selectedItems autorelease]; -} - -@end \ No newline at end of file diff --git a/shell_integration/MacOSX/OwnCloudFinder/OwnCloudFinder.xcodeproj/project.pbxproj b/shell_integration/MacOSX/OwnCloudFinder/OwnCloudFinder.xcodeproj/project.pbxproj deleted file mode 100644 index a0aba6a39f..0000000000 --- a/shell_integration/MacOSX/OwnCloudFinder/OwnCloudFinder.xcodeproj/project.pbxproj +++ /dev/null @@ -1,369 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 0B13ECAF173C687900548DA1 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0BFC9ACB173C57E400CDD329 /* Security.framework */; }; - 5BB74A8719DBF9BB001BAAAC /* FinishedIconCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BB74A8619DBF9BB001BAAAC /* FinishedIconCache.m */; }; - 692C18A516660C4700BF6A53 /* ContextMenuHandlers.m in Sources */ = {isa = PBXBuildFile; fileRef = 692C18A416660C4600BF6A53 /* ContextMenuHandlers.m */; }; - 692C18A9166617F500BF6A53 /* IconOverlayHandlers.m in Sources */ = {isa = PBXBuildFile; fileRef = 692C18A8166617F500BF6A53 /* IconOverlayHandlers.m */; }; - 692C18AC1666392700BF6A53 /* MenuManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 692C18AB1666392700BF6A53 /* MenuManager.m */; }; - 6993878616494C000044E4DF /* RequestManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 6993878516494C000044E4DF /* RequestManager.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; }; - 69948B361636D50E0093B6CE /* ContentManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 69948B351636D50E0093B6CE /* ContentManager.m */; }; - 8C37DD9F161593BD00016A95 /* FinderHook.m in Sources */ = {isa = PBXBuildFile; fileRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */; }; - 8C37DDB2161593FF00016A95 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8C37DDB1161593FF00016A95 /* Cocoa.framework */; }; - 8C37DDBA161594B400016A95 /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8C37DDB9161594B400016A95 /* Quartz.framework */; }; - 8C99F6941622D145002D2135 /* IconCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 8C99F6931622D145002D2135 /* IconCache.m */; }; - 8D576314048677EA00EA77CD /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0AA1909FFE8422F4C02AAC07 /* CoreFoundation.framework */; }; - 8D5B49A804867FD3000E48DA /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8D5B49A704867FD3000E48DA /* InfoPlist.strings */; }; - C220057B1B31B04C00A4FB37 /* error_swm.icns in Resources */ = {isa = PBXBuildFile; fileRef = C22005731B31B04C00A4FB37 /* error_swm.icns */; }; - C220057C1B31B04C00A4FB37 /* error.icns in Resources */ = {isa = PBXBuildFile; fileRef = C22005741B31B04C00A4FB37 /* error.icns */; }; - C220057D1B31B04C00A4FB37 /* ok_swm.icns in Resources */ = {isa = PBXBuildFile; fileRef = C22005751B31B04C00A4FB37 /* ok_swm.icns */; }; - C220057E1B31B04C00A4FB37 /* ok.icns in Resources */ = {isa = PBXBuildFile; fileRef = C22005761B31B04C00A4FB37 /* ok.icns */; }; - C220057F1B31B04C00A4FB37 /* sync_swm.icns in Resources */ = {isa = PBXBuildFile; fileRef = C22005771B31B04C00A4FB37 /* sync_swm.icns */; }; - C22005801B31B04C00A4FB37 /* sync.icns in Resources */ = {isa = PBXBuildFile; fileRef = C22005781B31B04C00A4FB37 /* sync.icns */; }; - C22005811B31B04C00A4FB37 /* warning_swm.icns in Resources */ = {isa = PBXBuildFile; fileRef = C22005791B31B04C00A4FB37 /* warning_swm.icns */; }; - C22005821B31B04C00A4FB37 /* warning.icns in Resources */ = {isa = PBXBuildFile; fileRef = C220057A1B31B04C00A4FB37 /* warning.icns */; }; - C2B573831B1CD5AE00303B36 /* SyncClientProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = C2B573821B1CD5AE00303B36 /* SyncClientProxy.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - 089C167EFE841241C02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = ""; }; - 0AA1909FFE8422F4C02AAC07 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = /System/Library/Frameworks/CoreFoundation.framework; sourceTree = ""; }; - 0B2BF60B176A43DB001246CD /* Finder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Finder.h; sourceTree = ""; }; - 0BFC9ACB173C57E400CDD329 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; - 5BB74A8519DBF9BB001BAAAC /* FinishedIconCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FinishedIconCache.h; sourceTree = ""; }; - 5BB74A8619DBF9BB001BAAAC /* FinishedIconCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FinishedIconCache.m; sourceTree = ""; }; - 692C18A316660C4600BF6A53 /* ContextMenuHandlers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContextMenuHandlers.h; sourceTree = ""; }; - 692C18A416660C4600BF6A53 /* ContextMenuHandlers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ContextMenuHandlers.m; sourceTree = ""; }; - 692C18A7166617F500BF6A53 /* IconOverlayHandlers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IconOverlayHandlers.h; sourceTree = ""; }; - 692C18A8166617F500BF6A53 /* IconOverlayHandlers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IconOverlayHandlers.m; sourceTree = ""; }; - 692C18AA1666392700BF6A53 /* MenuManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MenuManager.h; sourceTree = ""; }; - 692C18AB1666392700BF6A53 /* MenuManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MenuManager.m; sourceTree = ""; }; - 6993878416494C000044E4DF /* RequestManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RequestManager.h; sourceTree = ""; }; - 6993878516494C000044E4DF /* RequestManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RequestManager.m; sourceTree = ""; usesTabs = 1; }; - 69948B341636D50E0093B6CE /* ContentManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContentManager.h; sourceTree = ""; }; - 69948B351636D50E0093B6CE /* ContentManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ContentManager.m; sourceTree = ""; }; - 8C37DD99161593BD00016A95 /* FinderHook.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FinderHook.h; sourceTree = ""; }; - 8C37DD9A161593BD00016A95 /* FinderHook.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FinderHook.m; sourceTree = ""; }; - 8C37DDB1161593FF00016A95 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; - 8C37DDB9161594B400016A95 /* Quartz.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quartz.framework; path = System/Library/Frameworks/Quartz.framework; sourceTree = SDKROOT; }; - 8C99F6921622D145002D2135 /* IconCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IconCache.h; sourceTree = ""; }; - 8C99F6931622D145002D2135 /* IconCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IconCache.m; sourceTree = ""; }; - 8D576316048677EA00EA77CD /* SyncStateFinder.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SyncStateFinder.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; - 8D576317048677EA00EA77CD /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - C22005731B31B04C00A4FB37 /* error_swm.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = error_swm.icns; path = ../../icons/icns/error_swm.icns; sourceTree = ""; }; - C22005741B31B04C00A4FB37 /* error.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = error.icns; path = ../../icons/icns/error.icns; sourceTree = ""; }; - C22005751B31B04C00A4FB37 /* ok_swm.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = ok_swm.icns; path = ../../icons/icns/ok_swm.icns; sourceTree = ""; }; - C22005761B31B04C00A4FB37 /* ok.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = ok.icns; path = ../../icons/icns/ok.icns; sourceTree = ""; }; - C22005771B31B04C00A4FB37 /* sync_swm.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = sync_swm.icns; path = ../../icons/icns/sync_swm.icns; sourceTree = ""; }; - C22005781B31B04C00A4FB37 /* sync.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = sync.icns; path = ../../icons/icns/sync.icns; sourceTree = ""; }; - C22005791B31B04C00A4FB37 /* warning_swm.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = warning_swm.icns; path = ../../icons/icns/warning_swm.icns; sourceTree = ""; }; - C220057A1B31B04C00A4FB37 /* warning.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = warning.icns; path = ../../icons/icns/warning.icns; sourceTree = ""; }; - C2B573811B1CD5AE00303B36 /* SyncClientProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SyncClientProxy.h; sourceTree = ""; }; - C2B573821B1CD5AE00303B36 /* SyncClientProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SyncClientProxy.m; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 8D576313048677EA00EA77CD /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 0B13ECAF173C687900548DA1 /* Security.framework in Frameworks */, - 8D576314048677EA00EA77CD /* CoreFoundation.framework in Frameworks */, - 8C37DDB2161593FF00016A95 /* Cocoa.framework in Frameworks */, - 8C37DDBA161594B400016A95 /* Quartz.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 089C166AFE841209C02AAC07 /* SyncStateFinder */ = { - isa = PBXGroup; - children = ( - C2B573801B1CD5AE00303B36 /* common */, - 08FB77AFFE84173DC02AAC07 /* Source */, - 089C167CFE841241C02AAC07 /* Resources */, - 089C1671FE841209C02AAC07 /* External Frameworks and Libraries */, - 19C28FB6FE9D52B211CA2CBB /* Products */, - ); - name = SyncStateFinder; - sourceTree = ""; - usesTabs = 1; - }; - 089C1671FE841209C02AAC07 /* External Frameworks and Libraries */ = { - isa = PBXGroup; - children = ( - 0BFC9ACB173C57E400CDD329 /* Security.framework */, - 8C37DDB9161594B400016A95 /* Quartz.framework */, - 0AA1909FFE8422F4C02AAC07 /* CoreFoundation.framework */, - 8C37DDB1161593FF00016A95 /* Cocoa.framework */, - ); - name = "External Frameworks and Libraries"; - sourceTree = ""; - }; - 089C167CFE841241C02AAC07 /* Resources */ = { - isa = PBXGroup; - children = ( - C22005731B31B04C00A4FB37 /* error_swm.icns */, - C22005741B31B04C00A4FB37 /* error.icns */, - C22005751B31B04C00A4FB37 /* ok_swm.icns */, - C22005761B31B04C00A4FB37 /* ok.icns */, - C22005771B31B04C00A4FB37 /* sync_swm.icns */, - C22005781B31B04C00A4FB37 /* sync.icns */, - C22005791B31B04C00A4FB37 /* warning_swm.icns */, - C220057A1B31B04C00A4FB37 /* warning.icns */, - 8D576317048677EA00EA77CD /* Info.plist */, - 8D5B49A704867FD3000E48DA /* InfoPlist.strings */, - ); - name = Resources; - sourceTree = ""; - }; - 08FB77AFFE84173DC02AAC07 /* Source */ = { - isa = PBXGroup; - children = ( - 0B2BF60A176A43DB001246CD /* Finder */, - 8C37DD99161593BD00016A95 /* FinderHook.h */, - 8C37DD9A161593BD00016A95 /* FinderHook.m */, - 692C18A316660C4600BF6A53 /* ContextMenuHandlers.h */, - 692C18A416660C4600BF6A53 /* ContextMenuHandlers.m */, - 692C18A7166617F500BF6A53 /* IconOverlayHandlers.h */, - 692C18A8166617F500BF6A53 /* IconOverlayHandlers.m */, - 6993878416494C000044E4DF /* RequestManager.h */, - 6993878516494C000044E4DF /* RequestManager.m */, - 69948B341636D50E0093B6CE /* ContentManager.h */, - 69948B351636D50E0093B6CE /* ContentManager.m */, - 8C99F6921622D145002D2135 /* IconCache.h */, - 8C99F6931622D145002D2135 /* IconCache.m */, - 5BB74A8519DBF9BB001BAAAC /* FinishedIconCache.h */, - 5BB74A8619DBF9BB001BAAAC /* FinishedIconCache.m */, - 692C18AA1666392700BF6A53 /* MenuManager.h */, - 692C18AB1666392700BF6A53 /* MenuManager.m */, - ); - name = Source; - sourceTree = ""; - }; - 0B2BF60A176A43DB001246CD /* Finder */ = { - isa = PBXGroup; - children = ( - 0B2BF60B176A43DB001246CD /* Finder.h */, - ); - path = Finder; - sourceTree = ""; - }; - 19C28FB6FE9D52B211CA2CBB /* Products */ = { - isa = PBXGroup; - children = ( - 8D576316048677EA00EA77CD /* SyncStateFinder.bundle */, - ); - name = Products; - sourceTree = ""; - }; - C2B573801B1CD5AE00303B36 /* common */ = { - isa = PBXGroup; - children = ( - C2B573811B1CD5AE00303B36 /* SyncClientProxy.h */, - C2B573821B1CD5AE00303B36 /* SyncClientProxy.m */, - ); - name = common; - path = ../common; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 8D57630D048677EA00EA77CD /* SyncStateFinder */ = { - isa = PBXNativeTarget; - buildConfigurationList = 1DEB911A08733D790010E9CD /* Build configuration list for PBXNativeTarget "SyncStateFinder" */; - buildPhases = ( - 8D57630F048677EA00EA77CD /* Resources */, - 8D576311048677EA00EA77CD /* Sources */, - 8D576313048677EA00EA77CD /* Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = SyncStateFinder; - productInstallPath = "$(HOME)/Library/Bundles"; - productName = SyncStateFinder; - productReference = 8D576316048677EA00EA77CD /* SyncStateFinder.bundle */; - productType = "com.apple.product-type.bundle"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 089C1669FE841209C02AAC07 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0460; - }; - buildConfigurationList = 1DEB911E08733D790010E9CD /* Build configuration list for PBXProject "OwnCloudFinder" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 1; - knownRegions = ( - English, - Japanese, - French, - German, - ); - mainGroup = 089C166AFE841209C02AAC07 /* SyncStateFinder */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 8D57630D048677EA00EA77CD /* SyncStateFinder */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 8D57630F048677EA00EA77CD /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - C220057E1B31B04C00A4FB37 /* ok.icns in Resources */, - C22005821B31B04C00A4FB37 /* warning.icns in Resources */, - C220057F1B31B04C00A4FB37 /* sync_swm.icns in Resources */, - C220057C1B31B04C00A4FB37 /* error.icns in Resources */, - 8D5B49A804867FD3000E48DA /* InfoPlist.strings in Resources */, - C22005801B31B04C00A4FB37 /* sync.icns in Resources */, - C220057D1B31B04C00A4FB37 /* ok_swm.icns in Resources */, - C220057B1B31B04C00A4FB37 /* error_swm.icns in Resources */, - C22005811B31B04C00A4FB37 /* warning_swm.icns in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 8D576311048677EA00EA77CD /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 8C37DD9F161593BD00016A95 /* FinderHook.m in Sources */, - 8C99F6941622D145002D2135 /* IconCache.m in Sources */, - 69948B361636D50E0093B6CE /* ContentManager.m in Sources */, - C2B573831B1CD5AE00303B36 /* SyncClientProxy.m in Sources */, - 6993878616494C000044E4DF /* RequestManager.m in Sources */, - 5BB74A8719DBF9BB001BAAAC /* FinishedIconCache.m in Sources */, - 692C18A516660C4700BF6A53 /* ContextMenuHandlers.m in Sources */, - 692C18A9166617F500BF6A53 /* IconOverlayHandlers.m in Sources */, - 692C18AC1666392700BF6A53 /* MenuManager.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - 8D5B49A704867FD3000E48DA /* InfoPlist.strings */ = { - isa = PBXVariantGroup; - children = ( - 089C167EFE841241C02AAC07 /* English */, - ); - name = InfoPlist.strings; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 1DEB911B08733D790010E9CD /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_LINK_OBJC_RUNTIME = NO; - COMBINE_HIDPI_IMAGES = YES; - CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)"; - COPY_PHASE_STRIP = NO; - GCC_DYNAMIC_NO_PIC = NO; - GCC_MODEL_TUNING = G5; - GCC_OPTIMIZATION_LEVEL = 0; - INFOPLIST_FILE = Info.plist; - INSTALL_PATH = "$(HOME)/Library/Bundles"; - OC_APPLICATION_REV_DOMAIN = com.owncloud.desktopclient; - OC_SOCKETAPI_TEAM_IDENTIFIER_PREFIX = ""; - PRODUCT_NAME = SyncStateFinder; - WRAPPER_EXTENSION = bundle; - }; - name = Debug; - }; - 1DEB911C08733D790010E9CD /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_LINK_OBJC_RUNTIME = NO; - COMBINE_HIDPI_IMAGES = YES; - CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - GCC_MODEL_TUNING = G5; - INFOPLIST_FILE = Info.plist; - INSTALL_PATH = "$(HOME)/Library/Bundles"; - OC_APPLICATION_REV_DOMAIN = com.owncloud.desktopclient; - OC_SOCKETAPI_TEAM_IDENTIFIER_PREFIX = ""; - PRODUCT_NAME = SyncStateFinder; - WRAPPER_EXTENSION = bundle; - }; - name = Release; - }; - 1DEB911F08733D790010E9CD /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; - COPY_PHASE_STRIP = NO; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = _DEBUG_LOG; - GCC_WARN_ABOUT_RETURN_TYPE = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.7; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = macosx; - }; - name = Debug; - }; - 1DEB912008733D790010E9CD /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; - COPY_PHASE_STRIP = NO; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_WARN_ABOUT_RETURN_TYPE = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.7; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = macosx; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 1DEB911A08733D790010E9CD /* Build configuration list for PBXNativeTarget "SyncStateFinder" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 1DEB911B08733D790010E9CD /* Debug */, - 1DEB911C08733D790010E9CD /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 1DEB911E08733D790010E9CD /* Build configuration list for PBXProject "OwnCloudFinder" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 1DEB911F08733D790010E9CD /* Debug */, - 1DEB912008733D790010E9CD /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 089C1669FE841209C02AAC07 /* Project object */; -} diff --git a/shell_integration/MacOSX/OwnCloudFinder/OwnCloudFinder.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/shell_integration/MacOSX/OwnCloudFinder/OwnCloudFinder.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 25ab3b4240..0000000000 --- a/shell_integration/MacOSX/OwnCloudFinder/OwnCloudFinder.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/shell_integration/MacOSX/OwnCloudFinder/OwnCloudFinder.xcodeproj/xcuserdata/guruz.xcuserdatad/xcschemes/xcschememanagement.plist b/shell_integration/MacOSX/OwnCloudFinder/OwnCloudFinder.xcodeproj/xcuserdata/guruz.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index 2f490c9952..0000000000 --- a/shell_integration/MacOSX/OwnCloudFinder/OwnCloudFinder.xcodeproj/xcuserdata/guruz.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,14 +0,0 @@ - - - - - SuppressBuildableAutocreation - - 8D57630D048677EA00EA77CD - - primary - - - - - diff --git a/shell_integration/MacOSX/OwnCloudFinder/RequestManager.h b/shell_integration/MacOSX/OwnCloudFinder/RequestManager.h deleted file mode 100644 index f3924fac60..0000000000 --- a/shell_integration/MacOSX/OwnCloudFinder/RequestManager.h +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved. - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 2.1 of the License, or (at your option) - * any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - * details. - */ - -#import -#import "RequestManager.h" -#import "SyncClientProxy.h" - -@interface OwnCloudFinderRequestManager : NSObject -{ - SyncClientProxy *_syncClientProxy; - - NSMutableArray* _requestQueue; - NSMutableDictionary* _registeredPathes; - NSMutableSet* _requestedPaths; - - NSString *_shareMenuTitle; -} - -@property (nonatomic, retain) NSString* filterFolder; - -+ (OwnCloudFinderRequestManager*)sharedInstance; - -- (BOOL)isRegisteredPath:(NSString*)path isDirectory:(BOOL)isDir; -- (void)askForIcon:(NSString*)path isDirectory:(BOOL)isDir; -- (void)menuItemClicked:(NSDictionary*)actionDictionary; - -- (NSString*) shareItemTitle; - -@end \ No newline at end of file diff --git a/shell_integration/MacOSX/OwnCloudFinder/RequestManager.m b/shell_integration/MacOSX/OwnCloudFinder/RequestManager.m deleted file mode 100644 index 1d4a5c40af..0000000000 --- a/shell_integration/MacOSX/OwnCloudFinder/RequestManager.m +++ /dev/null @@ -1,159 +0,0 @@ -/** - * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved. - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 2.1 of the License, or (at your option) - * any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - * details. - */ - -#import "ContentManager.h" -#import "IconCache.h" -#import "RequestManager.h" - -static OwnCloudFinderRequestManager* sharedInstance = nil; - -@implementation OwnCloudFinderRequestManager - -- (id)init -{ - if ((self = [super init])) - { - // For the sake of allowing both the legacy and the FinderSync extensions to work with the same - // client build, use the same server name including the Team ID even though we won't be sandboxed. - NSBundle *extBundle = [NSBundle bundleForClass:[self class]]; - // This was added to the bundle's Info.plist to get it from the build system - NSString *socketApiPrefix = [extBundle objectForInfoDictionaryKey:@"SocketApiPrefix"]; - NSString *serverName = [socketApiPrefix stringByAppendingString:@".socketApi"]; - // NSLog(@"OwnCloudFinderRequestManager serverName %@", serverName); - - _syncClientProxy = [[SyncClientProxy alloc] initWithDelegate:self serverName:serverName]; - - _registeredPathes = [[NSMutableDictionary alloc] init]; - _requestedPaths = [[NSMutableSet alloc] init]; - - _shareMenuTitle = nil; - - // The NSConnection will block until the distant object came back and this creates a loop hanging Finder. - // Start from a timer to have time to unwind the stack first. - [NSTimer scheduledTimerWithTimeInterval:0 target:_syncClientProxy selector:@selector(start) userInfo:nil repeats:NO]; - } - - return self; -} - -- (void)dealloc -{ - sharedInstance = nil; -} - -+ (OwnCloudFinderRequestManager*)sharedInstance -{ - @synchronized(self) - { - if (sharedInstance == nil) - { - sharedInstance = [[self alloc] init]; - } - } - - return sharedInstance; -} - -- (BOOL)isRegisteredPath:(NSString*)path isDirectory:(BOOL)isDir -{ - // check if the file in question is underneath a registered directory - NSArray *regPathes = [_registeredPathes allKeys]; - BOOL registered = NO; - - NSString* checkPath = [NSString stringWithString:path]; - if (isDir && ![checkPath hasSuffix:@"/"]) { - // append a slash - checkPath = [path stringByAppendingString:@"/"]; - } - - for( NSString *regPath in regPathes ) { - if( [checkPath hasPrefix:regPath]) { - // the path was registered - registered = YES; - break; - } - } - - return registered; -} - -- (void)askForIcon:(NSString*)path isDirectory:(BOOL)isDir -{ - if( [self isRegisteredPath:path isDirectory:isDir] ) { - [_requestedPaths addObject:path]; - [_syncClientProxy askForIcon:path isDirectory:isDir]; - } -} - -- (void)setResultForPath:(NSString*)path result:(NSString*)result -{ - // The client will broadcast all changes, do not fill the cache for paths that Finder didn't ask for. - if ([_requestedPaths containsObject:path]) { - [[OwnCloudFinderContentManager sharedInstance] setResultForPath:path result:result]; - } -} - -- (void)reFetchFileNameCacheForPath:(NSString*)path -{ - [_requestedPaths removeAllObjects]; - [[OwnCloudFinderContentManager sharedInstance] reFetchFileNameCacheForPath:path]; -} - -- (void)registerPath:(NSString*)path -{ - NSNumber *one = [NSNumber numberWithInt:1]; - [_registeredPathes setObject:one forKey:path]; - [[OwnCloudFinderContentManager sharedInstance] repaintAllWindows]; -} - -- (void)unregisterPath:(NSString*)path -{ - [_registeredPathes removeObjectForKey:path]; - [[OwnCloudFinderContentManager sharedInstance] repaintAllWindows]; -} - -- (void)setShareMenuTitle:(NSString*)title -{ - _shareMenuTitle = title; -} - -- (void)connectionDidDie -{ - // NSLog(@"Socket DISconnected! %@", [err localizedDescription]); - - // clear the registered paths. - _registeredPathes = [[NSMutableDictionary alloc] init]; - [_requestedPaths removeAllObjects]; - - // clear the caches in content manager - OwnCloudFinderContentManager *contentman = [OwnCloudFinderContentManager sharedInstance]; - [contentman clearFileNameCache]; - [contentman repaintAllWindows]; -} - -- (void)menuItemClicked:(NSDictionary*)actionDictionary -{ - // NSLog(@"RequestManager menuItemClicked %@", actionDictionary); - NSArray *filePaths = [actionDictionary valueForKey:@"files"]; - for (int i = 0; i < filePaths.count; i++) { - [_syncClientProxy askOnSocket:[filePaths objectAtIndex:i] query:@"SHARE"]; - } -} - -- (NSString*) shareItemTitle -{ - return _shareMenuTitle; -} - -@end diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.h b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.h index a1356045b7..67a4360945 100644 --- a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.h +++ b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.h @@ -22,6 +22,7 @@ SyncClientProxy *_syncClientProxy; NSMutableSet *_registeredDirectories; NSString *_shareMenuTitle; + NSMutableDictionary *_strings; } @end diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.m b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.m index 065be2e2f5..2e917d132c 100644 --- a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.m +++ b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.m @@ -59,7 +59,7 @@ _syncClientProxy = [[SyncClientProxy alloc] initWithDelegate:self serverName:serverName]; _registeredDirectories = [[NSMutableSet alloc] init]; - _shareMenuTitle = nil; + _strings = [[NSMutableDictionary alloc] init]; [_syncClientProxy start]; return self; @@ -101,11 +101,21 @@ } }]; - if (_shareMenuTitle && !onlyRootsSelected) { - NSMenu *menu = [[NSMenu alloc] initWithTitle:@""]; - NSMenuItem *item = [menu addItemWithTitle:_shareMenuTitle action:@selector(shareMenuAction:) keyEquivalent:@"title"]; - item.image = [[NSBundle mainBundle] imageForResource:@"app.icns"]; - + id contextMenuTitle = [_strings objectForKey:@"CONTEXT_MENU_TITLE"]; + id shareTitle = [_strings objectForKey:@"SHARE_MENU_TITLE"]; + id copyLinkTitle = [_strings objectForKey:@"COPY_PRIVATE_LINK_MENU_TITLE"]; + id emailLinkTitle = [_strings objectForKey:@"EMAIL_PRIVATE_LINK_MENU_TITLE"]; + if (contextMenuTitle && !onlyRootsSelected) { + NSMenu *menu = [[NSMenu alloc] initWithTitle:@""]; + NSMenu *subMenu = [[NSMenu alloc] initWithTitle:@""]; + NSMenuItem *subMenuItem = [menu addItemWithTitle:contextMenuTitle action:nil keyEquivalent:@""]; + subMenuItem.submenu = subMenu; + subMenuItem.image = [[NSBundle mainBundle] imageForResource:@"app.icns"]; + + [subMenu addItemWithTitle:shareTitle action:@selector(shareMenuAction:) keyEquivalent:@""]; + [subMenu addItemWithTitle:copyLinkTitle action:@selector(copyLinkMenuAction:) keyEquivalent:@""]; + [subMenu addItemWithTitle:emailLinkTitle action:@selector(emailLinkMenuAction:) keyEquivalent:@""]; + return menu; } return nil; @@ -114,13 +124,33 @@ - (IBAction)shareMenuAction:(id)sender { NSArray* items = [[FIFinderSyncController defaultController] selectedItemURLs]; - + [items enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) { NSString* normalizedPath = [[obj path] decomposedStringWithCanonicalMapping]; [_syncClientProxy askOnSocket:normalizedPath query:@"SHARE"]; }]; } +- (IBAction)copyLinkMenuAction:(id)sender +{ + NSArray* items = [[FIFinderSyncController defaultController] selectedItemURLs]; + + [items enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) { + NSString* normalizedPath = [[obj path] decomposedStringWithCanonicalMapping]; + [_syncClientProxy askOnSocket:normalizedPath query:@"COPY_PRIVATE_LINK"]; + }]; +} + +- (IBAction)emailLinkMenuAction:(id)sender +{ + NSArray* items = [[FIFinderSyncController defaultController] selectedItemURLs]; + + [items enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) { + NSString* normalizedPath = [[obj path] decomposedStringWithCanonicalMapping]; + [_syncClientProxy askOnSocket:normalizedPath query:@"EMAIL_PRIVATE_LINK"]; + }]; +} + #pragma mark - SyncClientProxyDelegate implementation - (void)setResultForPath:(NSString*)path result:(NSString*)result @@ -146,15 +176,14 @@ [FIFinderSyncController defaultController].directoryURLs = _registeredDirectories; } -- (void)setShareMenuTitle:(NSString*)title +- (void)setString:(NSString*)key value:(NSString*)value { - _shareMenuTitle = title; + [_strings setObject:value forKey:key]; } - (void)connectionDidDie { - _shareMenuTitle = nil; - + [_strings removeAllObjects]; [_registeredDirectories removeAllObjects]; // For some reason the FIFinderSync cache doesn't seem to be cleared for the root item when // we reset the directoryURLs (seen on macOS 10.12 at least). diff --git a/shell_integration/MacOSX/common/SyncClientProxy.h b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/SyncClientProxy.h similarity index 95% rename from shell_integration/MacOSX/common/SyncClientProxy.h rename to shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/SyncClientProxy.h index aaaa294b62..8f96334164 100644 --- a/shell_integration/MacOSX/common/SyncClientProxy.h +++ b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/SyncClientProxy.h @@ -20,7 +20,7 @@ - (void)reFetchFileNameCacheForPath:(NSString*)path; - (void)registerPath:(NSString*)path; - (void)unregisterPath:(NSString*)path; -- (void)setShareMenuTitle:(NSString*)title; +- (void)setString:(NSString*)key value:(NSString*)value; - (void)connectionDidDie; @end diff --git a/shell_integration/MacOSX/common/SyncClientProxy.m b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/SyncClientProxy.m similarity index 92% rename from shell_integration/MacOSX/common/SyncClientProxy.m rename to shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/SyncClientProxy.m index 1e89ea6764..b7b64571f7 100644 --- a/shell_integration/MacOSX/common/SyncClientProxy.m +++ b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/SyncClientProxy.m @@ -73,7 +73,7 @@ [_remoteEnd setProtocolForProxy:@protocol(ChannelProtocol)]; // Everything is set up, start querying - [self askOnSocket:@"" query:@"SHARE_MENU_TITLE"]; + [self askOnSocket:@"" query:@"GET_STRINGS"]; } - (void)scheduleRetry @@ -119,8 +119,10 @@ } else if( [[chunks objectAtIndex:0 ] isEqualToString:@"UNREGISTER_PATH"] ) { NSString *path = [chunks objectAtIndex:1]; [_delegate unregisterPath:path]; - } else if( [[chunks objectAtIndex:0 ] isEqualToString:@"SHARE_MENU_TITLE"] ) { - [_delegate setShareMenuTitle:[chunks objectAtIndex:1]]; + } else if( [[chunks objectAtIndex:0 ] isEqualToString:@"GET_STRINGS"] ) { + // BEGIN and END messages, do nothing. + } else if( [[chunks objectAtIndex:0 ] isEqualToString:@"STRING"] ) { + [_delegate setString:[chunks objectAtIndex:1] value:[chunks objectAtIndex:2]]; } else { NSLog(@"SyncState: Unknown command %@", [chunks objectAtIndex:0]); } diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/project.pbxproj b/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/project.pbxproj index f72bfc2d1b..950ac20272 100644 --- a/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/project.pbxproj +++ b/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/project.pbxproj @@ -11,12 +11,12 @@ C2B573D21B1CD94B00303B36 /* main.m in Resources */ = {isa = PBXBuildFile; fileRef = C2B573B91B1CD91E00303B36 /* main.m */; }; C2B573DE1B1CD9CE00303B36 /* FinderSync.m in Sources */ = {isa = PBXBuildFile; fileRef = C2B573DD1B1CD9CE00303B36 /* FinderSync.m */; }; C2B573E21B1CD9CE00303B36 /* FinderSyncExt.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = C2B573D71B1CD9CE00303B36 /* FinderSyncExt.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - C2B573E91B1DA1FB00303B36 /* SyncClientProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = C2B573E81B1DA1FB00303B36 /* SyncClientProxy.m */; }; C2B573F31B1DAD6400303B36 /* error.iconset in Resources */ = {isa = PBXBuildFile; fileRef = C2B573EB1B1DAD6400303B36 /* error.iconset */; }; C2B573F41B1DAD6400303B36 /* ok_swm.iconset in Resources */ = {isa = PBXBuildFile; fileRef = C2B573EC1B1DAD6400303B36 /* ok_swm.iconset */; }; C2B573F51B1DAD6400303B36 /* ok.iconset in Resources */ = {isa = PBXBuildFile; fileRef = C2B573ED1B1DAD6400303B36 /* ok.iconset */; }; C2B573F71B1DAD6400303B36 /* sync.iconset in Resources */ = {isa = PBXBuildFile; fileRef = C2B573EF1B1DAD6400303B36 /* sync.iconset */; }; C2B573F91B1DAD6400303B36 /* warning.iconset in Resources */ = {isa = PBXBuildFile; fileRef = C2B573F11B1DAD6400303B36 /* warning.iconset */; }; + C2C932F01F0BFC6700C8BCB3 /* SyncClientProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = C2C932EF1F0BFC6700C8BCB3 /* SyncClientProxy.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -52,13 +52,13 @@ C2B573DB1B1CD9CE00303B36 /* FinderSyncExt.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = FinderSyncExt.entitlements; sourceTree = ""; }; C2B573DC1B1CD9CE00303B36 /* FinderSync.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FinderSync.h; sourceTree = ""; }; C2B573DD1B1CD9CE00303B36 /* FinderSync.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FinderSync.m; sourceTree = ""; }; - C2B573E71B1DA1FB00303B36 /* SyncClientProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SyncClientProxy.h; sourceTree = ""; }; - C2B573E81B1DA1FB00303B36 /* SyncClientProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SyncClientProxy.m; sourceTree = ""; }; C2B573EB1B1DAD6400303B36 /* error.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = error.iconset; path = ../../icons/nopadding/error.iconset; sourceTree = SOURCE_ROOT; }; C2B573EC1B1DAD6400303B36 /* ok_swm.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = ok_swm.iconset; path = ../../icons/nopadding/ok_swm.iconset; sourceTree = SOURCE_ROOT; }; C2B573ED1B1DAD6400303B36 /* ok.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = ok.iconset; path = ../../icons/nopadding/ok.iconset; sourceTree = SOURCE_ROOT; }; C2B573EF1B1DAD6400303B36 /* sync.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = sync.iconset; path = ../../icons/nopadding/sync.iconset; sourceTree = SOURCE_ROOT; }; C2B573F11B1DAD6400303B36 /* warning.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = warning.iconset; path = ../../icons/nopadding/warning.iconset; sourceTree = SOURCE_ROOT; }; + C2C932EE1F0BFC6700C8BCB3 /* SyncClientProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SyncClientProxy.h; sourceTree = ""; }; + C2C932EF1F0BFC6700C8BCB3 /* SyncClientProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SyncClientProxy.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -82,7 +82,6 @@ C2B573941B1CD88000303B36 = { isa = PBXGroup; children = ( - C2B573E61B1DA1FB00303B36 /* common */, C2B573B31B1CD91E00303B36 /* desktopclient */, C2B573D81B1CD9CE00303B36 /* FinderSyncExt */, C2B573B21B1CD91E00303B36 /* Products */, @@ -118,6 +117,8 @@ C2B573D81B1CD9CE00303B36 /* FinderSyncExt */ = { isa = PBXGroup; children = ( + C2C932EE1F0BFC6700C8BCB3 /* SyncClientProxy.h */, + C2C932EF1F0BFC6700C8BCB3 /* SyncClientProxy.m */, C2B573DC1B1CD9CE00303B36 /* FinderSync.h */, C2B573DD1B1CD9CE00303B36 /* FinderSync.m */, C2B573D91B1CD9CE00303B36 /* Supporting Files */, @@ -139,16 +140,6 @@ name = "Supporting Files"; sourceTree = ""; }; - C2B573E61B1DA1FB00303B36 /* common */ = { - isa = PBXGroup; - children = ( - C2B573E71B1DA1FB00303B36 /* SyncClientProxy.h */, - C2B573E81B1DA1FB00303B36 /* SyncClientProxy.m */, - ); - name = common; - path = ../common; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -284,7 +275,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - C2B573E91B1DA1FB00303B36 /* SyncClientProxy.m in Sources */, + C2C932F01F0BFC6700C8BCB3 /* SyncClientProxy.m in Sources */, C2B573DE1B1CD9CE00303B36 /* FinderSync.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/shell_integration/MacOSX/OwnCloudInjector/English.lproj/InfoPlist.strings b/shell_integration/MacOSX/OwnCloudInjector/English.lproj/InfoPlist.strings deleted file mode 100644 index 88f65cf6ea..0000000000 --- a/shell_integration/MacOSX/OwnCloudInjector/English.lproj/InfoPlist.strings +++ /dev/null @@ -1,2 +0,0 @@ -/* Localized versions of Info.plist keys */ - diff --git a/shell_integration/MacOSX/OwnCloudInjector/Info.plist b/shell_integration/MacOSX/OwnCloudInjector/Info.plist deleted file mode 100644 index ec13d6497d..0000000000 --- a/shell_integration/MacOSX/OwnCloudInjector/Info.plist +++ /dev/null @@ -1,59 +0,0 @@ - - - - - CFBundleDevelopmentRegion - English - CFBundleExecutable - ${EXECUTABLE_NAME} - CFBundleIdentifier - com.owncloud.injector - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - OwnCloudInjector - CFBundlePackageType - osax - CFBundleShortVersionString - 1.0.2 - CFBundleSignature - OWNC - CFBundleVersion - 1.0.2 - OSAScriptingDefinition - OwnCloudInjector.sdef - OSAXHandlers - - Events - - OWNClded - - Context - Process - Handler - HandleLoadedEvent - ThreadSafe - - - OWNCload - - Context - Process - Handler - HandleLoadEvent - ThreadSafe - - - OWNCunld - - Context - Process - Handler - HandleUnloadEvent - ThreadSafe - - - - - - diff --git a/shell_integration/MacOSX/OwnCloudInjector/LNStandardVersionComparator.h b/shell_integration/MacOSX/OwnCloudInjector/LNStandardVersionComparator.h deleted file mode 100644 index 224b81efdf..0000000000 --- a/shell_integration/MacOSX/OwnCloudInjector/LNStandardVersionComparator.h +++ /dev/null @@ -1,35 +0,0 @@ -// -// LNStandardVersionComparator.h -// Sparkle -// -// Created by Andy Matuschak on 12/21/07. -// Copyright 2007 Andy Matuschak. All rights reserved. -// - -#ifndef LNSTANDARDVERSIONCOMPARATOR_H -#define LNSTANDARDVERSIONCOMPARATOR_H - -#import "LNVersionComparisonProtocol.h" - -/*! - @class - @abstract Sparkle's default version comparator. - @discussion This comparator is adapted from MacPAD, by Kevin Ballard. It's "dumb" in that it does essentially string comparison, in components split by character type. - */ -@interface LNStandardVersionComparator : NSObject { } - -/*! - @method - @abstract Returns a singleton instance of the comparator. - */ -+(LNStandardVersionComparator*)defaultComparator; - -/*! - @method - @abstract Compares version strings through textual analysis. - @discussion See the implementation for more details. - */ --(NSComparisonResult)compareVersion:(NSString*)versionA toVersion:(NSString*)versionB; -@end - -#endif diff --git a/shell_integration/MacOSX/OwnCloudInjector/LNStandardVersionComparator.m b/shell_integration/MacOSX/OwnCloudInjector/LNStandardVersionComparator.m deleted file mode 100644 index 5d7e8d8829..0000000000 --- a/shell_integration/MacOSX/OwnCloudInjector/LNStandardVersionComparator.m +++ /dev/null @@ -1,158 +0,0 @@ -// -// LNStandardVersionComparator.m -// Sparkle -// -// Created by Andy Matuschak on 12/21/07. -// Copyright 2007 Andy Matuschak. All rights reserved. -// - -#import -#import "LNStandardVersionComparator.h" - -@implementation LNStandardVersionComparator - -+(LNStandardVersionComparator*) defaultComparator { - static LNStandardVersionComparator* defaultComparator = nil; - - if (defaultComparator == nil) defaultComparator = [[LNStandardVersionComparator alloc] init]; - return defaultComparator; -} - -typedef enum { - kNumberType, - kStringType, - kPeriodType -} SUCharacterType; - --(SUCharacterType) typeOfCharacter:(NSString*)character { - if ([character isEqualToString:@"."]) { - return kPeriodType; - } else if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:[character characterAtIndex:0]]) { - return kNumberType; - } else { - return kStringType; - } -} - --(NSArray*) splitVersionString:(NSString*)version { - NSString* character; - NSMutableString* s; - NSUInteger i, n; - SUCharacterType oldType, newType; - NSMutableArray* parts = [NSMutableArray array]; - - if ([version length] == 0) { - // Nothing to do here - return parts; - } - s = [[[version substringToIndex:1] mutableCopy] autorelease]; - oldType = [self typeOfCharacter:s]; - n = [version length] - 1; - for (i = 1; i <= n; ++i) { - character = [version substringWithRange:NSMakeRange(i, 1)]; - newType = [self typeOfCharacter:character]; - if ((oldType != newType) || (oldType == kPeriodType)) { - // We've reached a new segment - NSString* aPart = [[[NSString alloc] initWithString:s] autorelease]; - [parts addObject:aPart]; - [s setString:character]; - } else { - // Add character to string and continue - [s appendString:character]; - } - oldType = newType; - } - - // Add the last part onto the array - [parts addObject:[NSString stringWithString:s]]; - return parts; -} - --(NSComparisonResult) compareVersion:(NSString*)versionA toVersion:(NSString*)versionB; -{ - NSArray* partsA = [self splitVersionString:versionA]; - NSArray* partsB = [self splitVersionString:versionB]; - - NSString* partA, * partB; - NSUInteger i, n; - int intA, intB; - SUCharacterType typeA, typeB; - - n = MIN([partsA count], [partsB count]); - for (i = 0; i < n; ++i) { - partA = [partsA objectAtIndex:i]; - partB = [partsB objectAtIndex:i]; - - typeA = [self typeOfCharacter:partA]; - typeB = [self typeOfCharacter:partB]; - - // Compare types - if (typeA == typeB) { - // Same type; we can compare - if (typeA == kNumberType) { - intA = [partA intValue]; - intB = [partB intValue]; - if (intA > intB) { - return NSOrderedDescending; - } else if (intA < intB) { - return NSOrderedAscending; - } - } else if (typeA == kStringType) { - NSComparisonResult result = [partA compare:partB]; - if (result != NSOrderedSame) { - return result; - } - } - } else { - // Not the same type? Now we have to do some validity checking - if ((typeA != kStringType) && (typeB == kStringType)) { - // typeA wins - return NSOrderedDescending; - } else if ((typeA == kStringType) && (typeB != kStringType)) { - // typeB wins - return NSOrderedAscending; - } else { - // One is a number and the other is a period. The period is invalid - if (typeA == kNumberType) { - return NSOrderedDescending; - } else { - return NSOrderedAscending; - } - } - } - } - // The versions are equal up to the point where they both still have parts - // Lets check to see if one is larger than the other - if ([partsA count] != [partsB count]) { - // Yep. Lets get the next part of the larger - // n holds the index of the part we want. - NSString* missingPart; - SUCharacterType missingType; - NSComparisonResult shorterResult, largerResult; - - if ([partsA count] > [partsB count]) { - missingPart = [partsA objectAtIndex:n]; - shorterResult = NSOrderedAscending; - largerResult = NSOrderedDescending; - } else { - missingPart = [partsB objectAtIndex:n]; - shorterResult = NSOrderedDescending; - largerResult = NSOrderedAscending; - } - - missingType = [self typeOfCharacter:missingPart]; - // Check the type - if (missingType == kStringType) { - // It's a string. Shorter version wins - return shorterResult; - } else { - // It's a number/period. Larger version wins - return largerResult; - } - } - - // The 2 strings are identical - return NSOrderedSame; -} - -@end diff --git a/shell_integration/MacOSX/OwnCloudInjector/LNVersionComparisonProtocol.h b/shell_integration/MacOSX/OwnCloudInjector/LNVersionComparisonProtocol.h deleted file mode 100644 index 82686ee600..0000000000 --- a/shell_integration/MacOSX/OwnCloudInjector/LNVersionComparisonProtocol.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// LNVersionComparisonProtocol.h -// Sparkle -// -// Created by Andy Matuschak on 12/21/07. -// Copyright 2007 Andy Matuschak. All rights reserved. -// - -#ifndef LNVERSIONCOMPARISONPROTOCOL_H -#define LNVERSIONCOMPARISONPROTOCOL_H - -/*! - @protocol - @abstract Implement this protocol to provide version comparison facilities for Sparkle. - */ -@protocol LNVersionComparison - -/*! - @method - @abstract An abstract method to compare two version strings. - @discussion Should return NSOrderedAscending if b > a, NSOrderedDescending if b < a, and NSOrderedSame if they are equivalent. - */ --(NSComparisonResult)compareVersion:(NSString*)versionA toVersion:(NSString*)versionB; - -@end - -#endif diff --git a/shell_integration/MacOSX/OwnCloudInjector/OwnCloudInjector.m b/shell_integration/MacOSX/OwnCloudInjector/OwnCloudInjector.m deleted file mode 100644 index 97a3f2893b..0000000000 --- a/shell_integration/MacOSX/OwnCloudInjector/OwnCloudInjector.m +++ /dev/null @@ -1,270 +0,0 @@ -#import - -#import "LNStandardVersionComparator.h" - -#define EXPORT __attribute__((visibility("default"))) - -#define WAIT_FOR_APPLE_EVENT_TO_ENTER_HANDLER_IN_SECONDS 1.0 -#define FINDER_MIN_TESTED_VERSION @"10.7" -#define FINDER_MAX_TESTED_VERSION @"10.8.5" -#define LIFERAYNATIVITY_INJECTED_NOTIFICATION @"SyncStateInjectedNotification" - -EXPORT OSErr HandleLoadEvent(const AppleEvent* ev, AppleEvent* reply, long refcon); - -static NSString* globalLock = @"I'm the global lock to prevent concurrent handler executions"; - -// SIMBL-compatible interface -@interface OwnCloudShell : NSObject { } --(void) install; --(void) uninstall; -@end - -// just a dummy class for locating our bundle -@interface OwnCloudInjector : NSObject { } -@end - -@implementation OwnCloudInjector { } -@end - -static bool liferayNativityLoaded = false; -static NSString* liferayNativityBundleName = @"SyncStateFinder"; - -typedef struct { - NSString* location; -} configuration; - -static OSErr AEPutParamString(AppleEvent* event, AEKeyword keyword, NSString* string) { - UInt8* textBuf; - CFIndex length, maxBytes, actualBytes; - - length = CFStringGetLength((CFStringRef)string); - maxBytes = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8); - textBuf = malloc(maxBytes); - if (textBuf) { - CFStringGetBytes((CFStringRef)string, CFRangeMake(0, length), kCFStringEncodingUTF8, 0, true, (UInt8*)textBuf, maxBytes, &actualBytes); - OSErr err = AEPutParamPtr(event, keyword, typeUTF8Text, textBuf, actualBytes); - free(textBuf); - return err; - } else { - return memFullErr; - } -} - -static void reportError(AppleEvent* reply, NSString* msg) { - NSLog(@"LiferayNativityInjector: %@", msg); - AEPutParamString(reply, keyErrorString, msg); -} - -typedef enum { - InvalidBundleType, - LiferayNativityBundleType, -} LNBundleType; - -static OSErr loadBundle(LNBundleType type, AppleEvent* reply, long refcon) { - bool isLoaded = false; - NSString* bundleName = nil; - NSString* targetAppName = nil; - NSString* versionCheckKey = nil; - NSString* maxVersion = nil; - NSString* minVersion = nil; - - switch (type) { - case LiferayNativityBundleType: - isLoaded = liferayNativityLoaded; - bundleName = liferayNativityBundleName; - targetAppName = @"Finder"; - versionCheckKey = @"LiferayNativityFinderVersionCheck"; - maxVersion = FINDER_MAX_TESTED_VERSION; - minVersion = FINDER_MIN_TESTED_VERSION; - break; - default: - NSLog(@"SyncStateInjector: Failed to load bundle for type %d", type); - return 8; - - break; - } - - if (isLoaded) { - // NSLog(@"SyncStateInjector: %@ already loaded.", bundleName); - return noErr; - } - - @try { - NSBundle* mainBundle = [NSBundle mainBundle]; - if (!mainBundle) { - reportError(reply, [NSString stringWithFormat:@"Unable to locate main %@ bundle!", targetAppName]); - return 4; - } - - NSString* mainVersion = [mainBundle objectForInfoDictionaryKey:@"CFBundleVersion"]; - if (!mainVersion || ![mainVersion isKindOfClass:[NSString class]]) { - reportError(reply, [NSString stringWithFormat:@"Unable to determine %@ version!", targetAppName]); - return 5; - } - - // future compatibility check - if (type == LiferayNativityBundleType) { - // in Dock we cannot use NSAlert and similar UI stuff - this would hang the Dock process and cause 100% CPU load - NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; - if ([defaults boolForKey:versionCheckKey]) { - LNStandardVersionComparator* comparator = [LNStandardVersionComparator defaultComparator]; - if (([comparator compareVersion:mainVersion toVersion:maxVersion] == NSOrderedDescending) || - ([comparator compareVersion:mainVersion toVersion:minVersion] == NSOrderedAscending)) { - NSAlert* alert = [NSAlert new]; - [alert setMessageText:[NSString stringWithFormat:@"You have %@ version %@", targetAppName, mainVersion]]; - [alert setInformativeText:[NSString stringWithFormat:@"But %@ was properly tested only with %@ versions in range %@ - %@\n\nYou have probably updated your system and %@ version got bumped by Apple developers.\n\nYou may expect a new LiferayNativity release soon.", bundleName, targetAppName, targetAppName, minVersion, maxVersion]]; - [alert setShowsSuppressionButton:YES]; - [alert addButtonWithTitle:@"Launch LiferayNativity anyway"]; - [alert addButtonWithTitle:@"Cancel"]; - NSInteger res = [alert runModal]; - if ([[alert suppressionButton] state] == NSOnState) { - [defaults setBool:NO forKey:versionCheckKey]; - } - if (res != NSAlertFirstButtonReturn) { - // cancel - return noErr; - } - } - } - } - - NSBundle* liferayNativityInjectorBundle = [NSBundle bundleForClass:[OwnCloudInjector class]]; - NSString* liferayNativityLocation = [liferayNativityInjectorBundle pathForResource:bundleName ofType:@"bundle"]; - NSBundle* pluginBundle = [NSBundle bundleWithPath:liferayNativityLocation]; - if (!pluginBundle) { - reportError(reply, [NSString stringWithFormat:@"Unable to create bundle from path: %@ [%@]", liferayNativityLocation, liferayNativityInjectorBundle]); - return 2; - } - - NSError* error; - if (![pluginBundle loadAndReturnError:&error]) { - reportError(reply, [NSString stringWithFormat:@"Unable to load bundle from path: %@ error: %@", liferayNativityLocation, [error localizedDescription]]); - return 6; - } - - Class principalClass = [pluginBundle principalClass]; - if (!principalClass) { - reportError(reply, [NSString stringWithFormat:@"Unable to retrieve principalClass for bundle: %@", pluginBundle]); - return 3; - } - id principalClassObject = NSClassFromString(NSStringFromClass(principalClass)); - if ([principalClassObject respondsToSelector:@selector(install)]) { - // NSLog(@"SyncStateInjector: Installing %@ ...", bundleName); - [principalClassObject install]; - } - - return noErr; - } @catch (NSException* exception) { - reportError(reply, [NSString stringWithFormat:@"Failed to load %@ with exception: %@", bundleName, exception]); - } - - return 1; -} - -static LNBundleType mainBundleType(AppleEvent* reply) { - @try { - NSBundle* mainBundle = [NSBundle mainBundle]; - if (!mainBundle) { - reportError(reply, [NSString stringWithFormat:@"Unable to locate main bundle!"]); - return InvalidBundleType; - } - - if ([[mainBundle bundleIdentifier] isEqualToString:@"com.apple.finder"]) { - return LiferayNativityBundleType; - } - } @catch (NSException* exception) { - reportError(reply, [NSString stringWithFormat:@"Failed to load main bundle with exception: %@", exception]); - } - - return InvalidBundleType; -} - -EXPORT OSErr HandleLoadEvent(const AppleEvent* ev, AppleEvent* reply, long refcon) { - @synchronized(globalLock) { - @autoreleasepool { - NSBundle* injectorBundle = [NSBundle bundleForClass:[OwnCloudInjector class]]; - NSString* injectorVersion = [injectorBundle objectForInfoDictionaryKey:@"CFBundleVersion"]; - - if (!injectorVersion || ![injectorVersion isKindOfClass:[NSString class]]) { - reportError(reply, [NSString stringWithFormat:@"Unable to determine SyncStateInjector version!"]); - return 7; - } - - @try { - OSErr err = loadBundle(mainBundleType(reply), reply, refcon); - - if (err != noErr) - { - return err; - } - - pid_t pid = [[NSProcessInfo processInfo] processIdentifier]; - - [[NSDistributedNotificationCenter defaultCenter]postNotificationName:LIFERAYNATIVITY_INJECTED_NOTIFICATION object:[[NSBundle mainBundle]bundleIdentifier] userInfo:@{@"pid": @(pid)}]; - - liferayNativityLoaded = true; - - return noErr; - } @catch (NSException* exception) { - reportError(reply, [NSString stringWithFormat:@"Failed to load OwnCloudFinder with exception: %@", exception]); - } - - return 1; - } - } -} - -EXPORT OSErr HandleLoadedEvent(const AppleEvent* ev, AppleEvent* reply, long refcon) { - @synchronized(globalLock) { - @autoreleasepool { - LNBundleType type = mainBundleType(reply); - if ((type == LiferayNativityBundleType) && liferayNativityLoaded) { - return noErr; - } - reportError(reply, @"LiferayNativity not loaded"); - return 1; - } - } -} - -EXPORT OSErr HandleUnloadEvent(const AppleEvent* ev, AppleEvent* reply, long refcon) { - @synchronized(globalLock) { - @autoreleasepool { - @try { - if (!liferayNativityLoaded) { - // NSLog(@"SyncStateInjector: not loaded."); - return noErr; - } - - NSString* bundleName = liferayNativityBundleName; - - NSBundle* liferayNativityInjectorBundle = [NSBundle bundleForClass:[OwnCloudInjector class]]; - NSString* liferayNativityLocation = [liferayNativityInjectorBundle pathForResource:bundleName ofType:@"bundle"]; - NSBundle* pluginBundle = [NSBundle bundleWithPath:liferayNativityLocation]; - if (!pluginBundle) { - reportError(reply, [NSString stringWithFormat:@"Unable to create bundle from path: %@ [%@]", liferayNativityLocation, liferayNativityInjectorBundle]); - return 2; - } - - Class principalClass = [pluginBundle principalClass]; - if (!principalClass) { - reportError(reply, [NSString stringWithFormat:@"Unable to retrieve principalClass for bundle: %@", pluginBundle]); - return 3; - } - id principalClassObject = NSClassFromString(NSStringFromClass(principalClass)); - if ([principalClassObject respondsToSelector:@selector(uninstall)]) { - // NSLog(@"SyncStateInjector: Uninstalling %@ ...", bundleName); - [principalClassObject uninstall]; - } - - liferayNativityLoaded = false; - - return noErr; - } @catch (NSException* exception) { - reportError(reply, [NSString stringWithFormat:@"Failed to unload OwnCloudFinder with exception: %@", exception]); - } - - return 1; - } - } -} \ No newline at end of file diff --git a/shell_integration/MacOSX/OwnCloudInjector/OwnCloudInjector.sdef b/shell_integration/MacOSX/OwnCloudInjector/OwnCloudInjector.sdef deleted file mode 100644 index 3d4c5fdd4e..0000000000 --- a/shell_integration/MacOSX/OwnCloudInjector/OwnCloudInjector.sdef +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/shell_integration/MacOSX/OwnCloudInjector/OwnCloudInjector.xcodeproj/project.pbxproj b/shell_integration/MacOSX/OwnCloudInjector/OwnCloudInjector.xcodeproj/project.pbxproj deleted file mode 100644 index 36dd492824..0000000000 --- a/shell_integration/MacOSX/OwnCloudInjector/OwnCloudInjector.xcodeproj/project.pbxproj +++ /dev/null @@ -1,269 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 0B36CB92182461A10039B237 /* SyncStateFinder.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 0B36CB91182461A10039B237 /* SyncStateFinder.bundle */; }; - 0BD9C38E1778EF450094CF5D /* license.txt in Resources */ = {isa = PBXBuildFile; fileRef = 0BD9C38D1778EF450094CF5D /* license.txt */; }; - 8D576314048677EA00EA77CD /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0AA1909FFE8422F4C02AAC07 /* CoreFoundation.framework */; }; - 8D5B49A804867FD3000E48DA /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8D5B49A704867FD3000E48DA /* InfoPlist.strings */; }; - D6ACBEA2117B7D5600F6691C /* OwnCloudInjector.m in Sources */ = {isa = PBXBuildFile; fileRef = D6ACBE9E117B7D5600F6691C /* OwnCloudInjector.m */; }; - D6ACBEA3117B7D5600F6691C /* LNStandardVersionComparator.m in Sources */ = {isa = PBXBuildFile; fileRef = D6ACBEA0117B7D5600F6691C /* LNStandardVersionComparator.m */; }; - D6ACBEA5117B7D6100F6691C /* OwnCloudInjector.sdef in Resources */ = {isa = PBXBuildFile; fileRef = D6ACBEA4117B7D6100F6691C /* OwnCloudInjector.sdef */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - 089C167EFE841241C02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = ""; }; - 0AA1909FFE8422F4C02AAC07 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = /System/Library/Frameworks/CoreFoundation.framework; sourceTree = ""; }; - 0B36CB91182461A10039B237 /* SyncStateFinder.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; path = SyncStateFinder.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; - 0BD9C38D1778EF450094CF5D /* license.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = license.txt; sourceTree = ""; }; - 8D576317048677EA00EA77CD /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - D60A992314CE37030061AD6D /* SyncStateFinder.osax */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SyncStateFinder.osax; sourceTree = BUILT_PRODUCTS_DIR; }; - D6ACBE9E117B7D5600F6691C /* OwnCloudInjector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OwnCloudInjector.m; sourceTree = ""; }; - D6ACBE9F117B7D5600F6691C /* LNVersionComparisonProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LNVersionComparisonProtocol.h; sourceTree = ""; }; - D6ACBEA0117B7D5600F6691C /* LNStandardVersionComparator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LNStandardVersionComparator.m; sourceTree = ""; }; - D6ACBEA1117B7D5600F6691C /* LNStandardVersionComparator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LNStandardVersionComparator.h; sourceTree = ""; }; - D6ACBEA4117B7D6100F6691C /* OwnCloudInjector.sdef */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = OwnCloudInjector.sdef; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 8D576313048677EA00EA77CD /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 8D576314048677EA00EA77CD /* CoreFoundation.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 089C166AFE841209C02AAC07 /* TotalFinder-osax */ = { - isa = PBXGroup; - children = ( - 08FB77AFFE84173DC02AAC07 /* Source */, - 089C167CFE841241C02AAC07 /* Resources */, - 089C1671FE841209C02AAC07 /* Dependencies */, - D60A992414CE37030061AD6D /* Products */, - ); - indentWidth = 2; - name = "SyncStateFinder-osax"; - sourceTree = ""; - tabWidth = 2; - usesTabs = 0; - }; - 089C1671FE841209C02AAC07 /* Dependencies */ = { - isa = PBXGroup; - children = ( - 0AA1909FFE8422F4C02AAC07 /* CoreFoundation.framework */, - ); - name = Dependencies; - sourceTree = ""; - }; - 089C167CFE841241C02AAC07 /* Resources */ = { - isa = PBXGroup; - children = ( - 0B36CB91182461A10039B237 /* SyncStateFinder.bundle */, - D6ACBEA4117B7D6100F6691C /* OwnCloudInjector.sdef */, - 8D576317048677EA00EA77CD /* Info.plist */, - 8D5B49A704867FD3000E48DA /* InfoPlist.strings */, - 0BD9C38D1778EF450094CF5D /* license.txt */, - ); - name = Resources; - sourceTree = ""; - }; - 08FB77AFFE84173DC02AAC07 /* Source */ = { - isa = PBXGroup; - children = ( - D6ACBE9E117B7D5600F6691C /* OwnCloudInjector.m */, - D6ACBE9F117B7D5600F6691C /* LNVersionComparisonProtocol.h */, - D6ACBEA0117B7D5600F6691C /* LNStandardVersionComparator.m */, - D6ACBEA1117B7D5600F6691C /* LNStandardVersionComparator.h */, - ); - name = Source; - sourceTree = ""; - }; - D60A992414CE37030061AD6D /* Products */ = { - isa = PBXGroup; - children = ( - D60A992314CE37030061AD6D /* SyncStateFinder.osax */, - ); - name = Products; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 8D57630D048677EA00EA77CD /* SyncStateFinder.osax */ = { - isa = PBXNativeTarget; - buildConfigurationList = 1DEB911A08733D790010E9CD /* Build configuration list for PBXNativeTarget "SyncStateFinder.osax" */; - buildPhases = ( - 8D57630F048677EA00EA77CD /* Resources */, - 8D576311048677EA00EA77CD /* Sources */, - 8D576313048677EA00EA77CD /* Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = SyncStateFinder.osax; - productInstallPath = "$(HOME)/Library/Bundles"; - productName = "SyncStateFinder-osax"; - productReference = D60A992314CE37030061AD6D /* SyncStateFinder.osax */; - productType = "com.apple.product-type.bundle"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 089C1669FE841209C02AAC07 /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 0460; - ORGANIZATIONNAME = BinaryAge; - }; - buildConfigurationList = 1DEB911E08733D790010E9CD /* Build configuration list for PBXProject "OwnCloudInjector" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 1; - knownRegions = ( - en, - English, - ); - mainGroup = 089C166AFE841209C02AAC07 /* TotalFinder-osax */; - productRefGroup = D60A992414CE37030061AD6D /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 8D57630D048677EA00EA77CD /* SyncStateFinder.osax */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 8D57630F048677EA00EA77CD /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 0B36CB92182461A10039B237 /* SyncStateFinder.bundle in Resources */, - 8D5B49A804867FD3000E48DA /* InfoPlist.strings in Resources */, - D6ACBEA5117B7D6100F6691C /* OwnCloudInjector.sdef in Resources */, - 0BD9C38E1778EF450094CF5D /* license.txt in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 8D576311048677EA00EA77CD /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - D6ACBEA2117B7D5600F6691C /* OwnCloudInjector.m in Sources */, - D6ACBEA3117B7D5600F6691C /* LNStandardVersionComparator.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - 8D5B49A704867FD3000E48DA /* InfoPlist.strings */ = { - isa = PBXVariantGroup; - children = ( - 089C167EFE841241C02AAC07 /* English */, - ); - name = InfoPlist.strings; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 1DEB911B08733D790010E9CD /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; - COMBINE_HIDPI_IMAGES = YES; - GCC_DYNAMIC_NO_PIC = NO; - INFOPLIST_FILE = Info.plist; - OTHER_LDFLAGS = ( - "-framework", - Foundation, - "-framework", - AppKit, - ); - PRODUCT_NAME = SyncStateFinder; - SKIP_INSTALL = YES; - WRAPPER_EXTENSION = osax; - }; - name = Debug; - }; - 1DEB911C08733D790010E9CD /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; - COMBINE_HIDPI_IMAGES = YES; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - INFOPLIST_FILE = Info.plist; - OTHER_LDFLAGS = ( - "-framework", - Foundation, - "-framework", - AppKit, - ); - PRODUCT_NAME = SyncStateFinder; - WRAPPER_EXTENSION = osax; - }; - name = Release; - }; - 1DEB911F08733D790010E9CD /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CONFIGURATION_TEMP_DIR = "$(PROJECT_TEMP_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)"; - COPY_PHASE_STRIP = NO; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_SYMBOLS_PRIVATE_EXTERN = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.7; - SDKROOT = macosx; - }; - name = Debug; - }; - 1DEB912008733D790010E9CD /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - GCC_SYMBOLS_PRIVATE_EXTERN = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.7; - SDKROOT = macosx; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 1DEB911A08733D790010E9CD /* Build configuration list for PBXNativeTarget "SyncStateFinder.osax" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 1DEB911B08733D790010E9CD /* Debug */, - 1DEB911C08733D790010E9CD /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 1DEB911E08733D790010E9CD /* Build configuration list for PBXProject "OwnCloudInjector" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 1DEB911F08733D790010E9CD /* Debug */, - 1DEB912008733D790010E9CD /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 089C1669FE841209C02AAC07 /* Project object */; -} diff --git a/shell_integration/MacOSX/OwnCloudInjector/OwnCloudInjector.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/shell_integration/MacOSX/OwnCloudInjector/OwnCloudInjector.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 25ab3b4240..0000000000 --- a/shell_integration/MacOSX/OwnCloudInjector/OwnCloudInjector.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/shell_integration/MacOSX/OwnCloudInjector/OwnCloudInjector.xcodeproj/xcshareddata/xcschemes/SyncStateFinder.osax.xcscheme b/shell_integration/MacOSX/OwnCloudInjector/OwnCloudInjector.xcodeproj/xcshareddata/xcschemes/SyncStateFinder.osax.xcscheme deleted file mode 100644 index ad6157683d..0000000000 --- a/shell_integration/MacOSX/OwnCloudInjector/OwnCloudInjector.xcodeproj/xcshareddata/xcschemes/SyncStateFinder.osax.xcscheme +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/shell_integration/MacOSX/OwnCloudInjector/OwnCloudInjector.xcodeproj/xcuserdata/guruz.xcuserdatad/xcschemes/xcschememanagement.plist b/shell_integration/MacOSX/OwnCloudInjector/OwnCloudInjector.xcodeproj/xcuserdata/guruz.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index 2f490c9952..0000000000 --- a/shell_integration/MacOSX/OwnCloudInjector/OwnCloudInjector.xcodeproj/xcuserdata/guruz.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,14 +0,0 @@ - - - - - SuppressBuildableAutocreation - - 8D57630D048677EA00EA77CD - - primary - - - - - diff --git a/shell_integration/MacOSX/OwnCloudInjector/license.txt b/shell_integration/MacOSX/OwnCloudInjector/license.txt deleted file mode 100644 index 581d9194fd..0000000000 --- a/shell_integration/MacOSX/OwnCloudInjector/license.txt +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2010-2013, BinaryAge Limited -Contributors: https://github.com/binaryage/totalfinder-osax/contributors -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of Antonin Hildebrand nor the - names of other contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY BINARYAGE LIMITED ``AS IS'' AND ANY -EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL Antonin Hildebrand BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/shell_integration/MacOSX/check.scpt b/shell_integration/MacOSX/check.scpt deleted file mode 100644 index 2f111cc435..0000000000 --- a/shell_integration/MacOSX/check.scpt +++ /dev/null @@ -1,9 +0,0 @@ -tell application "Finder" - try - «event OWNClded» - set the result to 0 - on error msg number code - set the result to code - end try -end tell - diff --git a/shell_integration/MacOSX/deploy.sh b/shell_integration/MacOSX/deploy.sh deleted file mode 100644 index ebeeba25e4..0000000000 --- a/shell_integration/MacOSX/deploy.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh - -echo "Not used anymore, please do (from build dir) (this is <= 10.9 only)" -echo sudo cp -r ./shell_integration/MacOSX/Release/SyncStateFinder.osax /Library/ScriptingAdditions/ -echo killall Finder -exit 1 - -SELFPATH=`dirname $0` -# osascript $SELFPATH/unload.scpt - -sudo rm -rf /Library/ScriptingAdditions/SyncStateFinder.osax -# Klaas' machine -OSAXDIR=$HOME/Library/Developer/Xcode/DerivedData/OwnCloud-*/Build/Products/Debug/SyncStateFinder.osax -[ -d $OSAXDIR ] ||OSAXDIR=$HOME/Library/Developer/Xcode/DerivedData/OwnCloud-*/Build/Intermediates/ArchiveIntermediates/SyncStateFinder.osax/IntermediateBuildFilesPath/UninstalledProducts/SyncStateFinder.osax - -# Markus' machine -[ -d $OSAXDIR ] || echo "OSAX does not exist" -[ -d $OSAXDIR ] && sudo cp -rv $OSAXDIR /Library/ScriptingAdditions/ - -sudo killall Finder -sleep 1 -osascript $SELFPATH/load.scpt -osascript $SELFPATH/check.scpt - diff --git a/shell_integration/MacOSX/load.scpt b/shell_integration/MacOSX/load.scpt deleted file mode 100644 index 823d2b42a6..0000000000 --- a/shell_integration/MacOSX/load.scpt +++ /dev/null @@ -1,6 +0,0 @@ -tell application "Finder" - try - «event OWNCload» - end try -end tell - diff --git a/shell_integration/MacOSX/loadPlugin.sh b/shell_integration/MacOSX/loadPlugin.sh deleted file mode 100755 index 7fd14e2ea2..0000000000 --- a/shell_integration/MacOSX/loadPlugin.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -osascript -e 'tell application "Finder" \ - try \ - «event OWNCload» \ - end try \ - end tell' - diff --git a/shell_integration/MacOSX/unload.scpt b/shell_integration/MacOSX/unload.scpt deleted file mode 100644 index 620c1a053d..0000000000 --- a/shell_integration/MacOSX/unload.scpt +++ /dev/null @@ -1,6 +0,0 @@ -tell application "Finder" - try - «event OWNCunld» - end try -end tell - diff --git a/shell_integration/dolphin/ownclouddolphinactionplugin.cpp b/shell_integration/dolphin/ownclouddolphinactionplugin.cpp index dfb2bdff77..c47cb783d5 100644 --- a/shell_integration/dolphin/ownclouddolphinactionplugin.cpp +++ b/shell_integration/dolphin/ownclouddolphinactionplugin.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include "ownclouddolphinpluginhelper.h" @@ -53,12 +54,31 @@ public: } )) return {}; - auto act = new QAction(parentWidget); - act->setText(helper->shareActionString()); - connect(act, &QAction::triggered, this, [localFile, helper] { + auto menuaction = new QAction(parentWidget); + menuaction->setText(helper->contextMenuTitle()); + auto menu = new QMenu(parentWidget); + menuaction->setMenu(menu); + + auto shareAction = menu->addAction(helper->shareActionTitle()); + connect(shareAction, &QAction::triggered, this, [localFile, helper] { helper->sendCommand(QByteArray("SHARE:"+localFile.toUtf8()+"\n")); } ); - return { act }; + + if (!helper->copyPrivateLinkTitle().isEmpty()) { + auto copyPrivateLinkAction = menu->addAction(helper->copyPrivateLinkTitle()); + connect(copyPrivateLinkAction, &QAction::triggered, this, [localFile, helper] { + helper->sendCommand(QByteArray("COPY_PRIVATE_LINK:" + localFile.toUtf8() + "\n")); + }); + } + + if (!helper->emailPrivateLinkTitle().isEmpty()) { + auto emailPrivateLinkAction = menu->addAction(helper->emailPrivateLinkTitle()); + connect(emailPrivateLinkAction, &QAction::triggered, this, [localFile, helper] { + helper->sendCommand(QByteArray("EMAIL_PRIVATE_LINK:" + localFile.toUtf8() + "\n")); + }); + } + + return { menuaction }; } }; diff --git a/shell_integration/dolphin/ownclouddolphinpluginhelper.cpp b/shell_integration/dolphin/ownclouddolphinpluginhelper.cpp index 68c2a9c297..ae1705220f 100644 --- a/shell_integration/dolphin/ownclouddolphinpluginhelper.cpp +++ b/shell_integration/dolphin/ownclouddolphinpluginhelper.cpp @@ -59,7 +59,7 @@ void OwncloudDolphinPluginHelper::sendCommand(const char* data) void OwncloudDolphinPluginHelper::slotConnected() { - sendCommand("SHARE_MENU_TITLE:\n"); + sendCommand("GET_STRINGS:\n"); } void OwncloudDolphinPluginHelper::tryConnect() @@ -92,9 +92,11 @@ void OwncloudDolphinPluginHelper::slotReadyRead() QString file = QString::fromUtf8(line.constData() + col + 1, line.size() - col - 1); _paths.append(file); continue; - } else if (line.startsWith("SHARE_MENU_TITLE:")) { - auto col = line.indexOf(':'); - _shareActionString = QString::fromUtf8(line.constData() + col + 1, line.size() - col - 1); + } else if (line.startsWith("STRING:")) { + auto args = QString::fromUtf8(line).split(QLatin1Char(':')); + if (args.size() >= 3) { + _strings[args[1]] = args.mid(2).join(QLatin1Char(':')); + } continue; } emit commandRecieved(line); diff --git a/shell_integration/dolphin/ownclouddolphinpluginhelper.h b/shell_integration/dolphin/ownclouddolphinpluginhelper.h index 26762caaf4..294d1969d4 100644 --- a/shell_integration/dolphin/ownclouddolphinpluginhelper.h +++ b/shell_integration/dolphin/ownclouddolphinpluginhelper.h @@ -28,11 +28,22 @@ class OWNCLOUDDOLPHINPLUGINHELPER_EXPORT OwncloudDolphinPluginHelper : public QO public: static OwncloudDolphinPluginHelper *instance(); - QString shareActionString() const { return _shareActionString; } bool isConnected() const; void sendCommand(const char *data); QVector paths() const { return _paths; } + QString contextMenuTitle() const + { + return _strings.value("CONTEXT_MENU_TITLE", "ownCloud"); + } + QString shareActionTitle() const + { + return _strings.value("SHARE_MENU_TITLE", "Share..."); + } + + QString copyPrivateLinkTitle() const { return _strings["COPY_PRIVATE_LINK_MENU_TITLE"]; } + QString emailPrivateLinkTitle() const { return _strings["EMAIL_PRIVATE_LINK_MENU_TITLE"]; } + signals: void commandRecieved(const QByteArray &cmd); @@ -47,6 +58,7 @@ private: QLocalSocket _socket; QByteArray _line; QVector _paths; - QString _shareActionString; QBasicTimer _connectTimer; + + QMap _strings; }; diff --git a/shell_integration/nautilus/syncstate.py b/shell_integration/nautilus/syncstate.py index 7fb35b80c6..082be4e227 100644 --- a/shell_integration/nautilus/syncstate.py +++ b/shell_integration/nautilus/syncstate.py @@ -95,6 +95,9 @@ class SocketConnect(GObject.GObject): print("Setting connected to %r." % self.connected ) self._watch_id = GObject.io_add_watch(self._sock, GObject.IO_IN, self._handle_notify) print("Socket watch id: " + str(self._watch_id)) + + self.sendCommand('GET_STRINGS:\n') + return False # Don't run again except Exception as e: print("Could not connect to unix socket. " + str(e)) @@ -153,6 +156,13 @@ class MenuExtension(GObject.GObject, Nautilus.MenuProvider): def __init__(self): GObject.GObject.__init__(self) + self.strings = {} + socketConnect.addListener(self.handle_commands) + + def handle_commands(self, action, args): + if action == 'STRING': + self.strings[args[0]] = ':'.join(args[1:]) + def check_registered_paths(self, filename): topLevelFolder = False internalFile = False @@ -178,7 +188,6 @@ class MenuExtension(GObject.GObject, Nautilus.MenuProvider): if len(files) != 1: return file = files[0] - items = [] filename = get_local_path(file.get_uri()) # Check if its a folder (ends with an /), if yes add a "/" @@ -190,12 +199,14 @@ class MenuExtension(GObject.GObject, Nautilus.MenuProvider): # Check if toplevel folder, we need to ignore those as they cannot be shared topLevelFolder, internalFile = self.check_registered_paths(filename) if topLevelFolder or not internalFile: - return items + return [] entry = socketConnect.nautilusVFSFile_table.get(filename) if not entry: - return items + return [] + # Currently 'sharable' also controls access to private link actions, + # and we definitely don't want to show them for IGNORED. shareable = False state = entry['state'] state_ok = state.startswith('OK') @@ -212,22 +223,42 @@ class MenuExtension(GObject.GObject, Nautilus.MenuProvider): break if not shareable: - return items + return [] - # Create a menu item - labelStr = "Share with " + appname + "..." - item = Nautilus.MenuItem(name='NautilusPython::ShareItem', label=labelStr, - tip='Share file {} through {}'.format(file.get_name(), appname) ) - item.connect("activate", self.menu_share, file) - items.append(item) + # Set up the 'ownCloud...' submenu + item_owncloud = Nautilus.MenuItem( + name='IntegrationMenu', label=self.strings.get('CONTEXT_MENU_TITLE', appname)) + menu = Nautilus.Menu() + item_owncloud.set_submenu(menu) - return items + # Add share menu option + item = Nautilus.MenuItem( + name='NautilusPython::ShareItem', + label=self.strings.get('SHARE_MENU_TITLE', 'Share...')) + item.connect("activate", self.context_menu_action, 'SHARE', file) + menu.append_item(item) + + # Add permalink menu options, but hide these options for older clients + # that don't have these actions. + if 'COPY_PRIVATE_LINK_MENU_TITLE' in self.strings: + item_copyprivatelink = Nautilus.MenuItem( + name='CopyPrivateLink', label=self.strings.get('COPY_PRIVATE_LINK_MENU_TITLE', 'Copy private link to clipboard')) + item_copyprivatelink.connect("activate", self.context_menu_action, 'COPY_PRIVATE_LINK', file) + menu.append_item(item_copyprivatelink) + + if 'EMAIL_PRIVATE_LINK_MENU_TITLE' in self.strings: + item_emailprivatelink = Nautilus.MenuItem( + name='EmailPrivateLink', label=self.strings.get('EMAIL_PRIVATE_LINK_MENU_TITLE', 'Send private link by email...')) + item_emailprivatelink.connect("activate", self.context_menu_action, 'EMAIL_PRIVATE_LINK', file) + menu.append_item(item_emailprivatelink) + + return [item_owncloud] - def menu_share(self, menu, file): + def context_menu_action(self, menu, action, file): filename = get_local_path(file.get_uri()) - print("Share file " + filename) - socketConnect.sendCommand("SHARE:" + filename + "\n") + print("Context menu: " + action + ' ' + filename) + socketConnect.sendCommand(action + ":" + filename + "\n") class SyncStateExtension(GObject.GObject, Nautilus.ColumnProvider, Nautilus.InfoProvider): diff --git a/shell_integration/windows/OCContextMenu/OCClientInterface.cpp b/shell_integration/windows/OCContextMenu/OCClientInterface.cpp index 05b23ec09a..6c6396fa5e 100644 --- a/shell_integration/windows/OCContextMenu/OCClientInterface.cpp +++ b/shell_integration/windows/OCContextMenu/OCClientInterface.cpp @@ -45,7 +45,7 @@ OCClientInterface::ContextMenuInfo OCClientInterface::FetchInfo() if (!socket.Connect(pipename)) { return {}; } - socket.SendMsg(L"SHARE_MENU_TITLE\n"); + socket.SendMsg(L"GET_STRINGS\n"); ContextMenuInfo info; std::wstring response; @@ -56,9 +56,21 @@ OCClientInterface::ContextMenuInfo OCClientInterface::FetchInfo() wstring responsePath = response.substr(14); // length of REGISTER_PATH info.watchedDirectories.push_back(responsePath); } - else if (StringUtil::begins_with(response, wstring(L"SHARE_MENU_TITLE:"))) { - info.shareMenuTitle = response.substr(17); // length of SHARE_MENU_TITLE: - break; // Stop once we received the last sent request + else if (StringUtil::begins_with(response, wstring(L"STRING:"))) { + wstring stringName, stringValue; + if (!StringUtil::extractChunks(response, stringName, stringValue)) + continue; + if (stringName == L"SHARE_MENU_TITLE") + info.shareMenuTitle = move(stringValue); + else if (stringName == L"CONTEXT_MENU_TITLE") + info.contextMenuTitle = move(stringValue); + else if (stringName == L"COPY_PRIVATE_LINK_MENU_TITLE") + info.copyLinkMenuTitle = move(stringValue); + else if (stringName == L"EMAIL_PRIVATE_LINK_MENU_TITLE") + info.emailLinkMenuTitle = move(stringValue); + } + else if (StringUtil::begins_with(response, wstring(L"GET_STRINGS:END"))) { + break; // Stop once we completely received the last sent request } } else { @@ -69,7 +81,22 @@ OCClientInterface::ContextMenuInfo OCClientInterface::FetchInfo() return info; } -void OCClientInterface::ShareObject(const std::wstring &path) +void OCClientInterface::RequestShare(const std::wstring &path) +{ + SendRequest(L"SHARE", path); +} + +void OCClientInterface::RequestCopyLink(const std::wstring &path) +{ + SendRequest(L"COPY_PRIVATE_LINK", path); +} + +void OCClientInterface::RequestEmailLink(const std::wstring &path) +{ + SendRequest(L"EMAIL_PRIVATE_LINK", path); +} + +void OCClientInterface::SendRequest(wchar_t *verb, const std::wstring &path) { auto pipename = CommunicationSocket::DefaultPipePath(); @@ -82,7 +109,7 @@ void OCClientInterface::ShareObject(const std::wstring &path) } wchar_t msg[SOCK_BUFFER] = { 0 }; - if (SUCCEEDED(StringCchPrintf(msg, SOCK_BUFFER, L"SHARE:%s\n", path.c_str()))) + if (SUCCEEDED(StringCchPrintf(msg, SOCK_BUFFER, L"%s:%s\n", verb, path.c_str()))) { socket.SendMsg(msg); } diff --git a/shell_integration/windows/OCContextMenu/OCClientInterface.h b/shell_integration/windows/OCContextMenu/OCClientInterface.h index 00d4470eaf..74e6364fec 100644 --- a/shell_integration/windows/OCContextMenu/OCClientInterface.h +++ b/shell_integration/windows/OCContextMenu/OCClientInterface.h @@ -45,10 +45,19 @@ class OCClientInterface public: struct ContextMenuInfo { std::vector watchedDirectories; + std::wstring contextMenuTitle; std::wstring shareMenuTitle; + std::wstring copyLinkMenuTitle; + std::wstring emailLinkMenuTitle; }; static ContextMenuInfo FetchInfo(); - static void ShareObject(const std::wstring &path); + + static void RequestShare(const std::wstring &path); + static void RequestCopyLink(const std::wstring &path); + static void RequestEmailLink(const std::wstring &path); + +private: + static void SendRequest(wchar_t *verb, const std::wstring &path); }; #endif //ABSTRACTSOCKETHANDLER_H diff --git a/shell_integration/windows/OCContextMenu/OCContextMenu.cpp b/shell_integration/windows/OCContextMenu/OCContextMenu.cpp index a5558fc0a3..bda86b86d8 100644 --- a/shell_integration/windows/OCContextMenu/OCContextMenu.cpp +++ b/shell_integration/windows/OCContextMenu/OCContextMenu.cpp @@ -25,19 +25,12 @@ extern HINSTANCE g_hInst; extern long g_cDllRef; -#define IDM_SHARE 0 - - +#define IDM_SHARE 0 +#define IDM_COPYLINK 1 +#define IDM_EMAILLINK 2 OCContextMenu::OCContextMenu(void) : m_cRef(1) - , m_pszMenuText(L"&Share") - , m_pszVerb("ocshare") - , m_pwszVerb(L"ocshare") - , m_pszVerbCanonicalName("OCShareViaOC") - , m_pwszVerbCanonicalName(L"OCShareViaOC") - , m_pszVerbHelpText("Share via ownCloud") - , m_pwszVerbHelpText(L"Share via ownCloud") { InterlockedIncrement(&g_cDllRef); } @@ -48,9 +41,19 @@ OCContextMenu::~OCContextMenu(void) } -void OCContextMenu::OnVerbDisplayFileName(HWND hWnd) +void OCContextMenu::OnVerbShare(HWND hWnd) { - OCClientInterface::ShareObject(std::wstring(m_szSelectedFile)); + OCClientInterface::RequestShare(std::wstring(m_szSelectedFile)); +} + +void OCContextMenu::OnVerbCopyLink(HWND hWnd) +{ + OCClientInterface::RequestCopyLink(std::wstring(m_szSelectedFile)); +} + +void OCContextMenu::OnVerbEmailLink(HWND hWnd) +{ + OCClientInterface::RequestEmailLink(std::wstring(m_szSelectedFile)); } @@ -164,29 +167,61 @@ IFACEMETHODIMP OCContextMenu::QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(0)); } - InsertSeperator(hMenu, indexMenu); - indexMenu++; + InsertSeperator(hMenu, indexMenu++); - assert(!info.shareMenuTitle.empty()); - MENUITEMINFO mii = { sizeof(mii) }; - mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID | MIIM_STATE; - mii.wID = idCmdFirst + IDM_SHARE; - mii.fType = MFT_STRING; - mii.dwTypeData = &info.shareMenuTitle[0]; - mii.fState = MFS_ENABLED; - if (!InsertMenuItem(hMenu, indexMenu, TRUE, &mii)) + HMENU hSubmenu = CreateMenu(); { - return HRESULT_FROM_WIN32(GetLastError()); - } + MENUITEMINFO mii = { sizeof(mii) }; + mii.fMask = MIIM_SUBMENU | MIIM_FTYPE | MIIM_STRING; + mii.hSubMenu = hSubmenu; + mii.fType = MFT_STRING; + mii.dwTypeData = &info.contextMenuTitle[0]; - indexMenu++; - InsertSeperator(hMenu, indexMenu); + if (!InsertMenuItem(hMenu, indexMenu++, TRUE, &mii)) + return HRESULT_FROM_WIN32(GetLastError()); + } + InsertSeperator(hMenu, indexMenu++); + + UINT indexSubMenu = 0; + { + assert(!info.shareMenuTitle.empty()); + MENUITEMINFO mii = { sizeof(mii) }; + mii.fMask = MIIM_ID | MIIM_FTYPE | MIIM_STRING; + mii.wID = idCmdFirst + IDM_SHARE; + mii.fType = MFT_STRING; + mii.dwTypeData = &info.shareMenuTitle[0]; + + if (!InsertMenuItem(hSubmenu, indexSubMenu++, TRUE, &mii)) + return HRESULT_FROM_WIN32(GetLastError()); + } + { + assert(!info.copyLinkMenuTitle.empty()); + MENUITEMINFO mii = { sizeof(mii) }; + mii.fMask = MIIM_ID | MIIM_FTYPE | MIIM_STRING; + mii.wID = idCmdFirst + IDM_COPYLINK; + mii.fType = MFT_STRING; + mii.dwTypeData = &info.copyLinkMenuTitle[0]; + + if (!InsertMenuItem(hSubmenu, indexSubMenu++, TRUE, &mii)) + return HRESULT_FROM_WIN32(GetLastError()); + } + { + assert(!info.emailLinkMenuTitle.empty()); + MENUITEMINFO mii = { sizeof(mii) }; + mii.fMask = MIIM_ID | MIIM_FTYPE | MIIM_STRING; + mii.wID = idCmdFirst + IDM_EMAILLINK; + mii.fType = MFT_STRING; + mii.dwTypeData = &info.emailLinkMenuTitle[0]; + + if (!InsertMenuItem(hSubmenu, indexSubMenu++, TRUE, &mii)) + return HRESULT_FROM_WIN32(GetLastError()); + } // Return an HRESULT value with the severity set to SEVERITY_SUCCESS. // Set the code value to the offset of the largest command identifier // that was assigned, plus one (1). - return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(IDM_SHARE + 1)); + return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(IDM_EMAILLINK + 1)); } IFACEMETHODIMP OCContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO pici) @@ -197,12 +232,16 @@ IFACEMETHODIMP OCContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO pici) if (HIWORD(((CMINVOKECOMMANDINFOEX*)pici)->lpVerbW)) { // Is the verb supported by this context menu extension? - if (StrCmpIW(((CMINVOKECOMMANDINFOEX*)pici)->lpVerbW, m_pwszVerb) == 0) - { - OnVerbDisplayFileName(pici->hwnd); + if (StrCmpIW(((CMINVOKECOMMANDINFOEX*)pici)->lpVerbW, L"ocshare") == 0) { + OnVerbShare(pici->hwnd); } - else - { + else if (StrCmpIW(((CMINVOKECOMMANDINFOEX*)pici)->lpVerbW, L"occopylink") == 0) { + OnVerbCopyLink(pici->hwnd); + } + else if (StrCmpIW(((CMINVOKECOMMANDINFOEX*)pici)->lpVerbW, L"ocemaillink") == 0) { + OnVerbEmailLink(pici->hwnd); + } + else { // If the verb is not recognized by the context menu handler, it // must return E_FAIL to allow it to be passed on to the other // context menu handlers that might implement that verb. @@ -216,12 +255,16 @@ IFACEMETHODIMP OCContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO pici) { // Is the command identifier offset supported by this context menu // extension? - if (LOWORD(pici->lpVerb) == IDM_SHARE) - { - OnVerbDisplayFileName(pici->hwnd); + if (LOWORD(pici->lpVerb) == IDM_SHARE) { + OnVerbShare(pici->hwnd); } - else - { + else if (LOWORD(pici->lpVerb) == IDM_COPYLINK) { + OnVerbCopyLink(pici->hwnd); + } + else if (LOWORD(pici->lpVerb) == IDM_EMAILLINK) { + OnVerbEmailLink(pici->hwnd); + } + else { // If the verb is not recognized by the context menu handler, it // must return E_FAIL to allow it to be passed on to the other // context menu handlers that might implement that verb. @@ -237,31 +280,33 @@ IFACEMETHODIMP OCContextMenu::GetCommandString(UINT_PTR idCommand, { HRESULT hr = E_INVALIDARG; - if (idCommand == IDM_SHARE) - { - switch (uFlags) - { - case GCS_HELPTEXTW: - // Only useful for pre-Vista versions of Windows that have a - // Status bar. - hr = StringCchCopy(reinterpret_cast(pszName), cchMax, - m_pwszVerbHelpText); - break; - - case GCS_VERBW: - // GCS_VERBW is an optional feature that enables a caller to - // discover the canonical name for the verb passed in through + switch (idCommand) { + case IDM_SHARE: + if (uFlags == GCS_VERBW) { + // GCS_VERBW is an optional feature that enables a caller to + // discover the canonical name for the verb passed in through // idCommand. hr = StringCchCopy(reinterpret_cast(pszName), cchMax, - m_pwszVerbCanonicalName); - break; - - default: - hr = S_OK; + L"OCShareViaOC"); } + break; + case IDM_COPYLINK: + if (uFlags == GCS_VERBW) { + hr = StringCchCopy(reinterpret_cast(pszName), cchMax, + L"OCCopyLink"); + } + break; + case IDM_EMAILLINK: + if (uFlags == GCS_VERBW) { + hr = StringCchCopy(reinterpret_cast(pszName), cchMax, + L"OCEmailLink"); + } + break; + default: + break; } - // If the command (idCommand) is not supported by this context menu + // If the idCommand or uFlags is not supported by this context menu // extension handler, return E_INVALIDARG. return hr; diff --git a/shell_integration/windows/OCContextMenu/OCContextMenu.h b/shell_integration/windows/OCContextMenu/OCContextMenu.h index 49834fce05..7fe3e2adea 100644 --- a/shell_integration/windows/OCContextMenu/OCContextMenu.h +++ b/shell_integration/windows/OCContextMenu/OCContextMenu.h @@ -46,8 +46,10 @@ private: // The name of the selected file. wchar_t m_szSelectedFile[MAX_PATH]; - // The method that handles the "display" verb. - void OnVerbDisplayFileName(HWND hWnd); + // The method that handles the "ocshare" verb. + void OnVerbShare(HWND hWnd); + void OnVerbCopyLink(HWND hWnd); + void OnVerbEmailLink(HWND hWnd); PWSTR m_pszMenuText; PCSTR m_pszVerb; diff --git a/shell_integration/windows/OCUtil/RemotePathChecker.cpp b/shell_integration/windows/OCUtil/RemotePathChecker.cpp index 4a13c1cd56..37caee1824 100644 --- a/shell_integration/windows/OCUtil/RemotePathChecker.cpp +++ b/shell_integration/windows/OCUtil/RemotePathChecker.cpp @@ -111,17 +111,10 @@ void RemotePathChecker::workerThreadLoop() } else if (StringUtil::begins_with(response, wstring(L"STATUS:")) || StringUtil::begins_with(response, wstring(L"BROADCAST:"))) { - auto statusBegin = response.find(L':', 0); - assert(statusBegin != std::wstring::npos); - - auto statusEnd = response.find(L':', statusBegin + 1); - if (statusEnd == std::wstring::npos) { - // the command do not contains two colon? + wstring responseStatus, responsePath; + if (!StringUtil::extractChunks(response, responseStatus, responsePath)) continue; - } - auto responseStatus = response.substr(statusBegin+1, statusEnd - statusBegin-1); - auto responsePath = response.substr(statusEnd+1); auto state = _StrToFileState(responseStatus); bool wasAsked = asked.erase(responsePath) > 0; diff --git a/shell_integration/windows/OCUtil/StringUtil.h b/shell_integration/windows/OCUtil/StringUtil.h index 0f52d51dd6..8ec325aa94 100644 --- a/shell_integration/windows/OCUtil/StringUtil.h +++ b/shell_integration/windows/OCUtil/StringUtil.h @@ -17,6 +17,7 @@ #pragma once #include +#include class __declspec(dllexport) StringUtil { public: @@ -44,6 +45,22 @@ public: return (childLength == parentLength || childLength > parentLength && (child[parentLength] == L'\\' || child[parentLength - 1] == L'\\')) && wcsncmp(child, parent, parentLength) == 0; } + + static bool extractChunks(const std::wstring &source, std::wstring &secondChunk, std::wstring &thirdChunk) { + auto statusBegin = source.find(L':', 0); + assert(statusBegin != std::wstring::npos); + + auto statusEnd = source.find(L':', statusBegin + 1); + if (statusEnd == std::wstring::npos) { + // the command do not contains two colon? + return false; + } + + // Assume the caller extracted the chunk before the first colon. + secondChunk = source.substr(statusBegin + 1, statusEnd - statusBegin - 1); + thirdChunk = source.substr(statusEnd + 1); + return true; + } }; #endif // STRINGUTIL_H diff --git a/shell_integration/windows/OCUtil/Version.h b/shell_integration/windows/OCUtil/Version.h index 4010a60a10..d2c83b060a 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 45 +#define OCEXT_BUILD_NUM 46 #define STRINGIZE2(s) #s #define STRINGIZE(s) STRINGIZE2(s) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1b53279d22..3cbacd537f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,19 +4,6 @@ endif() set(synclib_NAME ${APPLICATION_EXECUTABLE}sync) -include(QtVersionAbstraction) -setup_qt() -if (${Qt5Core_VERSION_MAJOR} EQUAL "5") - if (${Qt5Core_VERSION_MINOR} EQUAL "6" OR ${Qt5Core_VERSION_MINOR} GREATER 6) - else() - message(STATUS "If possible compile me with Qt 5.6 or higher.") - endif() -endif() - -if (APPLE) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") -endif() - if(NOT TOKEN_AUTH_ONLY) find_package(Qt5Keychain REQUIRED) endif() diff --git a/src/cmd/cmd.cpp b/src/cmd/cmd.cpp index 5baeb8a8be..8fa1f3ba49 100644 --- a/src/cmd/cmd.cpp +++ b/src/cmd/cmd.cpp @@ -15,6 +15,7 @@ */ #include +#include #include #include #include @@ -311,7 +312,7 @@ int main(int argc, char **argv) qputenv("OPENSSL_CONF", opensslConf.toLocal8Bit()); #endif - qsrand(QTime::currentTime().msec() * QCoreApplication::applicationPid()); + qsrand(std::random_device()()); CmdOptions options; options.silent = false; @@ -425,7 +426,6 @@ int main(int argc, char **argv) account->setCredentials(cred); account->setSslErrorHandler(sslErrorHandler); -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) //obtain capabilities using event loop QEventLoop loop; @@ -440,7 +440,11 @@ int main(int argc, char **argv) job->start(); loop.exec(); -#endif + + if (job->reply()->error() != QNetworkReply::NoError){ + std::cout<<"Error connecting to server\n"; + return EXIT_FAILURE; + } // much lower age than the default since this utility is usually made to be run right after a change in the tests SyncEngine::minimumFileAgeForUpload = 0; diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 643bb932cd..2157d3cd68 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -21,6 +21,7 @@ set(client_UI ignorelisteditor.ui networksettings.ui protocolwidget.ui + issueswidget.ui activitywidget.ui synclogdialog.ui settingsdialog.ui @@ -64,6 +65,7 @@ set(client_SRCS owncloudgui.cpp owncloudsetupwizard.cpp protocolwidget.cpp + issueswidget.cpp activitydata.cpp activitylistmodel.cpp activitywidget.cpp @@ -92,6 +94,8 @@ set(client_SRCS notificationwidget.cpp notificationconfirmjob.cpp servernotificationhandler.cpp + guiutility.cpp + elidedlabel.cpp creds/credentialsfactory.cpp creds/httpcredentialsgui.cpp creds/oauth.cpp @@ -127,7 +131,6 @@ IF( APPLE ) list(APPEND client_SRCS settingsdialogmac.cpp) list(APPEND client_SRCS socketapisocket_mac.mm) list(APPEND client_SRCS systray.mm) - list(APPEND client_SRCS clipboard.mm) if(SPARKLE_FOUND) # Define this, we need to check in updater.cpp @@ -229,7 +232,7 @@ if (NOT DEFINED APPLICATION_ICON_NAME) set(APPLICATION_ICON_NAME ${APPLICATION_SHORTNAME}) endif() -kde4_add_app_icon( ownCloud "${theme_dir}/colored/${APPLICATION_ICON_NAME}-icon*.png") +kde4_add_app_icon( ownCloud "${theme_dir}/colored/${APPLICATION_ICON_NAME}-*.png") list(APPEND final_src ${ownCloud}) set(ownCloud ${ownCloud_old}) diff --git a/src/gui/accountsettings.cpp b/src/gui/accountsettings.cpp index 2f866502ad..a5a5e105dc 100644 --- a/src/gui/accountsettings.cpp +++ b/src/gui/accountsettings.cpp @@ -30,6 +30,7 @@ #include "accountmanager.h" #include "owncloudsetupwizard.h" #include "creds/abstractcredentials.h" +#include "creds/httpcredentialsgui.h" #include "tooltipupdater.h" #include "filesystem.h" @@ -69,6 +70,42 @@ static const char progressBarStyleC[] = "background-color: %1; width: 1px;" "}"; +/** + * Adjusts the mouse cursor based on the region it is on over the folder tree view. + * + * Used to show that one can click the red error list box by changing the cursor + * to the pointing hand. + */ +class MouseCursorChanger : public QObject +{ + Q_OBJECT +public: + MouseCursorChanger(QObject *parent) + : QObject(parent) + { + } + + QTreeView *folderList; + FolderStatusModel *model; + +protected: + bool eventFilter(QObject *watched, QEvent *event) override + { + if (event->type() == QEvent::HoverMove) { + Qt::CursorShape shape = Qt::ArrowCursor; + auto pos = folderList->mapFromGlobal(QCursor::pos()); + auto index = folderList->indexAt(pos); + if (model->classify(index) == FolderStatusModel::RootFolder + && (FolderStatusDelegate::errorsListRect(folderList->visualRect(index)).contains(pos) + || FolderStatusDelegate::optionsButtonRect(folderList->visualRect(index),folderList->layoutDirection()).contains(pos))) { + shape = Qt::PointingHandCursor; + } + folderList->setCursor(shape); + } + return QObject::eventFilter(watched, event); + } +}; + AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent) : QWidget(parent) , ui(new Ui::AccountSettings) @@ -94,6 +131,13 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent) #endif new ToolTipUpdater(ui->_folderList); + auto mouseCursorChanger = new MouseCursorChanger(this); + mouseCursorChanger->folderList = ui->_folderList; + mouseCursorChanger->model = _model; + ui->_folderList->setMouseTracking(true); + ui->_folderList->setAttribute(Qt::WA_Hover, true); + ui->_folderList->installEventFilter(mouseCursorChanger); + createAccountToolbox(); connect(AccountManager::instance(), SIGNAL(accountAdded(AccountState *)), SLOT(slotAccountAdded(AccountState *))); @@ -137,8 +181,8 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent) ui->connectLabel->setText(tr("No account configured.")); - connect(_accountState, SIGNAL(stateChanged(int)), SLOT(slotAccountStateChanged(int))); - slotAccountStateChanged(_accountState->state()); + connect(_accountState, &AccountState::stateChanged, this, &AccountSettings::slotAccountStateChanged); + slotAccountStateChanged(); connect(&_quotaInfo, SIGNAL(quotaUpdated(qint64, qint64)), this, SLOT(slotUpdateQuota(qint64, qint64))); @@ -301,6 +345,10 @@ void AccountSettings::slotFolderListClicked(const QModelIndex &indx) slotCustomContextMenuRequested(pos); return; } + if (FolderStatusDelegate::errorsListRect(tv->visualRect(indx)).contains(pos)) { + emit showIssuesList(_model->data(indx, FolderStatusDelegate::FolderAliasRole).toString()); + return; + } // Expand root items on single click if (_accountState && _accountState->state() == AccountState::Connected) { @@ -348,6 +396,7 @@ void AccountSettings::slotFolderWizardAccepted() } } FileSystem::setFolderMinimumPermissions(definition.localPath); + Utility::setupFavLink(definition.localPath); } /* take the value from the definition of already existing folders. All folders have @@ -575,8 +624,9 @@ void AccountSettings::slotUpdateQuota(qint64 total, qint64 used) } } -void AccountSettings::slotAccountStateChanged(int state) +void AccountSettings::slotAccountStateChanged() { + int state = _accountState ? _accountState->state() : AccountState::Disconnected; if (_accountState) { ui->sslButton->updateAccountState(_accountState); AccountPtr account = _accountState->account(); @@ -607,6 +657,20 @@ void AccountSettings::slotAccountStateChanged(int state) showConnectionLabel(tr("Server %1 is currently in maintenance mode.").arg(server)); } else if (state == AccountState::SignedOut) { showConnectionLabel(tr("Signed out from %1.").arg(serverWithUser)); + } else if (state == AccountState::AskingCredentials) { + QUrl url; + if (auto cred = qobject_cast(account->credentials())) { + connect(cred, &HttpCredentialsGui::authorisationLinkChanged, + this, &AccountSettings::slotAccountStateChanged, Qt::UniqueConnection); + url = cred->authorisationLink(); + } + if (url.isValid()) { + showConnectionLabel(tr("Obtaining authorization from the browser. " + "Click here to re-open the browser.") + .arg(url.toString(QUrl::FullyEncoded))); + } else { + showConnectionLabel(tr("Connecting to %1...").arg(serverWithUser)); + } } else { showConnectionLabel(tr("No connection to %1 at %2.") .arg(Utility::escape(Theme::instance()->appNameGUI()), server), @@ -741,9 +805,12 @@ void AccountSettings::refreshSelectiveSyncStatus() auto anim = new QPropertyAnimation(ui->selectiveSyncStatus, "maximumHeight", ui->selectiveSyncStatus); anim->setEndValue(shouldBeVisible ? hint.height() : 0); anim->start(QAbstractAnimation::DeleteWhenStopped); - if (!shouldBeVisible) { - connect(anim, SIGNAL(finished()), ui->selectiveSyncStatus, SLOT(hide())); - } + connect(anim, &QPropertyAnimation::finished, [this, shouldBeVisible]() { + ui->selectiveSyncStatus->setMaximumHeight(QWIDGETSIZE_MAX); + if (!shouldBeVisible) { + ui->selectiveSyncStatus->hide(); + } + }); } } @@ -808,3 +875,5 @@ bool AccountSettings::event(QEvent *e) } } // namespace OCC + +#include "accountsettings.moc" diff --git a/src/gui/accountsettings.h b/src/gui/accountsettings.h index 9c4a1fc569..eaef07eb47 100644 --- a/src/gui/accountsettings.h +++ b/src/gui/accountsettings.h @@ -60,11 +60,12 @@ public: signals: void folderChanged(); void openFolderAlias(const QString &); + void showIssuesList(const QString &folderAlias); public slots: void slotOpenOC(); void slotUpdateQuota(qint64, qint64); - void slotAccountStateChanged(int state); + void slotAccountStateChanged(); AccountState *accountsState() { return _accountState; } diff --git a/src/gui/accountstate.cpp b/src/gui/accountstate.cpp index e7ed89bc29..63316f6d2d 100644 --- a/src/gui/accountstate.cpp +++ b/src/gui/accountstate.cpp @@ -16,10 +16,12 @@ #include "accountmanager.h" #include "account.h" #include "creds/abstractcredentials.h" +#include "creds/httpcredentials.h" #include "logger.h" #include "configfile.h" #include +#include #include namespace OCC { @@ -32,6 +34,7 @@ AccountState::AccountState(AccountPtr account) , _state(AccountState::Disconnected) , _connectionStatus(ConnectionValidator::Undefined) , _waitingForNewCredentials(false) + , _maintenanceToConnectedDelay(60000 + (qrand() % (4 * 60000))) // 1-5min delay { qRegisterMetaType("AccountState*"); @@ -131,6 +134,8 @@ QString AccountState::stateString(State state) return tr("Network error"); case ConfigurationError: return tr("Configuration error"); + case AskingCredentials: + return tr("Asking Credentials"); } return tr("Unknown account state"); } @@ -149,6 +154,7 @@ void AccountState::signOutByUi() void AccountState::signIn() { if (_state == SignedOut) { + _waitingForNewCredentials = false; setState(Disconnected); } } @@ -228,6 +234,23 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta return; } + // Come online gradually from 503 or maintenance mode + if (status == ConnectionValidator::Connected + && (_connectionStatus == ConnectionValidator::ServiceUnavailable + || _connectionStatus == ConnectionValidator::MaintenanceMode)) { + if (!_timeSinceMaintenanceOver.isValid()) { + qCInfo(lcAccountState) << "AccountState reconnection: delaying for" + << _maintenanceToConnectedDelay << "ms"; + _timeSinceMaintenanceOver.start(); + QTimer::singleShot(_maintenanceToConnectedDelay + 100, this, SLOT(checkConnectivity())); + return; + } else if (_timeSinceMaintenanceOver.elapsed() < _maintenanceToConnectedDelay) { + qCInfo(lcAccountState) << "AccountState reconnection: only" + << _timeSinceMaintenanceOver.elapsed() << "ms have passed"; + return; + } + } + if (_connectionStatus != status) { qCInfo(lcAccountState) << "AccountState connection status change: " << connectionStatusString(_connectionStatus) << "->" @@ -263,9 +286,11 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta setState(SignedOut); break; case ConnectionValidator::ServiceUnavailable: + _timeSinceMaintenanceOver.invalidate(); setState(ServiceUnavailable); break; case ConnectionValidator::MaintenanceMode: + _timeSinceMaintenanceOver.invalidate(); setState(MaintenanceMode); break; case ConnectionValidator::Timeout: @@ -282,12 +307,17 @@ void AccountState::slotInvalidCredentials() qCInfo(lcAccountState) << "Invalid credentials for" << _account->url().toString() << "asking user"; - if (account()->credentials()->ready()) - account()->credentials()->invalidateToken(); - account()->credentials()->askFromUser(); - - setState(ConfigurationError); _waitingForNewCredentials = true; + setState(AskingCredentials); + + if (account()->credentials()->ready()) { + account()->credentials()->invalidateToken(); + if (auto creds = qobject_cast(account()->credentials())) { + if (creds->refreshAccessToken()) + return; + } + } + account()->credentials()->askFromUser(); } void AccountState::slotCredentialsFetched(AbstractCredentials *) diff --git a/src/gui/accountstate.h b/src/gui/accountstate.h index 450766bf82..ce36c94fbe 100644 --- a/src/gui/accountstate.h +++ b/src/gui/accountstate.h @@ -64,8 +64,11 @@ public: /// again automatically. NetworkError, - /// An error like invalid credentials where retrying won't help. - ConfigurationError + /// Server configuration error. (For example: unsupported version) + ConfigurationError, + + /// We are currently asking the user for credentials + AskingCredentials }; /// The actual current connectivity status. @@ -107,10 +110,6 @@ public: bool isConnected() const; - /// Triggers a ping to the server to update state and - /// connection status and errors. - void checkConnectivity(); - /** Returns a new settings object for this account, already in the right groups. */ std::unique_ptr settings(); @@ -127,6 +126,11 @@ public: */ void tagLastSuccessfullETagRequest(); +public slots: + /// Triggers a ping to the server to update state and + /// connection status and errors. + void checkConnectivity(); + private: void setState(State state); @@ -148,6 +152,18 @@ private: bool _waitingForNewCredentials; QElapsedTimer _timeSinceLastETagCheck; QPointer _connectionValidator; + + /** + * Starts counting when the server starts being back up after 503 or + * maintenance mode. The account will only become connected once this + * timer exceeds the _maintenanceToConnectedDelay value. + */ + QElapsedTimer _timeSinceMaintenanceOver; + + /** + * Milliseconds for which to delay reconnection after 503/maintenance. + */ + int _maintenanceToConnectedDelay; }; } diff --git a/src/gui/activitywidget.cpp b/src/gui/activitywidget.cpp index 2d40ce8ee4..c4e82884b1 100644 --- a/src/gui/activitywidget.cpp +++ b/src/gui/activitywidget.cpp @@ -33,6 +33,7 @@ #include "accountmanager.h" #include "activityitemdelegate.h" #include "protocolwidget.h" +#include "issueswidget.h" #include "QProgressIndicator.h" #include "notificationwidget.h" #include "notificationconfirmjob.h" @@ -518,33 +519,23 @@ ActivitySettings::ActivitySettings(QWidget *parent) _tab = new QTabWidget(this); hbox->addWidget(_tab); _activityWidget = new ActivityWidget(this); - _activityTabId = _tab->insertTab(0, _activityWidget, Theme::instance()->applicationIcon(), tr("Server Activity")); + _activityTabId = _tab->addTab(_activityWidget, Theme::instance()->applicationIcon(), tr("Server Activity")); connect(_activityWidget, SIGNAL(copyToClipboard()), this, SLOT(slotCopyToClipboard())); connect(_activityWidget, SIGNAL(hideActivityTab(bool)), this, SLOT(setActivityTabHidden(bool))); connect(_activityWidget, SIGNAL(guiLog(QString, QString)), this, SIGNAL(guiLog(QString, QString))); connect(_activityWidget, SIGNAL(newNotification()), SLOT(slotShowActivityTab())); _protocolWidget = new ProtocolWidget(this); - _tab->insertTab(1, _protocolWidget, Theme::instance()->syncStateIcon(SyncResult::Success), tr("Sync Protocol")); + _protocolTabId = _tab->addTab(_protocolWidget, Theme::instance()->syncStateIcon(SyncResult::Success), tr("Sync Protocol")); connect(_protocolWidget, SIGNAL(copyToClipboard()), this, SLOT(slotCopyToClipboard())); - connect(_protocolWidget, SIGNAL(issueItemCountUpdated(int)), - this, SLOT(slotShowIssueItemCount(int))); - // Add the not-synced list into the tab - QWidget *w = new QWidget; - QVBoxLayout *vbox2 = new QVBoxLayout(w); - vbox2->addWidget(new QLabel(tr("List of ignored or erroneous files"), this)); - vbox2->addWidget(_protocolWidget->issueWidget()); - QDialogButtonBox *dlgButtonBox = new QDialogButtonBox(this); - vbox2->addWidget(dlgButtonBox); - QPushButton *_copyBtn = dlgButtonBox->addButton(tr("Copy"), QDialogButtonBox::ActionRole); - _copyBtn->setToolTip(tr("Copy the activity list to the clipboard.")); - _copyBtn->setEnabled(true); - connect(_copyBtn, SIGNAL(clicked()), this, SLOT(slotCopyToClipboard())); - - w->setLayout(vbox2); - _syncIssueTabId = _tab->insertTab(2, w, Theme::instance()->syncStateIcon(SyncResult::Problem), QString()); + _issuesWidget = new IssuesWidget(this); + _syncIssueTabId = _tab->addTab(_issuesWidget, Theme::instance()->syncStateIcon(SyncResult::Problem), QString()); slotShowIssueItemCount(0); // to display the label. + connect(_issuesWidget, SIGNAL(issueCountUpdated(int)), + this, SLOT(slotShowIssueItemCount(int))); + connect(_issuesWidget, SIGNAL(copyToClipboard()), + this, SLOT(slotCopyToClipboard())); // Add a progress indicator to spin if the acitivity list is updated. _progressIndicator = new QProgressIndicator(this); @@ -571,10 +562,14 @@ void ActivitySettings::setActivityTabHidden(bool hidden) if (hidden && _activityTabId > -1) { _tab->removeTab(_activityTabId); _activityTabId = -1; + _protocolTabId -= 1; + _syncIssueTabId -= 1; } if (!hidden && _activityTabId == -1) { _activityTabId = _tab->insertTab(0, _activityWidget, Theme::instance()->applicationIcon(), tr("Server Activity")); + _protocolTabId += 1; + _syncIssueTabId += 1; } } @@ -595,6 +590,15 @@ void ActivitySettings::slotShowActivityTab() } } +void ActivitySettings::slotShowIssuesTab(const QString &folderAlias) +{ + if (_syncIssueTabId == -1) + return; + _tab->setCurrentIndex(_syncIssueTabId); + + _issuesWidget->showFolderErrors(folderAlias); +} + void ActivitySettings::slotCopyToClipboard() { QString text; @@ -603,18 +607,18 @@ void ActivitySettings::slotCopyToClipboard() int idx = _tab->currentIndex(); QString message; - if (idx == 0) { + if (idx == _activityTabId) { // the activity widget _activityWidget->storeActivityList(ts); message = tr("The server activity list has been copied to the clipboard."); - } else if (idx == 1) { + } else if (idx == _protocolTabId) { // the protocol widget _protocolWidget->storeSyncActivity(ts); message = tr("The sync activity list has been copied to the clipboard."); - } else if (idx == 2) { + } else if (idx == _syncIssueTabId) { // issues Widget message = tr("The list of unsynced items has been copied to the clipboard."); - _protocolWidget->storeSyncIssues(ts); + _issuesWidget->storeSyncIssues(ts); } QApplication::clipboard()->setText(text); diff --git a/src/gui/activitywidget.h b/src/gui/activitywidget.h index bae9b6ff95..e2fa314e05 100644 --- a/src/gui/activitywidget.h +++ b/src/gui/activitywidget.h @@ -35,6 +35,7 @@ namespace OCC { class Account; class AccountStatusPtr; class ProtocolWidget; +class IssuesWidget; class JsonApiJob; class NotificationWidget; class ActivityListModel; @@ -138,6 +139,8 @@ public slots: void setNotificationRefreshInterval(quint64 interval); + void slotShowIssuesTab(const QString &folderAlias); + private slots: void slotCopyToClipboard(); void setActivityTabHidden(bool hidden); @@ -153,10 +156,12 @@ private: QTabWidget *_tab; int _activityTabId; + int _protocolTabId; int _syncIssueTabId; ActivityWidget *_activityWidget; ProtocolWidget *_protocolWidget; + IssuesWidget *_issuesWidget; QProgressIndicator *_progressIndicator; QTimer _notificationCheckTimer; QHash _timeSinceLastCheck; diff --git a/src/gui/application.cpp b/src/gui/application.cpp index 40bbfb1d7e..f06054b3f3 100644 --- a/src/gui/application.cpp +++ b/src/gui/application.cpp @@ -17,6 +17,7 @@ #include "application.h" #include +#include #include "config.h" #include "account.h" @@ -109,6 +110,8 @@ Application::Application(int &argc, char **argv) { _startedAt.start(); + qsrand(std::random_device()()); + #ifdef Q_OS_WIN // Ensure OpenSSL config file is only loaded from app directory QString opensslConf = QCoreApplication::applicationDirPath() + QString("/openssl.cnf"); @@ -206,8 +209,8 @@ Application::Application(int &argc, char **argv) slotAccountStateAdded(ai.data()); } - connect(FolderMan::instance()->socketApi(), SIGNAL(shareCommandReceived(QString, QString, bool)), - _gui, SLOT(slotShowShareDialog(QString, QString, bool))); + connect(FolderMan::instance()->socketApi(), SIGNAL(shareCommandReceived(QString, QString)), + _gui, SLOT(slotShowShareDialog(QString, QString))); // startup procedure. connect(&_checkConnectionTimer, SIGNAL(timeout()), this, SLOT(slotCheckConnection())); @@ -308,7 +311,8 @@ void Application::slotCheckConnection() // Don't check if we're manually signed out or // when the error is permanent. if (state != AccountState::SignedOut - && state != AccountState::ConfigurationError) { + && state != AccountState::ConfigurationError + && state != AccountState::AskingCredentials) { accountState->checkConnectivity(); } } diff --git a/src/gui/clipboard.mm b/src/gui/clipboard.mm deleted file mode 100644 index 48ea813877..0000000000 --- a/src/gui/clipboard.mm +++ /dev/null @@ -1,14 +0,0 @@ -#include -#import - -namespace OCC { - -// https://github.com/owncloud/client/issues/3300 -void copyToPasteboard(const QString &string) -{ - [[NSPasteboard generalPasteboard] clearContents]; - [[NSPasteboard generalPasteboard] setString:[NSString stringWithUTF8String:string.toUtf8().data()] - forType:NSStringPboardType]; -} - -} diff --git a/src/gui/creds/httpcredentialsgui.cpp b/src/gui/creds/httpcredentialsgui.cpp index 9c629ce654..2ab2d0d30e 100644 --- a/src/gui/creds/httpcredentialsgui.cpp +++ b/src/gui/creds/httpcredentialsgui.cpp @@ -23,6 +23,7 @@ #include "theme.h" #include "account.h" #include +#include "asserts.h" using namespace QKeychain; @@ -40,9 +41,13 @@ void HttpCredentialsGui::askFromUser() if (reply->rawHeader("WWW-Authenticate").contains("Bearer ")) { // OAuth _asyncAuth.reset(new OAuth(_account, this)); + _asyncAuth->_expectedUser = _user; connect(_asyncAuth.data(), &OAuth::result, this, &HttpCredentialsGui::asyncAuthResult); + connect(_asyncAuth.data(), &OAuth::destroyed, + this, &HttpCredentialsGui::authorisationLinkChanged); _asyncAuth->start(); + emit authorisationLinkChanged(); } else if (reply->error() == QNetworkReply::AuthenticationRequiredError) { // Show the dialog // We will re-enter the event loop, so better wait the next iteration @@ -71,11 +76,8 @@ void HttpCredentialsGui::asyncAuthResult(OAuth::Result r, const QString &user, break; } - if (_user != user) { - QMessageBox::warning(nullptr, tr("Login Error"), tr("You must sign in as user %1").arg(_user)); - _asyncAuth->openBrowser(); - return; - } + ASSERT(_user == user); // ensured by _asyncAuth + _password = token; _refreshToken = refreshToken; _ready = true; @@ -118,6 +120,7 @@ void HttpCredentialsGui::showDialog() bool ok = dialog.exec(); if (ok) { _password = dialog.textValue(); + _refreshToken.clear(); _ready = true; persist(); } diff --git a/src/gui/creds/httpcredentialsgui.h b/src/gui/creds/httpcredentialsgui.h index 0eaeeee812..fefc4dd1d2 100644 --- a/src/gui/creds/httpcredentialsgui.h +++ b/src/gui/creds/httpcredentialsgui.h @@ -49,12 +49,21 @@ public: * or call showDialog to ask the password */ Q_INVOKABLE void askFromUser() Q_DECL_OVERRIDE; + /** + * In case of oauth, return an URL to the link to open the browser. + * An invalid URL otherwise + */ + QUrl authorisationLink() const { return _asyncAuth ? _asyncAuth->authorisationLink() : QUrl(); } + static QString requestAppPasswordText(const Account *account); private slots: void asyncAuthResult(OAuth::Result, const QString &user, const QString &accessToken, const QString &refreshToken); void showDialog(); +signals: + void authorisationLinkChanged(); + private: QScopedPointer> _asyncAuth; }; diff --git a/src/gui/creds/oauth.cpp b/src/gui/creds/oauth.cpp index 38e279d293..e711755b57 100644 --- a/src/gui/creds/oauth.cpp +++ b/src/gui/creds/oauth.cpp @@ -29,15 +29,23 @@ OAuth::~OAuth() { } -static void httpReplyAndClose(QTcpSocket *socket, const char *code, const char *html) +static void httpReplyAndClose(QTcpSocket *socket, const char *code, const char *html, + const char *moreHeaders = nullptr) { 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))); + if (moreHeaders) { + socket->write("\r\n"); + socket->write(moreHeaders); + } socket->write("\r\n\r\n"); socket->write(html); socket->disconnectFromHost(); + // We don't want that deleting the server too early prevent queued data to be sent on this socket. + // The socket will be deleted after disconnection because disconnected is connected to deleteLater + socket->setParent(nullptr); } void OAuth::start() @@ -64,9 +72,6 @@ void OAuth::start() 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() @@ -79,21 +84,57 @@ void OAuth::start() 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] { + QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, socket] { 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(); + QUrl messageUrl = json["message_url"].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(); + QString errorReason; + QString errorFromJson = json["error"].toString(); + if (!errorFromJson.isEmpty()) { + errorReason = tr("Error returned from the server: %1") + .arg(errorFromJson.toHtmlEscaped()); + } else if (reply->error() != QNetworkReply::NoError) { + errorReason = tr("There was an error accessing the 'token' endpoint:
%1") + .arg(reply->errorString().toHtmlEscaped()); + } else if (jsonParseError.error != QJsonParseError::NoError) { + errorReason = tr("Could not parse the JSON returned from the server:
%1") + .arg(jsonParseError.errorString()); + } else { + errorReason = tr("The reply from the server did not contain all expected fields"); + } + qCWarning(lcOauth) << "Error when getting the accessToken" << json << errorReason; + httpReplyAndClose(socket, "500 Internal Server Error", + tr("

Login Error

%1

").arg(errorReason).toUtf8().constData()); emit result(Error); return; } + if (!_expectedUser.isNull() && user != _expectedUser) { + // Connected with the wrong user + QString message = tr("

Wrong user

" + "

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

") + .arg(user, _expectedUser, Theme::instance()->appNameGUI(), + authorisationLink().toString(QUrl::FullyEncoded)); + httpReplyAndClose(socket, "200 OK", message.toUtf8().constData()); + // We are still listening on the socket so we will get the new connection + return; + } + const char *loginSuccessfullHtml = "

Login Successful

You can close this window.

"; + if (messageUrl.isValid()) { + httpReplyAndClose(socket, "303 See Other", loginSuccessfullHtml, + QByteArray("Location: " + messageUrl.toEncoded()).constData()); + } else { + httpReplyAndClose(socket, "200 OK", loginSuccessfullHtml); + } emit result(LoggedIn, user, accessToken, refreshToken); }); }); @@ -102,17 +143,21 @@ void OAuth::start() QTimer::singleShot(5 * 60 * 1000, this, [this] { result(Error); }); } - -bool OAuth::openBrowser() +QUrl OAuth::authorisationLink() const { Q_ASSERT(_server.isListening()); - auto url = QUrl(_account->url().toString() + QUrl 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 (!_expectedUser.isNull()) + url.addQueryItem("user", _expectedUser); + return url; +} - - if (!QDesktopServices::openUrl(url)) { +bool OAuth::openBrowser() +{ + if (!QDesktopServices::openUrl(authorisationLink())) { // We cannot open the browser, then we claim we don't support OAuth. emit result(NotSupported, QString()); return false; diff --git a/src/gui/creds/oauth.h b/src/gui/creds/oauth.h index fe7fd1c402..7024396473 100644 --- a/src/gui/creds/oauth.h +++ b/src/gui/creds/oauth.h @@ -15,6 +15,7 @@ #pragma once #include #include +#include namespace OCC { @@ -53,6 +54,7 @@ public: Q_ENUM(Result); void start(); bool openBrowser(); + QUrl authorisationLink() const; signals: /** @@ -64,6 +66,9 @@ signals: private: Account *_account; QTcpServer _server; + +public: + QString _expectedUser; }; diff --git a/src/gui/creds/shibbolethcredentials.cpp b/src/gui/creds/shibbolethcredentials.cpp index cc3d6fb686..b7fca6dd3d 100644 --- a/src/gui/creds/shibbolethcredentials.cpp +++ b/src/gui/creds/shibbolethcredentials.cpp @@ -90,7 +90,7 @@ QString ShibbolethCredentials::user() const return _user; } -QNetworkAccessManager *ShibbolethCredentials::getQNAM() const +QNetworkAccessManager *ShibbolethCredentials::createQNAM() const { QNetworkAccessManager *qnam(new AccessManager); connect(qnam, SIGNAL(finished(QNetworkReply *)), diff --git a/src/gui/creds/shibbolethcredentials.h b/src/gui/creds/shibbolethcredentials.h index 46b6bc08df..a4909ed741 100644 --- a/src/gui/creds/shibbolethcredentials.h +++ b/src/gui/creds/shibbolethcredentials.h @@ -53,7 +53,7 @@ public: void setAccount(Account *account) Q_DECL_OVERRIDE; QString authType() const Q_DECL_OVERRIDE; QString user() const Q_DECL_OVERRIDE; - QNetworkAccessManager *getQNAM() const Q_DECL_OVERRIDE; + QNetworkAccessManager *createQNAM() const Q_DECL_OVERRIDE; bool ready() const Q_DECL_OVERRIDE; void fetchFromKeychain() Q_DECL_OVERRIDE; void askFromUser() Q_DECL_OVERRIDE; diff --git a/src/gui/elidedlabel.cpp b/src/gui/elidedlabel.cpp new file mode 100644 index 0000000000..a5ce9bb227 --- /dev/null +++ b/src/gui/elidedlabel.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) by Christian Kamm + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "elidedlabel.h" + +#include + +namespace OCC { + +ElidedLabel::ElidedLabel(const QString &text, QWidget *parent) + : QLabel(text, parent) + , _text(text) + , _elideMode(Qt::ElideNone) +{ +} + +void ElidedLabel::setText(const QString &text) +{ + _text = text; + QLabel::setText(text); + update(); +} + +void ElidedLabel::setElideMode(Qt::TextElideMode elideMode) +{ + _elideMode = elideMode; + update(); +} + +void ElidedLabel::resizeEvent(QResizeEvent *event) +{ + QLabel::resizeEvent(event); + + QFontMetrics fm = fontMetrics(); + QString elided = fm.elidedText(_text, _elideMode, event->size().width()); + QLabel::setText(elided); +} +} diff --git a/src/gui/elidedlabel.h b/src/gui/elidedlabel.h new file mode 100644 index 0000000000..a06100c5eb --- /dev/null +++ b/src/gui/elidedlabel.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) by Christian Kamm + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#ifndef ELIDEDLABEL_H +#define ELIDEDLABEL_H + +#include + +namespace OCC { + +/// Label that can elide its text +class ElidedLabel : public QLabel +{ + Q_OBJECT +public: + explicit ElidedLabel(const QString &text, QWidget *parent = 0); + + void setText(const QString &text); + const QString &text() const { return _text; } + + void setElideMode(Qt::TextElideMode elideMode); + Qt::TextElideMode elideMode() const { return _elideMode; } + +protected: + void resizeEvent(QResizeEvent *event) override; + +private: + QString _text; + Qt::TextElideMode _elideMode; +}; +} + +#endif diff --git a/src/gui/folder.cpp b/src/gui/folder.cpp index e00166262e..4ca5de844f 100644 --- a/src/gui/folder.cpp +++ b/src/gui/folder.cpp @@ -62,7 +62,6 @@ Folder::Folder(const FolderDefinition &definition, , _fileLog(new SyncRunFileLog) , _saveBackwardsCompatible(false) { - qsrand(QTime::currentTime().msec()); _timeSinceLastSyncStart.start(); _timeSinceLastSyncDone.start(); @@ -89,7 +88,6 @@ Folder::Folder(const FolderDefinition &definition, connect(_engine.data(), SIGNAL(started()), SLOT(slotSyncStarted()), Qt::QueuedConnection); connect(_engine.data(), SIGNAL(finished(bool)), SLOT(slotSyncFinished(bool)), Qt::QueuedConnection); - connect(_engine.data(), SIGNAL(csyncError(QString)), SLOT(slotSyncError(QString)), Qt::QueuedConnection); connect(_engine.data(), SIGNAL(csyncUnavailable()), SLOT(slotCsyncUnavailable()), Qt::QueuedConnection); //direct connection so the message box is blocking the sync. @@ -97,7 +95,6 @@ Folder::Folder(const FolderDefinition &definition, SLOT(slotAboutToRemoveAllFiles(SyncFileItem::Direction, bool *))); connect(_engine.data(), SIGNAL(aboutToRestoreBackup(bool *)), SLOT(slotAboutToRestoreBackup(bool *))); - connect(_engine.data(), SIGNAL(folderDiscovered(bool, QString)), this, SLOT(slotFolderDiscovered(bool, QString))); connect(_engine.data(), SIGNAL(transmissionProgress(ProgressInfo)), this, SLOT(slotTransmissionProgress(ProgressInfo))); connect(_engine.data(), SIGNAL(itemCompleted(const SyncFileItemPtr &)), this, SLOT(slotItemCompleted(const SyncFileItemPtr &))); @@ -106,6 +103,7 @@ Folder::Folder(const FolderDefinition &definition, connect(_engine.data(), SIGNAL(seenLockedFile(QString)), FolderMan::instance(), SLOT(slotSyncOnceFileUnlocks(QString))); connect(_engine.data(), SIGNAL(aboutToPropagate(SyncFileItemVector &)), SLOT(slotLogPropagationStart())); + connect(_engine.data(), &SyncEngine::syncError, this, &Folder::slotSyncError); _scheduleSelfTimer.setSingleShot(true); _scheduleSelfTimer.setInterval(SyncEngine::minimumFileAgeForUpload); @@ -342,8 +340,8 @@ void Folder::showSyncResultPopup() _syncResult.numRenamedItems(), _syncResult.firstItemRenamed()->_renameTarget); } - if (_syncResult.firstConflictItem()) { - createGuiLog(_syncResult.firstConflictItem()->_file, LogStatusConflict, _syncResult.numConflictItems()); + if (_syncResult.firstNewConflictItem()) { + createGuiLog(_syncResult.firstNewConflictItem()->_file, LogStatusConflict, _syncResult.numNewConflictItems()); } if (int errorCount = _syncResult.numErrorItems()) { createGuiLog(_syncResult.firstItemError()->_file, LogStatusError, errorCount); @@ -461,9 +459,8 @@ void Folder::slotWatchedPathChanged(const QString &path) // own process. Therefore nothing needs to be done here! #else // Use the path to figure out whether it was our own change - const auto maxNotificationDelay = 15 * 1000; - qint64 time = _engine->timeSinceFileTouched(path); - if (time != -1 && time < maxNotificationDelay) { + if (_engine->wasFileTouched(path)) { + qCDebug(lcFolder) << "Changed path was touched by SyncEngine, ignoring:" << path; return; } #endif @@ -721,10 +718,10 @@ void Folder::setDirtyNetworkLimits() _engine->setNetworkLimits(uploadLimit, downloadLimit); } - -void Folder::slotSyncError(const QString &err) +void Folder::slotSyncError(const QString &message, ErrorCategory category) { - _syncResult.appendErrorString(err); + _syncResult.appendErrorString(message); + emit ProgressDispatcher::instance()->syncError(alias(), message, category); } void Folder::slotSyncStarted() @@ -823,16 +820,6 @@ void Folder::slotEmitFinishedDelayed() emit syncFinished(_syncResult); } - -void Folder::slotFolderDiscovered(bool, QString folderName) -{ - ProgressInfo pi; - pi._currentDiscoveredFolder = folderName; - emit progressInfo(pi); - ProgressDispatcher::instance()->setProgressInfo(alias(), pi); -} - - // the progress comes without a folder and the valid path set. Add that here // and hand the result over to the progress dispatcher. void Folder::slotTransmissionProgress(const ProgressInfo &pi) diff --git a/src/gui/folder.h b/src/gui/folder.h index ce1b3a7d99..1c67c07226 100644 --- a/src/gui/folder.h +++ b/src/gui/folder.h @@ -278,11 +278,14 @@ public slots: private slots: void slotSyncStarted(); - void slotSyncError(const QString &); - void slotCsyncUnavailable(); void slotSyncFinished(bool); - void slotFolderDiscovered(bool local, QString folderName); + /** Adds a error message that's not tied to a specific item. + */ + void slotSyncError(const QString &message, ErrorCategory category = ErrorCategory::Normal); + + void slotCsyncUnavailable(); + void slotTransmissionProgress(const ProgressInfo &pi); void slotItemCompleted(const SyncFileItemPtr &); diff --git a/src/gui/folderman.cpp b/src/gui/folderman.cpp index ce5376b2ba..a1f1286a9e 100644 --- a/src/gui/folderman.cpp +++ b/src/gui/folderman.cpp @@ -141,6 +141,7 @@ int FolderMan::unloadAndDeleteAllFolders() _lastSyncFolder = 0; _currentSyncFolder = 0; _scheduledFolders.clear(); + emit folderListChanged(_folderMap); emit scheduleQueueChanged(); return cnt; @@ -1038,6 +1039,8 @@ void FolderMan::removeFolder(Folder *f) } else { delete f; } + + emit folderListChanged(_folderMap); } QString FolderMan::getBackupName(QString fullPathName) const diff --git a/src/gui/folderstatusdelegate.cpp b/src/gui/folderstatusdelegate.cpp index 3d193d5cd0..da41cbf939 100644 --- a/src/gui/folderstatusdelegate.cpp +++ b/src/gui/folderstatusdelegate.cpp @@ -76,12 +76,18 @@ QSize FolderStatusDelegate::sizeHint(const QStyleOptionViewItem &option, // calc height int h = rootFolderHeightWithoutErrors(fm, aliasFm); + // this already includes the bottom margin + // add some space to show an conflict indicator. + int margin = fm.height() / 4; + if (!qvariant_cast(index.data(FolderConflictMsg)).isEmpty()) { + QStringList msgs = qvariant_cast(index.data(FolderConflictMsg)); + h += margin + 2 * margin + msgs.count() * fm.height(); + } // add some space to show an error condition. if (!qvariant_cast(index.data(FolderErrorMsg)).isEmpty()) { - int margin = fm.height() / 4; QStringList errMsgs = qvariant_cast(index.data(FolderErrorMsg)); - h += margin + errMsgs.count() * fm.height(); + h += margin + 2 * margin + errMsgs.count() * fm.height(); } return QSize(0, h); @@ -98,7 +104,7 @@ int FolderStatusDelegate::rootFolderHeightWithoutErrors(const QFontMetrics &fm, h += fm.height(); // local path h += margin; // between local and remote path h += fm.height(); // remote path - h += aliasMargin; // bottom margin + h += margin; // bottom margin return h; } @@ -151,6 +157,7 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem & QString aliasText = qvariant_cast(index.data(HeaderRole)); QString pathText = qvariant_cast(index.data(FolderPathRole)); QString remotePath = qvariant_cast(index.data(FolderSecondPathRole)); + QStringList conflictTexts = qvariant_cast(index.data(FolderConflictMsg)); QStringList errorTexts = qvariant_cast(index.data(FolderErrorMsg)); int overallPercent = qvariant_cast(index.data(SyncProgressOverallPercent)); @@ -252,37 +259,40 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem & textAlign, elidedPathText); } - // paint an error overlay if there is an error string + int h = iconRect.bottom() + margin; - int h = iconRect.bottom(); - if (!errorTexts.isEmpty()) { - h += margin; - QRect errorRect = localPathRect; - errorRect.setLeft(iconRect.left()); - errorRect.setTop(h); - errorRect.setHeight(errorTexts.count() * subFm.height() + 2 * margin); - errorRect.setRight(option.rect.right() - margin); + // paint an error overlay if there is an error string or conflict string + auto drawTextBox = [&](const QStringList &texts, QColor color) { + QRect rect = localPathRect; + rect.setLeft(iconRect.left()); + rect.setTop(h); + rect.setHeight(texts.count() * subFm.height() + 2 * margin); + rect.setRight(option.rect.right() - margin); - painter->setBrush(QColor(0xbb, 0x4d, 0x4d)); + painter->setBrush(color); painter->setPen(QColor(0xaa, 0xaa, 0xaa)); - painter->drawRoundedRect(QStyle::visualRect(option.direction, option.rect, errorRect), + painter->drawRoundedRect(QStyle::visualRect(option.direction, option.rect, rect), 4, 4); painter->setPen(Qt::white); painter->setFont(errorFont); - QRect errorTextRect(errorRect.left() + margin, - errorRect.top() + margin, - errorRect.width() - 2 * margin, + QRect textRect(rect.left() + margin, + rect.top() + margin, + rect.width() - 2 * margin, subFm.height()); - foreach (QString eText, errorTexts) { - painter->drawText(QStyle::visualRect(option.direction, option.rect, errorTextRect), textAlign, - subFm.elidedText(eText, Qt::ElideLeft, errorTextRect.width())); - errorTextRect.translate(0, errorTextRect.height()); + foreach (QString eText, texts) { + painter->drawText(QStyle::visualRect(option.direction, option.rect, textRect), textAlign, + subFm.elidedText(eText, Qt::ElideLeft, textRect.width())); + textRect.translate(0, textRect.height()); } - h = errorRect.bottom(); - } - h += margin; + h = rect.bottom() + margin; + }; + + if (!conflictTexts.isEmpty()) + drawTextBox(conflictTexts, QColor(0xba, 0xba, 0x4d)); + if (!errorTexts.isEmpty()) + drawTextBox(errorTexts, QColor(0xbb, 0x4d, 0x4d)); // Sync File Progress Bar: Show it if syncFile is not empty. if (showProgess) { @@ -373,5 +383,15 @@ QRect FolderStatusDelegate::optionsButtonRect(QRect within, Qt::LayoutDirection return QStyle::visualRect(direction, within, r); } +QRect FolderStatusDelegate::errorsListRect(QRect within) +{ + QFont font = QFont(); + QFont aliasFont = makeAliasFont(font); + QFontMetrics fm(font); + QFontMetrics aliasFm(aliasFont); + within.setTop(within.top() + FolderStatusDelegate::rootFolderHeightWithoutErrors(fm, aliasFm)); + return within; +} + } // namespace OCC diff --git a/src/gui/folderstatusdelegate.h b/src/gui/folderstatusdelegate.h index b42ae134d0..213c822dd1 100644 --- a/src/gui/folderstatusdelegate.h +++ b/src/gui/folderstatusdelegate.h @@ -33,6 +33,7 @@ public: HeaderRole, FolderPathRole, // for a SubFolder it's the complete path FolderSecondPathRole, + FolderConflictMsg, FolderErrorMsg, FolderSyncPaused, FolderStatusIconRole, @@ -56,6 +57,7 @@ public: * return the position of the option button within the item */ static QRect optionsButtonRect(QRect within, Qt::LayoutDirection direction); + static QRect errorsListRect(QRect within); static int rootFolderHeightWithoutErrors(const QFontMetrics &fm, const QFontMetrics &aliasFm); private: diff --git a/src/gui/folderstatusmodel.cpp b/src/gui/folderstatusmodel.cpp index b0f83e9c8e..ac32dc865d 100644 --- a/src/gui/folderstatusmodel.cpp +++ b/src/gui/folderstatusmodel.cpp @@ -226,6 +226,10 @@ QVariant FolderStatusModel::data(const QModelIndex &index, int role) const return f->shortGuiLocalPath(); case FolderStatusDelegate::FolderSecondPathRole: return f->remotePath(); + case FolderStatusDelegate::FolderConflictMsg: + return (f->syncResult().numNewConflictItems() + f->syncResult().numOldConflictItems() > 0) + ? QStringList(tr("There are unresolved conflicts. Click for details.")) + : QStringList(); case FolderStatusDelegate::FolderErrorMsg: return f->syncResult().errorStrings(); case FolderStatusDelegate::SyncRunning: @@ -891,12 +895,21 @@ void FolderStatusModel::slotSetProgress(const ProgressInfo &progress) << FolderStatusDelegate::WarningCount << Qt::ToolTipRole; - if (!progress._currentDiscoveredFolder.isEmpty()) { + if (progress.status() == ProgressInfo::Discovery + && !progress._currentDiscoveredFolder.isEmpty()) { pi->_overallSyncString = tr("Checking for changes in '%1'").arg(progress._currentDiscoveredFolder); emit dataChanged(index(folderIndex), index(folderIndex), roles); return; } + if (progress.status() == ProgressInfo::Reconcile) { + pi->_overallSyncString = tr("Reconciling changes"); + emit dataChanged(index(folderIndex), index(folderIndex), roles); + return; + } + + // Status is Starting, Propagation or Done + if (!progress._lastCompletedItem.isEmpty() && Progress::isWarningKind(progress._lastCompletedItem._status)) { pi->_warningCount++; diff --git a/src/gui/folderwizard.cpp b/src/gui/folderwizard.cpp index 940cf8b3ac..168f60d88a 100644 --- a/src/gui/folderwizard.cpp +++ b/src/gui/folderwizard.cpp @@ -22,6 +22,7 @@ #include "accountstate.h" #include "creds/abstractcredentials.h" #include "wizard/owncloudwizard.h" +#include "asserts.h" #include #include @@ -229,7 +230,8 @@ void FolderWizardRemotePath::slotHandleMkdirNetworkError(QNetworkReply *reply) void FolderWizardRemotePath::slotHandleLsColNetworkError(QNetworkReply * /*reply*/) { - auto job = qobject_cast(sender()); + auto job = qobject_cast(sender()); + ASSERT(job); showWarn(tr("Failed to list a folder. Error: %1") .arg(job->errorStringParsingBody())); } diff --git a/src/gui/generalsettings.ui b/src/gui/generalsettings.ui index 20f2c86d37..43d006fae2 100644 --- a/src/gui/generalsettings.ui +++ b/src/gui/generalsettings.ui @@ -47,103 +47,6 @@ - - - - Advanced - - - - - - - - Edit &Ignored Files - - - - - - - Qt::Horizontal - - - - 555 - 20 - - - - - - - - - - - - Ask for confirmation before synchronizing folders larger than - - - true - - - - - - - 999999 - - - 99 - - - - - - - MB - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Ask for confirmation before synchronizing external storages - - - - - - - - 0 - 0 - - - - S&how crash reporter - - - - - - @@ -230,6 +133,111 @@ + + + + Advanced + + + + + + + + Edit &Ignored Files + + + + + + + Qt::Horizontal + + + + 555 + 20 + + + + + + + + + + + + Ask for confirmation before synchronizing folders larger than + + + true + + + + + + + 999999 + + + 99 + + + + + + + MB + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Ask for confirmation before synchronizing external storages + + + + + + + + + + + + 0 + 0 + + + + S&how crash reporter + + + + + + + + diff --git a/src/gui/guiutility.cpp b/src/gui/guiutility.cpp new file mode 100644 index 0000000000..0a3ee6f0f4 --- /dev/null +++ b/src/gui/guiutility.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) by Christian Kamm + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "guiutility.h" + +#include +#include +#include +#include +#include + +using namespace OCC; + +Q_LOGGING_CATEGORY(lcUtility, "gui.utility", QtInfoMsg) + +bool Utility::openBrowser(const QUrl &url, QWidget *errorWidgetParent) +{ + if (!QDesktopServices::openUrl(url)) { + if (errorWidgetParent) { + QMessageBox::warning( + errorWidgetParent, + QCoreApplication::translate("utility", "Could not open browser"), + QCoreApplication::translate("utility", + "There was an error when launching the browser to go to " + "URL %1. Maybe no default browser is configured?") + .arg(url.toString())); + } + qCWarning(lcUtility) << "QDesktopServices::openUrl failed for" << url; + return false; + } + return true; +} + +bool Utility::openEmailComposer(const QString &subject, const QString &body, QWidget *errorWidgetParent) +{ + QUrl url(QLatin1String("mailto:")); + url.setQueryItems({ { QLatin1String("subject"), subject }, + { QLatin1String("body"), body } }); + + if (!QDesktopServices::openUrl(url)) { + if (errorWidgetParent) { + QMessageBox::warning( + errorWidgetParent, + QCoreApplication::translate("utility", "Could not open email client"), + QCoreApplication::translate("utility", + "There was an error when launching the email client to " + "create a new message. Maybe no default email client is " + "configured?")); + } + qCWarning(lcUtility) << "QDesktopServices::openUrl failed for" << url; + return false; + } + return true; +} diff --git a/src/gui/guiutility.h b/src/gui/guiutility.h new file mode 100644 index 0000000000..55f808ac24 --- /dev/null +++ b/src/gui/guiutility.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) by Christian Kamm + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#ifndef GUIUTILITY_H +#define GUIUTILITY_H + +#include +#include +#include + +namespace OCC { +namespace Utility { + + /** Open an url in the browser. + * + * If launching the browser fails, display a message. + */ + bool openBrowser(const QUrl &url, QWidget *errorWidgetParent); + + /** Start composing a new email message. + * + * If launching the email program fails, display a message. + */ + bool openEmailComposer(const QString &subject, const QString &body, + QWidget *errorWidgetParent); + +} // namespace Utility +} // namespace OCC + +#endif diff --git a/src/gui/issueswidget.cpp b/src/gui/issueswidget.cpp new file mode 100644 index 0000000000..4803f17a4f --- /dev/null +++ b/src/gui/issueswidget.cpp @@ -0,0 +1,422 @@ +/* + * Copyright (C) by Klaas Freitag + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include +#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) +#include +#endif + +#include "issueswidget.h" +#include "configfile.h" +#include "syncresult.h" +#include "logger.h" +#include "utility.h" +#include "theme.h" +#include "folderman.h" +#include "syncfileitem.h" +#include "folder.h" +#include "openfilemanager.h" +#include "activityitemdelegate.h" +#include "protocolwidget.h" +#include "accountstate.h" +#include "account.h" +#include "accountmanager.h" +#include "syncjournalfilerecord.h" +#include "elidedlabel.h" + +#include "ui_issueswidget.h" + +#include + +namespace OCC { + +IssuesWidget::IssuesWidget(QWidget *parent) + : QWidget(parent) + , _ui(new Ui::IssuesWidget) +{ + _ui->setupUi(this); + + connect(ProgressDispatcher::instance(), SIGNAL(progressInfo(QString, ProgressInfo)), + this, SLOT(slotProgressInfo(QString, ProgressInfo))); + connect(ProgressDispatcher::instance(), SIGNAL(itemCompleted(QString, SyncFileItemPtr)), + this, SLOT(slotItemCompleted(QString, SyncFileItemPtr))); + connect(ProgressDispatcher::instance(), &ProgressDispatcher::syncError, + this, &IssuesWidget::addError); + + connect(_ui->_treeWidget, SIGNAL(itemActivated(QTreeWidgetItem *, int)), SLOT(slotOpenFile(QTreeWidgetItem *, int))); + connect(_ui->copyIssuesButton, SIGNAL(clicked()), SIGNAL(copyToClipboard())); + + connect(_ui->showIgnores, SIGNAL(toggled(bool)), SLOT(slotRefreshIssues())); + connect(_ui->showWarnings, SIGNAL(toggled(bool)), SLOT(slotRefreshIssues())); + connect(_ui->filterAccount, SIGNAL(currentIndexChanged(int)), SLOT(slotRefreshIssues())); + connect(_ui->filterAccount, SIGNAL(currentIndexChanged(int)), SLOT(slotUpdateFolderFilters())); + connect(_ui->filterFolder, SIGNAL(currentIndexChanged(int)), SLOT(slotRefreshIssues())); + for (auto account : AccountManager::instance()->accounts()) { + slotAccountAdded(account.data()); + } + connect(AccountManager::instance(), SIGNAL(accountAdded(AccountState *)), + SLOT(slotAccountAdded(AccountState *))); + connect(AccountManager::instance(), SIGNAL(accountRemoved(AccountState *)), + SLOT(slotAccountRemoved(AccountState *))); + connect(FolderMan::instance(), SIGNAL(folderListChanged(Folder::Map)), + SLOT(slotUpdateFolderFilters())); + + + // Adjust copyToClipboard() when making changes here! + QStringList header; + header << tr("Time"); + header << tr("File"); + header << tr("Folder"); + header << tr("Issue"); + + int timestampColumnExtra = 0; +#ifdef Q_OS_WIN + timestampColumnExtra = 20; // font metrics are broken on Windows, see #4721 +#endif + + _ui->_treeWidget->setHeaderLabels(header); + int timestampColumnWidth = + ActivityItemDelegate::rowHeight() // icon + + _ui->_treeWidget->fontMetrics().width(ProtocolWidget::timeString(QDateTime::currentDateTime())) + + timestampColumnExtra; + _ui->_treeWidget->setColumnWidth(0, timestampColumnWidth); + _ui->_treeWidget->setColumnWidth(1, 180); + _ui->_treeWidget->setColumnCount(4); + _ui->_treeWidget->setRootIsDecorated(false); + _ui->_treeWidget->setTextElideMode(Qt::ElideMiddle); + _ui->_treeWidget->header()->setObjectName("ActivityErrorListHeader"); +#if defined(Q_OS_MAC) + _ui->_treeWidget->setMinimumWidth(400); +#endif +} + +IssuesWidget::~IssuesWidget() +{ + delete _ui; +} + +void IssuesWidget::showEvent(QShowEvent *ev) +{ + ConfigFile cfg; + cfg.restoreGeometryHeader(_ui->_treeWidget->header()); + QWidget::showEvent(ev); +} + +void IssuesWidget::hideEvent(QHideEvent *ev) +{ + ConfigFile cfg; + cfg.saveGeometryHeader(_ui->_treeWidget->header()); + QWidget::hideEvent(ev); +} + +void IssuesWidget::cleanItems(const QString &folder) +{ + // The issue list is a state, clear it and let the next sync fill it + // with ignored files and propagation errors. + int itemCnt = _ui->_treeWidget->topLevelItemCount(); + for (int cnt = itemCnt - 1; cnt >= 0; cnt--) { + QTreeWidgetItem *item = _ui->_treeWidget->topLevelItem(cnt); + QString itemFolder = item->data(2, Qt::UserRole).toString(); + if (itemFolder == folder) { + delete item; + } + } + // update the tabtext + emit(issueCountUpdated(_ui->_treeWidget->topLevelItemCount())); +} + +void IssuesWidget::addItem(QTreeWidgetItem *item) +{ + if (!item) + return; + + int insertLoc = 0; + + // Insert item specific errors behind the others + if (!item->text(1).isEmpty()) { + for (int i = 0; i < _ui->_treeWidget->topLevelItemCount(); ++i) { + if (_ui->_treeWidget->topLevelItem(i)->text(1).isEmpty()) { + insertLoc = i + 1; + } else { + break; + } + } + } + + _ui->_treeWidget->insertTopLevelItem(insertLoc, item); + item->setHidden(!shouldBeVisible(item, currentAccountFilter(), currentFolderFilter())); + emit issueCountUpdated(_ui->_treeWidget->topLevelItemCount()); +} + +void IssuesWidget::slotOpenFile(QTreeWidgetItem *item, int) +{ + QString folderName = item->data(2, Qt::UserRole).toString(); + QString fileName = item->text(1); + + Folder *folder = FolderMan::instance()->folder(folderName); + if (folder) { + // folder->path() always comes back with trailing path + QString fullPath = folder->path() + fileName; + if (QFile(fullPath).exists()) { + showInFileManager(fullPath); + } + } +} + +void IssuesWidget::slotProgressInfo(const QString &folder, const ProgressInfo &progress) +{ + if (progress.status() == ProgressInfo::Starting) { + // The sync is restarting, clean the old items + cleanItems(folder); + } +} + +void IssuesWidget::slotItemCompleted(const QString &folder, const SyncFileItemPtr &item) +{ + if (!item->hasErrorStatus()) + return; + QTreeWidgetItem *line = ProtocolWidget::createCompletedTreewidgetItem(folder, *item); + if (!line) + return; + addItem(line); +} + +void IssuesWidget::slotRefreshIssues() +{ + auto tree = _ui->_treeWidget; + auto filterFolderAlias = currentFolderFilter(); + auto filterAccount = currentAccountFilter(); + + for (int i = 0; i < tree->topLevelItemCount(); ++i) { + auto item = tree->topLevelItem(i); + item->setHidden(!shouldBeVisible(item, filterAccount, filterFolderAlias)); + } + + _ui->_treeWidget->setColumnHidden(2, !filterFolderAlias.isEmpty()); +} + +void IssuesWidget::slotAccountAdded(AccountState *account) +{ + _ui->filterAccount->addItem(account->account()->displayName(), QVariant::fromValue(account)); + updateAccountChoiceVisibility(); +} + +void IssuesWidget::slotAccountRemoved(AccountState *account) +{ + for (int i = _ui->filterAccount->count() - 1; i >= 0; --i) { + if (account == _ui->filterAccount->itemData(i).value()) + _ui->filterAccount->removeItem(i); + } + updateAccountChoiceVisibility(); +} + +void IssuesWidget::updateAccountChoiceVisibility() +{ + bool visible = _ui->filterAccount->count() > 2; + _ui->filterAccount->setVisible(visible); + _ui->accountLabel->setVisible(visible); + slotUpdateFolderFilters(); +} + +AccountState *IssuesWidget::currentAccountFilter() const +{ + return _ui->filterAccount->currentData().value(); +} + +QString IssuesWidget::currentFolderFilter() const +{ + return _ui->filterFolder->currentData().toString(); +} + +bool IssuesWidget::shouldBeVisible(QTreeWidgetItem *item, AccountState *filterAccount, + const QString &filterFolderAlias) const +{ + bool visible = true; + auto status = item->data(0, Qt::UserRole); + visible &= (_ui->showIgnores->isChecked() || status != SyncFileItem::FileIgnored); + visible &= (_ui->showWarnings->isChecked() + || (status != SyncFileItem::SoftError + && status != SyncFileItem::Restoration)); + + auto folderalias = item->data(2, Qt::UserRole).toString(); + if (filterAccount) { + auto folder = FolderMan::instance()->folder(folderalias); + visible &= folder && folder->accountState() == filterAccount; + } + visible &= (filterFolderAlias.isEmpty() || filterFolderAlias == folderalias); + + return visible; +} + +void IssuesWidget::slotUpdateFolderFilters() +{ + auto account = _ui->filterAccount->currentData().value(); + + // If there is no account selector, show folders for the single + // available account + if (_ui->filterAccount->isHidden() && _ui->filterAccount->count() > 1) { + account = _ui->filterAccount->itemData(1).value(); + } + + if (!account) { + _ui->filterFolder->setCurrentIndex(0); + } + _ui->filterFolder->setEnabled(account != 0); + + for (int i = _ui->filterFolder->count() - 1; i >= 1; --i) { + _ui->filterFolder->removeItem(i); + } + + // Find all selectable folders while figuring out if we need a folder + // selector in the first place + bool anyAccountHasMultipleFolders = false; + QSet accountsWithFolders; + for (auto folder : FolderMan::instance()->map().values()) { + if (accountsWithFolders.contains(folder->accountState())) + anyAccountHasMultipleFolders = true; + accountsWithFolders.insert(folder->accountState()); + + if (folder->accountState() != account) + continue; + _ui->filterFolder->addItem(folder->shortGuiLocalPath(), folder->alias()); + } + + // If we don't need the combo box, hide it. + _ui->filterFolder->setVisible(anyAccountHasMultipleFolders); + _ui->folderLabel->setVisible(anyAccountHasMultipleFolders); + + // If there's no choice, select the only folder and disable + if (_ui->filterFolder->count() == 2 && anyAccountHasMultipleFolders) { + _ui->filterFolder->setCurrentIndex(1); + _ui->filterFolder->setEnabled(false); + } +} + +void IssuesWidget::storeSyncIssues(QTextStream &ts) +{ + int topLevelItems = _ui->_treeWidget->topLevelItemCount(); + + for (int i = 0; i < topLevelItems; i++) { + QTreeWidgetItem *child = _ui->_treeWidget->topLevelItem(i); + if (child->isHidden()) + continue; + ts << right + // time stamp + << qSetFieldWidth(20) + << child->data(0, Qt::DisplayRole).toString() + // separator + << qSetFieldWidth(0) << "," + + // file name + << qSetFieldWidth(64) + << child->data(1, Qt::DisplayRole).toString() + // separator + << qSetFieldWidth(0) << "," + + // folder + << qSetFieldWidth(30) + << child->data(2, Qt::DisplayRole).toString() + // separator + << qSetFieldWidth(0) << "," + + // action + << qSetFieldWidth(15) + << child->data(3, Qt::DisplayRole).toString() + << qSetFieldWidth(0) + << endl; + } +} + +void IssuesWidget::showFolderErrors(const QString &folderAlias) +{ + auto folder = FolderMan::instance()->folder(folderAlias); + if (!folder) + return; + + _ui->filterAccount->setCurrentIndex( + qMax(0, _ui->filterAccount->findData(QVariant::fromValue(folder->accountState())))); + _ui->filterFolder->setCurrentIndex( + qMax(0, _ui->filterFolder->findData(folderAlias))); + _ui->showIgnores->setChecked(false); + _ui->showWarnings->setChecked(false); +} + +void IssuesWidget::addError(const QString &folderAlias, const QString &message, + ErrorCategory category) +{ + auto folder = FolderMan::instance()->folder(folderAlias); + if (!folder) + return; + + QStringList columns; + QDateTime timestamp = QDateTime::currentDateTime(); + const QString timeStr = ProtocolWidget::timeString(timestamp); + const QString longTimeStr = ProtocolWidget::timeString(timestamp, QLocale::LongFormat); + + columns << timeStr; + columns << ""; // no "File" entry + columns << folder->shortGuiLocalPath(); + columns << message; + + QIcon icon = Theme::instance()->syncStateIcon(SyncResult::Error); + + QTreeWidgetItem *twitem = new QTreeWidgetItem(columns); + twitem->setData(0, Qt::SizeHintRole, QSize(0, ActivityItemDelegate::rowHeight())); + twitem->setIcon(0, icon); + twitem->setToolTip(0, longTimeStr); + twitem->setToolTip(3, message); + twitem->setData(0, Qt::UserRole, SyncFileItem::NormalError); + twitem->setData(2, Qt::UserRole, folderAlias); + + addItem(twitem); + addErrorWidget(twitem, message, category); +} + +void IssuesWidget::addErrorWidget(QTreeWidgetItem *item, const QString &message, ErrorCategory category) +{ + QWidget *widget = 0; + if (category == ErrorCategory::InsufficientRemoteStorage) { + widget = new QWidget; + auto layout = new QHBoxLayout; + widget->setLayout(layout); + + auto label = new ElidedLabel(message, widget); + label->setElideMode(Qt::ElideMiddle); + layout->addWidget(label); + + auto button = new QPushButton("Retry all uploads", widget); + button->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding); + auto folderAlias = item->data(2, Qt::UserRole).toString(); + connect(button, &QPushButton::clicked, + this, [this, folderAlias]() { retryInsufficentRemoteStorageErrors(folderAlias); }); + layout->addWidget(button); + } + + if (widget) { + item->setText(3, QString()); + } + _ui->_treeWidget->setItemWidget(item, 3, widget); +} + +void IssuesWidget::retryInsufficentRemoteStorageErrors(const QString &folderAlias) +{ + auto folderman = FolderMan::instance(); + auto folder = folderman->folder(folderAlias); + if (!folder) + return; + + folder->journalDb()->wipeErrorBlacklistCategory(SyncJournalErrorBlacklistRecord::InsufficientRemoteStorage); + folderman->scheduleFolderNext(folder); +} +} diff --git a/src/gui/issueswidget.h b/src/gui/issueswidget.h new file mode 100644 index 0000000000..4accd89b22 --- /dev/null +++ b/src/gui/issueswidget.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) by Klaas Freitag + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#ifndef ISSUESWIDGET_H +#define ISSUESWIDGET_H + +#include +#include +#include + +#include "progressdispatcher.h" +#include "owncloudgui.h" + +#include "ui_issueswidget.h" + +class QPushButton; + +namespace OCC { +class SyncResult; + +namespace Ui { + class ProtocolWidget; +} +class Application; + +/** + * @brief The ProtocolWidget class + * @ingroup gui + */ +class IssuesWidget : public QWidget +{ + Q_OBJECT +public: + explicit IssuesWidget(QWidget *parent = 0); + ~IssuesWidget(); + QSize sizeHint() const { return ownCloudGui::settingsDialogSize(); } + + void storeSyncIssues(QTextStream &ts); + void showFolderErrors(const QString &folderAlias); + +public slots: + void addError(const QString &folderAlias, const QString &message, ErrorCategory category); + void slotProgressInfo(const QString &folder, const ProgressInfo &progress); + void slotItemCompleted(const QString &folder, const SyncFileItemPtr &item); + void slotOpenFile(QTreeWidgetItem *item, int); + +protected: + void showEvent(QShowEvent *); + void hideEvent(QHideEvent *); + +signals: + void copyToClipboard(); + void issueCountUpdated(int); + +private slots: + void slotRefreshIssues(); + void slotUpdateFolderFilters(); + void slotAccountAdded(AccountState *account); + void slotAccountRemoved(AccountState *account); + +private: + void updateAccountChoiceVisibility(); + AccountState *currentAccountFilter() const; + QString currentFolderFilter() const; + bool shouldBeVisible(QTreeWidgetItem *item, AccountState *filterAccount, + const QString &filterFolderAlias) const; + void cleanItems(const QString &folder); + void addItem(QTreeWidgetItem *item); + + /// Add the special error widget for the category, if any + void addErrorWidget(QTreeWidgetItem *item, const QString &message, ErrorCategory category); + + /// Wipes all insufficient remote storgage blacklist entries + void retryInsufficentRemoteStorageErrors(const QString &folderAlias); + + Ui::IssuesWidget *_ui; +}; +} + +#endif diff --git a/src/gui/issueswidget.ui b/src/gui/issueswidget.ui new file mode 100644 index 0000000000..27bfe11184 --- /dev/null +++ b/src/gui/issueswidget.ui @@ -0,0 +1,158 @@ + + + OCC::IssuesWidget + + + + 0 + 0 + 580 + 578 + + + + Form + + + + + + List of issues + + + Qt::PlainText + + + + + + + + + + + Account + + + + + + + + <no filter> + + + + + + + + Folder + + + + + + + false + + + + <no filter> + + + + + + + + + + + + Show warnings + + + true + + + + + + + Show ignored files + + + true + + + + + + + + + + + true + + + false + + + 4 + + + + 1 + + + + + 2 + + + + + 3 + + + + + 4 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Copy the issues list to the clipboard. + + + Copy + + + + + + + + + + diff --git a/src/gui/main.cpp b/src/gui/main.cpp index 8f77b50bc2..4c5970a579 100644 --- a/src/gui/main.cpp +++ b/src/gui/main.cpp @@ -109,7 +109,7 @@ int main(int argc, char **argv) // the updater is triggered Updater *updater = Updater::instance(); if (updater && updater->handleStartup()) { - return true; + return 1; } // if the application is already running, notify it. diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index 684689dae7..65350f3677 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -33,6 +33,7 @@ #include "accountstate.h" #include "openfilemanager.h" #include "accountmanager.h" +#include "syncjournalfilerecord.h" #include "creds/abstractcredentials.h" #include @@ -107,48 +108,6 @@ ownCloudGui::ownCloudGui(Application *parent) SLOT(slotShowOptionalTrayMessage(QString, QString))); connect(Logger::instance(), SIGNAL(guiMessage(QString, QString)), SLOT(slotShowGuiMessage(QString, QString))); - - setupOverlayIcons(); -} - -// Use this to do platform specific code to make overlay icons appear -// in the gui -// MacOSX: perform a AppleScript code piece to load the Finder Plugin. - - -void ownCloudGui::setupOverlayIcons() -{ -#ifdef Q_OS_MAC - // Make sure that we only send the load event to the legacy plugin when - // using OS X <= 10.9 since 10.10 starts using the new FinderSync one. - if (QSysInfo::MacintoshVersion < QSysInfo::MV_10_10) { - const QLatin1String finderExtension("/Library/ScriptingAdditions/SyncStateFinder.osax"); - if (QFile::exists(finderExtension)) { - QString aScript = QString::fromUtf8("tell application \"Finder\"\n" - " try\n" - " «event OWNCload»\n" - " end try\n" - "end tell\n"); - - QString osascript = "/usr/bin/osascript"; - QStringList processArguments; - // processArguments << "-l" << "AppleScript"; - - QProcess p; - p.start(osascript, processArguments); - p.write(aScript.toUtf8()); - p.closeWriteChannel(); - //p.waitForReadyRead(-1); - p.waitForFinished(5000); - QByteArray result = p.readAll(); - QString resultAsString(result); // if appropriate - qCInfo(lcApplication) << "Load Finder Overlay-Plugin: " << resultAsString << ": " << p.exitCode() - << (p.exitCode() != 0 ? p.errorString() : QString::null); - } else { - qCWarning(lcApplication) << finderExtension << "does not exist! Finder Overlay Plugin loading failed"; - } - } -#endif } // This should rather be in application.... or rather in ConfigFile? @@ -789,10 +748,19 @@ void ownCloudGui::slotUpdateProgress(const QString &folder, const ProgressInfo & { Q_UNUSED(folder); - if (!progress._currentDiscoveredFolder.isEmpty()) { - _actionStatus->setText(tr("Checking for changes in '%1'") - .arg(progress._currentDiscoveredFolder)); - } else if (progress.totalSize() == 0) { + if (progress.status() == ProgressInfo::Discovery) { + if (!progress._currentDiscoveredFolder.isEmpty()) { + _actionStatus->setText(tr("Checking for changes in '%1'") + .arg(progress._currentDiscoveredFolder)); + } + } else if (progress.status() == ProgressInfo::Done) { + QTimer::singleShot(2000, this, SLOT(slotDisplayIdle())); + } + if (progress.status() != ProgressInfo::Propagation) { + return; + } + + if (progress.totalSize() == 0) { quint64 currentFile = progress.currentFile(); quint64 totalFileCount = qMax(progress.totalFiles(), currentFile); QString msg; @@ -855,12 +823,6 @@ void ownCloudGui::slotUpdateProgress(const QString &folder, const ProgressInfo & slotRebuildRecentMenus(); } } - - if (progress.isUpdatingEstimates() - && progress.completedFiles() >= progress.totalFiles() - && progress._currentDiscoveredFolder.isEmpty()) { - QTimer::singleShot(2000, this, SLOT(slotDisplayIdle())); - } } void ownCloudGui::slotDisplayIdle() @@ -1039,7 +1001,7 @@ void ownCloudGui::raiseDialog(QWidget *raiseWidget) } -void ownCloudGui::slotShowShareDialog(const QString &sharePath, const QString &localPath, bool resharingAllowed) +void ownCloudGui::slotShowShareDialog(const QString &sharePath, const QString &localPath) { const auto folder = FolderMan::instance()->folderForPath(localPath); if (!folder) { @@ -1052,6 +1014,17 @@ void ownCloudGui::slotShowShareDialog(const QString &sharePath, const QString &l const auto accountState = folder->accountState(); + const QString file = localPath.mid(folder->cleanPath().length() + 1); + SyncJournalFileRecord fileRecord = folder->journalDb()->getFileRecord(file); + + bool resharingAllowed = true; // lets assume the good + if (fileRecord.isValid()) { + // check the permission: Is resharing allowed? + if (!fileRecord._remotePerm.contains('R')) { + resharingAllowed = false; + } + } + // As a first approximation, set the set of permissions that can be granted // either to everything (resharing allowed) or nothing (no resharing). // @@ -1072,7 +1045,7 @@ void ownCloudGui::slotShowShareDialog(const QString &sharePath, const QString &l w = _shareDialogs[localPath]; } else { qCInfo(lcApplication) << "Opening share dialog" << sharePath << localPath << maxSharingPermissions; - w = new ShareDialog(accountState, sharePath, localPath, maxSharingPermissions); + w = new ShareDialog(accountState, sharePath, localPath, maxSharingPermissions, fileRecord.numericFileId()); w->setAttribute(Qt::WA_DeleteOnClose, true); _shareDialogs[localPath] = w; diff --git a/src/gui/owncloudgui.h b/src/gui/owncloudgui.h index c0238a7617..c24fa173a8 100644 --- a/src/gui/owncloudgui.h +++ b/src/gui/owncloudgui.h @@ -86,7 +86,16 @@ public slots: void slotOpenPath(const QString &path); void slotAccountStateChanged(); void slotTrayMessageIfServerUnsupported(Account *account); - void slotShowShareDialog(const QString &sharePath, const QString &localPath, bool resharingAllowed); + + /** + * Open a share dialog for a file or folder. + * + * sharePath is the full remote path to the item, + * localPath is the absolute local path to it (so not relative + * to the folder). + */ + void slotShowShareDialog(const QString &sharePath, const QString &localPath); + void slotRemoveDestroyedShareDialogs(); private slots: diff --git a/src/gui/owncloudsetupwizard.cpp b/src/gui/owncloudsetupwizard.cpp index f124fa265b..dcd510f78d 100644 --- a/src/gui/owncloudsetupwizard.cpp +++ b/src/gui/owncloudsetupwizard.cpp @@ -383,6 +383,7 @@ void OwncloudSetupWizard::slotCreateLocalAndRemoteFolders(const QString &localFo bool nextStep = true; if (fi.exists()) { FileSystem::setFolderMinimumPermissions(localFolder); + Utility::setupFavLink(localFolder); // there is an existing local folder. If its non empty, it can only be synced if the // ownCloud is newly created. _ocWizard->appendToConfigurationLog( diff --git a/src/gui/protocolwidget.cpp b/src/gui/protocolwidget.cpp index 9d7058dd8b..2f177a1c64 100644 --- a/src/gui/protocolwidget.cpp +++ b/src/gui/protocolwidget.cpp @@ -37,13 +37,10 @@ namespace OCC { ProtocolWidget::ProtocolWidget(QWidget *parent) : QWidget(parent) - , IgnoredIndicatorRole(Qt::UserRole + 1) , _ui(new Ui::ProtocolWidget) { _ui->setupUi(this); - connect(ProgressDispatcher::instance(), SIGNAL(progressInfo(QString, ProgressInfo)), - this, SLOT(slotProgressInfo(QString, ProgressInfo))); connect(ProgressDispatcher::instance(), SIGNAL(itemCompleted(QString, SyncFileItemPtr)), this, SLOT(slotItemCompleted(QString, SyncFileItemPtr))); @@ -81,25 +78,6 @@ ProtocolWidget::ProtocolWidget(QWidget *parent) copyBtn->setToolTip(tr("Copy the activity list to the clipboard.")); copyBtn->setEnabled(true); connect(copyBtn, SIGNAL(clicked()), SIGNAL(copyToClipboard())); - - // this view is used to display all errors such as real errors, soft errors and ignored files - // it is instantiated here, but made accessible via the method issueWidget() so that it can - // be embedded into another gui element. - _issueItemView = new QTreeWidget(this); - header.removeLast(); - _issueItemView->setHeaderLabels(header); - timestampColumnWidth = - ActivityItemDelegate::rowHeight() // icon - + _issueItemView->fontMetrics().width(timeString(QDateTime::currentDateTime())) - + timestampColumnExtra; - _issueItemView->setColumnWidth(0, timestampColumnWidth); - _issueItemView->setColumnWidth(1, 180); - _issueItemView->setColumnCount(4); - _issueItemView->setRootIsDecorated(false); - _issueItemView->setTextElideMode(Qt::ElideMiddle); - _issueItemView->header()->setObjectName("ActivityErrorListHeader"); - connect(_issueItemView, SIGNAL(itemActivated(QTreeWidgetItem *, int)), - SLOT(slotOpenFile(QTreeWidgetItem *, int))); } ProtocolWidget::~ProtocolWidget() @@ -121,23 +99,8 @@ void ProtocolWidget::hideEvent(QHideEvent *ev) QWidget::hideEvent(ev); } -void ProtocolWidget::cleanItems(const QString &folder) -{ - // The issue list is a state, clear it and let the next sync fill it - // with ignored files and propagation errors. - int itemCnt = _issueItemView->topLevelItemCount(); - for (int cnt = itemCnt - 1; cnt >= 0; cnt--) { - QTreeWidgetItem *item = _issueItemView->topLevelItem(cnt); - QString itemFolder = item->data(2, Qt::UserRole).toString(); - if (itemFolder == folder) { - delete item; - } - } - // update the tabtext - emit(issueItemCountUpdated(_issueItemView->topLevelItemCount())); -} -QString ProtocolWidget::timeString(QDateTime dt, QLocale::FormatType format) const +QString ProtocolWidget::timeString(QDateTime dt, QLocale::FormatType format) { const QLocale loc = QLocale::system(); QString dtFormat = loc.dateTimeFormat(format); @@ -186,7 +149,8 @@ QTreeWidgetItem *ProtocolWidget::createCompletedTreewidgetItem(const QString &fo QIcon icon; if (item._status == SyncFileItem::NormalError - || item._status == SyncFileItem::FatalError) { + || item._status == SyncFileItem::FatalError + || item._status == SyncFileItem::BlacklistedError) { icon = Theme::instance()->syncStateIcon(SyncResult::Error); } else if (Progress::isWarningKind(item._status)) { icon = Theme::instance()->syncStateIcon(SyncResult::Problem); @@ -197,50 +161,32 @@ QTreeWidgetItem *ProtocolWidget::createCompletedTreewidgetItem(const QString &fo } QTreeWidgetItem *twitem = new QTreeWidgetItem(columns); - if (item._status == SyncFileItem::FileIgnored) { - // Tell that we want to remove it on the next sync. - twitem->setData(0, IgnoredIndicatorRole, true); - } - twitem->setData(0, Qt::SizeHintRole, QSize(0, ActivityItemDelegate::rowHeight())); twitem->setIcon(0, icon); twitem->setToolTip(0, longTimeStr); twitem->setToolTip(1, item._file); twitem->setToolTip(3, message); + twitem->setData(0, Qt::UserRole, item._status); twitem->setData(2, Qt::UserRole, folder); return twitem; } -void ProtocolWidget::slotProgressInfo(const QString &folder, const ProgressInfo &progress) -{ - if (!progress.isUpdatingEstimates()) { - // The sync is restarting, clean the old items - cleanItems(folder); - } else if (progress.completedFiles() >= progress.totalFiles()) { - //Sync completed - } -} - void ProtocolWidget::slotItemCompleted(const QString &folder, const SyncFileItemPtr &item) { + if (item->hasErrorStatus()) + return; QTreeWidgetItem *line = createCompletedTreewidgetItem(folder, *item); if (line) { - if (item->hasErrorStatus()) { - _issueItemView->insertTopLevelItem(0, line); - emit issueItemCountUpdated(_issueItemView->topLevelItemCount()); - } else { - // Limit the number of items - int itemCnt = _ui->_treeWidget->topLevelItemCount(); - while (itemCnt > 2000) { - delete _ui->_treeWidget->takeTopLevelItem(itemCnt - 1); - itemCnt--; - } - _ui->_treeWidget->insertTopLevelItem(0, line); + // Limit the number of items + int itemCnt = _ui->_treeWidget->topLevelItemCount(); + while (itemCnt > 2000) { + delete _ui->_treeWidget->takeTopLevelItem(itemCnt - 1); + itemCnt--; } + _ui->_treeWidget->insertTopLevelItem(0, line); } } - void ProtocolWidget::storeSyncActivity(QTextStream &ts) { int topLevelItems = _ui->_treeWidget->topLevelItemCount(); @@ -280,36 +226,4 @@ void ProtocolWidget::storeSyncActivity(QTextStream &ts) } } -void ProtocolWidget::storeSyncIssues(QTextStream &ts) -{ - int topLevelItems = _issueItemView->topLevelItemCount(); - - for (int i = 0; i < topLevelItems; i++) { - QTreeWidgetItem *child = _issueItemView->topLevelItem(i); - ts << right - // time stamp - << qSetFieldWidth(20) - << child->data(0, Qt::DisplayRole).toString() - // separator - << qSetFieldWidth(0) << "," - - // file name - << qSetFieldWidth(64) - << child->data(1, Qt::DisplayRole).toString() - // separator - << qSetFieldWidth(0) << "," - - // folder - << qSetFieldWidth(30) - << child->data(2, Qt::DisplayRole).toString() - // separator - << qSetFieldWidth(0) << "," - - // action - << qSetFieldWidth(15) - << child->data(3, Qt::DisplayRole).toString() - << qSetFieldWidth(0) - << endl; - } -} } diff --git a/src/gui/protocolwidget.h b/src/gui/protocolwidget.h index 927a23c4b1..7e3a154a5c 100644 --- a/src/gui/protocolwidget.h +++ b/src/gui/protocolwidget.h @@ -46,12 +46,13 @@ public: ~ProtocolWidget(); QSize sizeHint() const { return ownCloudGui::settingsDialogSize(); } - QTreeWidget *issueWidget() { return _issueItemView; } void storeSyncActivity(QTextStream &ts); - void storeSyncIssues(QTextStream &ts); + + // Shared with IssueWidget + static QTreeWidgetItem *createCompletedTreewidgetItem(const QString &folder, const SyncFileItem &item); + static QString timeString(QDateTime dt, QLocale::FormatType format = QLocale::NarrowFormat); public slots: - void slotProgressInfo(const QString &folder, const ProgressInfo &progress); void slotItemCompleted(const QString &folder, const SyncFileItemPtr &item); void slotOpenFile(QTreeWidgetItem *item, int); @@ -61,19 +62,9 @@ protected: signals: void copyToClipboard(); - void issueItemCountUpdated(int); private: - void setSyncResultStatus(const SyncResult &result); - void cleanItems(const QString &folder); - - QTreeWidgetItem *createCompletedTreewidgetItem(const QString &folder, const SyncFileItem &item); - - QString timeString(QDateTime dt, QLocale::FormatType format = QLocale::NarrowFormat) const; - - const int IgnoredIndicatorRole; Ui::ProtocolWidget *_ui; - QTreeWidget *_issueItemView; }; } #endif // PROTOCOLWIDGET_H diff --git a/src/gui/settingsdialog.cpp b/src/gui/settingsdialog.cpp index bb44a080aa..f21ecd5f8f 100644 --- a/src/gui/settingsdialog.cpp +++ b/src/gui/settingsdialog.cpp @@ -211,6 +211,14 @@ void SettingsDialog::showActivityPage() } } +void SettingsDialog::showIssuesList(const QString &folderAlias) +{ + if (!_activityAction) + return; + _activityAction->trigger(); + _activitySettings->slotShowIssuesTab(folderAlias); +} + void SettingsDialog::accountAdded(AccountState *s) { auto height = _toolBar->sizeHint().height(); @@ -242,6 +250,7 @@ void SettingsDialog::accountAdded(AccountState *s) connect(accountSettings, SIGNAL(folderChanged()), _gui, SLOT(slotFoldersChanged())); connect(accountSettings, SIGNAL(openFolderAlias(const QString &)), _gui, SLOT(slotFolderOpenAction(QString))); + connect(accountSettings, SIGNAL(showIssuesList(QString)), SLOT(showIssuesList(QString))); connect(s->account().data(), SIGNAL(accountChangedAvatar()), SLOT(slotAccountAvatarChanged())); slotRefreshActivity(s); diff --git a/src/gui/settingsdialog.h b/src/gui/settingsdialog.h index 9d44599599..15596a00f0 100644 --- a/src/gui/settingsdialog.h +++ b/src/gui/settingsdialog.h @@ -56,6 +56,7 @@ public: public slots: void showFirstPage(); void showActivityPage(); + void showIssuesList(const QString &folderAlias); void slotSwitchPage(QAction *action); void slotRefreshActivity(AccountState *accountState); void slotAccountAvatarChanged(); diff --git a/src/gui/settingsdialogmac.cpp b/src/gui/settingsdialogmac.cpp index 486bc70184..db4fad95fc 100644 --- a/src/gui/settingsdialogmac.cpp +++ b/src/gui/settingsdialogmac.cpp @@ -133,6 +133,14 @@ void SettingsDialogMac::showActivityPage() setCurrentPanelIndex(preferencePanelCount() - 1 - 2); } +void SettingsDialogMac::showIssuesList(const QString &folderAlias) +{ + // Count backwards (0-based) from the last panel (multiple accounts can be on the left) + setCurrentPanelIndex(preferencePanelCount() - 1 - 2); + _activitySettings->slotShowIssuesTab(folderAlias); +} + + void SettingsDialogMac::accountAdded(AccountState *s) { QIcon accountIcon = MacStandardIcon::icon(MacStandardIcon::UserAccounts); @@ -144,6 +152,7 @@ void SettingsDialogMac::accountAdded(AccountState *s) connect(accountSettings, &AccountSettings::folderChanged, _gui, &ownCloudGui::slotFoldersChanged); connect(accountSettings, &AccountSettings::openFolderAlias, _gui, &ownCloudGui::slotFolderOpenAction); + connect(accountSettings, &AccountSettings::showIssuesList, this, &SettingsDialogMac::showIssuesList); connect(s->account().data(), SIGNAL(accountChangedAvatar()), this, SLOT(slotAccountAvatarChanged())); diff --git a/src/gui/settingsdialogmac.h b/src/gui/settingsdialogmac.h index f931f90095..c69ee5f140 100644 --- a/src/gui/settingsdialogmac.h +++ b/src/gui/settingsdialogmac.h @@ -47,6 +47,7 @@ public: public slots: void showActivityPage(); + void showIssuesList(const QString &folderAlias); void slotRefreshActivity(AccountState *accountState); private slots: diff --git a/src/gui/sharedialog.cpp b/src/gui/sharedialog.cpp index 17399d1a41..24a912fdc2 100644 --- a/src/gui/sharedialog.cpp +++ b/src/gui/sharedialog.cpp @@ -38,6 +38,7 @@ ShareDialog::ShareDialog(QPointer accountState, const QString &sharePath, const QString &localPath, SharePermissions maxSharingPermissions, + const QByteArray &numericFileId, QWidget *parent) : QDialog(parent) , _ui(new Ui::ShareDialog) @@ -45,6 +46,7 @@ ShareDialog::ShareDialog(QPointer accountState, , _sharePath(sharePath) , _localPath(localPath) , _maxSharingPermissions(maxSharingPermissions) + , _numericFileId(numericFileId) , _linkWidget(NULL) , _userGroupWidget(NULL) , _progressIndicator(NULL) @@ -192,7 +194,7 @@ void ShareDialog::showSharingUi() && _accountState->account()->serverVersionInt() >= Account::makeServerVersion(8, 2, 0); if (userGroupSharing) { - _userGroupWidget = new ShareUserGroupWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, this); + _userGroupWidget = new ShareUserGroupWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, _numericFileId, this); _ui->shareWidgets->addTab(_userGroupWidget, tr("Users and Groups")); _userGroupWidget->getShares(); } diff --git a/src/gui/sharedialog.h b/src/gui/sharedialog.h index dac7b9841d..50aeea2e17 100644 --- a/src/gui/sharedialog.h +++ b/src/gui/sharedialog.h @@ -43,6 +43,7 @@ public: const QString &sharePath, const QString &localPath, SharePermissions maxSharingPermissions, + const QByteArray &numericFileId, QWidget *parent = 0); ~ShareDialog(); @@ -60,8 +61,8 @@ private: QPointer _accountState; QString _sharePath; QString _localPath; - SharePermissions _maxSharingPermissions; + QByteArray _numericFileId; ShareLinkWidget *_linkWidget; ShareUserGroupWidget *_userGroupWidget; diff --git a/src/gui/sharelinkwidget.cpp b/src/gui/sharelinkwidget.cpp index b2f6d7c7d6..bba3151bbb 100644 --- a/src/gui/sharelinkwidget.cpp +++ b/src/gui/sharelinkwidget.cpp @@ -19,6 +19,7 @@ #include "capabilities.h" #include "sharemanager.h" +#include "guiutility.h" #include "QProgressIndicator.h" #include @@ -67,7 +68,7 @@ ShareLinkWidget::ShareLinkWidget(AccountPtr account, _pi_editing = new QProgressIndicator(); _ui->horizontalLayout_create->addWidget(_pi_create); _ui->horizontalLayout_password->addWidget(_pi_password); - _ui->horizontalLayout_editing->addWidget(_pi_editing); + _ui->layout_editing->addWidget(_pi_editing, 0, 2); _ui->horizontalLayout_expire->insertWidget(_ui->horizontalLayout_expire->count() - 1, _pi_date); connect(_ui->nameLineEdit, SIGNAL(returnPressed()), SLOT(slotShareNameEntered())); @@ -80,7 +81,8 @@ ShareLinkWidget::ShareLinkWidget(AccountPtr account, connect(_ui->pushButton_setPassword, SIGNAL(clicked(bool)), SLOT(slotPasswordReturnPressed())); connect(_ui->checkBox_expire, SIGNAL(clicked()), this, SLOT(slotCheckBoxExpireClicked())); connect(_ui->calendar, SIGNAL(dateChanged(QDate)), SLOT(slotExpireDateChanged(QDate))); - connect(_ui->checkBox_editing, SIGNAL(clicked()), this, SLOT(slotCheckBoxEditingClicked())); + connect(_ui->checkBox_editing, SIGNAL(clicked()), this, SLOT(slotPermissionsCheckboxClicked())); + connect(_ui->checkBox_fileListing, SIGNAL(clicked(bool)), this, SLOT(slotPermissionsCheckboxClicked())); _ui->errorLabel->hide(); @@ -144,15 +146,18 @@ ShareLinkWidget::ShareLinkWidget(AccountPtr account, _expiryRequired = true; } - // File can't have public upload set. - _ui->widget_editing->setVisible(!_isFile); - _ui->checkBox_editing->setEnabled( - _account->capabilities().sharePublicLinkAllowUpload()); + // File can't have public upload set; we also hide it if the capability isn't there + _ui->widget_editing->setVisible( + !_isFile && _account->capabilities().sharePublicLinkAllowUpload()); + _ui->checkBox_fileListing->setVisible( + _account->capabilities().sharePublicLinkSupportsUploadOnly()); // Prepare sharing menu _shareLinkMenu = new QMenu(this); + connect(_shareLinkMenu, SIGNAL(triggered(QAction *)), + SLOT(slotShareLinkActionTriggered(QAction *))); _openLinkAction = _shareLinkMenu->addAction(tr("Open link in browser")); _copyLinkAction = _shareLinkMenu->addAction(tr("Copy link to clipboard")); _copyDirectLinkAction = _shareLinkMenu->addAction(tr("Copy link to clipboard (direct download)")); @@ -216,9 +221,10 @@ void ShareLinkWidget::slotSharesFetched(const QList> &shar connect(share.data(), SIGNAL(serverError(int, QString)), SLOT(slotServerError(int, QString))); connect(share.data(), SIGNAL(shareDeleted()), SLOT(slotDeleteShareFetched())); connect(share.data(), SIGNAL(expireDateSet()), SLOT(slotExpireSet())); - connect(share.data(), SIGNAL(publicUploadSet()), SLOT(slotPublicUploadSet())); + connect(share.data(), SIGNAL(publicUploadSet()), SLOT(slotPermissionsSet())); connect(share.data(), SIGNAL(passwordSet()), SLOT(slotPasswordSet())); connect(share.data(), SIGNAL(passwordSetError(int, QString)), SLOT(slotPasswordSetError(int, QString))); + connect(share.data(), SIGNAL(permissionsSet()), SLOT(slotPermissionsSet())); // Build the table row auto row = table->rowCount(); @@ -241,9 +247,7 @@ void ShareLinkWidget::slotSharesFetched(const QList> &shar auto shareButton = new QToolButton; shareButton->setText("..."); shareButton->setProperty(propertyShareC, QVariant::fromValue(linkShare)); - shareButton->setMenu(_shareLinkMenu); - shareButton->setPopupMode(QToolButton::InstantPopup); - connect(shareButton, SIGNAL(triggered(QAction *)), SLOT(slotShareLinkButtonTriggered(QAction *))); + connect(shareButton, SIGNAL(clicked(bool)), SLOT(slotShareLinkButtonClicked())); table->setCellWidget(row, 1, shareButton); auto deleteButton = new QToolButton; @@ -324,6 +328,8 @@ void ShareLinkWidget::slotShareSelectionChanged() // Public upload state (box is hidden for files) if (!_isFile) { _ui->checkBox_editing->setChecked(share->getPublicUpload()); + _ui->checkBox_fileListing->setChecked(share->getShowFileListing()); + _ui->checkBox_fileListing->setEnabled(share->getPublicUpload()); } } @@ -494,61 +500,39 @@ void ShareLinkWidget::slotCheckBoxExpireClicked() } } -#ifdef Q_OS_MAC -extern void copyToPasteboard(const QString &string); -#endif - -void ShareLinkWidget::copyShareLink(const QUrl &url) -{ -#ifdef Q_OS_MAC - copyToPasteboard(url.toString()); -#else - QClipboard *clipboard = QApplication::clipboard(); - clipboard->setText(url.toString()); -#endif -} - void ShareLinkWidget::emailShareLink(const QUrl &url) { QString fileName = _sharePath.mid(_sharePath.lastIndexOf('/') + 1); - - if (!QDesktopServices::openUrl(QUrl(QString( - "mailto: " - "?subject=I shared %1 with you" - "&body=%2") - .arg( - fileName, - url.toString()), - QUrl::TolerantMode))) { - QMessageBox::warning( - this, - tr("Could not open email client"), - tr("There was an error when launching the email client to " - "create a new message. Maybe no default email client is " - "configured?")); - } + Utility::openEmailComposer( + QString("I shared %1 with you").arg(fileName), + url.toString(), + this); } void ShareLinkWidget::openShareLink(const QUrl &url) { - if (!QDesktopServices::openUrl(url)) { - QMessageBox::warning( - this, - tr("Could not open browser"), - tr("There was an error when launching the browser to " - "view the public link share. Maybe no default browser is " - "configured?")); - } + Utility::openBrowser(url, this); } -void ShareLinkWidget::slotShareLinkButtonTriggered(QAction *action) +void ShareLinkWidget::slotShareLinkButtonClicked() +{ + auto share = sender()->property(propertyShareC).value>(); + bool downloadEnabled = share->getShowFileListing(); + _copyDirectLinkAction->setVisible(downloadEnabled); + _emailDirectLinkAction->setVisible(downloadEnabled); + + _shareLinkMenu->setProperty(propertyShareC, QVariant::fromValue(share)); + _shareLinkMenu->exec(QCursor::pos()); +} + +void ShareLinkWidget::slotShareLinkActionTriggered(QAction *action) { auto share = sender()->property(propertyShareC).value>(); if (action == _copyLinkAction) { - copyShareLink(share->getLink()); + QApplication::clipboard()->setText(share->getLink().toString()); } else if (action == _copyDirectLinkAction) { - copyShareLink(share->getDirectDownloadLink()); + QApplication::clipboard()->setText(share->getDirectDownloadLink().toString()); } else if (action == _emailLinkAction) { emailShareLink(share->getLink()); } else if (action == _emailDirectLinkAction) { @@ -564,14 +548,22 @@ void ShareLinkWidget::slotDeleteShareClicked() share->deleteShare(); } -void ShareLinkWidget::slotCheckBoxEditingClicked() +void ShareLinkWidget::slotPermissionsCheckboxClicked() { if (auto current = selectedShare()) { _ui->checkBox_editing->setEnabled(false); + _ui->checkBox_fileListing->setEnabled(false); _pi_editing->startAnimation(); _ui->errorLabel->hide(); - current->setPublicUpload(_ui->checkBox_editing->isChecked()); + SharePermissions perm = SharePermissionRead; + if (_ui->checkBox_editing->isChecked() && _ui->checkBox_fileListing->isChecked()) { + perm = SharePermissionRead | SharePermissionCreate + | SharePermissionUpdate | SharePermissionDelete; + } else if (_ui->checkBox_editing->isChecked() && !_ui->checkBox_fileListing->isChecked()) { + perm = SharePermissionCreate; + } + current->setPermissions(perm); } } @@ -585,7 +577,7 @@ QSharedPointer ShareLinkWidget::selectedShare() const return items.first()->data(Qt::UserRole).value>(); } -void ShareLinkWidget::slotPublicUploadSet() +void ShareLinkWidget::slotPermissionsSet() { if (sender() == selectedShare().data()) { slotShareSelectionChanged(); diff --git a/src/gui/sharelinkwidget.h b/src/gui/sharelinkwidget.h index 78dcc596b7..1d8397505c 100644 --- a/src/gui/sharelinkwidget.h +++ b/src/gui/sharelinkwidget.h @@ -65,19 +65,20 @@ private slots: void slotCheckBoxPasswordClicked(); void slotCheckBoxExpireClicked(); void slotPasswordReturnPressed(); - void slotCheckBoxEditingClicked(); + void slotPermissionsCheckboxClicked(); void slotExpireDateChanged(const QDate &date); void slotPasswordChanged(const QString &newText); void slotNameEdited(QTableWidgetItem *item); - void slotShareLinkButtonTriggered(QAction *action); + void slotShareLinkButtonClicked(); + void slotShareLinkActionTriggered(QAction *action); void slotDeleteShareFetched(); void slotCreateShareFetched(const QSharedPointer share); void slotCreateShareRequiresPassword(const QString &message); void slotPasswordSet(); void slotExpireSet(); - void slotPublicUploadSet(); + void slotPermissionsSet(); void slotServerError(int code, const QString &message); void slotPasswordSetError(int code, const QString &message); diff --git a/src/gui/sharelinkwidget.ui b/src/gui/sharelinkwidget.ui index a04511c034..9d80573be9 100644 --- a/src/gui/sharelinkwidget.ui +++ b/src/gui/sharelinkwidget.ui @@ -181,7 +181,7 @@ - + 0 @@ -194,14 +194,14 @@ 0 - + Allow editing - + Qt::Horizontal @@ -214,6 +214,13 @@ + + + + Show file listing + + + diff --git a/src/gui/sharemanager.cpp b/src/gui/sharemanager.cpp index 10d13a0677..37c39839fa 100644 --- a/src/gui/sharemanager.cpp +++ b/src/gui/sharemanager.cpp @@ -146,17 +146,14 @@ LinkShare::LinkShare(AccountPtr account, { } -bool LinkShare::getPublicUpload() +bool LinkShare::getPublicUpload() const { - return ((_permissions & SharePermissionUpdate) && (_permissions & SharePermissionCreate)); + return _permissions & SharePermissionCreate; } -void LinkShare::setPublicUpload(bool publicUpload) +bool LinkShare::getShowFileListing() const { - OcsShareJob *job = new OcsShareJob(_account); - connect(job, SIGNAL(shareJobFinished(QJsonDocument, QVariant)), SLOT(slotPublicUploadSet(QJsonDocument, QVariant))); - connect(job, SIGNAL(ocsError(int, QString)), SLOT(slotOcsError(int, QString))); - job->setPublicUpload(getId(), publicUpload); + return _permissions & SharePermissionRead; } QString LinkShare::getName() const @@ -177,17 +174,6 @@ QString LinkShare::getToken() const return _token; } -void LinkShare::slotPublicUploadSet(const QJsonDocument &, const QVariant &value) -{ - if (value.toBool()) { - _permissions = SharePermissionRead | SharePermissionUpdate | SharePermissionCreate; - } else { - _permissions = SharePermissionRead; - } - - emit publicUploadSet(); -} - void LinkShare::setPassword(const QString &password) { OcsShareJob *job = new OcsShareJob(_account); diff --git a/src/gui/sharemanager.h b/src/gui/sharemanager.h index 62ff6ee4c6..8c361b7326 100644 --- a/src/gui/sharemanager.h +++ b/src/gui/sharemanager.h @@ -153,16 +153,12 @@ public: /* * Get the publicUpload status of this share */ - bool getPublicUpload(); + bool getPublicUpload() const; /* - * Set a share to be public upload - * This function can only be called on link shares - * - * On success the publicUploadSet signal is emitted - * In case of a server error the serverError signal is emitted. + * Whether directory listings are available (READ permission) */ - void setPublicUpload(bool publicUpload); + bool getShowFileListing() const; /* * Returns the name of the link share. Can be empty. @@ -209,14 +205,12 @@ public: signals: void expireDateSet(); - void publicUploadSet(); void passwordSet(); void passwordSetError(int statusCode, const QString &message); void nameSet(); private slots: void slotPasswordSet(const QJsonDocument &, const QVariant &value); - void slotPublicUploadSet(const QJsonDocument &, const QVariant &value); void slotExpireDateSet(const QJsonDocument &reply, const QVariant &value); void slotSetPasswordError(int statusCode, const QString &message); void slotNameSet(const QJsonDocument &, const QVariant &value); diff --git a/src/gui/shareusergroupwidget.cpp b/src/gui/shareusergroupwidget.cpp index 1351f888f3..8cecac2a42 100644 --- a/src/gui/shareusergroupwidget.cpp +++ b/src/gui/shareusergroupwidget.cpp @@ -22,7 +22,7 @@ #include "theme.h" #include "configfile.h" #include "capabilities.h" - +#include "guiutility.h" #include "thumbnailjob.h" #include "sharee.h" #include "sharemanager.h" @@ -39,6 +39,8 @@ #include #include #include +#include +#include namespace OCC { @@ -46,6 +48,7 @@ ShareUserGroupWidget::ShareUserGroupWidget(AccountPtr account, const QString &sharePath, const QString &localPath, SharePermissions maxSharingPermissions, + const QByteArray &numericFileId, QWidget *parent) : QWidget(parent) , _ui(new Ui::ShareUserGroupWidget) @@ -53,6 +56,7 @@ ShareUserGroupWidget::ShareUserGroupWidget(AccountPtr account, , _sharePath(sharePath) , _localPath(localPath) , _maxSharingPermissions(maxSharingPermissions) + , _numericFileId(numericFileId) , _disableCompleterActivated(false) { setAttribute(Qt::WA_DeleteOnClose); @@ -80,6 +84,7 @@ ShareUserGroupWidget::ShareUserGroupWidget(AccountPtr account, connect(_manager, SIGNAL(shareCreated(QSharedPointer)), SLOT(getShares())); connect(_manager, SIGNAL(serverError(int, QString)), this, SLOT(displayError(int, QString))); connect(_ui->shareeLineEdit, SIGNAL(returnPressed()), SLOT(slotLineEditReturn())); + connect(_ui->privateLinkText, SIGNAL(linkActivated(QString)), SLOT(slotPrivateLinkShare())); // By making the next two QueuedConnections we can override // the strings the completer sets on the line edit. @@ -222,6 +227,21 @@ void ShareUserGroupWidget::slotAdjustScrollWidgetSize() } } +void ShareUserGroupWidget::slotPrivateLinkShare() +{ + auto menu = new QMenu(this); + menu->setAttribute(Qt::WA_DeleteOnClose); + + menu->addAction(tr("Open link in browser"), + this, SLOT(slotPrivateLinkOpenBrowser())); + menu->addAction(tr("Copy link to clipboard"), + this, SLOT(slotPrivateLinkCopy())); + menu->addAction(tr("Send link by email"), + this, SLOT(slotPrivateLinkEmail())); + + menu->exec(QCursor::pos()); +} + void ShareUserGroupWidget::slotShareesReady() { _pi_sharee.stopAnimation(); @@ -301,6 +321,24 @@ void ShareUserGroupWidget::displayError(int code, const QString &message) _ui->shareeLineEdit->setEnabled(true); } +void ShareUserGroupWidget::slotPrivateLinkOpenBrowser() +{ + Utility::openBrowser(_account->filePermalinkUrl(_numericFileId), this); +} + +void ShareUserGroupWidget::slotPrivateLinkCopy() +{ + QApplication::clipboard()->setText(_account->filePermalinkUrl(_numericFileId).toString()); +} + +void ShareUserGroupWidget::slotPrivateLinkEmail() +{ + Utility::openEmailComposer( + tr("I shared something with you"), + _account->filePermalinkUrl(_numericFileId).toString(), + this); +} + ShareUserLine::ShareUserLine(QSharedPointer share, SharePermissions maxSharingPermissions, bool isFile, diff --git a/src/gui/shareusergroupwidget.h b/src/gui/shareusergroupwidget.h index 36639196da..fbc2bdfa4d 100644 --- a/src/gui/shareusergroupwidget.h +++ b/src/gui/shareusergroupwidget.h @@ -57,6 +57,7 @@ public: const QString &sharePath, const QString &localPath, SharePermissions maxSharingPermissions, + const QByteArray &numericFileId, QWidget *parent = 0); ~ShareUserGroupWidget(); @@ -75,19 +76,25 @@ private slots: void slotCompleterHighlighted(const QModelIndex &index); void slotShareesReady(); void slotAdjustScrollWidgetSize(); + void slotPrivateLinkShare(); void displayError(int code, const QString &message); + void slotPrivateLinkOpenBrowser(); + void slotPrivateLinkCopy(); + void slotPrivateLinkEmail(); + private: Ui::ShareUserGroupWidget *_ui; AccountPtr _account; QString _sharePath; QString _localPath; + SharePermissions _maxSharingPermissions; + QByteArray _numericFileId; QCompleter *_completer; ShareeModel *_completerModel; QTimer _completionTimer; - SharePermissions _maxSharingPermissions; bool _isFile; bool _disableCompleterActivated; // in order to avoid that we share the contents twice ShareManager *_manager; diff --git a/src/gui/shareusergroupwidget.ui b/src/gui/shareusergroupwidget.ui index 615c5b2f19..028d897ce2 100644 --- a/src/gui/shareusergroupwidget.ui +++ b/src/gui/shareusergroupwidget.ui @@ -94,14 +94,24 @@ 0 0 - 395 - 221 + 377 + 169 + + + + <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline; color:#0000ff;">by giving them a private link</span></a>.</p></body></html> + + + true + + + diff --git a/src/gui/socketapi.cpp b/src/gui/socketapi.cpp index 82705c59ae..f2616d3780 100644 --- a/src/gui/socketapi.cpp +++ b/src/gui/socketapi.cpp @@ -32,7 +32,9 @@ #include "account.h" #include "capabilities.h" #include "asserts.h" +#include "guiutility.h" +#include #include #include #include @@ -45,6 +47,8 @@ #include #include +#include + #include @@ -124,7 +128,7 @@ public: void sendMessage(const QString &message, bool doWait = false) const { - qCInfo(lcSocketApi) << "Sending SocketAPI message: " << message << "to" << socket; + qCInfo(lcSocketApi) << "Sending SocketAPI message -->" << message << "to" << socket; QString localMessage = message; if (!localMessage.endsWith(QLatin1Char('\n'))) { localMessage.append(QLatin1Char('\n')); @@ -278,6 +282,7 @@ void SocketApi::slotReadSocket() // make sure that the path will match, especially on OS X. QString line = QString::fromUtf8(socket->readLine()).normalized(QString::NormalizationForm_C); line.chop(1); // remove the '\n' + qCInfo(lcSocketApi) << "Received SocketAPI message <--" << line << "from" << socket; QByteArray command = line.split(":").value(0).toAscii(); QByteArray functionWithArguments = "command_" + command + "(QString,SocketListener*)"; int indexOfMethod = staticMetaObject.indexOfMethod(functionWithArguments); @@ -369,8 +374,6 @@ void SocketApi::command_RETRIEVE_FOLDER_STATUS(const QString &argument, SocketLi void SocketApi::command_RETRIEVE_FILE_STATUS(const QString &argument, SocketListener *listener) { - qCDebug(lcSocketApi) << argument; - QString statusString; Folder *syncFolder = FolderMan::instance()->folderForPath(argument); @@ -399,8 +402,6 @@ void SocketApi::command_RETRIEVE_FILE_STATUS(const QString &argument, SocketList void SocketApi::command_SHARE(const QString &localFile, SocketListener *listener) { - qCDebug(lcSocketApi) << localFile; - auto theme = Theme::instance(); Folder *shareFolder = FolderMan::instance()->folderForPath(localFile); @@ -436,19 +437,10 @@ void SocketApi::command_SHARE(const QString &localFile, SocketListener *listener return; } - SyncJournalFileRecord rec = shareFolder->journalDb()->getFileRecord(localFileClean); - - bool allowReshare = true; // lets assume the good - if (rec.isValid()) { - // check the permission: Is resharing allowed? - if (!rec._remotePerm.contains('R')) { - allowReshare = false; - } - } const QString message = QLatin1String("SHARE:OK:") + QDir::toNativeSeparators(localFile); listener->sendMessage(message); - emit shareCommandReceived(remotePath, localFileClean, allowReshare); + emit shareCommandReceived(remotePath, localFileClean); } } @@ -459,8 +451,6 @@ void SocketApi::command_VERSION(const QString &, SocketListener *listener) void SocketApi::command_SHARE_STATUS(const QString &localFile, SocketListener *listener) { - qCDebug(lcSocketApi) << localFile; - Folder *shareFolder = FolderMan::instance()->folderForPath(localFile); if (!shareFolder) { @@ -514,6 +504,40 @@ void SocketApi::command_SHARE_MENU_TITLE(const QString &, SocketListener *listen listener->sendMessage(QLatin1String("SHARE_MENU_TITLE:") + tr("Share with %1", "parameter is ownCloud").arg(Theme::instance()->appNameGUI())); } +void SocketApi::command_COPY_PRIVATE_LINK(const QString &localFile, SocketListener *) +{ + auto url = getPrivateLinkUrl(localFile); + if (!url.isEmpty()) { + QApplication::clipboard()->setText(url.toString()); + } +} + +void SocketApi::command_EMAIL_PRIVATE_LINK(const QString &localFile, SocketListener *) +{ + auto url = getPrivateLinkUrl(localFile); + if (!url.isEmpty()) { + Utility::openEmailComposer( + tr("I shared something with you"), + url.toString(QUrl::FullyEncoded), + 0); + } +} + +void SocketApi::command_GET_STRINGS(const QString &, SocketListener *listener) +{ + static std::array, 5> strings { { + { "SHARE_MENU_TITLE", tr("Share...") }, + { "CONTEXT_MENU_TITLE", Theme::instance()->appNameGUI() }, + { "COPY_PRIVATE_LINK_MENU_TITLE", tr("Copy private link to clipboard") }, + { "EMAIL_PRIVATE_LINK_MENU_TITLE", tr("Send private link by email...") }, + } }; + listener->sendMessage(QString("GET_STRINGS:BEGIN")); + for (auto key_value : strings) { + listener->sendMessage(QString("STRING:%1:%2").arg(key_value.first, key_value.second)); + } + listener->sendMessage(QString("GET_STRINGS:END")); +} + QString SocketApi::buildRegisterPathMessage(const QString &path) { QFileInfo fi(path); @@ -522,4 +546,22 @@ QString SocketApi::buildRegisterPathMessage(const QString &path) return message; } +QUrl SocketApi::getPrivateLinkUrl(const QString &localFile) const +{ + Folder *shareFolder = FolderMan::instance()->folderForPath(localFile); + if (!shareFolder) { + qCWarning(lcSocketApi) << "Unknown path" << localFile; + return QUrl(); + } + + const QString localFileClean = QDir::cleanPath(localFile); + const QString file = localFileClean.mid(shareFolder->cleanPath().length() + 1); + + SyncJournalFileRecord rec = shareFolder->journalDb()->getFileRecord(file); + if (rec.isValid()) { + return shareFolder->accountState()->account()->filePermalinkUrl(rec.numericFileId()); + } + return QUrl(); +} + } // namespace OCC diff --git a/src/gui/socketapi.h b/src/gui/socketapi.h index e0b4c3ada3..d4f6e7bf86 100644 --- a/src/gui/socketapi.h +++ b/src/gui/socketapi.h @@ -55,8 +55,7 @@ public slots: void slotRegisterPath(const QString &alias); signals: - void shareCommandReceived(const QString &sharePath, const QString &localPath, bool resharingAllowed); - void shareUserGroupCommandReceived(const QString &sharePath, const QString &localPath, bool resharingAllowed); + void shareCommandReceived(const QString &sharePath, const QString &localPath); private slots: void slotNewConnection(); @@ -70,13 +69,22 @@ private: Q_INVOKABLE void command_RETRIEVE_FOLDER_STATUS(const QString &argument, SocketListener *listener); Q_INVOKABLE void command_RETRIEVE_FILE_STATUS(const QString &argument, SocketListener *listener); - Q_INVOKABLE void command_SHARE(const QString &localFile, SocketListener *listener); Q_INVOKABLE void command_VERSION(const QString &argument, SocketListener *listener); Q_INVOKABLE void command_SHARE_STATUS(const QString &localFile, SocketListener *listener); Q_INVOKABLE void command_SHARE_MENU_TITLE(const QString &argument, SocketListener *listener); + + // The context menu actions + Q_INVOKABLE void command_SHARE(const QString &localFile, SocketListener *listener); + Q_INVOKABLE void command_COPY_PRIVATE_LINK(const QString &localFile, SocketListener *listener); + Q_INVOKABLE void command_EMAIL_PRIVATE_LINK(const QString &localFile, SocketListener *listener); + + /** Sends translated/branded strings that may be useful to the integration */ + Q_INVOKABLE void command_GET_STRINGS(const QString &argument, SocketListener *listener); + QString buildRegisterPathMessage(const QString &path); + QUrl getPrivateLinkUrl(const QString &localFile) const; QSet _registeredAliases; QList _listeners; diff --git a/src/libsync/accessmanager.cpp b/src/libsync/accessmanager.cpp index 1d8e91d4cb..fb131b02c2 100644 --- a/src/libsync/accessmanager.cpp +++ b/src/libsync/accessmanager.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include "cookiejar.h" #include "accessmanager.h" @@ -59,6 +60,13 @@ void AccessManager::setRawCookie(const QByteArray &rawCookie, const QUrl &url) jar->setCookiesFromUrl(cookieList, url); } +static QByteArray generateRequestId() +{ + // Use a UUID with the starting and ending curly brace removed. + auto uuid = QUuid::createUuid().toByteArray(); + return uuid.mid(1, uuid.size() - 2); +} + QNetworkReply *AccessManager::createRequest(QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *outgoingData) { QNetworkRequest newRequest(request); @@ -79,6 +87,20 @@ QNetworkReply *AccessManager::createRequest(QNetworkAccessManager::Operation op, if (verb == "PROPFIND") { newRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("text/xml; charset=utf-8")); } + + // Generate a new request id + QByteArray requestId = generateRequestId(); + qInfo(lcAccessManager) << op << verb << newRequest.url().toString() << "has X-Request-ID" << requestId; + newRequest.setRawHeader("X-Request-ID", requestId); + +#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) + // only enable HTTP2 with Qt 5.9 because Qt 5.8.0 has too many bugs + // (only use one connection if the server does not support HTTP2) + if (newRequest.url().scheme() == "https") { // Not for "http": QTBUG-61397 + newRequest.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, true); + } +#endif + return QNetworkAccessManager::createRequest(op, newRequest, outgoingData); } diff --git a/src/libsync/account.cpp b/src/libsync/account.cpp index 39d15f9719..45168033f6 100644 --- a/src/libsync/account.cpp +++ b/src/libsync/account.cpp @@ -136,11 +136,13 @@ void Account::setCredentials(AbstractCredentials *cred) // The order for these two is important! Reading the credential's // settings accesses the account as well as account->_credentials, - // so deleteLater must be used. - _credentials = QSharedPointer(cred, &QObject::deleteLater); + _credentials.reset(cred); cred->setAccount(this); - _am = QSharedPointer(_credentials->getQNAM(), &QObject::deleteLater); + // Note: This way the QNAM can outlive the Account and Credentials. + // This is necessary to avoid issues with the QNAM being deleted while + // processing slotHandleSslErrors(). + _am = QSharedPointer(_credentials->createQNAM(), &QObject::deleteLater); if (jar) { _am->setCookieJar(jar); @@ -160,6 +162,12 @@ QUrl Account::davUrl() const return Utility::concatUrlPath(url(), davPath()); } +QUrl Account::filePermalinkUrl(const QByteArray &numericFileId) const +{ + return Utility::concatUrlPath(url(), + QLatin1String("/index.php/f/") + QUrl::toPercentEncoding(QString::fromLatin1(numericFileId))); +} + /** * clear all cookies. (Session cookies or not) */ @@ -199,7 +207,7 @@ void Account::resetNetworkAccessManager() // Use a QSharedPointer to allow locking the life of the QNAM on the stack. // Make it call deleteLater to make sure that we can return to any QNAM stack frames safely. - _am = QSharedPointer(_credentials->getQNAM(), &QObject::deleteLater); + _am = QSharedPointer(_credentials->createQNAM(), &QObject::deleteLater); _am->setCookieJar(jar); // takes ownership of the old cookie jar connect(_am.data(), SIGNAL(sslErrors(QNetworkReply *, QList)), @@ -391,6 +399,11 @@ void Account::handleInvalidCredentials() emit invalidCredentials(); } +void Account::clearQNAMCache() +{ + _am->clearAccessCache(); +} + const Capabilities &Account::capabilities() const { return _capabilities; diff --git a/src/libsync/account.h b/src/libsync/account.h index 4e554e5908..de1cd0293c 100644 --- a/src/libsync/account.h +++ b/src/libsync/account.h @@ -107,6 +107,9 @@ public: /** Returns webdav entry URL, based on url() */ QUrl davUrl() const; + /** Returns a permalink url for a file */ + QUrl filePermalinkUrl(const QByteArray &numericFileId) const; + /** Holds the accounts credentials */ AbstractCredentials *credentials() const; void setCredentials(AbstractCredentials *cred); @@ -186,6 +189,10 @@ public: /** Detects a specific bug in older server versions */ bool rootEtagChangesNotOnlySubFolderEtags(); + /** True when the server supports HTTP2 */ + bool isHttp2Supported() { return _http2Supported; } + void setHttp2Supported(bool value) { _http2Supported = value; }; + void clearCookieJar(); void lendCookieJarTo(QNetworkAccessManager *guest); QString cookieJarPath(); @@ -197,6 +204,10 @@ public: /// Called by network jobs on credential errors, emits invalidCredentials() void handleInvalidCredentials(); +public slots: + /// Used when forgetting credentials + void clearQNAMCache(); + signals: /// Emitted whenever there's network activity void propagatorNetworkActivity(); @@ -239,7 +250,8 @@ private: QScopedPointer _sslErrorHandler; QuotaInfo *_quotaInfo; QSharedPointer _am; - QSharedPointer _credentials; + QScopedPointer _credentials; + bool _http2Supported = false; /// Certificates that were explicitly rejected by the user QList _rejectedCertificates; diff --git a/src/libsync/capabilities.cpp b/src/libsync/capabilities.cpp index 651b1981d5..4722c0b99b 100644 --- a/src/libsync/capabilities.cpp +++ b/src/libsync/capabilities.cpp @@ -51,6 +51,11 @@ bool Capabilities::sharePublicLinkAllowUpload() const return _capabilities["files_sharing"].toMap()["public"].toMap()["upload"].toBool(); } +bool Capabilities::sharePublicLinkSupportsUploadOnly() const +{ + return _capabilities["files_sharing"].toMap()["public"].toMap()["supports_upload_only"].toBool(); +} + bool Capabilities::sharePublicLinkEnforcePassword() const { return _capabilities["files_sharing"].toMap()["public"].toMap()["password"].toMap()["enforced"].toBool(); diff --git a/src/libsync/capabilities.h b/src/libsync/capabilities.h index 4044d85841..6baaaa0ec7 100644 --- a/src/libsync/capabilities.h +++ b/src/libsync/capabilities.h @@ -36,6 +36,7 @@ public: bool shareAPI() const; bool sharePublicLink() const; bool sharePublicLinkAllowUpload() const; + bool sharePublicLinkSupportsUploadOnly() const; bool sharePublicLinkEnforcePassword() const; bool sharePublicLinkEnforceExpireDate() const; int sharePublicLinkExpireDateDays() const; diff --git a/src/libsync/connectionvalidator.cpp b/src/libsync/connectionvalidator.cpp index 0b5897f1ff..9eff065dee 100644 --- a/src/libsync/connectionvalidator.cpp +++ b/src/libsync/connectionvalidator.cpp @@ -282,10 +282,18 @@ bool ConnectionValidator::setAndCheckServerVersion(const QString &version) reportResult(ServerVersionMismatch); return false; } - // We attempt to work with servers >= 5.0.0 but warn users. // Check usages of Account::serverVersionUnsupported() for details. +#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) + // Record that the server supports HTTP/2 + if (auto job = qobject_cast(sender())) { + if (auto reply = job->reply()) { + _account->setHttp2Supported( + reply->attribute(QNetworkRequest::HTTP2WasUsedAttribute).toBool()); + } + } +#endif return true; } diff --git a/src/libsync/creds/abstractcredentials.h b/src/libsync/creds/abstractcredentials.h index 09ba50e3e4..9958b56666 100644 --- a/src/libsync/creds/abstractcredentials.h +++ b/src/libsync/creds/abstractcredentials.h @@ -43,7 +43,7 @@ public: virtual QString authType() const = 0; virtual QString user() const = 0; - virtual QNetworkAccessManager *getQNAM() const = 0; + virtual QNetworkAccessManager *createQNAM() const = 0; /** Whether there are credentials that can be used for a connection attempt. */ virtual bool ready() const = 0; diff --git a/src/libsync/creds/dummycredentials.cpp b/src/libsync/creds/dummycredentials.cpp index c55e979ac1..3c5609d356 100644 --- a/src/libsync/creds/dummycredentials.cpp +++ b/src/libsync/creds/dummycredentials.cpp @@ -27,7 +27,7 @@ QString DummyCredentials::user() const return _user; } -QNetworkAccessManager *DummyCredentials::getQNAM() const +QNetworkAccessManager *DummyCredentials::createQNAM() const { return new AccessManager; } diff --git a/src/libsync/creds/dummycredentials.h b/src/libsync/creds/dummycredentials.h index ad56a08dd5..6729b2e5a5 100644 --- a/src/libsync/creds/dummycredentials.h +++ b/src/libsync/creds/dummycredentials.h @@ -28,7 +28,7 @@ public: QString _password; QString authType() const Q_DECL_OVERRIDE; QString user() const Q_DECL_OVERRIDE; - QNetworkAccessManager *getQNAM() const Q_DECL_OVERRIDE; + QNetworkAccessManager *createQNAM() const Q_DECL_OVERRIDE; bool ready() const Q_DECL_OVERRIDE; bool stillValid(QNetworkReply *reply) Q_DECL_OVERRIDE; void fetchFromKeychain() Q_DECL_OVERRIDE; diff --git a/src/libsync/creds/httpcredentials.cpp b/src/libsync/creds/httpcredentials.cpp index ce37ce93ee..0f71f445ee 100644 --- a/src/libsync/creds/httpcredentials.cpp +++ b/src/libsync/creds/httpcredentials.cpp @@ -58,7 +58,7 @@ protected: QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData) Q_DECL_OVERRIDE { QNetworkRequest req(request); - if (!_cred->password().isEmpty()) { + if (_cred && !_cred->password().isEmpty()) { if (_cred->isUsingOAuth()) { req.setRawHeader("Authorization", "Bearer " + _cred->password().toUtf8()); } else { @@ -72,7 +72,7 @@ protected: req.setRawHeader("Authorization", "Basic " + credHash); } - if (!_cred->_clientSslKey.isNull() && !_cred->_clientSslCertificate.isNull()) { + if (_cred && !_cred->_clientSslKey.isNull() && !_cred->_clientSslCertificate.isNull()) { // SSL configuration QSslConfiguration sslConfiguration = req.sslConfiguration(); sslConfiguration.setLocalCertificate(_cred->_clientSslCertificate); @@ -85,7 +85,9 @@ protected: } private: - const HttpCredentials *_cred; + // The credentials object dies along with the account, while the QNAM might + // outlive both. + QPointer _cred; }; @@ -135,7 +137,7 @@ void HttpCredentials::setAccount(Account *account) } } -QNetworkAccessManager *HttpCredentials::getQNAM() const +QNetworkAccessManager *HttpCredentials::createQNAM() const { AccessManager *qnam = new HttpCredentialsAccessManager(this); @@ -290,8 +292,11 @@ void HttpCredentials::slotReadJobDone(QKeychain::Job *incomingJob) } } -void HttpCredentials::refreshAccessToken() +bool HttpCredentials::refreshAccessToken() { + if (_refreshToken.isEmpty()) + return false; + QUrl requestToken(_account->url().toString() + QLatin1String("/index.php/apps/oauth2/api/v1/token?grant_type=refresh_token&refresh_token=") + _refreshToken); @@ -322,6 +327,7 @@ void HttpCredentials::refreshAccessToken() } emit fetched(); }); + return true; } @@ -342,6 +348,9 @@ void HttpCredentials::invalidateToken() return; } + // clear the session cookie. + _account->clearCookieJar(); + if (!_refreshToken.isEmpty()) { // Only invalidate the access_token (_password) but keep the _refreshToken in the keychain // (when coming from forgetSensitiveData, the _refreshToken is cleared) @@ -362,24 +371,12 @@ void HttpCredentials::invalidateToken() job2->setKey(kck); job2->start(); - // clear the session cookie. - _account->clearCookieJar(); - // let QNAM forget about the password // This needs to be done later in the event loop because we might be called (directly or // indirectly) from QNetworkAccessManagerPrivate::authenticationRequired, which itself // is a called from a BlockingQueuedConnection from the Qt HTTP thread. And clearing the // cache needs to synchronize again with the HTTP thread. - QTimer::singleShot(0, this, SLOT(clearQNAMCache())); -} - -void HttpCredentials::clearQNAMCache() -{ -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) - _account->networkAccessManager()->clearAccessCache(); -#else - _account->resetNetworkAccessManager(); -#endif + QTimer::singleShot(0, _account, SLOT(clearQNAMCache())); } void HttpCredentials::forgetSensitiveData() @@ -400,6 +397,7 @@ void HttpCredentials::persist() _account->setCredentialSetting(QLatin1String(userC), _user); _account->setCredentialSetting(QLatin1String(isOAuthC), isUsingOAuth()); + _account->wantsAccountSaved(_account); // write cert WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName()); diff --git a/src/libsync/creds/httpcredentials.h b/src/libsync/creds/httpcredentials.h index 45b01c5ee5..df3bcdabc4 100644 --- a/src/libsync/creds/httpcredentials.h +++ b/src/libsync/creds/httpcredentials.h @@ -46,8 +46,8 @@ namespace OCC { 1) First, AccountState will attempt to load the certificate from the keychain - ----> fetchFromKeychain ------------------------> shortcut to refreshAccessToken if the cached - | } information is still valid + ----> fetchFromKeychain + | } v } slotReadClientCertPEMJobDone } There are first 3 QtKeychain jobs to fetch | } the TLS client keys, if any, and the password @@ -79,7 +79,7 @@ public: HttpCredentials(const QString &user, const QString &password, const QSslCertificate &certificate = QSslCertificate(), const QSslKey &key = QSslKey()); QString authType() const Q_DECL_OVERRIDE; - QNetworkAccessManager *getQNAM() const Q_DECL_OVERRIDE; + QNetworkAccessManager *createQNAM() const Q_DECL_OVERRIDE; bool ready() const Q_DECL_OVERRIDE; void fetchFromKeychain() Q_DECL_OVERRIDE; bool stillValid(QNetworkReply *reply) Q_DECL_OVERRIDE; @@ -92,7 +92,10 @@ public: QString fetchUser(); virtual bool sslIsTrusted() { return false; } - void refreshAccessToken(); + /* If we still have a valid refresh token, try to refresh it assynchronously and emit fetched() + * otherwise return false + */ + bool refreshAccessToken(); // To fetch the user name as early as possible void setAccount(Account *account) Q_DECL_OVERRIDE; @@ -110,7 +113,6 @@ private Q_SLOTS: void slotWriteClientCertPEMJobDone(QKeychain::Job *); void slotWriteClientKeyPEMJobDone(QKeychain::Job *); void slotWriteJobDone(QKeychain::Job *); - void clearQNAMCache(); protected: QString _user; diff --git a/src/libsync/discoveryphase.cpp b/src/libsync/discoveryphase.cpp index 16b98faeb2..dcad77390c 100644 --- a/src/libsync/discoveryphase.cpp +++ b/src/libsync/discoveryphase.cpp @@ -276,6 +276,10 @@ void DiscoverySingleDirectoryJob::start() << "http://owncloud.org/ns:checksums"; if (_isRootPath) props << "http://owncloud.org/ns:data-fingerprint"; + if (_account->serverVersionInt() >= Account::makeServerVersion(10, 0, 0)) { + // Server older than 10.0 have performances issue if we ask for the share-types on every PROPFIND + props << "http://owncloud.org/ns:share-types"; + } lsColJob->setProperties(props); @@ -371,9 +375,25 @@ static csync_vio_file_stat_t *propertyMapToFileStat(const QMap if (!checksum.isEmpty()) { file_stat->checksumHeader = strdup(checksum.constData()); } + } else if (property == "share-types" && !value.isEmpty()) { + // Since QMap is sorted, "share-types" is always "permissions". + if (file_stat->remotePerm[0] == '\0' || !(file_stat->fields & CSYNC_VIO_FILE_STAT_FIELDS_PERM)) { + qWarning() << "Server returned a share type, but no permissions?"; + } else { + // S means shared with me. + // But for our purpose, we want to know if the file is shared. It does not matter + // if we are the owner or not. + // Piggy back on the persmission field 'S' + if (!std::strchr(file_stat->remotePerm, 'S')) { + if (std::strlen(file_stat->remotePerm) < sizeof(file_stat->remotePerm) - 1) { + std::strcat(file_stat->remotePerm, "S"); + } else { + qWarning() << "permissions too large" << file_stat->remotePerm; + } + } + } } } - return file_stat; } diff --git a/src/libsync/discoveryphase.h b/src/libsync/discoveryphase.h index 9603c39ab0..b7d252c675 100644 --- a/src/libsync/discoveryphase.h +++ b/src/libsync/discoveryphase.h @@ -53,9 +53,10 @@ struct SyncOptions /** If a confirmation should be asked for external storages */ bool _confirmExternalStorage; - /** The initial un-adjusted chunk size in bytes for chunked uploads + /** The initial un-adjusted chunk size in bytes for chunked uploads, both + * for old and new chunking algorithm, which classifies the item to be chunked * - * When dynamic chunk size adjustments are done, this is the + * In chunkingNG, when dynamic chunk size adjustments are done, this is the * starting value and is then gradually adjusted within the * minChunkSize / maxChunkSize bounds. */ diff --git a/src/libsync/filesystem.cpp b/src/libsync/filesystem.cpp index c068f46943..18e6f800ff 100644 --- a/src/libsync/filesystem.cpp +++ b/src/libsync/filesystem.cpp @@ -54,12 +54,14 @@ Q_LOGGING_CATEGORY(lcFileSystem, "sync.filesystem", QtInfoMsg) QString FileSystem::longWinPath(const QString &inpath) { - QString path(inpath); - #ifdef Q_OS_WIN - path = QString::fromWCharArray(static_cast(c_utf8_path_to_locale(inpath.toUtf8()))); -#endif + const char *unc_str = c_path_to_UNC(inpath.toUtf8()); + QString path = QString::fromUtf8(unc_str); + free((void*)unc_str); return path; +#else + return inpath; +#endif } bool FileSystem::fileEquals(const QString &fn1, const QString &fn2) diff --git a/src/libsync/owncloudpropagator.cpp b/src/libsync/owncloudpropagator.cpp index 62f12f0232..17ab9facab 100644 --- a/src/libsync/owncloudpropagator.cpp +++ b/src/libsync/owncloudpropagator.cpp @@ -84,18 +84,18 @@ int OwncloudPropagator::maximumActiveTransferJob() // disable parallelism when there is a network limit. return 1; } - return qCeil(hardMaximumActiveJob() / 2.); + return qMin(3, qCeil(hardMaximumActiveJob() / 2.)); } /* The maximum number of active jobs in parallel */ int OwncloudPropagator::hardMaximumActiveJob() { static int max = qgetenv("OWNCLOUD_MAX_PARALLEL").toUInt(); - if (!max) { - max = 6; //default (Qt cannot do more anyway) - // TODO: increase this number when using HTTP2 - } - return max; + if (max) + return max; + if (_account->isHttp2Supported()) + return 20; + return 6; // (Qt cannot do more anyway) } PropagateItemJob::~PropagateItemJob() @@ -129,15 +129,7 @@ static time_t getMaxBlacklistTime() static SyncJournalErrorBlacklistRecord createBlacklistEntry( const SyncJournalErrorBlacklistRecord &old, const SyncFileItem &item) { - SyncJournalErrorBlacklistRecord entry; - - entry._errorString = item._errorString; - entry._lastTryModtime = item._modtime; - entry._lastTryEtag = item._etag; - entry._lastTryTime = Utility::qDateTimeToTime_t(QDateTime::currentDateTime()); - entry._file = item._file; - entry._renameTarget = item._renameTarget; - + auto entry = SyncJournalErrorBlacklistRecord::fromSyncFileItem(item); entry._retryCount = old._retryCount + 1; static time_t minBlacklistTime(getMinBlacklistTime()); @@ -162,6 +154,10 @@ static SyncJournalErrorBlacklistRecord createBlacklistEntry( entry._ignoreDuration = 0; } + if (item._httpErrorCode == 507) { + entry._errorCategory = SyncJournalErrorBlacklistRecord::InsufficientRemoteStorage; + } + return entry; } @@ -189,14 +185,13 @@ static void blacklistUpdate(SyncJournalDb *journal, SyncFileItem &item) } auto newEntry = createBlacklistEntry(oldEntry, item); - journal->updateErrorBlacklistEntry(newEntry); + journal->setErrorBlacklistEntry(newEntry); // Suppress the error if it was and continues to be blacklisted. // An ignoreDuration of 0 mean we're tracking the error, but not actively // suppressing it. if (item._hasBlacklistEntry && newEntry._ignoreDuration > 0) { - item._status = SyncFileItem::FileIgnored; - item._errorString.prepend(PropagateItemJob::tr("Continue blacklisting:") + " "); + item._status = SyncFileItem::BlacklistedError; qCInfo(lcPropagator) << "blacklisting " << item._file << " for " << newEntry._ignoreDuration @@ -239,12 +234,18 @@ void PropagateItemJob::done(SyncFileItem::Status statusArg, const QString &error _item->_status = SyncFileItem::SoftError; } + // Blacklist handling switch (_item->_status) { case SyncFileItem::SoftError: case SyncFileItem::FatalError: case SyncFileItem::NormalError: + case SyncFileItem::BlacklistedError: // Check the blacklist, possibly adjusting the item (including its status) - blacklistUpdate(propagator()->_journal, *_item); + // but not if this status comes from blacklisting in the first place + if (!(_item->_status == SyncFileItem::BlacklistedError + && _item->_instruction == CSYNC_INSTRUCTION_IGNORE)) { + blacklistUpdate(propagator()->_journal, *_item); + } break; case SyncFileItem::Success: case SyncFileItem::Restoration: @@ -379,7 +380,8 @@ PropagateItemJob *OwncloudPropagator::createJob(const SyncFileItemPtr &item) return job; } else { PropagateUploadFileCommon *job = 0; - if (item->_size > _chunkSize && account()->capabilities().chunkingNg()) { + if (item->_size > syncOptions()._initialChunkSize && account()->capabilities().chunkingNg()) { + // Item is above _initialChunkSize, thus will be classified as to be chunked job = new PropagateUploadFileNG(this, item); } else { job = new PropagateUploadFileV1(this, item); diff --git a/src/libsync/owncloudpropagator.h b/src/libsync/owncloudpropagator.h index 0e7dd573cc..02145fa193 100644 --- a/src/libsync/owncloudpropagator.h +++ b/src/libsync/owncloudpropagator.h @@ -417,7 +417,6 @@ public: */ DiskSpaceResult diskSpaceCheck() const; - private slots: /** Emit the finished signal and make sure it is only emitted once */ @@ -445,6 +444,9 @@ signals: */ void touchedFile(const QString &fileName); + void insufficientLocalStorage(); + void insufficientRemoteStorage(); + private: AccountPtr _account; QScopedPointer _rootJob; diff --git a/src/libsync/owncloudtheme.cpp b/src/libsync/owncloudtheme.cpp index 5528fe92d3..4f30571b24 100644 --- a/src/libsync/owncloudtheme.cpp +++ b/src/libsync/owncloudtheme.cpp @@ -44,7 +44,7 @@ QString ownCloudTheme::about() const { QString devString; devString = trUtf8("

Version %2. For more information visit https://%4

" - "

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

" + "

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

" "

By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, " " Jan-Christoph Borchardt, and others.

" "

Copyright ownCloud GmbH

" diff --git a/src/libsync/progressdispatcher.cpp b/src/libsync/progressdispatcher.cpp index 8473bc3ada..d5b3f20f50 100644 --- a/src/libsync/progressdispatcher.cpp +++ b/src/libsync/progressdispatcher.cpp @@ -90,7 +90,8 @@ bool Progress::isWarningKind(SyncFileItem::Status kind) { return kind == SyncFileItem::SoftError || kind == SyncFileItem::NormalError || kind == SyncFileItem::FatalError || kind == SyncFileItem::FileIgnored - || kind == SyncFileItem::Conflict || kind == SyncFileItem::Restoration; + || kind == SyncFileItem::Conflict || kind == SyncFileItem::Restoration + || kind == SyncFileItem::BlacklistedError; } bool Progress::isIgnoredKind(SyncFileItem::Status kind) @@ -135,6 +136,8 @@ ProgressInfo::ProgressInfo() void ProgressInfo::reset() { + _status = Starting; + _currentItems.clear(); _currentDiscoveredFolder.clear(); _sizeProgress = Progress(); @@ -150,6 +153,11 @@ void ProgressInfo::reset() _lastCompletedItem = SyncFileItem(); } +ProgressInfo::Status ProgressInfo::status() const +{ + return _status; +} + void ProgressInfo::startEstimateUpdates() { _updateEstimatesTimer.start(1000); diff --git a/src/libsync/progressdispatcher.h b/src/libsync/progressdispatcher.h index 05aa06b4ff..f7015f6c55 100644 --- a/src/libsync/progressdispatcher.h +++ b/src/libsync/progressdispatcher.h @@ -41,6 +41,35 @@ public: */ void reset(); + /** Records the status of the sync run + */ + enum Status { + /// Emitted once at start + Starting, + + /** + * Emitted once without _currentDiscoveredFolder when it starts, + * then for each folder. + */ + Discovery, + + /// Emitted once when reconcile starts + Reconcile, + + /// Emitted during propagation, with progress data + Propagation, + + /** + * Emitted once when done + * + * Except when SyncEngine jumps directly to finalize() without going + * through slotFinished(). + */ + Done + }; + + Status status() const; + /** * Called when propagation starts. * @@ -140,6 +169,8 @@ public: friend class ProgressInfo; }; + Status _status; + struct OWNCLOUDSYNC_EXPORT ProgressItem { SyncFileItem _item; @@ -217,6 +248,16 @@ namespace Progress { OWNCLOUDSYNC_EXPORT bool isIgnoredKind(SyncFileItem::Status); } +/** Type of error + * + * Used for ProgressDispatcher::syncError. May trigger error interactivity + * in IssuesWidget. + */ +enum class ErrorCategory { + Normal, + InsufficientRemoteStorage, +}; + /** * @file progressdispatcher.h * @brief A singleton class to provide sync progress information to other gui classes. @@ -249,6 +290,11 @@ signals: */ void itemCompleted(const QString &folder, const SyncFileItemPtr &item); + /** + * @brief A new folder-wide sync error was seen. + */ + void syncError(const QString &folder, const QString &message, ErrorCategory category); + protected: void setProgressInfo(const QString &folder, const ProgressInfo &progress); diff --git a/src/libsync/propagatedownload.cpp b/src/libsync/propagatedownload.cpp index 8cc87ccac9..2a6ca14394 100644 --- a/src/libsync/propagatedownload.cpp +++ b/src/libsync/propagatedownload.cpp @@ -118,6 +118,8 @@ void GETFileJob::start() req.setRawHeader(it.key(), it.value()); } + req.setPriority(QNetworkRequest::LowPriority); // Long downloads must not block non-propagation jobs. + if (_directDownloadUrl.isEmpty()) { sendRequest("GET", makeDavUrl(path()), req); } else { @@ -427,9 +429,12 @@ void PropagateDownloadFile::startDownload() const auto diskSpaceResult = propagator()->diskSpaceCheck(); if (diskSpaceResult != OwncloudPropagator::DiskSpaceOk) { if (diskSpaceResult == OwncloudPropagator::DiskSpaceFailure) { - _item->_errorMayBeBlacklisted = true; - done(SyncFileItem::NormalError, - tr("The download would reduce free disk space below %1").arg(Utility::octetsToString(freeSpaceLimit()))); + // Using BlacklistedError here will make the error not pop up in the account + // tab: instead we'll generate a general "disk space low" message and show + // these detail errors only in the error view. + done(SyncFileItem::BlacklistedError, + tr("The download would reduce free local disk space below the limit")); + emit propagator()->insufficientLocalStorage(); } else if (diskSpaceResult == OwncloudPropagator::DiskSpaceCritical) { done(SyncFileItem::FatalError, tr("Free space on disk is less than %1").arg(Utility::octetsToString(criticalFreeSpaceLimit()))); diff --git a/src/libsync/propagateupload.cpp b/src/libsync/propagateupload.cpp index f3b0e8336b..3032bb65fa 100644 --- a/src/libsync/propagateupload.cpp +++ b/src/libsync/propagateupload.cpp @@ -35,12 +35,6 @@ #include #include -#if QT_VERSION < QT_VERSION_CHECK(5, 4, 2) -namespace { -const char owncloudShouldSoftCancelPropertyName[] = "owncloud-should-soft-cancel"; -} -#endif - namespace OCC { Q_LOGGING_CATEGORY(lcPutJob, "sync.networkjob.put", QtInfoMsg) @@ -79,6 +73,8 @@ void PUTFileJob::start() req.setRawHeader(it.key(), it.value()); } + req.setPriority(QNetworkRequest::LowPriority); // Long uploads must not block non-propagation jobs. + if (_url.isValid()) { sendRequest("PUT", _url, req, _device); } else { @@ -91,27 +87,10 @@ void PUTFileJob::start() connect(reply(), SIGNAL(uploadProgress(qint64, qint64)), this, SIGNAL(uploadProgress(qint64, qint64))); connect(this, SIGNAL(networkActivity()), account().data(), SIGNAL(propagatorNetworkActivity())); - -// For Qt versions not including https://codereview.qt-project.org/110150 -// Also do the runtime check if compiled with an old Qt but running with fixed one. -// (workaround disabled on windows and mac because the binaries we ship have patched qt) -#if QT_VERSION < QT_VERSION_CHECK(5, 4, 2) && !defined Q_OS_WIN && !defined Q_OS_MAC - if (QLatin1String(qVersion()) < QLatin1String("5.4.2")) - connect(_device, SIGNAL(wasReset()), this, SLOT(slotSoftAbort())); -#endif - _requestTimer.start(); AbstractNetworkJob::start(); } -#if QT_VERSION < QT_VERSION_CHECK(5, 4, 2) -void PUTFileJob::slotSoftAbort() -{ - reply()->setProperty(owncloudShouldSoftCancelPropertyName, true); - reply()->abort(); -} -#endif - void PollJob::start() { setTimeout(120 * 1000); @@ -522,6 +501,38 @@ void PropagateUploadFileCommon::checkResettingErrors() } } +void PropagateUploadFileCommon::commonErrorHandling(AbstractNetworkJob *job) +{ + QByteArray replyContent; + QString errorString = job->errorStringParsingBody(&replyContent); + qCDebug(lcPropagateUpload) << replyContent; // display the XML error in the debug + + if (_item->_httpErrorCode == 412) { + // Precondition Failed: Either an etag or a checksum mismatch. + + // Maybe the bad etag is in the database, we need to clear the + // parent folder etag so we won't read from DB next sync. + propagator()->_journal->avoidReadFromDbOnNextSync(_item->_file); + propagator()->_anotherSyncNeeded = true; + } + + // Ensure errors that should eventually reset the chunked upload are tracked. + checkResettingErrors(); + + SyncFileItem::Status status = classifyError(job->reply()->error(), _item->_httpErrorCode, + &propagator()->_anotherSyncNeeded); + + if (_item->_httpErrorCode == 507) { + // Insufficient remote storage. + _item->_errorMayBeBlacklisted = true; + status = SyncFileItem::BlacklistedError; + errorString = tr("Upload of %1 exceeds the quota for the folder").arg(Utility::octetsToString(_item->_size)); + emit propagator()->insufficientRemoteStorage(); + } + + abortWithError(status, errorString); +} + void PropagateUploadFileCommon::slotJobDestroyed(QObject *job) { _jobs.erase(std::remove(_jobs.begin(), _jobs.end(), job), _jobs.end()); diff --git a/src/libsync/propagateupload.h b/src/libsync/propagateupload.h index 5e16b5ff98..dd75509254 100644 --- a/src/libsync/propagateupload.h +++ b/src/libsync/propagateupload.h @@ -154,10 +154,6 @@ signals: void finishedSignal(); void uploadProgress(qint64, qint64); -private slots: -#if QT_VERSION < 0x050402 - void slotSoftAbort(); -#endif }; /** @@ -277,6 +273,11 @@ protected: */ void checkResettingErrors(); + /** + * Error handling functionality that is shared between jobs. + */ + void commonErrorHandling(AbstractNetworkJob *job); + // Bases headers that need to be sent with every chunk QMap headers(); }; @@ -307,7 +308,11 @@ private: int _chunkCount; /// Total number of chunks for this file int _transferId; /// transfer id (part of the url) - quint64 chunkSize() const { return propagator()->syncOptions()._initialChunkSize; } + quint64 chunkSize() const { + // Old chunking does not use dynamic chunking algorithm, and does not adjusts the chunk size respectively, + // thus this value should be used as the one classifing item to be chunked + return propagator()->syncOptions()._initialChunkSize; + } public: diff --git a/src/libsync/propagateuploadng.cpp b/src/libsync/propagateuploadng.cpp index 45768102da..7717203a14 100644 --- a/src/libsync/propagateuploadng.cpp +++ b/src/libsync/propagateuploadng.cpp @@ -284,9 +284,9 @@ void PropagateUploadFileNG::startNextChunk() headers["If"] = "<" + destination.toUtf8() + "> ([" + ifMatch + "])"; } if (!_transmissionChecksumHeader.isEmpty()) { + qCInfo(lcPropagateUpload) << destination << _transmissionChecksumHeader; headers[checkSumHeaderC] = _transmissionChecksumHeader; } - headers["OC-Total-Length"] = QByteArray::number(fileSize); auto job = new MoveJob(propagator()->account(), Utility::concatUrlPath(chunkUrl(), "/.file"), @@ -333,8 +333,6 @@ void PropagateUploadFileNG::startNextChunk() job->start(); propagator()->_activeJobList.append(this); _currentChunk++; - - // FIXME! parallel chunk? } void PropagateUploadFileNG::slotPutFinished() @@ -366,16 +364,7 @@ void PropagateUploadFileNG::slotPutFinished() if (err != QNetworkReply::NoError) { _item->_httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - QByteArray replyContent; - QString errorString = job->errorStringParsingBody(&replyContent); - qCDebug(lcPropagateUpload) << replyContent; // display the XML error in the debug - - // Ensure errors that should eventually reset the chunked upload are tracked. - checkResettingErrors(); - - SyncFileItem::Status status = classifyError(err, _item->_httpErrorCode, - &propagator()->_anotherSyncNeeded); - abortWithError(status, errorString); + commonErrorHandling(job); return; } @@ -400,6 +389,7 @@ void PropagateUploadFileNG::slotPutFinished() // the chunk sizes a bit. quint64 targetSize = (propagator()->_chunkSize + predictedGoodSize) / 2; + // Adjust the dynamic chunk size _chunkSize used for sizing of the item's chunks to be send propagator()->_chunkSize = qBound( propagator()->syncOptions()._minChunkSize, targetSize, @@ -458,21 +448,7 @@ void PropagateUploadFileNG::slotMoveJobFinished() _item->_httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (err != QNetworkReply::NoError) { - if (_item->_httpErrorCode == 412) { - // Precondition Failed: Either an etag or a checksum mismatch. - - // Maybe the bad etag is in the database, we need to clear the - // parent folder etag so we won't read from DB next sync. - propagator()->_journal->avoidReadFromDbOnNextSync(_item->_file); - propagator()->_anotherSyncNeeded = true; - } - - // Ensure errors that should eventually reset the chunked upload are tracked. - checkResettingErrors(); - - SyncFileItem::Status status = classifyError(err, _item->_httpErrorCode, - &propagator()->_anotherSyncNeeded); - abortWithError(status, job->errorStringParsingBody()); + commonErrorHandling(job); return; } if (_item->_httpErrorCode != 201 && _item->_httpErrorCode != 204) { diff --git a/src/libsync/propagateuploadv1.cpp b/src/libsync/propagateuploadv1.cpp index b48b433ab7..74a5f6e52e 100644 --- a/src/libsync/propagateuploadv1.cpp +++ b/src/libsync/propagateuploadv1.cpp @@ -104,6 +104,7 @@ void PropagateUploadFileV1::startNextChunk() qCDebug(lcPropagateUpload) << _chunkCount << isFinalChunk << chunkStart << currentChunkSize; if (isFinalChunk && !_transmissionChecksumHeader.isEmpty()) { + qCInfo(lcPropagateUpload) << propagator()->_remoteFolder + path << _transmissionChecksumHeader; headers[checkSumHeaderC] = _transmissionChecksumHeader; } @@ -201,25 +202,7 @@ void PropagateUploadFileV1::slotPutFinished() "It is restored and your edit is in the conflict file."))) { return; } - QByteArray replyContent; - QString errorString = job->errorStringParsingBody(&replyContent); - qCDebug(lcPropagateUpload) << replyContent; // display the XML error in the debug - - if (_item->_httpErrorCode == 412) { - // Precondition Failed: Either an etag or a checksum mismatch. - - // Maybe the bad etag is in the database, we need to clear the - // parent folder etag so we won't read from DB next sync. - propagator()->_journal->avoidReadFromDbOnNextSync(_item->_file); - propagator()->_anotherSyncNeeded = true; - } - - // Ensure errors that should eventually reset the chunked upload are tracked. - checkResettingErrors(); - - SyncFileItem::Status status = classifyError(err, _item->_httpErrorCode, - &propagator()->_anotherSyncNeeded); - abortWithError(status, errorString); + commonErrorHandling(job); return; } diff --git a/src/libsync/propagatorjobs.cpp b/src/libsync/propagatorjobs.cpp index 5cf17b26ac..65d7ef86ad 100644 --- a/src/libsync/propagatorjobs.cpp +++ b/src/libsync/propagatorjobs.cpp @@ -42,6 +42,11 @@ Q_LOGGING_CATEGORY(lcPropagateLocalRemove, "sync.propagator.localremove", QtInfo Q_LOGGING_CATEGORY(lcPropagateLocalMkdir, "sync.propagator.localmkdir", QtInfoMsg) Q_LOGGING_CATEGORY(lcPropagateLocalRename, "sync.propagator.localrename", QtInfoMsg) +QByteArray localFileIdFromFullId(const QByteArray &id) +{ + return id.left(8); +} + /** * Code inspired from Qt5's QDir::removeRecursively * The code will update the database in case of error. diff --git a/src/libsync/syncengine.cpp b/src/libsync/syncengine.cpp index faf003fbf6..d4afcc57c6 100644 --- a/src/libsync/syncengine.cpp +++ b/src/libsync/syncengine.cpp @@ -24,6 +24,7 @@ #include "csync_private.h" #include "filesystem.h" #include "propagateremotedelete.h" +#include "propagatedownload.h" #include "asserts.h" #ifdef Q_OS_WIN @@ -54,6 +55,7 @@ namespace OCC { Q_LOGGING_CATEGORY(lcEngine, "sync.engine", QtInfoMsg) +static const int s_touchedFilesMaxAgeMs = 15 * 1000; bool SyncEngine::s_anySyncRunning = false; qint64 SyncEngine::minimumFileAgeForUpload = 2000; @@ -256,12 +258,24 @@ bool SyncEngine::checkErrorBlacklisting(SyncFileItem &item) } } + int waitSeconds = entry._lastTryTime + entry._ignoreDuration - now; qCInfo(lcEngine) << "Item is on blacklist: " << entry._file << "retries:" << entry._retryCount - << "for another" << (entry._lastTryTime + entry._ignoreDuration - now) << "s"; - item._instruction = CSYNC_INSTRUCTION_ERROR; - item._status = SyncFileItem::FileIgnored; - item._errorString = tr("The item is not synced because of previous errors: %1").arg(entry._errorString); + << "for another" << waitSeconds << "s"; + + // We need to indicate that we skip this file due to blacklisting + // for reporting and for making sure we don't update the blacklist + // entry yet. + // Classification is this _instruction and _status + item._instruction = CSYNC_INSTRUCTION_IGNORE; + item._status = SyncFileItem::BlacklistedError; + + auto waitSecondsStr = Utility::durationToDescriptiveString1(1000 * waitSeconds); + item._errorString = tr("%1 (skipped due to earlier error, trying again in %2)").arg(entry._errorString, waitSecondsStr); + + if (entry._errorCategory == SyncJournalErrorBlacklistRecord::InsufficientRemoteStorage) { + slotInsufficientRemoteStorage(); + } return true; } @@ -382,8 +396,10 @@ int SyncEngine::treewalkFile(TREE_WALK_FILE *file, bool remote) if (item->_instruction == CSYNC_INSTRUCTION_NONE || (item->_instruction == CSYNC_INSTRUCTION_IGNORE && instruction != CSYNC_INSTRUCTION_NONE)) { + // Take values from side (local/remote) where instruction is not _NONE item->_instruction = instruction; item->_modtime = file->modtime; + item->_size = file->size; } else { if (instruction != CSYNC_INSTRUCTION_NONE) { qCWarning(lcEngine) << "ERROR: Instruction" << item->_instruction << "vs" << instruction << "for" << fileUtf8; @@ -480,6 +496,10 @@ int SyncEngine::treewalkFile(TREE_WALK_FILE *file, bool remote) case CSYNC_STATUS_INDIVIDUAL_TOO_DEEP: item->_errorString = tr("Folder hierarchy is too deep"); break; + case CSYNC_STATUS_INDIVIDUAL_IS_CONFLICT_FILE: + item->_status = SyncFileItem::Conflict; + item->_errorString = tr("Conflict: Server version downloaded, local copy renamed and not uploaded."); + break; case CYSNC_STATUS_FILE_LOCKED_OR_OPEN: item->_errorString = QLatin1String("File locked"); // don't translate, internal use! break; @@ -519,7 +539,7 @@ int SyncEngine::treewalkFile(TREE_WALK_FILE *file, bool remote) if (file->etag && file->etag[0]) { item->_etag = file->etag; } - item->_size = file->size; + if (!item->_inode) { item->_inode = file->inode; @@ -696,11 +716,17 @@ void SyncEngine::handleSyncError(CSYNC *ctx, const char *state) } else if (CSYNC_STATUS_IS_EQUAL(err, CSYNC_STATUS_SERVICE_UNAVAILABLE) || CSYNC_STATUS_IS_EQUAL(err, CSYNC_STATUS_CONNECT_ERROR)) { emit csyncUnavailable(); } else { - emit csyncError(errStr); + csyncError(errStr); } finalize(false); } +void SyncEngine::csyncError(const QString &message) +{ + emit syncError(message, ErrorCategory::Normal); +} + + void SyncEngine::startSync() { if (_journal->exists()) { @@ -731,7 +757,7 @@ void SyncEngine::startSync() if (!QDir(_localPath).exists()) { _anotherSyncNeeded = DelayedFollowUp; // No _tr, it should only occur in non-mirall - emit csyncError("Unable to find local sync folder."); + csyncError("Unable to find local sync folder."); finalize(false); return; } @@ -744,11 +770,11 @@ void SyncEngine::startSync() << "and at least" << minFree << "are required"; if (freeBytes < minFree) { _anotherSyncNeeded = DelayedFollowUp; - emit csyncError(tr("Only %1 are available, need at least %2 to start", + csyncError(tr("Only %1 are available, need at least %2 to start", "Placeholders are postfixed with file sizes using Utility::octetsToString()") - .arg( - Utility::octetsToString(freeBytes), - Utility::octetsToString(minFree))); + .arg( + Utility::octetsToString(freeBytes), + Utility::octetsToString(minFree))); finalize(false); return; } @@ -781,7 +807,7 @@ void SyncEngine::startSync() if (fileRecordCount == -1) { qCWarning(lcEngine) << "No way to create a sync journal!"; - emit csyncError(tr("Unable to open or create the local sync database. Make sure you have write access in the sync folder.")); + csyncError(tr("Unable to open or create the local sync database. Make sure you have write access in the sync folder.")); finalize(false); return; // database creation error! @@ -800,7 +826,7 @@ void SyncEngine::startSync() qCInfo(lcEngine) << (usingSelectiveSync ? "Using Selective Sync" : "NOT Using Selective Sync"); } else { qCWarning(lcEngine) << "Could not retrieve selective sync list from DB"; - emit csyncError(tr("Unable to read the blacklist from the local database")); + csyncError(tr("Unable to read the blacklist from the local database")); finalize(false); return; } @@ -811,8 +837,12 @@ void SyncEngine::startSync() _csync_ctx->callbacks.checksum_userdata = &_checksum_hook; _stopWatch.start(); + _progressInfo->_status = ProgressInfo::Starting; + emit transmissionProgress(*_progressInfo); qCInfo(lcEngine) << "#### Discovery start ####################################################"; + _progressInfo->_status = ProgressInfo::Discovery; + emit transmissionProgress(*_progressInfo); // Usually the discovery runs in the background: We want to avoid // stealing too much time from other processes that the user might @@ -823,7 +853,7 @@ void SyncEngine::startSync() _discoveryMainThread->setParent(this); connect(this, SIGNAL(finished(bool)), _discoveryMainThread, SLOT(deleteLater())); qCInfo(lcEngine) << "Server" << account()->serverVersion() - << QString("rootEtagChangesNotOnlySubFolderEtags=%1").arg(account()->rootEtagChangesNotOnlySubFolderEtags()); + << (account()->isHttp2Supported() ? "Using HTTP/2" : ""); if (account()->rootEtagChangesNotOnlySubFolderEtags()) { connect(_discoveryMainThread, SIGNAL(etag(QString)), this, SLOT(slotRootEtagReceived(QString))); } else { @@ -837,7 +867,7 @@ void SyncEngine::startSync() if (!ok) { delete discoveryJob; qCWarning(lcEngine) << "Unable to read selective sync list, aborting."; - emit csyncError(tr("Unable to read from the sync journal.")); + csyncError(tr("Unable to read from the sync journal.")); finalize(false); return; } @@ -846,7 +876,7 @@ void SyncEngine::startSync() discoveryJob->moveToThread(&_thread); connect(discoveryJob, SIGNAL(finished(int)), this, SLOT(slotDiscoveryJobFinished(int))); connect(discoveryJob, SIGNAL(folderDiscovered(bool, QString)), - this, SIGNAL(folderDiscovered(bool, QString))); + this, SLOT(slotFolderDiscovered(bool, QString))); connect(discoveryJob, SIGNAL(newBigFolder(QString, bool)), this, SIGNAL(newBigFolder(QString, bool))); @@ -860,6 +890,12 @@ void SyncEngine::startSync() QMetaObject::invokeMethod(discoveryJob, "start", Qt::QueuedConnection); } +void SyncEngine::slotFolderDiscovered(bool /*local*/, const QString &folder) +{ + _progressInfo->_currentDiscoveredFolder = folder; + emit transmissionProgress(*_progressInfo); +} + void SyncEngine::slotRootEtagReceived(const QString &e) { if (_remoteRootEtag.isEmpty()) { @@ -871,9 +907,6 @@ void SyncEngine::slotRootEtagReceived(const QString &e) void SyncEngine::slotDiscoveryJobFinished(int discoveryResult) { - // To clean the progress info - emit folderDiscovered(false, QString()); - if (discoveryResult < 0) { handleSyncError(_csync_ctx, "csync_update"); return; @@ -883,7 +916,7 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult) // Sanity check if (!_journal->isConnected()) { qCWarning(lcEngine) << "Bailing out, DB failure"; - emit csyncError(tr("Cannot open the sync journal")); + csyncError(tr("Cannot open the sync journal")); finalize(false); return; } else { @@ -891,6 +924,10 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult) _journal->commitIfNeededAndStartNewTransaction("Post discovery"); } + _progressInfo->_currentDiscoveredFolder.clear(); + _progressInfo->_status = ProgressInfo::Reconcile; + emit transmissionProgress(*_progressInfo); + if (csync_reconcile(_csync_ctx) < 0) { handleSyncError(_csync_ctx, "csync_reconcile"); return; @@ -988,7 +1025,9 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult) // To announce the beginning of the sync emit aboutToPropagate(syncItems); + // it's important to do this before ProgressInfo::start(), to announce start of new sync + _progressInfo->_status = ProgressInfo::Propagation; emit transmissionProgress(*_progressInfo); _progressInfo->startEstimateUpdates(); @@ -1017,6 +1056,8 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult) connect(_propagator.data(), SIGNAL(finished(bool)), this, SLOT(slotFinished(bool)), Qt::QueuedConnection); connect(_propagator.data(), SIGNAL(seenLockedFile(QString)), SIGNAL(seenLockedFile(QString))); connect(_propagator.data(), SIGNAL(touchedFile(QString)), SLOT(slotAddTouchedFile(QString))); + connect(_propagator.data(), SIGNAL(insufficientLocalStorage()), SLOT(slotInsufficientLocalStorage())); + connect(_propagator.data(), SIGNAL(insufficientRemoteStorage()), SLOT(slotInsufficientRemoteStorage())); // apply the network limits to the propagator setNetworkLimits(_uploadLimit, _downloadLimit); @@ -1073,7 +1114,7 @@ void SyncEngine::slotItemCompleted(const SyncFileItemPtr &item) _progressInfo->setProgressComplete(*item); if (item->_status == SyncFileItem::FatalError) { - emit csyncError(item->_errorString); + csyncError(item->_errorString); } emit transmissionProgress(*_progressInfo); @@ -1101,6 +1142,7 @@ void SyncEngine::slotFinished(bool success) // files needed propagation, but clear the lastCompletedItem // so we don't count this twice (like Recent Files) _progressInfo->_lastCompletedItem = SyncFileItem(); + _progressInfo->_status = ProgressInfo::Done; emit transmissionProgress(*_progressInfo); finalize(success); @@ -1127,6 +1169,7 @@ void SyncEngine::finalize(bool success) _seenFiles.clear(); _temporarilyUnavailablePaths.clear(); _renamedFolders.clear(); + _uniqueErrors.clear(); _clearTouchedFilesTimer.start(); } @@ -1470,12 +1513,27 @@ void SyncEngine::restoreOldFiles(SyncFileItemVector &syncItems) void SyncEngine::slotAddTouchedFile(const QString &fn) { + QElapsedTimer now; + now.start(); QString file = QDir::cleanPath(fn); - QElapsedTimer timer; - timer.start(); + // Iterate from the oldest and remove anything older than 15 seconds. + while (true) { + auto first = _touchedFiles.begin(); + if (first == _touchedFiles.end()) + break; + // Compare to our new QElapsedTimer instead of using elapsed(). + // This avoids querying the current time from the OS for every loop. + if (now.msecsSinceReference() - first.key().msecsSinceReference() <= s_touchedFilesMaxAgeMs) { + // We found the first path younger than 15 second, keep the rest. + break; + } - _touchedFiles.insert(file, timer); + _touchedFiles.erase(first); + } + + // This should be the largest QElapsedTimer yet, use constEnd() as hint. + _touchedFiles.insert(_touchedFiles.constEnd(), now, file); } void SyncEngine::slotClearTouchedFiles() @@ -1483,13 +1541,15 @@ void SyncEngine::slotClearTouchedFiles() _touchedFiles.clear(); } -qint64 SyncEngine::timeSinceFileTouched(const QString &fn) const +bool SyncEngine::wasFileTouched(const QString &fn) const { - if (!_touchedFiles.contains(fn)) { - return -1; + // Start from the end (most recent) and look for our path. Check the time just in case. + auto begin = _touchedFiles.constBegin(); + for (auto it = _touchedFiles.constEnd(); it != begin; --it) { + if ((it-1).value() == fn) + return (it-1).key().elapsed() <= s_touchedFilesMaxAgeMs; } - - return _touchedFiles[fn].elapsed(); + return false; } AccountPtr SyncEngine::account() const @@ -1515,4 +1575,31 @@ void SyncEngine::abort() } } +void SyncEngine::slotSummaryError(const QString &message) +{ + if (_uniqueErrors.contains(message)) + return; + + _uniqueErrors.insert(message); + emit syncError(message, ErrorCategory::Normal); +} + +void SyncEngine::slotInsufficientLocalStorage() +{ + slotSummaryError( + tr("Disk space is low: Downloads that would reduce free space " + "below %1 were skipped.") + .arg(Utility::octetsToString(freeSpaceLimit()))); +} + +void SyncEngine::slotInsufficientRemoteStorage() +{ + auto msg = tr("There is insufficient space available on the server for some uploads."); + if (_uniqueErrors.contains(msg)) + return; + + _uniqueErrors.insert(msg); + emit syncError(msg, ErrorCategory::InsufficientRemoteStorage); +} + } // namespace OCC diff --git a/src/libsync/syncengine.h b/src/libsync/syncengine.h index 8652ef2cb0..a275618a43 100644 --- a/src/libsync/syncengine.h +++ b/src/libsync/syncengine.h @@ -87,11 +87,7 @@ public: /* Returns whether another sync is needed to complete the sync */ AnotherSyncNeeded isAnotherSyncNeeded() { return _anotherSyncNeeded; } - /** Get the ms since a file was touched, or -1 if it wasn't. - * - * Thread-safe. - */ - qint64 timeSinceFileTouched(const QString &fn) const; + bool wasFileTouched(const QString &fn) const; AccountPtr account() const; SyncJournalDb *journal() const { return _journal; } @@ -104,12 +100,10 @@ public: static qint64 minimumFileAgeForUpload; // in ms signals: - void csyncError(const QString &); void csyncUnavailable(); // During update, before reconcile void rootEtag(QString); - void folderDiscovered(bool local, const QString &folderUrl); // before actual syncing (after update+reconcile) for each item void syncItemDiscovered(const SyncFileItem &); @@ -121,6 +115,9 @@ signals: void transmissionProgress(const ProgressInfo &progress); + /// We've produced a new sync error of a type. + void syncError(const QString &message, ErrorCategory category); + void finished(bool success); void started(); @@ -147,6 +144,7 @@ signals: void seenLockedFile(const QString &fileName); private slots: + void slotFolderDiscovered(bool local, const QString &folder); void slotRootEtagReceived(const QString &); void slotItemCompleted(const SyncFileItemPtr &item); void slotFinished(bool success); @@ -160,8 +158,15 @@ private slots: /** Wipes the _touchedFiles hash */ void slotClearTouchedFiles(); + /** Emit a summary error, unless it was seen before */ + void slotSummaryError(const QString &message); + + void slotInsufficientLocalStorage(); + void slotInsufficientRemoteStorage(); + private: void handleSyncError(CSYNC *ctx, const char *state); + void csyncError(const QString &message); QString journalDbFilePath() const; @@ -263,10 +268,13 @@ private: AnotherSyncNeeded _anotherSyncNeeded; /** Stores the time since a job touched a file. */ - QHash _touchedFiles; + QMultiMap _touchedFiles; /** For clearing the _touchedFiles variable after sync finished */ QTimer _clearTouchedFilesTimer; + + /** List of unique errors that occurred in a sync run. */ + QSet _uniqueErrors; }; } diff --git a/src/libsync/syncfileitem.h b/src/libsync/syncfileitem.h index 3eefac5cee..baa8a75102 100644 --- a/src/libsync/syncfileitem.h +++ b/src/libsync/syncfileitem.h @@ -53,7 +53,7 @@ public: SoftLink = CSYNC_FTW_TYPE_SLINK }; - enum Status { + enum Status { // stored in 4 bits NoStatus, FatalError, ///< Error that causes the sync to stop @@ -61,9 +61,25 @@ public: SoftError, ///< More like an information Success, ///< The file was properly synced - Conflict, ///< The file was properly synced, but a conflict was created + + /** Marks a conflict, old or new. + * + * With instruction:IGNORE: detected an old unresolved old conflict + * With instruction:CONFLICT: a new conflict this sync run + */ + Conflict, + FileIgnored, ///< The file is in the ignored list (or blacklisted with no retries left) - Restoration ///< The file was restored because what should have been done was not allowed + Restoration, ///< The file was restored because what should have been done was not allowed + + /** For files whose errors were blacklisted. + * + * If _instruction == IGNORE, the file wasn't even reattempted. + * + * These errors should usually be shown as NormalErrors, but not be as loud + * as them. + */ + BlacklistedError }; SyncFileItem() diff --git a/src/libsync/syncfilestatus.cpp b/src/libsync/syncfilestatus.cpp index 12a9977860..8d1a5933fe 100644 --- a/src/libsync/syncfilestatus.cpp +++ b/src/libsync/syncfilestatus.cpp @@ -17,13 +17,13 @@ namespace OCC { SyncFileStatus::SyncFileStatus() : _tag(StatusNone) - , _sharedWithMe(false) + , _shared(false) { } SyncFileStatus::SyncFileStatus(SyncFileStatusTag tag) : _tag(tag) - , _sharedWithMe(false) + , _shared(false) { } @@ -37,14 +37,14 @@ SyncFileStatus::SyncFileStatusTag SyncFileStatus::tag() const return _tag; } -void SyncFileStatus::setSharedWithMe(bool isShared) +void SyncFileStatus::setShared(bool isShared) { - _sharedWithMe = isShared; + _shared = isShared; } -bool SyncFileStatus::sharedWithMe() const +bool SyncFileStatus::shared() const { - return _sharedWithMe; + return _shared; } QString SyncFileStatus::toSocketAPIString() const @@ -71,7 +71,7 @@ QString SyncFileStatus::toSocketAPIString() const statusString = QLatin1String("ERROR"); break; } - if (canBeShared && _sharedWithMe) { + if (canBeShared && _shared) { statusString += QLatin1String("+SWM"); } diff --git a/src/libsync/syncfilestatus.h b/src/libsync/syncfilestatus.h index 27006a5722..e4743f7a3f 100644 --- a/src/libsync/syncfilestatus.h +++ b/src/libsync/syncfilestatus.h @@ -44,19 +44,19 @@ public: void set(SyncFileStatusTag tag); SyncFileStatusTag tag() const; - void setSharedWithMe(bool isShared); - bool sharedWithMe() const; + void setShared(bool isShared); + bool shared() const; QString toSocketAPIString() const; private: SyncFileStatusTag _tag; - bool _sharedWithMe; + bool _shared; }; inline bool operator==(const SyncFileStatus &a, const SyncFileStatus &b) { - return a.tag() == b.tag() && a.sharedWithMe() == b.sharedWithMe(); + return a.tag() == b.tag() && a.shared() == b.shared(); } inline bool operator!=(const SyncFileStatus &a, const SyncFileStatus &b) diff --git a/src/libsync/syncfilestatustracker.cpp b/src/libsync/syncfilestatustracker.cpp index db1f778c20..9d4fa6e37d 100644 --- a/src/libsync/syncfilestatustracker.cpp +++ b/src/libsync/syncfilestatustracker.cpp @@ -65,6 +65,7 @@ static inline bool showErrorInSocketApi(const SyncFileItem &item) return item._instruction == CSYNC_INSTRUCTION_ERROR || status == SyncFileItem::NormalError || status == SyncFileItem::FatalError + || status == SyncFileItem::BlacklistedError || item._hasBlacklistEntry; } @@ -285,7 +286,7 @@ SyncFileStatus SyncFileStatusTracker::resolveSyncAndErrorStatus(const QString &r ASSERT(sharedFlag != UnknownShared, "The shared status needs to have been fetched from a SyncFileItem or the DB at this point."); if (sharedFlag == Shared) - status.setSharedWithMe(true); + status.setShared(true); return status; } diff --git a/src/libsync/syncjournaldb.cpp b/src/libsync/syncjournaldb.cpp index b6bcff4d0d..0eb34e6097 100644 --- a/src/libsync/syncjournaldb.cpp +++ b/src/libsync/syncjournaldb.cpp @@ -564,7 +564,7 @@ bool SyncJournalDb::checkConnect() return sqlFail("prepare _deleteFileRecordRecursively", *_deleteFileRecordRecursively); } - QString sql("SELECT lastTryEtag, lastTryModtime, retrycount, errorstring, lastTryTime, ignoreDuration, renameTarget " + QString sql("SELECT lastTryEtag, lastTryModtime, retrycount, errorstring, lastTryTime, ignoreDuration, renameTarget, errorCategory " "FROM blacklist WHERE path=?1"); if (Utility::fsCasePreserving()) { // if the file system is case preserving we have to check the blacklist @@ -578,8 +578,8 @@ bool SyncJournalDb::checkConnect() _setErrorBlacklistQuery.reset(new SqlQuery(_db)); if (_setErrorBlacklistQuery->prepare("INSERT OR REPLACE INTO blacklist " - "(path, lastTryEtag, lastTryModtime, retrycount, errorstring, lastTryTime, ignoreDuration, renameTarget) " - "VALUES ( ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)")) { + "(path, lastTryEtag, lastTryModtime, retrycount, errorstring, lastTryTime, ignoreDuration, renameTarget, errorCategory) " + "VALUES ( ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)")) { return sqlFail("prepare _setErrorBlacklistQuery", *_setErrorBlacklistQuery); } @@ -800,7 +800,17 @@ bool SyncJournalDb::updateErrorBlacklistTableStructure() sqlFail("updateBlacklistTableStructure: Add renameTarget", query); re = false; } - commitInternal("update database structure: add lastTryTime, ignoreDuration cols"); + commitInternal("update database structure: add renameTarget col"); + } + + if (columns.indexOf(QLatin1String("errorCategory")) == -1) { + SqlQuery query(_db); + query.prepare("ALTER TABLE blacklist ADD COLUMN errorCategory INTEGER(8);"); + if (!query.exec()) { + sqlFail("updateBlacklistTableStructure: Add errorCategory", query); + re = false; + } + commitInternal("update database structure: add errorCategory col"); } SqlQuery query(_db); @@ -1410,6 +1420,8 @@ SyncJournalErrorBlacklistRecord SyncJournalDb::errorBlacklistEntry(const QString entry._lastTryTime = _getErrorBlacklistQuery->int64Value(4); entry._ignoreDuration = _getErrorBlacklistQuery->int64Value(5); entry._renameTarget = _getErrorBlacklistQuery->stringValue(6); + entry._errorCategory = static_cast( + _getErrorBlacklistQuery->intValue(7)); entry._file = file; } _getErrorBlacklistQuery->reset_and_clear_bindings(); @@ -1501,13 +1513,28 @@ void SyncJournalDb::wipeErrorBlacklistEntry(const QString &file) } } -void SyncJournalDb::updateErrorBlacklistEntry(const SyncJournalErrorBlacklistRecord &item) +void SyncJournalDb::wipeErrorBlacklistCategory(SyncJournalErrorBlacklistRecord::Category category) +{ + QMutexLocker locker(&_mutex); + if (checkConnect()) { + SqlQuery query(_db); + + query.prepare("DELETE FROM blacklist WHERE errorCategory=?1"); + query.bindValue(1, category); + if (!query.exec()) { + sqlFail("Deletion of blacklist category failed.", query); + } + } +} + +void SyncJournalDb::setErrorBlacklistEntry(const SyncJournalErrorBlacklistRecord &item) { QMutexLocker locker(&_mutex); qCInfo(lcDb) << "Setting blacklist entry for " << item._file << item._retryCount << item._errorString << item._lastTryTime << item._ignoreDuration - << item._lastTryModtime << item._lastTryEtag << item._renameTarget; + << item._lastTryModtime << item._lastTryEtag << item._renameTarget + << item._errorCategory; if (!checkConnect()) { return; @@ -1521,6 +1548,7 @@ void SyncJournalDb::updateErrorBlacklistEntry(const SyncJournalErrorBlacklistRec _setErrorBlacklistQuery->bindValue(6, QString::number(item._lastTryTime)); _setErrorBlacklistQuery->bindValue(7, QString::number(item._ignoreDuration)); _setErrorBlacklistQuery->bindValue(8, item._renameTarget); + _setErrorBlacklistQuery->bindValue(9, item._errorCategory); _setErrorBlacklistQuery->exec(); _setErrorBlacklistQuery->reset_and_clear_bindings(); } diff --git a/src/libsync/syncjournaldb.h b/src/libsync/syncjournaldb.h index 3f6c89e435..62295f62e1 100644 --- a/src/libsync/syncjournaldb.h +++ b/src/libsync/syncjournaldb.h @@ -22,10 +22,10 @@ #include "utility.h" #include "ownsql.h" +#include "syncjournalfilerecord.h" namespace OCC { class SyncJournalFileRecord; -class SyncJournalErrorBlacklistRecord; /** * @brief Class that handles the sync database @@ -71,8 +71,9 @@ public: static qint64 getPHash(const QString &); - void updateErrorBlacklistEntry(const SyncJournalErrorBlacklistRecord &item); + void setErrorBlacklistEntry(const SyncJournalErrorBlacklistRecord &item); void wipeErrorBlacklistEntry(const QString &file); + void wipeErrorBlacklistCategory(SyncJournalErrorBlacklistRecord::Category category); int wipeErrorBlacklist(); int errorBlackListEntryCount(); diff --git a/src/libsync/syncjournalfilerecord.cpp b/src/libsync/syncjournalfilerecord.cpp index b2c65931d4..024c327460 100644 --- a/src/libsync/syncjournalfilerecord.cpp +++ b/src/libsync/syncjournalfilerecord.cpp @@ -109,6 +109,31 @@ SyncFileItem SyncJournalFileRecord::toSyncFileItem() return item; } +QByteArray SyncJournalFileRecord::numericFileId() const +{ + // Use the id up until the first non-numeric character + for (int i = 0; i < _fileId.size(); ++i) { + if (_fileId[i] < '0' || _fileId[i] > '9') { + return _fileId.left(i); + } + } + return _fileId; +} + +SyncJournalErrorBlacklistRecord SyncJournalErrorBlacklistRecord::fromSyncFileItem( + const SyncFileItem &item) +{ + SyncJournalErrorBlacklistRecord record; + record._file = item._file; + record._errorString = item._errorString; + record._lastTryModtime = item._modtime; + record._lastTryEtag = item._etag; + record._lastTryTime = Utility::qDateTimeToTime_t(QDateTime::currentDateTime()); + record._renameTarget = item._renameTarget; + record._retryCount = 1; + return record; +} + bool SyncJournalErrorBlacklistRecord::isValid() const { return !_file.isEmpty() diff --git a/src/libsync/syncjournalfilerecord.h b/src/libsync/syncjournalfilerecord.h index c7579683b9..6ef0ba169e 100644 --- a/src/libsync/syncjournalfilerecord.h +++ b/src/libsync/syncjournalfilerecord.h @@ -48,6 +48,14 @@ public: return !_path.isEmpty(); } + /** Returns the numeric part of the full id in _fileId. + * + * On the server this is sometimes known as the internal file id. + * + * It is used in the construction of private links. + */ + QByteArray numericFileId() const; + QString _path; quint64 _inode; QDateTime _modtime; @@ -67,19 +75,32 @@ operator==(const SyncJournalFileRecord &lhs, class SyncJournalErrorBlacklistRecord { public: + enum Category { + /// Normal errors have no special behavior + Normal = 0, + /// These get a special summary message + InsufficientRemoteStorage + }; + SyncJournalErrorBlacklistRecord() : _retryCount(0) + , _errorCategory(Category::Normal) , _lastTryModtime(0) , _lastTryTime(0) , _ignoreDuration(0) { } + /// Create a record based on an item. + static SyncJournalErrorBlacklistRecord fromSyncFileItem(const SyncFileItem &item); + /// The number of times the operation was unsuccessful so far. int _retryCount; /// The last error string. QString _errorString; + /// The error category. Sometimes used for special actions. + Category _errorCategory; time_t _lastTryModtime; QByteArray _lastTryEtag; diff --git a/src/libsync/syncresult.cpp b/src/libsync/syncresult.cpp index de5c81c54b..88e78320b0 100644 --- a/src/libsync/syncresult.cpp +++ b/src/libsync/syncresult.cpp @@ -25,7 +25,8 @@ SyncResult::SyncResult() , _numRemovedItems(0) , _numUpdatedItems(0) , _numRenamedItems(0) - , _numConflictItems(0) + , _numNewConflictItems(0) + , _numOldConflictItems(0) , _numErrorItems(0) { @@ -147,9 +148,13 @@ void SyncResult::processCompletedItem(const SyncFileItemPtr &item) _firstItemError = item; } } else if (item->_status == SyncFileItem::Conflict) { - _numConflictItems++; - if (!_firstConflictItem) { - _firstConflictItem = item; + if (item->_instruction == CSYNC_INSTRUCTION_CONFLICT) { + _numNewConflictItems++; + if (!_firstNewConflictItem) { + _firstNewConflictItem = item; + } + } else { + _numOldConflictItems++; } } else { if (!item->hasErrorStatus() && item->_status != SyncFileItem::FileIgnored && item->_direction == SyncFileItem::Down) { diff --git a/src/libsync/syncresult.h b/src/libsync/syncresult.h index 7c5e501aa6..ab53c8dba0 100644 --- a/src/libsync/syncresult.h +++ b/src/libsync/syncresult.h @@ -66,14 +66,15 @@ public: int numRemovedItems() const { return _numRemovedItems; } int numUpdatedItems() const { return _numUpdatedItems; } int numRenamedItems() const { return _numRenamedItems; } - int numConflictItems() const { return _numConflictItems; } + int numNewConflictItems() const { return _numNewConflictItems; } + int numOldConflictItems() const { return _numOldConflictItems; } int numErrorItems() const { return _numErrorItems; } const SyncFileItemPtr &firstItemNew() const { return _firstItemNew; } const SyncFileItemPtr &firstItemDeleted() const { return _firstItemDeleted; } const SyncFileItemPtr &firstItemUpdated() const { return _firstItemUpdated; } const SyncFileItemPtr &firstItemRenamed() const { return _firstItemRenamed; } - const SyncFileItemPtr &firstConflictItem() const { return _firstConflictItem; } + const SyncFileItemPtr &firstNewConflictItem() const { return _firstNewConflictItem; } const SyncFileItemPtr &firstItemError() const { return _firstItemError; } void processCompletedItem(const SyncFileItemPtr &item); @@ -95,14 +96,15 @@ private: int _numRemovedItems; int _numUpdatedItems; int _numRenamedItems; - int _numConflictItems; + int _numNewConflictItems; + int _numOldConflictItems; int _numErrorItems; SyncFileItemPtr _firstItemNew; SyncFileItemPtr _firstItemDeleted; SyncFileItemPtr _firstItemUpdated; SyncFileItemPtr _firstItemRenamed; - SyncFileItemPtr _firstConflictItem; + SyncFileItemPtr _firstNewConflictItem; SyncFileItemPtr _firstItemError; }; } diff --git a/src/libsync/utility_win.cpp b/src/libsync/utility_win.cpp index f46cde4d54..3762808b7b 100644 --- a/src/libsync/utility_win.cpp +++ b/src/libsync/utility_win.cpp @@ -28,8 +28,20 @@ namespace OCC { static void setupFavLink_private(const QString &folder) { - // Windows Explorer: Place under "Favorites" (Links) + // First create a Desktop.ini so that the folder and favorite link show our application's icon. + QFile desktopIni(folder + QLatin1String("/Desktop.ini")); + if (desktopIni.exists()) { + qCWarning(lcUtility) << desktopIni.fileName() << "already exists, not overwriting it to set the folder icon."; + } else { + qCInfo(lcUtility) << "Creating" << desktopIni.fileName() << "to set a folder icon in Explorer."; + desktopIni.open(QFile::WriteOnly); + desktopIni.write("[.ShellClassInfo]\r\nIconResource="); + desktopIni.write(QDir::toNativeSeparators(qApp->applicationFilePath()).toUtf8()); + desktopIni.write(",0\r\n"); + desktopIni.close(); + } + // Windows Explorer: Place under "Favorites" (Links) QString linkName; QDir folderDir(QDir::fromNativeSeparators(folder)); @@ -41,7 +53,7 @@ static void setupFavLink_private(const QString &folder) linkName = QDir(links).filePath(folderDir.dirName() + QLatin1String(".lnk")); CoTaskMemFree(path); } - qCDebug(lcUtility) << " creating link from " << linkName << " to " << folder; + qCInfo(lcUtility) << "Creating favorite link from" << folder << "to" << linkName; if (!QFile::link(folder, linkName)) qCWarning(lcUtility) << "linking" << folder << "to" << linkName << "failed!"; } diff --git a/sync-exclude.lst b/sync-exclude.lst index 9d58aa9fbd..bdf6c34415 100644 --- a/sync-exclude.lst +++ b/sync-exclude.lst @@ -8,7 +8,6 @@ ].ds_store ._* ]Thumbs.db -desktop.ini System Volume Information .*.sw? diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ac0f1dca1e..860f140e9b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -55,6 +55,7 @@ list(APPEND FolderMan_SRC ../src/gui/socketapi.cpp ) list(APPEND FolderMan_SRC ../src/gui/accountstate.cpp ) list(APPEND FolderMan_SRC ../src/gui/syncrunfilelog.cpp ) list(APPEND FolderMan_SRC ../src/gui/lockwatcher.cpp ) +list(APPEND FolderMan_SRC ../src/gui/guiutility.cpp ) list(APPEND FolderMan_SRC ${FolderWatcher_SRC}) list(APPEND FolderMan_SRC stub.cpp ) owncloud_add_test(FolderMan "${FolderMan_SRC}") diff --git a/test/owncloud_add_test.cmake b/test/owncloud_add_test.cmake index bf15ab890b..cefe264eac 100644 --- a/test/owncloud_add_test.cmake +++ b/test/owncloud_add_test.cmake @@ -22,7 +22,7 @@ macro(owncloud_add_test test_class additional_cpp) ) add_definitions(-DOWNCLOUD_TEST) - add_definitions(-DOWNCLOUD_BIN_PATH=${CMAKE_BINARY_DIR}/bin) + add_definitions(-DOWNCLOUD_BIN_PATH="${CMAKE_BINARY_DIR}/bin") add_test(NAME ${OWNCLOUD_TEST_CLASS}Test COMMAND ${OWNCLOUD_TEST_CLASS}Test) endmacro() @@ -51,5 +51,5 @@ macro(owncloud_add_benchmark test_class additional_cpp) ) add_definitions(-DOWNCLOUD_TEST) - add_definitions(-DOWNCLOUD_BIN_PATH=${CMAKE_BINARY_DIR}/bin) + add_definitions(-DOWNCLOUD_BIN_PATH="${CMAKE_BINARY_DIR}/bin") endmacro() diff --git a/test/syncenginetestutils.h b/test/syncenginetestutils.h index 997e5054e5..062b62cea7 100644 --- a/test/syncenginetestutils.h +++ b/test/syncenginetestutils.h @@ -289,6 +289,7 @@ public: QString etag = generateEtag(); QByteArray fileId = generateFileId(); QByteArray checksums; + QByteArray extraDavProperties; qint64 size = 0; char contentChar = 'W'; @@ -360,6 +361,7 @@ public: 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); + buffer.write(fileInfo.extraDavProperties); xml.writeEndElement(); // prop xml.writeTextElement(davUri, QStringLiteral("status"), "HTTP/1.1 200 OK"); xml.writeEndElement(); // propstat @@ -778,7 +780,7 @@ public: FakeCredentials(QNetworkAccessManager *qnam) : _qnam{qnam} { } virtual QString authType() const { return "test"; } virtual QString user() const { return "admin"; } - virtual QNetworkAccessManager* getQNAM() const { return _qnam; } + virtual QNetworkAccessManager *createQNAM() const { return _qnam; } virtual bool ready() const { return true; } virtual void fetchFromKeychain() { } virtual void askFromUser() { } @@ -826,7 +828,7 @@ public: OCC::SyncEngine &syncEngine() const { return *_syncEngine; } FileModifier &localModifier() { return _localModifier; } - FileModifier &remoteModifier() { return _fakeQnam->currentRemoteState(); } + FileInfo &remoteModifier() { return _fakeQnam->currentRemoteState(); } FileInfo currentLocalState() { QDir rootDir{_tempDir.path()}; FileInfo rootTemplate; diff --git a/test/testsyncengine.cpp b/test/testsyncengine.cpp index 1f48931da1..cfac432f96 100644 --- a/test/testsyncengine.cpp +++ b/test/testsyncengine.cpp @@ -385,6 +385,86 @@ private slots: QVERIFY(fakeFolder.syncOnce()); QCOMPARE(nGET, 1); } + + /** + * Checks whether SyncFileItems have the expected properties before start + * of propagation. + */ + void testSyncFileItemProperties() + { + auto initialMtime = QDateTime::currentDateTime().addDays(-7); + auto changedMtime = QDateTime::currentDateTime().addDays(-4); + auto changedMtime2 = QDateTime::currentDateTime().addDays(-3); + + // Base mtime with no ms content (filesystem is seconds only) + initialMtime.setMSecsSinceEpoch(initialMtime.toMSecsSinceEpoch() / 1000 * 1000); + changedMtime.setMSecsSinceEpoch(changedMtime.toMSecsSinceEpoch() / 1000 * 1000); + changedMtime2.setMSecsSinceEpoch(changedMtime2.toMSecsSinceEpoch() / 1000 * 1000); + + // Ensure the initial mtimes are as expected + auto initialFileInfo = FileInfo::A12_B12_C12_S12(); + initialFileInfo.setModTime("A/a1", initialMtime); + initialFileInfo.setModTime("B/b1", initialMtime); + initialFileInfo.setModTime("C/c1", initialMtime); + + FakeFolder fakeFolder{ initialFileInfo }; + + + // upload a + fakeFolder.localModifier().appendByte("A/a1"); + fakeFolder.localModifier().setModTime("A/a1", changedMtime); + // download b + fakeFolder.remoteModifier().appendByte("B/b1"); + fakeFolder.remoteModifier().setModTime("B/b1", changedMtime); + // conflict c + fakeFolder.localModifier().appendByte("C/c1"); + fakeFolder.localModifier().appendByte("C/c1"); + fakeFolder.localModifier().setModTime("C/c1", changedMtime); + fakeFolder.remoteModifier().appendByte("C/c1"); + fakeFolder.remoteModifier().setModTime("C/c1", changedMtime2); + + connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToPropagate, [&](SyncFileItemVector &items) { + SyncFileItemPtr a1, b1, c1; + for (auto &item : items) { + if (item->_file == "A/a1") + a1 = item; + if (item->_file == "B/b1") + b1 = item; + if (item->_file == "C/c1") + c1 = item; + } + + // a1: should have local size and modtime + QVERIFY(a1); + QCOMPARE(a1->_instruction, CSYNC_INSTRUCTION_SYNC); + QCOMPARE(a1->_direction, SyncFileItem::Up); + QCOMPARE(a1->_size, quint64(5)); + + QCOMPARE(Utility::qDateTimeFromTime_t(a1->_modtime), changedMtime); + QCOMPARE(a1->log._other_size, quint64(4)); + QCOMPARE(Utility::qDateTimeFromTime_t(a1->log._other_modtime), initialMtime); + + // b2: should have remote size and modtime + QVERIFY(b1); + QCOMPARE(b1->_instruction, CSYNC_INSTRUCTION_SYNC); + QCOMPARE(b1->_direction, SyncFileItem::Down); + QCOMPARE(b1->_size, quint64(17)); + QCOMPARE(Utility::qDateTimeFromTime_t(b1->_modtime), changedMtime); + QCOMPARE(b1->log._other_size, quint64(16)); + QCOMPARE(Utility::qDateTimeFromTime_t(b1->log._other_modtime), initialMtime); + + // c1: conflicts are downloads, so remote size and modtime + QVERIFY(c1); + QCOMPARE(c1->_instruction, CSYNC_INSTRUCTION_CONFLICT); + QCOMPARE(c1->_direction, SyncFileItem::None); + QCOMPARE(c1->_size, quint64(25)); + QCOMPARE(Utility::qDateTimeFromTime_t(c1->_modtime), changedMtime2); + QCOMPARE(c1->log._other_size, quint64(26)); + QCOMPARE(Utility::qDateTimeFromTime_t(c1->log._other_modtime), changedMtime); + }); + + QVERIFY(fakeFolder.syncOnce()); + } }; QTEST_GUILESS_MAIN(TestSyncEngine) diff --git a/test/testsyncfilestatustracker.cpp b/test/testsyncfilestatustracker.cpp index bcb6ae2058..741ae1dc6d 100644 --- a/test/testsyncfilestatustracker.cpp +++ b/test/testsyncfilestatustracker.cpp @@ -411,11 +411,14 @@ private slots: void sharedStatus() { SyncFileStatus sharedUpToDateStatus(SyncFileStatus::StatusUpToDate); - sharedUpToDateStatus.setSharedWithMe(true); + sharedUpToDateStatus.setShared(true); FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; fakeFolder.remoteModifier().insert("S/s0"); fakeFolder.remoteModifier().appendByte("S/s1"); + fakeFolder.remoteModifier().insert("B/b3"); + fakeFolder.remoteModifier().find("B/b3")->extraDavProperties = "0"; + StatusPushSpy statusSpy(fakeFolder.syncEngine()); fakeFolder.scheduleSync(); @@ -435,6 +438,8 @@ private slots: QEXPECT_FAIL("", "We currently only know if a new file is shared on the second sync, after a PROPFIND.", Continue); QCOMPARE(statusSpy.statusOf("S/s0"), sharedUpToDateStatus); QCOMPARE(statusSpy.statusOf("S/s1"), sharedUpToDateStatus); + QCOMPARE(statusSpy.statusOf("B/b1").shared(), false); + QCOMPARE(statusSpy.statusOf("B/b3"), sharedUpToDateStatus); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); } diff --git a/test/testsyncjournaldb.cpp b/test/testsyncjournaldb.cpp index ec673077dc..881bb865d2 100644 --- a/test/testsyncjournaldb.cpp +++ b/test/testsyncjournaldb.cpp @@ -17,10 +17,13 @@ class TestSyncJournalDB : public QObject { Q_OBJECT + QTemporaryDir _tempDir; + public: TestSyncJournalDB() - : _db("/tmp/csync-test.db") + : _db((_tempDir.path() + "/sync.db")) { + QVERIFY(_tempDir.isValid()); } QDateTime dropMsecs(QDateTime time) diff --git a/test/testutility.cpp b/test/testutility.cpp index 5a656ac846..5093ce76de 100644 --- a/test/testutility.cpp +++ b/test/testutility.cpp @@ -11,10 +11,6 @@ #include "utility.h" -#define STR_(X) #X -#define STR(X) STR_(X) -#define BIN_PATH STR(OWNCLOUD_BIN_PATH) - using namespace OCC::Utility; class TestUtility : public QObject @@ -118,7 +114,7 @@ private slots: } // pass the binary name owncloud to the next call. This brakes branding, // but branding is not supposed to work with this. - QString ver = versionOfInstalledBinary(BIN_PATH+QLatin1String("/owncloud")); + QString ver = versionOfInstalledBinary(OWNCLOUD_BIN_PATH+QLatin1String("/owncloud")); qDebug() << "Version of installed ownCloud Binary: " << ver; QVERIFY( !ver.isEmpty()); diff --git a/theme/colored/owncloud-icon-1024.png b/theme/colored/owncloud-icon-1024.png new file mode 100644 index 0000000000..e32847c1c9 Binary files /dev/null and b/theme/colored/owncloud-icon-1024.png differ diff --git a/theme/colored/owncloud-icon-16.png b/theme/colored/owncloud-icon-16.png new file mode 100644 index 0000000000..5c01c4fe9e Binary files /dev/null and b/theme/colored/owncloud-icon-16.png differ diff --git a/theme/colored/owncloud-sidebar-16.png b/theme/colored/owncloud-sidebar-16.png new file mode 100644 index 0000000000..5c01c4fe9e Binary files /dev/null and b/theme/colored/owncloud-sidebar-16.png differ diff --git a/theme/colored/owncloud-sidebar-18.png b/theme/colored/owncloud-sidebar-18.png new file mode 100644 index 0000000000..bff6175741 Binary files /dev/null and b/theme/colored/owncloud-sidebar-18.png differ diff --git a/theme/colored/owncloud-sidebar-32.png b/theme/colored/owncloud-sidebar-32.png new file mode 100644 index 0000000000..721c04e485 Binary files /dev/null and b/theme/colored/owncloud-sidebar-32.png differ diff --git a/theme/colored/owncloud-sidebar-36.png b/theme/colored/owncloud-sidebar-36.png new file mode 100644 index 0000000000..27e415d315 Binary files /dev/null and b/theme/colored/owncloud-sidebar-36.png differ diff --git a/theme/colored/owncloud-sidebar-64.png b/theme/colored/owncloud-sidebar-64.png new file mode 100644 index 0000000000..5c6f17f0cd Binary files /dev/null and b/theme/colored/owncloud-sidebar-64.png differ diff --git a/translations/client_ca.ts b/translations/client_ca.ts index dce4d38608..d64d4eaf0b 100644 --- a/translations/client_ca.ts +++ b/translations/client_ca.ts @@ -2991,23 +2991,23 @@ No és aconsellada usar-la. Error en llegir la carpeta. - + File/Folder is ignored because it's hidden. El fitxer/carpeta s'ha ignorat perquè és ocult. - + Only %1 are available, need at least %2 to start Placeholders are postfixed with file sizes using Utility::octetsToString() - + Not allowed because you don't have permission to add parent folder - + Not allowed because you don't have permission to add files in that folder @@ -3067,124 +3067,124 @@ No és aconsellada usar-la. L'element no s'ha sincronitzat degut a errors previs: %1 - + Symbolic links are not supported in syncing. La sincronització d'enllaços simbòlics no està implementada. - + File is listed on the ignore list. El fitxer està a la llista d'ignorats. - + File names ending with a period are not supported on this file system. - + File names containing the character '%1' are not supported on this file system. - + The file name is a reserved name on this file system. - + Filename contains trailing spaces. - + Filename is too long. El nom de fitxer és massa llarg. - + Stat failed. - + Filename encoding is not valid La codificació del nom de fitxer no és vàlida - + Invalid characters, please rename "%1" Caràcters no vàlids. Reanomeneu "%1" - + Unable to initialize a sync journal. No es pot inicialitzar un periòdic de sincronització - + Unable to read the blacklist from the local database - + Unable to read from the sync journal. - + Cannot open the sync journal No es pot obrir el diari de sincronització - + File name contains at least one invalid character El nom del fitxer conté al menys un caràcter invàlid - - + + Ignored because of the "choose what to sync" blacklist S'ignora degut al filtre a «Trieu què sincronitzar» - + Not allowed because you don't have permission to add subfolders to that folder - + Not allowed to upload this file because it is read-only on the server, restoring No es permet pujar aquest fitxer perquè només és de lectura en el servidor, es restaura - - + + Not allowed to remove, restoring No es permet l'eliminació, es restaura - + Local files and share folder removed. Fitxers locals i carpeta compartida esborrats. - + Move not allowed, item restored No es permet moure'l, l'element es restaura - + Move not allowed because %1 is read-only No es permet moure perquè %1 només és de lectura - + the destination el destí - + the source l'origen @@ -3453,8 +3453,8 @@ No és aconsellada usar-la. OCC::ownCloudTheme - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> - <p>Versió %2. Per més informació visiteu <a href="%3">https://%4</a></p><p>Per errors coneguts i ajuda, visiteu: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>Per Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt i altres.</small></p><p>Copyright ownCloud GmbH</p><p>amb llicència GNU General Public License (GPL) versió 2.0<br/>ownCloud i el logo d'ownCloud són marques registrades d'ownCloud GmbH als Estats Units, altres països, o ambdós.</p> + <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> + diff --git a/translations/client_cs.ts b/translations/client_cs.ts index 3c2eb0519f..c91112bfc6 100644 --- a/translations/client_cs.ts +++ b/translations/client_cs.ts @@ -2994,23 +2994,23 @@ Nedoporučuje se jí používat. Chyba při čtení adresáře. - + File/Folder is ignored because it's hidden. Soubor/adresář je ignorován, protože je skrytý. - + Only %1 are available, need at least %2 to start Placeholders are postfixed with file sizes using Utility::octetsToString() Je dostupných pouze %1, pro spuštění je potřeba alespoň %2 - + Not allowed because you don't have permission to add parent folder Není povoleno, protože nemáte oprávnění vytvořit nadřazený adresář - + Not allowed because you don't have permission to add files in that folder Není povoleno, protože nemáte oprávnění přidávat soubory do tohoto adresáře @@ -3070,124 +3070,124 @@ Nedoporučuje se jí používat. Položka nebyla synchronizována kvůli předchozí chybě: %1 - + Symbolic links are not supported in syncing. Symbolické odkazy nejsou při synchronizaci podporovány. - + File is listed on the ignore list. Soubor se nachází na seznamu ignorovaných. - + File names ending with a period are not supported on this file system. Jména souborů končících tečkou nejsou na tomto systému souborů podporována. - + File names containing the character '%1' are not supported on this file system. Názvy souborů obsahující znak '%1' nejsou na tomto souborovém systému podporovány. - + The file name is a reserved name on this file system. Jméno souboru je na tomto systému souborů rezervovaným jménem. - + Filename contains trailing spaces. Jméno souboru obsahuje mezery na konci řádky. - + Filename is too long. Jméno souboru je příliš dlouhé. - + Stat failed. Stat selhal. - + Filename encoding is not valid Kódování znaků jména soubor je neplatné - + Invalid characters, please rename "%1" Neplatné znaky, prosím přejmenujte "%1" - + Unable to initialize a sync journal. Nemohu inicializovat synchronizační žurnál. - + Unable to read the blacklist from the local database Nelze načíst blacklist z místní databáze - + Unable to read from the sync journal. Nelze číst ze žurnálu synchronizace. - + Cannot open the sync journal Nelze otevřít synchronizační žurnál - + File name contains at least one invalid character Jméno souboru obsahuje alespoň jeden neplatný znak - - + + Ignored because of the "choose what to sync" blacklist Ignorováno podle nastavení "vybrat co synchronizovat" - + Not allowed because you don't have permission to add subfolders to that folder Není povoleno, protože nemáte oprávnění přidávat podadresáře do tohoto adresáře - + Not allowed to upload this file because it is read-only on the server, restoring Není povoleno nahrát tento soubor, protože je na serveru uložen pouze pro čtení, obnovuji - - + + Not allowed to remove, restoring Odstranění není povoleno, obnovuji - + Local files and share folder removed. Místní soubory a sdílený adresář byly odstraněny. - + Move not allowed, item restored Přesun není povolen, položka obnovena - + Move not allowed because %1 is read-only Přesun není povolen, protože %1 je pouze pro čtení - + the destination cílové umístění - + the source zdroj @@ -3456,8 +3456,8 @@ Nedoporučuje se jí používat. OCC::ownCloudTheme - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> - <p>Verze %2. Pro další informace navštivte <a href="%3">https://%4</a></p><p>Informace o známých chybách a pomoc hledejte na: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt a další.</small></p><p>Copyright ownCloud GmbH</p><p>Licencováno pod GNU General Public License (GPL) Verze 2.0<br/>ownCloud a ownCloud logo jsou registrované obchodní známky ownCloud GmbH ve Spojených státech, ostatních zemích nebo obojí.</p> + <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> + diff --git a/translations/client_de.ts b/translations/client_de.ts index 5b3b13befd..d12002d124 100644 --- a/translations/client_de.ts +++ b/translations/client_de.ts @@ -2995,23 +2995,23 @@ Es ist nicht ratsam, diese zu benutzen. Fehler beim Lesen eines Ordners. - + File/Folder is ignored because it's hidden. Datei wird ignoriert, weil sie versteckt ist. - + Only %1 are available, need at least %2 to start Placeholders are postfixed with file sizes using Utility::octetsToString() Nur %1 sind verfügbar. Zum Beginnen werden mindestens %2 benötigt. - + Not allowed because you don't have permission to add parent folder Nicht erlaubt, da Sie keine Rechte zur Erstellung von Unterordnern haben - + Not allowed because you don't have permission to add files in that folder Nicht erlaubt, da Sie keine Rechte zum Hinzufügen von Dateien in diesen Ordner haben @@ -3071,124 +3071,124 @@ Es ist nicht ratsam, diese zu benutzen. Das Element ist aufgrund vorheriger Fehler nicht synchronisiert: %1 - + Symbolic links are not supported in syncing. Symbolische Verknüpfungen werden bei der Synchronisation nicht unterstützt. - + File is listed on the ignore list. Die Datei ist in der Ignorierliste geführt. - + File names ending with a period are not supported on this file system. Dateinamen enden mit einem Punkt, die in diesem Dateisystem nicht unterstützt wird. - + File names containing the character '%1' are not supported on this file system. Dateinamen beinhalten das Zeichen '%1' und diese werden in diesem Dateisystems nicht unterstützt. - + The file name is a reserved name on this file system. Der Dateiname ist ein reservierter Name in diesem Dateisystem. - + Filename contains trailing spaces. Dateiname endet mit Leerzeichen. - + Filename is too long. Der Dateiname ist zu lang. - + Stat failed. Stat fehlgeschlagen. - + Filename encoding is not valid Dateikodierung ist ungültig - + Invalid characters, please rename "%1" Ungültige Zeichenm bitte benennen Sie "%1" um - + Unable to initialize a sync journal. Synchronisationsbericht konnte nicht initialisiert werden. - + Unable to read the blacklist from the local database Fehler beim Einlesen der Blacklist aus der lokalen Datenbank - + Unable to read from the sync journal. Fehler beim Einlesen des Synchronisierungsprotokolls. - + Cannot open the sync journal Synchronisationsbericht kann nicht geöffnet werden - + File name contains at least one invalid character Der Dateiname enthält mindestens ein ungültiges Zeichen - - + + Ignored because of the "choose what to sync" blacklist Aufgrund der »Zu synchronisierende Elemente auswählen«-Sperrliste ignoriert - + Not allowed because you don't have permission to add subfolders to that folder Nicht erlaubt, da Sie keine Rechte zur Erstellung von Unterordnern haben - + Not allowed to upload this file because it is read-only on the server, restoring Das Hochladen dieser Datei ist nicht erlaubt, da die Datei auf dem Server schreibgeschützt ist, Wiederherstellung - - + + Not allowed to remove, restoring Löschen nicht erlaubt, Wiederherstellung - + Local files and share folder removed. Lokale Dateien und Freigabeordner wurden entfernt. - + Move not allowed, item restored Verschieben nicht erlaubt, Element wiederhergestellt - + Move not allowed because %1 is read-only Verschieben nicht erlaubt, da %1 schreibgeschützt ist - + the destination Das Ziel - + the source Die Quelle @@ -3457,8 +3457,8 @@ Es ist nicht ratsam, diese zu benutzen. OCC::ownCloudTheme - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> - <p>Version %2. Weitere Informationen unter <a href="%3">https://%4</a></p><p>Für bekannte Fehler und die Hilfe, besuchen Sie bitte: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>Von Klaas Freitag, Daniel Molkentin, Jan-Christoph Borchardt, Olivier Goffart, Markus Götz und anderen.</small></p><p>Copyright ownCloud GmbH</p><p>Lizenziert unter den Bedingungen der GNU General Public License (GPL) Version 2.0<br/>ownCloud und das ownCloud Logo sind eingetragene Warenzeichen der ownCloud Inc. in den USA, anderen Ländern, oder beidem.</p> + <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> + <p>Version %2. Weitere Informationen unter <a href="%3">https://%4</a></p><p>Für bekannte Fehler und die Hilfe, besuchen Sie bitte: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>Von Klaas Freitag, Daniel Molkentin, Jan-Christoph Borchardt, Olivier Goffart, Markus Götz und anderen.</small></p><p>Copyright ownCloud GmbH</p><p>Lizenziert unter den Bedingungen der GNU General Public License (GPL) Version 2.0<br/>ownCloud und das ownCloud Logo sind eingetragene Warenzeichen der ownCloud Inc. in den USA, anderen Ländern, oder beidem.</p> diff --git a/translations/client_el.ts b/translations/client_el.ts index bac0620b61..d878522830 100644 --- a/translations/client_el.ts +++ b/translations/client_el.ts @@ -2996,23 +2996,23 @@ It is not advisable to use it. Σφάλμα κατά την ανάγνωση του φακέλου. - + File/Folder is ignored because it's hidden. Το Αρχείο/ο Φάκελος αγνοήθηκε επειδή είναι κρυφό. - + Only %1 are available, need at least %2 to start Placeholders are postfixed with file sizes using Utility::octetsToString() Μόνο %1 είναι διαθέσιμα, απαιτούνται τουλάχιστον %2 για την εκκίνηση - + Not allowed because you don't have permission to add parent folder Δεν επιτρέπεται επειδή δεν έχετε δικαιώματα να προσθέσετε γονικό κατάλογο - + Not allowed because you don't have permission to add files in that folder Δεν επιτρέπεται επειδή δεν έχετε δικαιώματα να προσθέσετε αρχεία σε αυτόν τον φάκελο @@ -3072,124 +3072,124 @@ It is not advisable to use it. Το αντικείμενο δεν είναι συγχρονισμένο λόγω προηγούμενων σφαλμάτων: %1 - + Symbolic links are not supported in syncing. Οι συμβολικού σύνδεσμοι δεν υποστηρίζονται για το συγχρονισμό. - + File is listed on the ignore list. Το αρχείο περιέχεται στη λίστα αρχείων προς αγνόηση. - + File names ending with a period are not supported on this file system. Τα ονόματα αρχείων που διαρκούν μια ορισμένη χρονική περίοδο δεν υποστηρίζονται σε αυτό το σύστημα αρχείων. - + File names containing the character '%1' are not supported on this file system. Τα ονόματα αρχείων που περιέχουν τον χαρακτήρα '% 1' δεν υποστηρίζονται σε αυτό το σύστημα αρχείων. - + The file name is a reserved name on this file system. Το όνομα αρχείου είναι ένα κατοχυρωμένο όνομα σε αυτό το σύστημα αρχείων. - + Filename contains trailing spaces. Το όνομα του αρχείου περιέχει συνεχόμενα κενά. - + Filename is too long. Το όνομα αρχείου είνια πολύ μεγάλο. - + Stat failed. Απέτυχε. - + Filename encoding is not valid Η κωδικοποίηση του ονόματος αρχείου δεν είναι έγκυρη - + Invalid characters, please rename "%1" Μη έγκυροι χαρακτήρες, παρακαλώ μετονομάστε το "%1" - + Unable to initialize a sync journal. Αδυναμία προετοιμασίας αρχείου συγχρονισμού. - + Unable to read the blacklist from the local database Αδυναμία ανάγνωσης της μαύρης λίστας από την τοπική βάση δεδομένων - + Unable to read from the sync journal. Αδυναμία ανάγνωσης από το ημερολόγιο συγχρονισμού. - + Cannot open the sync journal Αδυναμία ανοίγματος του αρχείου συγχρονισμού - + File name contains at least one invalid character Το όνομα αρχείου περιέχει έναν τουλάχιστον μη έγκυρο χαρακτήρα - - + + Ignored because of the "choose what to sync" blacklist Αγνοήθηκε εξαιτίας της μαύρης λίστας "διάλεξε τι να συγχρονιστεί" - + Not allowed because you don't have permission to add subfolders to that folder Δεν επιτρέπεται επειδή δεν έχετε δικαιώματα να προσθέσετε υποφακέλους σε αυτό τον φάκελο - + Not allowed to upload this file because it is read-only on the server, restoring Δεν επιτρέπεται να μεταφορτώσετε αυτό το αρχείο επειδή είναι μόνο για ανάγνωση στο διακομιστή, αποκατάσταση σε εξέλιξη - - + + Not allowed to remove, restoring Δεν επιτρέπεται η αφαίρεση, αποκατάσταση σε εξέλιξη - + Local files and share folder removed. Οι τοπικοί φάκελοι και ο φάκελος κοινής χρήσης αφαιρέθηκαν. - + Move not allowed, item restored Η μετακίνηση δεν επιτρέπεται, το αντικείμενο αποκαταστάθηκε - + Move not allowed because %1 is read-only Η μετακίνηση δεν επιτρέπεται επειδή το %1 είναι μόνο για ανάγνωση - + the destination ο προορισμός - + the source η προέλευση @@ -3458,8 +3458,8 @@ It is not advisable to use it. OCC::ownCloudTheme - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> - <p>Έκδοση% 2.Για περισσότερες πληροφορίες επισκεφτείτε<a href="%3"> επίσημη ιστοσελίδα://%4</a></p><p> Για γνωστά ζητήματα και βοήθεια, παρακαλώ επισκεφτείτε: <a href=επίσημη ιστοσελίδα://central.owncloud.org/c/help/desktop-file-sync">επίσημη ιστοσελίδα://central.owncloud.org</a></p><p><small> Από τον Klaas Freitag, τον Daniel Molkentin, τον Olivier Goffart, τον Markus Götz , Jan-Christoph Borchardt και άλλους.</small></p><p> Κατοχυρωμένα υπό το ownCloud GmbH </ p> <p>Άδεια χρήσης υπό την GNU General Public License (GPL) Έκδοση 2.0 <br/> ownCloud και το ownCloud Τα λογότυπα είναι εμπορικά σήματα κατατεθέντα της ownCloud GmbH στις Ηνωμένες Πολιτείες, σε άλλες χώρες ή και στα δύο. </ P> + <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> + diff --git a/translations/client_en.ts b/translations/client_en.ts index b6a2146eae..5425dc5112 100644 --- a/translations/client_en.ts +++ b/translations/client_en.ts @@ -3013,23 +3013,23 @@ It is not advisable to use it. - + File/Folder is ignored because it's hidden. - + Only %1 are available, need at least %2 to start Placeholders are postfixed with file sizes using Utility::octetsToString() - + Not allowed because you don't have permission to add parent folder - + Not allowed because you don't have permission to add files in that folder @@ -3089,124 +3089,124 @@ It is not advisable to use it. - + Symbolic links are not supported in syncing. - + File is listed on the ignore list. - + File names ending with a period are not supported on this file system. - + File names containing the character '%1' are not supported on this file system. - + The file name is a reserved name on this file system. - + Filename contains trailing spaces. - + Filename is too long. - + Stat failed. - + Filename encoding is not valid - + Invalid characters, please rename "%1" - + Unable to initialize a sync journal. - + Unable to read the blacklist from the local database - + Unable to read from the sync journal. - + Cannot open the sync journal - + File name contains at least one invalid character - - + + Ignored because of the "choose what to sync" blacklist - + Not allowed because you don't have permission to add subfolders to that folder - + Not allowed to upload this file because it is read-only on the server, restoring - - + + Not allowed to remove, restoring - + Local files and share folder removed. - + Move not allowed, item restored - + Move not allowed because %1 is read-only - + the destination - + the source @@ -3475,7 +3475,7 @@ It is not advisable to use it. OCC::ownCloudTheme - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> + <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> diff --git a/translations/client_es.ts b/translations/client_es.ts index 97b7e559ba..df6a089405 100644 --- a/translations/client_es.ts +++ b/translations/client_es.ts @@ -2995,23 +2995,23 @@ No se recomienda usarla. Error al leer el directorio. - + File/Folder is ignored because it's hidden. Se ignoran los Archivos/Carpetas ocultos. - + Only %1 are available, need at least %2 to start Placeholders are postfixed with file sizes using Utility::octetsToString() Solo %1 disponible, se necesita por lo menos %2 para comenzar - + Not allowed because you don't have permission to add parent folder No permitido porque no tienes permiso para añadir un directorio padre - + Not allowed because you don't have permission to add files in that folder No permitido porque no tienes permiso para añadir archivos a ese directorio @@ -3071,124 +3071,124 @@ No se recomienda usarla. El elemento no está sincronizado por errores previos: %1 - + Symbolic links are not supported in syncing. No se admiten enlaces simbólicos en la sincronización. - + File is listed on the ignore list. El fichero está en la lista de ignorados - + File names ending with a period are not supported on this file system. Los nombres de archivo que terminan con un punto no son compatibles con este sistema de archivos. - + File names containing the character '%1' are not supported on this file system. Los nombres de archivo que contengan el caracter '%1' no son compatibles con este sistema de archivos. - + The file name is a reserved name on this file system. El nombre del archivo es una palabra reservada del sistema de archivos. - + Filename contains trailing spaces. El nombre del archivo contiene espacios finales. - + Filename is too long. El nombre del archivo es demasiado largo. - + Stat failed. Stat ha fallado. - + Filename encoding is not valid Los caracteres del nombre de fichero no son válidos - + Invalid characters, please rename "%1" Caracteres inválidos, por favor renombre "%1" - + Unable to initialize a sync journal. No se pudo inicializar un registro (journal) de sincronización. - + Unable to read the blacklist from the local database No se pudo leer la lista de bloqueo de la base de datos local - + Unable to read from the sync journal. No se ha podido leer desde el registro de sincronización - + Cannot open the sync journal No es posible abrir el diario de sincronización - + File name contains at least one invalid character Nombre de archivo contiene al menos un caracter no válido - - + + Ignored because of the "choose what to sync" blacklist Ignorado porque se encuentra en la lista negra de "elija qué va a sincronizar" - + Not allowed because you don't have permission to add subfolders to that folder No permitido porque no tienes permiso para añadir subdirectorios a ese directorio - + Not allowed to upload this file because it is read-only on the server, restoring No está permitido subir este archivo porque es de solo lectura en el servidor, restaurando. - - + + Not allowed to remove, restoring No está permitido borrar, restaurando. - + Local files and share folder removed. Se han eliminado los archivos locales y la carpeta compartida. - + Move not allowed, item restored No está permitido mover, elemento restaurado. - + Move not allowed because %1 is read-only No está permitido mover, porque %1 es de sólo lectura. - + the destination destino - + the source origen @@ -3457,8 +3457,8 @@ No se recomienda usarla. OCC::ownCloudTheme - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> - <p>Versiòn %2. Para más información visita:<a href="%3">https://%4</a></p><p>Para ayuda y fallos conocidos, visita: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> + <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> + <p>Version %2. Para mas información visite: <a href="%3">https://%4</a></p><p>Para ayuda y asistencia , visite: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licenciado bajo la GNU General Public License (GPL) Version 2.0<br/>ownCloud y el logo de ownCloud son marcas registradas de ownCloud GmbH en los Estados Unidos, en otros paises o en ambos.</p> diff --git a/translations/client_es_AR.ts b/translations/client_es_AR.ts index f0839b5d42..f35f41e292 100644 --- a/translations/client_es_AR.ts +++ b/translations/client_es_AR.ts @@ -2981,23 +2981,23 @@ It is not advisable to use it. - + File/Folder is ignored because it's hidden. - + Only %1 are available, need at least %2 to start Placeholders are postfixed with file sizes using Utility::octetsToString() - + Not allowed because you don't have permission to add parent folder - + Not allowed because you don't have permission to add files in that folder @@ -3057,124 +3057,124 @@ It is not advisable to use it. - + Symbolic links are not supported in syncing. Los vínculos simbólicos no está soportados al sincronizar. - + File is listed on the ignore list. El archivo está en la lista de ignorados. - + File names ending with a period are not supported on this file system. - + File names containing the character '%1' are not supported on this file system. - + The file name is a reserved name on this file system. - + Filename contains trailing spaces. - + Filename is too long. - + Stat failed. - + Filename encoding is not valid - + Invalid characters, please rename "%1" - + Unable to initialize a sync journal. Imposible inicializar un diario de sincronización. - + Unable to read the blacklist from the local database - + Unable to read from the sync journal. - + Cannot open the sync journal - + File name contains at least one invalid character - - + + Ignored because of the "choose what to sync" blacklist - + Not allowed because you don't have permission to add subfolders to that folder - + Not allowed to upload this file because it is read-only on the server, restoring - - + + Not allowed to remove, restoring - + Local files and share folder removed. - + Move not allowed, item restored - + Move not allowed because %1 is read-only - + the destination - + the source @@ -3443,7 +3443,7 @@ It is not advisable to use it. OCC::ownCloudTheme - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> + <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> diff --git a/translations/client_et.ts b/translations/client_et.ts index 1e194a6058..571e86992f 100644 --- a/translations/client_et.ts +++ b/translations/client_et.ts @@ -2984,23 +2984,23 @@ Selle kasutamine pole soovitatav. - + File/Folder is ignored because it's hidden. - + Only %1 are available, need at least %2 to start Placeholders are postfixed with file sizes using Utility::octetsToString() - + Not allowed because you don't have permission to add parent folder - + Not allowed because you don't have permission to add files in that folder @@ -3060,124 +3060,124 @@ Selle kasutamine pole soovitatav. Üksust ei sünkroniseeritud eelnenud vigade tõttu: %1 - + Symbolic links are not supported in syncing. Sümboolsed lingid ei ole sünkroniseerimisel toetatud. - + File is listed on the ignore list. Fail on märgitud ignoreeritavate nimistus. - + File names ending with a period are not supported on this file system. - + File names containing the character '%1' are not supported on this file system. - + The file name is a reserved name on this file system. - + Filename contains trailing spaces. - + Filename is too long. Faili nimi on liiga pikk. - + Stat failed. - + Filename encoding is not valid Failinime kodeering pole kehtiv - + Invalid characters, please rename "%1" - + Unable to initialize a sync journal. Ei suuda lähtestada sünkroniseeringu zurnaali. - + Unable to read the blacklist from the local database - + Unable to read from the sync journal. - + Cannot open the sync journal Ei suuda avada sünkroniseeringu zurnaali - + File name contains at least one invalid character Faili nimesonvähemalt üks keelatud märk - - + + Ignored because of the "choose what to sync" blacklist "Vali, mida sünkroniseerida" musta nimekirja tõttu vahele jäetud - + Not allowed because you don't have permission to add subfolders to that folder - + Not allowed to upload this file because it is read-only on the server, restoring Pole lubatud üles laadida, kuna tegemist on ainult-loetava serveriga, taastan - - + + Not allowed to remove, restoring Eemaldamine pole lubatud, taastan - + Local files and share folder removed. Kohalikud failid ja jagatud kaustad eemaldatud. - + Move not allowed, item restored Liigutamine pole lubatud, üksus taastatud - + Move not allowed because %1 is read-only Liigutamien pole võimalik kuna %1 on ainult lugemiseks - + the destination sihtkoht - + the source allikas @@ -3446,7 +3446,7 @@ Selle kasutamine pole soovitatav. OCC::ownCloudTheme - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> + <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> diff --git a/translations/client_eu.ts b/translations/client_eu.ts index 73e5d44a24..fae7ce9dbd 100644 --- a/translations/client_eu.ts +++ b/translations/client_eu.ts @@ -2984,23 +2984,23 @@ Ez da gomendagarria erabltzea. - + File/Folder is ignored because it's hidden. - + Only %1 are available, need at least %2 to start Placeholders are postfixed with file sizes using Utility::octetsToString() - + Not allowed because you don't have permission to add parent folder - + Not allowed because you don't have permission to add files in that folder @@ -3060,124 +3060,124 @@ Ez da gomendagarria erabltzea. - + Symbolic links are not supported in syncing. Esteka sinbolikoak ezin dira sinkronizatu. - + File is listed on the ignore list. Fitxategia baztertutakoen zerrendan dago. - + File names ending with a period are not supported on this file system. - + File names containing the character '%1' are not supported on this file system. - + The file name is a reserved name on this file system. - + Filename contains trailing spaces. - + Filename is too long. - + Stat failed. - + Filename encoding is not valid - + Invalid characters, please rename "%1" - + Unable to initialize a sync journal. Ezin izan da sinkronizazio egunerokoa hasieratu. - + Unable to read the blacklist from the local database - + Unable to read from the sync journal. - + Cannot open the sync journal Ezin da sinkronizazio egunerokoa ireki - + File name contains at least one invalid character Fitxategi izenak behintzat baliogabeko karaktere bat du - - + + Ignored because of the "choose what to sync" blacklist - + Not allowed because you don't have permission to add subfolders to that folder - + Not allowed to upload this file because it is read-only on the server, restoring - - + + Not allowed to remove, restoring Ezabatzeko baimenik gabe, berrezartzen - + Local files and share folder removed. - + Move not allowed, item restored Mugitzea ez dago baimenduta, elementua berrezarri da - + Move not allowed because %1 is read-only Mugitzea ez dago baimenduta %1 irakurtzeko bakarrik delako - + the destination helburua - + the source jatorria @@ -3446,7 +3446,7 @@ Ez da gomendagarria erabltzea. OCC::ownCloudTheme - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> + <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> diff --git a/translations/client_fa.ts b/translations/client_fa.ts index cafd7cfadc..998bb53edf 100644 --- a/translations/client_fa.ts +++ b/translations/client_fa.ts @@ -2981,23 +2981,23 @@ It is not advisable to use it. خطا در هنگام خواندن پوشه - + File/Folder is ignored because it's hidden. - + Only %1 are available, need at least %2 to start Placeholders are postfixed with file sizes using Utility::octetsToString() - + Not allowed because you don't have permission to add parent folder - + Not allowed because you don't have permission to add files in that folder @@ -3057,124 +3057,124 @@ It is not advisable to use it. - + Symbolic links are not supported in syncing. - + File is listed on the ignore list. - + File names ending with a period are not supported on this file system. - + File names containing the character '%1' are not supported on this file system. - + The file name is a reserved name on this file system. - + Filename contains trailing spaces. - + Filename is too long. نام فایل خیلی طولانی است. - + Stat failed. وضعیت ناموفق - + Filename encoding is not valid رمزگذاری نام فایل معتبر نیست - + Invalid characters, please rename "%1" کاراکتر نامعتبر، لطفا "%1" را تغییر نام دهید - + Unable to initialize a sync journal. - + Unable to read the blacklist from the local database - + Unable to read from the sync journal. - + Cannot open the sync journal - + File name contains at least one invalid character نام فایل دارای حداقل یک کاراکتر نامعتبر است - - + + Ignored because of the "choose what to sync" blacklist - + Not allowed because you don't have permission to add subfolders to that folder با توجه به عدم اجازه‌ی شما به ایجاد زیرپوشه به پوشه مجاز نیست - + Not allowed to upload this file because it is read-only on the server, restoring آپلود این فایل با توجه به فقط-خواندنی بودن آن در سرور مجاز نیست، در حال بازگرداندن - - + + Not allowed to remove, restoring حذف مجاز نیست، در حال بازگردادن - + Local files and share folder removed. فایل‌های محلی و پوشه‌ی اشتراک حذف شد. - + Move not allowed, item restored انتقال مجاز نیست، مورد بازگردانده شد - + Move not allowed because %1 is read-only - + the destination مقصد - + the source مبدا @@ -3443,7 +3443,7 @@ It is not advisable to use it. OCC::ownCloudTheme - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> + <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> diff --git a/translations/client_fi.ts b/translations/client_fi.ts index 08064209e8..bb15cff507 100644 --- a/translations/client_fi.ts +++ b/translations/client_fi.ts @@ -2986,23 +2986,23 @@ Osoitteen käyttäminen ei ole suositeltavaa. Kansiota lukiessa tapahtui virhe - + File/Folder is ignored because it's hidden. Tiedosto/kansi ohitetaan, koska se on piilotettu. - + Only %1 are available, need at least %2 to start Placeholders are postfixed with file sizes using Utility::octetsToString() Vain %1 on käytettävissä, käynnistymiseen tarvitaan %2 - + Not allowed because you don't have permission to add parent folder Ei sallittu, koska käyttöoikeutesi eivät riitä ylätason kansion lisäämiseen - + Not allowed because you don't have permission to add files in that folder Ei sallittu, koska käyttöoikeutesi eivät riitä tiedostojen lisäämiseen kyseiseen kansioon @@ -3062,124 +3062,124 @@ Osoitteen käyttäminen ei ole suositeltavaa. Kohdetta ei synkronoitu aiempien virheiden vuoksi: %1 - + Symbolic links are not supported in syncing. Symboliset linkit eivät ole tuettuja synkronoinnissa. - + File is listed on the ignore list. Tiedosto on ohituslistalla. - + File names ending with a period are not supported on this file system. - + File names containing the character '%1' are not supported on this file system. - + The file name is a reserved name on this file system. - + Filename contains trailing spaces. - + Filename is too long. Tiedoston nimi on liian pitkä. - + Stat failed. Stat epäonnistui. - + Filename encoding is not valid Tiedostonimen merkistökoodaus ei ole kelvollista - + Invalid characters, please rename "%1" Virheellisiä merkkejä, anna uusi nimi kohteelle "%1" - + Unable to initialize a sync journal. - + Unable to read the blacklist from the local database - + Unable to read from the sync journal. - + Cannot open the sync journal - + File name contains at least one invalid character Tiedoston nimi sisältää ainakin yhden virheellisen merkin - - + + Ignored because of the "choose what to sync" blacklist - + Not allowed because you don't have permission to add subfolders to that folder Ei sallittu, koska oikeutesi eivät riitä alikansioiden lisäämiseen kyseiseen kansioon - + Not allowed to upload this file because it is read-only on the server, restoring - - + + Not allowed to remove, restoring Poistaminen ei ole sallittua, palautetaan - + Local files and share folder removed. Paikalliset tiedostot ja jakokansio poistettu. - + Move not allowed, item restored Siirtäminen ei ole sallittua, kohde palautettu - + Move not allowed because %1 is read-only Siirto ei ole sallittu, koska %1 on "vain luku"-tilassa - + the destination kohde - + the source lähde @@ -3448,7 +3448,7 @@ Osoitteen käyttäminen ei ole suositeltavaa. OCC::ownCloudTheme - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> + <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> diff --git a/translations/client_fr.ts b/translations/client_fr.ts index c570e49b41..5c4fcc8bf2 100644 --- a/translations/client_fr.ts +++ b/translations/client_fr.ts @@ -2997,23 +2997,23 @@ Il est déconseillé de l'utiliser. Erreur lors de la lecture du dossier. - + File/Folder is ignored because it's hidden. Le fichier ou dossier a été ignoré car il est masqué. - + Only %1 are available, need at least %2 to start Placeholders are postfixed with file sizes using Utility::octetsToString() Seulement %1 disponibles, il faut au moins %2 pour démarrer - + Not allowed because you don't have permission to add parent folder Non autorisé car vous n'avez pas la permission d'ajouter un dossier parent - + Not allowed because you don't have permission to add files in that folder Non autorisé car vous n'avez pas la permission d'ajouter des fichiers dans ce dossier @@ -3073,124 +3073,124 @@ Il est déconseillé de l'utiliser. Cet élément n'a pas été synchronisé en raison des erreurs précédentes : %1 - + Symbolic links are not supported in syncing. Les liens symboliques ne sont pas pris en charge par la synchronisation. - + File is listed on the ignore list. Le fichier est présent dans la liste des fichiers à exclure. - + File names ending with a period are not supported on this file system. Les noms de fichier se terminant par un point ne sont pas pris en charge sur votre système. - + File names containing the character '%1' are not supported on this file system. Les noms de fichier contenant le caractère '%1' ne sont pas pris en charge sur votre système. - + The file name is a reserved name on this file system. Le nom du fichier est réservé sur votre système. - + Filename contains trailing spaces. Le nom du fichier se fini par des espaces. - + Filename is too long. Le nom de fichier est trop long. - + Stat failed. Stat échoué. - + Filename encoding is not valid L'encodage du nom de fichier n'est pas valide - + Invalid characters, please rename "%1" Caractères non valides. Veuillez renommer "%1" - + Unable to initialize a sync journal. Impossible d'initialiser un journal de synchronisation. - + Unable to read the blacklist from the local database Impossible de lire la liste noire de la base de données locale - + Unable to read from the sync journal. Impossible de lire le journal de synchronisation. - + Cannot open the sync journal Impossible d'ouvrir le journal de synchronisation - + File name contains at least one invalid character Le nom de fichier contient au moins un caractère non valable - - + + Ignored because of the "choose what to sync" blacklist Ignoré en raison de la liste noire "Sélectionner le contenu à synchroniser". - + Not allowed because you don't have permission to add subfolders to that folder Non autorisé car vous n'avez pas la permission d'ajouter des sous-dossiers dans ce dossier - + Not allowed to upload this file because it is read-only on the server, restoring Non autorisé à envoyer ce fichier car il est en lecture seule sur le serveur. Restauration - - + + Not allowed to remove, restoring Non autorisé à supprimer. Restauration - + Local files and share folder removed. Fichiers locaux et dossier partagé supprimés. - + Move not allowed, item restored Déplacement non autorisé, élément restauré - + Move not allowed because %1 is read-only Déplacement non autorisé car %1 est en mode lecture seule - + the destination la destination - + the source la source @@ -3459,8 +3459,8 @@ Il est déconseillé de l'utiliser. OCC::ownCloudTheme - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> - <p>Version %2. Pour plus d'informations, consultez <a href="%3">https://%4</a>.</p><p>Pour consulter les problèmes connus et obtenir de l'aide, merci de visiter : <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a>.</p><p><small>Par Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt et d'autres personnes.</small></p><p>Copyright ownCloud GmbH</p><p>Sous licence GNU General Public License (GPL) Version 2.0.<br/>ownCloud et le logo ownCloud sont des marques déposées de ownCloud GmbH aux États-Unis, dans d'autres pays ou dans les deux.</p> + <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> + diff --git a/translations/client_gl.ts b/translations/client_gl.ts index 84643b388b..77d352a4ec 100644 --- a/translations/client_gl.ts +++ b/translations/client_gl.ts @@ -2985,23 +2985,23 @@ Recomendámoslle que non o use. - + File/Folder is ignored because it's hidden. - + Only %1 are available, need at least %2 to start Placeholders are postfixed with file sizes using Utility::octetsToString() - + Not allowed because you don't have permission to add parent folder - + Not allowed because you don't have permission to add files in that folder @@ -3061,124 +3061,124 @@ Recomendámoslle que non o use. Este elemento non foi sincronizado por mor de erros anteriores: %1 - + Symbolic links are not supported in syncing. As ligazóns simbolicas non son admitidas nas sincronizacións - + File is listed on the ignore list. O ficheiro está na lista de ignorados. - + File names ending with a period are not supported on this file system. - + File names containing the character '%1' are not supported on this file system. - + The file name is a reserved name on this file system. - + Filename contains trailing spaces. - + Filename is too long. O nome de ficheiro é longo de máis. - + Stat failed. Fallou a obtención de estatísticas. - + Filename encoding is not valid O nome de ficheiro codificado non é correcto - + Invalid characters, please rename "%1" Caracteres incorrectos, déalle outro nome a «%1» - + Unable to initialize a sync journal. Non é posíbel preparar un rexistro de sincronización. - + Unable to read the blacklist from the local database - + Unable to read from the sync journal. - + Cannot open the sync journal Non foi posíbel abrir o rexistro de sincronización - + File name contains at least one invalid character O nome de ficheiro contén algún carácter incorrecto - - + + Ignored because of the "choose what to sync" blacklist Ignorado por mor da lista negra de «escolla que sincronizar» - + Not allowed because you don't have permission to add subfolders to that folder - + Not allowed to upload this file because it is read-only on the server, restoring Non está permitido o envío xa que o ficheiro é só de lectura no servidor, restaurando - - + + Not allowed to remove, restoring Non está permitido retiralo, restaurando - + Local files and share folder removed. Retirados os ficheiros locais e o cartafol compartido. - + Move not allowed, item restored Nos está permitido movelo, elemento restaurado - + Move not allowed because %1 is read-only Bon está permitido movelo xa que %1 é só de lectura - + the destination o destino - + the source a orixe @@ -3447,7 +3447,7 @@ Recomendámoslle que non o use. OCC::ownCloudTheme - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> + <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> diff --git a/translations/client_hu.ts b/translations/client_hu.ts index 192694b89b..c82a752a49 100644 --- a/translations/client_hu.ts +++ b/translations/client_hu.ts @@ -2982,23 +2982,23 @@ It is not advisable to use it. - + File/Folder is ignored because it's hidden. - + Only %1 are available, need at least %2 to start Placeholders are postfixed with file sizes using Utility::octetsToString() - + Not allowed because you don't have permission to add parent folder - + Not allowed because you don't have permission to add files in that folder @@ -3058,124 +3058,124 @@ It is not advisable to use it. - + Symbolic links are not supported in syncing. - + File is listed on the ignore list. Fájl a kizárási listán. - + File names ending with a period are not supported on this file system. - + File names containing the character '%1' are not supported on this file system. - + The file name is a reserved name on this file system. - + Filename contains trailing spaces. - + Filename is too long. Fájlnév túl nagy. - + Stat failed. - + Filename encoding is not valid - + Invalid characters, please rename "%1" Érvénytelen karakterek, kérjük nevezd át: %1 - + Unable to initialize a sync journal. - + Unable to read the blacklist from the local database - + Unable to read from the sync journal. - + Cannot open the sync journal - + File name contains at least one invalid character A fájlnév legalább egy érvénytelen karaktert tartalmaz! - - + + Ignored because of the "choose what to sync" blacklist - + Not allowed because you don't have permission to add subfolders to that folder - + Not allowed to upload this file because it is read-only on the server, restoring - - + + Not allowed to remove, restoring - + Local files and share folder removed. - + Move not allowed, item restored - + Move not allowed because %1 is read-only - + the destination a cél - + the source a forrás @@ -3444,7 +3444,7 @@ It is not advisable to use it. OCC::ownCloudTheme - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> + <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> diff --git a/translations/client_it.ts b/translations/client_it.ts index fc949e14a5..c254697a97 100644 --- a/translations/client_it.ts +++ b/translations/client_it.ts @@ -2991,23 +2991,23 @@ Non è consigliabile utilizzarlo. Errore durante la lettura della cartella. - + File/Folder is ignored because it's hidden. Il file/cartella è ignorato poiché è nascosto. - + Only %1 are available, need at least %2 to start Placeholders are postfixed with file sizes using Utility::octetsToString() Sono disponibili solo %1, servono almeno %2 per iniziare - + Not allowed because you don't have permission to add parent folder Non consentito poiché non disponi dei permessi per aggiungere la cartella superiore - + Not allowed because you don't have permission to add files in that folder Non consentito poiché non disponi dei permessi per aggiungere file in quella cartella @@ -3067,124 +3067,124 @@ Non è consigliabile utilizzarlo. L'elemento non è sincronizzato a causa dell'errore precedente: %1 - + Symbolic links are not supported in syncing. I collegamenti simbolici non sono supportati dalla sincronizzazione. - + File is listed on the ignore list. Il file è stato aggiunto alla lista ignorati. - + File names ending with a period are not supported on this file system. I nomi del file che terminano con un punto non sono supportati su questo file system. - + File names containing the character '%1' are not supported on this file system. I nomi del file che contengono il carattere '%1' non sono supportati su questo file system. - + The file name is a reserved name on this file system. Il nome del file è un nome riservato su questo file system. - + Filename contains trailing spaces. Il nome del file contiene spazi alla fine. - + Filename is too long. Il nome del file è troppo lungo. - + Stat failed. Stat non riuscita. - + Filename encoding is not valid La codifica del nome del file non è valida - + Invalid characters, please rename "%1" Caratteri non validi, rinomina "%1" - + Unable to initialize a sync journal. Impossibile inizializzare il registro di sincronizzazione. - + Unable to read the blacklist from the local database Impossibile leggere la lista nera dal database locale - + Unable to read from the sync journal. Impossibile leggere dal registro di sincronizzazione. - + Cannot open the sync journal Impossibile aprire il registro di sincronizzazione - + File name contains at least one invalid character Il nome del file contiene almeno un carattere non valido - - + + Ignored because of the "choose what to sync" blacklist Ignorato in base alla lista nera per la scelta di cosa sincronizzare - + Not allowed because you don't have permission to add subfolders to that folder Non consentito poiché non disponi dei permessi per aggiungere sottocartelle in quella cartella - + Not allowed to upload this file because it is read-only on the server, restoring Il caricamento di questo file non è consentito poiché è in sola lettura sul server, ripristino - - + + Not allowed to remove, restoring Rimozione non consentita, ripristino - + Local files and share folder removed. I file locali e la cartella condivisa sono stati rimossi. - + Move not allowed, item restored Spostamento non consentito, elemento ripristinato - + Move not allowed because %1 is read-only Spostamento non consentito poiché %1 è in sola lettura - + the destination la destinazione - + the source l'origine @@ -3453,8 +3453,8 @@ Non è consigliabile utilizzarlo. OCC::ownCloudTheme - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> - <p>Versione %2. Per ulteriori informazioni, visita <a href="%3">https://%4</a></p><p>Per problemi noti e aiuto, visita:<a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>Di Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, e altri.</small></p><p>Copyright ownCloud GmbH</p><p>Sotto licenza GNU General Public License (GPL) versione 2.0<br/>ownCloud e il logo di ownCloud sono marchi registrati di ownCloud, Inc. negli Stati Uniti, in altri paesi o entrambi.</p> + <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> + diff --git a/translations/client_ja.ts b/translations/client_ja.ts index d750ca1f24..841ece9575 100644 --- a/translations/client_ja.ts +++ b/translations/client_ja.ts @@ -2553,7 +2553,7 @@ It is not advisable to use it. Anyone with the link has access to the file/folder - + リンクを知っている人はファイル/フォルダにアクセスできます @@ -2992,23 +2992,23 @@ It is not advisable to use it. フォルダーの読み込みエラー - + File/Folder is ignored because it's hidden. 隠しファイル/フォルダーのため無視されました - + Only %1 are available, need at least %2 to start Placeholders are postfixed with file sizes using Utility::octetsToString() %1 しか空き容量がありません、開始するためには少なくとも %2 は必要です。 - + Not allowed because you don't have permission to add parent folder 親フォルダーを追加する権限がありません - + Not allowed because you don't have permission to add files in that folder そのフォルダーにファイルを追加する権限がありません @@ -3068,124 +3068,124 @@ It is not advisable to use it. このアイテムは以前にエラーが発生したため同期しません: %1 - + Symbolic links are not supported in syncing. 同期機能はシンボリックリンクをサポートしていません。 - + File is listed on the ignore list. ファイルは除外リストに登録されています。 - + File names ending with a period are not supported on this file system. 末尾にピリオドを使うファイル名はサポートされていません - + File names containing the character '%1' are not supported on this file system. ファイル名に使用できない文字列が含まれています: '%1' - + The file name is a reserved name on this file system. ファイル名はこのファイルシステムで予約されている名前です。 - + Filename contains trailing spaces. ファイル名末尾にスペースが含まれます。 - + Filename is too long. ファイル名が長すぎます - + Stat failed. 情報取得エラー - + Filename encoding is not valid ファイル名のエンコーディングが無効です。 - + Invalid characters, please rename "%1" 無効な文字です、"%1" を変更してください。 - + Unable to initialize a sync journal. 同期ジャーナルの初期化ができません。 - + Unable to read the blacklist from the local database ローカルデータベースからブラックリストを読み込みできません - + Unable to read from the sync journal. 同期ジャーナルから読み込みできません - + Cannot open the sync journal 同期ジャーナルを開くことができません - + File name contains at least one invalid character ファイル名に1文字以上の無効な文字が含まれています - - + + Ignored because of the "choose what to sync" blacklist "同期対象先" ブラックリストにより無視されました。 - + Not allowed because you don't have permission to add subfolders to that folder そのフォルダーにサブフォルダーを追加する権限がありません - + Not allowed to upload this file because it is read-only on the server, restoring サーバーでは読み取り専用となっているため、このファイルをアップロードすることはできません、復元しています - - + + Not allowed to remove, restoring 削除できないので復元しています - + Local files and share folder removed. ローカルファイルと共有フォルダーを削除しました。 - + Move not allowed, item restored 移動できないので項目を復元しました - + Move not allowed because %1 is read-only %1 は読み取り専用のため移動できません - + the destination 移動先 - + the source 移動元 @@ -3454,8 +3454,8 @@ It is not advisable to use it. OCC::ownCloudTheme - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> - <p>バージョン %2 詳細については、<a href="%3">https://%4</a>をご覧ください。</p><p>既知の問題とヘルプは、<a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a>をご覧ください。By Klaas Freitag, Daniel Molkentin, Jan-Christoph Borchardt, Olivier Goffart, Markus Götz and others.</small></p><p>著作権 ownCloud, Inc.<p><p>がGNU General Public License (GPL) バージョン2.0 でライセンスされています。<br/>ownCloud 及び ownCloud のロゴはアメリカ合衆国またはその他の国、あるいはその両方における<br> ownCloud, Inc.の登録商標です。</p> + <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> + diff --git a/translations/client_nb_NO.ts b/translations/client_nb_NO.ts index 76bd6db10e..64979cce49 100644 --- a/translations/client_nb_NO.ts +++ b/translations/client_nb_NO.ts @@ -2556,7 +2556,7 @@ Det er ikke tilrådelig å bruke den. Anyone with the link has access to the file/folder - + Alle med linken har tilgang til filen/mappen @@ -2995,23 +2995,23 @@ Det er ikke tilrådelig å bruke den. Feil ved lesing av mappe. - + File/Folder is ignored because it's hidden. Filen/mappen ignoreres fordi den er skjult. - + Only %1 are available, need at least %2 to start Placeholders are postfixed with file sizes using Utility::octetsToString() Bare %1 er tilgjengelig, trenger minst %2 for å begynne - + Not allowed because you don't have permission to add parent folder Ikke tillatt fordi du ikke har lov til å legge til foreldremappe - + Not allowed because you don't have permission to add files in that folder Ikke tillatt fordi du ikke har lov til å opprette filer i den mappen @@ -3071,124 +3071,124 @@ Det er ikke tilrådelig å bruke den. Elementet er ikke synkronisert på grunn av tidligere feil: %1 - + Symbolic links are not supported in syncing. Symbolske lenker støttes ikke i synkronisering. - + File is listed on the ignore list. Filen ligger på ignoreringslisten. - + File names ending with a period are not supported on this file system. Filnavn som slutter med punktum er ikke tillatt på dette filsystemet - + File names containing the character '%1' are not supported on this file system. Filnavn som inneholder '%1' er ikke tillatt på dette filsystemet - + The file name is a reserved name on this file system. Filnavnet er et reservert navn på dette filsystemet. - + Filename contains trailing spaces. Filnavn inneholder blanke på slutten. - + Filename is too long. Filnavn er for langt. - + Stat failed. Stat feilet. - + Filename encoding is not valid Filnavn-koding er ikke gyldig - + Invalid characters, please rename "%1" Ugyldige tegn, gi et annet navn til "%1" - + Unable to initialize a sync journal. Kan ikke initialisere en synkroniseringsjournal. - + Unable to read the blacklist from the local database Kan ikke lese svartelisten fra den lokale databasen - + Unable to read from the sync journal. Kan ikke lese fra synkroniseringsjournalen - + Cannot open the sync journal Kan ikke åpne synkroniseringsjournalen - + File name contains at least one invalid character Filnavnet inneholder minst ett ulovlig tegn - - + + Ignored because of the "choose what to sync" blacklist Ignorert på grunn av svartelisten "velg hva som skal synkroniseres" - + Not allowed because you don't have permission to add subfolders to that folder Ikke tillatt fordi du ikke har lov til å lage undermapper i den mappen - + Not allowed to upload this file because it is read-only on the server, restoring Ikke tillatt å laste opp denne filenfordi den er skrivebeskyttet på serveren, gjenoppretter - - + + Not allowed to remove, restoring Ikke tillatt å fjerne, gjenoppretter - + Local files and share folder removed. Lokale filer og delingsmappe fjernet. - + Move not allowed, item restored Flytting ikke tillatt, element gjenopprettet - + Move not allowed because %1 is read-only Flytting ikke tillatt fordi %1 er skrivebeskyttet - + the destination målet - + the source kilden @@ -3457,8 +3457,8 @@ Det er ikke tilrådelig å bruke den. OCC::ownCloudTheme - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> - <p>Versjon %2. For mer informasjon gå til <a href="%3">https://%4</a></p><p>For kjente problemer og hjelp, gå til: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>Av Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt med flere.</small></p><p>Copyright ownCloud GmbH</p><p>Lisensiert under GNU General Public License (GPL) Version 2.0<br/>ownCloud og ownCloud-logo er registrerte varemerker for ownCloud GmbH i USA, andre land eller begge deler.</p> + <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> + <p>Versjon %2. For mer informasjon gå til <a href="%3">https://%4</a></p><p>For kjente problemer og hjelp, gå til: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>Av Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt med flere.</small></p><p>Copyright ownCloud GmbH</p><p>Lisensiert under GNU General Public License (GPL) Version 2.0<br/>ownCloud og ownCloud-logo er registrerte varemerker for ownCloud GmbH i USA, andre land eller begge deler.</p> diff --git a/translations/client_nl.ts b/translations/client_nl.ts index d2e03afd6e..644c5f9b07 100644 --- a/translations/client_nl.ts +++ b/translations/client_nl.ts @@ -3000,23 +3000,23 @@ We adviseren deze site niet te gebruiken. Fout tijdens lezen map. - + File/Folder is ignored because it's hidden. Bestand/Map is genegeerd omdat het verborgen is. - + Only %1 are available, need at least %2 to start Placeholders are postfixed with file sizes using Utility::octetsToString() Slechts %1 beschikbaar, maar heeft minimaal %2 nodig om te starten - + Not allowed because you don't have permission to add parent folder Niet toegestaan omdat u geen rechten hebt om een bovenliggende map toe te voegen - + Not allowed because you don't have permission to add files in that folder Niet toegestaan omdat u geen rechten hebt om bestanden in die map toe te voegen @@ -3076,124 +3076,124 @@ We adviseren deze site niet te gebruiken. Dit onderwerp is niet gesynchroniseerd door eerdere fouten: %1 - + Symbolic links are not supported in syncing. Symbolische links worden niet ondersteund bij het synchroniseren. - + File is listed on the ignore list. Het bestand is opgenomen op de negeerlijst. - + File names ending with a period are not supported on this file system. Bestandsnamen die eindigen met een punt worden niet ondersteund door het bestandssysteem. - + File names containing the character '%1' are not supported on this file system. Bestandsnamen met een '%1' symbool worden niet ondersteund door het bestandssysteem. - + The file name is a reserved name on this file system. De bestandsnaam is een gereserveerde naam op dit bestandssysteem. - + Filename contains trailing spaces. De bestandsnaam bevat spaties achteraan. - + Filename is too long. De bestandsnaam is te lang. - + Stat failed. Stat mislukt. - + Filename encoding is not valid Bestandsnaamcodering is niet geldig - + Invalid characters, please rename "%1" Ongeldige tekens, hernoem "%1" - + Unable to initialize a sync journal. Niet in staat om een synchronisatie transactielog te starten. - + Unable to read the blacklist from the local database Kan de blacklist niet lezen uit de lokale database - + Unable to read from the sync journal. Niet mogelijk om te lezen uit het synchronisatie verslag. - + Cannot open the sync journal Kan het sync transactielog niet openen - + File name contains at least one invalid character De bestandsnaam bevat ten minste één ongeldig teken - - + + Ignored because of the "choose what to sync" blacklist Genegeerd vanwege de "wat synchroniseren" zwarte lijst - + Not allowed because you don't have permission to add subfolders to that folder Niet toegestaan, omdat je geen permissies hebt om submappen aan die map toe te voegen - + Not allowed to upload this file because it is read-only on the server, restoring Niet toegestaan om dit bestand te uploaden, omdat het alleen-lezen is op de server, herstellen - - + + Not allowed to remove, restoring Niet toegestaan om te verwijderen, herstellen - + Local files and share folder removed. Lokale bestanden en share-map verwijderd. - + Move not allowed, item restored Verplaatsen niet toegestaan, object hersteld - + Move not allowed because %1 is read-only Verplaatsen niet toegestaan, omdat %1 alleen-lezen is - + the destination bestemming - + the source bron @@ -3462,8 +3462,8 @@ We adviseren deze site niet te gebruiken. OCC::ownCloudTheme - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> - <p>Versie %2. Voor meer informatie bezoek <a href="%3">https://%4</a></p><p>Voor bekende problemen en hulp, bezoek: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a>.</p><p><small>Door Klaas Freitag, Daniel Molkentin, Jan-Christoph Borchardt, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt en anderen.</small></p><p>Copyright ownCloud, GmbH</p><p>Gelicenseerd onder de GNU General Public License (GPL) Versie 2.0<br>ownCloud en het ownCloud logo zijn geregistreerde handelsmerken van ownCloud GmbH in de Verenigde Staten, andere landen, of beide.</p> + <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> + diff --git a/translations/client_pl.ts b/translations/client_pl.ts index e773f7c385..5f4415b377 100644 --- a/translations/client_pl.ts +++ b/translations/client_pl.ts @@ -2023,7 +2023,7 @@ Niezalecane jest jego użycie. The downloaded file is empty despite the server announced it should have been %1. - + Pobrany plik jest pusty pomimo tego, że według zapowiedzi serwera powinien mieć %1. @@ -2184,7 +2184,7 @@ Niezalecane jest jego użycie. Local file changed during syncing. It will be resumed. - + Lokalny plik uległ zmianie w trakcie synchronizacji. Zostanie wznowiony. @@ -2383,7 +2383,7 @@ Niezalecane jest jego użycie. No subfolders currently on the server. - + Na serwerze nie ma w tej chwili żadnych podkatalogów. @@ -2989,23 +2989,23 @@ Niezalecane jest jego użycie. Błąd podczas odczytu katalogu. - + File/Folder is ignored because it's hidden. Plik / katalog zostanie zignorowany, ponieważ jest ukryty. - + Only %1 are available, need at least %2 to start Placeholders are postfixed with file sizes using Utility::octetsToString() - + Not allowed because you don't have permission to add parent folder Niedozwolone, ponieważ nie masz uprawnień do dodawania katalogu nadrzędnego - + Not allowed because you don't have permission to add files in that folder Niedozwolone, ponieważ nie masz uprawnień do dodawania plików w tym katalogu @@ -3065,124 +3065,124 @@ Niezalecane jest jego użycie. Ten element nie jest zsynchronizowane z powodu poprzednich błędów: %1 - + Symbolic links are not supported in syncing. Linki symboliczne nie są wspierane przy synchronizacji. - + File is listed on the ignore list. Plik jest na liście plików ignorowanych. - + File names ending with a period are not supported on this file system. - + File names containing the character '%1' are not supported on this file system. Nazwy plików zawierające znaki '%1' nie są wspierane - + The file name is a reserved name on this file system. Nazwa pliku jest zarezerwowana dla plików systemowych - + Filename contains trailing spaces. Nazwa pliku zawiera spacje - + Filename is too long. Nazwa pliku zbyt długa - + Stat failed. Błąd statystyk. - + Filename encoding is not valid - + Invalid characters, please rename "%1" - + Unable to initialize a sync journal. Nie można zainicjować synchronizacji dziennika. - + Unable to read the blacklist from the local database Nie można odczytać czarnej listy z lokalnej bazy danych - + Unable to read from the sync journal. Nie można czytać z dziennika synchronizacji. - + Cannot open the sync journal Nie można otworzyć dziennika synchronizacji - + File name contains at least one invalid character Nazwa pliku zawiera co najmniej jeden nieprawidłowy znak - - + + Ignored because of the "choose what to sync" blacklist - + Not allowed because you don't have permission to add subfolders to that folder Niedozwolone, ponieważ nie masz uprawnień do dodawania podkatalogów w tym katalogu - + Not allowed to upload this file because it is read-only on the server, restoring Wgrywanie niedozwolone, ponieważ plik jest tylko do odczytu na serwerze, przywracanie - - + + Not allowed to remove, restoring Brak uprawnień by usunąć, przywracanie - + Local files and share folder removed. Lokalne pliki i udostępniane foldery zostały usunięte. - + Move not allowed, item restored Przenoszenie niedozwolone, obiekt przywrócony - + Move not allowed because %1 is read-only Przenoszenie niedozwolone, ponieważ %1 jest tylko do odczytu - + the destination docelowy - + the source źródło @@ -3451,7 +3451,7 @@ Niezalecane jest jego użycie. OCC::ownCloudTheme - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> + <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> @@ -3491,7 +3491,7 @@ Niezalecane jest jego użycie. Ask for confirmation before synchroni&zing folders larger than - + Zapytaj o potwierdzenie przed synchroni&zowaniem folderów większych niż @@ -3628,7 +3628,7 @@ Niezalecane jest jego użycie. Ser&ver Address - + Adres ser&wera @@ -3671,7 +3671,7 @@ Kliknij QT_LAYOUT_DIRECTION - + QT_LAYOUT_DIRECTION @@ -3768,7 +3768,7 @@ Kliknij %n second(s) - + %n sekunda%n sekundy%n sekund%n sekund @@ -3817,7 +3817,7 @@ Kliknij Server version downloaded, copied changed local file into conflict file - + Pobrano wersję z serwera, zmienona wersja lokalna została skopiowana do pliku konfliktu diff --git a/translations/client_pt.ts b/translations/client_pt.ts index 5bdf1dd69a..d5f5bb6436 100644 --- a/translations/client_pt.ts +++ b/translations/client_pt.ts @@ -2996,23 +2996,23 @@ Não é aconselhada a sua utilização. Erro ao ler o ficheiro. - + File/Folder is ignored because it's hidden. O ficheiro/pasta foi ignorado porque está oculto. - + Only %1 are available, need at least %2 to start Placeholders are postfixed with file sizes using Utility::octetsToString() Apenas %1 estão disponíveis, é preciso um mínimo de %2 para começar - + Not allowed because you don't have permission to add parent folder Não permitido, porque não tem permissão para adicionar a pasta fonte - + Not allowed because you don't have permission to add files in that folder Não permitido, porque não tem permissão para adicionar os ficheiros nessa pasta @@ -3072,124 +3072,124 @@ Não é aconselhada a sua utilização. O item não está sincronizado devido a erros anteriores: %1 - + Symbolic links are not supported in syncing. Hiperligações simbólicas não são suportadas em sincronização. - + File is listed on the ignore list. O ficheiro está na lista de ficheiros a ignorar. - + File names ending with a period are not supported on this file system. Nomes de ficheiros acabados com um ponto final não são suportados neste sistema de ficheiros. - + File names containing the character '%1' are not supported on this file system. Nomes de ficheiros que contêm o caractér '%1' não são suportados neste sistema de ficheiros. - + The file name is a reserved name on this file system. O nome de ficheiro é um nome reservado neste sistema de ficheiros. - + Filename contains trailing spaces. Nome de ficheiro contém espaços em branco seguidos. - + Filename is too long. O nome do ficheiro é muito grande - + Stat failed. Estado falhou. - + Filename encoding is not valid Codificação de nome de ficheiro não é válida - + Invalid characters, please rename "%1" Carateres inválidos, por favor, renomeie "%1" - + Unable to initialize a sync journal. Impossível inicializar sincronização 'journal'. - + Unable to read the blacklist from the local database Não foi possível ler a lista negra a partir da base de dados local - + Unable to read from the sync journal. Não foi possível ler a partir do jornal de sincronização. - + Cannot open the sync journal Impossível abrir o jornal de sincronismo - + File name contains at least one invalid character O nome de ficheiro contém pelo menos um caráter inválido - - + + Ignored because of the "choose what to sync" blacklist Ignorado devido à blacklist de escolha para sincronização - + Not allowed because you don't have permission to add subfolders to that folder Não permitido, porque não tem permissão para adicionar as subpastas nessa pasta - + Not allowed to upload this file because it is read-only on the server, restoring Não é permitido enviar este ficheiro porque este é só de leitura no servidor, a restaurar - - + + Not allowed to remove, restoring Não autorizado para remoção, restaurando - + Local files and share folder removed. Ficheiros locais e pasta partilhada removidos. - + Move not allowed, item restored Mover não foi permitido, item restaurado - + Move not allowed because %1 is read-only Mover não foi autorizado porque %1 é só de leitura - + the destination o destino - + the source a origem @@ -3458,8 +3458,8 @@ Não é aconselhada a sua utilização. OCC::ownCloudTheme - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> - <p>Versão %2. Para mais informação visite <a href="%3">https://%4</a></p><p>Para problemas conhecidos e melhoramento, por favor visite: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>Por Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, entre outros.</small></p><p>Direitos autorais ownCloud GmbH</p><p>Licenciado sob a GNU General Public License (GPL) Versão 2.0<br/>ownCloud e o logótipo ownCloud são marcas registadas de ownCloud GmbH nos Estados Unidos, outros países, ou ambos.</p> + <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> + <p>Versão %2. Para mais informação visite <a href="%3">https://%4</a></p><p>Para problemas conhecidos e ajuda, por favor, visite: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>por Por Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, e outros.</small></p><p>Direitos de Autor - ownCloud GmbH</p><p>Licenciado sob a Licença Pública Geral GNU (GPL), versão 2.0<br/>ownCloud e o logótipo ownCloud são marcas registadas de ownCloud GmbH nos Estados Unidos, outros países, ou ambos.</p> diff --git a/translations/client_pt_BR.ts b/translations/client_pt_BR.ts index 9bb8a78516..4e51114e10 100644 --- a/translations/client_pt_BR.ts +++ b/translations/client_pt_BR.ts @@ -2993,23 +2993,23 @@ It is not advisable to use it. Erro ao ler pasta. - + File/Folder is ignored because it's hidden. Arquivo/pasta ignorado porque porque está escondido. - + Only %1 are available, need at least %2 to start Placeholders are postfixed with file sizes using Utility::octetsToString() Apenas %1 estão disponíveis, precisamos de pelo menos %2 para começar - + Not allowed because you don't have permission to add parent folder Não permitido porque você não tem permissão para adicionar pasta mãe - + Not allowed because you don't have permission to add files in that folder Não permitido porque você não tem permissão para adicionar arquivos na pasta @@ -3069,124 +3069,124 @@ It is not advisable to use it. O item não está sincronizado devido a erros anteriores: %1 - + Symbolic links are not supported in syncing. Linques simbólicos não são suportados em sincronização. - + File is listed on the ignore list. O arquivo está listado na lista de ignorados. - + File names ending with a period are not supported on this file system. Os nomes de arquivos que terminam com um ponto não são suportados neste sistema de arquivos. - + File names containing the character '%1' are not supported on this file system. Os nomes de arquivos que contêm o caractere '%1' não são suportados neste sistema de arquivos. - + The file name is a reserved name on this file system. O nome do arquivo é um nome reservado neste sistema de arquivos. - + Filename contains trailing spaces. O nome do arquivo contém espaços deixados para trás. - + Filename is too long. O nome do arquivo é muito longo. - + Stat failed. Stat falhou. - + Filename encoding is not valid A codificação do nome do arquivo não é válida - + Invalid characters, please rename "%1" Caracteres inválidos, por favor renomear "%1" - + Unable to initialize a sync journal. Impossibilitado de iniciar a sincronização. - + Unable to read the blacklist from the local database Não é possível ler a lista negra a partir do banco de dados local - + Unable to read from the sync journal. Não é possível ler a partir do relatório de sincronização. - + Cannot open the sync journal Não é possível abrir o arquivo de sincronização - + File name contains at least one invalid character O nome do arquivo contem pelo menos um caractere inválido - - + + Ignored because of the "choose what to sync" blacklist Ignorado por causa da lista negra "escolher o que sincronizar" - + Not allowed because you don't have permission to add subfolders to that folder Não permitido porque você não tem permissão para adicionar subpastas para essa pasta - + Not allowed to upload this file because it is read-only on the server, restoring Não é permitido fazer o upload deste arquivo porque ele é somente leitura no servidor, restaurando - - + + Not allowed to remove, restoring Não é permitido remover, restaurando - + Local files and share folder removed. Arquivos locais e pasta compartilhada removida. - + Move not allowed, item restored Não é permitido mover, item restaurado - + Move not allowed because %1 is read-only Não é permitido mover porque %1 é somente para leitura - + the destination o destino - + the source a fonte @@ -3455,9 +3455,8 @@ It is not advisable to use it. OCC::ownCloudTheme - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> - <p>Versão %2. Para mais informações visite <a href="%3">https://%4</a></p><p> -Para problemas conhecidos e ajuda, visite: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>Por Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, e outros.</small></p><p>Copyright ownCloud GmbH</p><p>Licenciado sob o GNU General Public License (GPL) Versão 2.0<br/>ownCloud e o ownCloud Logo são marcas registradas da ownCloud GmbH nos United States, outros países, ou ambos.</p> + <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> + <p>Versão %2. Para mais informações visite <a href="%3">https://%4</a></p><p>Para saber sobre problemas e ajuda, por favor visite: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>Por Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, e outros.</small></p><p>Direitos Autorais ownCloud GmbH</p><p>Licenciado sob a GNU General Public License (GPL) Versão 2.0<br/>ownCloud e o Logo ownCloud são marcas registradas da ownCloud GmbH nos United States, e outros países, ou ambos.</p> diff --git a/translations/client_ru.ts b/translations/client_ru.ts index 75d0152827..dff0342c98 100644 --- a/translations/client_ru.ts +++ b/translations/client_ru.ts @@ -2993,23 +2993,23 @@ It is not advisable to use it. Произошла ошибка во время чтения папки. - + File/Folder is ignored because it's hidden. Файл/папка проигнорированы, так как являются скрытыми. - + Only %1 are available, need at least %2 to start Placeholders are postfixed with file sizes using Utility::octetsToString() Только %1 доступно, нужно как минимум %2 чтобы начать - + Not allowed because you don't have permission to add parent folder Не разрешается, так как у вас нет полномочий на добавление родительской папки - + Not allowed because you don't have permission to add files in that folder Не разрешается, так как у вас нет полномочий на добавление файлов в эту папку @@ -3069,124 +3069,124 @@ It is not advisable to use it. Элемент не синхронизируется из-за произошедших ошибок: %1 - + Symbolic links are not supported in syncing. Синхронизация символических ссылок не поддерживается. - + File is listed on the ignore list. Файл присутствует в списке игнорируемых. - + File names ending with a period are not supported on this file system. Эта файловая система не поддерживает имена файлов, оканчивающиеся на точку. - + File names containing the character '%1' are not supported on this file system. Эта файловая система не поддерживает имена файлов, содержащие символ '%1'. - + The file name is a reserved name on this file system. Данное имя файла зарезервировано в данной файловой системе. - + Filename contains trailing spaces. Имя файла содержит пробелы на конце. - + Filename is too long. Имя файла слишком длинное. - + Stat failed. Не удалось загрузить статистику. - + Filename encoding is not valid Кодировка имени файла не верна - + Invalid characters, please rename "%1" Недопустимые символы, пожалуйста, переименуйте "%1" - + Unable to initialize a sync journal. Невозможно инициализировать журнал синхронизации. - + Unable to read the blacklist from the local database Не удалось прочитать файл чёрного списка из локальной базы данных. - + Unable to read from the sync journal. Не удалось прочитать из журнала синхронизации. - + Cannot open the sync journal Не удаётся открыть журнал синхронизации - + File name contains at least one invalid character Имя файла содержит по крайней мере один некорректный символ - - + + Ignored because of the "choose what to sync" blacklist Игнорируется из-за черного списка в "что синхронизировать" - + Not allowed because you don't have permission to add subfolders to that folder Не разрешается, так как у вас нет полномочий на добавление подпапок в папку. - + Not allowed to upload this file because it is read-only on the server, restoring Не допускается загрузка этого файла, так как на сервере он помечен только для чтения, восстанавливаем - - + + Not allowed to remove, restoring Не допускается удаление, восстанавливаем - + Local files and share folder removed. Локальные файлы и общий каталог удалены. - + Move not allowed, item restored Перемещение не допускается, элемент восстановлен - + Move not allowed because %1 is read-only Перемещение не допускается, поскольку %1 помечен только для чтения - + the destination назначение - + the source источник @@ -3455,8 +3455,8 @@ It is not advisable to use it. OCC::ownCloudTheme - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> - <p>Версия %2. За известными проблемами и помощью обращайтесь по ссылке <a href="%3">https://%4</a></p><p><small>Авторы: Клаас Фрейтаг, Дэниель Молкентен, Оливье Гоффар, Маркус Гётц, Жан-Кристоф Бошар, и другие.</small></p><p>Авторские права принадлежат ownCloud GmbH</p><p>Лицензировано под GNU General Public License (GPL) версии 2.0<br/>ownCloud и логотип ownCloud — зарегистрированные торговые марки ownCloud GmbH в Соединённых Штатах, и/или других странах.</p> + <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> + <p>Версия %2. Для более подробной информации посетите <a href="%3">https://%4</a></p><p>По поводу известных проблем и помощи, пожалуйста посетите: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>Авторы: Клаас Фрейтаг, Дэниель Молкентен, Оливье Гоффар, Маркус Гётц, Жан-Кристоф Бошар, и другие.</small></p><p>Авторские права принадлежат ownCloud GmbH</p><p>Лицензировано под GNU General Public License (GPL) версии 2.0<br/>ownCloud и логотип ownCloud — зарегистрированные торговые марки ownCloud GmbH в Соединённых Штатах, и/или других странах.</p> diff --git a/translations/client_sk.ts b/translations/client_sk.ts index e27219054b..bd19c7aacd 100644 --- a/translations/client_sk.ts +++ b/translations/client_sk.ts @@ -2985,23 +2985,23 @@ Nie je vhodné ju používať. Chyba pri čítaní adresára - + File/Folder is ignored because it's hidden. - + Only %1 are available, need at least %2 to start Placeholders are postfixed with file sizes using Utility::octetsToString() - + Not allowed because you don't have permission to add parent folder - + Not allowed because you don't have permission to add files in that folder @@ -3061,124 +3061,124 @@ Nie je vhodné ju používať. Položka nebola synchronizovaná kvôli predchádzajúcej chybe: %1 - + Symbolic links are not supported in syncing. Symbolické odkazy nie sú podporované pri synchronizácii. - + File is listed on the ignore list. Súbor je zapísaný na zozname ignorovaných. - + File names ending with a period are not supported on this file system. - + File names containing the character '%1' are not supported on this file system. - + The file name is a reserved name on this file system. - + Filename contains trailing spaces. - + Filename is too long. Meno súboru je veľmi dlhé. - + Stat failed. - + Filename encoding is not valid Kódovanie znakov názvu súboru je neplatné - + Invalid characters, please rename "%1" Neplatné znaky, premenujte prosím "%1" - + Unable to initialize a sync journal. Nemôžem inicializovať synchronizačný žurnál. - + Unable to read the blacklist from the local database - + Unable to read from the sync journal. Nemožno čítať zo synchronizačného žurnálu - + Cannot open the sync journal Nemožno otvoriť sync žurnál - + File name contains at least one invalid character Názov súboru obsahuje nevhodný znak - - + + Ignored because of the "choose what to sync" blacklist Ignorované podľa nastavenia "vybrať čo synchronizovať" - + Not allowed because you don't have permission to add subfolders to that folder Nie je dovolené, lebo nemáte oprávnenie pridávať podpriečinky do tohto priečinka - + Not allowed to upload this file because it is read-only on the server, restoring Nie je dovolené tento súbor nahrať, pretože je na serveri iba na čítanie. Obnovuje sa. - - + + Not allowed to remove, restoring Nie je dovolené odstrániť. Obnovuje sa. - + Local files and share folder removed. Lokálne súbory a zdieľaný priečinok boli odstránené. - + Move not allowed, item restored Presunutie nie je dovolené. Položka obnovená. - + Move not allowed because %1 is read-only Presunutie nie je dovolené, pretože %1 je na serveri iba na čítanie - + the destination cieľ - + the source zdroj @@ -3447,7 +3447,7 @@ Nie je vhodné ju používať. OCC::ownCloudTheme - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> + <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> diff --git a/translations/client_sl.ts b/translations/client_sl.ts index eaec8fcec7..ceac569bab 100644 --- a/translations/client_sl.ts +++ b/translations/client_sl.ts @@ -2996,23 +2996,23 @@ Uporaba ni priporočljiva. Napaka med branjem mape - + File/Folder is ignored because it's hidden. Datoteka/Mapa je prezrta, ker je skrita. - + Only %1 are available, need at least %2 to start Placeholders are postfixed with file sizes using Utility::octetsToString() Le %1 je na voljo, zahtevanih pa je vaj %2 za zagon - + Not allowed because you don't have permission to add parent folder Dejanje ni dovoljeno, ker ni ustreznih dovoljenj za dodajanje starševske mape - + Not allowed because you don't have permission to add files in that folder Dejanje ni dovoljeno, ker ni ustreznih dovoljenj za dodajanje datotek v to mapo @@ -3072,124 +3072,124 @@ Uporaba ni priporočljiva. Predmet ni usklajen zaradi predhodne napake: %1 - + Symbolic links are not supported in syncing. Usklajevanje simbolnih povezav ni podprto. - + File is listed on the ignore list. Datoteka je na seznamu prezrtih datotek. - + File names ending with a period are not supported on this file system. Imena datotek, ki vsebujejo piko, na tem sistemu niso podprta. - + File names containing the character '%1' are not supported on this file system. Imena datotek, ki vsebujejo znak »%1«, na tem sistemu niso podprta. - + The file name is a reserved name on this file system. Ime datoteke je na tem sistemu zadržano za sistemsko datoteko. - + Filename contains trailing spaces. Datoteka vsebuje pripete presledne znake - + Filename is too long. Ime datoteke je predolgo. - + Stat failed. Določanje statističnih podatkov je spodletelo. - + Filename encoding is not valid Kodni zapis imena datoteke ni veljaven. - + Invalid characters, please rename "%1" Uporabljen je neveljaven znak; preimenujte "%1" - + Unable to initialize a sync journal. Dnevnika usklajevanja ni mogoče začeti. - + Unable to read the blacklist from the local database Ni mogoče prebrati črnega seznama iz krajevne mape - + Unable to read from the sync journal. Ni mogoče brati iz dnevnika usklajevanja - + Cannot open the sync journal Ni mogoče odpreti dnevnika usklajevanja - + File name contains at least one invalid character Ime datoteke vsebuje vsaj en neveljaven znak. - - + + Ignored because of the "choose what to sync" blacklist Prezrto, ker je predmet označen na črni listi za usklajevanje - + Not allowed because you don't have permission to add subfolders to that folder Dejanje ni dovoljeno! Ni ustreznih dovoljenj za dodajanje podmap v to mapo. - + Not allowed to upload this file because it is read-only on the server, restoring Ni dovoljeno pošiljati te datoteke, ker ima določena dovoljenja le za branje. Datoteka bo obnovljena na izvorno različico. - - + + Not allowed to remove, restoring Odstranitev ni dovoljena, datoteka bo obnovljena. - + Local files and share folder removed. Krajevne datoteke in mape v souporabi so odstranjene. - + Move not allowed, item restored Premikanje ni dovoljeno, datoteka bo obnovljena. - + Move not allowed because %1 is read-only Premikanje ni dovoljeno, ker je nastavljeno določilo %1 le za branje. - + the destination cilj - + the source vir @@ -3458,8 +3458,8 @@ Uporaba ni priporočljiva. OCC::ownCloudTheme - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> - <p>Različica %2. Več podrobnosti je mogoče najti na <a href="%3">https://%4</a></p><p>Znane težave in pomoč je na voljo na povezavi <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a>.</p><p><small>Avtorstvo: Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt in drugi.</small></p><p>Avtorske pravice ownCloud GmbH</p><p>Programski paket je objavljen z dovoljenjem GNU General Public License (GPL), različice 2.0.<br/>Znamka in logotip ownCloud sta blagovni znamki družbe ownCloud GmbH v Združenih državah, drugih državah ali obojih.</p> + <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> + diff --git a/translations/client_sr.ts b/translations/client_sr.ts index 6ab459366c..d448d82386 100644 --- a/translations/client_sr.ts +++ b/translations/client_sr.ts @@ -2985,23 +2985,23 @@ It is not advisable to use it. - + File/Folder is ignored because it's hidden. - + Only %1 are available, need at least %2 to start Placeholders are postfixed with file sizes using Utility::octetsToString() - + Not allowed because you don't have permission to add parent folder - + Not allowed because you don't have permission to add files in that folder @@ -3061,124 +3061,124 @@ It is not advisable to use it. Ставка није синхронизована због ранијих грешака: %1 - + Symbolic links are not supported in syncing. Симболичке везе нису подржане у синхронизацији. - + File is listed on the ignore list. Фајл се налази на листи за игнорисање. - + File names ending with a period are not supported on this file system. - + File names containing the character '%1' are not supported on this file system. - + The file name is a reserved name on this file system. - + Filename contains trailing spaces. - + Filename is too long. Назив фајла је предугачак. - + Stat failed. - + Filename encoding is not valid Кодирање назива фајла није исправно - + Invalid characters, please rename "%1" - + Unable to initialize a sync journal. Није могуће покренути у синхронизацију дневника. - + Unable to read the blacklist from the local database - + Unable to read from the sync journal. - + Cannot open the sync journal Не могу да отворим дневник синхронизације - + File name contains at least one invalid character Назив садржи бар један недозвољен карактер - - + + Ignored because of the "choose what to sync" blacklist Игнорисано јер се не налази на листи за синхронизацију - + Not allowed because you don't have permission to add subfolders to that folder - + Not allowed to upload this file because it is read-only on the server, restoring Није могуће отпремити овај фајл јер је на серверу само за читање. Враћам - - + + Not allowed to remove, restoring Није могуће уклањање. Враћам - + Local files and share folder removed. Локални фајлови и дељена фасцикла су уклоњени. - + Move not allowed, item restored Премештање није дозвољено. Ставка је враћена - + Move not allowed because %1 is read-only Премештање није дозвољено јер %1 је само за читање - + the destination одредиште - + the source извор @@ -3447,7 +3447,7 @@ It is not advisable to use it. OCC::ownCloudTheme - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> + <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> diff --git a/translations/client_sv.ts b/translations/client_sv.ts index 804934345f..570a5b8eb4 100644 --- a/translations/client_sv.ts +++ b/translations/client_sv.ts @@ -163,12 +163,12 @@ Force sync now - + Tvinga synkning Restart sync - + Starta om synkning @@ -508,7 +508,7 @@ Certificate & Key (pkcs12) : - + Certifikat och nyckel (pkcs12) : @@ -536,7 +536,7 @@ Error accessing the configuration file - + Kunde inte komma åt konfigurationsfilen @@ -546,7 +546,7 @@ Quit ownCloud - + Avsluta ownCloud @@ -1833,7 +1833,7 @@ Det är inte lämpligt använda den. Invalid URL - + Ogiltig URL @@ -2222,12 +2222,12 @@ Det är inte lämpligt använda den. Missing File ID from server - + Saknar Fil-ID från servern Missing ETag from server - + Saknar ETag från servern @@ -2550,7 +2550,7 @@ Det är inte lämpligt använda den. Anyone with the link has access to the file/folder - + Vem som helst med länken kan komma åt filen eller mappen @@ -2989,23 +2989,23 @@ Det är inte lämpligt använda den. Fel vid mappinläsning. - + File/Folder is ignored because it's hidden. Filen/Mappen är ignorerad för att den är dold. - + Only %1 are available, need at least %2 to start Placeholders are postfixed with file sizes using Utility::octetsToString() Endast %1 tillgängligt, behöver minst %2 för att starta - + Not allowed because you don't have permission to add parent folder Otillåtet eftersom du inte har rättigheter att lägga till övermappar - + Not allowed because you don't have permission to add files in that folder Otillåtet eftersom du inte har rättigheter att lägga till filer i den mappen. @@ -3065,124 +3065,124 @@ Det är inte lämpligt använda den. Objektet kunde inte synkas på grund av tidigare fel: %1 - + Symbolic links are not supported in syncing. Symboliska länkar stöds ej i synkningen. - + File is listed on the ignore list. Filen är listad i ignorerings listan. - + File names ending with a period are not supported on this file system. - + File names containing the character '%1' are not supported on this file system. - + The file name is a reserved name on this file system. - + Filename contains trailing spaces. Filnamn innehåller mellanslag i slutet. - + Filename is too long. Filnamnet är för långt. - + Stat failed. Stat misslyckades. - + Filename encoding is not valid Filnamnskodning är inte giltig - + Invalid characters, please rename "%1" Otillåtna tecken, var vänlig byt namn på "%1" - + Unable to initialize a sync journal. Kan inte initialisera en synk journal. - + Unable to read the blacklist from the local database Kunde inte läsa svartlistan från den lokala databasen - + Unable to read from the sync journal. Kunde inte läsa från synk-journalen. - + Cannot open the sync journal Kunde inte öppna synk journalen - + File name contains at least one invalid character Filnamnet innehåller minst ett ogiltigt tecken - - + + Ignored because of the "choose what to sync" blacklist Ignorerad eftersom den är svartlistad i "välj vad som ska synkas" - + Not allowed because you don't have permission to add subfolders to that folder Otillåtet eftersom du inte har rättigheter att lägga till undermappar i den mappen. - + Not allowed to upload this file because it is read-only on the server, restoring Inte behörig att ladda upp denna fil då den är skrivskyddad på servern, återställer - - + + Not allowed to remove, restoring Inte behörig att radera, återställer - + Local files and share folder removed. Lokala filer och mappar som är delade är borttagna. - + Move not allowed, item restored Det gick inte att genomföra flytten, objektet återställs - + Move not allowed because %1 is read-only Det gick inte att genomföra flytten då %1 är skrivskyddad - + the destination destinationen - + the source källan @@ -3403,7 +3403,7 @@ Det är inte lämpligt använda den. New account... - + Nytt konto... @@ -3451,7 +3451,7 @@ Det är inte lämpligt använda den. OCC::ownCloudTheme - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> + <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> @@ -3628,7 +3628,7 @@ Det är inte lämpligt använda den. Ser&ver Address - + Ser&veradress @@ -3798,7 +3798,7 @@ Det är inte lämpligt använda den. built with %1 - + byggt med %1 diff --git a/translations/client_th.ts b/translations/client_th.ts index 376816964a..ff11aa8071 100644 --- a/translations/client_th.ts +++ b/translations/client_th.ts @@ -2995,23 +2995,23 @@ It is not advisable to use it. เกิดข้อผิดพลาดขณะกำลังอ่านโฟลเดอร์ - + File/Folder is ignored because it's hidden. ไฟล์/โฟลเดอร์ ที่ซ่อนอยู่จะถูกละเว้น - + Only %1 are available, need at least %2 to start Placeholders are postfixed with file sizes using Utility::octetsToString() มีเพียง %1 ที่พร้อมใช้งาน คุณจำเป็นต้องมีไม่น้อยกว่า %2 เพื่อเริ่มใช้งาน - + Not allowed because you don't have permission to add parent folder ไม่ได้รับอนุญาต เพราะคุณไม่มีสิทธิ์ที่จะเพิ่มโฟลเดอร์หลัก - + Not allowed because you don't have permission to add files in that folder ไม่ได้รับอนุญาต เพราะคุณไม่มีสิทธิ์ที่จะเพิ่มไฟล์ในโฟลเดอร์นั้น @@ -3071,124 +3071,124 @@ It is not advisable to use it. รายการจะไม่ถูกประสานข้อมูลเนื่องจากเกิดข้อผิดพลาดก่อนหน้านี้: %1 - + Symbolic links are not supported in syncing. ลิงค์สัญลักษณ์จะไม่ได้รับการสนับสนุนในการประสานข้อมูล - + File is listed on the ignore list. ไฟล์อยู่ในรายการที่ละเว้น - + File names ending with a period are not supported on this file system. ชื่อไฟล์ที่ลงท้ายด้วยระยะเวลา ยังไม่ได้รับการสนับสนุนบนระบบไฟล์นี้ - + File names containing the character '%1' are not supported on this file system. ชื่อไฟล์ที่มีตัวอักษร '%1' ยังไม่ได้รับการสนับสนุนบนระบบไฟล์นี้ - + The file name is a reserved name on this file system. ชื่อไฟล์นี้เป็นชื่อที่ถูกสงวนไว้ - + Filename contains trailing spaces. ชื่อไฟล์มีช่องว่างต่อท้าย - + Filename is too long. ชื่อไฟล์ยาวเกินไป - + Stat failed. สถิติความล้มเหลว - + Filename encoding is not valid การเข้ารหัสชื่อไฟล์ไม่ถูกต้อง - + Invalid characters, please rename "%1" ตัวอักษรไม่ถูกต้อง โปรดเปลี่ยนชื่อ "%1" - + Unable to initialize a sync journal. ไม่สามารถเตรียมการประสานข้อมูลเจอร์นัล - + Unable to read the blacklist from the local database ไม่สามารถอ่านบัญชีดำจากฐานข้อมูลต้นทาง - + Unable to read from the sync journal. ไม่สามารถอ่านจากบันทึกการประสานข้อมูล - + Cannot open the sync journal ไม่สามารถเปิดการผสานข้อมูลเจอร์นัล - + File name contains at least one invalid character มีชื่อแฟ้มอย่างน้อยหนึ่งตัวอักษรที่ไม่ถูกต้อง - - + + Ignored because of the "choose what to sync" blacklist ถูกละเว้นเพราะ "ข้อมูลที่เลือกประสาน" ติดบัญชีดำ - + Not allowed because you don't have permission to add subfolders to that folder ไม่อนุญาติเพราะคุณไม่มีสิทธิ์ที่จะเพิ่มโฟลเดอร์ย่อยของโฟลเดอร์นั้น - + Not allowed to upload this file because it is read-only on the server, restoring ไม่อนุญาตให้อัพโหลดไฟล์นี้เพราะมันจะอ่านได้เพียงอย่างเดียวบนเซิร์ฟเวอร์ กำลังฟื้นฟู - - + + Not allowed to remove, restoring ไม่อนุญาตให้ลบเพราะกำลังฟื้นฟู - + Local files and share folder removed. ไฟล์ต้นทางและโฟลเดอร์ที่แชร์ถูกลบออก - + Move not allowed, item restored ไม่ได้รับอนุญาตให้ย้าย เพราะกำลังกู้คืนรายการ - + Move not allowed because %1 is read-only ไม่อนุญาตให้ย้ายเพราะ %1 จะอ่านได้เพียงอย่างเดียว - + the destination ปลายทาง - + the source แหล่งที่มา @@ -3457,8 +3457,8 @@ It is not advisable to use it. OCC::ownCloudTheme - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> - <p>เวอร์ชัน %2 สำหรับข้อมูลเพิ่มเติมสามารถอ่านได้ที่ <a href="%3">https://%4</a></p><p>สำหรับปัญหาที่พบ, และอ่านข้อมูลเพิ่มเติมที่: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>โดย Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt และคนอื่นๆ</small></p><p>ลิขสิทธิ์ ownCloud GmbH</p><p>ภายใต้สัญญาอนุญาต GNU General Public (GPL) เวอร์ชั่น 2.0 <br/> ownCloud และโลโก้ ownCloud เป็นเครื่องหมายการค้าจดทะเบียนของ ownCloud GmbH ในสหรัฐอเมริกาและประเทศอื่นๆ</p> + <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> + <p>รุ่น %2 สำหรับข้อมูลเพิ่มเดิมไปดูได้ที่ <a href="%3">https://%4</a></p><p> หรือแจ้งปัญหาที่ทราบไปที่: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>โดย Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt และอีกหลายท่าน</small></p><p>ลิขสิทธิ์ ownCloud GmbH</p><p>ได้รับอนุญาตภายใต้ GNU General Public License (GPL) เวอร์ชัน 2.0<br/>ownCloud และโลโก้ OwnCloud เป็นเครื่องหมายจดทะเบียนการค้าของ ownCloud GmbH ในประเทศสหรัฐอเมริกาหรือประเทศอื่นๆ</p> diff --git a/translations/client_tr.ts b/translations/client_tr.ts index 99944a8804..f053594e10 100644 --- a/translations/client_tr.ts +++ b/translations/client_tr.ts @@ -2986,23 +2986,23 @@ Kullanmanız önerilmez. Klasör okunurken hata oluştu. - + File/Folder is ignored because it's hidden. Dosya/Klasör gizli olduğu için yoksayıldı. - + Only %1 are available, need at least %2 to start Placeholders are postfixed with file sizes using Utility::octetsToString() Sadece %1 mevcut, Çalıştırmak için en az %2 gerekmektedir - + Not allowed because you don't have permission to add parent folder Üst dizin ekleme yetkiniz olmadığından izin verilmedi - + Not allowed because you don't have permission to add files in that folder Bu klasöre dosya ekleme yetkiniz olmadığından izin verilmedi @@ -3062,124 +3062,124 @@ Kullanmanız önerilmez. Bu öge, önceki hatalardan dolayı eşitlenemiyor: %1 - + Symbolic links are not supported in syncing. Sembolik bağlantılar eşitlemede desteklenmiyor. - + File is listed on the ignore list. Dosya yoksayma listesinde. - + File names ending with a period are not supported on this file system. - + File names containing the character '%1' are not supported on this file system. - + The file name is a reserved name on this file system. Dosya adı bu dosya sisteminde ayrılmış bir addır. - + Filename contains trailing spaces. - + Filename is too long. Dosya adı çok uzun. - + Stat failed. Durum alma başarısız. - + Filename encoding is not valid Dosya adı kodlaması geçerli değil - + Invalid characters, please rename "%1" Geçersiz karakterler, lütfen "%1" yerine yeni bir isim girin - + Unable to initialize a sync journal. Bir eşitleme günlüğü başlatılamadı. - + Unable to read the blacklist from the local database Yerel veritabanından kara liste okunamadı - + Unable to read from the sync journal. Eşitleme günlüğünden okunamadı. - + Cannot open the sync journal Eşitleme günlüğü açılamıyor - + File name contains at least one invalid character Dosya adı en az bir geçersiz karakter içeriyor - - + + Ignored because of the "choose what to sync" blacklist "Eşitlenecekleri seçin" kara listesinde olduğundan yoksayıldı. - + Not allowed because you don't have permission to add subfolders to that folder Bu dizine alt dizin ekleme yetkiniz olmadığından izin verilmedi - + Not allowed to upload this file because it is read-only on the server, restoring Sunucuda salt okunur olduğundan, bu dosya yüklenemedi, geri alınıyor - - + + Not allowed to remove, restoring Kaldırmaya izin verilmedi, geri alınıyor - + Local files and share folder removed. Yerel dosyalar ve paylaşım klasörü kaldırıldı. - + Move not allowed, item restored Taşımaya izin verilmedi, öge geri alındı - + Move not allowed because %1 is read-only %1 salt okunur olduğundan taşımaya izin verilmedi - + the destination hedef - + the source kaynak @@ -3448,7 +3448,7 @@ Kullanmanız önerilmez. OCC::ownCloudTheme - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> + <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> diff --git a/translations/client_uk.ts b/translations/client_uk.ts index 5f4a399af1..66f2561d8c 100644 --- a/translations/client_uk.ts +++ b/translations/client_uk.ts @@ -2984,23 +2984,23 @@ It is not advisable to use it. - + File/Folder is ignored because it's hidden. - + Only %1 are available, need at least %2 to start Placeholders are postfixed with file sizes using Utility::octetsToString() Доступно лише %1, для початку необхідно хоча б %2 - + Not allowed because you don't have permission to add parent folder - + Not allowed because you don't have permission to add files in that folder @@ -3060,124 +3060,124 @@ It is not advisable to use it. Шлях не синхронізується через помилки: %1 - + Symbolic links are not supported in syncing. Синхронізація символічних посилань не підтримується. - + File is listed on the ignore list. Файл присутній у списку ігнорованих. - + File names ending with a period are not supported on this file system. - + File names containing the character '%1' are not supported on this file system. - + The file name is a reserved name on this file system. - + Filename contains trailing spaces. - + Filename is too long. Шлях до файлу занадто довгий. - + Stat failed. - + Filename encoding is not valid Кодування файлу не припустиме - + Invalid characters, please rename "%1" - + Unable to initialize a sync journal. Не вдалося ініціалізувати протокол синхронізації. - + Unable to read the blacklist from the local database - + Unable to read from the sync journal. - + Cannot open the sync journal Не вдається відкрити протокол синхронізації - + File name contains at least one invalid character Ім’я файлу містить принаймні один некоректний символ - - + + Ignored because of the "choose what to sync" blacklist Ігнорується через чорний список в "обрати що синхронізувати" - + Not allowed because you don't have permission to add subfolders to that folder Заборонено через відсутність прав додавання підкаталогів в цю теку. - + Not allowed to upload this file because it is read-only on the server, restoring Не дозволено завантажувати цей файл, оскільки він має дозвіл лише на перегляд, відновлюємо - - + + Not allowed to remove, restoring Не дозволено видаляти, відновлюємо - + Local files and share folder removed. Локальні файли та теки в загальному доступі було видалено. - + Move not allowed, item restored Переміщення не дозволено, елемент відновлено - + Move not allowed because %1 is read-only Переміщення не дозволено, оскільки %1 помічений тільки для перегляду - + the destination призначення - + the source джерело @@ -3446,7 +3446,7 @@ It is not advisable to use it. OCC::ownCloudTheme - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> + <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> diff --git a/translations/client_zh_CN.ts b/translations/client_zh_CN.ts index 57fbe2b663..0f0ce5d3e3 100644 --- a/translations/client_zh_CN.ts +++ b/translations/client_zh_CN.ts @@ -2986,23 +2986,23 @@ It is not advisable to use it. 读取目录时出错 - + File/Folder is ignored because it's hidden. 已忽略隐藏的文件和文件夹。 - + Only %1 are available, need at least %2 to start Placeholders are postfixed with file sizes using Utility::octetsToString() 仅有 %1 有效,至少需要 %2 才能开始 - + Not allowed because you don't have permission to add parent folder 你没有权限增加父目录 - + Not allowed because you don't have permission to add files in that folder 你没有权限增加文件 @@ -3062,124 +3062,124 @@ It is not advisable to use it. 文件没有被同步因为之前的错误: %1 - + Symbolic links are not supported in syncing. 符号链接不被同步支持。 - + File is listed on the ignore list. 文件在忽略列表中。 - + File names ending with a period are not supported on this file system. - + File names containing the character '%1' are not supported on this file system. 此文件系统不支持包含字符 '%1' 的文件名。 - + The file name is a reserved name on this file system. - + Filename contains trailing spaces. 文件名尾部含有空格 - + Filename is too long. 文件名过长。 - + Stat failed. 状态失败。 - + Filename encoding is not valid 文件名编码无效 - + Invalid characters, please rename "%1" 无效的字符,请更改为 “%1” - + Unable to initialize a sync journal. 无法初始化同步日志 - + Unable to read the blacklist from the local database 无法从本地数据库读取黑名单 - + Unable to read from the sync journal. 无法读取同步日志。 - + Cannot open the sync journal 无法打开同步日志 - + File name contains at least one invalid character 文件名中存在至少一个非法字符 - - + + Ignored because of the "choose what to sync" blacklist 已忽略(“选择同步内容”黑名单) - + Not allowed because you don't have permission to add subfolders to that folder 你没有权限增加子目录 - + Not allowed to upload this file because it is read-only on the server, restoring 无法上传文件,因为服务器端此文件为只读,正在回退 - - + + Not allowed to remove, restoring 无法删除,正在回退 - + Local files and share folder removed. 本地文件和共享文件夹已被删除。 - + Move not allowed, item restored 无法移动,正在回退 - + Move not allowed because %1 is read-only 无法移动,%1为是只读的 - + the destination 目标 - + the source @@ -3448,7 +3448,7 @@ It is not advisable to use it. OCC::ownCloudTheme - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> + <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> diff --git a/translations/client_zh_TW.ts b/translations/client_zh_TW.ts index 460511e108..9513b38fb1 100644 --- a/translations/client_zh_TW.ts +++ b/translations/client_zh_TW.ts @@ -2987,23 +2987,23 @@ It is not advisable to use it. 讀取資料夾時發生錯誤。 - + File/Folder is ignored because it's hidden. - + Only %1 are available, need at least %2 to start Placeholders are postfixed with file sizes using Utility::octetsToString() 目前僅有 %1 可以使用,至少需要 %2 才能開始 - + Not allowed because you don't have permission to add parent folder 拒絕此操作,您沒有新增母資料夾的權限。 - + Not allowed because you don't have permission to add files in that folder 拒絕此操作,您沒有新增檔案在此資料夾的權限。 @@ -3063,124 +3063,124 @@ It is not advisable to use it. 因為先前的錯誤: %1 物件沒有同步成功 - + Symbolic links are not supported in syncing. 同步不支援捷徑連結 - + File is listed on the ignore list. 檔案被列在忽略清單。 - + File names ending with a period are not supported on this file system. - + File names containing the character '%1' are not supported on this file system. - + The file name is a reserved name on this file system. - + Filename contains trailing spaces. - + Filename is too long. 檔案名稱太長了。 - + Stat failed. 狀態失敗。 - + Filename encoding is not valid 檔案名稱編碼是無效的 - + Invalid characters, please rename "%1" 無效的字元,請您重新命名 "%1" - + Unable to initialize a sync journal. 同步處理日誌無法初始化 - + Unable to read the blacklist from the local database - + Unable to read from the sync journal. - + Cannot open the sync journal 同步處理日誌無法開啟 - + File name contains at least one invalid character 檔案名稱含有不合法的字元 - - + + Ignored because of the "choose what to sync" blacklist 已忽略。根據 "選擇要同步的項目"的黑名單 - + Not allowed because you don't have permission to add subfolders to that folder 拒絕此操作,您沒有在此新增子資料夾的權限。 - + Not allowed to upload this file because it is read-only on the server, restoring 拒絕上傳此檔案,此檔案在伺服器是唯讀檔,復原中 - - + + Not allowed to remove, restoring 不允許刪除,復原中 - + Local files and share folder removed. 本地端檔案和共享資料夾已被刪除。 - + Move not allowed, item restored 不允許移動,物件復原中 - + Move not allowed because %1 is read-only 不允許移動,因為 %1 是唯讀的 - + the destination 目標 - + the source 來源 @@ -3449,7 +3449,7 @@ It is not advisable to use it. OCC::ownCloudTheme - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p> + <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p>