pax_global_header00006660000000000000000000000064152152133000014502gustar00rootroot0000000000000052 comment=5077c41c4d9166236a102ec2fc2c98a5b346b611 lomiri-storage-framework-0.5.0/000077500000000000000000000000001521521330000164345ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/.gitignore000066400000000000000000000000001521521330000204120ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/AUTHORS000066400000000000000000000002211521521330000174770ustar00rootroot00000000000000Alfred Neumayer Gary Wzl Guido Berhoerster James Henstridge Marcus Tomlinson Marius Gripsgard Michi Henning Mike Gabriel Ratchanan Srirattanamet lomiri-storage-framework-0.5.0/CMakeLists.txt000066400000000000000000000140711521521330000211770ustar00rootroot00000000000000# Default install location. Must be set here, before setting the project. if (NOT DEFINED CMAKE_INSTALL_PREFIX) set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/install CACHE PATH "" FORCE) set(LOCAL_INSTALL "ON") endif() cmake_minimum_required(VERSION 3.25) project(lomiri-storage-framework VERSION "0.5.0" LANGUAGES C CXX) option(ENABLE_QT6 "Enable Qt6 build" OFF) # These variables should be incremented when we wish to create a new # source incompatible version of the library where users of the # old API will not compile against the new one. It is not # necessary to increment this for ABI breaks that are source compatible. if(ENABLE_QT6) set(LSF_CLIENT_API_VERSION "1") else() set(LSF_CLIENT_API_VERSION "2") endif() set(LSF_PROVIDER_API_VERSION "1") # These two should be incremented when the ABI changes. set(LSF_CLIENT_SOVERSION "0") set(LSF_PROVIDER_SOVERSION "1") set(LSF_CLIENT_LIBVERSION "${LSF_CLIENT_SOVERSION}.${PROJECT_VERSION}") set(LSF_PROVIDER_LIBVERSION "${LSF_PROVIDER_SOVERSION}.${PROJECT_VERSION}") string(TOLOWER "${CMAKE_BUILD_TYPE}" cmake_build_type_lower) # Build types should always be lower case set(ACCEPTED_BUILD_TYPES "" none release debug relwithdebinfo coverage) list(FIND ACCEPTED_BUILD_TYPES "${cmake_build_type_lower}" IS_BUILD_TYPE_ACCEPTED) if (${IS_BUILD_TYPE_ACCEPTED} EQUAL -1) message(FATAL_ERROR "Invalid CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}\nValid types are: ${ACCEPTED_BUILD_TYPES}") endif() # Ensure that generated files can be linked with. set(CMAKE_INCLUDE_CURRENT_DIR ON) include_directories(include) include_directories(${CMAKE_BINARY_DIR}/include) # For generated headers set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -Wextra") # Some additional warnings not included by the general flags set above. set(EXTRA_C_WARNINGS "-Wcast-align -Wcast-qual -Wformat -Wredundant-decls -Wswitch-default") set(EXTRA_CXX_WARNINGS "-Wnon-virtual-dtor -Wctor-dtor-privacy -Wold-style-cast") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${EXTRA_C_WARNINGS} ${EXTRA_CXX_WARNINGS}") # By default, for release builds, warnings become hard errors. if ("${cmake_build_type_lower}" STREQUAL "release" OR "${cmake_build_type_lower}" STREQUAL "relwithdebinfo") option(Werror "Treat warnings as errors" ON) else() option(Werror "Treat warnings as errors" OFF) endif() # If warnings are errors, don't error on deprecated declarations. if (${Werror}) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror") if ("${cmake_build_type_lower}" STREQUAL "release" OR "${cmake_build_type_lower}" STREQUAL "relwithdebinfo") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-error=deprecated-declarations") endif() endif() set(CMAKE_CXX_STANDARD 17 CACHE STRING "The C++ standard to use") set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_VISIBILITY_PRESET hidden) set(CMAKE_POSITION_INDEPENDENT_CODE TRUE) # Flags for address and undefined behavior sanitizer set(SANITIZER "" CACHE STRING "Build with -fsanitize= (legal values: address, ub)") if ("${SANITIZER}" STREQUAL "") # Do nothing elseif (${SANITIZER} STREQUAL "ub") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -fsanitize=float-divide-by-zero -fno-omit-frame-pointer -g") elseif (${SANITIZER} STREQUAL "address") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -g") else() message(FATAL_ERROR "Invalid SANITIZER setting: ${SANITIZER}") endif() # Some tests are slow, so make it possible not to run them # during day-to-day development. option(slowtests "Run slow tests" ON) if (${slowtests}) add_definitions(-DSLOW_TESTS=1) else() add_definitions(-DSLOW_TESTS=0) endif() # Definitions for testing with valgrind. configure_file(CTestCustom.cmake.in CTestCustom.cmake) # Tests in CTestCustom.cmake are skipped for valgrind find_program(MEMORYCHECK_COMMAND NAMES valgrind) if (MEMORYCHECK_COMMAND) set(MEMORYCHECK_COMMAND_OPTIONS "--suppressions=${CMAKE_SOURCE_DIR}/valgrind-suppress --errors-for-leak-kinds=definite --show-leak-kinds=definite --leak-check=full --num-callers=50 --error-exitcode=3" ) add_custom_target(valgrind DEPENDS NightlyMemCheck) else() message(WARNING "Cannot find valgrind: valgrind target will not be available") endif() include(CTest) enable_testing() find_package(CoverageReport) option(SNAP_BUILD "Build for snap release") if("${SNAP_BUILD}") add_definitions(-DSNAP_BUILD=1) endif() include(GNUInstallDirs) find_package(Boost 1.56 COMPONENTS filesystem system thread REQUIRED) if(ENABLE_QT6) find_package(Qt6 COMPONENTS Core Concurrent DBus Network Qml Test REQUIRED) qt_standard_project_setup() set(QT_VERSION_MAJOR 6) else() find_package(Qt5 COMPONENTS Core Concurrent DBus Network Qml Test REQUIRED) set(QT_VERSION_MAJOR 5) endif() include(FindPkgConfig) pkg_check_modules(APPARMOR_DEPS REQUIRED IMPORTED_TARGET libapparmor) pkg_check_modules(GIO_DEPS REQUIRED IMPORTED_TARGET gio-2.0 gio-unix-2.0) pkg_check_modules(GLIB_DEPS REQUIRED IMPORTED_TARGET glib-2.0) pkg_check_modules(LIBLOMIRI_API_DEPS REQUIRED IMPORTED_TARGET liblomiri-api) if(ENABLE_QT6) pkg_check_modules(ONLINEACCOUNTS_DEPS REQUIRED IMPORTED_TARGET LomiriOnlineAccountsQt6) else() pkg_check_modules(ONLINEACCOUNTS_DEPS REQUIRED IMPORTED_TARGET LomiriOnlineAccountsQt5) endif() add_definitions(-DQT_NO_KEYWORDS) add_subdirectory(include) add_subdirectory(src) add_subdirectory(plugins/Lomiri/StorageFramework) add_subdirectory(tests) add_subdirectory(demo) add_subdirectory(tools) enable_coverage_report( TARGETS local-provider-lib qt-client-lib-common lomiri-storage-framework-common-internal lomiri-storage-framework-qt-client lomiri-storage-framework-qt-client-v2 lomiri-storage-framework-qt-local-client lsf-provider-objects lomiri-storage-framework-provider registry-static lomiri-storage-framework-registry lomiri-storage-provider-local FILTER ${CMAKE_SOURCE_DIR}/tests/* ${CMAKE_BINARY_DIR}/* TESTS ${UNIT_TEST_TARGETS} ) lomiri-storage-framework-0.5.0/CTestCustom.cmake.in000066400000000000000000000020601521521330000222560ustar00rootroot00000000000000# # Tests listed here will not be run by the valgrind target, # either because there is no point (we don't want to # test that a python script doesn't leak), or because, # under valgrind, the test runs too slowly to meet # its timing constraints (or crashes valgrind). # SET(CTEST_CUSTOM_MEMCHECK_IGNORE clean-public-lomiri-storage-headers clean-public-lomiri-storage-provider-headers clean-public-lomiri-storage-qt-client-headers clean-public-lomiri-storage-qt-headers copyright debian-version stand-alone-lomiri-storage-headers stand-alone-lomiri-storage-internal-headers stand-alone-lomiri-storage-provider-headers stand-alone-lomiri-storage-provider-internal-headers stand-alone-lomiri-storage-qt-client-headers stand-alone-lomiri-storage-qt-client-internal-headers stand-alone-lomiri-storage-qt-client-internal-local_client-headers stand-alone-lomiri-storage-qt-client-internal-remote_client-headers stand-alone-lomiri-storage-qt-headers stand-alone-lomiri-storage-qt-internal-headers whitespace ) lomiri-storage-framework-0.5.0/ChangeLog000066400000000000000000002356211521521330000202170ustar00rootroot000000000000002026-06-19 Mike Gabriel * Release 0.5.0 (HEAD -> main, tag: 0.5.0) 2026-03-12 Ratchanan Srirattanamet * Merge branch 'personal/gberh/qt6' into 'main' (d2a36a8) 2026-03-05 Guido Berhoerster * debian: Enable parallel build (d96e9c8) * Use imported targets for pkg-config-based dependencies (ec79e5c) * Enable GoogleTest and CTest integration (074fd07) * Integrate GoogleTest using its CMake package and imported targets (d5f0684) 2026-03-03 Guido Berhoerster * Increase copyright test timeout in order to avoid CI build failures (4457d7c) 2026-01-26 Guido Berhoerster * Fix include paths for headers test (a05f6e3) * Add missing include (5b4df8e) * debian: Ignore files in build directories in copyright and whitespace checks (d67427c) * Remove leftover bazaar files (1bef905) * Add build option to specify directories ignored by tests (2b2a9aa) 2026-01-12 Guido Berhoerster * Unwrap QFuture (47eeb77) * Fix return types (e911dff) * Fix QDateTime::fromTime_t usage (16825bd) * Fix ambiguous type in QString construction (818fe38) * Fix invalid cast (d04e52f) * Fix missing headers causing build failures with Qt 6 moc (26333b6) 2026-01-07 Guido Berhoerster * debian: Build packages for both Qt 5 and 6 (90e4593) 2026-01-08 Guido Berhoerster * Add prefix to Qt 6 libraries (ddf57e2) 2026-01-07 Guido Berhoerster * Add support for building against Qt 6 (c715c0d) 2026-01-09 Guido Berhoerster * Revert workaround for ancient gcc issue regarding cv-qualified references (1ab3f35) 2026-01-07 Guido Berhoerster * Do not hardcode PIC and symbol visibility in compiler flags (6261d76) * Do not hardcode C++ 14 in compiler flags and switch to C++ 17 for Qt 6 (adb35e1) * Bump minimum CMake version (0ebbd96) * debian: Remove redundant dh_missing invocation (93fd805) 2025-10-23 Mike Gabriel * Release 0.4.0 (d64ba96) (tag: 0.4.0) 2025-10-13 Mike Gabriel * Merge branch 'personal/fredldotme/noble-bringup' into 'main' (cc3612d) 2025-08-26 Alfred Neumayer * src/provider: Keep LSF_PROVIDER_API_VERSION in pkg-config name (a05898a) 2025-08-25 Alfred Neumayer * src/provider: Remove LSF_PROVIDER_API_VERSION from library name (3b49031) * Clear .gitignore (cf3d098) * debian: Reinstate no-parallel build (65c9e8c) 2025-08-20 Mike Gabriel * r/rules: Assure dh_missing --fail-missing option is used. (747304d) * d/control: Enable all hardening build flags. (1338aef) * d/control: Amend package descriptions of Qt5 client APIv1 / APIv2 lib:pkgs. Avoid lintian warning of too short package descriptions. (6025cbb) * plugins/: Rename QML namespace from Ubuntu to Lomiri. (4dbe4a9) * d/control: Bump Standards-Version: to 4.7.2. No changes needed. (948c9b9) * d/copyright: Use https URL in Format: field. (a7b97a4) * d/rules: Drop --no-parallel. Build supports building parallelly. Drop --fail-missing, default in DH compat level 13. (a16b6fa) * d/{control,compat}: Bump to DH compat level version 13. (2d804f9) * d/lib*.install: Include SO_VERSION_MAJOR in filename globbings so we realized when to bump SO_VERSION_MAJOR in lib:pkg names. (78bc67f) * d/*.shlibs: Remove .shlibs files. Not required anymore nowadays. (1a4a032) 2025-08-19 Alfred Neumayer * treewide: Move binaries to libexec folder (b3c2d81) * debian: Rename ll-s-f-provider-1 to liblomiri-storage-framework-provider1 (96e85da) 2025-08-16 Alfred Neumayer * Update .gitignore (90e9e9c) 2025-07-24 Alfred Neumayer * Revert "debian: Ignore missing shlibdeps info" (dcfbeb9) * Apply 1 suggestion(s) to 1 file(s) (c1d9f5e) 2025-07-21 Alfred Neumayer * debian: Remove now-invalid comment (2e180f5) 2025-07-18 Alfred Neumayer * debian: Ignore missing shlibdeps info (ac8de79) * debian: Fix missed rename and versioning in shlibs file (353e264) 2025-07-16 Alfred Neumayer * debian: Remove unused file (aa63834) * tests: Use GTEST_SKIP for broken tests (99f9fc8) * debian: Use correct flag for non-parallel builds (fe6e485) * debian: Fix Maintainer field and missed rename (203c312) * debian: Rename missed shlibs file (26ea18e) 2025-07-14 Alfred Neumayer * debian: Resort to -j1 (3c9acee) 2025-07-08 Alfred Neumayer * cmake, include, src: sed-replace SF_CLIENT_API_VERSION (244d39a) * debian & tools: Cleanup & fixup (49876c0) * debian, src, cmake: Remove version from library file name (2cdca09) * debian: Restore history changelog entry (82d0ebc) * include: Rename missed macros (c1a4c3f) 2025-07-02 Alfred Neumayer * src/qt: Correct Cflags for qt-client.pc.in (8af7d29) * src: Also fix up paths to DBus activatiable services (7e38cc6) * src: Rename DBus service configs to Lomiri namespace (f4487e2) * src/qt: Point pkgconfig files to correct library names (5a3d381) 2025-07-01 Alfred Neumayer * Revert "debian: Only leave amd64 for running tests" (1b290b8) * Initial .gitignore (e9bcf8c) * debian: Remove Debian as build target for now (29e9b0a) * debian: Only leave amd64 for running tests (e398273) * tests: Hide last failing confusive test (52a5d59) * tests: Further disable known broken tests (c4f2f1c) * debian & tests: Multiple changes (72c8e53) * Version Bump (369f09a) * treewide: Finalize Lomiri renaming for now (5af6b85) * treewide: Further renamings to lomiri (875aefe) * src: Further renamings (c2a4d89) * debian: Rename install and shlibs files to lomiri (6201c70) * debian: Further lomiri renaming (2463581) * treewide: Rename to lomiri namespace (16a7318) * debian: Begin lomiri renaming (7863217) * debian: Remove old TODO (ff50f3f) 2025-06-29 Alfred Neumayer * treewide: Bringup to noble (90d2235) 2023-04-11 Marius Gripsgard * tests/: Correct dbus path to OnlineAccounts to lomiri rename (16b0951) 2023-04-10 Marius Gripsgard * [debian] Set version to allign with source format (d7dbecd) * [ubports] Add jenkinsfile (c998523) 2023-04-10 Alfred Neumayer * debian & src: Enable builds on focal & integrate with NextCloud provider (93e0fad) 2017-03-20 Bileto Bot * Releasing 0.3+17.04.20170320.1-0ubuntu1 (e1d4442) * Make providers exit after 30 seconds of inactivity. (LP: #1616758); Add UnauthorizedException to the provider library, so the provider can trigger reauthentication of the account and have the request restarted. (LP: #1616756); Dynamically add and remove providers as the associated accounts are enabled and disabled. (LP: #1616757); Allow creation of storage providers that don't use online-accounts. Thisis likely only of interest to the local storage provider.; Add a storage provider backed by the local file system.; Move unity::storage::provider::Item to its own header file. (LP: #1668872); If a provider can't acquire its D-Bus well known name, exit rather than throwing a (usually uncaught) exception. (LP: #1604640) (d16ee08) 2017-03-20 Michi Henning * Re-instated previous dummy debian/control file which got accidentally clobbered by merge from trunk. (fabd01d) * Merged devel for 0.3 release. (d9623e3) 2017-03-20 James Henstridge * Add tests for storage-framework-registry. (4178c91) 2017-03-17 James Henstridge * Add change log entries for some of the other landings with user visible changes. (73ecf19) * Add a test to exercise the ListAccounts method of the registry. (3530941) * Link registry code to registry test. (c06da20) 2017-03-17 Michi Henning * Use temporary dir for local provider test. (2ef57ac) * Add entry for local provider to list of accounts returned by registry. (93991e4) * Use temporary dir for local provider test. (990050e) * Move provider::Item definition into its own header file. Fixes: https://bugs.launchpad.net/bugs/1668872. (63db99a) 2017-03-17 James Henstridge * Move the client and provider libraries up to $SNAP/client/lib and $SNAP/provider/lib respectively. Make sure webdav provider can find the provider library. (a6c0e37) 2017-03-17 Michi Henning * Add user name to account details. Fix object path. (23e5155) * Added proper local provider. (a7bf773) 2017-03-17 James Henstridge * Move location for content slots up to the top level, and fix launch script for webdav provider. (53692a0) 2017-03-17 Michi Henning * Merged devel. (31c432c) * Add local provider to accounts returned by registry. (8358733) * Updated debian Standards-Version. (2df58fc) * Renamed STORAGE_FRAMEWORK_ROOT to SF_LOCAL_PROVIDER_ROOT. Added .service file and fixed debian packaging. (175fbeb) * Ignore backup files ending in ~ in copyright test. Also ignore noise caused by snapcraft builds for copyright and whitespace tests. (04e7189) * Rename storage-framework -> storage-framework-service. (6c6d4f5) * Rename plug and slot: storage-framework -> storage-framework-service (f5b5fb9) * Allow Server::run() to return exit status and exit if it cannot register itself as a service on DBus. Fixes: https://bugs.launchpad.net/bugs/1604640. (815ce9f) * Added missing #include. (41216c3) * Add snap build support. (c2b9b84) * Added accidentally lost ~$ pattern back into copyright check. (c11bed7) 2017-03-16 Michi Henning * Review comments from James. (550734e) * Also ignore noise caused by snapcraft builds for copyright and whitespace tests. (8586bec) * Allow Server::run() to return exit status and exit if it cannot register itself as a service on DBus. (fde5d32) * Move provider::Item definition into its own header file. (6c12aeb) * Ignore files ending in ~ in copyright test. (05f714c) * Merged devel. (096e710) * Pay attention to SNAP_USER_DATA, plus other minor changes discussed with James. (a319463) 2017-03-15 Michi Henning * Using gobj_ptr now. (aa0756b) * Fix debian install file. (3cc4014) * Review comments from James. (e926c06) 2017-03-14 Michi Henning * Merged devel. (b9a5a3e) * 100% test coverage. (494aaf5) 2017-03-10 Michi Henning * tmp check-in. (6eff5c2) 2017-03-10 James Henstridge * If an empty service ID is passed to the Server constructor, export a single instance of the provider without consulting online-accounts. (1643d0a) * Expose and hide providers on D-Bus as accounts are added and removed at runtime. Fixes: https://bugs.launchpad.net/bugs/1616757. (b5ccab6) 2017-03-10 Michi Henning * Temp check-in before stripping down test. (94498b7) 2017-03-09 James Henstridge * Allow passing a NULL account to TestServer, which will cause FixedAccountData to be used. (396b2ae) * If the service_id is empty, don't connect to online-accounts and instead export a single instance of the provider at "/provider/0" using the FixedAccountData class to manage the account data. (db8ea0d) * Add FixedAccountData class that bypasses online-accounts. (741b354) * Split AccountData into a virtual base class, and a subclass containing the OnlineAccounts integration code. (61ee5d8) 2017-03-09 Michi Henning * Initial local provider implementation. Test not yet complete. (8e67795) 2017-03-09 James Henstridge * Merge trunk into devel. (53e5009) * Update debian/changelog. (221c1fb) 2017-03-08 James Henstridge * Add a test to show that account details changes are handled correctly. (900733c) * Add test for adding a new account and having it show up on the bus. (8a089f6) * Add a test to show an account being unexported. (b73a349) 2017-03-07 James Henstridge * Add skeleton of Server/ServerImpl tests. (5cd8989) 2017-03-03 James Henstridge * Remove using directives from header file. (a52985d) * Add and remove accounts as OnlineAccounts reports them. (dd6f513) 2017-03-02 James Henstridge * When the account says it has changed, invalidate authentication data. (235ac30) 2017-02-24 Michi Henning * Added missing copyright header. (f9952d6) * Make snap-launch part of storage-framework part. (f16d2d8) * Unconditional coverage again. (59a6899) * Conditional coverage report. (8161310) * Added build-packages. (0473e5b) 2017-02-23 Michi Henning * Minor fixes. (90653de) 2017-02-22 Michi Henning * More changes to patch up paths and fix LD_LIBRARY_PATH. (7489153) 2017-02-17 Michi Henning * First cut at snapcraft.yaml with owncloud provider. (25070ba) 2017-01-30 James Henstridge * Add an UnauthorizedException that providers can use to signal when the credentials provided by online-accounts are invalid or have expired. Fixes: https://bugs.launchpad.net/bugs/1616756. (568a39c) 2017-01-27 James Henstridge * Update documentation based on Michi's review. (4f06e75) 2017-01-25 James Henstridge * Update debian/changelog (dc01c09) * Merge from devel (79b58f3) 2017-01-24 James Henstridge * Fix up handling of invalid credentials so that they actually get re-requested on failure. (9c1ecb2) * Don't call the ProviderBase implementation if authentication failed. (d3aee7b) * Expose Unauthorized error on the client side. (278f9ae) * Add a test to show UnauthorizedException being returned to the client. (3f6be20) * Add tests for throwing UnauthorizedException, and for requests requiring interactive authentication. (3eb6eb8) 2017-01-14 James Henstridge * Change retry flag to a boolean. (63fa009) 2017-01-13 Gary.Wzl * A quick fix to service name conflict between mcloud scope and its storage provider. Also added a new bus name for google drive provider. (4a12f27) 2017-01-13 James Henstridge * Retry on UnauthorizedException errors once. (edc448a) 2017-01-13 Gary.Wzl * One more fix in tests/utils/fake-online-accounts-deamon.py. (496e9b8) 2017-01-13 Michi Henning * Fixed memory leak in remote_client-test and ProviderFixture. Process pending events at end of tests to avoid bogus leak reports from valgrind. (7aa5136) 2017-01-13 Gary.Wzl * Fixed bus name for mcloud and added a new bus name for gdrive. (ee1a8a6) 2017-01-12 James Henstridge * Handle UnauthorizedException specially. (5baad06) * Move authenticate() call into Handler class. (7b2cbfc) 2017-01-12 Michi Henning * Process pending events at end of tests to avoid bogus leak reports from valgrind. (5e399c2) 2016-12-21 Michi Henning * Make providers exit after 30 seconds of inactivity. (LP: #1616758) (5368882) 2016-12-21 James Henstridge * Make providers exit after 30 seconds of idle time. Fixes: https://bugs.launchpad.net/bugs/1616758. (347b248) 2016-12-20 James Henstridge * Move initialisation of member variable to definition. (7fcaa14) * Make storage provider exit timeout configurable via environment variable. (f5942c5) 2016-12-20 Michi Henning * Added missing reset() for account manager in ProviderFixture. (ba74736) * Fix memory leak in remote-client_test. (a5fd004) * Skip tests that are pointless when running with valgrind. (41131fa) 2016-12-20 James Henstridge * Start changelog for next release. (911d6c4) * Add a message before exiting on idle. (ae2d74e) 2016-12-20 Michi Henning * Skip tests that are pointless when testing with valgrind. (7f537e4) 2016-12-20 James Henstridge * Add move semantics to ActivityNotifier, and have UploadJob and DownloadJob track activity. (f847289) * Make Handler keep track of its activity. (c4d2d04) * Simplify InactivityTimer further by letting the QTimer hold the timeout value. (912adf8) * Share InactivityTimer to AccountData object. (164d329) 2016-12-19 James Henstridge * Add InactivityTimer instance to ServerImpl. (37f8a27) * Manage timeout callback in InactivityTimer as a signal. (73dcff8) 2016-12-12 Michi Henning * Updated for new cmake-extras. Replaced debian/control with minimal dummy. (607039f) 2016-12-12 Bileto Bot * Releasing 0.2+17.04.20161212.1-0ubuntu1 (d433c15) * Fix for lp:1644577, fail list job if metadata for any item is incorrect.; Always emit itemsReady(), even if list is empty.; Improvements to error logging and detail in error messages: lp:1644577; Added separate registry service. (a4e1ed6) 2016-12-12 Michi Henning * Replaced debian/control with minimal dummy. Updated control.in for new cmake-extras. (c6b8151) * Replaced debian/control with minimal dummy. Updated control.in for new cmake-extras. (96d2850) 2016-12-09 Michi Henning * Merged devel at r99 for merge to trunk. Updated changelog. (1b7b6d6) 2016-11-29 Michi Henning * Added a separate registry service. (89b2254) 2016-11-29 James Henstridge * Fix up "copyright" test. (a248be1) * Run with "set -eu" to make the shell script a little more robust. (7e13134) * Build-Depend on licensecheck, or a version of devscripts old enough to bundle licensecheck. (03f29c9) 2016-11-28 Michi Henning * Account::name() -> Account::displayName() (562faac) * Merged devel. (c4916c1) 2016-11-25 Michi Henning * Fixed a bunch of warnings caused by new gtest. (30d554b) * Fix for bug 1644577, fail list job if metadata for any item is incorrect. Always emit itemsReady(), even if list is empty. validate() failures now log the error. Lots of improvements to log messages to provide more detail. Fixes: https://bugs.launchpad.net/bugs/1644577. (b8d5c10) * Review comments from James. (0aaa50e) * Fix for bug 1644577, fail list job if metadata for any item is incorrect. Always emit itemsReady(), even if list is empty. validate() failures now log the error. Lots of improvements to log messages to provide more detail. (05447f6) 2016-11-23 Michi Henning * Fix a bunch of warnings exposed by new gtest. (d54b4fb) 2016-11-22 Michi Henning * Review comments from James. (d533a96) 2016-11-21 Michi Henning * Merged devel. (fe5ab30) * Review comments from James. (1d599aa) * Make ubuntu-system-settings-online-accounts a runtime dependency. (bdef91d) * Fix install path in registry .install file. (0fc683b) * Registry in separate package. (d6c9c89) * Removed redundant QDBusArgument inserter/extractor. Fixed macros for install locations. One more rename of provider_id -> bus_name. (112ae10) * Don't use temporary file names for header test. This hugely speeds up the header compilation tests if ccache is enabled. (1f141d1) * Use unique pointer for RegistryInterface. (ff30c9e) * Use QDBusObjectPath instead of QString. (f781b65) * Using o dbus format for bus name, and u dbus format for account id. Renamed AccountDetails members and Account methods to be clearer. (ca8b42c) * Removed env var for registry bus name. (6779de0) * Add test for double-inclusion of headers. (8e5eff0) 2016-11-18 Michi Henning * Added missing depedency of client-dev package on lib for v2 of the API. (fd24784) * Added missing depedency of client-dev package on lib for v2 of the API. (8e29b24) * Don't use temporary file names for header test. This hugely speeds up the header compilation tests if ccache is enabled. (abdce9d) * Merged devel. (21b2b87) * Remove prog_name from accounts manager; it doesn't work. (08d6a30) 2016-11-16 James Henstridge * Update fake-online-accounts-daemon.py to work with online-accounts-api 0.1+17.04.20161110-0ubuntu1. (f9ad59d) * Remove copy of online accounts introspection XML. (f85eb0f) * Update to work with online-accounts-api 0.1+17.04.20161110-0ubuntu1 (6890189) 2016-11-15 Michi Henning * Merged trunk at revision at r14: Bileto Bot 2016-11-04 Releasing 0.2+17.04.20161104-0ubuntu1 (e913fd8) * Merged devel. (7131914) * Client-side and testing support for registry. (851693f) * Use "storage-provider-test" instead of "google-drive-scope" for provider test account. (101755e) * Tests pass now if registry is started by hand. (a356c81) * Changed client-side implementation to use registry instead of online accounts. (5cc9254) 2016-11-11 Michi Henning * Packaging changes. (3b61736) 2016-11-10 Michi Henning * Fault seems to be moving. (0b26d26) * Registry now part of qt-client package. (983cfeb) * Dummy registry test. (60e991c) 2016-11-09 Michi Henning * Env var for registry timeout. (7a74dd8) 2016-11-08 Michi Henning * Fix whitespace. (430b4d4) * Added inactivity timer. (f0f85d2) * debian/control fixes. (5dee9cc) * Fix whitespace. (15ec658) * Updated debian for shared runtime. (3e1ef6a) * Fixed coverage build. (0ffb972) * Updated control. Without ubuntu-system-settings-online-accounts installed, the registry gets an error on start-up. (dd952ab) * Correctly registering adapter now. (70ecd51) * Complete implementation, not tested yet. (5ccd88f) 2016-11-07 Michi Henning * Registry in outline. CMakefiles and the like are working. (61a396c) 2016-11-04 Bileto Bot * Releasing 0.2+17.04.20161104-0ubuntu1 (a626461) * Added v2 of the client-side API.; Updated server-side API to tell the provider which metadata to return.; Update provider API to manager ProviderBase class as a shared_ptr.; Update client to discover ownCloud/Nextcloud and OneDrive accounts.; Add match_etag argument to Download() D-Bus method. (746436f) 2016-11-04 Michi Henning * Merged devel at revision 91. (a066a58) 2016-11-04 James Henstridge * Fix bileto_pre_release_hook for Zesty. Add debian/*.shlibs files. (eab7a0f) * Fix up "auto generated" warning. (4d4a143) * Make copyright test pass. (8e5dc6b) 2016-11-04 Michi Henning * Changed Uploader and Downloader to derive from QIODevice. (5c91008) 2016-11-04 James Henstridge * Replace all occurences on a line (needed for substitution in shlibs file). (5080879) * Actually add shlibs files, and update .bzrignore. (05e1732) * Add shlibs files, and parameterise creation of install and shlibs files for provider binary package. (d057ba2) * Treat Zesty like Yakkety for the purposes of libstorage-framework-provider sonames. They won't be identical once Zesty switches to Boost 1.62, but we may not update Yakkety by then. (85246ab) 2016-11-04 Michi Henning * Renamed finishUpload() and finishDownload() to close(). (2a9f126) * Review comments from James. (fa34925) * Added code to deal with synchronous wait on uploader. Not tested yet, because we need to make changes to the mock provider harness for this. (c998cf7) 2016-11-04 James Henstridge * Merge from trunk. (5876256) 2016-11-03 Michi Henning * Renamed Account accessors to reflect what we get from online accounts. A few fixes for QML: - accounts property is no tied to a method that returns a QVariantList - Changed to fully-qualified type names for Q_INVOKABLE methods, so QML doesn't complain about an unknown return type. (7d764e1) * Merged devel and resolved conflict. (b4c690e) 2016-11-03 James Henstridge * Add match_etag argument to Download D-Bus method. (4fe6852) 2016-11-03 Michi Henning * Review comments from James. (431db5b) 2016-11-03 James Henstridge * Merge from devel, fixing conflicts. (e1847fa) * Add service ID for ownCloud/Nextcloud and OneDrive providers. (50369a1) 2016-11-03 Michi Henning * Client-side changes for etag check on downloads. (53057a3) * Merged devel. (0529de2) 2016-11-03 James Henstridge * Update debian changelog. (9a27337) * Add match_etag argument to Download() D-Bus method. Currently only exposed on the provider side: client side is still to-do. (a719852) 2016-11-03 Michi Henning * Merged devel and resolved conflicts. (2908445) * Removed the Account id() and serviceId() methods and replaced them with bus_name() and object_path(). Internally, for comparison, id and serviceId are still used, because a destroyed and re-created account isn't the same account. (20b6896) * Got rid of merge conflict file and redundant Q_INVOKABLEs. (735d7d3) 2016-11-02 James Henstridge * Add some changelog entries. (edad87e) * Add OneDrive, at Gary's request. (4f7b0af) 2016-11-02 Michi Henning * Reduce noise from fake online accounts daemon. (6233bbd) 2016-11-02 James Henstridge * Add service ID / D-Bus well known name for owncloud provider. (9e24aaf) 2016-11-02 Michi Henning * Moved metadata key definitions into common.h and move metadata_keys.h to include/unity/storage/internal so both client and server side can see them. Added commonly supported metadata keys. Removed free and used space methods (they are metadata now). No metadata validation for roots because Dropbox can't support that. (2892419) * Added custom type ostream operators for gtest so we get decent output when a QString comparison fails. (a8e3894) * Added string list param to all methods that return an item, so the client can specify which metadata values should be returned. (6b0299b) * Relaxed validation of etag so it applies only to files. (ae5f221) * Merged dependent branch. (8319956) * Merged dependent branch. (b25b3f2) * Fixed compile failure due to Qt header changes on Vivid. (a14a62f) * Merged dependent branch. (582323d) * Merged dependent branch. (d0bd8d0) * Got rid of MetadataKeys typedef. (3198f3b) * Renamed Account accessors to reflect what we get from online accounts. A few fixes for QML: - accounts property is no tied to a method that returns a QVariantList - Changed to fully-qualified type names for Q_INVOKABLE methods, so QML doesn't complain about an unknown return type. (a8f2138) 2016-11-01 Michi Henning * Reduce noise from fake online accounts daemon. (9e35017) * Suppress warnings from Qt headers. (93e19e4) * Added custom type ostream operators for gtest so we get decent output when a QString comparison fails. (ccc52e5) * Added tests for upload/download. (37aca8b) * Uploader derives from QIODevice now. (b5c86c1) * Downloader derives from QIODevice now. (7a07722) 2016-10-31 Michi Henning * Server-side API version back to 1. Updated changelog accordingly. (d8f8d75) * Updated changelog. Bumped API version. (edd1a25) * Coverage for metadata validation. (adaf0f7) * Added missing sizeInBytes() and implemented metadata(). (2c69f1b) * Fixed comment. (1f1fe0f) * Added commonly supported metadata keys. Removed free and used space methods (they are metadata now). No metadata validation for roots because Dropbox can't support that. (a60686b) * Moved metadata key definitions into common.h and move metadata_keys.h to include/unity/storage/internal so both client and server side can see them. (96dd28b) 2016-10-21 Michi Henning * Added string list param so client can specify metadata keys. (b5e1686) 2016-10-17 Michi Henning * Added createFile(). (82490a9) * Check policy for exists error. (0de5a4d) * Test to cover Exists error. (ba874f1) * Coverage tests for createFile. (2baaa97) 2016-10-13 Michi Henning * Implemented createFile(). No tests yet. (7abbf11) * Lots of upload tests. (85cbbf2) * Added lots of tests for upload. (2e671a3) * Merged devel. (8fd45fe) 2016-10-12 Michi Henning * Download implementation. (293a358) * Fixed more compiler warnings. (8e2d3da) * Two more Vivid errors. (13093f4) * Another fix for non-const reply argument. Added suppression for tons of warnings on Vivid from provider/internal/ProviderInterface.cpp. (95e65c8) * Work around compiler bug on Vivid: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60420 (6ea8776) * Got rid of DeletedError (not needed). Setting error info on cancallation now. (3a3a707) * Fixed typos. (52e2cdd) * Upload implementation with basic test only. (a101765) * Complete coverage for download. (270f1f2) 2016-10-11 Michi Henning * Intermediate check-in. Still race condition in cancel test, and a memory leak when downloader goes out of scope. (3b83705) * Log more provider errors. (4eaf5a7) * Log more provider errors. (c1d9e04) 2016-10-10 Michi Henning * Merged devel. (872d9d5) * Outline for createDownloader(). (1cfa2f6) * Added more operation implementations and tests. Replaced enum class with enum. (7b8becf) * Re-enabled two Q_DECLARE_METATYPE. (f0a2872) * Merged devel. (692c5db) 2016-10-10 James Henstridge * Add start of QML plugin for v2 client API. The plugin is currently only built but not installed. (a209939) 2016-10-10 Michi Henning * Merged James's QML branch. (6428b8b) * Merged devel. Replaced "enum class" with "enum". (73613ac) * Added list(). More tests for coverage. (570bf04) * Added copy and move implementations. (227f3a7) * Changed ItemMetadata for parent_ids to QList (from QVector). Added parents() implementation. Refactored ItemListJobImpl and MultiItemJobImpl to use a base class that does most of the work, so we can create an ItemListJob from both a single invocation that returns a list, and from multiple invocations that return a single item each. Minor renaming of the make_* factory methods for brevity. A few minor bug fixes here and there. Lots more tests. (a16c7b2) * Merged parent branch and resolved conflicts. (f27e774) * Simplified ItemImpl template methods for checking preconditions. (ecf2de7) * Merged dependent branch. (37138a2) * Chained StorageErrorImpl constructors. Used QStringLiteral for type names initialization. (1340e18) 2016-09-30 James Henstridge * Include in Account.h to try and standalone headers test on yakkety. (bd7b490) * Make copyright check test ignore qmldir file. (4a7c65e) 2016-09-30 Michi Henning * Added VoidJob. Implemeted get(). Implemeted deleteItem(). Some minor refactoring and tidy-up. Lots more tests. (e72f029) * Started list implementation. (fa07b87) 2016-09-30 James Henstridge * Update control file. (afff778) 2016-09-29 Michi Henning * Merged dependent branch. Removed redundant fully-qualified names. (b59351b) * Merged dependent branch. Removed redundant fully-qualified names. (61d5572) * Removed redundant fully-qualified names. (5f0f479) * Merged dependent branch. (e02f552) * Review comments from James. (a2c03e5) 2016-09-29 James Henstridge * Update demo a bit. (ee8da0f) * Add skeleton (useless) demo.qml file. (696b78b) * Fix up Qt metatype foo for Runtime and Account. (a0729b6) * Add skeleton QML plugin. (f5267a3) 2016-09-29 Michi Henning * Changed enums to enum classes. (7b82359) * Added copy and move implementations and tests. (27b54e1) 2016-09-29 James Henstridge * Make ProviderBase derive from std::enable_shared_from_this. Add "host" member to PasswordCredentials for benefit of OwnCloud provider. Relax client side ETag validation, so it is only required for files. (cd35c6a) 2016-09-28 James Henstridge * Add temporary workaround for https://bugs.launchpad.net/bugs/1628473 (b57dba2) * Bump soname for provider library. (3403d20) * If the etag is empty, only throw if the type is ItemType::file. (2932736) * Use QPointer to guard the OnlineAccounts::Account object that is owned by the OnlineAccounts::Manager object. We still don't correctly handle accounts being deleted/disabled, but this will ensure we see a null pointer dereference rather than garbage memory. (a732a8a) * Extract hostname setting for password-style accounts. (1fe2a9d) * Make ProviderBase subclass std::shared_from_this<>, and manage instances as std::shared_ptr's for the benefit of lifecycle management in provider implementations. (41dd1ae) 2016-09-28 Michi Henning * Changed ItemMetadata for parent_ids to QList (from QVector). Added parents() implementation. Refactored ItemListJobImpl and MultiItemJobImpl to use a base class that does most of the work, so we can create an ItemListJob from both a single invocation that returns a list, and from multiple invocations that return a single item each. Minor renaming of the make_* factory methods for brevity. A few minor bug fixes here and there. Lots more tests. (3c4d689) 2016-09-27 Michi Henning * Changed Q_PROPERTY definitions to emit statusChanged where appropriate. Moved ConflictPolicy definition into Item. Enumerator values are set from common.h. Got rid of deferred signal emission except where we need to emit signal from constructor. Removed disconnectFromBus() in RuntimeImpl. Moved ProviderFixture construct/destructor code to SetUp()/TearDown(). (e24433b) * Fixed whitespace. (6d55d16) 2016-09-26 Michi Henning * Added VoidJob. Implemeted get(). Implemeted deleteItem(). Some minor refactoring and tidy-up. Lots more tests. (1c318b7) * Moved ProviderFixture construct/destructor code to SetUp()/TearDown(). (93cc0b3) * Changed Q_PROPERTY definitions to emit statusChanged wehre appropriate. Moved ConflictPolicy definition into Item. Enumerator values are set from common.h. Got rid of deferred signal emission except where we need to emit signal from constructor. Removed disconnectFromBus() in RuntimeImpl. (2403c7f) 2016-09-23 Michi Henning * First batch of fixes from code review: Removed CONSTANT from Q_PROPERTY definitions of Item and Account. Moved qHash() into correct namespace. Adjusted hash() and qHash() of Item to also combine with the account hash. Adjusted operator==() and operator<() of Item to compare equal or less than only if the accounts also compare equal or less than. make_test_account() trailing arguments are now defaulted, so we don't need an overload. Chained the RuntimeImpl constructors. (cc7c59d) 2016-09-22 Michi Henning * Removed CONSTANT from Q_PROPERTY definitions of Item and Account. Moved qHash() into correct namespace. Adjusted hash() and qHash() of Item to also combine with the account hash. Adjusted operator==() and operator<() of Item to compare equal or less than only if the accounts also compare equal or less than. make_test_account() trailing arguments are now defaulted, so we don't need an overload. Chained the RuntimeImpl constructors. (df23ac7) 2016-09-21 Michi Henning * Arm build warning fix. (ce2f7f5) * Started implementation of v2 API. (07c2610) * A few more packaging fixes. (f5b4b6e) * Separated old and new header install location. Added new binary package for v2 client API. Fix broken pkgconfig files. Bumped project version. Updated changelog. (3eabdbd) * More Q_DECLARE_METATYPE :-( (7b2c2f3) * Another missing Q_DECLARE_METATYPE (e1a0c20) * Added missing Q_DECLARE_METATYPE (62b2af9) * Fixed wrong location for installing pc file. (403754a) * Fixed compilation error on Vivid. More warning suppressions for Arm. (a51e948) * Q_ENUM -> Q_ENUIMS for Vivid. Disabled check for connection state when creating the runtime. (31fcc15) 2016-09-20 Michi Henning * impossible_name() -> object_path() (80f3723) * bus_path() -> impossible_name() for testing. (278a758) * Trying this->bus_path(); (a31f086) * Whitespace fixes. Trying qualified name for failing bus_path() call. (ee873b3) * Comment changes to mark review items. (43464f6) * get() now on Account. More tests. (d592237) 2016-09-16 Michi Henning * More tests, added most of ItemImpl and metadata validation. (74d1971) 2016-09-12 Michi Henning * AccountsJob tests. (7668ba2) * Added qHash(). (3242e5f) 2016-09-09 Bileto Bot * Releasing 0.1+16.10.20160909-0ubuntu1 (b61989f) * Merged devel at revision 64. (8e9810d) 2016-09-09 Michi Henning * Merged devel at revision 68. (3094bed) 2016-09-08 Michi Henning * Added some basic unity tests. (ca07802) 2016-09-06 James Henstridge * Use std:: prefix on make_shared() call to avoid ADL error on Yakkety. (66acc05) * Fix the deadlock when cleaning up stale upload and download jobs when a client disconnects from the session bus. (d7c1b34) 2016-09-06 Michi Henning * Merged lp:~michihenning/storage-framework/remove-qfuture-overloads/+merge/304042: Removed overloads from make_future.h that accept a QFutureInterface because that made it too easy to accidentally call the wrong overload. Added attribute to remaining functions to warn if the return value is unused. (ec4a466) * Merged devel. (416d853) * Retrieve accounts in AccountsJob. (78f106e) 2016-09-05 James Henstridge * Include the upload or download ID in the "No such upload/download" exception messages. (c6200e2) 2016-09-05 Michi Henning * More internal wiring for correct life cycle. (5a53aea) 2016-09-03 James Henstridge * Cancel remaining jobs when destroying PendingJobs instance. (82ec87b) * When a client disconnects, remove them from the service watcher. (13e9f84) * Remove code special casing Boost 1.55, since we never build with that version now. (590f1c9) 2016-09-02 James Henstridge * Don't scan the map twice in remove_download/remove_upload, and output LogicException on unknown uploads/downloads. (d63027a) * Add tests for cancelling downloads on closed connections, and tests for finishing or cancelling uploads/downloads belonging to other connections. (b6fafef) * Store the job cancellation future in a shared_ptr<> that will live until the continuation is called. (045f0c4) * Add a test to cover clients disconnecting from the bus during an upload. (4137ce9) * Simplify ProviderFixture a bit. (a1753d0) 2016-09-01 Michi Henning * Started outline implementation of v2 API. (de37a66) * Use custom deleter for sockets given to client, so the socket is destroyed when the event loop is re-entered. (28f23ba) * Use custom deleter for sockets given to client, so the socket is destroyed when the event loop is re-entered. (97e0612) 2016-08-31 Michi Henning * Merged devel. (4f11b1a) 2016-08-29 Michi Henning * Tidy up after review comments from James. (1e9b168) * Draft outline of new Qt/QML API. (7e51a9c) 2016-08-26 Michi Henning * Removed overloads from make_future.h that accept a QFutureInterface because that made it too easy to accidentally call the wrong overload. Adding warning to remaining functions to warn if the return value is unused. (f460842) * Merged devel at revision 64. (c84efdb) * Send cancel message to server if uploader is destroyed without a prior call to cancel() or finish_upload(). (5b388f7) * Set future via the promise in the handler rather than returning a new ready future. (5765428) * Suppressed one more compiler warning. (215ed2e) * Suppressed a bunch more compiler warnings from Arm builds. (2edf9cb) * Send cancel message to server if uploader is destroyed without a prior cancel() or finish_upload(). (effe3e2) 2016-08-25 Michi Henning * Removed stale debug trace. Suppressed more compile warnings from system headers that caused a lot of noise in the arm builds. (f07c055) * Don't throw when unknown metadata key is received and log a warning instead. (a250439) 2016-08-25 James Henstridge * Add a drain() method to UploadJob to allow the provider to read the last bit of data from the socket when the client asks to finish the upload. (b693c88) 2016-08-25 Michi Henning * Removed stale debug trace. Suppressed more compile warnings from system headers that caused a lot of noise in the arm builds. (e13339e) * Increased test timeouts because CI builders are hopelessly overloaded. (9ca9435) * Don't throw when unknown metadata key is received and log a warning instead. (c01b0a9) * Increased test timeouts because CI builders are hopelessly overloaded. (d12cb9d) 2016-08-24 James Henstridge * Fix compile failure caused by merge. (64af625) * Merge from devel (3743c03) * Run all tests with a larger file content. (77cbf01) * Remove drain() virtual from UploadJob, but keep version on TempfileUploadJob as a normal method. Update test provider to match. (625b2e3) * Fix select() code in TestUploadJob. (b872b35) 2016-08-24 Michi Henning * Removed half-close on the provider side because this messes with QLocalSocket on the client side. (2e11aa7) * Added provider headers to unit tests. (55f91e7) * Don't half-close sockets in the provider side because this confuses QLocalSocket, which thinks that both channels were closed. (d200fae) * Fixed stand-alone header test failure on yakkety. (2a3040b) * Merged devel. (06b7137) 2016-08-23 Michi Henning * Increased remote client coverage. (8479d37) 2016-08-23 James Henstridge * Add tests for TempfileUploadJob. (4ebf44b) * Add drain() implementation to TempfileUploadJob. (aa647fa) 2016-08-23 Michi Henning * Renamed FakeProvider -> ProviderFixture. (6231997) 2016-08-22 Michi Henning * Oops, missed change in MockProvider.cpp. (5332ef7) * Review comments from James. (35e84e5) * Added provider headers to unit tests. (5cc6dc2) 2016-08-22 James Henstridge * Add a test for unclosed socket behaviour. (51a4bcc) * Add drain() method to UploadJob to drain the socket before calling finish(). (16ebdde) 2016-08-19 Michi Henning * Merged devel. (6d484b9) * Fixed whitespace. (2094d4c) * Added mock provider that can respond to instructions via cmd parameter passed to constructor. Increased coverage for check for destroyed runtime before reply for an async call trickles in. (fe1ba90) * Got rid of hard-wired bus name in RuntimeImpl. (f6e922c) * Got rid of hard-wired bus name in RuntimeImpl. (efcf433) 2016-08-18 James Henstridge * Merge lp:storage-framework into devel, fixing conflicts. (697a248) 2016-08-18 Michi Henning * Added trace for surprising exceptions to server side and remote client. (4603bd3) * Moved test fixture out of provider interface test into utils FakeProvider class. (915cd48) 2016-08-17 Michi Henning * Logging surprising exceptions with qCritical() now. (6869fa6) 2016-08-12 Michi Henning * Fixed broken test. (730b39a) * Merged devel. (7986253) * First steps towards exception logging. (42648d7) * Replaced runtime_error with storage exceptions on server side. (9b94b88) * Replaced runtime_error with storage exceptions on server side. (2ee58a9) 2016-08-12 James Henstridge * Provide an API to create a new Account object in the client for a provider identified by an arbitrary (bus_name, object_path) pair. (7f34454) * Exclude make_test_account() from doxygen docs. (0d2fa39) * Silence unused variable warning. (6599c6b) * Merge from devel, fixing conflicts. (18b3b4b) * Add Runtime::make_test_account() method. (0dd697f) 2016-08-11 Michi Henning * Changed single parent_id to vector. (d5e4ba6) * Merged devel and resolved conflict. (14fe6b1) * Added fake mcloud scope. (dedf542) 2016-08-11 James Henstridge * Add unity::storage::provider::testing::TestServer class. (32a6e3c) * Move Account object creation out to a helper function. (c499020) * Pull construction of (bus_name, object_path) pair up into RuntimeImpl. (3d52427) 2016-08-11 Michi Henning * Merged devel. (13ed4ce) 2016-08-11 James Henstridge * Move QDBusArgument marshalling code for ItemMetadata to storage-framework-common-internal library: while local-client doesn't explicitly need it, it was already linking to QtDBus, so it doesn't really matter. (118bea3) 2016-08-11 Michi Henning * Marshal known metadata, with sanity checks on the client side. (d7b5914) 2016-08-11 James Henstridge * Merge from devel, fixing conflict. (438150c) 2016-08-11 Michi Henning * Fixed broken test. (1d753d4) 2016-08-11 James Henstridge * Make ProviderInterface test link against the .so version of storage-framework-provider. (bb8760e) * Use TestServer in provider-ProviderInterface test. (164c529) 2016-08-11 Michi Henning * Merged devel. (6111e0b) * Merged devel and resolved conflict. (de1fc6e) * Merged devel. (5fef2e2) 2016-08-11 James Henstridge * Make TestServer actually hook up the provider to the bus. (31e5407) 2016-08-11 Michi Henning * Added exception hierarchy for server side, plus marshaling/unmarshaling code for exceptions. Some coverage in the remote client to show that this works (but more coverage is needed). (821f266) * Removed stale comments. (ec826ac) * Added check on unmashaling to ensure that ISO time includes a time zone. (e2e773d) * Merged dependent branch and resolved conflicts. (7df7b4d) * Merged devel. (934e084) * Moved get-provider-soversion.sh into ./tools. (1d06c4b) 2016-08-11 James Henstridge * Add stub of TestServer class. (79bceb9) 2016-08-11 Michi Henning * Instrumented failing test. (9172978) * Added fake mcloud provider. (f044ad1) * Merged devel. (e4b12be) * Fixed top-level makefile soversion script invocation. (1c2fb35) * Fixed wrong parent_ids type spec in provider.xml. (4ec031b) * Addressed review comments from James. (8018e74) * Merged devel and resolved conflicts. (b8f447f) * Removed O_NONBLOCK from local client socket pair. (5924aee) 2016-08-10 James Henstridge * Add tests for the D-Bus interface code in libstorage-framework-provider. (251932f) * Add some tests for the AccountData and DBusPeerCache classes from the provider. (acb9bb6) 2016-08-10 Michi Henning * Merged lp:~michihenning/storage-framework/upload-download-fixes: Added size() accessor to Uploader. Fixed upload/download finalization for remote client. (0445277) * Merged lp:~michihenning/storage-framework/more-coverage: Improved coverage. Better error reporting/handling. (b1a3e48) * Removed O_NONBLOCK from local client socket pair. (af28d0f) 2016-08-09 Michi Henning * Changed single parent_id to vector. (1dd3ca4) 2016-08-09 James Henstridge * Silence unused variable warnings. (faf5a9b) * Add tests for uploads. (88cd524) 2016-08-09 Michi Henning * Moved get-provider-soversion.sh into ./tools. (0358c27) * Added metadata marshaling. (67870d2) 2016-08-08 James Henstridge * Test download operations. (3a3cf9c) 2016-08-08 Michi Henning * Use call() template instead of explicit watchers in test. (46eb8f9) 2016-08-08 James Henstridge * Fix some unused variable warnings. (494528f) 2016-08-05 James Henstridge * Merge from provider-tests-auth (e8d65a0) * Add tests for more D-Bus methods. (0b3dd67) 2016-08-05 Michi Henning * Added exception hierarchy for server side, plus marshaling/unmarshaling code for exceptions. Some coverage in the remote client to show that this works (but more coverage is needed). (4f96f83) 2016-08-04 James Henstridge * Add tests for provider Roots and List D-Bus methods. (34e08c2) 2016-08-04 Bileto Bot * Releasing 0.1+16.10.20160804.1-0ubuntu1 (3ecaac0) 2016-08-04 James Henstridge * Initial release of storage framework. (f799e72) 2016-08-04 Michi Henning * Added trace to track down failure on arm. (df474b3) 2016-08-04 James Henstridge * Merge lp:storage-framework/devel (626561b) * Update Debian control file to require Boost 1.58, explicitly depending on libboost-*1.58-dev packages if the default Boost is older. (e30d8e9) 2016-08-04 Michi Henning * Tests for destroyed runtime. (0035929) 2016-08-03 James Henstridge * Clean up extra new lines the whitespace test was complaining about. (62a6cd8) 2016-08-03 Michi Henning * Merged dependent branch. (e7627bf) * Tidied up checks for destroyed runtime/item in a few places. (7d7b912) * Fix typos. (0f50d42) * Fixed incorrect check for parent destruction in copy() and move(). (3c755c9) * Added a few missing checks for destroyed runtime or item. (ba52c9d) * Fixed remote upload/download finalization. (4c94359) 2016-08-03 James Henstridge * Silence warnings caused by Qt headers. (fb88d94) 2016-08-03 Michi Henning * Test to check that calling finish_upload() twice after error returns the same error future. (d06611f) 2016-08-03 James Henstridge * Ensure generated files are built before object library. (c738f36) 2016-08-03 Michi Henning * Added size() accessor to Uploader. Added tests for upload size mismatch. (9e13426) * Fixed hang in test. Fixed sigpipe problem when cancelling upload. (ff3281e) 2016-08-02 Michi Henning * A few coverage suppressions. (7baec2f) * Removed stale code, fixed spy usage in tests. (a3bf95f) * More refactoring to get rid of repeated exception handlers. (a131d2a) * More refactoring for error handling. (40ffbb0) 2016-08-02 James Henstridge * Require Boost >= 1.58. (e4b5d69) 2016-08-02 Michi Henning * Removed dependency on boost from remote client for exception handling. Refactored exception handling logic and fixed tests to look for the correct exception. (4770b5c) 2016-07-29 Michi Henning * Improved coverage. Better error reporting/handling. (ecebcaa) 2016-07-28 James Henstridge * Fix test name. (83293cc) * Add a test for DBusPeerCache class. (287194b) 2016-07-27 James Henstridge * Add test for account credentials parsing. (d3b1658) * Build a static version of libstorage-framework-provider for use in tests. (2c95ec0) 2016-07-27 Michi Henning * Merged lp:~michihenning/storage-framework/no-boost-with-remote-client: Removed boost dependency from remote client lib. (e8302e8) * Remote client lib no longer has any dependency on boost now. (95d7ff2) 2016-07-26 James Henstridge * Don't use boost::make_ready_future(value), because Boost 1.55 (found in Vivid) will instead create an exceptional future. (7c56c68) 2016-07-26 Michi Henning * Merged devel and resolved conflicts. (2622b1b) 2016-07-26 James Henstridge * Flush stdout after printing log information so it actually shows up in the test output. (2ebc526) 2016-07-26 Michi Henning * Wait time for remote client test now 30 seconds, which is more than the DBus timeout (25 seconds). (3f67329) * Increased timeout for remote client test. (2cffd8a) * Merged devel and resolved conflict. (234dd18) * Merged lp:~michihenning/storage-framework/fix-linkat: Work-around for failing linkat() on Vivid/Arm. (09a66be) * Merged lp:~michihenning/storage-framework/fix-compile-warnings: Minor changes to get rid of warnings when compiling on vivid. (890ec5d) 2016-07-26 James Henstridge * Move fake-online-accounts-daemon.py over to python3-dbus, since Gio D-Bus bindings don't seem to be working on vivid. (1732f8b) 2016-07-26 Michi Henning * Changed logic for handling temp file linking/renaming. We use O_TMPFILE and linkat() if that works, and mkstemp() and rename(), otherwise. (6fb92d8) 2016-07-25 James Henstridge * Add python3-dbus as a dependency. (6690b22) * Add missing break; statements. (79f1c93) * Move fake provider over to old python-dbus bindings, which work on Vivid. (5f6df1b) 2016-07-25 Michi Henning * Merged exceptions branch and resolved conflicts. (2583ab5) 2016-07-23 Michi Henning * Work-around for failing linkat() on Vivid/Arm. (0c0afa9) 2016-07-22 James Henstridge * Add support for landing to multiple distro series with different sonames for the provider library. (132bde9) * Add trusty as a special case. (5b6b14e) 2016-07-22 Michi Henning * Work-around for O_TMPFILE failure on Vivid/Arm. (13d3de9) 2016-07-22 James Henstridge * Add copyright header to shell script. (6a4cb6a) 2016-07-22 Michi Henning * Some more trace to get kernel version. (dd25455) 2016-07-22 James Henstridge * Add lsb-release as a dependency. (8c0f079) * Make storage provider soversion dependent on distro release. (02cb11c) 2016-07-22 Michi Henning * More trace. (1e752f4) * Added trace to see whether linkat() failure on Vivid is due to missing /proc. (5388081) * Reverted test for failing link() again because the fix is a bit more involved, and we are focussing on other things right now. (30618d7) * Minor changes to get rid of warnings when compiling on vivid. (f0fdaba) * Merged devel for initial release. (41304b0) * Merged lp:~michihenning/storage-framework/exceptions: Cleaned up exception reporting. (344b4f3) * More warning cleanup. (514dc65) * Include file fixes to get rid of warnings on Vivid. (7f69051) * Merged devel and resolved conflicts. Added override for what() to StorageException. (ff6d1df) * Merged devel and started to resolve conflicts. (f846e7f) * Merged devel. Started to resolve conflicts. (205ab02) 2016-07-21 Michi Henning * Merged lp:~michihenning/storage-framework/metadata: Initial cut at metadata API. (724068b) * Merged devel. (261c5f7) * Merged devel. (bbc683a) * Merged lp:~michihenning/storage-framework/add-upload-size: Added size to create_uploader() and create_file(). (e8a380a) 2016-07-21 Marcus Tomlinson * Temporary fix to get storage-framework building in CI (f88da83) 2016-07-21 Michi Henning * Merged devel and resolved conflict. (0544721) 2016-07-21 Marcus Tomlinson * Temporary fix to get storage-framework building in CI (055a3ee) 2016-07-21 Michi Henning * Merged devel and remove bogus files. (2330a6c) 2016-07-21 Marcus Tomlinson * Fix for building in dev PPA (75c404b) 2016-07-21 Michi Henning * Merged lp:~michihenning/storage-framework/fix-version-typo: Fixed wrong version comparison for boost. (684fd21) * Merged lp:~michihenning/storage-framework/no-half-close into lp:storage-framework/devel: Removed socket half-close from uploader because that seems to interfere with QLocalSocket. (64d95f6) * Fixed wrong version comparison for boost. (639daff) 2016-07-21 James Henstridge * Run remote-client tests under a private session bus with a fake account provider. These changes rely on the new online-accounts-api packages that will be siloed with storage-framework. (454bbaa) * Add missing copyright headers. (9c2ecdd) * Add an instance of the demo provider under the private session bus, and make the fake account daemon expose a google-drive-scope account. (7ed5a58) * Merge from devel, fixing some conflicts in the cmake files. (4fec34b) * Use a private session bus in the remote_client tests. (b17fc2c) * Use the provided session bus connection in the client. (e01a284) 2016-07-21 Michi Henning * Removed socket half-close from uploader because that seems to interfere with the signals from QLocalSocket. (a6659d5) * Merged devel. (6c34a6d) 2016-07-20 James Henstridge * Update provider library to compile on Vivid's old version of Boost. (e2c9a59) 2016-07-20 Michi Henning * Manually merged lp:~michihenning/storage-framework/qt-cmake-fixes (bde2804) * More shoring up against the runtime disappearing while a reply is outstanding. (3bff1d1) * Merged dependent branch. (6194212) 2016-07-20 James Henstridge * Add exception guard around call to Handler callback that I accidentally removed while refactoring the code to work without relying on executors. (3d9052c) 2016-07-20 Michi Henning * Fixed broken coverage in top-level cmake file. Simplified boost version check. (1694f2b) 2016-07-19 James Henstridge * promis.set_exception() fails for std:: exceptions due to ambiguity over which copy_exception function to call on old Boost. (ff90e00) * More vivid build fixes. (b2e8b0b) * Actually use queued connections. (d25099a) * Make MainLoopExecutor call in ServerImpl conditional on executor support. (60b6337) * Fix up coverage build. (515c23c) * Merge from lp:~michihenning/storage-framework/qt-cmake-fixes and fix conflict. (8b45885) * Provide our own versions of make_ready_future/make_exceptional_future, since vivid's Boost doesn't include them. (c3e8bac) 2016-07-19 Michi Henning * Tests for DeletedException. (3ca930c) 2016-07-19 James Henstridge * Don't compile MainLoopExecutor if executors are not supported. (50f4ac9) * Make code run correctly with boost versions that do not support executors: (eead429) 2016-07-18 Michi Henning * More details for ResourceException. Generic unpacking of boost::filesystem errors. (54963c5) 2016-07-15 Michi Henning * Another include file clean-up. (c0b8fcc) * Cleaned up a bunch of include directives. (428279a) * Made sure that lambdas don't use things that no longer exist if a reply trickles in after the runtime is destroyed. (141fdcf) * More future streamlining. (f22a24f) 2016-07-14 Michi Henning * Minor fix to top-level cmake file. (d2698a7) * Need exceptions to be thread-safe. (83098db) * Added QuotaException and PermissionException. (d83d21a) * Cleaned up conflict detection. (ce1ef20) * Merged devel, resolved conflicts. Some exception handling added to remote client. (536a0e8) * Changed debian source/format to 1.0. (66d2c5e) * Merged devel. Added missing copyright header. Removed stale files. (779501a) * Merged devel and resolved conflict. (2d310a2) 2016-07-13 Michi Henning * Fixed macro to test for boost version. (cef8b6e) * Fixed compile error with boost::filesystem on Vivid. (6137c72) * Fixed typo in comment. (6fc883f) * Fix for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60420 (ab45fcf) * Another try... (ff69454) * Code formatting fix. (aadaac8) * Fixed a bunch of clang warnings. (ea1cd6b) * Another try... (d400190) * Testing fix for vivid build failure. (11f0190) * Changing source/format back to 1.0, due to Jenkins problems. (fd2aff7) * Merged devel. (0d00e98) * Merged https://code.launchpad.net/~michihenning/storage-framework/copyright/+merge/299761 (b14f5bc) * Added missing dependency to control. (3fdb628) * Changed cmake files to build the local and remote libs in the qt/client dir because we cannot add .moc files to OBJECT libraries on Vivid. Added GENERATED property to generated files. Adjusted indentation in a few places. (006d527) 2016-07-12 James Henstridge * Add a second Runtime constructor that lets us inject a custom QDBusConnection. (f19b2dc) * Add test utility library, currently consisting of a class to set up a private D-Bus session bus with a fake online-accounts daemon running. (216026b) 2016-07-12 Michi Henning * Removed one more old qt5_use_modules. (f99016e) * Get rid of the old qt5_use_modules macros. (be9042e) 2016-07-12 James Henstridge * First go at a fake version of the OnlineAccounts D-Bus service, written with python3-gi. (7363b44) 2016-07-12 Michi Henning * Added copyright headers and enabled copyright test. Some minor improvements to debian files to get rid of lintian noise. (fe4f630) * Merged devel and resolved conflicts. (3a2b09e) * Fixed test that was broken by merge of another branch. (012f1a3) * Merged devel. (dfb19bc) * Added etag() method to public API. (5846199) 2016-07-11 Michi Henning * Merged decltype branch. (a2c1338) * "title" -> "name" (0ed97f4) * Upload size for server side. (d862c8b) * Merged decltype branch. (3efd865) * Merged decltype branch. (f764918) * Merged decltype branch. (bc01218) * Merged devel and got rid of a bunch of compiler warnings. (be1cbc6) * Fixed recursive list(). Got rid of a few redundant std:: qualifiers. (ab010cf) 2016-07-11 James Henstridge * Add cmake rules to install libraries, headers, and pkg-config files. Update debian packaging to produce multiple binary packages. (ed18278) 2016-07-11 Michi Henning * Added implementation of remote client-side API. Lots of refactoring and API polish. (8e6fc9b) 2016-07-11 James Henstridge * Fix typos in packaging. (16c8b8f) * Split the API_VERSION variable into client and provider versions. Also create separate -dev packages for the two versions. (a7db92d) * Add a pkg-config file for the real client library. (9ffcd7d) 2016-07-11 Michi Henning * Simplified handler callbacks with decltype. (3e3f07a) 2016-07-10 James Henstridge * Contraint Handler template constructor a bit more: it looks like we can declare the closure using decltype() without getting compile errors. (0e594ad) 2016-07-09 James Henstridge * Merge from devel, fixing conflicts (022b1ff) 2016-07-08 Michi Henning * More Q_OBJECT cleanup. (eb2cc1b) * Minor cleanup for unneeded moc includes and Q_OBJECT macros. (610758b) * Removed Runnable from Handler. (f9591e4) 2016-07-07 Michi Henning * Factored out item creation from metadata. (d735e52) * Removed redundant #includes. (246e1f8) * Factored out reply error checking. (6e3aa2d) * Refactored reply handling to use template. (8c0ae65) 2016-07-06 Michi Henning * Fixed file descriptor life cycle problems. (b819f50) * Added make_future templates. (93ba8e2) * Added visibility hiddent to privated constructors. (293b2db) * Proper exception handling for local client. (e191ffc) 2016-07-01 Michi Henning * Moved metadata methods into Item. (445cfe0) 2016-07-01 James Henstridge * Add a build-depend on libonline-accounts-qt-dev. (b7be843) 2016-07-01 Michi Henning * First shot at metadata API. (29259b9) 2016-07-01 James Henstridge * Make debian/rules executable. (1b74257) * Add a test to ensure the Debian package version matches the project version. (c6de386) * Some minor fixes after first build. (9fcfbb0) * Add bzr-builddeb boilerplate. (585593d) * Add binary packages to debian control file. (60aebdf) * Add pkg-config file for Qt local-client, and set its SONAME. (777a388) 2016-07-01 Michi Henning * Finished upload size. (2809546) 2016-07-01 James Henstridge * Install header files. (352dcfe) * Add a description to pkg-config file. (b12a48e) 2016-06-30 James Henstridge * Add a pkg-config file for the libstorage-framework-provider. (fb18fc4) 2016-06-30 Michi Henning * First few modifications for upload size. (533e9c3) * Added etag() method. (b723682) 2016-06-30 James Henstridge * Add some dependencies to debian/control. (dc20426) * Install the provider and client libraries. (279315b) 2016-06-29 Michi Henning * Merged devel. Removed a bunch of redundant std:: prefixes in the provider demo. (7d85ff6) 2016-06-29 James Henstridge * Add DownloadJob, and the remaining D-Bus methods to ProviderBase. (518b1a7) * Update Python test script to exercise more of the D-Bus API. (9303a3a) * Add early reporting of failures to Uploadjob. (ace9d3e) 2016-06-28 Michi Henning * Removed some empty namespaces. (27a47b0) * Minor doc tidy-up. (ae36a4e) 2016-06-28 James Henstridge * Add support for reporting completion of the download early. (e013ba6) 2016-06-28 Michi Henning * Removed some debug trace. (0c25121) * Some basic tests for remote client API. (bff24d4) * shared_ptr in handlers so things can't accidentally disappear. (c187761) * Implemented remote client side. (2d678ab) 2016-06-24 James Henstridge * Fix up some unused variable declarations, and make sure the closures being used as future continuations keep the AccountData instance alive. (02632ec) 2016-06-24 Michi Henning * Added CancelledException. Change finish_upload to return File. (b307d05) 2016-06-23 James Henstridge * Hide some methods on UploadJob/DownloadJob that shouldn't be called by provider implementations. (ad14eb5) * Fill out remaining D-Bus methods. (4d3c1b1) 2016-06-22 Michi Henning * Merged devel. (2aab7e5) * Merged changes from add_tests branch (now in devel). (75058be) 2016-06-22 James Henstridge * Add DownloadJob management to PendingJobs. (8c85f97) * Add DownloadJob class. (59f54e0) 2016-06-21 James Henstridge * Pass online accounts credentials to ProviderBase methods via the Context struct. Also refactor the internal classes a bit to simplify memory management. (238aeba) * Merge from devel, fixing conflicts. (406c4e7) 2016-06-21 Michi Henning * Merged add_tests branch. (c51b37c) * Temp check-in. (4fe0901) * Temp check-in, moving machines. (c023643) * Merged devel. (15b8a11) * disconnectFromServer() now called as part of finish_upload(). (0c6c917) 2016-06-21 James Henstridge * Add UploadJob API to the provider library. (0c827a4) 2016-06-21 Michi Henning * Moving machines. (648bcb9) 2016-06-17 James Henstridge * Rename CredentialsCache to DBusPeerCache to avoid confusion with online accounts credentials. (72abc25) * Pass in online accounts credentials through Context structure. (977bf5f) * jobs_ doesn't need to be a shared pointer anymore. (7275bae) * Just pass the AccountData object to the Handler's callback rather than passing the provider and jobs object separately. (ba21467) * Move ProviderInterface over to using AccountData to manage the account information. (5e5fe86) 2016-06-16 James Henstridge * Add AccountData class to manage authentication and hold the jobs and other state for the account. (a55deff) 2016-06-16 Michi Henning * Using readChannelFinished signal now instead of disconnected signal. (a35daf7) 2016-06-16 James Henstridge * Extract credentials from online-accounts reply. (1cfb754) 2016-06-15 James Henstridge * Fix marshalling of Item.metadata map, pointed out by Michi in review. (67efa3f) 2016-06-15 Michi Henning * Fix for boost 1.55 copy_file issue. (55b4d93) * Fall back to conventional temp file creation if O_TMPFILE isn't supported. (214158e) 2016-06-14 Michi Henning * Fixed incorrect library dependencies. Added -Wl,--no-undefined when building .so. (0ab4863) 2016-06-13 James Henstridge * Make MainLoopExecutor subclass boost::executors::executor, in hope of better compatibility with yakkety's boost. (9cf9a51) 2016-06-13 Michi Henning * Got rid of busy-wait for thread initialization. (d6a3071) * Removed StorageSocket. (2e39ad7) 2016-06-09 James Henstridge * Use unique_ptr to manage TempfileUploadJobImpl's child QObjects rather than Qt's object tree memory management. The UploadJobImpl class gets deleted with deleteLater(), so destruction will happen in the correct thread, and not be re-entering any problem code. (f3d29ff) 2016-06-09 Michi Henning * Temp check-in, moving machines. (6e4ec7b) * Added accounts creation from online accounts info. (282c64c) 2016-06-09 James Henstridge * Complete initialisation of the UploadJobImpl in the event loop thread so that signals can be delivered correctly. (9c0fdc6) 2016-06-08 James Henstridge * Add a Python program to test out the upload code path in the D-Bus provider. (267f2af) * Fix up a few issues picked up by test upload client. (6d2bcba) * Remove DisconnectWatcher, since it has been folded into PendingJobs. (3befdbb) * Add a mutex to protect PendingJobs from access by other threads. (6d5680d) * Hook in Update(), CancelUpload() and FinishUpload(). (50a12f5) 2016-06-08 Michi Henning * Fixed broken MakeLists.txt for the tests. (ae6771b) * Big refactor. Added remote client skeletons. (19b6d51) 2016-06-08 James Henstridge * Cancel pending uploads if the client drops off the bus. (b163463) 2016-06-07 James Henstridge * Add PendingJobs class. (1ff4a68) 2016-06-06 Michi Henning * Merged dependent branch. (aa4a44b) * Moved ItemMedata to storage::internal. (bf806ce) * Slightly more coverage. (643fd79) * Update modified time after upload. (c41f441) * Uploader thread. (4b612b3) 2016-06-05 Michi Henning * Minor tidy-up. (ae45789) * Downloader with worker thread. (be966e6) 2016-06-03 James Henstridge * Use the executor interface so that our future continuations are run in the event loop thread. (c2f7229) * Expose CreateFile over D-Bus. Still needs to manage UploadJob objects cancelation and completion. (d400219) 2016-06-03 Michi Henning * Merged devel. (8f0aa5e) * Minor tidy-up. (e664671) * More tests and minor fixes. (1baf433) * Lots more tests and fixes for upload/download. (0630d10) 2016-06-02 James Henstridge * Add UploadJob classes. (eb31e5f) 2016-06-01 James Henstridge * Add class to track disconnections from the bus. (742c4c9) 2016-06-01 Michi Henning * Protected socket read methods. (fb0399b) * Added parent_ids(). For root, changed return value of parents() and parent_ids() to empty vector. (6df7a7f) * Slight more coverage. (51b10b4) * Fixed recursive copy. More and simpler tests. (479474a) 2016-05-31 James Henstridge * Create one instance of the ProviderBase for each account providing the relevant service ID, and attempt a non-interactive authentication to retrieve the credentials. (0556e01) 2016-05-31 Michi Henning * Download tests. (a931ce7) * More tests, including uploader. (8b5925c) 2016-05-27 Michi Henning * More tests. Need to think about destroy and threading some mor. the atomic flag may not be good enough. (6b8ba77) * Added some basic functionality unit tests. (27b9571) 2016-05-26 Michi Henning * finish_{up,down}load() now waits for disconnection and is async. state_ is thread-safe now. (3443acf) * Added create_file() implementation. (52087c2) * Don't leak socket pair descriptors. Minor include file tidy-up. (79fb8d2) * Merged parent. (35a7bac) * Fix incorrect eof detection in uploader. (d5dc5b5) 2016-05-25 Michi Henning * A few #include clean-ups. (68f0dff) * Minor tidy-up. (bae4166) * Improved comment. (bd63f85) * Added uploader implementation. Not tested yet. (812d8f5) 2016-05-25 James Henstridge * Push setting of account service ID up to provider-test.cpp. (ccbadab) * Request authentication details from account non-interactively. (6b0e43b) 2016-05-25 Michi Henning * Download implemented, superficially tested. (21999e9) 2016-05-24 James Henstridge * Construct one instance of the provider for each online account. (36dd0be) 2016-05-23 James Henstridge * Move server implementation to an internal impl class. (8c9b39a) 2016-05-23 Michi Henning * Start of Qt API. (05e4518) * Added recursive move and copy. Metadata and modified time are synchronous now. (edc8b3b) * More fleshing out of the local client implementation. (a8a4da8) 2016-05-18 Michi Henning * Complete libs for qt and qt-local client lib. (bfb0fae) * Hoisted ItemType up into common namespace. Got rid of mime_type() and replaced with type() returning ItemType enum. (b9fb3e5) * Added move() and copy(). Review comments from James. (26f0af8) 2016-05-17 James Henstridge * Add unity::provider::Context class holding information about the client app, and pass it to the various ProviderBase methods. (2f85129) 2016-05-13 James Henstridge * Pass client details to ProviderBase as an extra Context argument. (33e1489) 2016-05-12 James Henstridge * Create credentials cache, and pass it down to the underlying Handler instances. (1b8176d) * Add credentialscache code. (3576fb4) * Merge in skeleton of backend provider support library. (0625a38) * Rename upload_fd/download_fd to just file_descriptor to avoid confusion with upload_id/download_id. (3c96ee6) * Code style updates from review. (c1d5a45) 2016-05-11 Michi Henning * Adjusted API to match DBus side. (b04eb9d) 2016-05-11 James Henstridge * Fix a few cases where I still had the CompleteUpload name in documentation. (773af24) 2016-05-10 James Henstridge * Change error name so Qt will actually send it. (7609f4a) * Add demo script, and fix up bugs preventing method calls from being handled. (c03e542) * Add Server class. (c4555b6) 2016-05-06 James Henstridge * Add marshalling code for Item class. (e97ad77) * Add Handler class. (925cdcc) 2016-05-05 James Henstridge * Add QtDBus skeletons. (e9114f0) 2016-05-04 James Henstridge * Update d-bus interface XML. (7874d1e) 2016-05-03 Michi Henning * More skeleton code for public and internal APIs. (fa3a63d) * More skeleton files for public and internal implementations. (f75eee6) 2016-04-27 Michi Henning * Qt version of client API. (ffa88de) 2016-04-20 Michi Henning * Documentation from Ted. (d357311) * Fixed header test. (09b2c26) * More build env scaffolding. (d7cf45e) * Added debian/rules. (4c8f38d) * Dummy first entry for changelog. (6d1e2a3) * Bits of the debian files. (6c0ed9c) * Started basic build env. (b8eef18) 2016-04-19 Michi Henning * Added account, root, runtime, metadata classes. (d1aa96f) 2016-04-18 Michi Henning * First steps towards a client-side API. (f68aa44) lomiri-storage-framework-0.5.0/HACKING000066400000000000000000000071211521521330000174240ustar00rootroot00000000000000Building the code ----------------- By default, the code is built in release mode. To build a debug version, use $ mkdir builddebug $ cd builddebug $ cmake -DCMAKE_BUILD_TYPE=debug .. $ make For a release version, use -DCMAKE_BUILD_TYPE=release Running the tests ----------------- $ make $ make test Note that "make test" alone is dangerous because it does not rebuild any tests if either the library or the test files themselves need rebuilding. It's not possible to fix this with cmake because cmake cannot add build dependencies to built-in targets. To make sure that everything is up-to-date, run "make" before running "make test"! To run the tests with valgrind: $ make valgrind It doesn't make sense for some tests to run them with valgrind. For example, the header compilation tests don't need valgrind because we'd just be testing that Python doesn't leak. There are also some tests that run too slow and time out under valgrind and, occasionally, valgrind crashes for a particular test. There are two ways to suppress tests: You can add a test name to CTestCustom.cmake.in to suppress that test completely. That makes sense for the header compilation tests, for example. If a specific test case in a test program causes a valgrind problem, you can selectively disable a section of code like this: #include if (!RUNNING_ON_VALGRIND) { // Code here crashes valgrind... } That way, the test will still be run as part of the normal "make test" target, but will be ommitted when running "make valgrind". Coverage -------- To build with the flags for coverage testing enabled and get coverage: $ mkdir buildcoverage $ cd buildcoverage $ cmake -DCMAKE_BUILD_TYPE=coverage $ make $ make test $ make coverage Unfortunately, it is not possible to get 100% coverage for some files, mainly due to gcc's generation of two destructors for dynamic and non- dynamic instances. For abstract base classes and for classes that prevent stack and static allocation, this causes one of the destructors to be reported as uncovered. There are also issues with some functions in header files that are incorrectly reported as uncovered due to inlining, as well as the impossibility of covering defensive assert(false) statements, such as an assert in the default branch of a switch, where the switch is meant to handle all possible cases explicitly. If you run a binary and get lots of warnings about a "merge mismatch for summaries", this is caused by having made changes to the source that add or remove code that was previously run, so the new coverage output cannot sensibly be merged into the old coverage output. You can get rid of this problem by running $ make clean-coverage This deletes all the .gcda files, allowing the merge to (sometimes) succeed again. If this doesn't work either, the only remedy is to do a clean build. Code style ---------- Please maintain the existing coding style. For details on the style, see lp:canonical-client-development-guidelines. We use a format tool that fixes a whole lot of issues regarding code style. See the HACKING file of lp:lomiri-scopes-api for details on the tool. Undefined behavior and address sanitizer ---------------------------------------- Set SANITIZER to "ub" or "address" to build with the corresponding sanitizer enabled. If a test runs too slowly under address sanitizer, you can hide a section of code from address sanitzer with: #if defined(__has_feature) #if !__has_feature(address_sanitizer) // Code here takes forever under address sanitizer... #endif #endif lomiri-storage-framework-0.5.0/data/000077500000000000000000000000001521521330000173455ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/data/build-docs.sh000077500000000000000000000017421521521330000217350ustar00rootroot00000000000000#!/bin/bash # # Copyright (C) 2016 Canonical Ltd # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License version 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that 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 program. If not, see . # # Authors: James Henstridge # set -e mkdir doc-temp || true cd doc-temp gdbus-codegen --generate-docbook=docbook ../registry.xml ../provider.xml find . -name "docbook*.xml" -exec docbook2x-texi {} \; find . -name "com.lomiri.*.texi" -exec texi2pdf --quiet {} \; pdfunite com.lomiri.*.pdf ../dbus-interface.pdf lomiri-storage-framework-0.5.0/data/metadata-types.yaml000066400000000000000000000015021521521330000231510ustar00rootroot00000000000000# Name that should be shown to the user display-name: string # Whether there is data associated with this node has-document: boolean # If there is data, how much? In bytes document-size: uint64 # When was it last modified? modification-time: date # If there's a thumbnail, what is its document id thumbnail: document-id # What are the MIME types of the data mimetype: string-list # Is there a copy of the document local? document-local: boolean # Whether there is a copy on the service we're exporting # to. Mostly useful for content originating locally to # ensure it has been uploaded. copy-on-service: boolean ###### # NOTE: These are the default entries that are defined, various # backends can provide their own metadata keys in the x- namespace. # That can then be supported by their libraries or developer docs. ###### lomiri-storage-framework-0.5.0/data/provider.xml000066400000000000000000000306761521521330000217350ustar00rootroot00000000000000 lomiri-storage-framework-0.5.0/data/registry.xml000066400000000000000000000027071521521330000217450ustar00rootroot00000000000000 lomiri-storage-framework-0.5.0/debian/000077500000000000000000000000001521521330000176565ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/debian/Jenkinsfile000066400000000000000000000006511521521330000220440ustar00rootroot00000000000000@Library('ubports-build-tools') _ buildAndProvideDebianPackage() // Or if the package consists entirely of arch-independent packages: // (optional optimization, will confuse BlueOcean's live view at build stage) // buildAndProvideDebianPackage(/* isArchIndependent */ true) // Optionally, to skip building on some architectures (amd64 is always built): // buildAndProvideDebianPackage(false, /* ignoredArchs */ ['arm64']) lomiri-storage-framework-0.5.0/debian/changelog000066400000000000000000000056531521521330000215410ustar00rootroot00000000000000lomiri-storage-framework (0.5.0) unstable; urgency=medium * Upstream-provided Debian package for lomiri-storage-framework. See upstream ChangeLog for recent changes. -- UBports developers Fri, 19 Jun 2026 12:14:39 +0200 lomiri-storage-framework (0.4.0) unstable; urgency=medium * Upstream-provided Debian package for lomiri-storage-framework. See upstream ChangeLog for recent changes. -- UBports developers Thu, 23 Oct 2025 18:37:01 +0200 lomiri-storage-framework (0.4) noble; urgency=medium [ Alfred Neumayer] * Rename to Lomiri namespace * Bring up on noble -- Alfred Neumayer Tue, 01 Jul 2025 10:52:17 +0200 storage-framework (0.3) focal; urgency=medium [ James Henstridge ] * Make providers exit after 30 seconds of inactivity. (LP: #1616758) * Add UnauthorizedException to the provider library, so the provider can trigger reauthentication of the account and have the request restarted. (LP: #1616756) * Dynamically add and remove providers as the associated accounts are enabled and disabled. (LP: #1616757) * Allow creation of storage providers that don't use online-accounts. Thisis likely only of interest to the local storage provider. [ Michi Henning ] * Add a storage provider backed by the local file system. * Move unity::storage::provider::Item to its own header file. (LP: #1668872) * If a provider can't acquire its D-Bus well known name, exit rather than throwing a (usually uncaught) exception. (LP: #1604640) -- Michi Henning Mon, 20 Mar 2017 04:51:08 +0000 storage-framework (0.2+17.04.20161212.1-0ubuntu1) zesty; urgency=medium * Fix for lp:1644577, fail list job if metadata for any item is incorrect. * Always emit itemsReady(), even if list is empty. * Improvements to error logging and detail in error messages: lp:1644577 * Added separate registry service. -- Michi Henning Mon, 12 Dec 2016 02:54:46 +0000 storage-framework (0.2+17.04.20161104-0ubuntu1) zesty; urgency=medium [ Michi Henning ] * Added v2 of the client-side API. * Updated server-side API to tell the provider which metadata to return. [ James Henstridge ] * Update provider API to manager ProviderBase class as a shared_ptr. * Update client to discover ownCloud/Nextcloud and OneDrive accounts. * Add match_etag argument to Download() D-Bus method. -- Michi Henning Fri, 04 Nov 2016 12:22:33 +0000 storage-framework (0.1+16.10.20160909-0ubuntu1) yakkety; urgency=medium * Merged devel at revision 64. -- Michi Henning Fri, 09 Sep 2016 02:36:03 +0000 storage-framework (0.1+16.10.20160804.1-0ubuntu1) yakkety; urgency=medium [ Michi Henning ] * Initial release. [ James Henstridge, Michi Henning ] * Initial release of storage framework. -- Michi Henning Thu, 04 Aug 2016 07:20:09 +0000 lomiri-storage-framework-0.5.0/debian/control000066400000000000000000000137151521521330000212700ustar00rootroot00000000000000Source: lomiri-storage-framework Section: libs Priority: optional Maintainer: UBports Developers Standards-Version: 4.7.2 Build-Depends: cmake, cmake-extras (>= 0.10), debhelper-compat (= 13), doxygen, google-mock, libapparmor-dev, libboost-filesystem-dev (>= 1.58) | libboost-filesystem1.58-dev, libboost-system-dev (>= 1.58) | libboost-system1.58-dev, libboost-thread-dev (>= 1.58) | libboost-thread1.58-dev, libglib2.0-dev, libgtest-dev, liblomiri-online-accounts-qt5-dev, liblomiri-online-accounts-qt6-dev, libqtdbustest1-dev, libqtdbustest-qt6-dev, liblomiri-api-dev, licensecheck | devscripts (<< 2.16.6), lsb-release, python3-dbus, python3-gi, qtbase5-dev, qtbase5-dev-tools, qtdeclarative5-dev, qt6-base-dev, qt6-base-dev-tools, qt6-declarative-dev, Homepage: https://gitlab.com/ubports/development/core/storage-framework Package: liblomiri-storage-framework-provider1 Architecture: any Multi-Arch: same Pre-Depends: ${misc:Pre-Depends}, Depends: ${misc:Depends}, ${shlibs:Depends}, Description: Library for Storage Framework providers API for storage framework clients and providers in Lomiri. . Server-side runtime support for provider implementations. Package: liblomiri-storage-framework-qt6-provider-1-1 Architecture: any Multi-Arch: same Pre-Depends: ${misc:Pre-Depends}, Depends: ${misc:Depends}, ${shlibs:Depends}, Description: Library for Storage Framework providers API for storage framework clients and providers in Lomiri. . Server-side runtime support for provider implementations. Package: liblomiri-storage-framework-qt-client-1-0 Architecture: any Multi-Arch: same Pre-Depends: ${misc:Pre-Depends}, Depends: ${misc:Depends}, ${shlibs:Depends}, Description: Client library for the Storage Framework (API v1, soon to be removed) API for storage framework clients and providers in Lomiri. . Runtime support for storage framework clients (Qt5 API v1, soon to be removed). Package: lomiri-storage-framework-registry Architecture: any Multi-Arch: foreign Pre-Depends: ${misc:Pre-Depends}, Depends: ${misc:Depends}, ${shlibs:Depends}, Description: Registry for the Storage Framework API for storage framework clients and providers in Lomiri. . DBus service that provides access to provider account information. Includes a local storage provider. Package: liblomiri-storage-framework-qt-client-2-0 Architecture: any Multi-Arch: same Pre-Depends: ${misc:Pre-Depends}, Depends: ${misc:Depends}, ${shlibs:Depends}, lomiri-storage-framework-registry Description: Client library for the Storage Framework (API v2) API for storage framework clients and providers in Lomiri. . Runtime support for storage framework clients (Qt5 API v2). Package: liblomiri-storage-framework-qt6-client-1-0 Architecture: any Multi-Arch: same Pre-Depends: ${misc:Pre-Depends}, Depends: ${misc:Depends}, ${shlibs:Depends}, lomiri-storage-framework-registry Description: Client library for the Storage Framework (Qt 6) API for storage framework clients and providers in Lomiri. . Runtime support for storage framework clients (Qt 6). Package: liblomiri-storage-framework-qt-local-client-1-0 Architecture: any Multi-Arch: same Pre-Depends: ${misc:Pre-Depends}, Depends: ${misc:Depends}, ${shlibs:Depends}, Description: Client library for the Storage Framework backed by the local file system API for storage framework clients and providers in Lomiri. . A version of the client-side API that implements a local file system provider. This is intended mainly for testing; it allows application code to use the storage framework API without requiring use of DBus and cloud service account. Package: lomiri-storage-framework-client-dev Section: libdevel Architecture: any Multi-Arch: same Pre-Depends: ${misc:Pre-Depends}, Depends: liblomiri-storage-framework-qt-client-1-0 (= ${binary:Version}), liblomiri-storage-framework-qt-local-client-1-0 (= ${binary:Version}), liblomiri-storage-framework-qt-client-2-0 (= ${binary:Version}), qtbase5-dev, ${misc:Depends}, Description: Header files for the Storage Framework client libraries API for storage framework clients and providers in Lomiri. . Development C++ headers for the Qt5 client side API. Package: lomiri-storage-framework-qt6-client-dev Section: libdevel Architecture: any Multi-Arch: same Pre-Depends: ${misc:Pre-Depends}, Depends: liblomiri-storage-framework-qt6-client-1-0 (= ${binary:Version}), qt6-base-dev, ${misc:Depends}, Description: Header files for the Storage Framework client libraries API for storage framework clients and providers in Lomiri. . Development C++ headers for the Qt 6 client side API. Package: lomiri-storage-framework-provider-dev Section: libdevel Architecture: any Multi-Arch: same Pre-Depends: ${misc:Pre-Depends}, Depends: liblomiri-storage-framework-provider1 (= ${binary:Version}), libboost-thread-dev (>= 1.58) | libboost-thread1.58-dev, ${misc:Depends}, Description: Header files for the Storage Framework provider library API for storage framework clients and providers in Lomiri. . Development C++ headers for the provider API. Package: lomiri-storage-framework-qt6-provider-dev Section: libdevel Architecture: any Multi-Arch: same Pre-Depends: ${misc:Pre-Depends}, Depends: liblomiri-storage-framework-qt6-provider-1-1 (= ${binary:Version}), libboost-thread-dev (>= 1.58) | libboost-thread1.58-dev, ${misc:Depends}, Description: Header files for the Storage Framework provider library API for storage framework clients and providers in Lomiri. . Development C++ headers for the provider API. lomiri-storage-framework-0.5.0/debian/copyright000066400000000000000000000015001521521330000216050ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: lomiri-storage-framework Source: https://gitlab.com/ubports/development/core/storage-framework Files: * Copyright: 2016 Canonical Ltd. License: LGPL-3 This program is free software: you can redistribute it and/or modify it under the terms of version 3 of the GNU Lesser General Public License as published by the Free Software Foundation. . This program is distributed in the hope that 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. . On Debian systems, the full text of the GNU Lesser General Public License version 3 can be found in the file `/usr/share/common-licenses/LGPL-3' lomiri-storage-framework-0.5.0/debian/liblomiri-storage-framework-provider1.install000066400000000000000000000000651521521330000305770ustar00rootroot00000000000000usr/lib/*/liblomiri-storage-framework-provider.so.1* lomiri-storage-framework-0.5.0/debian/liblomiri-storage-framework-qt-client-1-0.install000066400000000000000000000000701521521330000310530ustar00rootroot00000000000000usr/lib/*/liblomiri-storage-framework-qt-client-1.so.0* lomiri-storage-framework-0.5.0/debian/liblomiri-storage-framework-qt-client-2-0.install000066400000000000000000000000701521521330000310540ustar00rootroot00000000000000usr/lib/*/liblomiri-storage-framework-qt-client-2.so.0* lomiri-storage-framework-0.5.0/debian/liblomiri-storage-framework-qt-local-client-1-0.install000066400000000000000000000000761521521330000321510ustar00rootroot00000000000000usr/lib/*/liblomiri-storage-framework-qt-local-client-*.so.0* lomiri-storage-framework-0.5.0/debian/liblomiri-storage-framework-qt6-client-1-0.install000066400000000000000000000000711521521330000311420ustar00rootroot00000000000000usr/lib/*/liblomiri-storage-framework-qt6-client-1.so.0* lomiri-storage-framework-0.5.0/debian/liblomiri-storage-framework-qt6-provider-1-1.install000066400000000000000000000000731521521330000315210ustar00rootroot00000000000000usr/lib/*/liblomiri-storage-framework-qt6-provider-1.so.1* lomiri-storage-framework-0.5.0/debian/lomiri-storage-framework-client-dev.install000066400000000000000000000002271521521330000302270ustar00rootroot00000000000000usr/include/lomiri-storage-framework-client-* usr/lib/*/liblomiri-storage-framework*client*.so usr/lib/*/pkgconfig/lomiri-storage-framework*client*.pc lomiri-storage-framework-0.5.0/debian/lomiri-storage-framework-provider-dev.install000066400000000000000000000002351521521330000306020ustar00rootroot00000000000000usr/include/lomiri-storage-framework-provider-* usr/lib/*/liblomiri-storage-framework-provider*.so usr/lib/*/pkgconfig/lomiri-storage-framework-provider*.pc lomiri-storage-framework-0.5.0/debian/lomiri-storage-framework-qt6-client-dev.install000066400000000000000000000002451521521330000307370ustar00rootroot00000000000000usr/include/lomiri-storage-framework-qt6-client-1 usr/lib/*/liblomiri-storage-framework-qt6-client-1.so usr/lib/*/pkgconfig/lomiri-storage-framework-qt6-client-1.pc lomiri-storage-framework-0.5.0/debian/lomiri-storage-framework-qt6-provider-dev.install000066400000000000000000000002531521521330000313120ustar00rootroot00000000000000usr/include/lomiri-storage-framework-qt6-provider-1 usr/lib/*/liblomiri-storage-framework-qt6-provider-1.so usr/lib/*/pkgconfig/lomiri-storage-framework-qt6-provider-1.pc lomiri-storage-framework-0.5.0/debian/lomiri-storage-framework-registry.install000066400000000000000000000003601521521330000300430ustar00rootroot00000000000000usr/libexec/*/lomiri-storage-framework-registry usr/share/dbus-1/services/com.lomiri.StorageFramework.Registry.service usr/libexec/*/lomiri-storage-provider-local usr/share/dbus-1/services/com.lomiri.StorageFramework.Provider.Local.service lomiri-storage-framework-0.5.0/debian/rules000077500000000000000000000036441521521330000207450ustar00rootroot00000000000000#!/usr/bin/make -f # -*- makefile -*- # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 export DPKG_GENSYMBOLS_CHECK_LEVEL=4 export DEB_BUILD_MAINT_OPTIONS = hardening=+all DPKG_EXPORT_BUILDFLAGS = 1 include /usr/share/dpkg/buildflags.mk # http://ccache.samba.org/manual.html#_precompiled_headers export CCACHE_SLOPPINESS=time_macros export QT_SELECT=qt5 %: dh $@ override_dh_auto_configure: dh_auto_configure --builddirectory=build-qt5 --buildsystem=cmake .. -- \ -DENABLE_QT6=OFF \ -DCHECK_WHITESPACE_IGNORE=$(CURDIR)/build-qt6\;$(CURDIR)/build-qt5 \ -DCHECK_COPYRIGHT_IGNORE=$(CURDIR)/build dh_auto_configure --builddirectory=build-qt6 --buildsystem=cmake .. -- \ -DENABLE_QT6=ON \ -DCHECK_WHITESPACE_IGNORE=$(CURDIR)/build-qt6\;$(CURDIR)/build-qt5 \ -DCHECK_COPYRIGHT_IGNORE=$(CURDIR)/build override_dh_auto_build: dh_auto_build --builddirectory=build-qt6 --buildsystem=cmake .. dh_auto_build --builddirectory=build-qt5 --buildsystem=cmake .. # Tests are not written to be run in parallel. # We ignore failing tests on PPC and s390x because the storage-framework is irrelevant there. test_args = ARGS=\"--verbose\" override_dh_auto_test: ifneq (,$(filter powerpc ppc64el s390x,$(DEB_HOST_ARCH))) -dh_auto_test --builddirectory=build-qt5 --buildsystem=cmake --no-parallel .. -- $(test_args) -dh_auto_test --builddirectory=build-qt6 --buildsystem=cmake --no-parallel .. -- $(test_args) else dh_auto_test --builddirectory=build-qt5 --buildsystem=cmake --no-parallel .. -- $(test_args) dh_auto_test --builddirectory=build-qt6 --buildsystem=cmake --no-parallel .. -- $(test_args) endif override_dh_auto_install: dh_auto_install --builddirectory=build-qt6 --buildsystem=cmake .. dh_auto_install --builddirectory=build-qt5 --buildsystem=cmake .. override_dh_auto_clean: dh_auto_clean --builddirectory=build-qt5 --buildsystem=cmake .. dh_auto_clean --builddirectory=build-qt6 --buildsystem=cmake .. lomiri-storage-framework-0.5.0/debian/source/000077500000000000000000000000001521521330000211565ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/debian/source/format000066400000000000000000000000041521521330000223630ustar00rootroot000000000000001.0 lomiri-storage-framework-0.5.0/debian/ubports.skip_distro000066400000000000000000000000721521521330000236270ustar00rootroot00000000000000# Remove Debian from target distros for now. devel-debian lomiri-storage-framework-0.5.0/demo/000077500000000000000000000000001521521330000173605ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/demo/CMakeLists.txt000066400000000000000000000000401521521330000221120ustar00rootroot00000000000000add_subdirectory(provider_test) lomiri-storage-framework-0.5.0/demo/demo.qml000066400000000000000000000032321521521330000210170ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: James Henstridge * Michi Henning */ import QtQuick 2.0 import Ubuntu.StorageFramework 0.1 as SF // From Build dir, run with: // qmlscene -I plugins $srcdir/demo/demo.qml Item { id: root width: 100 height: 100 property var accountsjob: null; SF.Runtime { id: runtime Component.onCompleted: root.accountsjob = runtime.accounts() } Connections { target: root.accountsjob onStatusChanged: { console.log("AccountsJob status changed to " + status); if (status == SF.AccountsJob.Finished) { var accounts = root.accountsjob.accounts; console.log("Got accounts " + accounts); for (var i = 0; i < accounts.length; i++) { console.log("Account " + i + ": busName = " + accounts[i].busName()); console.log("Account " + i + ": objectPath = " + accounts[i].objectPath()); console.log("Account " + i + ": name = " + accounts[i].name); } } } } } lomiri-storage-framework-0.5.0/demo/provider_test/000077500000000000000000000000001521521330000222515ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/demo/provider_test/CMakeLists.txt000066400000000000000000000002421521521330000250070ustar00rootroot00000000000000add_definitions(-DBOOST_THREAD_VERSION=4) add_executable(provider-test provider-test.cpp) target_link_libraries(provider-test lomiri-storage-framework-provider) lomiri-storage-framework-0.5.0/demo/provider_test/provider-test.cpp000066400000000000000000000273401521521330000255720ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #include #include #include #include #include #include #include #include #include #include #include using namespace lomiri::storage; using namespace lomiri::storage::provider; using namespace std; using boost::make_ready_future; using boost::make_exceptional_future; class MyProvider : public ProviderBase { public: MyProvider(); boost::future roots(vector const& keys, Context const& ctx) override; boost::future> list( string const& item_id, string const& page_token, vector const& keys, Context const& ctx) override; boost::future lookup( string const& parent_id, string const& name, vector const& keys, Context const& ctx) override; boost::future metadata( string const& item_id, vector const& keys, Context const& ctx) override; boost::future create_folder( string const& parent_id, string const& name, vector const& keys, Context const& ctx) override; boost::future> create_file( string const& parent_id, string const& name, int64_t size, string const& content_type, bool allow_overwrite, vector const& keys, Context const& ctx) override; boost::future> update( string const& item_id, int64_t size, string const& old_etag, vector const& keys, Context const& ctx) override; boost::future> download( string const& item_id, string const& match_etag, Context const& ctx) override; boost::future delete_item( string const& item_id, Context const& ctx) override; boost::future move( string const& item_id, string const& new_parent_id, string const& new_name, vector const& keys, Context const& ctx) override; boost::future copy( string const& item_id, string const& new_parent_id, string const& new_name, vector const& keys, Context const& ctx) override; }; class MyUploadJob : public TempfileUploadJob { public: using TempfileUploadJob::TempfileUploadJob; boost::future cancel() override; boost::future finish() override; }; class MyDownloadJob : public DownloadJob { public: using DownloadJob::DownloadJob; boost::future cancel() override; boost::future finish() override; }; MyProvider::MyProvider() { } boost::future MyProvider::roots(vector const& keys, Context const& ctx) { printf("roots() called by %s (%d)\n", ctx.security_label.c_str(), ctx.pid); fflush(stdout); ItemList roots = { {"root_id", {}, "Root", "etag", ItemType::root, {}}, }; return make_ready_future(roots); } boost::future> MyProvider::list( string const& item_id, string const& page_token, vector const& keys, Context const& ctx) { printf("list('%s', '%s') called by %s (%d)\n", item_id.c_str(), page_token.c_str(), ctx.security_label.c_str(), ctx.pid); fflush(stdout); if (item_id != "root_id") { string msg = string("Item::list(): no such item: \"") + item_id + "\""; return make_exceptional_future>(NotExistsException(msg, item_id)); } if (page_token != "") { string msg = string("Item::list(): invalid page token: \"") + page_token + "\""; return make_exceptional_future>(LogicException(msg)); } ItemList children = { { "child_id", { "root_id" }, "Child", "etag", ItemType::file, { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } } } }; boost::promise> p; p.set_value(make_tuple(children, string())); return p.get_future(); } boost::future MyProvider::lookup( string const& parent_id, string const& name, vector const& keys, Context const& ctx) { printf("lookup('%s', '%s') called by %s (%d)\n", parent_id.c_str(), name.c_str(), ctx.security_label.c_str(), ctx.pid); fflush(stdout); if (parent_id != "root_id") { string msg = string("Folder::lookup(): no such item: \"") + parent_id + "\""; return make_exceptional_future(NotExistsException(msg, parent_id)); } if (name != "Child") { string msg = string("Folder::lookup(): no such item: \"") + name + "\""; return make_exceptional_future(NotExistsException(msg, name)); } ItemList children = { { "child_id", { "root_id" }, "Child", "etag", ItemType::file, { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } } } }; return make_ready_future(children); } boost::future MyProvider::metadata(string const& item_id, vector const& keys, Context const& ctx) { printf("metadata('%s') called by %s (%d)\n", item_id.c_str(), ctx.security_label.c_str(), ctx.pid); fflush(stdout); if (item_id == "root_id") { Item metadata{"root_id", {}, "Root", "etag", ItemType::root, {}}; return make_ready_future(metadata); } else if (item_id == "child_id") { Item metadata { "child_id", { "root_id" }, "Child", "etag", ItemType::file, { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } } }; return make_ready_future(metadata); } else if (item_id == "child_folder_id") { Item metadata{"child_folder_id", { "root_id" }, "Child_Folder", "etag", ItemType::folder, {}}; return make_ready_future(metadata); } return make_exceptional_future(NotExistsException("metadata(): no such item: " + item_id, item_id)); } boost::future MyProvider::create_folder( string const& parent_id, string const& name, vector const& keys, Context const& ctx) { printf("create_folder('%s', '%s') called by %s (%d)\n", parent_id.c_str(), name.c_str(), ctx.security_label.c_str(), ctx.pid); fflush(stdout); Item metadata{"new_folder_id", { parent_id }, name, "etag", ItemType::folder, {}}; return make_ready_future(metadata); } string make_job_id() { static int last_job_id = 0; return to_string(++last_job_id); } boost::future> MyProvider::create_file( string const& parent_id, string const& name, int64_t size, string const& content_type, bool allow_overwrite, vector const& keys, Context const& ctx) { printf("create_file('%s', '%s', %" PRId64 ", '%s', %d) called by %s (%d)\n", parent_id.c_str(), name.c_str(), size, content_type.c_str(), allow_overwrite, ctx.security_label.c_str(), ctx.pid); fflush(stdout); return make_ready_future(unique_ptr(new MyUploadJob(make_job_id()))); } boost::future> MyProvider::update( string const& item_id, int64_t size, string const& old_etag, vector const& keys, Context const& ctx) { printf("update('%s', %" PRId64 ", '%s') called by %s (%d)\n", item_id.c_str(), size, old_etag.c_str(), ctx.security_label.c_str(), ctx.pid); fflush(stdout); return make_ready_future(unique_ptr(new MyUploadJob(make_job_id()))); } boost::future> MyProvider::download( string const& item_id, string const& match_etag, Context const& ctx) { printf("download('%s', '%s') called by %s (%d)\n", item_id.c_str(), match_etag.c_str(), ctx.security_label.c_str(), ctx.pid); fflush(stdout); unique_ptr job(new MyDownloadJob(make_job_id())); const char contents[] = "Hello world"; if (write(job->write_socket(), contents, sizeof(contents)) != sizeof(contents)) { ResourceException e("download(): write failed", errno); job->report_error(make_exception_ptr(e)); return make_exceptional_future>(e); } job->report_complete(); return make_ready_future(std::move(job)); } boost::future MyProvider::delete_item( string const& item_id, Context const& ctx) { printf("delete('%s') called by %s (%d)\n", item_id.c_str(), ctx.security_label.c_str(), ctx.pid); fflush(stdout); return make_ready_future(); } boost::future MyProvider::move( string const& item_id, string const& new_parent_id, string const& new_name, vector const& keys, Context const& ctx) { printf("move('%s', '%s', '%s') called by %s (%d)\n", item_id.c_str(), new_parent_id.c_str(), new_name.c_str(), ctx.security_label.c_str(), ctx.pid); fflush(stdout); Item metadata{item_id, { new_parent_id }, new_name, "etag", ItemType::file, {}}; return make_ready_future(metadata); } boost::future MyProvider::copy( string const& item_id, string const& new_parent_id, string const& new_name, vector const& keys, Context const& ctx) { printf("copy('%s', '%s', '%s') called by %s (%d)\n", item_id.c_str(), new_parent_id.c_str(), new_name.c_str(), ctx.security_label.c_str(), ctx.pid); fflush(stdout); Item metadata{"new_item_id", { new_parent_id }, new_name, "etag", ItemType::file, {}}; return make_ready_future(metadata); } boost::future MyUploadJob::cancel() { printf("cancel_upload('%s')\n", upload_id().c_str()); fflush(stdout); return make_ready_future(); } boost::future MyUploadJob::finish() { printf("finish_upload('%s')\n", upload_id().c_str()); fflush(stdout); string old_filename = file_name(); string new_filename = upload_id() + ".txt"; printf("Linking %s to %s\n", old_filename.c_str(), new_filename.c_str()); fflush(stdout); unlink(new_filename.c_str()); link(old_filename.c_str(), new_filename.c_str()); Item metadata { "some_id", { "root_id" }, "some_upload", "etag", ItemType::file, { { metadata::SIZE_IN_BYTES, 10 }, { metadata::LAST_MODIFIED_TIME, "2011-04-05T14:30:10.005Z" } } }; return make_ready_future(metadata); } boost::future MyDownloadJob::cancel() { printf("cancel_download('%s')\n", download_id().c_str()); fflush(stdout); return make_ready_future(); } boost::future MyDownloadJob::finish() { printf("finish_download('%s')\n", download_id().c_str()); fflush(stdout); return make_ready_future(); } int main(int argc, char **argv) { const std::string bus_name = "com.lomiri.StorageFramework.Provider.ProviderTest"; std::string account_service_id = "storage-provider-test"; if (argc > 1) { account_service_id = argv[1]; } Server server(bus_name, account_service_id); server.init(argc, argv); return server.run(); } lomiri-storage-framework-0.5.0/demo/provider_test/test-client.py000077500000000000000000000121361521521330000250640ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2016 Canonical Ltd # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License version 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that 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 program. If not, see . # # Authored by: James Henstridge # import os import sys from gi.repository import Gio, GLib PROVIDER_BUS_NAME = 'com.lomiri.StorageFramework.Provider.ProviderTest' PROVIDER_IFACE = 'com.lomiri.StorageFramework.Provider' class Provider: def __init__(self, bus, bus_name, object_path): self._proxy = Gio.DBusProxy.new_sync( bus, Gio.DBusProxyFlags.NONE, None, bus_name, object_path, PROVIDER_IFACE, None) def roots(self): return self._proxy.Roots('()') def list(self, item_id, page_token): return self._proxy.List('(ss)', item_id, page_token) def lookup(self, parent_id, name): return self._proxy.Lookup('(ss)', parent_id, name) def metadata(self, item_id): return self._proxy.Metadata('(s)', item_id) def create_folder(self, parent_id, name): return self._proxy.CreateFolder('(ss)', parent_id, name) def create_file(self, parent_id, name, content_type, allow_overwrite): args = GLib.Variant('(sssb)', (parent_id, name, content_type, allow_overwrite)) result, fd_list = self._proxy.call_with_unix_fd_list_sync( 'CreateFile', args, 0, -1) upload_id, fd_idx = result.unpack() assert fd_list.get_length() == 1 assert fd_idx == 0 return upload_id, fd_list.steal_fds()[fd_idx] def update(self, item_id, old_etag=''): args = GLib.Variant('(ss)', (item_id, old_etag)) result, fd_list = self._proxy.call_with_unix_fd_list_sync( 'Update', args, 0, -1) upload_id, fd_idx = result.unpack() assert fd_list.get_length() == 1 assert fd_idx == 0 return upload_id, fd_list.steal_fds()[fd_idx] def cancel_upload(self, upload_id): self._proxy.CancelUpload('(s)', upload_id) def finish_upload(self, upload_id): return self._proxy.FinishUpload('(s)', upload_id) def download(self, item_id): args = GLib.Variant('(s)', (item_id,)) result, fd_list = self._proxy.call_with_unix_fd_list_sync( 'Download', args, 0, -1) download_id, fd_idx = result.unpack() assert fd_list.get_length() == 1 assert fd_idx == 0 return download_id, fd_list.steal_fds()[fd_idx] def finish_download(self, download_id): return self._proxy.FinishDownload('(s)', download_id) def delete(self, item_id): self._proxy.Delete('(s)', item_id) def move(self, item_id, new_parent_id, new_name): return self._proxy.Move('(sss)', item_id, new_parent_id, new_name) def copy(self, item_id, new_parent_id, new_name): return self._proxy.Copy('(sss)', item_id, new_parent_id, new_name) def main(argv): bus = Gio.bus_get_sync(Gio.BusType.SESSION, None) provider = Provider(bus, PROVIDER_BUS_NAME, argv[1]) print("Getting roots...", end='', flush=True) roots = provider.roots() print(roots) print() root_id = roots[0][0] print("Listing %r..." % root_id, end='', flush=True) children, next_token = provider.list(root_id, '') print(children) print() child_id = children[0][0] child_name = children[0][2] print("Looking up %r under %r..." % (child_name, root_id), end='', flush=True) items = provider.lookup(root_id, child_name) print(items) print() print("Getting metadata for %r..." % child_id, end='', flush=True) item = provider.metadata(child_id) print(item) print() print("Creating folder...", end='', flush=True) provider.create_folder(root_id, 'Some folder') print("done") print() print("Preparing to upload file...", end='', flush=True) upload_id, fd = provider.create_file( root_id, 'file name', 'text/plain', False) print(upload_id, fd) os.write(fd, b'Hello world\n' * 1000) os.close(fd) provider.finish_upload(upload_id) print("Completed upload") print() print("Preparing to download file...", end='', flush=True) download_id, fd = provider.download("some-id") print(download_id, fd) with os.fdopen(fd) as fp: contents = fp.read() provider.finish_download(download_id) print("Contents: %r" % contents) print("Moving file...", end='', flush=True) item = provider.move(child_id, root_id, "New Name") print(item) print() print("Copying file...", end='', flush=True) item = provider.copy(child_id, root_id, "Copy name") print(item) print() if __name__ == '__main__': sys.exit(main(sys.argv)) lomiri-storage-framework-0.5.0/include/000077500000000000000000000000001521521330000200575ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/include/CMakeLists.txt000066400000000000000000000011321521521330000226140ustar00rootroot00000000000000set(client_base_includedir ${CMAKE_INSTALL_INCLUDEDIR}/lomiri-storage-framework-client-${LSF_CLIENT_API_VERSION}) if(QT_VERSION_MAJOR GREATER_EQUAL 6) set(client_base_includedir ${CMAKE_INSTALL_INCLUDEDIR}/lomiri-storage-framework-qt${QT_VERSION_MAJOR}-client-${LSF_CLIENT_API_VERSION}) set(provider_base_includedir ${CMAKE_INSTALL_INCLUDEDIR}/lomiri-storage-framework-qt${QT_VERSION_MAJOR}-provider-${LSF_PROVIDER_API_VERSION}) else() set(provider_base_includedir ${CMAKE_INSTALL_INCLUDEDIR}/lomiri-storage-framework-provider-${LSF_PROVIDER_API_VERSION}) endif() add_subdirectory(lomiri) lomiri-storage-framework-0.5.0/include/lomiri/000077500000000000000000000000001521521330000213525ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/include/lomiri/CMakeLists.txt000066400000000000000000000000321521521330000241050ustar00rootroot00000000000000add_subdirectory(storage) lomiri-storage-framework-0.5.0/include/lomiri/storage/000077500000000000000000000000001521521330000230165ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/include/lomiri/storage/CMakeLists.txt000066400000000000000000000007741521521330000255660ustar00rootroot00000000000000set(includeprefix lomiri/storage) file(GLOB common_headers *.h) # Install common headers to both include prefixes install(FILES ${common_headers} DESTINATION ${client_base_includedir}/${includeprefix}) install(FILES ${common_headers} DESTINATION ${provider_base_includedir}/${includeprefix}) # Deprecated client API v1 install install(FILES ${common_headers} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/lomiri-storage-framework-client-1/${includeprefix}) add_subdirectory(provider) add_subdirectory(qt) lomiri-storage-framework-0.5.0/include/lomiri/storage/common.h000066400000000000000000000041421521521330000244600ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once namespace lomiri { namespace storage { enum class ItemType { file, folder, root, LAST_ENTRY__ }; enum class ConflictPolicy { error_if_conflict, ignore_conflict, overwrite = ignore_conflict, // TODO: remove this, it's here only for compatibility with v1 API }; namespace metadata { static char constexpr SIZE_IN_BYTES[] = "size_in_bytes"; // int64_t, >= 0 static char constexpr CREATION_TIME[] = "creation_time"; // String, ISO 8601 format static char constexpr LAST_MODIFIED_TIME[] = "last_modified_time"; // String, ISO 8601 format static char constexpr CHILD_COUNT[] = "child_count"; // int64_t, >= 0 static char constexpr DESCRIPTION[] = "description"; // String static char constexpr DISPLAY_NAME[] = "display_name"; // String static char constexpr FREE_SPACE_BYTES[] = "free_space_bytes"; // int64_t, >= 0 static char constexpr USED_SPACE_BYTES[] = "used_space_bytes"; // int64_t, >= 0 static char constexpr CONTENT_TYPE[] = "content_type"; // String static char constexpr WRITABLE[] = "writable"; // Bool static char constexpr MD5[] = "md5"; // String static char constexpr DOWNLOAD_URL[] = "download_url"; // String static char constexpr ALL[] = "__ALL__"; } // namespace metadata } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/internal/000077500000000000000000000000001521521330000246325ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/include/lomiri/storage/internal/AccountDetails.h000066400000000000000000000036711521521330000277140ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #pragma GCC diagnostic ignored "-Wswitch-default" #include #pragma GCC diagnostic pop namespace lomiri { namespace storage { namespace internal { struct AccountDetails { QString busName; QDBusObjectPath objectPath; quint32 id; QString serviceId; QString displayName; QString providerName; QString iconName; }; bool operator==(AccountDetails const& lhs, AccountDetails const& rhs); bool operator!=(AccountDetails const& lhs, AccountDetails const& rhs); bool operator<(AccountDetails const& lhs, AccountDetails const& rhs); bool operator<=(AccountDetails const& lhs, AccountDetails const& rhs); bool operator>(AccountDetails const& lhs, AccountDetails const& rhs); bool operator>=(AccountDetails const& lhs, AccountDetails const& rhs); QDBusArgument& operator<<(QDBusArgument& argument, storage::internal::AccountDetails const& account); QDBusArgument const& operator>>(QDBusArgument const& argument, storage::internal::AccountDetails& account); } // namespace internal } // namespace storage } // namespace lomiri Q_DECLARE_METATYPE(lomiri::storage::internal::AccountDetails) lomiri-storage-framework-0.5.0/include/lomiri/storage/internal/ActivityNotifier.h000066400000000000000000000033131521521330000302770ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include #include namespace lomiri { namespace storage { namespace internal { class InactivityTimer; class ActivityNotifier { public: ActivityNotifier() = default; ActivityNotifier(std::shared_ptr const& timer) : timer_(timer) { assert(timer); timer_->request_started(); } ActivityNotifier(ActivityNotifier&& other) : timer_(std::move(other.timer_)) { } ActivityNotifier& operator=(ActivityNotifier&& other) { timer_ = std::move(other.timer_); return *this; } ActivityNotifier(ActivityNotifier const&) = delete; ActivityNotifier& operator=(ActivityNotifier const&) = delete; ~ActivityNotifier() { if (timer_) { timer_->request_finished(); } } private: std::shared_ptr timer_; }; } // namespace internal } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/internal/EnvVars.h000066400000000000000000000033431521521330000263720ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include namespace lomiri { namespace storage { namespace internal { constexpr char const* REGISTRY_IDLE_TIMEOUT = "SF_REGISTRY_IDLE_TIMEOUT"; // Seconds, 0 means "never" constexpr int REGISTRY_IDLE_TIMEOUT_DFLT = 30; constexpr char PROVIDER_IDLE_TIMEOUT[] = "LSF_PROVIDER_IDLE_TIMEOUT"; constexpr int PROVIDER_IDLE_TIMEOUT_DFLT = 30; // Helper class to make retrieval of environment variables type-safe and // to sanity check the setting, if applicable. Also returns a default // setting, if applicable. class EnvVars { public: static int registry_timeout_ms(); static int provider_timeout_ms(); // Returns value of var_name in the environment, if set, and an empty string otherwise. // Can be used for any environment variable, not just the ones defined above. static std::string get(char const* var_name); private: static int get_timeout_ms(char const* var_name, int dflt); }; } // namespace internal } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/internal/InactivityTimer.h000066400000000000000000000025431521521330000301330ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #pragma GCC diagnostic ignored "-Wswitch-default" #include #pragma GCC diagnostic pop #include namespace lomiri { namespace storage { namespace internal { class InactivityTimer : public QObject { Q_OBJECT public: InactivityTimer(int timeout_ms); ~InactivityTimer(); void request_started(); void request_finished(); Q_SIGNALS: void timeout(); private: QTimer timer_; int32_t num_requests_ = 0; }; } // namespace internal } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/internal/ItemMetadata.h000066400000000000000000000026071521521330000273470ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #pragma GCC diagnostic ignored "-Wswitch-default" #include #pragma GCC diagnostic pop namespace lomiri { namespace storage { namespace internal { struct ItemMetadata { QString item_id; QList parent_ids; QString name; QString etag; ItemType type; QMap metadata; }; } // namespace internal } // namespace storage } // namespace lomiri Q_DECLARE_METATYPE(lomiri::storage::internal::ItemMetadata) Q_DECLARE_METATYPE(QList) lomiri-storage-framework-0.5.0/include/lomiri/storage/internal/TraceMessageHandler.h000066400000000000000000000025521521521330000306500ustar00rootroot00000000000000/* * Copyright (C) 2015 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #pragma GCC diagnostic ignored "-Wswitch-default" #include #pragma GCC diagnostic pop namespace lomiri { namespace storage { namespace internal { class TraceMessageHandler final { public: TraceMessageHandler(); TraceMessageHandler(std::string const& prog_name); TraceMessageHandler(QString const& prog_name); TraceMessageHandler(char const* prog_name); ~TraceMessageHandler(); private: QtMessageHandler old_message_handler_; }; } // namespace internal } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/internal/dbus_error.h000066400000000000000000000016351521521330000271560ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once namespace lomiri { namespace storage { namespace internal { constexpr char DBUS_ERROR_PREFIX[] = "com.lomiri.StorageFramework."; } // namespace internal } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/internal/dbusmarshal.h000066400000000000000000000024071521521330000273130ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include #include namespace lomiri { namespace storage { namespace internal { QDBusArgument& operator<<(QDBusArgument& argument, ItemMetadata const& metadata); QDBusArgument const& operator>>(QDBusArgument const& argument, ItemMetadata& metadata); QDBusArgument& operator<<(QDBusArgument& argument, QList const& md_list); QDBusArgument const& operator>>(QDBusArgument const& argument, QList& md_list); } // namespace internal } // storage } // lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/internal/gobj_memory.h000066400000000000000000000121321521521330000273130ustar00rootroot00000000000000/* * Copyright (C) 2013 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authored by: Jussi Pakkanen */ #pragma once #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wold-style-cast" #pragma GCC diagnostic ignored "-Wcast-qual" #include namespace lomiri { namespace storage { namespace internal { /** * This class is meant for automatically managing the lifetime of C objects derived * from gobject. Its API perfectly mirrors the API of unique_ptr except that you * can't define your own deleter function as it is always g_object_unref. * * API/ABI stability is not guaranteed. If you need to pass the object across an ABI * boundary, pass the plain gobject. * * This is how you would use gobj_ptr 99% of the time: * * gobj_ptr o(g_some_type_new(...)); * * More specifically, the object will decrement the gobject reference count * of the object it points to when it goes out of scope. It will never increment it. * Thus you should only assign to it when already holding a reference. gobj_ptr * will then take ownership of that particular reference. * * Floating gobjects can not be put in this container as they are meant to be put * into native gobject aware containers immediately upon construction. Trying to insert * a floating gobject into a gobj_ptr will throw an invalid_argument exception. To * prevent accidental memory leaks, the floating gobject is unreffed in this case. */ template class gobj_ptr final { private: T* u; void validate_float(T* t) { if (t != nullptr && g_object_is_floating(G_OBJECT(t))) { // LCOV_EXCL_START // False negative from gcovr. throw std::invalid_argument("Tried to add a floating gobject into a gobj_ptr."); // LCOV_EXCL_STOP } } public: typedef T element_type; typedef T* pointer; typedef decltype(g_object_unref) deleter_type; constexpr gobj_ptr() noexcept : u(nullptr) { } explicit gobj_ptr(T* t) : u(t) { // What should we do if validate throws? Unreffing unknown objs // is dodgy but not unreffing runs the risk of // memory leaks. Currently unrefs as u is destroyed // when this exception is thrown. validate_float(t); } constexpr gobj_ptr(std::nullptr_t) noexcept : u(nullptr){}; gobj_ptr(gobj_ptr&& o) noexcept { u = o.u; o.u = nullptr; } gobj_ptr(const gobj_ptr& o) : u(nullptr) { *this = o; } gobj_ptr& operator=(const gobj_ptr& o) { if (o.u != nullptr) { g_object_ref(o.u); } reset(o.u); return *this; } ~gobj_ptr() { reset(); } deleter_type& get_deleter() noexcept { return g_object_unref; } deleter_type& get_deleter() const noexcept { return g_object_unref; } void swap(gobj_ptr& o) noexcept { T* tmp = u; u = o.u; o.u = tmp; } void reset(pointer p = pointer()) { if (u != nullptr) { g_object_unref(G_OBJECT(u)); u = nullptr; } // Same throw dilemma as in pointer constructor. u = p; validate_float(p); } T* release() noexcept { T* r = u; u = nullptr; return r; } T* get() const noexcept { return u; } T& operator*() const { return *u; } T* operator->() const noexcept { return u; } explicit operator bool() const noexcept { return u != nullptr; } gobj_ptr& operator=(gobj_ptr&& o) noexcept { reset(); u = o.u; o.u = nullptr; return *this; } gobj_ptr& operator=(std::nullptr_t) noexcept { reset(); return *this; } bool operator==(const gobj_ptr& o) const noexcept { return u == o.u; } bool operator!=(const gobj_ptr& o) const noexcept { return u != o.u; } bool operator<(const gobj_ptr& o) const noexcept { return u < o.u; } bool operator<=(const gobj_ptr& o) const noexcept { return u <= o.u; } bool operator>(const gobj_ptr& o) const noexcept { return u > o.u; } bool operator>=(const gobj_ptr& o) const noexcept { return u >= o.u; } }; } // namespace internal } // namespace storage } // namespace lomiri #pragma GCC diagnostic pop lomiri-storage-framework-0.5.0/include/lomiri/storage/internal/metadata_keys.h000066400000000000000000000034401521521330000276170ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include #include namespace lomiri { namespace storage { namespace metadata { enum class MetadataType { non_zero_pos_int64, iso_8601_date_time, string, boolean }; static std::unordered_map const known_metadata = { { metadata::SIZE_IN_BYTES, MetadataType::non_zero_pos_int64 }, { metadata::CREATION_TIME, MetadataType::iso_8601_date_time }, { metadata::LAST_MODIFIED_TIME, MetadataType::iso_8601_date_time }, { metadata::CHILD_COUNT, MetadataType::non_zero_pos_int64 }, { metadata::DESCRIPTION, MetadataType::string }, { metadata::DISPLAY_NAME, MetadataType::string }, { metadata::FREE_SPACE_BYTES, MetadataType::non_zero_pos_int64 }, { metadata::USED_SPACE_BYTES, MetadataType::non_zero_pos_int64 }, { metadata::CONTENT_TYPE, MetadataType::string }, { metadata::WRITABLE, MetadataType::boolean }, { metadata::MD5, MetadataType::string }, { metadata::DOWNLOAD_URL, MetadataType::string } }; } // namespace metadata } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/internal/safe_strerror.h000066400000000000000000000016271521521330000276710ustar00rootroot00000000000000/* * Copyright (C) 2014 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authored by: Michi Henning */ #pragma once #include namespace lomiri { namespace storage { namespace internal { std::string safe_strerror(int errnum); } // namespace internal } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/provider/000077500000000000000000000000001521521330000246505ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/include/lomiri/storage/provider/CMakeLists.txt000066400000000000000000000003051521521330000274060ustar00rootroot00000000000000set(includeprefix lomiri/storage/provider) file(GLOB provider_headers *.h) install(FILES ${provider_headers} DESTINATION ${provider_base_includedir}/${includeprefix}) add_subdirectory(testing) lomiri-storage-framework-0.5.0/include/lomiri/storage/provider/Credentials.h000066400000000000000000000026501521521330000272610ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #pragma once #include #include #include #include namespace lomiri { namespace storage { namespace provider { struct LOMIRI_STORAGE_EXPORT NoCredentials { }; struct LOMIRI_STORAGE_EXPORT OAuth1Credentials { std::string consumer_key; std::string consumer_secret; std::string token; std::string token_secret; }; struct LOMIRI_STORAGE_EXPORT OAuth2Credentials { std::string access_token; }; struct LOMIRI_STORAGE_EXPORT PasswordCredentials { std::string username; std::string password; std::string host; }; typedef boost::variant Credentials; } } } lomiri-storage-framework-0.5.0/include/lomiri/storage/provider/DownloadJob.h000066400000000000000000000033171521521330000272270ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #pragma once #include #include #include #include namespace lomiri { namespace storage { namespace provider { namespace internal { class DownloadJobImpl; class PendingJobs; class ProviderInterface; } class LOMIRI_STORAGE_EXPORT DownloadJob { public: DownloadJob(std::string const& download_id); virtual ~DownloadJob(); std::string const& download_id() const; int write_socket() const; // If the result of the download is reported with either of the // following two functions, then neither cancel() or finish() will // be called. void report_complete(); void report_error(std::exception_ptr p); virtual boost::future cancel() = 0; virtual boost::future finish() = 0; protected: DownloadJob(internal::DownloadJobImpl *p) LOMIRI_STORAGE_HIDDEN; internal::DownloadJobImpl *p_ = nullptr; friend class internal::PendingJobs; friend class internal::ProviderInterface; }; } } } lomiri-storage-framework-0.5.0/include/lomiri/storage/provider/Exceptions.h000066400000000000000000000137761521521330000271600ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include namespace lomiri { namespace storage { namespace provider { // Note: Adding new exception types also requires updating the marshaling and // unmarshaling code for exceptions in the client and server APIs. /** \brief Base exception class for all server-side exceptions. */ class LOMIRI_STORAGE_EXPORT StorageException : public std::exception { public: StorageException(std::string const& exception_type, std::string const& error_message); ~StorageException(); virtual char const* what() const noexcept override; std::string type() const; std::string error_message() const; private: std::string what_string_; std::string type_; std::string error_message_; }; /** \brief Indicates errors in the communication between the storage provider and the cloud service. */ class LOMIRI_STORAGE_EXPORT RemoteCommsException : public StorageException { public: RemoteCommsException(std::string const& error_message); ~RemoteCommsException(); }; /** \brief Indicates that an item does not exist or could not be found. */ class LOMIRI_STORAGE_EXPORT NotExistsException : public StorageException { public: NotExistsException(std::string const& error_message, std::string const& key); ~NotExistsException(); std::string key() const; private: std::string key_; }; /** \brief Indicates that an item cannot be created because it exists already. */ class LOMIRI_STORAGE_EXPORT ExistsException : public StorageException { public: ExistsException(std::string const& error_message, std::string const& identity, std::string const& name); ~ExistsException(); std::string native_identity() const; std::string name() const; private: std::string identity_; std::string name_; }; /** \brief Indicates that an upload or download detected a version mismatch. */ class LOMIRI_STORAGE_EXPORT ConflictException : public StorageException { public: ConflictException(std::string const& error_message); ~ConflictException(); }; /** \brief Indicates that an operation failed because the authentication credentials are invalid or expired. A provider implementation must throw this exception if it cannot reach its provider because the credentials are invalid. Do not throw this exception if the credentials are valid, but an operation failed due to insufficient permission for an item (such as an attempt to write to a read-only file). Typically, this will cause the request to be retried after refreshing the authentication credentials, but may be returned to the client on repeated failures. \see PermissionException */ class LOMIRI_STORAGE_EXPORT UnauthorizedException : public StorageException { public: UnauthorizedException(std::string const& error_message); ~UnauthorizedException(); }; /** \brief Indicates that an operation failed because of a permission problem. A provider implementation must throw this exception if it can authenticate with its provider, but the provider denied the operation due to insufficient permission for an item (such as an attempt to write to a read-only file). Do not throw this exception for failure to authenticate with the provider. \see UnauthorizedException */ class LOMIRI_STORAGE_EXPORT PermissionException : public StorageException { public: PermissionException(std::string const& error_message); ~PermissionException(); }; /** \brief Indicates that an update failed because the provider ran out of space. */ class LOMIRI_STORAGE_EXPORT QuotaException : public StorageException { public: QuotaException(std::string const& error_message); ~QuotaException(); }; /** \brief Indicates that an upload or download was cancelled before it could complete. */ class LOMIRI_STORAGE_EXPORT CancelledException : public StorageException { public: CancelledException(std::string const& error_message); ~CancelledException(); }; /** \brief Indicates incorrect use of the API, such as calling methods in the wrong order. */ class LOMIRI_STORAGE_EXPORT LogicException : public StorageException { public: LogicException(std::string const& error_message); ~LogicException(); }; /** \brief Indicates an invalid parameter, such as a negative value when a positive one was expected, or a string that does not parse correctly or is empty when it should be non-empty. */ class LOMIRI_STORAGE_EXPORT InvalidArgumentException : public StorageException { public: InvalidArgumentException(std::string const& error_message); ~InvalidArgumentException(); }; /** \brief Indicates a system error, such as failure to create a file or folder, or any other (usually non-recoverable) kind of error that should not arise during normal operation. */ class LOMIRI_STORAGE_EXPORT ResourceException : public StorageException { public: ResourceException(std::string const& error_message, int error_code); ~ResourceException(); int error_code() const noexcept; private: int error_code_; }; /** \brief Indicates that the server side caught an exception that does not derive from StorageException, such as a std::exception, or caught some other unknown type (such as `int`). */ class LOMIRI_STORAGE_EXPORT UnknownException : public StorageException { public: UnknownException(std::string const& error_message); ~UnknownException(); }; } // namespace provider } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/provider/Item.h000066400000000000000000000026151521521330000257230ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #pragma once #include #include #include #include #include namespace lomiri { namespace storage { namespace provider { // Note: When growing the set of supported variant types, add new types // to the *end* of the list, and update the marshaling code in dbusmarshal.cpp. typedef boost::variant MetadataValue; struct LOMIRI_STORAGE_EXPORT Item { std::string item_id; std::vector parent_ids; std::string name; std::string etag; lomiri::storage::ItemType type; std::map metadata; }; typedef std::vector ItemList; } } } lomiri-storage-framework-0.5.0/include/lomiri/storage/provider/ProviderBase.h000066400000000000000000000070321521521330000274100ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #pragma once #include #include #include #include #include #include #include #include #include #include #include namespace lomiri { namespace storage { namespace provider { class DownloadJob; class UploadJob; struct LOMIRI_STORAGE_EXPORT Context { uid_t uid; pid_t pid; std::string security_label; Credentials credentials; }; class LOMIRI_STORAGE_EXPORT ProviderBase : public std::enable_shared_from_this { public: ProviderBase(); virtual ~ProviderBase(); ProviderBase(ProviderBase const& other) = delete; ProviderBase& operator=(ProviderBase const& other) = delete; virtual boost::future roots(std::vector const& keys, Context const& context) = 0; virtual boost::future> list( std::string const& item_id, std::string const& page_token, std::vector const& keys, Context const& context) = 0; virtual boost::future lookup( std::string const& parent_id, std::string const& name, std::vector const& keys, Context const& context) = 0; virtual boost::future metadata(std::string const& item_id, std::vector const& keys, Context const& context) = 0; virtual boost::future create_folder( std::string const& parent_id, std::string const& name, std::vector const& keys, Context const& context) = 0; virtual boost::future> create_file( std::string const& parent_id, std::string const& name, int64_t size, std::string const& content_type, bool allow_overwrite, std::vector const& keys, Context const& context) = 0; virtual boost::future> update( std::string const& item_id, int64_t size, std::string const& old_etag, std::vector const& keys, Context const& context) = 0; virtual boost::future> download( std::string const& item_id, std::string const& match_etag, Context const& context) = 0; virtual boost::future delete_item( std::string const& item_id, Context const& context) = 0; virtual boost::future move( std::string const& item_id, std::string const& new_parent_id, std::string const& new_name, std::vector const& keys, Context const& context) = 0; virtual boost::future copy( std::string const& item_id, std::string const& new_parent_id, std::string const& new_name, std::vector const& keys, Context const& context) = 0; }; } } } lomiri-storage-framework-0.5.0/include/lomiri/storage/provider/Server.h000066400000000000000000000030071521521330000262670ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #pragma once #include #include #include namespace lomiri { namespace storage { namespace provider { namespace internal { class ServerImpl; } class ProviderBase; class LOMIRI_STORAGE_EXPORT ServerBase { public: ServerBase(std::string const& bus_name, std::string const& account_service_id); virtual ~ServerBase(); void init(int& argc, char** argv); int run(); protected: virtual std::shared_ptr make_provider() = 0; private: std::unique_ptr p_; friend class internal::ServerImpl; }; template class Server : public ServerBase { public: using ServerBase::ServerBase; protected: std::shared_ptr make_provider() override { return std::make_shared(); } }; } } } lomiri-storage-framework-0.5.0/include/lomiri/storage/provider/TempfileUploadJob.h000066400000000000000000000027401521521330000303710ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #pragma once #include #include #include #include namespace lomiri { namespace storage { namespace provider { namespace internal { class TempfileUploadJobImpl; } class LOMIRI_STORAGE_EXPORT TempfileUploadJob : public UploadJob { public: TempfileUploadJob(std::string const& upload_id); virtual ~TempfileUploadJob(); std::string file_name() const; // This function should be called from your finish() // implementation to read the remaining data from the socket. If // the client has not closed the socket as expected, LogicError // will be thrown. void drain(); protected: TempfileUploadJob(internal::TempfileUploadJobImpl *p) LOMIRI_STORAGE_HIDDEN; }; } } } lomiri-storage-framework-0.5.0/include/lomiri/storage/provider/UploadJob.h000066400000000000000000000031241521521330000267000ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #pragma once #include #include #include namespace lomiri { namespace storage { namespace provider { struct Item; namespace internal { class PendingJobs; class ProviderInterface; class UploadJobImpl; } class LOMIRI_STORAGE_EXPORT UploadJob { public: UploadJob(std::string const& upload_id); virtual ~UploadJob(); std::string const& upload_id() const; int read_socket() const; // If an error is reported early, cancel() or finish() will not be // invoked. void report_error(std::exception_ptr p); virtual boost::future cancel() = 0; virtual boost::future finish() = 0; protected: UploadJob(internal::UploadJobImpl *p) LOMIRI_STORAGE_HIDDEN; internal::UploadJobImpl *p_ = nullptr; friend class internal::PendingJobs; friend class internal::ProviderInterface; }; } } } lomiri-storage-framework-0.5.0/include/lomiri/storage/provider/internal/000077500000000000000000000000001521521330000264645ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/include/lomiri/storage/provider/internal/AccountData.h000066400000000000000000000044161521521330000310300ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #pragma once #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #pragma GCC diagnostic ignored "-Wswitch-default" #include #include #include #pragma GCC diagnostic pop namespace lomiri { namespace storage { namespace internal { class InactivityTimer; } namespace provider { class ProviderBase; namespace internal { class DBusPeerCache; class PendingJobs; class AccountData : public QObject { Q_OBJECT public: AccountData(std::shared_ptr const& provider, std::shared_ptr const& dbus_peer, std::shared_ptr const& inactivity_timer, QDBusConnection const& bus, QObject* parent=nullptr); virtual ~AccountData(); virtual void authenticate(bool interactive, bool invalidate_cache=false) = 0; virtual bool has_credentials() = 0; virtual Credentials const& credentials() = 0; ProviderBase& provider(); DBusPeerCache& dbus_peer(); std::shared_ptr inactivity_timer(); PendingJobs& jobs(); Q_SIGNALS: void authenticated(); private: std::shared_ptr const provider_; std::shared_ptr const dbus_peer_; std::shared_ptr const inactivity_timer_; std::unique_ptr const jobs_; Q_DISABLE_COPY(AccountData) }; } } } } lomiri-storage-framework-0.5.0/include/lomiri/storage/provider/internal/DBusPeerCache.h000066400000000000000000000043061521521330000312350ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #pragma once #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #pragma GCC diagnostic ignored "-Wswitch-default" #include #include #pragma GCC diagnostic pop #include #include #include #include #include #include class BusInterface; namespace lomiri { namespace storage { namespace provider { namespace internal { class DBusPeerCache final { public: struct Credentials { bool valid = false; uid_t uid = 0; pid_t pid = 0; // Not using QString, because this is not necessarily unicode. std::string label; }; DBusPeerCache(QDBusConnection const& bus); ~DBusPeerCache(); DBusPeerCache(DBusPeerCache const&) = delete; DBusPeerCache& operator=(DBusPeerCache const&) = delete; // Retrieve the security credentials for the given D-Bus peer. boost::future get(QString const& peer); private: struct Request; std::unique_ptr bus_daemon_; bool apparmor_enabled_; std::map cache_; std::map old_cache_; std::map> pending_; void received_credentials(QString const& peer, QDBusPendingReply const& reply); }; } // namespace internal } // namespace provider } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/provider/internal/DownloadJobImpl.h000066400000000000000000000035671521521330000316740ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #pragma once #include #include #include #include #include #include namespace lomiri { namespace storage { namespace provider { class DownloadJob; namespace internal { class DownloadJobImpl : public QObject { Q_OBJECT public: explicit DownloadJobImpl(std::string const& download_id); virtual ~DownloadJobImpl(); std::string const& download_id() const; int write_socket() const; int take_read_socket(); void set_activity(std::shared_ptr const& inactivity_timer); void report_complete(); void report_error(std::exception_ptr p); boost::future finish(DownloadJob& job); boost::future cancel(DownloadJob& job); public Q_SLOTS: virtual void complete_init(); protected: std::string const download_id_; int read_socket_ = -1; int write_socket_ = -1; std::mutex completion_lock_; bool completed_ = false; boost::promise completion_promise_; lomiri::storage::internal::ActivityNotifier activity_; Q_DISABLE_COPY(DownloadJobImpl) }; } } } } lomiri-storage-framework-0.5.0/include/lomiri/storage/provider/internal/FixedAccountData.h000066400000000000000000000030601521521330000320020ustar00rootroot00000000000000/* * Copyright (C) 2017 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #pragma once #include namespace lomiri { namespace storage { namespace provider { namespace internal { class FixedAccountData : public AccountData { Q_OBJECT public: FixedAccountData(std::shared_ptr const& provider, std::shared_ptr const& dbus_peer, std::shared_ptr const& inactivity_timer, QDBusConnection const& bus, QObject* parent=nullptr); virtual ~FixedAccountData(); void authenticate(bool interactive, bool invalidate_cache=false) override; bool has_credentials() override; Credentials const& credentials() override; private: Credentials credentials_ = boost::blank(); Q_DISABLE_COPY(FixedAccountData) }; } } } } lomiri-storage-framework-0.5.0/include/lomiri/storage/provider/internal/Handler.h000066400000000000000000000042621521521330000302160ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #pragma once #include #include #include #include #include #include #include #include #include namespace lomiri { namespace storage { namespace provider { namespace internal { class AccountData; class PendingJobs; class Handler : public QObject { Q_OBJECT public: typedef std::function(std::shared_ptr const&, Context const&, QDBusMessage const&)> Callback; Handler(std::shared_ptr const& account, Callback const& callback, QDBusConnection const& bus, QDBusMessage const& message); void begin(); private Q_SLOTS: void on_authenticated(); void credentials_received(); void handle_unauthorized(std::exception_ptr ep); void send_reply(); Q_SIGNALS: void finished(); private: void marshal_exception(std::exception_ptr ep); std::shared_ptr const account_; Callback const callback_; QDBusConnection const bus_; QDBusMessage const message_; lomiri::storage::internal::ActivityNotifier activity_; boost::future creds_future_; boost::future reply_future_; Context context_; QDBusMessage reply_; bool retry_ = false; Q_DISABLE_COPY(Handler) }; } } } } Q_DECLARE_METATYPE(std::exception_ptr) lomiri-storage-framework-0.5.0/include/lomiri/storage/provider/internal/MainLoopExecutor.h000066400000000000000000000033431521521330000320750ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #pragma once #include #include #include #include namespace lomiri { namespace storage { namespace provider { namespace internal { /* Declare future continuations like so to execute within the event * loop if possible: * * auto f2 = f.then(EXEC_IN_MAIN [](decltype(f) f) { ... }); * * On Boost >= 1.56, this will use a custom executor to run the * continuation as an event in the main thread. On older versions, * the continuation will be executed in a new thread. */ #define EXEC_IN_MAIN MainLoopExecutor::instance(), class MainLoopExecutor : public QObject, public boost::executors::executor { Q_OBJECT public: static MainLoopExecutor& instance(); void submit(work&& closure) override; void close() override; bool closed() override; bool try_executing_one() override; bool event(QEvent *event) override; private: MainLoopExecutor(); void execute(work& closure) noexcept; Q_DISABLE_COPY(MainLoopExecutor) }; } } } } lomiri-storage-framework-0.5.0/include/lomiri/storage/provider/internal/OnlineAccountData.h000066400000000000000000000043231521521330000321720ustar00rootroot00000000000000/* * Copyright (C) 2017 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #pragma once #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #pragma GCC diagnostic ignored "-Wswitch-default" #include #include #include #pragma GCC diagnostic pop namespace lomiri { namespace storage { namespace provider { namespace internal { class OnlineAccountData : public AccountData { Q_OBJECT public: OnlineAccountData(std::shared_ptr const& provider, std::shared_ptr const& dbus_peer, std::shared_ptr const& inactivity_timer, QDBusConnection const& bus, OnlineAccounts::Account* account, QObject* parent=nullptr); virtual ~OnlineAccountData(); void authenticate(bool interactive, bool invalidate_cache=false) override; bool has_credentials() override; Credentials const& credentials() override; private Q_SLOTS: void on_authenticated(); void on_changed(); private: QPointer const account_; std::unique_ptr auth_watcher_; bool authenticating_interactively_ = false; bool authenticating_invalidate_cache_ = false; Credentials credentials_ = boost::blank(); Q_DISABLE_COPY(OnlineAccountData) }; } } } } lomiri-storage-framework-0.5.0/include/lomiri/storage/provider/internal/PendingJobs.h000066400000000000000000000046671521521330000310540ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #pragma once #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #pragma GCC diagnostic ignored "-Wswitch-default" #include #include #include #include #pragma GCC diagnostic pop #include #include #include #include #include namespace lomiri { namespace storage { namespace provider { class DownloadJob; class UploadJob; namespace internal { class PendingJobs : public QObject { Q_OBJECT public: explicit PendingJobs(QDBusConnection const& bus, QObject *parent=nullptr); virtual ~PendingJobs(); void add_download(QString const& client_bus_name, std::unique_ptr &&job); std::shared_ptr remove_download(QString const& client_bus_name, std::string const& download_id); void add_upload(QString const& client_bus_name, std::unique_ptr &&job); std::shared_ptr remove_upload(QString const& client_bus_name, std::string const& upload_id); private Q_SLOTS: void service_disconnected(QString const& service_name); private: void watch_peer(QString const& bus_name); void unwatch_peer(QString const& bus_name); template void cancel_job(std::shared_ptr const& job, std::string const& identifier); std::mutex lock_; // Key is client_bus_name and upload or download ID. std::map,std::shared_ptr> uploads_; std::map,std::shared_ptr> downloads_; QDBusServiceWatcher watcher_; std::map services_; Q_DISABLE_COPY(PendingJobs) }; } } } } lomiri-storage-framework-0.5.0/include/lomiri/storage/provider/internal/ProviderInterface.h000066400000000000000000000067561521521330000322660ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #pragma once #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #pragma GCC diagnostic ignored "-Wswitch-default" #include #include #include #include #include #pragma GCC diagnostic pop #include #include namespace lomiri { namespace storage { namespace provider { namespace internal { class AccountData; class ProviderInterface : public QObject, protected QDBusContext { Q_OBJECT public: ProviderInterface(std::shared_ptr const& account_data, QObject *parent=nullptr); ~ProviderInterface(); private: typedef lomiri::storage::internal::ItemMetadata IMD; // To keep things readable public Q_SLOTS: QList Roots(QList const& keys); QList List(QString const& item_id, QString const& page_token, QList const& keys, QString& next_token); QList Lookup(QString const& parent_id, QString const& name, QList const& keys); IMD Metadata(QString const& item_id, QList const& keys); IMD CreateFolder(QString const& parent_id, QString const& name, QList const& keys); QString CreateFile(QString const& parent_id, QString const& name, int64_t size, QString const& content_type, bool allow_overwrite, QList const& keys, QDBusUnixFileDescriptor& file_descriptor); QString Update(QString const& item_id, int64_t size, QString const& old_etag, QList const& keys, QDBusUnixFileDescriptor& file_descriptor); IMD FinishUpload(QString const& upload_id); void CancelUpload(QString const& upload_id); QString Download(QString const& item_id, QString const& match_etag, QDBusUnixFileDescriptor& file_descriptor); void FinishDownload(QString const& download_id); void Delete(QString const& item_id); IMD Move(QString const& item_id, QString const& new_parent_id, QString const& new_name, QList const& metadata_keys); IMD Copy(QString const& item_id, QString const& new_parent_id, QString const& new_name, QList const& metadata_keys); private Q_SLOTS: void request_finished(); private: void queue_request(Handler::Callback callback); std::shared_ptr const account_; std::map> requests_; Q_DISABLE_COPY(ProviderInterface) }; } } } } lomiri-storage-framework-0.5.0/include/lomiri/storage/provider/internal/ServerImpl.h000066400000000000000000000047561521521330000307410ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include namespace lomiri { namespace storage { namespace provider { namespace internal { class ServerImpl : public QObject { Q_OBJECT public: ServerImpl(ServerBase* server, std::string const& bus_name, std::string const& account_service_id); ~ServerImpl(); void init(int& argc, char **argv, QDBusConnection *bus = nullptr); int run(); private Q_SLOTS: void on_account_manager_ready(); void on_account_available(OnlineAccounts::Account* account); void on_account_disabled(); void on_timeout(); Q_SIGNALS: void accountAdded(); void accountRemoved(); private: void register_bus_name(); void add_account(OnlineAccounts::Account* account); void remove_account(OnlineAccounts::Account* account); ServerBase* const server_; std::string const bus_name_; std::string const service_id_; lomiri::storage::internal::TraceMessageHandler trace_message_handler_; std::unique_ptr app_; std::unique_ptr bus_; std::shared_ptr inactivity_timer_; std::unique_ptr manager_; std::shared_ptr dbus_peer_; std::map> interfaces_; Q_DISABLE_COPY(ServerImpl) }; } } } } lomiri-storage-framework-0.5.0/include/lomiri/storage/provider/internal/TempfileUploadJobImpl.h000066400000000000000000000031201521521330000330200ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #pragma once #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #include #pragma GCC diagnostic pop #include #include namespace lomiri { namespace storage { namespace provider { namespace internal { class TempfileUploadJobImpl : public UploadJobImpl { Q_OBJECT public: explicit TempfileUploadJobImpl(std::string const& upload_id); virtual ~TempfileUploadJobImpl(); void complete_init() override; void drain(); std::string file_name() const; private Q_SLOTS: void on_ready_read(); void on_read_channel_finished(); private: std::unique_ptr tmpfile_; std::unique_ptr reader_; Q_DISABLE_COPY(TempfileUploadJobImpl) }; } } } } lomiri-storage-framework-0.5.0/include/lomiri/storage/provider/internal/TestServerImpl.h000066400000000000000000000033351521521330000315710ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #pragma once #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #pragma GCC diagnostic pop #include #include namespace lomiri { namespace storage { namespace internal { class InactivityTimer; } namespace provider { namespace internal { class ProviderInterface; class TestServerImpl { public: TestServerImpl(std::shared_ptr const& provider, OnlineAccounts::Account* account, QDBusConnection const& connection, std::string const& object_path); ~TestServerImpl(); QDBusConnection const& connection() const; std::string const& object_path() const; private: QDBusConnection connection_; std::string const object_path_; std::shared_ptr inactivity_timer_; std::unique_ptr interface_; }; } } } } lomiri-storage-framework-0.5.0/include/lomiri/storage/provider/internal/UploadJobImpl.h000066400000000000000000000040411521521330000313350ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #pragma once #include #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wswitch-default" #include #pragma GCC diagnostic pop #include #include #include #include namespace lomiri { namespace storage { namespace provider { class UploadJob; namespace internal { class UploadJobImpl : public QObject { Q_OBJECT public: explicit UploadJobImpl(std::string const& upload_id); virtual ~UploadJobImpl(); std::string const& upload_id() const; int read_socket() const; int take_write_socket(); void set_activity(std::shared_ptr const& inactivity_timer); void report_error(std::exception_ptr p); boost::future finish(UploadJob& job); boost::future cancel(UploadJob& job); public Q_SLOTS: virtual void complete_init(); protected: std::string const upload_id_; int read_socket_ = -1; int write_socket_ = -1; std::mutex completion_lock_; bool completed_ = false; boost::promise completion_promise_; lomiri::storage::internal::ActivityNotifier activity_; Q_DISABLE_COPY(UploadJobImpl) }; } } } } lomiri-storage-framework-0.5.0/include/lomiri/storage/provider/internal/dbusmarshal.h000066400000000000000000000026561521521330000311530ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #pragma once #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #include #pragma GCC diagnostic pop #include namespace lomiri { namespace storage { namespace provider { struct Item; QDBusArgument& operator<<(QDBusArgument& argument, Item const& item); QDBusArgument const& operator>>(QDBusArgument const& argument, Item& item); QDBusArgument& operator<<(QDBusArgument& argument, std::vector const& items); QDBusArgument const& operator>>(QDBusArgument const& argument, std::vector& items); } } } Q_DECLARE_METATYPE(lomiri::storage::provider::Item) lomiri-storage-framework-0.5.0/include/lomiri/storage/provider/testing/000077500000000000000000000000001521521330000263255ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/include/lomiri/storage/provider/testing/CMakeLists.txt000066400000000000000000000002621521521330000310650ustar00rootroot00000000000000set(includeprefix lomiri/storage/provider/testing) file(GLOB provider_headers *.h) install(FILES ${provider_headers} DESTINATION ${provider_base_includedir}/${includeprefix}) lomiri-storage-framework-0.5.0/include/lomiri/storage/provider/testing/TestServer.h000066400000000000000000000026671521521330000306170ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #pragma once #include #include #include namespace OnlineAccounts { class Account; } class QDBusConnection; namespace lomiri { namespace storage { namespace provider { class ProviderBase; namespace internal { class TestServerImpl; } namespace testing { class LOMIRI_STORAGE_EXPORT TestServer { public: TestServer(std::shared_ptr const& provider, OnlineAccounts::Account* account, QDBusConnection const& connection, std::string const& object_path); ~TestServer(); QDBusConnection const& connection() const; std::string const& object_path() const; private: std::unique_ptr p_; }; } } } } lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/000077500000000000000000000000001521521330000234425ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/Account.h000066400000000000000000000057031521521330000252140ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include #include namespace lomiri { namespace storage { namespace qt { namespace internal { class AccountImpl; class ItemImpl; } class ItemJob; class ItemListJob; class Q_DECL_EXPORT Account final { Q_GADGET Q_PROPERTY(bool isValid READ isValid FINAL) Q_PROPERTY(QString busName READ busName FINAL) Q_PROPERTY(QString objectPath READ objectPath FINAL) Q_PROPERTY(QString displayName READ displayName FINAL) Q_PROPERTY(QString providerName READ providerName FINAL) Q_PROPERTY(QString iconName READ iconName FINAL) public: Account(); Account(Account const&); Account(Account&&); ~Account(); Account& operator=(Account const&); Account& operator=(Account&&); bool isValid() const; QString busName() const; QString objectPath() const; QString displayName() const; QString providerName() const; QString iconName() const; Q_INVOKABLE lomiri::storage::qt::ItemListJob* roots(QStringList const& keys = QStringList()) const; Q_INVOKABLE lomiri::storage::qt::ItemJob* get(QString const& itemId, QStringList const& keys = QStringList()) const; bool operator==(Account const&) const; bool operator!=(Account const&) const; bool operator<(Account const&) const; bool operator<=(Account const&) const; bool operator>(Account const&) const; bool operator>=(Account const&) const; size_t hash() const; private: Account(std::shared_ptr const& p); std::shared_ptr p_; friend class internal::AccountImpl; friend class internal::ItemImpl; }; // Note: qHash(Account) does *not* return the same hash value is std::hash because // std:hash() returns size_t (typically 64 bits), but qHash() returns uint (typically 32 bits). uint Q_DECL_EXPORT qHash(Account const& acc); } // namespace qt } // namespace storage } // namespace lomiri Q_DECLARE_METATYPE(lomiri::storage::qt::Account) Q_DECLARE_METATYPE(QList) namespace std { template<> struct Q_DECL_EXPORT hash { std::size_t operator()(lomiri::storage::qt::Account const& a) const { return a.hash(); } }; } // namespace std lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/AccountsJob.h000066400000000000000000000041201521521330000260220ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include #include #include namespace lomiri { namespace storage { namespace qt { namespace internal { class AccountsJobImpl; } // namespace internal class Account; class StorageError; class Q_DECL_EXPORT AccountsJob final : public QObject { Q_OBJECT Q_PROPERTY(bool isValid READ isValid NOTIFY statusChanged FINAL) Q_PROPERTY(lomiri::storage::qt::AccountsJob::Status status READ status NOTIFY statusChanged FINAL) Q_PROPERTY(lomiri::storage::qt::StorageError error READ error NOTIFY statusChanged FINAL) Q_PROPERTY(QVariantList accounts READ accountsAsVariantList NOTIFY statusChanged FINAL) public: enum Status { Loading, Finished, Error }; Q_ENUMS(Status) virtual ~AccountsJob(); bool isValid() const; Status status() const; StorageError error() const; QList accounts() const; Q_SIGNALS: void statusChanged(lomiri::storage::qt::AccountsJob::Status status) const; private: AccountsJob(std::unique_ptr accounts_job_impl); QVariantList accountsAsVariantList() const; std::unique_ptr const p_; friend class internal::AccountsJobImpl; }; } // namespace qt } // namespace storage } // namespace lomiri Q_DECLARE_METATYPE(lomiri::storage::qt::AccountsJob::Status) lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/CMakeLists.txt000066400000000000000000000011371521521330000262040ustar00rootroot00000000000000if(QT_VERSION_MAJOR LESS 6) add_subdirectory(client) # Old (v1) API endif() set(includeprefix lomiri/storage/qt) file(GLOB public_hdrs *.h) set(convenience_hdr ${CMAKE_CURRENT_BINARY_DIR}/client-api.h) add_custom_command( OUTPUT ${convenience_hdr} COMMAND ${CMAKE_SOURCE_DIR}/tools/create_globalheader.py ${convenience_hdr} ${includeprefix} ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS ${public_hdrs}) add_custom_target(qt-client-all-headers ALL DEPENDS ${convenience_hdr}) install( FILES ${public_hdrs} ${convenience_hdr} DESTINATION ${client_base_includedir}/${includeprefix}) lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/Downloader.h000066400000000000000000000047671521521330000257270ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include #include namespace lomiri { namespace storage { namespace qt { namespace internal { class DownloaderImpl; } // namespace internal class Q_DECL_EXPORT Downloader final : public QIODevice { Q_OBJECT Q_PROPERTY(bool isValid READ isValid NOTIFY statusChanged FINAL) Q_PROPERTY(lomiri::storage::qt::Downloader::Status status READ status NOTIFY statusChanged FINAL) Q_PROPERTY(lomiri::storage::qt::StorageError error READ error NOTIFY statusChanged FINAL) Q_PROPERTY(lomiri::storage::qt::Item item READ item NOTIFY statusChanged FINAL) public: enum Status { Loading, Ready, Cancelled, Finished, Error }; Q_ENUMS(Status) Downloader(); virtual ~Downloader(); bool isValid() const; Status status() const; StorageError error() const; Item item() const; Q_INVOKABLE void cancel(); // From QLocalSocket interface. Q_INVOKABLE void close(); Q_INVOKABLE qint64 bytesAvailable() const override; Q_INVOKABLE qint64 bytesToWrite() const override; Q_INVOKABLE bool canReadLine() const override; Q_INVOKABLE bool isSequential() const override; Q_INVOKABLE bool waitForBytesWritten(int msecs = 30000) override; Q_INVOKABLE bool waitForReadyRead(int msecs = 30000) override; Q_SIGNALS: void statusChanged(lomiri::storage::qt::Downloader::Status status) const; private: Downloader(std::unique_ptr p); qint64 readData(char* data, qint64 c); qint64 writeData(char const* data, qint64 c); std::unique_ptr p_; friend class internal::DownloaderImpl; }; } // namespace qt } // namespace storage } // namespace lomiri Q_DECLARE_METATYPE(lomiri::storage::qt::Downloader::Status) lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/Item.h000066400000000000000000000130061521521330000245110ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #include #pragma GCC diagnostic pop #include namespace lomiri { namespace storage { namespace qt { namespace internal { class ItemImpl; class DownloaderImpl; class UploaderImpl; } // namespace internal class Account; class Downloader; class IntJob; class ItemJob; class ItemListJob; class Uploader; class VoidJob; class Q_DECL_EXPORT Item final { Q_GADGET Q_PROPERTY(QString itemId READ itemId FINAL) Q_PROPERTY(QString name READ name FINAL) Q_PROPERTY(lomiri::storage::qt::Account account READ account FINAL) Q_PROPERTY(QString etag READ etag FINAL) Q_PROPERTY(lomiri::storage::qt::Item::Type type READ type FINAL) Q_PROPERTY(QVariantMap metadata READ metadata FINAL) Q_PROPERTY(QDateTime lastModifiedTime READ lastModifiedTime FINAL) Q_PROPERTY(QStringList parentIds READ parentIds FINAL) public: Item(); Item(Item const&); Item(Item&&); ~Item(); Item& operator=(Item const&); Item& operator=(Item&&); enum Type { File = unsigned(lomiri::storage::ItemType::file), Folder = unsigned(lomiri::storage::ItemType::folder), Root = unsigned(lomiri::storage::ItemType::root) }; Q_ENUMS(Type) enum ConflictPolicy { ErrorIfConflict = unsigned(lomiri::storage::ConflictPolicy::error_if_conflict), IgnoreConflict = unsigned(lomiri::storage::ConflictPolicy::ignore_conflict) }; Q_ENUMS(ConflictPolicy) bool isValid() const; QString itemId() const; QString name() const; Account account() const; QString etag() const; Type type() const; QVariantMap metadata() const; qint64 sizeInBytes() const; QDateTime lastModifiedTime() const; QStringList parentIds() const; Q_INVOKABLE lomiri::storage::qt::ItemListJob* parents(QStringList const& keys = QStringList()) const; Q_INVOKABLE lomiri::storage::qt::ItemJob* copy(Item const& newParent, QString const& newName, QStringList const& keys = QStringList()) const; Q_INVOKABLE lomiri::storage::qt::ItemJob* move(Item const& newParent, QString const& newName, QStringList const& keys = QStringList()) const; Q_INVOKABLE lomiri::storage::qt::VoidJob* deleteItem() const; Q_INVOKABLE lomiri::storage::qt::Uploader* createUploader(ConflictPolicy policy, qint64 sizeInBytes, QStringList const& keys = QStringList()) const; Q_INVOKABLE lomiri::storage::qt::Downloader* createDownloader(ConflictPolicy policy) const; Q_INVOKABLE lomiri::storage::qt::ItemListJob* list(QStringList const& keys = QStringList()) const; Q_INVOKABLE lomiri::storage::qt::ItemListJob* lookup(QString const& name, QStringList const& = QStringList()) const; Q_INVOKABLE lomiri::storage::qt::ItemJob* createFolder(QString const& name, QStringList const& = QStringList()) const; Q_INVOKABLE lomiri::storage::qt::Uploader* createFile(QString const& name, ConflictPolicy policy, qint64 sizeInBytes, QString const& contentType, QStringList const& keys = QStringList()) const; bool operator==(Item const&) const; bool operator!=(Item const&) const; bool operator<(Item const&) const; bool operator<=(Item const&) const; bool operator>(Item const&) const; bool operator>=(Item const&) const; size_t hash() const; private: Item(std::shared_ptr const&); std::shared_ptr p_; friend class internal::ItemImpl; friend class internal::DownloaderImpl; friend class internal::UploaderImpl; }; // Note: qHash(Item) does *not* return the same hash value is std::hash because // std:hash() returns size_t (typically 64 bits), but qHash() returns uint (typically 32 bits). uint Q_DECL_EXPORT qHash(lomiri::storage::qt::Item const& i); } // namespace qt } // namespace storage } // namespace lomiri Q_DECLARE_METATYPE(lomiri::storage::qt::Item) Q_DECLARE_METATYPE(QList) Q_DECLARE_METATYPE(lomiri::storage::qt::Item::Type) Q_DECLARE_METATYPE(lomiri::storage::qt::Item::ConflictPolicy) namespace std { template<> struct Q_DECL_EXPORT hash { std::size_t operator()(lomiri::storage::qt::Item const& i) const { return i.hash(); } }; } // namespace std lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/ItemJob.h000066400000000000000000000036121521521330000251460ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include namespace lomiri { namespace storage { namespace qt { namespace internal { class ItemJobImpl; } // namespace internal class Item; class StorageError; class Q_DECL_EXPORT ItemJob final : public QObject { Q_OBJECT Q_PROPERTY(bool isValid READ isValid NOTIFY statusChanged FINAL) Q_PROPERTY(lomiri::storage::qt::ItemJob::Status status READ status NOTIFY statusChanged FINAL) Q_PROPERTY(lomiri::storage::qt::StorageError error READ error NOTIFY statusChanged FINAL) Q_PROPERTY(lomiri::storage::qt::Item item READ item NOTIFY statusChanged FINAL) public: virtual ~ItemJob(); enum Status { Loading, Finished, Error }; Q_ENUMS(Status) bool isValid() const; Status status() const; StorageError error() const; Item item() const; Q_SIGNALS: void statusChanged(lomiri::storage::qt::ItemJob::Status status) const; private: ItemJob(std::unique_ptr p); std::unique_ptr const p_; friend class internal::ItemJobImpl; }; } // namespace qt } // namespace storage } // namespace lomiri Q_DECLARE_METATYPE(lomiri::storage::qt::ItemJob::Status) lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/ItemListJob.h000066400000000000000000000042711521521330000260040ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #include #pragma GCC diagnostic pop #include namespace lomiri { namespace storage { namespace qt { namespace internal { class ListJobImplBase; class ItemListJobImpl; class MultiItemJobImpl; class MultiItemListJobImpl; } // namespace internal class Item; class StorageError; class Q_DECL_EXPORT ItemListJob final : public QObject { Q_OBJECT Q_PROPERTY(bool isValid READ isValid NOTIFY statusChanged FINAL) Q_PROPERTY(lomiri::storage::qt::ItemListJob::Status status READ status NOTIFY statusChanged FINAL) Q_PROPERTY(lomiri::storage::qt::StorageError error READ error NOTIFY statusChanged FINAL) public: virtual ~ItemListJob(); enum Status { Loading, Finished, Error }; Q_ENUMS(Status) bool isValid() const; Status status() const; StorageError error() const; Q_SIGNALS: void statusChanged(lomiri::storage::qt::ItemListJob::Status status) const; void itemsReady(QList const& items) const; private: ItemListJob(std::unique_ptr p); std::unique_ptr const p_; friend class internal::ListJobImplBase; friend class internal::ItemListJobImpl; friend class internal::MultiItemJobImpl; friend class internal::MultiItemListJobImpl; }; } // namespace qt } // namespace storage } // namespace lomiri Q_DECLARE_METATYPE(lomiri::storage::qt::ItemListJob::Status) lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/Runtime.h000066400000000000000000000042031521521330000252350ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #pragma GCC diagnostic pop #include class QDBusConnection; namespace lomiri { namespace storage { namespace qt { namespace internal { class RuntimeImpl; } // namespace internal class AccountsJob; class Q_DECL_EXPORT Runtime : public QObject { Q_OBJECT Q_PROPERTY(bool isValid READ isValid FINAL) Q_PROPERTY(lomiri::storage::qt::StorageError error READ error FINAL) Q_PROPERTY(QDBusConnection connection READ connection CONSTANT FINAL) public: Runtime(QObject* parent = nullptr); Runtime(QDBusConnection const& bus, QObject* parent = nullptr); virtual ~Runtime(); bool isValid() const; StorageError error() const; QDBusConnection connection() const; StorageError shutdown(); Q_INVOKABLE lomiri::storage::qt::AccountsJob* accounts() const; Account make_test_account(QString const& bus_name, QString const& object_path, quint32 id = 999, QString const& service_id = "", QString const& name = "") const; private: std::shared_ptr p_; }; } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/StorageError.h000066400000000000000000000043441521521330000262360ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include #include namespace lomiri { namespace storage { namespace qt { namespace internal { class StorageErrorImpl; } class Q_DECL_EXPORT StorageError final { Q_GADGET Q_PROPERTY(lomiri::storage::qt::StorageError::Type type READ type FINAL) Q_PROPERTY(QString name READ name FINAL) Q_PROPERTY(QString message READ message FINAL) Q_PROPERTY(QString errorString READ errorString FINAL) Q_PROPERTY(QString itemId READ itemId FINAL) Q_PROPERTY(QString itemName READ itemName FINAL) Q_PROPERTY(int errorCode READ errorCode FINAL) public: StorageError(); StorageError(StorageError const&); StorageError(StorageError&&); ~StorageError(); StorageError& operator=(StorageError const&); StorageError& operator=(StorageError&&); enum Type { NoError, LocalCommsError, RemoteCommsError, RuntimeDestroyed, NotExists, Exists, Conflict, PermissionDenied, Cancelled, LogicError, InvalidArgument, ResourceError, QuotaExceeded, Unauthorized, __LAST_STORAGE_ERROR }; Q_ENUMS(Type) Type type() const; QString name() const; QString message() const; QString errorString() const; QString itemId() const; QString itemName() const; int errorCode() const; private: StorageError(std::unique_ptr); std::unique_ptr p_; friend class internal::StorageErrorImpl; }; } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/Uploader.h000066400000000000000000000054131521521330000253710ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include #include namespace lomiri { namespace storage { namespace qt { namespace internal { class UploaderImpl; } // namespace internal class Item; class StorageError; class Q_DECL_EXPORT Uploader final : public QIODevice { Q_OBJECT Q_PROPERTY(bool isValid READ isValid NOTIFY statusChanged FINAL) Q_PROPERTY(lomiri::storage::qt::Uploader::Status status READ status NOTIFY statusChanged FINAL) Q_PROPERTY(lomiri::storage::qt::StorageError error READ error NOTIFY statusChanged FINAL) Q_PROPERTY(lomiri::storage::qt::Item::ConflictPolicy policy READ policy NOTIFY statusChanged FINAL) Q_PROPERTY(qint64 sizeInBytes READ sizeInBytes NOTIFY statusChanged FINAL) Q_PROPERTY(lomiri::storage::qt::Item item READ item NOTIFY statusChanged FINAL) public: enum Status { Loading, Ready, Cancelled, Finished, Error }; Q_ENUMS(Status) Uploader(); virtual ~Uploader(); bool isValid() const; Status status() const; StorageError error() const; Item::ConflictPolicy policy() const; qint64 sizeInBytes() const; Item item() const; Q_INVOKABLE void cancel(); // From QLocalSocket interface. Q_INVOKABLE void close() override; Q_INVOKABLE qint64 bytesAvailable() const override; Q_INVOKABLE qint64 bytesToWrite() const override; Q_INVOKABLE bool canReadLine() const override; Q_INVOKABLE bool isSequential() const override; Q_INVOKABLE bool waitForBytesWritten(int msecs = 30000) override; Q_INVOKABLE bool waitForReadyRead(int msecs = 30000) override; Q_SIGNALS: void statusChanged(lomiri::storage::qt::Uploader::Status status) const; private: Uploader(std::unique_ptr p); qint64 readData(char* data, qint64 c); qint64 writeData(char const* data, qint64 c); std::unique_ptr p_; friend class internal::UploaderImpl; }; } // namespace qt } // namespace storage } // namespace lomiri Q_DECLARE_METATYPE(lomiri::storage::qt::Uploader::Status) lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/VoidJob.h000066400000000000000000000034011521521330000251450ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include namespace lomiri { namespace storage { namespace qt { namespace internal { class VoidJobImpl; } // namespace internal class StorageError; class Q_DECL_EXPORT VoidJob final : public QObject { Q_OBJECT Q_PROPERTY(bool isValid READ isValid NOTIFY statusChanged FINAL) Q_PROPERTY(lomiri::storage::qt::VoidJob::Status status READ status NOTIFY statusChanged FINAL) Q_PROPERTY(lomiri::storage::qt::StorageError error READ error NOTIFY statusChanged FINAL) public: virtual ~VoidJob(); enum Status { Loading, Finished, Error }; Q_ENUMS(Status) bool isValid() const; Status status() const; StorageError error() const; Q_SIGNALS: void statusChanged(lomiri::storage::qt::VoidJob::Status status) const; private: VoidJob(std::unique_ptr p); std::unique_ptr const p_; friend class internal::VoidJobImpl; }; } // namespace qt } // namespace storage } // namespace lomiri Q_DECLARE_METATYPE(lomiri::storage::qt::VoidJob::Status) lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/000077500000000000000000000000001521521330000247205ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/Account.h000066400000000000000000000045241521521330000264720ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #pragma GCC diagnostic pop #include #include namespace lomiri { namespace storage { namespace qt { namespace client { class Runtime; class Root; namespace internal { class AccountBase; namespace local_client { class RuntimeImpl; } // namespace local_client namespace remote_client { class ItemImpl; class RuntimeImpl; } // namespace remote_client } // namespace internal /** \brief Class that represents an account. */ class LOMIRI_STORAGE_EXPORT Account final { public: /// @cond ~Account(); /// @endcond Account(Account&&); Account& operator=(Account&&); typedef std::shared_ptr SPtr; std::shared_ptr runtime() const; QString owner() const; QString owner_id() const; QString description() const; // TODO: Will almost certainly need more here. Other details? /** \brief Returns the root directories for the account. An account can have more than one root directory (for providers that support the concept of multiple drives). */ QFuture>> roots() const; private: Account(internal::AccountBase*) LOMIRI_STORAGE_HIDDEN; std::shared_ptr p_; friend class internal::local_client::RuntimeImpl; friend class internal::remote_client::ItemImpl; friend class internal::remote_client::RuntimeImpl; }; } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/CMakeLists.txt000066400000000000000000000010641521521330000274610ustar00rootroot00000000000000set(includeprefix lomiri/storage/qt/client) file(GLOB public_hdrs *.h) set(convenience_hdr ${CMAKE_CURRENT_BINARY_DIR}/client-api.h) add_custom_command( OUTPUT ${convenience_hdr} COMMAND ${CMAKE_SOURCE_DIR}/tools/create_globalheader.py ${convenience_hdr} ${includeprefix} ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS ${public_hdrs}) add_custom_target(qt-client-all-headers-v1 ALL DEPENDS ${convenience_hdr}) install(FILES ${public_hdrs} ${convenience_hdr} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/lomiri-storage-framework-client-1/${includeprefix}) lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/Downloader.h000066400000000000000000000074441521521330000272000ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #pragma GCC diagnostic pop #include #include class QLocalSocket; namespace lomiri { namespace storage { namespace qt { namespace client { class File; namespace internal { class DownloaderBase; namespace local_client { class FileImpl; } // namespace local_client namespace remote_client { class DownloaderImpl; } // namespace remote_client } // namespace internal class LOMIRI_STORAGE_EXPORT Downloader final { public: /** \brief Destroys the downloader. The destructor implicitly calls cancel() if it has not been called already. */ ~Downloader(); Downloader(Downloader&&); Downloader& operator=(Downloader&&); /** \brief Convenience type definition. */ typedef std::shared_ptr SPtr; /** \brief Returns the file for this downloader. */ std::shared_ptr file() const; /** \brief Returns a socket that is open for reading. To download the file contents, read from the returned socket. \return A socket open for reading. */ std::shared_ptr socket() const; /** \brief Finalizes the download. Once the returned socket indicates EOF, you must call finish_download(), which closes the socket. Call `waitForFinished()` on the returned future to check for errors. If an error occurred, `waitForFinished()` throws an exception. If the download was cancelled, `waitForFinished()` throws CancelledException. \warning Do not assume that a download completed successfully once you detect EOF on the socket. If something goes wrong during a download on the server side, the socket will return EOF for a partially-downloaded file. */ QFuture finish_download(); /** \brief Cancels a download. Calling cancel() informs the provider that the download is no longer needed. The provider will make a best-effort attempt to cancel the download from the remote service. You can check whether the cancel was successfully sent by calling `waitForFinished()` on the returned future. If this does not throw an exception, the message was received and acted upon by the provider. However, successful completion does _not_ indicate that the download was actually cancelled. (For example, the download may have completed already by the time the provider received the cancel request, or the provider may not support cancellation.) Calling cancel() more than once, or calling cancel() after a call to finish_download() is safe and does nothing. */ QFuture cancel(); private: Downloader(internal::DownloaderBase*) LOMIRI_STORAGE_HIDDEN; std::shared_ptr p_; friend class internal::local_client::FileImpl; friend class internal::remote_client::DownloaderImpl; }; } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/Exceptions.h000066400000000000000000000151631521521330000272200ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #include #pragma GCC diagnostic pop #include namespace lomiri { namespace storage { namespace qt { namespace client { /** \brief Base exception class for all exceptions returned by the API. */ class LOMIRI_STORAGE_EXPORT StorageException : public QException { public: StorageException(char const* exception_name, QString const& error_message); ~StorageException(); virtual StorageException* clone() const override = 0; virtual void raise() const override = 0; virtual char const* what() const noexcept override; QString error_message() const; private: std::string what_string_; QString error_message_; }; /** \brief Indicates errors in the communication with the storage provider service. */ class LOMIRI_STORAGE_EXPORT LocalCommsException : public StorageException { public: LocalCommsException(QString const& error_message); ~LocalCommsException(); virtual LocalCommsException* clone() const override; virtual void raise() const override; }; /** \brief Indicates errors in the communication between the storage provider and the cloud service. */ class LOMIRI_STORAGE_EXPORT RemoteCommsException : public StorageException { public: RemoteCommsException(QString const& error_message); ~RemoteCommsException(); virtual RemoteCommsException* clone() const override; virtual void raise() const override; }; /** \brief Indicates that the caller invoked an operation on a file or folder that was deleted. */ class LOMIRI_STORAGE_EXPORT DeletedException : public StorageException { public: DeletedException(QString const& error_message, QString const& identity_); ~DeletedException(); virtual DeletedException* clone() const override; virtual void raise() const override; QString native_identity() const; private: QString identity_; }; /** \brief Indicates that the caller destroyed the runtime. */ class LOMIRI_STORAGE_EXPORT RuntimeDestroyedException : public StorageException { public: RuntimeDestroyedException(QString const& method); ~RuntimeDestroyedException(); virtual RuntimeDestroyedException* clone() const override; virtual void raise() const override; }; /** \brief Indicates that an item does not exist or could not be found. */ class LOMIRI_STORAGE_EXPORT NotExistsException : public StorageException { public: NotExistsException(QString const& error_message, QString const& key); ~NotExistsException(); virtual NotExistsException* clone() const override; virtual void raise() const override; QString key() const; private: QString key_; }; /** \brief Indicates that an item cannot be created because it exists already. */ class LOMIRI_STORAGE_EXPORT ExistsException : public StorageException { public: ExistsException(QString const& error_message, QString const& identity, QString const& name); ~ExistsException(); virtual ExistsException* clone() const override; virtual void raise() const override; QString native_identity() const; QString name() const; private: QString identity_; QString name_; }; /** \brief Indicates that an upload detected a version mismatch. */ class LOMIRI_STORAGE_EXPORT ConflictException : public StorageException { public: ConflictException(QString const& error_message); ~ConflictException(); virtual ConflictException* clone() const override; virtual void raise() const override; }; /** \brief Indicates that an operation failed because of a permission problem. */ class LOMIRI_STORAGE_EXPORT PermissionException : public StorageException { public: PermissionException(QString const& error_message); ~PermissionException(); virtual PermissionException* clone() const override; virtual void raise() const override; }; /** \brief Indicates that an update failed because the provider ran out of space. */ class LOMIRI_STORAGE_EXPORT QuotaException : public StorageException { public: QuotaException(QString const& error_message); ~QuotaException(); virtual QuotaException* clone() const override; virtual void raise() const override; }; /** \brief Indicates that an upload or download was cancelled before it could complete. */ class LOMIRI_STORAGE_EXPORT CancelledException : public StorageException { public: CancelledException(QString const& error_message); ~CancelledException(); virtual CancelledException* clone() const override; virtual void raise() const override; }; /** \brief Indicates incorrect use of the API, such as calling methods in the wrong order. */ class LOMIRI_STORAGE_EXPORT LogicException : public StorageException { public: LogicException(QString const& error_message); ~LogicException(); virtual LogicException* clone() const override; virtual void raise() const override; }; /** \brief Indicates an invalid parameter, such as a negative value when a positive one was expected, or a string that does not parse correctly or is empty when it should be non-empty. */ class LOMIRI_STORAGE_EXPORT InvalidArgumentException : public StorageException { public: InvalidArgumentException(QString const& error_message); ~InvalidArgumentException(); virtual InvalidArgumentException* clone() const override; virtual void raise() const override; }; /** \brief Indicates a system error, such as failure to create a file or folder, or any other (usually non-recoverable) kind of error that should not arise during normal operation. */ class LOMIRI_STORAGE_EXPORT ResourceException : public StorageException { public: ResourceException(QString const& error_message, int error_code); ~ResourceException(); virtual ResourceException* clone() const override; virtual void raise() const override; int error_code() const noexcept; private: int error_code_; }; } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/File.h000066400000000000000000000054351521521330000257570ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include namespace lomiri { namespace storage { namespace qt { namespace client { class Downloader; class Uploader; namespace internal { class FileBase; namespace local_client { class FileImpl; } // namespace local_client namespace remote_client { class FileImpl; } // namespace remotelocal_client } // namespace internal /** \brief Class that represents a file. A file is a sequence of bytes. */ class LOMIRI_STORAGE_EXPORT File final : public Item { public: /// @cond virtual ~File(); /// @endcond File(File&&); File& operator=(File&&); /** \brief Convenience type definition. */ typedef std::shared_ptr SPtr; /** \brief Returns the size of the file in bytes. \throws DestroyedException if the file has been destroyed. */ int64_t size() const; /** \brief Creates an uploader for the file. \param policy The conflict resolution policy. If set to ConflictPolicy::overwrite, the contents of the file will be overwritten even if the file was modified after this File instance was retrieved. Otherwise, if set to ConflictPolicy::error_if_conflict, an attempt to retrieve the File instance from the future returned by Uploader::finish_upload() throws ConflictException if the file was was modified via some other channel. \param size The size of the upload in bytes. \note The provided file size must match the number of bytes that you write for the upload, otherwise an attampt to retrive the File instance from the future returned by Uploader::finish_upload() throws LogicException. */ QFuture> create_uploader(ConflictPolicy policy, int64_t size); /** \brief Creates a downloader for the file. */ QFuture> create_downloader(); private: File(internal::FileBase*) LOMIRI_STORAGE_HIDDEN; friend class internal::local_client::FileImpl; friend class internal::remote_client::FileImpl; }; } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/Folder.h000066400000000000000000000077431521521330000263170ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { class FolderBase; namespace local_client { class FolderImpl; class ItemImpl; } // namespace local_client namespace remote_client { class FolderImpl; class ItemImpl; } // namespace local_client } // namespace internal /** \brief Class that represents a folder. A folder is an unordered set of files and/or folders. */ class LOMIRI_STORAGE_EXPORT Folder : public Item { public: /// @cond virtual ~Folder(); /// @endcond Folder(Folder&&); Folder& operator=(Folder&&); typedef std::shared_ptr SPtr; /** \brief Returns the contents of a folder. \return A vector of items or, if this folder is empty, an empty vector. If there is a large number of items, the returned future may become ready more than once. (See QFutureWatcher for more information.) */ QFuture> list() const; /** \brief Returns the item within this folder with the given name. \return The item. If no such item exists, retrieving the result from the future throws an exception. */ QFuture> lookup(QString const& name) const; /** \brief Creates a new folder with the current folder as the parent. \param name The name of the new folder. Note that the actual name may be changed by the provider; call Item::name() once the folder is created to get its actual name. \warn Do not rely on create_folder() to fail if an attempt is made to create a folder with the same name as an already existing folder or file. Depending on the cloud provider, it may be possible to have several folders with the same name. // TODO: Explain issues with metacharacters. \return The new folder. */ QFuture create_folder(QString const& name); /** \brief Creates a new file with the current folder as the parent. Use the returned Uploader to write data to the file. You must call Uploader::finish_upload() for the file to actually be created (whether data was written to the file or not). \param name The name of the new file. Note that the actual name may be changed by the provider; call Item::name() once the file is created to get its actual name. \param size The size of the upload in bytes. \note The provided file size must match the number of bytes that you write for the upload, otherwise an attampt to retrive the File instance from the future returned by Uploader::finish_upload() throws LogicException. \warn Do not rely on create_file() to fail if an attempt is made to create a file with the same name as an already existing file or folder. Depending on the cloud provider, it may be possible to have several files with the same name. // TODO: Explain issues with metacharacters. */ QFuture> create_file(QString const& name, int64_t size); protected: Folder(internal::FolderBase*) LOMIRI_STORAGE_HIDDEN; friend class internal::local_client::FolderImpl; friend class internal::local_client::ItemImpl; friend class internal::remote_client::FolderImpl; }; } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/Item.h000066400000000000000000000174451521521330000260020ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #include #pragma GCC diagnostic pop #include namespace lomiri { namespace storage { namespace qt { namespace client { class Folder; class Root; typedef QMap MetadataMap; namespace internal { class ItemBase; namespace local_client { class UploadWorker; } // namespace local_client namespace remote_client { class CopyHandler; class ItemImpl; class LookupHandler; class MetadataHandler; } // namespace remote_client } // namespace internal /** \brief Base class for files and folders. */ class LOMIRI_STORAGE_EXPORT Item { public: /// @cond virtual ~Item(); /// @endcond Item(Item&&); Item& operator=(Item&&); /** \brief Convenience type definition. */ typedef std::shared_ptr SPtr; /** \brief Returns the native identifier used by the provider. */ QString native_identity() const; /** \brief Returns the name of the file or folder. The returned name may not be the same as the name that was used to create the item because the provider may have changed it in some way (such as converting upper case characters to lower case). */ QString name() const; /** \brief Returns the root folder for this item. If this item is a root, the returned pointer points at this item. */ std::shared_ptr root() const; /** \brief Returns the type of the item. */ ItemType type() const; /** \brief Returns a version identifier for the item. The version identifier changes each time the file is updated (possibly via some channel other than this API). */ QString etag() const; /** \brief Returns metadata for the item. TODO: Needs a lot more doc. Explain standard and provider-specific metadata. */ QVariantMap metadata() const; /** \brief Returns the time at which the item was last modified. */ QDateTime last_modified_time() const; /** \brief Returns a list of parent folders of this item. \return A vector of parents. For a root, the returned vector is empty. \warn Depending on the provider, a single file or folder may have multiple parents. Do not assume that only a single parent will be returned, or that parents are returned in a particular order. */ QFuture>> parents() const; /** \brief Returns the native identities of the parents of this item. \return A vector of parent identities. For a root, the returned vector is empty. \warn Depending on the provider, a single file or folder may have multiple parents. Do not assume that only a single parent ID will be returned, or that parent IDs are returned in a particular order. */ QVector parent_ids() const; /** \brief Copies this item. Copying a folder recursively copies its contents. \param new_parent The new parent folder for the item. If the item is to be copied within its current folder, this parameter must designate the currently existing parent. \param new_name The new name for the file. \warn Do not rely on copy() to fail if an attempt is made to copy a file or folder to a destination name that is the same as that of an already existing file or folder. Depending on the cloud provider, it may be possible to have several folders with the same name. */ QFuture copy(std::shared_ptr const& new_parent, QString const& new_name); /** \brief Renames and/or moves a file or folder. \param new_parent The new parent folder for the item. If the item is to be renamed within its current folder, this parameter must designate the currently existing parent. \param new_name The new name for the item. \warn Do not rely on move() to fail if an attempt is made to move a file or folder to a destination name that is the same as that of an already existing file or folder. Depending on the cloud provider, it may be possible to have several files or folders with the same name. \note It is not possible to move or rename the root folder. */ QFuture move(std::shared_ptr const& new_parent, QString const& new_name); /** \brief Permamently deletes the item. \warning Deleting a folder recursively deletes its contents. */ QFuture delete_item(); /** \brief Returns the time at which an item was created. \return If a provider does not support this method, the returned `QDateTime`'s `isValid()` method returns false. */ QDateTime creation_time() const; /** \brief Returns provider-specific metadata. The contents of the returned map depend on the actual provider. This method is provided to allow applications to use provider-specific features that may not be supported by all providers. \return The returned map may be empty if a provider does not support this feature. If a provider supports it, the following keys are guaranteed to be present: - `native_provider_id` (string) A string that identifies the provider, such as "mCloud". - `native_provider_version` (string) A string that provides a version identifier. \warn Unless you know that your application will only be used with a specific provider, avoid using this method. If you do use provider-specific data, ensure reasonable fallback behavior for your application if it encounters a different provider that does not support a particular metadata item. // TODO: document where to find the list of metadata items for each concrete provider. */ MetadataMap native_metadata() const; /** \brief Compares two items for equality. Equality comparison is deep, that is, it compares the native identities of two items, not their `shared_ptr` values. \note If you retrieve the same item more than once (such as by calling Root::get() twice with the same file ID) and then perform an upload using one of the two file handles, the files still have the same identity after the upload. However, the etag() values of the two file handles differ after the upload. Despite this, equal_to() still returns `true` for the two files, that is, the ETags are ignored for equality comparison. \return `!this->native_identity() == other->native_identity()` \throws DeletedException if `this` or `other` have been deleted. */ bool equal_to(Item::SPtr const& other) const noexcept; protected: Item(internal::ItemBase* p) LOMIRI_STORAGE_HIDDEN; std::shared_ptr p_; friend class internal::local_client::UploadWorker; friend class internal::remote_client::CopyHandler; friend class internal::remote_client::ItemImpl; friend class internal::remote_client::LookupHandler; friend class internal::remote_client::MetadataHandler; }; } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/Root.h000066400000000000000000000035771521521330000260300ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include namespace lomiri { namespace storage { namespace qt { namespace client { class Account; class Item; namespace internal { class RootBase; namespace local_client { class RootImpl; } // namespace local_client namespace remote_client { class RootImpl; } // namespace remote_client } // namespace internal /** \brief Class that represents a root folder. */ class LOMIRI_STORAGE_EXPORT Root final : public Folder { public: // @cond virtual ~Root(); /// @endcond Root(Root&&); Root& operator=(Root&&); typedef std::shared_ptr SPtr; /** \brief Returns the account for this root. */ std::shared_ptr account() const; QFuture free_space_bytes() const; QFuture used_space_bytes() const; QFuture get(QString native_identity) const; // TODO: Do we need a method to get lots of things? private: Root(internal::RootBase*) LOMIRI_STORAGE_HIDDEN; friend class internal::local_client::RootImpl; friend class internal::remote_client::RootImpl; }; } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/Runtime.h000066400000000000000000000055601521521330000265220ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #pragma GCC diagnostic pop #include class QDBusConnection; namespace lomiri { namespace storage { namespace qt { namespace client { class Account; namespace internal { class AccountBase; class RuntimeBase; namespace remote_client { class AccountImpl; } // namespace remote_client } // namespace internal /** TODO */ class LOMIRI_STORAGE_EXPORT Runtime final { public: /** \brief Destroys the runtime. The destructor implicitly calls shutdown(). \warning Do not invoke methods on any other part of the API once the runtime is destroyed; doing so has undefined behavior. */ ~Runtime(); Runtime(Runtime&&); Runtime& operator=(Runtime&&); typedef std::shared_ptr SPtr; /** \brief Initializes the runtime. */ static SPtr create(); static SPtr create(QDBusConnection const& bus); /** \brief Shuts down the runtime. This method shuts down the runtime. Calling shutdown() more than once is safe and does nothing. The destructor implicitly calls shutdown(). This method is provided mainly to permit logging of any errors that might arise during shut-down. \throws Various exceptions, depending on the error. TODO */ void shutdown(); QFuture>> accounts(); /// @cond /** \brief Creates an Account object pointing at (bus_name, object_path) This method is intended for use in tests, where you want to talk to a provider that has already been set up on the bus. */ std::shared_ptr make_test_account(QString const& bus_name, QString const& object_path); /// @endcond private: Runtime(internal::RuntimeBase* p) LOMIRI_STORAGE_HIDDEN; std::shared_ptr p_; friend class internal::AccountBase; friend class internal::remote_client::AccountImpl; }; } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/Uploader.h000066400000000000000000000077771521521330000266660ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #pragma GCC diagnostic pop #include #include class QLocalSocket; namespace lomiri { namespace storage { namespace qt { namespace client { class File; namespace internal { class UploaderBase; namespace local_client { class FileImpl; class FolderImpl; } // namespace local_client namespace remote_client { class UploaderImpl; } // namespace remote_client } // namespace internal class LOMIRI_STORAGE_EXPORT Uploader final { public: /** \brief Destroys the uploader. The destructor implicitly calls cancel() if it has not been called already. */ ~Uploader(); Uploader(Uploader&&); Uploader& operator=(Uploader&&); /** \brief Convenience type definition. */ typedef std::shared_ptr SPtr; /** \brief Returns a socket that is open for writing. To upload the file contents, write to the returned socket. If an operation on the socket returns an error, the file is in an indeterminate state. \return A socket open for writing. */ std::shared_ptr socket() const; /** \brief Returns the size that was passed to Folder::create_file() or File::create_uploader(). \return The number of bytes that the uploader expects to be written to the `QLocalSocket` returned from socket(). */ int64_t size() const; /** \brief Finalizes the upload. Once you have written the file contents to the socket returned by socket(), you must call finish_upload(), which closes the socket. Call `result()` on the returned future to check for errors. If an error occurred, `result()` throws an exception. If the upload was cancelled, `result` throws CancelledException. Otherwise, it returns the File that was uploaded. Calling finish_upload() more than once is safe; subsequent calls do nothing and return the future that was returned by the first call. */ QFuture> finish_upload(); /** \brief Cancels an upload. Calling cancel() informs the provider that the upload is no longer needed. The provider will make a best-effort attempt to cancel the upload to the remote service. You can check whether the cancel was successfully sent by calling `waitForFinished()` on the returned future. If this does not throw an exception, the message was received and acted upon by the provider. However, successful completion does _not_ indicate that the upload was actually cancelled. (For example, the upload may have completed already by the time the provider received the cancel request, or the provider may not support cancellation.) Calling cancel() more than once, or calling cancel() after a call to finish_upload() is safe and does nothing. */ QFuture cancel(); private: Uploader(internal::UploaderBase*) LOMIRI_STORAGE_HIDDEN; std::shared_ptr p_; friend class internal::local_client::FileImpl; friend class internal::local_client::FolderImpl; friend class internal::remote_client::UploaderImpl; }; } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/000077500000000000000000000000001521521330000265345ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/AccountBase.h000066400000000000000000000035341521521330000311010ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #pragma GCC diagnostic pop #include #include #include namespace lomiri { namespace storage { namespace qt { namespace client { class Account; class Root; class Runtime; namespace internal { class AccountBase { public: AccountBase(std::weak_ptr const& runtime); virtual ~AccountBase() = default; AccountBase(AccountBase const&) = delete; AccountBase& operator=(AccountBase const&) = delete; std::shared_ptr runtime() const; virtual QString owner() const = 0; virtual QString owner_id() const = 0; virtual QString description() const = 0; virtual QFuture>> roots() = 0; void set_public_instance(std::weak_ptr const& p); protected: std::weak_ptr runtime_; // Immutable once set std::weak_ptr public_instance_; // Immutable once set }; } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/DownloaderBase.h000066400000000000000000000027531521521330000316050ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #pragma GCC diagnostic pop #include class QLocalSocket; namespace lomiri { namespace storage { namespace qt { namespace client { class File; namespace internal { class DownloaderBase : public QObject { public: DownloaderBase(std::weak_ptr file); virtual std::shared_ptr file() const = 0; virtual std::shared_ptr socket() const = 0; virtual QFuture finish_download() = 0; virtual QFuture cancel() noexcept = 0; protected: std::shared_ptr file_; }; } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/FileBase.h000066400000000000000000000025041521521330000303600ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include namespace lomiri { namespace storage { namespace qt { namespace client { class Downloader; class File; class Uploader; namespace internal { class FileBase : public virtual ItemBase { public: FileBase(QString const& identity); virtual int64_t size() const = 0; virtual QFuture> create_uploader(ConflictPolicy policy, int64_t size) = 0; virtual QFuture> create_downloader() = 0; }; } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/FolderBase.h000066400000000000000000000026711521521330000307210ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include namespace lomiri { namespace storage { namespace qt { namespace client { class Uploader; namespace internal { class FolderBase : public virtual ItemBase { public: FolderBase(QString const& identity, ItemType type); virtual QFuture>> list() const = 0; virtual QFuture>> lookup(QString const& name) const = 0; virtual QFuture> create_folder(QString const& name) = 0; virtual QFuture> create_file(QString const& name, int64_t size) = 0; }; } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/ItemBase.h000066400000000000000000000054141521521330000304020ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #include #pragma GCC diagnostic pop #include #include namespace lomiri { namespace storage { namespace qt { namespace client { class Folder; class Item; class Root; typedef QMap MetadataMap; namespace internal { class ItemImpl; class ItemBase : public std::enable_shared_from_this { public: ItemBase(QString const& identity, ItemType type); virtual ~ItemBase(); ItemBase(ItemBase const&) = delete; ItemBase& operator=(ItemBase const&) = delete; QString native_identity() const; ItemType type() const; std::shared_ptr root() const; virtual QString name() const = 0; virtual QString etag() const = 0; virtual QVariantMap metadata() const = 0; virtual QDateTime last_modified_time() const = 0; virtual QFuture> copy(std::shared_ptr const& new_parent, QString const& new_name) = 0; virtual QFuture> move(std::shared_ptr const& new_parent, QString const& new_name) = 0; virtual QFuture>> parents() const = 0; virtual QVector parent_ids() const = 0; virtual QFuture delete_item() = 0; virtual QDateTime creation_time() const = 0; virtual MetadataMap native_metadata() const = 0; virtual bool equal_to(ItemBase const& other) const noexcept = 0; void set_root(std::weak_ptr p); void set_public_instance(std::weak_ptr p); protected: std::shared_ptr get_root() const noexcept; void throw_if_destroyed(QString const& method) const; const QString identity_; const ItemType type_; std::weak_ptr root_; std::weak_ptr public_instance_; bool deleted_ = false; friend class ItemImpl; }; } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/RootBase.h000066400000000000000000000036001521521330000304220ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace remote_client { class CopyHandler; class ItemImpl; class LookupHandler; class MetadataHandler; } // namespace remote_client class RootBase : public virtual FolderBase { public: RootBase(QString const& identity, std::weak_ptr const& account); std::shared_ptr account() const; virtual QFuture free_space_bytes() const = 0; virtual QFuture used_space_bytes() const = 0; virtual QFuture get(QString native_identity) const = 0; static std::shared_ptr make_root(QString const& identity, std::weak_ptr const& account); protected: std::weak_ptr account_; friend class remote_client::CopyHandler; friend class remote_client::ItemImpl; // TODO: probably no longer needed friend class remote_client::LookupHandler; friend class remote_client::MetadataHandler; }; } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/RuntimeBase.h000066400000000000000000000036701521521330000311310ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #pragma GCC diagnostic pop #include #include namespace lomiri { namespace storage { namespace qt { namespace client { class Account; class Runtime; namespace internal { class AccountBase; class RuntimeBase : public QObject { public: RuntimeBase() = default; virtual ~RuntimeBase() = default; RuntimeBase(RuntimeBase const&) = delete; RuntimeBase& operator=(RuntimeBase const&) = delete; virtual void shutdown() = 0; virtual QFuture>> accounts() = 0; virtual std::shared_ptr make_test_account(QString const& bus_name, QString const& object_path) = 0; void set_public_instance(std::weak_ptr p); protected: bool destroyed_ = false; QVector> accounts_; std::weak_ptr public_instance_; // Immutable once set friend class lomiri::storage::qt::client::internal::AccountBase; }; } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/UploaderBase.h000066400000000000000000000031771521521330000312630ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #pragma GCC diagnostic pop #include class QLocalSocket; namespace lomiri { namespace storage { namespace qt { namespace client { class File; namespace internal { class UploaderBase : public QObject { public: UploaderBase(ConflictPolicy policy, int64_t size); UploaderBase(UploaderBase&) = delete; UploaderBase& operator=(UploaderBase const&) = delete; virtual std::shared_ptr socket() const = 0; virtual QFuture> finish_upload() = 0; virtual QFuture cancel() noexcept = 0; int64_t size() const; protected: ConflictPolicy policy_; int64_t size_; }; } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/local_client/000077500000000000000000000000001521521330000311645ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/local_client/AccountImpl.h000066400000000000000000000033171521521330000335570ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace local_client { class AccountImpl : public virtual AccountBase { public: AccountImpl(std::weak_ptr const& runtime, QString const& owner, QString const& owner_id, QString const& description); virtual QString owner() const override; virtual QString owner_id() const override; virtual QString description() const override; virtual QFuture>> roots() override; private: QString owner_; // Immutable QString owner_id_; // Immutable QString description_; // Immutable QVector> roots_; // Immutable }; } // namespace local_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri DownloaderImpl.h000066400000000000000000000054211521521330000342000ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/local_client/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include #include class QLocalSocket; namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace local_client { class DownloadWorker : public QObject { Q_OBJECT public: DownloadWorker(int write_fd, QString const& filename, QFutureInterface& qf, QFutureInterface& worker_initialized); void start_downloading() noexcept; public Q_SLOTS: void do_finish(); void do_cancel(); private Q_SLOTS: void on_bytes_written(qint64 bytes); void on_disconnected(); void on_error(); private: void read_and_write_chunk(); void handle_error(QString const& msg, int error_code); enum State { in_progress, finalized, cancelled, error }; State state_ = in_progress; int write_fd_; std::shared_ptr write_socket_; QString filename_; std::unique_ptr input_file_; QFutureInterface& qf_; QFutureInterface& worker_initialized_; qint64 bytes_to_write_; QString error_msg_; int error_code_ = 0; }; class DownloadThread : public QThread { Q_OBJECT public: DownloadThread(DownloadWorker* worker); virtual void run() override; private: DownloadWorker* worker_; }; class DownloaderImpl : public DownloaderBase { Q_OBJECT public: DownloaderImpl(std::weak_ptr file); virtual ~DownloaderImpl(); std::shared_ptr file() const override; std::shared_ptr socket() const override; QFuture finish_download() override; QFuture cancel() noexcept override; Q_SIGNALS: void do_finish(); void do_cancel(); private: std::shared_ptr read_socket_; QFutureInterface qf_; std::unique_ptr download_thread_; std::unique_ptr worker_; }; } // namespace local_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/local_client/FileImpl.h000066400000000000000000000031021521521330000330320ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace local_client { class FileImpl : public virtual FileBase, public virtual ItemImpl { public: FileImpl(QString const& identity); virtual QString name() const override; virtual int64_t size() const override; virtual QFuture> create_uploader(ConflictPolicy policy, int64_t size) override; virtual QFuture> create_downloader() override; static std::shared_ptr make_file(QString const& identity, std::weak_ptr root); }; } // namespace local_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/local_client/FolderImpl.h000066400000000000000000000033531521521330000333760ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace local_client { class FolderImpl : public virtual FolderBase, public virtual ItemImpl { public: FolderImpl(QString const& identity); FolderImpl(QString const& identity, ItemType type); virtual QString name() const override; QFuture>> list() const override; QFuture>> lookup(QString const& name) const override; QFuture> create_folder(QString const& name) override; QFuture> create_file(QString const& name, int64_t size) override; static std::shared_ptr make_folder(QString const& identity, std::weak_ptr root); }; } // namespace local_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/local_client/ItemImpl.h000066400000000000000000000051641521521330000330630ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include #include #include #include namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { class MetadataImpl; namespace local_client { class ItemImpl : public virtual ItemBase { public: ItemImpl(QString const& identity, ItemType type); virtual ~ItemImpl(); virtual QString etag() const override; virtual QVariantMap metadata() const override; virtual QDateTime last_modified_time() const override; virtual QFuture> copy(std::shared_ptr const& new_parent, QString const& new_name) override; virtual QFuture> move(std::shared_ptr const& new_parent, QString const& new_name) override; virtual QFuture>> parents() const override; virtual QVector parent_ids() const override; virtual QFuture delete_item() override; virtual QDateTime creation_time() const override; virtual MetadataMap native_metadata() const override; virtual bool equal_to(ItemBase const& other) const noexcept override; void set_timestamps() noexcept; bool has_conflict() const noexcept; protected: static boost::filesystem::path sanitize(QString const& name, QString const& method); static bool is_reserved_path(boost::filesystem::path const& path) noexcept; QString name_; QString etag_; QDateTime modified_time_; QVariantMap metadata_; std::recursive_mutex mutable mutex_; private: static void copy_recursively(boost::filesystem::path const& source, boost::filesystem::path const& target); }; } // namespace local_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/local_client/RootImpl.h000066400000000000000000000034341521521330000331060ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace local_client { class RootImpl : public virtual RootBase, public virtual FolderImpl { public: RootImpl(QString const& identity, std::weak_ptr const& account); virtual QString name() const override; virtual QFuture>> parents() const override; virtual QVector parent_ids() const override; virtual QFuture delete_item() override; virtual QFuture free_space_bytes() const override; virtual QFuture used_space_bytes() const override; virtual QFuture get(QString native_identity) const override; static std::shared_ptr make_root(QString const& identity, std::weak_ptr const& account); }; } // namespace local_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/local_client/RuntimeImpl.h000066400000000000000000000026411521521330000336050ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace local_client { class RuntimeImpl : public virtual RuntimeBase { public: RuntimeImpl(); virtual ~RuntimeImpl(); virtual void shutdown() override; virtual QFuture>> accounts() override; virtual std::shared_ptr make_test_account(QString const& bus_name, QString const& object_path) override; }; } // namespace local_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/local_client/UploaderImpl.h000066400000000000000000000066031521521330000337370ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include #include #include #include class QLocalSocket; namespace lomiri { namespace storage { namespace qt { namespace client { class File; class Root; namespace internal { namespace local_client { class UploadWorker : public QObject { Q_OBJECT public: UploadWorker(int read_fd, std::weak_ptr file, int64_t size, QString const& path, ConflictPolicy policy, std::weak_ptr root, QFutureInterface>& qf, QFutureInterface& worker_initialized); virtual ~UploadWorker(); void start_uploading() noexcept; public Q_SLOTS: void do_finish(); void do_cancel(); private Q_SLOTS: void on_bytes_ready(); void on_read_channel_finished(); private: void read_and_write_chunk(); void finalize(); void handle_error(QString const& msg, int error_code); enum State { in_progress, finalized, cancelled, error }; State state_ = in_progress; int read_fd_; std::shared_ptr read_socket_; std::weak_ptr file_; int64_t size_; int64_t bytes_read_; QString path_; std::weak_ptr root_; std::unique_ptr output_file_; lomiri::util::ResourcePtr> tmp_fd_; ConflictPolicy policy_; QFutureInterface>& qf_; QFutureInterface& worker_initialized_; QString error_msg_; int error_code_ = 0; bool use_linkat_ = true; }; class UploadThread : public QThread { Q_OBJECT public: UploadThread(UploadWorker* worker); virtual void run() override; private: UploadWorker* worker_; }; class UploaderImpl : public UploaderBase { Q_OBJECT public: UploaderImpl(std::weak_ptr file, int64_t size, QString const& path, ConflictPolicy policy, std::weak_ptr root); virtual ~UploaderImpl(); virtual std::shared_ptr socket() const override; virtual QFuture> finish_upload() override; virtual QFuture cancel() noexcept override; Q_SIGNALS: void do_finish(); void do_cancel(); private: std::shared_ptr write_socket_; QFutureInterface> qf_; std::unique_ptr upload_thread_; std::unique_ptr worker_; }; } // namespace local_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri boost_filesystem.h000066400000000000000000000016571521521330000346610ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/local_client/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #if BOOST_VERSION < 105600 #define BOOST_NO_CXX11_SCOPED_ENUMS #include #undef BOOST_NO_CXX11_SCOPED_ENUMS #else #include #endif storage_exception.h000066400000000000000000000047301521521330000350040ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/local_client/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #pragma GCC diagnostic ignored "-Wcast-align" #include #pragma GCC diagnostic pop #include class QString; namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace local_client { void throw_storage_exception(QString const& method, std::exception_ptr ep) __attribute__ ((noreturn)); void throw_storage_exception(QString const& method, std::exception_ptr ep, QString const& key) __attribute__ ((noreturn)); template QFuture make_exceptional_future(QString const& method, std::exception_ptr ep) { try { throw_storage_exception(method, ep); } catch (StorageException const& e) { QFutureInterface qf; qf.reportException(e); qf.reportFinished(); return qf.future(); } abort(); // Impossible. // LCOV_EXCL_LINE } template QFuture make_exceptional_future(QString const& method, std::exception_ptr ep, QString const& key) { try { throw_storage_exception(method, ep, key); } catch (StorageException const& e) { QFutureInterface qf; qf.reportException(e); qf.reportFinished(); return qf.future(); } abort(); // Impossible. // LCOV_EXCL_LINE } } // namespace local_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri tmpfile_prefix.h000066400000000000000000000013771521521330000343030ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/local_client/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #define TMPFILE_PREFIX ".storage-framework-" lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/make_future.h000066400000000000000000000036461521521330000312250ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #pragma GCC diagnostic ignored "-Wcast-align" #include #pragma GCC diagnostic pop #include namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { template QFuture __attribute__ ((warn_unused_result)) make_ready_future(T const& val) { QFutureInterface qf; qf.reportResult(val); qf.reportFinished(); return qf.future(); } template QFuture __attribute__ ((warn_unused_result)) make_ready_future() { QFutureInterface qf; return make_ready_future(qf); } template QFuture __attribute__ ((warn_unused_result)) make_exceptional_future(E const& ex) { QFutureInterface qf; qf.reportException(ex); qf.reportFinished(); return qf.future(); } template QFuture __attribute__ ((warn_unused_result)) make_exceptional_future(E const& ex) { QFutureInterface qf; qf.reportException(ex); qf.reportFinished(); return qf.future(); } } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/remote_client/000077500000000000000000000000001521521330000313655ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/remote_client/AccountImpl.h000066400000000000000000000035111521521330000337540ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include class ProviderInterface; namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace remote_client { class AccountImpl : public QObject, public virtual AccountBase { public: AccountImpl(std::weak_ptr const& runtime, QString const& bus_name, QString const& object_path, QString const& owner, QString const& owner_id, QString const& description); virtual QString owner() const override; virtual QString owner_id() const override; virtual QString description() const override; virtual QFuture>> roots() override; std::shared_ptr provider() const noexcept; private: QString owner_; QString owner_id_; QString description_; QVector> roots_; std::shared_ptr provider_; }; } // namespace remote_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri DownloaderImpl.h000066400000000000000000000043221521521330000344000ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/remote_client/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include class ProviderInterface; class QLocalSocket; namespace lomiri { namespace storage { namespace qt { namespace client { class Downloader; namespace internal { namespace remote_client { class DownloaderImpl : public DownloaderBase { public: DownloaderImpl(QString const& download_id, QDBusUnixFileDescriptor fd, std::shared_ptr const& file, std::shared_ptr const& provider); virtual ~DownloaderImpl(); virtual std::shared_ptr file() const override; virtual std::shared_ptr socket() const override; virtual QFuture finish_download() override; virtual QFuture cancel() noexcept override; static std::shared_ptr make_downloader(QString const& upload_id, QDBusUnixFileDescriptor fd, std::shared_ptr const& file, std::shared_ptr const& provider); private: QString download_id_; QDBusUnixFileDescriptor fd_; std::shared_ptr file_; std::shared_ptr provider_; std::shared_ptr read_socket_; }; } // namespace remote_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/remote_client/FileImpl.h000066400000000000000000000032041521521330000332360ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include namespace lomiri { namespace storage { namespace internal { struct ItemMetadata; } // namespace internal namespace qt { namespace client { namespace internal { namespace remote_client { class FileImpl : public virtual FileBase, public virtual ItemImpl { public: FileImpl(storage::internal::ItemMetadata const& md); virtual int64_t size() const override; virtual QFuture> create_uploader(ConflictPolicy policy, int64_t size) override; virtual QFuture> create_downloader() override; static std::shared_ptr make_file(storage::internal::ItemMetadata const& md, std::weak_ptr root); }; } // namespace remote_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/remote_client/FolderImpl.h000066400000000000000000000033711521521330000335770ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace remote_client { class FolderImpl : public virtual FolderBase, public virtual ItemImpl { public: FolderImpl(storage::internal::ItemMetadata const& md); FolderImpl(storage::internal::ItemMetadata const& md, ItemType type); QFuture>> list() const override; QFuture>> lookup(QString const& name) const override; QFuture> create_folder(QString const& name) override; QFuture> create_file(QString const& name, int64_t size) override; static std::shared_ptr make_folder(storage::internal::ItemMetadata const& md, std::weak_ptr root); }; } // namespace remote_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/remote_client/Handler.h000066400000000000000000000101231521521330000331100ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include #include #include #include #include namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace remote_client { template class Handler : public HandlerBase { public: template Handler(QObject* parent, QDBusPendingReply const& reply, std::function&)> closure); QFuture future(); private: QFutureInterface qf_; }; template template Handler::Handler(QObject* parent, QDBusPendingReply const& reply, std::function&)> closure) : HandlerBase(parent, reply, [this, closure](QDBusPendingCallWatcher const& call) { if (call.isError()) { try { auto ep = unmarshal_exception(call); std::rethrow_exception(ep); } // We catch some exceptions that are "surprising" so we can log those. catch (LocalCommsException const& e) { qCritical() << "provider exception:" << e.what(); qf_.reportException(e); qf_.reportFinished(); } catch (RemoteCommsException const& e) { qCritical() << "provider exception:" << e.what(); qf_.reportException(e); qf_.reportFinished(); } catch (ResourceException const& e) { qCritical() << "provider exception:" << e.what(); qf_.reportException(e); qf_.reportFinished(); } catch (StorageException const& e) { qf_.reportException(e); qf_.reportFinished(); } // LCOV_EXCL_START catch (...) { abort(); // Impossible. } // LCOV_EXCL_STOP return; } closure(call, qf_); }) { qf_.reportStarted(); } template QFuture Handler::future() { return qf_.future(); } } // namespace remote_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/remote_client/HandlerBase.h000066400000000000000000000032621521521330000337110ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #pragma GCC diagnostic ignored "-Wswitch-default" #include #pragma GCC diagnostic pop #include #include class QDBusPendingCall; namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace remote_client { class HandlerBase : public QObject { Q_OBJECT public: HandlerBase(QObject* parent, QDBusPendingCall const& call, std::function const& closure); public Q_SLOTS: void finished(QDBusPendingCallWatcher* call); protected: QDBusPendingCallWatcher watcher_; private: std::function closure_; }; } // namespace remote_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/remote_client/ItemImpl.h000066400000000000000000000046251521521330000332650ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include #include #include class ProviderInterface; namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace remote_client { class DeleteHandler; class ItemImpl : public virtual ItemBase, public virtual QObject { public: ItemImpl(storage::internal::ItemMetadata const& md, ItemType type); virtual QString name() const override; virtual QString etag() const override; virtual QVariantMap metadata() const override; virtual QDateTime last_modified_time() const override; virtual QFuture> copy(std::shared_ptr const& new_parent, QString const& new_name) override; virtual QFuture> move(std::shared_ptr const& new_parent, QString const& new_name) override; virtual QFuture>> parents() const override; virtual QVector parent_ids() const override; virtual QFuture delete_item() override; virtual QDateTime creation_time() const override; virtual MetadataMap native_metadata() const override; virtual bool equal_to(ItemBase const& other) const noexcept override; std::shared_ptr provider() const noexcept; static std::shared_ptr make_item(storage::internal::ItemMetadata const& md, std::weak_ptr root); protected: storage::internal::ItemMetadata md_; friend class DeleteHandler; }; } // namespace remote_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/remote_client/RootImpl.h000066400000000000000000000036471521521330000333150ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include namespace lomiri { namespace storage { namespace internal { struct ItemMetadata; } // namespace internal namespace qt { namespace client { namespace internal { namespace remote_client { class RootImpl : public virtual RootBase, public virtual FolderImpl { public: RootImpl(storage::internal::ItemMetadata const& md, std::weak_ptr const& account); virtual QFuture>> parents() const override; virtual QVector parent_ids() const override; virtual QFuture delete_item() override; virtual QFuture free_space_bytes() const override; virtual QFuture used_space_bytes() const override; virtual QFuture get(QString native_identity) const override; static std::shared_ptr make_root(storage::internal::ItemMetadata const& md, std::weak_ptr const& account); friend class FolderImpl; }; } // namespace remote_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/remote_client/RuntimeImpl.h000066400000000000000000000043761521521330000340150ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include #include #include namespace lomiri { namespace storage { namespace qt { namespace client { class Account; namespace internal { namespace remote_client { class RuntimeImpl : public RuntimeBase { Q_OBJECT public: RuntimeImpl(QDBusConnection const& bus); virtual ~RuntimeImpl(); virtual void shutdown() override; virtual QFuture>> accounts() override; virtual std::shared_ptr make_test_account(QString const& bus_name, QString const& object_path) override; QDBusConnection& connection(); private Q_SLOTS: virtual void manager_ready(); virtual void timeout(); private: std::shared_ptr make_account(QString const& bus_name, QString const& object_path, QString const& owner, QString const& owner_id, QString const& description); QDBusConnection conn_; std::unique_ptr manager_; // TODO: Hack until we can use the registry QTimer timer_; QVector> accounts_; QFutureInterface>> qf_; }; } // namespace remote_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri UploaderImpl.h000066400000000000000000000046361521521330000340650ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/remote_client/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include class QLocalSocket; class ProviderInterface; namespace lomiri { namespace storage { namespace qt { namespace client { class Root; class Uploader; namespace internal { namespace remote_client { class UploaderImpl : public UploaderBase { public: UploaderImpl(QString const& upload_id, QDBusUnixFileDescriptor fd, int64_t size, QString const& old_etag, std::weak_ptr root, std::shared_ptr const& provider); ~UploaderImpl(); virtual std::shared_ptr socket() const override; virtual QFuture> finish_upload() override; virtual QFuture cancel() noexcept override; static std::shared_ptr make_uploader(QString const& upload_id, QDBusUnixFileDescriptor fd, int64_t size, QString const& old_etag, std::weak_ptr root, std::shared_ptr const& provider); private: enum State { uploading, finalized }; QString upload_id_; QDBusUnixFileDescriptor fd_; QString old_etag_; std::shared_ptr root_; std::shared_ptr provider_; std::shared_ptr write_socket_; State state_; }; } // namespace remote_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/remote_client/dbusmarshal.h000066400000000000000000000020671521521330000340500ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include class QDBusPendingCallWatcher; namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace remote_client { std::exception_ptr unmarshal_exception(QDBusPendingCallWatcher const& call); } // namespace remote_client } // namespace internal } // client } // qt } // storage } // lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/client/internal/remote_client/validate.h000066400000000000000000000022171521521330000333310ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include namespace lomiri { namespace storage { namespace internal { class ItemMetadata; } // namespace internal namespace qt { namespace client { namespace internal { namespace remote_client { void validate(QString const& method, lomiri::storage::internal::ItemMetadata const& md); } // namespace remote_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/internal/000077500000000000000000000000001521521330000252565ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/internal/AccountImpl.h000066400000000000000000000050311521521330000276440ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include class ProviderInterface; namespace lomiri { namespace storage { namespace qt { namespace internal { class RuntimeImpl; class AccountImpl : public std::enable_shared_from_this { public: AccountImpl(); AccountImpl(AccountImpl const&) = default; AccountImpl(AccountImpl&&) = default; ~AccountImpl() = default; AccountImpl& operator=(AccountImpl const&) = default; AccountImpl& operator=(AccountImpl&&) = default; QString busName() const; QString objectPath() const; QString displayName() const; QString providerName() const; QString iconName() const; ItemListJob* roots(QStringList const& keys) const; ItemJob* get(QString const& itemId, QStringList const& keys) const; bool operator==(AccountImpl const&) const; bool operator!=(AccountImpl const&) const; bool operator<(AccountImpl const&) const; bool operator<=(AccountImpl const&) const; bool operator>(AccountImpl const&) const; bool operator>=(AccountImpl const&) const; size_t hash() const; std::shared_ptr runtime_impl() const; std::shared_ptr provider() const; static Account make_account(std::shared_ptr const& runtime_impl, storage::internal::AccountDetails const& details); private: AccountImpl(std::shared_ptr const& runtime_impl, storage::internal::AccountDetails const& details); bool is_valid_; storage::internal::AccountDetails details_; std::weak_ptr runtime_impl_; std::shared_ptr provider_; friend class lomiri::storage::qt::Account; }; } // namespace internal } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/internal/AccountsJobImpl.h000066400000000000000000000046131521521330000304670ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #pragma GCC diagnostic pop namespace lomiri { namespace storage { namespace qt { namespace internal { class RuntimeImpl; class AccountsJobImpl : public QObject { Q_OBJECT public: AccountsJobImpl(std::shared_ptr const& runtime_impl, QString const& method, QDBusPendingReply> const& reply); AccountsJobImpl(StorageError const& error); virtual ~AccountsJobImpl() = default; bool isValid() const; AccountsJob::Status status() const; StorageError error() const; QList accounts() const; QVariantList accountsAsVariantList() const; static AccountsJob* make_job(std::shared_ptr const& runtime_impl, QString const& method, QDBusPendingReply> const& reply); static AccountsJob* make_job(StorageError const& e); private: std::shared_ptr get_runtime_impl(QString const& method) const; AccountsJob::Status emit_status_changed(AccountsJob::Status new_status) const; AccountsJob* public_instance_; AccountsJob::Status status_; StorageError error_; std::weak_ptr const runtime_impl_; QList accounts_; friend class lomiri::storage::qt::AccountsJob; }; } // namespace internal } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/internal/DownloaderImpl.h000066400000000000000000000045011521521330000303470ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include #include namespace lomiri { namespace storage { namespace qt { namespace internal { class DownloaderImpl : public QObject { Q_OBJECT public: DownloaderImpl(std::shared_ptr const& item_impl, QString const& method, QDBusPendingReply const& reply); DownloaderImpl(StorageError const& e); virtual ~DownloaderImpl(); bool isValid() const; Downloader::Status status() const; StorageError error() const; Item item() const; void cancel(); // From QLocalSocket interface. void close(); qint64 bytesAvailable() const; qint64 bytesToWrite() const; bool canReadLine() const; bool isSequential() const; bool waitForBytesWritten(int msecs); bool waitForReadyRead(int msecs); qint64 readData(char* data, qint64 c); qint64 writeData(char const* data, qint64 c); static Downloader* make_job(std::shared_ptr const& item_impl, QString const& method, QDBusPendingReply const& reply); static Downloader* make_job(StorageError const& e); private: Downloader* public_instance_; Downloader::Status status_; StorageError error_; std::shared_ptr item_impl_; QString download_id_; QDBusUnixFileDescriptor fd_; QLocalSocket socket_; bool finalizing_ = false; }; } // namespace internal } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/internal/Handler.h000066400000000000000000000073241521521330000270120ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include #include #include #include #include namespace lomiri { namespace storage { namespace qt { namespace internal { template class Handler : public HandlerBase { public: template Handler(QObject* parent, QDBusPendingReply const& reply, std::function const& success_closure, std::function const& error_closure) : HandlerBase(parent, reply, [this, &reply, success_closure, error_closure](QDBusPendingCallWatcher& call) { if (call.isError()) { auto e = unmarshal_error(call); switch (e.type()) { case StorageError::Type::NoError: { // LCOV_EXCL_START QString msg = "impossible service exception: " + e.errorString(); qCritical().noquote() << msg; e = StorageErrorImpl::local_comms_error(msg); break; // LCOV_EXCL_STOP } case StorageError::Type::LocalCommsError: case StorageError::Type::RemoteCommsError: case StorageError::Type::ResourceError: { // Log these errors because they are unexpected. QString msg = "service exception: " + e.errorString(); qCritical().noquote() << msg; break; } default: { // All other errors are not logged. break; } } error_closure(e); return; } QDBusPendingReply r = call; success_closure(call); }) { } void wait_and_process_now() { watcher_.waitForFinished(); finished(&watcher_); } }; } // namespace internal } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/internal/HandlerBase.h000066400000000000000000000030721521521330000276010ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #pragma GCC diagnostic ignored "-Wswitch-default" #include #include #pragma GCC diagnostic pop #include class QDBusPendingCall; namespace lomiri { namespace storage { namespace qt { namespace internal { class HandlerBase : public QObject { Q_OBJECT public: HandlerBase(QObject* parent, QDBusPendingCall const& call, std::function const& closure); public Q_SLOTS: void finished(QDBusPendingCallWatcher* call); protected: QDBusPendingCallWatcher watcher_; std::function closure_; }; } // namespace internal } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/internal/ItemImpl.h000066400000000000000000000133101521521330000271450ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include #include #include #include #include namespace lomiri { namespace storage { namespace qt { namespace internal { class AccountImpl; class RuntimeImpl; class ItemImpl : public std::enable_shared_from_this { public: ItemImpl(); ItemImpl(storage::internal::ItemMetadata const& md, std::shared_ptr const& account_impl); ItemImpl(ItemImpl const&) = default; ItemImpl(ItemImpl&&) = delete; ~ItemImpl() = default; ItemImpl& operator=(ItemImpl const&) = default; ItemImpl& operator=(ItemImpl&&) = delete; QString itemId() const; QString name() const; Account account() const; QString etag() const; Item::Type type() const; QVariantMap metadata() const; qint64 sizeInBytes() const; QDateTime lastModifiedTime() const; QList parentIds() const; ItemListJob* parents(QStringList const& keys) const; ItemJob* copy(Item const& newParent, QString const& newName, QStringList const& keys) const; ItemJob* move(Item const& newParent, QString const& newName, QStringList const& keys) const; VoidJob* deleteItem() const; Uploader* createUploader(Item::ConflictPolicy policy, qint64 sizeInBytes, QStringList const& keys) const; Downloader* createDownloader(Item::ConflictPolicy policy) const; ItemListJob* list(QStringList const& keys) const; ItemListJob* lookup(QString const& name, QStringList const& keys) const; ItemJob* createFolder(QString const& name, QStringList const& keys) const; Uploader* createFile(QString const& name) const; Uploader* createFile(QString const& name, Item::ConflictPolicy policy, qint64 sizeInBytes, QString const& contentType, QStringList const& keys) const; bool operator==(ItemImpl const&) const; bool operator!=(ItemImpl const&) const; bool operator<(ItemImpl const&) const; bool operator<=(ItemImpl const&) const; bool operator>(ItemImpl const&) const; bool operator>=(ItemImpl const&) const; size_t hash() const; static Item make_item(QString const& method, storage::internal::ItemMetadata const& md, std::shared_ptr const& account_impl); std::shared_ptr runtime_impl() const; std::shared_ptr account_impl() const; private: template decltype(T::make_job(StorageError())) check_invalid_or_destroyed(QString const& method) const; template decltype(T::make_job(StorageError())) check_copy_move_precondition(QString const& method, Item const& newParent, QString const& newName) const; bool is_valid_; storage::internal::ItemMetadata md_; std::shared_ptr account_impl_; friend class lomiri::storage::qt::Item; }; template decltype(T::make_job(StorageError())) ItemImpl::check_invalid_or_destroyed(QString const& method) const { if (!is_valid_) { auto e = StorageErrorImpl::logic_error(method + ": cannot create job from invalid item"); return T::make_job(e); } auto runtime = account_impl_->runtime_impl(); if (!runtime || !runtime->isValid()) { auto e = StorageErrorImpl::runtime_destroyed_error(method + ": Runtime was destroyed previously"); return T::make_job(e); } return nullptr; } template decltype(T::make_job(StorageError())) ItemImpl::check_copy_move_precondition(QString const& method, Item const& newParent, QString const& newName) const { auto invalid_job = check_invalid_or_destroyed(method); if (invalid_job) { return invalid_job; } if (!newParent.isValid()) { auto e = StorageErrorImpl::invalid_argument_error(method + ": newParent is invalid"); return T::make_job(e); } if (newName.isEmpty()) { auto e = StorageErrorImpl::invalid_argument_error(method + ": newName cannot be empty"); return T::make_job(e); } if (account() != newParent.account()) { auto e = StorageErrorImpl::logic_error(method + ": source and target must belong to the same account"); return T::make_job(e); } if (newParent.type() == Item::Type::File) { auto e = StorageErrorImpl::logic_error(method + ": newParent cannot be a file"); return T::make_job(e); } return nullptr; } } // namespace internal } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/internal/ItemJobImpl.h000066400000000000000000000055361521521330000276130ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include #include namespace lomiri { namespace storage { namespace internal { class ItemMetadata; } // namespace internal namespace qt { namespace internal { class AccountImpl; class ItemJobImpl : public QObject { Q_OBJECT public: virtual ~ItemJobImpl() = default; bool isValid() const; ItemJob::Status status() const; StorageError error() const; Item item() const; static ItemJob* make_job(std::shared_ptr const& account_impl, QString const& method, QDBusPendingReply const& reply, std::function const& validate); static ItemJob* make_job(std::shared_ptr const& item_impl, QString const& method, QDBusPendingReply const& reply, std::function const& validate); static ItemJob* make_job(StorageError const& e); private: ItemJobImpl(std::shared_ptr const& account, QString const& method, QDBusPendingReply const& reply, std::function const& validate); ItemJobImpl(std::shared_ptr const& item_impl, QString const& method, QDBusPendingReply const& reply, std::function const& validate); ItemJobImpl(StorageError const& e); ItemJob* public_instance_; ItemJob::Status status_; StorageError error_; QString method_; std::shared_ptr account_impl_; std::shared_ptr item_impl_; std::function validate_; Item item_; }; } // namespace internal } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/internal/ItemListJobImpl.h000066400000000000000000000051551521521330000304440ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include #include namespace lomiri { namespace storage { namespace internal { class ItemMetadata; } namespace qt { namespace internal { class AccountImpl; class ItemImpl; class ItemListJobImpl : public ListJobImplBase { Q_OBJECT public: virtual ~ItemListJobImpl() = default; static ItemListJob* make_job(std::shared_ptr const& account_impl, QString const& method, QDBusPendingReply> const& reply, std::function const& validate); static ItemListJob* make_job(std::shared_ptr const& item_impl, QString const& method, QDBusPendingReply> const& reply, std::function const& validate); static ItemListJob* make_job(StorageError const& error); private: ItemListJobImpl() = default; ItemListJobImpl(std::shared_ptr const& account_impl, QString const& method, QDBusPendingReply> const& reply, std::function const& validate); ItemListJobImpl(std::shared_ptr const& item_impl, QString const& method, QDBusPendingReply> const& reply, std::function const& validate); std::shared_ptr item_impl_; }; } // namespace internal } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/internal/ListJobImplBase.h000066400000000000000000000042071521521330000304150ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #pragma GCC diagnostic pop namespace lomiri { namespace storage { namespace internal { class ItemMetadata; } // namespace internal namespace qt { namespace internal { class AccountImpl; class MultiItemJobImpl; class ListJobImplBase : public QObject { public: ListJobImplBase(); // Makes job in Finished state. ListJobImplBase(std::shared_ptr const& account_impl, QString const& method, std::function const& validate); ListJobImplBase(StorageError const& error); virtual ~ListJobImplBase() = default; bool isValid() const; ItemListJob::Status status() const; StorageError error() const; void set_public_instance(ItemListJob* p); static ItemListJob* make_job(StorageError const& error); static ItemListJob* make_empty_job(); protected: ItemListJob* public_instance_; ItemListJob::Status status_; StorageError error_; QString method_; std::shared_ptr account_impl_; std::function validate_; }; } // namespace internal } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/internal/MultiItemJobImpl.h000066400000000000000000000036301521521330000306170ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include #include namespace lomiri { namespace storage { namespace internal { class ItemMetadata; } // namespace internal namespace qt { namespace internal { class AccountImpl; class MultiItemJobImpl : public ListJobImplBase { Q_OBJECT public: using ReplyType = QList>; using ValidateFunc = std::function; virtual ~MultiItemJobImpl() = default; static ItemListJob* make_job(std::shared_ptr const& account_impl, QString const& method, ReplyType const& replies, ValidateFunc const& validate); private: MultiItemJobImpl() = default; MultiItemJobImpl(std::shared_ptr const& account_impl, QString const& method, ReplyType const& replies, ValidateFunc const& validate); int replies_remaining_; }; } // namespace internal } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/internal/MultiItemListJobImpl.h000066400000000000000000000046551521521330000314630ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include #include namespace lomiri { namespace storage { namespace internal { class ItemMetadata; } namespace qt { namespace internal { class ItemImpl; class MultiItemListJobImpl : public ListJobImplBase { Q_OBJECT public: using ReplyType = QDBusPendingReply, QString>; using ValidateFunc = std::function; using FetchFunc = std::function, QString>(QString const& page_token)>; virtual ~MultiItemListJobImpl() = default; static ItemListJob* make_job(std::shared_ptr const& item_impl, QString const& method, ReplyType const& reply, ValidateFunc const& validate, FetchFunc const& fetch_next); static ItemListJob* make_job(StorageError const& error); private: MultiItemListJobImpl() = default; MultiItemListJobImpl(std::shared_ptr const& item_impl, QString const& method, ReplyType const& reply, ValidateFunc const& validate, FetchFunc const& fetch_next); std::function process_reply_; std::function process_error_; std::shared_ptr item_impl_; FetchFunc fetch_next_; }; } // namespace internal } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/internal/RuntimeImpl.h000066400000000000000000000042251521521330000276770ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #pragma GCC diagnostic pop class RegistryInterface; namespace lomiri { namespace storage { namespace qt { class AccountsJob; class Runtime; namespace internal { class RuntimeImpl : public std::enable_shared_from_this { public: RuntimeImpl(); RuntimeImpl(QDBusConnection const& conn); RuntimeImpl(RuntimeImpl const&) = delete; RuntimeImpl(RuntimeImpl&&) = delete; ~RuntimeImpl(); RuntimeImpl& operator=(RuntimeImpl const&) = delete; RuntimeImpl& operator=(RuntimeImpl&&) = delete; bool isValid() const; StorageError error() const; QDBusConnection connection() const; AccountsJob* accounts() const; StorageError shutdown(); Account make_test_account(QString const& bus_name, QString const& object_path, quint32 id, QString const& service_id, QString const& display_name); private: bool is_valid_; StorageError error_; QDBusConnection conn_; std::unique_ptr registry_; friend class lomiri::storage::qt::Runtime; }; } // namespace internal } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/internal/StorageErrorImpl.h000066400000000000000000000056401521521330000306740ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include namespace lomiri { namespace storage { namespace qt { namespace internal { class StorageErrorImpl { public: StorageErrorImpl(); StorageErrorImpl(StorageError::Type type, QString const& msg); StorageErrorImpl(StorageError::Type type, QString const& msg, QString const& item_id); StorageErrorImpl(StorageError::Type type, QString const& msg, QString const& item_id, QString const& item_name); StorageErrorImpl(StorageError::Type type, QString const& msg, int error_code); StorageErrorImpl(StorageErrorImpl const&) = default; StorageErrorImpl(StorageErrorImpl&&) = default; ~StorageErrorImpl() = default; StorageErrorImpl& operator=(StorageErrorImpl const&) = default; StorageErrorImpl& operator=(StorageErrorImpl&&) = default; StorageError::Type type() const; QString name() const; QString message() const; QString errorString() const; QString itemId() const; QString itemName() const; int errorCode() const; // Generic factory for errors that don't require extra arguments. static StorageError make_error(StorageError::Type, QString const& msg); // Factories to make things more convenient and ensure consistency. // Note that we deliberately have no factories for errors that are // never created locally and can only come from the server. static StorageError local_comms_error(QString const& msg); static StorageError runtime_destroyed_error(QString const& msg); static StorageError not_exists_error(QString const& msg, QString const& key); static StorageError exists_error(QString const& msg, QString const& item_id, QString const& item_name); static StorageError cancelled_error(QString const& msg); static StorageError logic_error(QString const& msg); static StorageError invalid_argument_error(QString const& msg); static StorageError resource_error(QString const& msg, int error_code); private: StorageErrorImpl(StorageError::Type type); StorageError::Type type_; QString name_; QString message_; QString item_id_; QString item_name_; int error_code_; }; } // namespace internal } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/internal/UploaderImpl.h000066400000000000000000000063231521521330000300300ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include #include #include #include namespace lomiri { namespace storage { namespace internal { class ItemMetadata; } // namespace internal namespace qt { namespace internal { class UploaderImpl : public QObject { Q_OBJECT public: UploaderImpl(std::shared_ptr const& item_impl, QString const& method, QDBusPendingReply const& reply, std::function const& validate, Item::ConflictPolicy policy, qint64 size_in_bytes); UploaderImpl(StorageError const& e); virtual ~UploaderImpl(); bool isValid() const; Uploader::Status status() const; StorageError error() const; Item::ConflictPolicy policy() const; qint64 sizeInBytes() const; Item item() const; void cancel(); // From QLocalSocket interface. void close(); qint64 bytesAvailable() const; qint64 bytesToWrite() const; bool canReadLine() const; bool isSequential() const; bool waitForBytesWritten(int msecs); bool waitForReadyRead(int msecs); qint64 readData(char* data, qint64 c); qint64 writeData(char const* data, qint64 c); static Uploader* make_job(std::shared_ptr const& item_impl, QString const& method, QDBusPendingReply const& reply, std::function const& validate, Item::ConflictPolicy policy, qint64 size_in_bytes); static Uploader* make_job(StorageError const& e); qint64 flush_buffer(); private: Uploader* public_instance_; Uploader::Status status_; StorageError error_; QString method_; std::shared_ptr item_impl_; std::function validate_; Item::ConflictPolicy policy_ = Item::ConflictPolicy::IgnoreConflict; qint64 size_in_bytes_ = 0; QPointer>> handler_; QString upload_id_; QDBusUnixFileDescriptor fd_; QLocalSocket socket_; QByteArray buffer_; bool finalizing_ = false; }; } // namespace internal } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/internal/VoidJobImpl.h000066400000000000000000000036621521521330000276140ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #pragma GCC diagnostic pop namespace lomiri { namespace storage { namespace qt { namespace internal { class ItemImpl; class VoidJobImpl : public QObject { Q_OBJECT public: virtual ~VoidJobImpl() = default; bool isValid() const; VoidJob::Status status() const; StorageError error() const; static VoidJob* make_job(std::shared_ptr const& item_impl, QString const& method, QDBusPendingReply const& reply); static VoidJob* make_job(StorageError const& e); private: VoidJobImpl(std::shared_ptr const& item_impl, QString const& method, QDBusPendingReply const& reply); VoidJobImpl(StorageError const& e); VoidJob* public_instance_; VoidJob::Status status_; StorageError error_; QString method_; std::shared_ptr item_impl_; }; } // namespace internal } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/internal/unmarshal_error.h000066400000000000000000000017661521521330000306440ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include class QDBusPendingCallWatcher; namespace lomiri { namespace storage { namespace qt { namespace internal { StorageError unmarshal_error(QDBusPendingCallWatcher const& call); } // namespace internal } // namespace qt } // storage } // lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/qt/internal/validate.h000066400000000000000000000020541521521330000272210ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include namespace lomiri { namespace storage { namespace internal { class ItemMetadata; } // namespace internal namespace qt { namespace internal { void validate(QString const& method, lomiri::storage::internal::ItemMetadata const& md); } // namespace internal } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/registry/000077500000000000000000000000001521521330000246665ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/include/lomiri/storage/registry/Registry.h000066400000000000000000000021711521521330000266500ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include namespace lomiri { namespace storage { namespace registry { static QString const BUS_NAME(QStringLiteral("com.lomiri.StorageFramework.Registry")); static QString const OBJECT_PATH(QStringLiteral("/com/canonical/StorageFramework/Registry")); static QString const INTERFACE(QStringLiteral("com.lomiri.StorageFramework.Registry")); } // namespace registry } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/registry/internal/000077500000000000000000000000001521521330000265025ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/include/lomiri/storage/registry/internal/ListAccountsHandler.h000066400000000000000000000035101521521330000325630ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #include #include #include #pragma GCC diagnostic pop namespace lomiri { namespace storage { namespace registry { namespace internal { class ListAccountsHandler : public QObject { Q_OBJECT public: ListAccountsHandler(QDBusConnection const& conn, QDBusMessage const& msg, std::shared_ptr const& timer); ~ListAccountsHandler(); private Q_SLOTS: void manager_ready(); void timeout(); private: void initialize_manager(); QDBusConnection const conn_; QDBusMessage const msg_; OnlineAccounts::Manager manager_; QTimer timer_; storage::internal::ActivityNotifier activity_notifier_; // RAII guard variable Q_DISABLE_COPY(ListAccountsHandler) }; } // namespace internal } // namespace registry } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/registry/internal/RegistryAdaptor.h000066400000000000000000000033551521521330000320040ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #include #pragma GCC diagnostic pop #include namespace lomiri { namespace storage { namespace internal { class InactivityTimer; } // namespace internal namespace registry { namespace internal { class RegistryAdaptor : public QObject, protected QDBusContext { Q_OBJECT public: RegistryAdaptor(QDBusConnection const& conn, std::shared_ptr const& timer, QObject* parent = nullptr); ~RegistryAdaptor(); public Q_SLOTS: QList ListAccounts(); private: QDBusConnection conn_; std::shared_ptr timer_; Q_DISABLE_COPY(RegistryAdaptor) }; } // namespace internal } // namespace registry } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/registry/internal/qdbus-last-error-msg.h000066400000000000000000000021611521521330000326450ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #pragma GCC diagnostic pop namespace lomiri { namespace storage { namespace registry { namespace internal { QString last_error_msg(QDBusConnection const& conn); } // namespace internal } // namespace registry } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/include/lomiri/storage/visibility.h000066400000000000000000000015411521521330000253570ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #pragma once #define LOMIRI_STORAGE_EXPORT __attribute__((visibility("default"))) #define LOMIRI_STORAGE_HIDDEN __attribute__((visibility("hidden"))) lomiri-storage-framework-0.5.0/plugins/000077500000000000000000000000001521521330000201155ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/plugins/Lomiri/000077500000000000000000000000001521521330000213505ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/plugins/Lomiri/StorageFramework/000077500000000000000000000000001521521330000246325ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/plugins/Lomiri/StorageFramework/CMakeLists.txt000066400000000000000000000005341521521330000273740ustar00rootroot00000000000000add_library(lomiri-storage-framework-qml MODULE plugin.cpp ) set_target_properties(lomiri-storage-framework-qml PROPERTIES AUTOMOC TRUE NO_SONAME TRUE LINK_FLAGS "-Wl,--no-undefined" ) target_link_libraries(lomiri-storage-framework-qml lomiri-storage-framework-qt-client-v2 Qt${QT_VERSION_MAJOR}::Qml ) configure_file(qmldir qmldir) lomiri-storage-framework-0.5.0/plugins/Lomiri/StorageFramework/plugin.cpp000066400000000000000000000031631521521330000266370ustar00rootroot00000000000000/* * Copyright 2016 Canonical Ltd. * * This program 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; version 3. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #include "plugin.h" #include #include #include #include #include #include using namespace lomiri::storage::qt; namespace lomiri { namespace storage { namespace qml { void StorageFrameworkPlugin::registerTypes(const char* uri) { qmlRegisterType(uri, 0, 1, "Runtime"); qmlRegisterUncreatableType(uri, 0, 1, "Account", ""); qmlRegisterUncreatableType(uri, 0, 1, "AccountsJob", "Use Runtime to create AccountsJob"); qmlRegisterUncreatableType(uri, 0, 1, "Item", ""); qmlRegisterUncreatableType(uri, 0, 1, "ItemJob", "Use Account or another item to access items"); qmlRegisterUncreatableType(uri, 0, 1, "ItemListJob", "Use Account or another item to access items"); } } } } lomiri-storage-framework-0.5.0/plugins/Lomiri/StorageFramework/plugin.h000066400000000000000000000017621521521330000263070ustar00rootroot00000000000000/* * Copyright 2016 Canonical Ltd. * * This program 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; version 3. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #pragma once #include namespace lomiri { namespace storage { namespace qml { class StorageFrameworkPlugin : public QQmlExtensionPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") public: void registerTypes(const char* uri) override; }; } } } lomiri-storage-framework-0.5.0/plugins/Lomiri/StorageFramework/qmldir000066400000000000000000000000741521521330000260460ustar00rootroot00000000000000module Lomiri.StorageFramework plugin storage-framework-qml lomiri-storage-framework-0.5.0/snap/000077500000000000000000000000001521521330000173755ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/snap/snapcraft.yaml000066400000000000000000000100331521521330000222370ustar00rootroot00000000000000name: storage-framework-service version: '0.2' summary: Service for accessing cloud-based storage providers. description: > This snap provides a service to access cloud-based storage providers, such as OneDrive, Owncloud, Google Drive, or mCloud, plus a client-side API for applications. See lp:storage-framework for details on the framework, as well as lp:storage-provider-onedrive, lp:storage-provider-webdav, lp:storage-provider-gdrive, and lp:mcloud. grade: devel confinement: strict slots: storage-framework-service: interface: storage-framework-service # Allow clients access to the client library client-libs: interface: content content: client-libs read: - $SNAP/client # Allow providers access to the provider library provider-libs: interface: content content: provider-libs read: - $SNAP/provider plugs: platform: interface: content content: ubuntu-app-platform1 target: ubuntu-app-platform default-provider: ubuntu-app-platform apps: storage-framework-registry: command: desktop-launch $SNAP/lib/storage-framework/storage-framework-registry plugs: - platform slots: - storage-framework-service storage-provider-owncloud: # snap-launch sets LD_LIBRARY_PATH to include the provider directory (exposed via content interface). command: snap-launch $SNAP/provider/lib $SNAP/lib/storage-provider-webdav/storage-provider-owncloud plugs: - platform - network slots: - storage-framework-service parts: storage-framework: plugin: cmake configflags: - -DSNAP_BUILD=1 source: . after: - desktop-ubuntu-app-platform organize: lib/libstorage-framework-qt*: client/lib/ lib/libstorage-framework-provider*: provider/lib/ filesets: binaries: - bin/snap-launch - lib/storage-framework/* - client/* - provider/* - -lib/pkgconfig dbus: - share/dbus-1/services/com.lomiri.StorageFramework.* install: | # Make sure we have a mount point for ubuntu-app-platform mkdir -p $SNAPCRAFT_PART_INSTALL/ubuntu-app-platform # Fix pkgconfig files to point at the parts subtree so # the providers will build correctly. sed -e "s@-I/include@-I${SNAPCRAFT_PART_INSTALL}/include@" \ -e "s@-L/lib@-L${SNAPCRAFT_PART_INSTALL}/provider/lib@" \ -i $SNAPCRAFT_PART_INSTALL/lib/pkgconfig/storage-framework-provider-1.pc sed -e "s@-I/include@-I${SNAPCRAFT_PART_INSTALL}/include@" \ -e "s@-L/lib@-L${SNAPCRAFT_PART_INSTALL}/client/lib@" \ -i $SNAPCRAFT_PART_INSTALL/lib/pkgconfig/storage-framework-qt-local-client-1.pc sed -e "s@-I/include@-I${SNAPCRAFT_PART_INSTALL}/include@" \ -e "s@-L/lib@-L${SNAPCRAFT_PART_INSTALL}/client/lib@" \ -i $SNAPCRAFT_PART_INSTALL/lib/pkgconfig/storage-framework-qt-client-1.pc sed -e "s@-I/include@-I${SNAPCRAFT_PART_INSTALL}/include@" \ -e "s@-L/lib@-L${SNAPCRAFT_PART_INSTALL}/client/lib@" \ -i $SNAPCRAFT_PART_INSTALL/lib/pkgconfig/storage-framework-qt-client-2.pc prime: - $binaries - ubuntu-app-platform build-packages: - cmake-extras - doxygen - google-mock - libapparmor-dev - libboost-filesystem-dev - libboost-system-dev - libboost-thread-dev - libglib2.0-dev - libgtest-dev - libonline-accounts-qt-dev - libqtdbustest1-dev - liblomiri-api-dev - lsb-release - python3-dbus - python3-gi - qtbase5-dev - qtbase5-dev-tools - qtdeclarative5-dev # For now, the providers are part of the storage-framework snap. Eventually, they will have to each # live inside their own snap. storage-provider-owncloud: plugin: cmake configflags: - -DSNAP_BUILD=1 source: lp:storage-provider-webdav after: - storage-framework filesets: binaries: - lib/storage-provider-webdav/* prime: - $binaries # TODO: Add other providers (OneDrive, Google Drive, mCloud) lomiri-storage-framework-0.5.0/src/000077500000000000000000000000001521521330000172235ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/src/CMakeLists.txt000066400000000000000000000002071521521330000217620ustar00rootroot00000000000000add_subdirectory(internal) add_subdirectory(provider) add_subdirectory(qt) add_subdirectory(registry) add_subdirectory(local-provider) lomiri-storage-framework-0.5.0/src/internal/000077500000000000000000000000001521521330000210375ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/src/internal/AccountDetails.cpp000066400000000000000000000053501521521330000244500ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include using namespace lomiri::storage::internal; namespace lomiri { namespace storage { namespace internal { bool operator==(AccountDetails const& lhs, AccountDetails const& rhs) { return lhs.id == rhs.id && lhs.serviceId == rhs.serviceId && lhs.displayName == rhs.displayName; } bool operator!=(AccountDetails const& lhs, AccountDetails const& rhs) { return !(lhs == rhs); } bool operator<(AccountDetails const& lhs, AccountDetails const& rhs) { if (lhs.id < rhs.id) { return true; } if (lhs.id > rhs.id) { return false; } if (lhs.serviceId < rhs.serviceId) { return true; } if (lhs.serviceId > rhs.serviceId) { return false; } return lhs.displayName < rhs.displayName; } bool operator<=(AccountDetails const& lhs, AccountDetails const& rhs) { return lhs < rhs || lhs == rhs; } bool operator>(AccountDetails const& lhs, AccountDetails const& rhs) { return !(lhs <= rhs); } bool operator>=(AccountDetails const& lhs, AccountDetails const& rhs) { return !(lhs < rhs); } QDBusArgument& operator<<(QDBusArgument& argument, storage::internal::AccountDetails const& account) { argument.beginStructure(); argument << account.busName; argument << account.objectPath; argument << account.id; argument << account.serviceId; argument << account.displayName; argument << account.providerName; argument << account.iconName; argument.endStructure(); return argument; } QDBusArgument const& operator>>(QDBusArgument const& argument, storage::internal::AccountDetails& account) { argument.beginStructure(); argument >> account.busName; argument >> account.objectPath; argument >> account.id; argument >> account.serviceId; argument >> account.displayName; argument >> account.providerName; argument >> account.iconName; argument.endStructure(); return argument; } } // namespace internal } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/internal/CMakeLists.txt000066400000000000000000000007321521521330000236010ustar00rootroot00000000000000set(src AccountDetails.cpp dbusmarshal.cpp EnvVars.cpp InactivityTimer.cpp safe_strerror.cpp TraceMessageHandler.cpp ${CMAKE_SOURCE_DIR}/include/lomiri/storage/internal/InactivityTimer.h ) add_library(lomiri-storage-framework-common-internal STATIC ${src}) set_target_properties(lomiri-storage-framework-common-internal PROPERTIES AUTOMOC TRUE) target_link_libraries(lomiri-storage-framework-common-internal Qt${QT_VERSION_MAJOR}::DBus ) lomiri-storage-framework-0.5.0/src/internal/EnvVars.cpp000066400000000000000000000043471521521330000231370ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include #include using namespace std; namespace lomiri { namespace storage { namespace internal { int EnvVars::registry_timeout_ms() { return get_timeout_ms(REGISTRY_IDLE_TIMEOUT, REGISTRY_IDLE_TIMEOUT_DFLT); } int EnvVars::provider_timeout_ms() { return get_timeout_ms(PROVIDER_IDLE_TIMEOUT, PROVIDER_IDLE_TIMEOUT_DFLT); } int EnvVars::get_timeout_ms(char const* var_name, int dflt) { int timeout = dflt; auto const val = get(var_name); if (!val.empty()) { try { size_t pos; auto int_val = stoi(val, &pos); if (pos != val.size()) { throw invalid_argument("unexpected trailing character(s)"); } if (int_val < 0) { throw invalid_argument("value must be >= 0"); } timeout = int_val; } catch (std::exception const& e) { qWarning().noquote().nospace() << "Invalid setting of env var " << var_name << " (\"" << QString::fromStdString(val) << "\"): " << e.what(); qWarning().nospace() << "Using default value of " << dflt; } } return timeout * 1000; } string EnvVars::get(char const* var_name) { assert(var_name != nullptr); auto p = getenv(var_name); if (!p) { return string(); } return string(p); } } // namespace internal } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/internal/InactivityTimer.cpp000066400000000000000000000027001521521330000246660ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include namespace lomiri { namespace storage { namespace internal { InactivityTimer::InactivityTimer(int timeout_ms) { assert(timeout_ms >= 0); timer_.setInterval(timeout_ms); timer_.setSingleShot(true); connect(&timer_, &QTimer::timeout, this, &InactivityTimer::timeout); } InactivityTimer::~InactivityTimer() = default; void InactivityTimer::request_started() { assert(num_requests_ >= 0); if (num_requests_++ == 0) { timer_.stop(); } } void InactivityTimer::request_finished() { assert(num_requests_ > 0); if (--num_requests_ == 0) { timer_.start(); } } } // namespace internal } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/internal/TraceMessageHandler.cpp000066400000000000000000000054241521521330000254110ustar00rootroot00000000000000/* * Copyright (C) 2015 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include using namespace std; namespace lomiri { namespace storage { namespace internal { namespace { string prefix; void trace_message_handler(QtMsgType type, const QMessageLogContext& /*context*/, const QString& msg) { using namespace std; using namespace std::chrono; static recursive_mutex mutex; lock_guard lock(mutex); auto now = system_clock::now(); auto sys_time = system_clock::to_time_t(now); struct tm local_time; localtime_r(&sys_time, &local_time); int msecs = duration_cast(now.time_since_epoch()).count() % 1000; if (!prefix.empty()) { fprintf(stderr, "%s: ", prefix.c_str()); } char buf[100]; strftime(buf, sizeof(buf), "%T", &local_time); fprintf(stderr, "[%s.%03d]", buf, msecs); switch (type) { case QtWarningMsg: fputs(" Warning:", stderr); break; case QtCriticalMsg: fputs(" Critical:", stderr); break; // LCOV_EXCL_START case QtFatalMsg: fputs(" Fatal:", stderr); break; // LCOV_EXCL_STOP default: break; // No label for debug messages. } fprintf(stderr, " %s\n", msg.toLocal8Bit().constData()); if (type == QtFatalMsg) { abort(); // LCOV_EXCL_LINE } } } // namespace TraceMessageHandler::TraceMessageHandler() : old_message_handler_(qInstallMessageHandler(trace_message_handler)) { } TraceMessageHandler::TraceMessageHandler(string const& prog_name) : TraceMessageHandler() { prefix = prog_name; } TraceMessageHandler::TraceMessageHandler(QString const& prog_name) : TraceMessageHandler() { prefix = prog_name.toStdString(); } TraceMessageHandler::TraceMessageHandler(char const* prog_name) : TraceMessageHandler() { prefix = prog_name; } TraceMessageHandler::~TraceMessageHandler() { qInstallMessageHandler(old_message_handler_); } } // namespace internal } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/internal/dbusmarshal.cpp000066400000000000000000000063631521521330000240600ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include using namespace lomiri::storage::internal; using namespace std; namespace lomiri { namespace storage { namespace internal { QDBusArgument& operator<<(QDBusArgument& argument, storage::internal::ItemMetadata const& metadata) { argument.beginStructure(); argument << metadata.item_id; argument << metadata.parent_ids; argument << metadata.name; argument << metadata.etag; argument << static_cast(metadata.type); argument.beginMap(QVariant::String, qMetaTypeId()); decltype(ItemMetadata::metadata)::const_iterator i = metadata.metadata.constBegin(); while (i != metadata.metadata.constEnd()) { argument.beginMapEntry(); argument << i.key() << QDBusVariant(i.value()); argument.endMapEntry(); ++i; } argument.endMap(); argument.endStructure(); return argument; } QDBusArgument const& operator>>(QDBusArgument const& argument, storage::internal::ItemMetadata& metadata) { argument.beginStructure(); argument >> metadata.item_id; argument >> metadata.parent_ids; argument >> metadata.name; argument >> metadata.etag; int32_t enum_val; argument >> enum_val; if (enum_val < 0 || enum_val >= int(ItemType::LAST_ENTRY__)) { qCritical() << "unmarshaling error: impossible ItemType value: " + QString::number(enum_val); return argument; // Forces error } metadata.type = static_cast(enum_val); metadata.metadata.clear(); argument.beginMap(); while (!argument.atEnd()) { QString key; QVariant value; argument.beginMapEntry(); argument >> key >> value; argument.endMapEntry(); metadata.metadata.insert(key, value); } argument.endMap(); argument.endStructure(); return argument; } QDBusArgument& operator<<(QDBusArgument& argument, QList const& md_list) { argument.beginArray(qMetaTypeId()); for (auto const& md : md_list) { argument << md; } argument.endArray(); return argument; } QDBusArgument const& operator>>(QDBusArgument const& argument, QList& md_list) { md_list.clear(); argument.beginArray(); while (!argument.atEnd()) { ItemMetadata imd; argument >> imd; md_list.append(imd); } argument.endArray(); return argument; } } // namespace internal } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/internal/safe_strerror.cpp000066400000000000000000000035521521521330000244300ustar00rootroot00000000000000/* * Copyright (C) 2014 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authored by: Michi Henning */ // Get XSI-compliant strerror_r() #undef _GNU_SOURCE #define _XOPEN_SOURCE 700 #include #include namespace lomiri { namespace storage { namespace internal { // We place this function into a source file by itself so the macro definitions above // cannot interfere with anything else. std::string safe_strerror(int errnum) { char buf[512]; int rc = strerror_r(errnum, buf, sizeof(buf)); switch (rc) { case 0: { return buf; } case EINVAL: { return "invalid error number " + std::to_string(errnum) + " for strerror_r()"; } // LCOV_EXCL_START case ERANGE: { return "buffer size of " + std::to_string(sizeof(buf)) + " is too small for strerror_r(), errnum = " + std::to_string(errnum); } default: { return "impossible return value " + std::to_string(rc) + " from strerror_r(), errnum = " + std::to_string(errnum); } // LCOV_EXCL_STOP } } } // namespace internal } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/local-provider/000077500000000000000000000000001521521330000221455ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/src/local-provider/CMakeLists.txt000066400000000000000000000021041521521330000247020ustar00rootroot00000000000000add_definitions(-DBOOST_THREAD_VERSION=4) add_library(local-provider-lib STATIC LocalDownloadJob.cpp LocalProvider.cpp LocalUploadJob.cpp utils.cpp ) target_link_libraries(local-provider-lib PUBLIC lomiri-storage-framework-provider Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Network PkgConfig::GLIB_DEPS PkgConfig::GIO_DEPS PkgConfig::LIBLOMIRI_API_DEPS ) set_target_properties(local-provider-lib PROPERTIES AUTOMOC TRUE POSITION_INDEPENDENT_CODE TRUE ) add_executable(lomiri-storage-provider-local main.cpp ) target_link_libraries(lomiri-storage-provider-local local-provider-lib lomiri-storage-framework-provider ) install( TARGETS lomiri-storage-provider-local RUNTIME DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/${PROJECT_NAME} ) configure_file(com.lomiri.StorageFramework.Provider.Local.service.in com.lomiri.StorageFramework.Provider.Local.service) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/com.lomiri.StorageFramework.Provider.Local.service DESTINATION ${CMAKE_INSTALL_DATADIR}/dbus-1/services ) lomiri-storage-framework-0.5.0/src/local-provider/LocalDownloadJob.cpp000066400000000000000000000132261521521330000260320ustar00rootroot00000000000000/* * Copyright (C) 2017 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include "LocalDownloadJob.h" #include "LocalProvider.h" #include "utils.h" #include #include using namespace lomiri::storage::provider; using namespace std; static int next_download_id = 0; string const method = "download()"; LocalDownloadJob::LocalDownloadJob(shared_ptr const& provider, string const& item_id, string const& match_etag) : DownloadJob(to_string(++next_download_id)) , provider_(provider) , item_id_(item_id) { using namespace boost::filesystem; // Sanitize parameters. provider_->throw_if_not_valid(method, item_id_); try { auto st = status(item_id_); if (!is_regular_file(st)) { throw InvalidArgumentException(method + ": \"" + item_id_ + "\" is not a file"); } } // LCOV_EXCL_START // Too small a window to hit with a test. catch (filesystem_error const& e) { throw_storage_exception(method, e); } // LCOV_EXCL_STOP if (!match_etag.empty()) { int64_t mtime = get_mtime_nsecs(method, item_id_); if (to_string(mtime) != match_etag) { throw ConflictException(method + ": etag mismatch"); } } // Make input file ready. QString filename = QString::fromStdString(item_id); file_.reset(new QFile(filename)); if (!file_->open(QIODevice::ReadOnly)) { throw_storage_exception(method, ": cannot open \"" + item_id + "\": " + file_->errorString().toStdString(), file_->error()); } bytes_to_write_ = file_->size(); // Make write socket ready. int dup_fd = dup(write_socket()); if (dup_fd == -1) { // LCOV_EXCL_START string msg = "LocalDownloadJob(): dup() failed: " + lomiri::storage::internal::safe_strerror(errno); throw ResourceException(msg, errno); // LCOV_EXCL_STOP } write_socket_.setSocketDescriptor(dup_fd, QLocalSocket::ConnectedState, QIODevice::WriteOnly); connect(&write_socket_, &QIODevice::bytesWritten, this, &LocalDownloadJob::on_bytes_written); // Kick off the read-write cycle. QMetaObject::invokeMethod(this, "read_and_write_chunk", Qt::QueuedConnection); } LocalDownloadJob::~LocalDownloadJob() = default; boost::future LocalDownloadJob::cancel() { disconnect(&write_socket_, nullptr, this, nullptr); write_socket_.abort(); file_->close(); return boost::make_ready_future(); } boost::future LocalDownloadJob::finish() { if (bytes_to_write_ > 0) { auto file_size = file_->size(); auto written = file_size - bytes_to_write_; string msg = "finish() method called too early, file \"" + item_id_ + "\" has size " + to_string(file_size) + " but only " + to_string(written) + " bytes were consumed"; cancel(); return boost::make_exceptional_future(LogicException(msg)); } // LCOV_EXCL_START // Not reachable because we call report_complete() in read_and_write_chunk(). return boost::make_ready_future(); // LCOV_EXCL_STOP } void LocalDownloadJob::on_bytes_written(qint64 bytes) { bytes_to_write_ -= bytes; assert(bytes_to_write_ >= 0); read_and_write_chunk(); } void LocalDownloadJob::read_and_write_chunk() { static qint64 constexpr READ_SIZE = 64 * 1024; if (bytes_to_write_ == 0) { file_->close(); write_socket_.close(); report_complete(); return; } QByteArray buf; buf.resize(READ_SIZE); auto bytes_read = file_->read(buf.data(), buf.size()); try { if (bytes_read == -1) { // LCOV_EXCL_START string msg = string("\"") + item_id_ + "\": read error: " + file_->errorString().toStdString(); throw_storage_exception(method, msg, file_->error()); // LCOV_EXCL_STOP } buf.resize(bytes_read); auto bytes_written = write_socket_.write(buf); if (bytes_written == -1) { // LCOV_EXCL_START string msg = string("\"") + item_id_ + "\": socket error: " + write_socket_.errorString().toStdString(); throw_storage_exception(method, msg, write_socket_.error()); // LCOV_EXCL_STOP } else if (bytes_written != bytes_read) { // LCOV_EXCL_START string msg = string("\"") + item_id_ + "\": socket write error, requested " + to_string(bytes_read) + " B, but wrote only " + to_string(bytes_written) + " B."; throw_storage_exception(method, msg, QLocalSocket::UnknownSocketError); // LCOV_EXCL_STOP } } // LCOV_EXCL_START catch (std::exception const&) { write_socket_.abort(); file_->close(); report_error(current_exception()); } // LCOV_EXCL_STOP } lomiri-storage-framework-0.5.0/src/local-provider/LocalDownloadJob.h000066400000000000000000000032371521521330000255000ustar00rootroot00000000000000/* * Copyright (C) 2017 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #pragma GCC diagnostic ignored "-Wswitch-default" #include #include #pragma GCC diagnostic pop class LocalProvider; class LocalDownloadJob : public QObject, public lomiri::storage::provider::DownloadJob { Q_OBJECT public: LocalDownloadJob(std::shared_ptr const& provider, std::string const& item_id, std::string const& match_etag); virtual ~LocalDownloadJob(); virtual boost::future cancel() override; virtual boost::future finish() override; private Q_SLOTS: void on_bytes_written(qint64 bytes); void read_and_write_chunk(); private: std::shared_ptr const provider_; std::string const item_id_; std::unique_ptr file_; QLocalSocket write_socket_; int64_t bytes_to_write_; }; lomiri-storage-framework-0.5.0/src/local-provider/LocalProvider.cpp000066400000000000000000000502241521521330000254210ustar00rootroot00000000000000/* * Copyright (C) 2017 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include "LocalProvider.h" #include "LocalDownloadJob.h" #include "LocalUploadJob.h" #include "utils.h" #include #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wold-style-cast" #include #include #pragma GCC diagnostic pop using namespace lomiri::storage::provider; using namespace std; namespace { // Return the root directory where we store files. // If SF_LOCAL_PROVIDER_ROOT is set (used for testing), any files are created // directly under the root. E.g., if we do root.createFile("foo.txt", ...), the file // will be created as ${SF_LOCAL_PROVIDER_ROOT/foo.txt. SF_LOCAL_PROVIDER_ROOT must // be a pre-existing directory. // // Otherwise, the root is determined by SNAP_USER_COMMON or, if that is not set, // by XDG_DATA_HOME. Either way, files are created in a storage-framework/local // subdirectory. E.g., if SNAP_USER_COMMON or XDG_DATA_HOME is set to "/tmp" and // we do root.createFile("foo.txt", ...), the file will be created as // /tmp/storage-framework/local/foo.txt. If /tmp/storage-framework/local does not exist, // the directory will be created. string get_root_dir(string const& method) { using namespace boost::filesystem; char const* dir = getenv("SF_LOCAL_PROVIDER_ROOT"); if (dir && *dir != '\0') { boost::system::error_code ec; if (!exists(dir, ec) || !is_directory(dir, ec)) { string msg = method + ": Environment variable SF_LOCAL_PROVIDER_ROOT must denote an existing directory"; throw InvalidArgumentException(msg); } return dir; } string data_dir; dir = getenv("SNAP_USER_COMMON"); if (dir && *dir != '\0') { data_dir = dir; } else { data_dir = g_get_user_data_dir(); // Never fails. } data_dir += "/storage-framework/local"; try { create_directories(data_dir); } catch (filesystem_error const& e) { throw_storage_exception(method, e); } return data_dir; } // Copy a file or directory (recursively). Ignore anything that has the temp file prefix // or is not a file or directory. void copy_recursively(boost::filesystem::path const& source, boost::filesystem::path const& target) { using namespace boost::filesystem; if (is_reserved_path(source)) { return; // Don't copy temporary directories. } auto s = status(source); if (is_regular_file(s)) { copy_file(source, target); } else if (is_directory(s)) { copy_directory(source, target); // Poorly named in boost; this creates the target dir without recursion for (directory_iterator it(source); it != directory_iterator(); ++it) { path source_entry = it->path(); path target_entry = target; target_entry /= source_entry.filename(); copy_recursively(source_entry, target_entry); } } else { // Ignore everything that's not a directory or file. } } // Convert nanoseconds since the epoch into ISO 8601 date-time. string make_iso_date(int64_t nsecs_since_epoch) { static char const* const FMT = "%Y-%m-%dT%TZ"; // ISO 8601, no fractional seconds. struct tm time; time_t secs_since_epoch = nsecs_since_epoch / 1000000000; gmtime_r(&secs_since_epoch, &time); char buf[128]; strftime(buf, sizeof(buf), FMT, &time); return buf; } string get_content_type(string const& filename) { using namespace lomiri::storage::internal; static string const unknown_content_type = "application/octet-stream"; gobj_ptr file(g_file_new_for_path(filename.c_str())); assert(file); // Cannot fail according to doc. GError* err = nullptr; gobj_ptr full_info(g_file_query_info(file.get(), G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE, G_FILE_QUERY_INFO_NONE, /* cancellable */ NULL, &err)); if (!full_info) { return unknown_content_type; // LCOV_EXCL_LINE } string content_type = g_file_info_get_attribute_string(full_info.get(), G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE); if (content_type.empty()) { return unknown_content_type; // LCOV_EXCL_LINE } return content_type; } // Simple wrapper template that deals with exception handling so we don't // have to repeat ourselves endlessly in the various lambdas below. // The auto deduction of the return type requires C++ 14. template auto invoke_async(string const& method, F& functor) { auto lambda = [method, functor] { try { return functor(); } catch (StorageException const&) { throw; } catch (boost::filesystem::filesystem_error const& e) { throw_storage_exception(method, e); } // LCOV_EXCL_START catch (std::exception const& e) { throw boost::enable_current_exception(UnknownException(e.what())); } // LCOV_EXCL_STOP }; // TODO: boost::async is potentially expensive for some operations. Consider boost::asio thread pool? return boost::async(boost::launch::async, lambda); } } // namespace LocalProvider::LocalProvider() : root_(boost::filesystem::canonical(get_root_dir("LocalProvider()"))) { } LocalProvider::~LocalProvider() = default; boost::future LocalProvider::roots(vector const& /* keys */, Context const& /* context */) { vector roots{ make_item("roots()", root_, status(root_)) }; return boost::make_ready_future(roots); } boost::future> LocalProvider::list(string const& item_id, string const& page_token, vector const& /* keys */, Context const& /* context */) { string const method = "list()"; auto This = dynamic_pointer_cast(shared_from_this()); auto do_list = [This, method, item_id, page_token] { using namespace boost::filesystem; This->throw_if_not_valid(method, item_id); vector items; for (directory_iterator it(item_id); it != directory_iterator(); ++it) { auto dirent = *it; auto path = dirent.path(); if (is_reserved_path(path)) { continue; // Hide temp files that we create during copy() and move(). } Item i; try { auto st = dirent.status(); i = This->make_item(method, path, st); items.push_back(i); } catch (std::exception const&) { // We ignore weird errors (such as entries that are not files or folders). } } return tuple(items, ""); }; return invoke_async(method, do_list); } boost::future LocalProvider::lookup(string const& parent_id, string const& name, vector const& /* keys */, Context const& /* context */) { string const method = "lookup()"; auto This = dynamic_pointer_cast(shared_from_this()); auto do_lookup = [This, method, parent_id, name] { using namespace boost::filesystem; This->throw_if_not_valid(method, parent_id); auto sanitized_name = sanitize(method, name); path p = parent_id; p /= sanitized_name; This->throw_if_not_valid(method, p.native()); auto st = status(p); return vector{ This->make_item(method, p, st) }; }; return invoke_async(method, do_lookup); } boost::future LocalProvider::metadata(string const& item_id, vector const& /* keys */, Context const& /* context */) { string const method = "metadata()"; auto This = dynamic_pointer_cast(shared_from_this()); auto do_metadata = [This, method, item_id] { using namespace boost::filesystem; This->throw_if_not_valid(method, item_id); path p = item_id; auto st = status(p); return This->make_item(method, p, st); }; return invoke_async(method, do_metadata); } boost::future LocalProvider::create_folder(string const& parent_id, string const& name, vector const& /* keys */, Context const& /* context */) { string const method = "create_folder()"; auto This = dynamic_pointer_cast(shared_from_this()); auto do_create = [This, method, parent_id, name] { using namespace boost::filesystem; This->throw_if_not_valid(method, parent_id); auto sanitized_name = sanitize(method, name); path p = parent_id; p /= sanitized_name; // create_directory() succeeds if the directory exists already, so we need to check explicitly. if (exists(p)) { string msg = method + ": \"" + p.native() + "\" exists already"; throw boost::enable_current_exception(ExistsException(msg, p.native(), name)); } create_directory(p); auto st = status(p); return This->make_item(method, p, st); }; return invoke_async(method, do_create); } boost::future> LocalProvider::create_file(string const& parent_id, string const& name, int64_t size, string const& /* content_type */, bool allow_overwrite, vector const& /* keys */, Context const& /* context */) { auto This = dynamic_pointer_cast(shared_from_this()); boost::promise> p; p.set_value(make_unique(This, parent_id, name, size, allow_overwrite)); return p.get_future(); } boost::future> LocalProvider::update(string const& item_id, int64_t size, string const& old_etag, vector const& /* keys */, Context const& /* context */) { auto This = dynamic_pointer_cast(shared_from_this()); boost::promise> p; p.set_value(make_unique(This, item_id, size, old_etag)); return p.get_future(); } boost::future> LocalProvider::download(string const& item_id, string const& match_etag, Context const& /* context */) { auto This = dynamic_pointer_cast(shared_from_this()); boost::promise> p; p.set_value(make_unique(This, item_id, match_etag)); return p.get_future(); } boost::future LocalProvider::delete_item(string const& item_id, Context const& /* context */) { string const method = "delete_item()"; auto This = dynamic_pointer_cast(shared_from_this()); auto do_delete = [This, method, item_id] { using namespace boost::filesystem; This->throw_if_not_valid(method, item_id); if (canonical(item_id).native() == This->root_) { string msg = method + ": cannot delete root"; throw boost::enable_current_exception(LogicException(msg)); } remove_all(item_id); }; return invoke_async(method, do_delete); } boost::future LocalProvider::move(string const& item_id, string const& new_parent_id, string const& new_name, vector const& /* keys */, Context const& /* context */) { string const method = "move()"; auto This = dynamic_pointer_cast(shared_from_this()); auto do_move = [This, method, item_id, new_parent_id, new_name] { using namespace boost::filesystem; This->throw_if_not_valid(method, item_id); This->throw_if_not_valid(method, new_parent_id); auto sanitized_name = sanitize(method, new_name); path parent_path = new_parent_id; path target_path = parent_path / sanitized_name; if (exists(target_path)) { string msg = method + ": \"" + target_path.native() + "\" exists already"; throw boost::enable_current_exception(ExistsException(msg, target_path.native(), new_name)); } // Small race condition here: if exists() just said that the target does not exist, it is // possible for it to have been created since. If so, if the target is a file or an empty // directory, it will be removed. In practice, this is unlikely to happen and, if it does, // it is not the end of the world. // TODO: deal with EXDEV rename(item_id, target_path); auto st = status(target_path); return This->make_item(method, target_path, st); }; return invoke_async(method, do_move); } boost::future LocalProvider::copy(string const& item_id, string const& new_parent_id, string const& new_name, vector const& /* keys */, Context const& /* context */) { string const method = "copy()"; auto This = dynamic_pointer_cast(shared_from_this()); auto do_copy = [This, method, item_id, new_parent_id, new_name] { using namespace boost::filesystem; This->throw_if_not_valid(method, item_id); This->throw_if_not_valid(method, new_parent_id); auto sanitized_name = sanitize(method, new_name); path parent_path = new_parent_id; path target_path = parent_path / sanitized_name; if (is_directory(item_id)) { if (exists(target_path)) { string msg = method + ": \"" + target_path.native() + "\" exists already"; throw boost::enable_current_exception(ExistsException(msg, target_path.native(), new_name)); } // For recursive copy, we create a temporary directory in lieu of target_path and recursively copy // everything into the temporary directory. This ensures that we don't invalidate directory iterators // by creating things while we are iterating, potentially getting trapped in an infinite loop. path tmp_path = canonical(parent_path); tmp_path /= unique_path(string(TMPFILE_PREFIX) + "-%%%%-%%%%-%%%%-%%%%"); create_directories(tmp_path); for (directory_iterator it(item_id); it != directory_iterator(); ++it) { if (is_reserved_path(it->path())) { continue; // Don't recurse into the temporary directory } file_status s = it->status(); if (is_directory(s) || is_regular_file(s)) { path source_entry = it->path(); path target_entry = tmp_path; target_entry /= source_entry.filename(); copy_recursively(source_entry, target_entry); } } rename(tmp_path, target_path); } else { copy_file(item_id, target_path); } auto st = status(target_path); return This->make_item(method, target_path, st); }; return invoke_async(method, do_copy); } // Make sure that id does not point outside the root. void LocalProvider::throw_if_not_valid(string const& method, string const& id) const { using namespace boost::filesystem; string suspect_id; try { suspect_id = canonical(id).native(); } catch (filesystem_error const& e) { throw_storage_exception(method, e); } // Disallow things such as /blah/../blah even though they lead to the correct path. if (suspect_id != id) { throw boost::enable_current_exception(InvalidArgumentException(method + ": invalid id: \"" + id + "\"")); } // id must denote the root or have the root as a prefix. auto const root_id = root_.native(); if (id != root_id && !boost::starts_with(id, root_id + "/")) { throw boost::enable_current_exception(InvalidArgumentException(method + ": invalid id: \"" + id + "\"")); } } // Return an Item initialized from item_path and st. Item LocalProvider::make_item(string const& method, boost::filesystem::path const& item_path, boost::filesystem::file_status const& st) const { using namespace lomiri::storage; using namespace lomiri::storage::metadata; using namespace boost::filesystem; map meta; string const item_id = item_path.native(); int64_t const mtime_nsecs = get_mtime_nsecs(method, item_id); string const iso_mtime = make_iso_date(mtime_nsecs); ItemType type; string name = item_path.filename().native(); vector parents{item_path.parent_path().native()}; string etag; switch (st.type()) { case regular_file: type = ItemType::file; etag = to_string(mtime_nsecs); meta.insert({SIZE_IN_BYTES, int64_t(file_size(item_path))}); break; case directory_file: if (item_path == root_) { name = "/"; parents.clear(); type = ItemType::root; } else { type = ItemType::folder; } break; default: throw boost::enable_current_exception( NotExistsException(method + ": \"" + item_id + "\" is neither a file nor a folder", item_id)); } auto const info = space(item_path); meta.insert({FREE_SPACE_BYTES, int64_t(info.available)}); meta.insert({USED_SPACE_BYTES, int64_t(info.capacity - info.available)}); meta.insert({LAST_MODIFIED_TIME, iso_mtime}); meta.insert({CONTENT_TYPE, get_content_type(item_id)}); auto perms = st.permissions(); bool writable; if (type == ItemType::file) { writable = perms & owner_write; } else { writable = perms & owner_write && perms & owner_exe; } meta.insert({WRITABLE, writable}); return Item{ item_id, parents, name, etag, type, meta }; } lomiri-storage-framework-0.5.0/src/local-provider/LocalProvider.h000066400000000000000000000077261521521330000250770ustar00rootroot00000000000000/* * Copyright (C) 2017 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include class LocalProvider : public lomiri::storage::provider::ProviderBase { public: LocalProvider(); virtual ~LocalProvider(); boost::future roots( std::vector const& metadata_keys, lomiri::storage::provider::Context const& ctx) override; boost::future> list( std::string const& item_id, std::string const& page_token, std::vector const& metadata_keys, lomiri::storage::provider::Context const& ctx) override; boost::future lookup( std::string const& parent_id, std::string const& name, std::vector const& metadata_keys, lomiri::storage::provider::Context const& ctx) override; boost::future metadata( std::string const& item_id, std::vector const& metadata_keys, lomiri::storage::provider::Context const& ctx) override; boost::future create_folder( std::string const& parent_id, std::string const& name, std::vector const& metadata_keys, lomiri::storage::provider::Context const& ctx) override; boost::future> create_file( std::string const& parent_id, std::string const& name, int64_t size, std::string const& content_type, bool allow_overwrite, std::vector const& metadata_keys, lomiri::storage::provider::Context const& ctx) override; boost::future> update( std::string const& item_id, int64_t size, std::string const& old_etag, std::vector const& metadata_keys, lomiri::storage::provider::Context const& ctx) override; boost::future> download( std::string const& item_id, std::string const& match_etag, lomiri::storage::provider::Context const& ctx) override; boost::future delete_item(std::string const& item_id, lomiri::storage::provider::Context const& ctx) override; boost::future move( std::string const& item_id, std::string const& new_parent_id, std::string const& new_name, std::vector const& metadata_keys, lomiri::storage::provider::Context const& ctx) override; boost::future copy( std::string const& item_id, std::string const& new_parent_id, std::string const& new_name, std::vector const& metadata_keys, lomiri::storage::provider::Context const& ctx) override; void throw_if_not_valid(std::string const& method, std::string const& id) const; lomiri::storage::provider::Item make_item(std::string const& method, boost::filesystem::path const& item_path, boost::filesystem::file_status const& st) const; private: boost::filesystem::path const root_; }; lomiri-storage-framework-0.5.0/src/local-provider/LocalUploadJob.cpp000066400000000000000000000263221521521330000255100ustar00rootroot00000000000000/* * Copyright (C) 2017 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include "LocalUploadJob.h" #include "LocalProvider.h" #include "utils.h" #include #include #include using namespace lomiri::storage::provider; using namespace std; static int next_upload_id = 0; LocalUploadJob::LocalUploadJob(shared_ptr const& provider, int64_t size, const string& method) : UploadJob(to_string(++next_upload_id)) , provider_(provider) , size_(size) , bytes_to_write_(size) , method_(method) , state_(in_progress) , tmp_fd_([](int fd){ if (fd != -1) ::close(fd); }) { } LocalUploadJob::LocalUploadJob(shared_ptr const& provider, string const& parent_id, string const& name, int64_t size, bool allow_overwrite) : LocalUploadJob(provider, size, "create_file()") { using namespace boost::filesystem; parent_id_ = parent_id; allow_overwrite_ = allow_overwrite; provider_->throw_if_not_valid(method_, parent_id); auto sanitized_name = sanitize(method_, name); path p = parent_id; p /= sanitized_name; item_id_ = p.native(); if (!allow_overwrite && exists(item_id_)) { string msg = method_ + ": \"" + item_id_ + "\" exists already"; throw ExistsException(msg, item_id_, sanitized_name.native()); } prepare_channels(); } LocalUploadJob::LocalUploadJob(shared_ptr const& provider, string const& item_id, int64_t size, string const& old_etag) : LocalUploadJob(provider, size, "update()") { using namespace boost::filesystem; item_id_ = item_id; provider_->throw_if_not_valid(method_, item_id); try { auto st = status(item_id); if (!is_regular_file(st)) { throw InvalidArgumentException(method_ + ": \"" + item_id + "\" is not a file"); } } // LCOV_EXCL_START catch (filesystem_error const& e) { // The call to status could throw if the file is unlinked immediately // after the call to throw_if_not_valid. throw_storage_exception(method_, e); } // LCOV_EXCL_STOP if (!old_etag.empty()) { int64_t mtime = get_mtime_nsecs(method_, item_id); if (to_string(mtime) != old_etag) { throw ConflictException(method_ + ": etag mismatch"); } } old_etag_ = old_etag; prepare_channels(); } LocalUploadJob::~LocalUploadJob() = default; void LocalUploadJob::prepare_channels() { using namespace boost::filesystem; // Open tmp file for writing. auto parent_path = path(item_id_).parent_path(); tmp_fd_.reset(open(parent_path.native().c_str(), O_TMPFILE | O_WRONLY, 0600)); if (tmp_fd_.get() == -1) { // Some kernels on the phones don't support O_TMPFILE and return various errno values when this fails. // So, if anything at all goes wrong, we fall back on conventional temp file creation and // produce a hard error if that doesn't work either. // Note that, in this case, the temp file retains its name in the file system. Not nice because, // if this process dies at the wrong moment, we leave the temp file behind. use_linkat_ = false; string tmpfile = parent_path.native() + "/" + TMPFILE_PREFIX + "-%%%%-%%%%-%%%%-%%%%"; tmp_fd_.reset(mkstemp(const_cast(tmpfile.data()))); if (tmp_fd_.get() == -1) { string msg = method_ + ": cannot create temp file \"" + tmpfile + "\": " + lomiri::storage::internal::safe_strerror(errno); throw ResourceException(msg, errno); } // LCOV_EXCL_START file_.reset(new QFile(QString::fromStdString(tmpfile))); file_->open(QIODevice::WriteOnly); // LCOV_EXCL_STOP } else { use_linkat_ = true; file_.reset(new QFile); file_->open(tmp_fd_.get(), QIODevice::WriteOnly, QFileDevice::DontCloseHandle); } // Make read socket ready. int dup_fd = dup(read_socket()); if (dup_fd == -1) { // LCOV_EXCL_START string msg = method_ + ": dup() failed: " + lomiri::storage::internal::safe_strerror(errno); throw ResourceException(msg, errno); // LCOV_EXCL_STOP } read_socket_.setSocketDescriptor(dup_fd, QLocalSocket::ConnectedState, QIODevice::ReadOnly); connect(&read_socket_, &QLocalSocket::readyRead, this, &LocalUploadJob::on_bytes_ready); connect(&read_socket_, &QIODevice::readChannelFinished, this, &LocalUploadJob::on_read_channel_finished); } boost::future LocalUploadJob::cancel() { if (state_ == in_progress) { abort_upload(); } return boost::make_ready_future(); } boost::future LocalUploadJob::finish() { on_bytes_ready(); // Read any remaining unread buffered data. if (bytes_to_write_ > 0) { string msg = "finish() method called too early, size was given as " + to_string(size_) + " but only " + to_string(size_ - bytes_to_write_) + " bytes were received"; return boost::make_exceptional_future(LogicException(msg)); } // We are committed to finishing successfully or with an error now. state_ = finished; try { // We check again for an etag mismatch or overwrite, in case the file was updated after the upload started. if (!parent_id_.empty()) { // create_file() if (!allow_overwrite_ && boost::filesystem::exists(item_id_)) { string msg = method_ + ": \"" + item_id_ + "\" exists already"; boost::filesystem::path(item_id_).filename().native(); BOOST_THROW_EXCEPTION( ExistsException(msg, item_id_, boost::filesystem::path(item_id_).filename().native())); } } else if (!old_etag_.empty()) { // update() int64_t mtime = get_mtime_nsecs(method_, item_id_); if (to_string(mtime) != old_etag_) { BOOST_THROW_EXCEPTION(ConflictException(method_ + ": etag mismatch")); } } if (!file_->flush()) // Make sure that all buffered data is written. { // LCOV_EXCL_START string msg = "finish(): cannot flush output file: " + file_->errorString().toStdString(); throw_storage_exception("finish()", msg, file_->error()); // LCOV_EXCL_STOP } // Link the anonymous tmp file into the file system. using namespace lomiri::storage::internal; if (use_linkat_) { auto old_path = string("/proc/self/fd/") + std::to_string(tmp_fd_.get()); ::unlink(item_id_.c_str()); // linkat() will not remove existing file: http://lwn.net/Articles/559969/ if (linkat(-1, old_path.c_str(), tmp_fd_.get(), item_id_.c_str(), AT_SYMLINK_FOLLOW) == -1) { // LCOV_EXCL_START string msg = "finish(): linkat \"" + old_path + "\" to \"" + item_id_ + "\" failed: " + safe_strerror(errno); BOOST_THROW_EXCEPTION(ResourceException(msg, errno)); // LCOV_EXCL_STOP } } else { // LCOV_EXCL_START auto old_path = file_->fileName().toStdString(); if (rename(old_path.c_str(), item_id_.c_str()) == -1) { string msg = "finish(): rename \"" + old_path + "\" to \"" + item_id_ + "\" failed: " + safe_strerror(errno); BOOST_THROW_EXCEPTION(ResourceException(msg, errno)); } // LCOV_EXCL_STOP } file_->close(); read_socket_.close(); auto st = boost::filesystem::status(item_id_); return boost::make_ready_future(provider_->make_item(method_, item_id_, st)); } catch (StorageException const&) { return boost::make_exceptional_future(boost::current_exception()); } // LCOV_EXCL_START catch (boost::filesystem::filesystem_error const& e) { try { throw_storage_exception("finish()", e); } catch (StorageException const&) { return boost::make_exceptional_future(boost::current_exception()); } } catch (std::exception const& e) { return boost::make_exceptional_future(UnknownException(e.what())); } // LCOV_EXCL_STOP } void LocalUploadJob::on_bytes_ready() { if (bytes_to_write_ < 0) { return; // LCOV_EXCL_LINE // We received too many bytes earlier. } try { auto buf = read_socket_.readAll(); if (buf.size() != 0) { bytes_to_write_ -= buf.size(); if (bytes_to_write_ < 0) { string msg = method_ + ": received more than the expected number (" + to_string(size_) + ") of bytes"; throw LogicException(msg); } auto bytes_written = file_->write(buf); if (bytes_written == -1) { // LCOV_EXCL_START string msg = "write error: " + file_->errorString().toStdString(); throw_storage_exception(method_, msg, file_->error()); // LCOV_EXCL_STOP } else if (bytes_written != buf.size()) { // LCOV_EXCL_START string msg = "write error, requested " + to_string(buf.size()) + " B, but wrote only " + to_string(bytes_written) + " B."; throw_storage_exception(method_, msg, QFileDevice::FatalError); // LCOV_EXCL_STOP } } } catch (std::exception const&) { abort_upload(); report_error(current_exception()); } } void LocalUploadJob::on_read_channel_finished() { on_bytes_ready(); // In case there s still buffered data to be read. } void LocalUploadJob::abort_upload() { state_ = cancelled; disconnect(&read_socket_, nullptr, this, nullptr); read_socket_.abort(); file_->close(); if (!use_linkat_) { // LCOV_EXCL_START string filename = file_->fileName().toStdString(); ::unlink(filename.c_str()); // Don't leave any temp file behind. // LCOV_EXCL_STOP } bytes_to_write_ = 0; } lomiri-storage-framework-0.5.0/src/local-provider/LocalUploadJob.h000066400000000000000000000050231521521330000251500ustar00rootroot00000000000000/* * Copyright (C) 2017 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #pragma GCC diagnostic ignored "-Wswitch-default" #include #include #pragma GCC diagnostic pop class LocalProvider; class LocalUploadJob : public QObject, public lomiri::storage::provider::UploadJob { Q_OBJECT public: LocalUploadJob(std::shared_ptr const& provider, int64_t size, const std::string& method); // create_file() LocalUploadJob(std::shared_ptr const& provider, std::string const& parent_id, std::string const& name, int64_t size, bool allow_overwrite); // update() LocalUploadJob(std::shared_ptr const& provider, std::string const& item_id, int64_t size, std::string const& old_etag); virtual ~LocalUploadJob(); virtual boost::future cancel() override; virtual boost::future finish() override; private Q_SLOTS: void on_bytes_ready(); void on_read_channel_finished(); private: enum State { in_progress, finished, cancelled }; void prepare_channels(); void abort_upload(); std::shared_ptr const provider_; int64_t const size_; int64_t bytes_to_write_; std::unique_ptr file_; QLocalSocket read_socket_; std::string const method_; State state_; std::string item_id_; std::string old_etag_; // Empty for create_file() std::string parent_id_; // Empty for update() bool allow_overwrite_; // Undefined for update() lomiri::util::ResourcePtr> tmp_fd_; bool use_linkat_; }; com.lomiri.StorageFramework.Provider.Local.service.in000066400000000000000000000002221521521330000342240ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/src/local-provider[D-BUS Service] Name=com.lomiri.StorageFramework.Provider.Local Exec=@CMAKE_INSTALL_FULL_LIBEXECDIR@/@PROJECT_NAME@/lomiri-storage-provider-local lomiri-storage-framework-0.5.0/src/local-provider/main.cpp000066400000000000000000000026451521521330000236040ustar00rootroot00000000000000/* * Copyright (C) 2017 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include "LocalProvider.h" #include #include using namespace std; using namespace lomiri::storage::provider; int main(int argc, char* argv[]) { using namespace boost::filesystem; string const bus_name = "com.lomiri.StorageFramework.Provider.Local"; string const account_service_id = ""; string progname = argv[0]; try { progname = path(progname).filename().native(); Server server(bus_name, account_service_id); server.init(argc, argv); server.run(); } catch (std::exception const& e) { cerr << progname << ": " << e.what() << endl; return 1; } } lomiri-storage-framework-0.5.0/src/local-provider/utils.cpp000066400000000000000000000116751521521330000240230ustar00rootroot00000000000000/* * Copyright (C) 2017 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include "utils.h" #include #include #include #include #include using namespace lomiri::storage::provider; using namespace std; // Return modification time in nanoseconds since the epoch. int64_t get_mtime_nsecs(string const& method, string const& path) { using namespace lomiri::storage::internal; struct stat st; if (stat(path.c_str(), &st) == -1) { // LCOV_EXCL_START string msg = method + ": cannot stat \"" + path + "\": " + safe_strerror(errno); throw boost::enable_current_exception(ResourceException(msg, errno)); // LCOV_EXCL_STOP } return int64_t(st.st_mtim.tv_sec) * 1000000000 + st.st_mtim.tv_nsec; } // Return true if the path uses the temp file prefix. bool is_reserved_path(boost::filesystem::path const& path) { string filename = path.filename().native(); return boost::starts_with(filename, TMPFILE_PREFIX); } // Check that name is a valid file or directory name, that is, has a single component // and is not "", ".", or "..". Also check that the name does not start with the // temp file prefix. Throw if the name is invalid. boost::filesystem::path sanitize(string const& method, string const& name) { using namespace boost::filesystem; path p = name; if (!p.parent_path().empty()) { // name contains more than one component. string msg = method + ": name \"" + name + "\" cannot contain a slash"; throw boost::enable_current_exception(InvalidArgumentException(msg)); } path filename = p.filename(); if (filename.empty() || filename == "." || filename == "..") { // Not an allowable file name. string msg = method + ": invalid name: \"" + name + "\""; throw boost::enable_current_exception(InvalidArgumentException(msg)); } if (is_reserved_path(filename)) { string msg = string(method + ": names beginning with \"") + TMPFILE_PREFIX + "\" are reserved"; throw boost::enable_current_exception(InvalidArgumentException(msg)); } return p; } // Throw a StorageException that corresponds to a boost::filesystem_error. void throw_storage_exception(string const& method, boost::filesystem::filesystem_error const& e) { using namespace boost::system::errc; string msg = method + ": "; string path1 = e.path1().native(); string path2 = e.path2().native(); if (!path2.empty()) { msg += "src = \"" + path1 + "\", target = \"" + path2 + "\""; // LCOV_EXCL_LINE } else { msg += "\"" + path1 + "\""; } msg += string(": ") + e.what(); switch (e.code().value()) { case permission_denied: case operation_not_permitted: throw boost::enable_current_exception(PermissionException(msg)); case no_such_file_or_directory: throw boost::enable_current_exception(NotExistsException(msg, e.path1().native())); // LCOV_EXCL_START case file_exists: throw boost::enable_current_exception( ExistsException(msg, e.path1().native(), e.path1().filename().native())); case no_space_on_device: throw boost::enable_current_exception(QuotaException(msg)); default: throw boost::enable_current_exception(ResourceException(msg, e.code().value())); // LCOV_EXCL_STOP } } // Throw a storage exception that corresponds to a FileError. void throw_storage_exception(string const& method, string const& msg, QFileDevice::FileError e) { string const error_msg = method + ": " + msg; switch (e) { case QFileDevice::NoError: abort(); // LCOV_EXCL_LINE // Precondition violation break; default: throw ResourceException(error_msg + " (QFileDevice::FileError = " + to_string(e) + ")", e); } } // Throw a storage exception that corresponds to a LocalSocketError. // LCOV_EXCL_START void throw_storage_exception(string const& method, string const& msg, QLocalSocket::LocalSocketError e) { throw ResourceException(method + ": " + msg + " (QLocalSocket::LocalSocketError = " + to_string(e) + ")", e); } // LCOV_EXCL_STOP lomiri-storage-framework-0.5.0/src/local-provider/utils.h000066400000000000000000000031071521521330000234570ustar00rootroot00000000000000/* * Copyright (C) 2017 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #include #include #pragma GCC diagnostic pop #include constexpr char const* TMPFILE_PREFIX = ".storage-framework"; int64_t get_mtime_nsecs(std::string const& method, std::string const& path); bool is_reserved_path(boost::filesystem::path const& path); boost::filesystem::path sanitize(std::string const& method, std::string const& name); [[ noreturn ]] void throw_storage_exception(std::string const& method, boost::filesystem::filesystem_error const& e); [[ noreturn ]] void throw_storage_exception(std::string const& method, std::string const& msg, QFileDevice::FileError e); [[ noreturn ]] void throw_storage_exception(std::string const& method, std::string const& msg, QLocalSocket::LocalSocketError e); lomiri-storage-framework-0.5.0/src/provider/000077500000000000000000000000001521521330000210555ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/src/provider/CMakeLists.txt000066400000000000000000000104571521521330000236240ustar00rootroot00000000000000if(QT_VERSION_MAJOR GREATER_EQUAL 6) set(LSF_PROVIDER_NAME lomiri-storage-framework-qt${QT_VERSION_MAJOR}-provider-${LSF_PROVIDER_API_VERSION}) set(LSF_PROVIDER_INCLUDE_NAME ${LSF_PROVIDER_NAME}) else() set(LSF_PROVIDER_NAME lomiri-storage-framework-provider) set(LSF_PROVIDER_INCLUDE_NAME ${LSF_PROVIDER_NAME}-${LSF_PROVIDER_API_VERSION}) endif() include_directories(${CMAKE_CURRENT_BINARY_DIR}) qt_add_dbus_adaptor(generated_files ${CMAKE_SOURCE_DIR}/data/provider.xml lomiri/storage/provider/internal/ProviderInterface.h lomiri::storage::provider::internal::ProviderInterface ) set_source_files_properties(bus.xml PROPERTIES CLASSNAME BusInterface) qt_add_dbus_interface(generated_files bus.xml businterface) set_source_files_properties(${generated_files} PROPERTIES COMPILE_FLAGS "-Wno-ctor-dtor-privacy -Wno-missing-field-initializers" GENERATED TRUE ) add_custom_target(sf-provider-generated-files DEPENDS ${generated_files}) add_library(lsf-provider-objects OBJECT DownloadJob.cpp Exceptions.cpp ProviderBase.cpp Server.cpp TempfileUploadJob.cpp UploadJob.cpp testing/TestServer.cpp internal/AccountData.cpp internal/DBusPeerCache.cpp internal/DownloadJobImpl.cpp internal/FixedAccountData.cpp internal/Handler.cpp internal/MainLoopExecutor.cpp internal/OnlineAccountData.cpp internal/PendingJobs.cpp internal/ProviderInterface.cpp internal/ServerImpl.cpp internal/TempfileUploadJobImpl.cpp internal/TestServerImpl.cpp internal/UploadJobImpl.cpp internal/dbusmarshal.cpp ${CMAKE_SOURCE_DIR}/include/lomiri/storage/provider/internal/AccountData.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/provider/internal/DownloadJobImpl.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/provider/internal/FixedAccountData.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/provider/internal/Handler.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/provider/internal/MainLoopExecutor.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/provider/internal/OnlineAccountData.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/provider/internal/PendingJobs.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/provider/internal/ProviderInterface.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/provider/internal/ServerImpl.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/provider/internal/TempfileUploadJobImpl.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/provider/internal/UploadJobImpl.h ) set_source_files_properties(internal/ProviderInterface.cpp PROPERTIES COMPILE_FLAGS "-Wno-missing-field-initializers" ) add_dependencies(lsf-provider-objects sf-provider-generated-files) set_target_properties(lsf-provider-objects PROPERTIES AUTOMOC TRUE ) target_compile_options(lsf-provider-objects PUBLIC -DBOOST_THREAD_VERSION=4 -DBOOST_THREAD_PROVIDES_EXECUTORS) target_link_libraries(lsf-provider-objects PUBLIC lomiri-storage-framework-common-internal Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::DBus Qt${QT_VERSION_MAJOR}::Network ${Boost_LIBRARIES} PkgConfig::APPARMOR_DEPS PkgConfig::ONLINEACCOUNTS_DEPS ) add_library(lomiri-storage-framework-provider SHARED ${generated_files}) set_target_properties(lomiri-storage-framework-provider PROPERTIES AUTOMOC TRUE LINK_FLAGS "-Wl,--no-undefined" OUTPUT_NAME ${LSF_PROVIDER_NAME} SOVERSION ${LSF_PROVIDER_SOVERSION} VERSION ${LSF_PROVIDER_LIBVERSION} ) target_compile_options(lomiri-storage-framework-provider PUBLIC $) target_link_libraries(lomiri-storage-framework-provider PUBLIC lsf-provider-objects) install( TARGETS lomiri-storage-framework-provider LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) # Build a static version of the library so that tests have access to # hidden visibility symbols. add_library(lomiri-storage-framework-provider-static STATIC ${generated_files}) set_target_properties(lomiri-storage-framework-provider-static PROPERTIES AUTOMOC TRUE ) target_compile_options(lomiri-storage-framework-provider-static PUBLIC $) target_link_libraries(lomiri-storage-framework-provider-static PUBLIC lsf-provider-objects) configure_file( lomiri-storage-framework-provider.pc.in ${LSF_PROVIDER_NAME}.pc ) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/${LSF_PROVIDER_NAME}.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig ) lomiri-storage-framework-0.5.0/src/provider/DownloadJob.cpp000066400000000000000000000035271521521330000237720ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #include #include #include using namespace std; namespace lomiri { namespace storage { namespace provider { DownloadJob::DownloadJob(internal::DownloadJobImpl *p) : p_(p) { // We may be created by user code running in some other thread: // make sure our events are processed on the event loop thread, // and then let the class complete its initialisation on that // thread. p_->moveToThread(QCoreApplication::instance()->thread()); QMetaObject::invokeMethod(p_, "complete_init", Qt::QueuedConnection); } DownloadJob::DownloadJob(string const& download_id) : DownloadJob(new internal::DownloadJobImpl(download_id)) { } DownloadJob::~DownloadJob() { if (p_) { p_->deleteLater(); } } string const& DownloadJob::download_id() const { return p_->download_id(); } int DownloadJob::write_socket() const { return p_->write_socket(); } void DownloadJob::report_complete() { p_->report_complete(); } void DownloadJob::report_error(std::exception_ptr p) { p_->report_error(p); } } } } lomiri-storage-framework-0.5.0/src/provider/Exceptions.cpp000066400000000000000000000100041521521330000236750ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include using namespace std; namespace lomiri { namespace storage { namespace provider { StorageException::StorageException(std::string const& exception_type, string const& error_message) : what_string_(string(exception_type) + ": " + error_message) , type_(exception_type) , error_message_(error_message) { } StorageException::~StorageException() = default; char const* StorageException::what() const noexcept { return what_string_.c_str(); } string StorageException::type() const { return type_; } string StorageException::error_message() const { return error_message_; } RemoteCommsException::RemoteCommsException(string const& error_message) : StorageException("RemoteCommsException", error_message) { } RemoteCommsException::~RemoteCommsException() = default; NotExistsException::NotExistsException(string const& error_message, string const& key) : StorageException("NotExistsException", error_message) , key_(key) { } NotExistsException::~NotExistsException() = default; string NotExistsException::key() const { return key_; } ExistsException::ExistsException(string const& error_message, string const& identity, string const& name) : StorageException("ExistsException", error_message) , identity_(identity) , name_(name) { } ExistsException::~ExistsException() = default; string ExistsException::native_identity() const { return identity_; } string ExistsException::name() const { return name_; } ConflictException::ConflictException(string const& error_message) : StorageException("ConflictException", error_message) { } ConflictException::~ConflictException() = default; UnauthorizedException::UnauthorizedException(string const& error_message) : StorageException("UnauthorizedException", error_message) { } UnauthorizedException::~UnauthorizedException() = default; PermissionException::PermissionException(string const& error_message) : StorageException("PermissionException", error_message) { } PermissionException::~PermissionException() = default; QuotaException::QuotaException(string const& error_message) : StorageException("QuotaException", error_message) { } QuotaException::~QuotaException() = default; CancelledException::CancelledException(string const& error_message) : StorageException("CancelledException", error_message) { } CancelledException::~CancelledException() = default; LogicException::LogicException(string const& error_message) : StorageException("LogicException", error_message) { } LogicException::~LogicException() = default; InvalidArgumentException::InvalidArgumentException(string const& error_message) : StorageException("InvalidArgumentException", error_message) { } InvalidArgumentException::~InvalidArgumentException() = default; ResourceException::ResourceException(string const& error_message, int error_code) : StorageException("ResourceException", error_message) , error_code_(error_code) { } ResourceException::~ResourceException() = default; int ResourceException::error_code() const noexcept { return error_code_; } UnknownException::UnknownException(string const& error_message) : StorageException("UnknownException", error_message) { } UnknownException::~UnknownException() = default; } // namespace provider } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/provider/ProviderBase.cpp000066400000000000000000000016141521521330000241500ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #include namespace lomiri { namespace storage { namespace provider { ProviderBase::ProviderBase() { } ProviderBase::~ProviderBase() = default; } } } lomiri-storage-framework-0.5.0/src/provider/Server.cpp000066400000000000000000000024071521521330000230320ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #include #include #include using namespace std; namespace lomiri { namespace storage { namespace provider { ServerBase::ServerBase(std::string const& bus_name, std::string const& account_service_id) : p_(new internal::ServerImpl(this, bus_name, account_service_id)) { } ServerBase::~ServerBase() = default; void ServerBase::init(int& argc, char** argv) { p_->init(argc, argv); } int ServerBase::run() { return p_->run(); } } } } lomiri-storage-framework-0.5.0/src/provider/TempfileUploadJob.cpp000066400000000000000000000026401521521330000251300ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #include #include using namespace std; namespace lomiri { namespace storage { namespace provider { TempfileUploadJob::TempfileUploadJob(internal::TempfileUploadJobImpl *p) : UploadJob(p) { } TempfileUploadJob::TempfileUploadJob(string const& upload_id) : TempfileUploadJob(new internal::TempfileUploadJobImpl(upload_id)) { } TempfileUploadJob::~TempfileUploadJob() = default; void TempfileUploadJob::drain() { static_cast(p_)->drain(); } string TempfileUploadJob::file_name() const { return static_cast(p_)->file_name(); } } } } lomiri-storage-framework-0.5.0/src/provider/UploadJob.cpp000066400000000000000000000033551521521330000234460ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #include #include #include using namespace std; namespace lomiri { namespace storage { namespace provider { UploadJob::UploadJob(internal::UploadJobImpl *p) : p_(p) { // We may be created by user code running in some other thread: // make sure our events are processed on the event loop thread, // and then let the class complete its initialisation on that // thread. p_->moveToThread(QCoreApplication::instance()->thread()); QMetaObject::invokeMethod(p_, "complete_init", Qt::QueuedConnection); } UploadJob::UploadJob(string const& upload_id) : UploadJob(new internal::UploadJobImpl(upload_id)) { } UploadJob::~UploadJob() { if (p_) { p_->deleteLater(); } } string const& UploadJob::upload_id() const { return p_->upload_id(); } int UploadJob::read_socket() const { return p_->read_socket(); } void UploadJob::report_error(std::exception_ptr p) { p_->report_error(p); } } } } lomiri-storage-framework-0.5.0/src/provider/bus.xml000066400000000000000000000005141521521330000223700ustar00rootroot00000000000000 lomiri-storage-framework-0.5.0/src/provider/internal/000077500000000000000000000000001521521330000226715ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/src/provider/internal/AccountData.cpp000066400000000000000000000036421521521330000255700ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #include #include #include #include #include #include using namespace std; using lomiri::storage::internal::InactivityTimer; namespace lomiri { namespace storage { namespace provider { namespace internal { AccountData::AccountData(shared_ptr const& provider, shared_ptr const& dbus_peer, shared_ptr const& inactivity_timer, QDBusConnection const& bus, QObject* parent) : QObject(parent), provider_(provider), dbus_peer_(dbus_peer), inactivity_timer_(inactivity_timer), jobs_(new PendingJobs(bus)) { } AccountData::~AccountData() = default; ProviderBase& AccountData::provider() { return *provider_; } DBusPeerCache& AccountData::dbus_peer() { return *dbus_peer_; } shared_ptr AccountData::inactivity_timer() { return inactivity_timer_; } PendingJobs& AccountData::jobs() { return *jobs_; } } } } } lomiri-storage-framework-0.5.0/src/provider/internal/DBusPeerCache.cpp000066400000000000000000000135051521521330000257760ustar00rootroot00000000000000/* * Copyright (C) 2015 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #include #include "businterface.h" #include #include #include #include using namespace std; namespace { char const DBUS_BUS_NAME[] = "org.freedesktop.DBus"; char const DBUS_BUS_PATH[] = "/org/freedesktop/DBus"; char const UNIX_USER_ID[] = "UnixUserID"; char const PROCESS_ID[] = "ProcessID"; char const LINUX_SECURITY_LABEL[] = "LinuxSecurityLabel"; int const MAX_CACHE_SIZE = 50; } namespace lomiri { namespace storage { namespace provider { namespace internal { struct DBusPeerCache::Request { QDBusPendingCallWatcher watcher; std::vector> promises; Request(QDBusPendingReply const& call) : watcher(call) {} }; DBusPeerCache::DBusPeerCache(QDBusConnection const& bus) : bus_daemon_(new BusInterface(DBUS_BUS_NAME, DBUS_BUS_PATH, bus)) , apparmor_enabled_(aa_is_enabled()) { } DBusPeerCache::~DBusPeerCache() = default; boost::future DBusPeerCache::get(QString const& peer) { // Return the credentials directly if they are cached try { Credentials const& credentials = cache_.at(peer); boost::promise p; p.set_value(credentials); return p.get_future(); } catch (std::out_of_range const &) { // ignore } // If the credentials exist in the previous generation of the // cache, move them to the current generation. try { Credentials& credentials = old_cache_.at(peer); // No real way to get coverage here because we'd // need more than 50 peers with different credentials. // LCOV_EXCL_START cache_.emplace(peer, std::move(credentials)); old_cache_.erase(peer); boost::promise p; p.set_value(credentials); return p.get_future(); // LCOV_EXCL_STOP } catch (std::out_of_range const &) { // ignore } boost::promise promise; auto future = promise.get_future(); // If the credentials are already being requested, add ourselves // to the callback list. try { unique_ptr& request = pending_.at(peer); request->promises.emplace_back(std::move(promise)); return future; } catch (std::out_of_range const &) { // ignore } // Ask the bus daemon for the peer's credentials unique_ptr request( new Request(bus_daemon_->GetConnectionCredentials(peer))); QObject::connect(&request->watcher, &QDBusPendingCallWatcher::finished, [this, peer](QDBusPendingCallWatcher *watcher) { this->received_credentials(peer, *watcher); }); request->promises.emplace_back(std::move(promise)); pending_.emplace(peer, std::move(request)); return future; } void DBusPeerCache::received_credentials(QString const& peer, QDBusPendingReply const& reply) { Credentials credentials; if (reply.isError()) { // LCOV_EXCL_START qWarning() << "DBusPeerCache::received_credentials(): " "error retrieving credentials for" << peer << ":" << reply.error().message(); // LCOV_EXCL_STOP } else { credentials.valid = true; // The contents of this map are described in the specification here: // http://dbus.freedesktop.org/doc/dbus-specification.html#bus-messages-get-connection-credentials credentials.uid = reply.value().value(UNIX_USER_ID).value(); credentials.pid = reply.value().value(PROCESS_ID).value(); if (apparmor_enabled_) { QByteArray label = reply.value().value(LINUX_SECURITY_LABEL).value(); if (label.size() > 0) { // The label is null terminated. assert(label[label.size()-1] == '\0'); label.truncate(label.size() - 1); // Trim the mode off the end of the label. int pos = label.lastIndexOf(' '); if (pos > 0 && label.endsWith(')') && label[pos+1] == '(') { label.truncate(pos); // LCOV_EXCL_LINE } credentials.label = string(label.constData(), label.size()); } } else { // If AppArmor is not enabled, treat peer as unconfined. credentials.label = "unconfined"; // LCOV_EXCL_LINE } } // If we've hit our maximum cache size, start a new generation. if (cache_.size() >= MAX_CACHE_SIZE) { // LCOV_EXCL_START old_cache_ = std::move(cache_); cache_.clear(); // LCOV_EXCL_STOP } cache_.emplace(peer, credentials); // Notify anyone waiting on the request and remove it from the map: for (auto& promise : pending_.at(peer)->promises) { promise.set_value(credentials); } pending_.erase(peer); } } // namespace internal } // namespace provider } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/provider/internal/DownloadJobImpl.cpp000066400000000000000000000103101521521330000264140ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #include #include #include #include #include #include #include #include #include using namespace std; using namespace lomiri::storage::internal; namespace lomiri { namespace storage { namespace provider { namespace internal { DownloadJobImpl::DownloadJobImpl(std::string const& download_id) : download_id_(download_id) { int socks[2]; if (socketpair(AF_UNIX, SOCK_STREAM, 0, socks) < 0) { int error_code = errno; string msg = "could not create socketpair: " + safe_strerror(error_code); throw ResourceException(msg, error_code); } read_socket_ = socks[0]; write_socket_ = socks[1]; #if 0 // TODO: We should be able to half-close the write channel of the read socket and the read channel of // the write socket. But, if we do, QLocalSocket indicates that everything was closed, which causes // failures on the client side. We suspect a QLocalSocket bug -- need to investigate. if (shutdown(read_socket_, SHUT_WR) < 0) { int error_code = errno; string msg = "Could not shut down write channel on read socket" + safe_strerror(error_code); throw ResourceException(msg, error_code); } if (shutdown(write_socket_, SHUT_RD) < 0) { int error_code = errno; string msg = "Could not shut down read channel on write socket: " + safe_strerror(error_code); throw ResourceException(msg, error_code); } #endif } DownloadJobImpl::~DownloadJobImpl() { if (read_socket_ >= 0) { close(read_socket_); } if (write_socket_ >= 0) { close(write_socket_); } } void DownloadJobImpl::complete_init() { } std::string const& DownloadJobImpl::download_id() const { return download_id_; } int DownloadJobImpl::write_socket() const { return write_socket_; } int DownloadJobImpl::take_read_socket() { assert(read_socket_ >= 0); int sock = read_socket_; read_socket_ = -1; return sock; } void DownloadJobImpl::set_activity(std::shared_ptr const& inactivity_timer) { activity_ = ActivityNotifier(inactivity_timer); } void DownloadJobImpl::report_complete() { if (write_socket_ >= 0) { close(write_socket_); write_socket_ = -1; } lock_guard guard(completion_lock_); completed_ = true; completion_promise_.set_value(); } void DownloadJobImpl::report_error(std::exception_ptr p) { if (write_socket_ >= 0) { close(write_socket_); write_socket_ = -1; } lock_guard guard(completion_lock_); completed_ = true; // Convert std::exception_ptr to boost::exception_ptr try { std::rethrow_exception(p); } catch (StorageException const& e) { completion_promise_.set_exception(e); } catch (...) { completion_promise_.set_exception(boost::current_exception()); } } boost::future DownloadJobImpl::finish(DownloadJob& job) { lock_guard guard(completion_lock_); if (completed_) { return completion_promise_.get_future(); } return job.finish(); } boost::future DownloadJobImpl::cancel(DownloadJob& job) { lock_guard guard(completion_lock_); if (completed_) { return boost::make_ready_future(); } return job.cancel(); } } } } } lomiri-storage-framework-0.5.0/src/provider/internal/FixedAccountData.cpp000066400000000000000000000034701521521330000265470ustar00rootroot00000000000000/* * Copyright (C) 2017 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #include using namespace std; using lomiri::storage::internal::InactivityTimer; namespace lomiri { namespace storage { namespace provider { namespace internal { FixedAccountData::FixedAccountData(shared_ptr const& provider, shared_ptr const& dbus_peer, shared_ptr const& inactivity_timer, QDBusConnection const& bus, QObject* parent) : AccountData(provider, dbus_peer, inactivity_timer, bus, parent) { } FixedAccountData::~FixedAccountData() = default; void FixedAccountData::authenticate(bool interactive, bool invalidate_cache) { Q_UNUSED(interactive); Q_UNUSED(invalidate_cache); // Queue an emission of the authenticated signal. QMetaObject::invokeMethod(this, "authenticated", Qt::QueuedConnection); } bool FixedAccountData::has_credentials() { return true; } Credentials const& FixedAccountData::credentials() { return credentials_; } } } } } lomiri-storage-framework-0.5.0/src/provider/internal/Handler.cpp000066400000000000000000000160121521521330000247520ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #include #include #include #include #include #include #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #pragma GCC diagnostic ignored "-Wswitch-default" #include #pragma GCC diagnostic pop #include using namespace lomiri::storage::internal; using namespace std; namespace lomiri { namespace storage { namespace provider { namespace internal { Handler::Handler(shared_ptr const& account, Callback const& callback, QDBusConnection const& bus, QDBusMessage const& message) : account_(account), callback_(callback), bus_(bus), message_(message), activity_(account->inactivity_timer()) { } void Handler::begin() { // If we have already retrieved credentials from OnlineAccounts, // and we aren't retrying the request, go to on_authenticated // immediately. if (account_->has_credentials() && !retry_) { on_authenticated(); return; } // Otherwise, try to authenticate and wait for the result. account_->authenticate(true, retry_); connect(account_.get(), &AccountData::authenticated, this, &Handler::on_authenticated); } void Handler::on_authenticated() { disconnect(account_.get(), &AccountData::authenticated, this, &Handler::on_authenticated); if (!account_->has_credentials()) { string msg = "Handler::begin(): could not retrieve account credentials"; qDebug() << QString::fromStdString(msg); auto ep = make_exception_ptr(UnauthorizedException(msg)); marshal_exception(ep); QMetaObject::invokeMethod(this, "send_reply", Qt::QueuedConnection); return; } // Need to put security check in here. auto peer_future = account_->dbus_peer().get(message_.service()); creds_future_ = peer_future.then( EXEC_IN_MAIN [this](decltype(peer_future) f) { auto info = f.get(); if (info.valid) { context_ = {info.uid, info.pid, std::move(info.label), account_->credentials()}; QMetaObject::invokeMethod(this, "credentials_received", Qt::QueuedConnection); } else { string msg = "Handler::begin(): could not retrieve D-Bus peer credentials"; qDebug() << QString::fromStdString(msg); auto ep = make_exception_ptr(UnauthorizedException(msg)); marshal_exception(ep); QMetaObject::invokeMethod(this, "send_reply", Qt::QueuedConnection); } }); } void Handler::credentials_received() { boost::future msg_future; try { msg_future = callback_(account_, context_, message_); } catch (std::exception const& e) { qDebug() << "provider method threw an exception:" << e.what(); marshal_exception(current_exception()); QMetaObject::invokeMethod(this, "send_reply", Qt::QueuedConnection); return; } reply_future_ = msg_future.then( EXEC_IN_MAIN [this](decltype(msg_future) f) { try { reply_ = f.get(); } catch (UnauthorizedException const& e) { QMetaObject::invokeMethod(this, "handle_unauthorized", Qt::QueuedConnection, Q_ARG(std::exception_ptr, current_exception())); return; } catch (std::exception const& e) { marshal_exception(current_exception()); } QMetaObject::invokeMethod(this, "send_reply", Qt::QueuedConnection); }); } void Handler::handle_unauthorized(exception_ptr ep) { if (retry_) { // We've already retried once, so send error out as is. marshal_exception(ep); send_reply(); } else { // Otherwise, restart the request with the retry_ flag set. retry_ = true; begin(); } } void Handler::send_reply() { bus_.send(reply_); Q_EMIT finished(); } void Handler::marshal_exception(exception_ptr ep) { try { rethrow_exception(ep); } catch (StorageException const& e) { reply_ = message_.createErrorReply(QString(DBUS_ERROR_PREFIX) + QString::fromStdString(e.type()), QString::fromStdString(e.error_message())); try { throw; } catch (NotExistsException const& e) { reply_ << QVariant(QString::fromStdString(e.key())); } catch (ExistsException const& e) { reply_ << QVariant(QString::fromStdString(e.native_identity())); reply_ << QVariant(QString::fromStdString(e.name())); } catch (ResourceException const& e) { qDebug() << e.what(); reply_ << QVariant(e.error_code()); } catch (RemoteCommsException const& e) { qDebug() << e.what(); } catch (UnknownException const& e) { qDebug() << e.what(); } catch (StorageException const&) { // Some other sub-type of StorageException without additional data members, // and we don't want to log this (not surprising) exception. } } catch (std::exception const& e) { QString msg = QString("unknown exception thrown by provider: ") + e.what(); qDebug() << msg; reply_ = message_.createErrorReply(QString(DBUS_ERROR_PREFIX) + "UnknownException", msg); } catch (...) { QString msg = "unknown exception thrown by provider"; qDebug() << msg; reply_ = message_.createErrorReply(QString(DBUS_ERROR_PREFIX) + "UnknownException", msg); } } } } } } lomiri-storage-framework-0.5.0/src/provider/internal/MainLoopExecutor.cpp000066400000000000000000000041071521521330000266340ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #include #include #include #include namespace { class WorkEvent : public QEvent { public: typedef lomiri::storage::provider::internal::MainLoopExecutor::work work; WorkEvent(work&& closure) : QEvent(WorkEvent::eventType()), closure_(std::move(closure)) { } static QEvent::Type eventType() { static auto type = static_cast(QEvent::registerEventType()); return type; } work closure_; }; } namespace lomiri { namespace storage { namespace provider { namespace internal { MainLoopExecutor::MainLoopExecutor() { } MainLoopExecutor& MainLoopExecutor::instance() { static MainLoopExecutor instance; return instance; } void MainLoopExecutor::submit(work&& closure) { QCoreApplication::instance()->postEvent( this, new WorkEvent(std::move(closure))); } void MainLoopExecutor::close() { } bool MainLoopExecutor::closed() { return false; } bool MainLoopExecutor::try_executing_one() { return false; } bool MainLoopExecutor::event(QEvent *e) { if (e->type() != WorkEvent::eventType()) { return QObject::event(e); } auto *we = static_cast(e); execute(we->closure_); return true; } void MainLoopExecutor::execute(work& closure) noexcept { closure(); } } } } } lomiri-storage-framework-0.5.0/src/provider/internal/OnlineAccountData.cpp000066400000000000000000000137141521521330000267360ustar00rootroot00000000000000/* * Copyright (C) 2017 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #include #include #include using namespace std; using lomiri::storage::internal::InactivityTimer; namespace lomiri { namespace storage { namespace provider { namespace internal { OnlineAccountData::OnlineAccountData(shared_ptr const& provider, shared_ptr const& dbus_peer, shared_ptr const& inactivity_timer, QDBusConnection const& bus, OnlineAccounts::Account* account, QObject* parent) : AccountData(provider, dbus_peer, inactivity_timer, bus, parent), account_(account) { connect(account_, &OnlineAccounts::Account::changed, this, &OnlineAccountData::on_changed); authenticate(false); } OnlineAccountData::~OnlineAccountData() = default; void OnlineAccountData::authenticate(bool interactive, bool invalidate_cache) { // If there is an existing authentication session running, check // if it matches our requirements. if (auth_watcher_) { if (invalidate_cache) { // If invalidate_cache has been requested, the existing // session must also be invalidating the cache. if (authenticating_invalidate_cache_) { return; } } else if (interactive) { // If interactive has been requested, the existing session // must also be interactive. if (authenticating_interactively_) { return; } } else { // Otherwise, any session will do. return; } } authenticating_interactively_ = interactive; authenticating_invalidate_cache_ = invalidate_cache; credentials_ = boost::blank(); OnlineAccounts::AuthenticationData auth_data( account_->authenticationMethod()); auth_data.setInteractive(interactive); if (invalidate_cache) { auth_data.invalidateCachedReply(); } OnlineAccounts::PendingCall call = account_->authenticate(auth_data); auth_watcher_.reset(new OnlineAccounts::PendingCallWatcher(call)); connect(auth_watcher_.get(), &OnlineAccounts::PendingCallWatcher::finished, this, &OnlineAccountData::on_authenticated); } bool OnlineAccountData::has_credentials() { // variant index 0 is boost::blank return credentials_.which() != 0; } Credentials const& OnlineAccountData::credentials() { return credentials_; } void OnlineAccountData::on_authenticated() { credentials_ = boost::blank(); switch (account_->authenticationMethod()) { case OnlineAccounts::AuthenticationMethodOAuth1: { OnlineAccounts::OAuth1Reply reply(*auth_watcher_); if (reply.hasError()) { qDebug() << "Failed to authenticate:" << reply.error().text(); } else { credentials_ = OAuth1Credentials{ reply.consumerKey().toStdString(), reply.consumerSecret().toStdString(), reply.token().toStdString(), reply.tokenSecret().toStdString(), }; } break; } case OnlineAccounts::AuthenticationMethodOAuth2: { OnlineAccounts::OAuth2Reply reply(*auth_watcher_); if (reply.hasError()) { qDebug() << "Failed to authenticate:" << reply.error().text(); } else { credentials_ = OAuth2Credentials{ reply.accessToken().toStdString(), }; } break; } case OnlineAccounts::AuthenticationMethodPassword: { // Grab hostname from account settings if available string host = account_->setting("host").toString().toStdString(); OnlineAccounts::PasswordReply reply(*auth_watcher_); if (reply.hasError()) { qDebug() << "Failed to authenticate:" << reply.error().text(); } else { QString username = reply.username(); QString password = reply.password(); // Work around password credentials bug in online-accounts-service // https://bugs.launchpad.net/bugs/1628473 if (username.isEmpty() && password.isEmpty()) { username = reply.data()["UserName"].toString(); password = reply.data()["Secret"].toString(); } credentials_ = PasswordCredentials{ username.toStdString(), password.toStdString(), move(host), }; } break; } default: qDebug() << "Unhandled authentication method:" << account_->authenticationMethod(); } auth_watcher_.reset(); Q_EMIT authenticated(); } void OnlineAccountData::on_changed() { // Assume that if we're in the middle of authenticating that we'll // receive valid credentials for the changed account. if (auth_watcher_) { return; } // Otherwise, invalidate the credentials credentials_ = boost::blank(); } } } } } lomiri-storage-framework-0.5.0/src/provider/internal/PendingJobs.cpp000066400000000000000000000137621521521330000256100ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #include #include #include #include #include #include #include #include #include #include using namespace std; namespace lomiri { namespace storage { namespace provider { namespace internal { PendingJobs::PendingJobs(QDBusConnection const& bus, QObject *parent) : QObject(parent) { watcher_.setConnection(bus); watcher_.setWatchMode(QDBusServiceWatcher::WatchForUnregistration); connect(&watcher_, &QDBusServiceWatcher::serviceUnregistered, this, &PendingJobs::service_disconnected); } PendingJobs::~PendingJobs() { for (const auto& pair : downloads_) { cancel_job(pair.second, "download " + pair.second->download_id()); } for (const auto& pair : uploads_) { cancel_job(pair.second, "upload " + pair.second->upload_id()); } } void PendingJobs::add_download(QString const& client_bus_name, unique_ptr &&job) { lock_guard guard(lock_); assert(!client_bus_name.isEmpty() && client_bus_name[0] == ':'); const auto job_id = make_pair(client_bus_name, job->download_id()); assert(downloads_.find(job_id) == downloads_.end()); shared_ptr j(std::move(job)); downloads_.emplace(job_id, j); watch_peer(client_bus_name); } shared_ptr PendingJobs::remove_download(QString const& client_bus_name, string const& download_id) { lock_guard guard(lock_); auto it = downloads_.find({client_bus_name, download_id}); if (it == downloads_.cend()) { throw LogicException("No such download: " + download_id); } auto job = it->second; downloads_.erase(it); unwatch_peer(client_bus_name); return job; } void PendingJobs::add_upload(QString const& client_bus_name, unique_ptr &&job) { lock_guard guard(lock_); assert(!client_bus_name.isEmpty() && client_bus_name[0] == ':'); const auto job_id = make_pair(client_bus_name, job->upload_id()); assert(uploads_.find(job_id) == uploads_.end()); shared_ptr j(std::move(job)); uploads_.emplace(job_id, j); watch_peer(client_bus_name); } shared_ptr PendingJobs::remove_upload(QString const& client_bus_name, string const& upload_id) { lock_guard guard(lock_); auto it = uploads_.find({client_bus_name, upload_id}); if (it == uploads_.cend()) { throw LogicException("No such upload: " + upload_id); } auto job = it->second; uploads_.erase(it); unwatch_peer(client_bus_name); return job; } void PendingJobs::watch_peer(QString const& bus_name) { auto it = services_.find(bus_name); if (it != services_.end()) { it->second++; } else { watcher_.addWatchedService(bus_name); services_[bus_name] = 1; } } void PendingJobs::unwatch_peer(QString const& bus_name) { auto it = services_.find(bus_name); if (it == services_.end()) { return; } it->second--; if (it->second == 0) { services_.erase(it); watcher_.removeWatchedService(bus_name); } } void PendingJobs::service_disconnected(QString const& service_name) { lock_guard guard(lock_); services_.erase(service_name); watcher_.removeWatchedService(service_name); const auto lower = make_pair(service_name, string()); for (auto it = downloads_.lower_bound(lower); it != downloads_.cend() && it->first.first == service_name; ) { auto job = it->second; it = downloads_.erase(it); cancel_job(job, "download " + job->download_id()); } for (auto it = uploads_.lower_bound(lower); it != uploads_.cend() && it->first.first == service_name; ) { auto job = it->second; it = uploads_.erase(it); cancel_job(job, "upload " + job->upload_id()); } } template void PendingJobs::cancel_job(shared_ptr const& job, string const& identifier) { auto f = job->p_->cancel(*job); // This continuation also ensures that the job remains // alive until the cancel method has completed. auto cancel_future = std::make_shared>(); *cancel_future = f.then( EXEC_IN_MAIN [job, identifier, cancel_future](decltype(f) f) { try { f.get(); } catch (std::exception const& e) { fprintf(stderr, "Error cancelling job '%s': %s\n", identifier.c_str(), e.what()); } // Break the reference cycle between the continuation // future and closure, while making sure the future // survives long enough to be marked ready. auto fut = std::make_shared>(std::move(*cancel_future)); MainLoopExecutor::instance().submit([fut]{}); }); } } } } } lomiri-storage-framework-0.5.0/src/provider/internal/ProviderInterface.cpp000066400000000000000000000400651521521330000270150ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; namespace { vector to_vector(QList const& l) { vector v; for (auto const& s : l) { v.push_back(s.toStdString()); } return v; } } namespace lomiri { namespace storage { namespace provider { namespace internal { ProviderInterface::ProviderInterface(shared_ptr const& account, QObject *parent) : QObject(parent), account_(account) { } ProviderInterface::~ProviderInterface() = default; void ProviderInterface::queue_request(Handler::Callback callback) { unique_ptr handler( new Handler(account_, callback, connection(), message())); connect(handler.get(), &Handler::finished, this, &ProviderInterface::request_finished); setDelayedReply(true); handler->begin(); requests_.emplace(handler.get(), std::move(handler)); } void ProviderInterface::request_finished() { Handler* handler = static_cast(sender()); try { auto& h = requests_.at(handler); h.release(); requests_.erase(handler); } // LCOV_EXCL_START catch (std::out_of_range const& e) { qWarning() << "finished() called on unknown handler" << handler; } // LCOV_EXCL_STOP // Queue deletion of handler once we re-enter the event loop. handler->deleteLater(); } QList ProviderInterface::Roots(QList const& keys) { queue_request([keys](shared_ptr const& account, Context const& ctx, QDBusMessage const& message) { auto f = account->provider().roots(to_vector(keys), ctx); return f.then( EXEC_IN_MAIN [account, message](decltype(f) f) -> QDBusMessage { auto roots = f.get(); return message.createReply(QVariant::fromValue(roots)); }); }); return {}; } QList ProviderInterface::List(QString const& item_id, QString const& page_token, QList const& keys, QString& /*next_token*/) { queue_request([item_id, page_token, keys](shared_ptr const& account, Context const& ctx, QDBusMessage const& message) { auto f = account->provider().list(item_id.toStdString(), page_token.toStdString(), to_vector(keys), ctx); return f.then( EXEC_IN_MAIN [account, message](decltype(f) f) -> QDBusMessage { vector children; string next_token; tie(children, next_token) = f.get(); return message.createReply({ QVariant::fromValue(children), QVariant(QString::fromStdString(next_token)), }); }); }); return {}; } QList ProviderInterface::Lookup(QString const& parent_id, QString const& name, QList const& keys) { queue_request([parent_id, name, keys](shared_ptr const& account, Context const& ctx, QDBusMessage const& message) { auto f = account->provider().lookup(parent_id.toStdString(), name.toStdString(), to_vector(keys), ctx); return f.then( EXEC_IN_MAIN [account, message](decltype(f) f) -> QDBusMessage { auto items = f.get(); return message.createReply(QVariant::fromValue(items)); }); }); return {}; } ProviderInterface::IMD ProviderInterface::Metadata(QString const& item_id, QList const& keys) { queue_request([item_id, keys](shared_ptr const& account, Context const& ctx, QDBusMessage const& message) { auto f = account->provider().metadata(item_id.toStdString(), to_vector(keys), ctx); return f.then( EXEC_IN_MAIN [account, message](decltype(f) f) -> QDBusMessage { auto item = f.get(); return message.createReply(QVariant::fromValue(item)); }); }); return {}; } ProviderInterface::IMD ProviderInterface::CreateFolder(QString const& parent_id, QString const& name, QList const& keys) { queue_request([parent_id, name, keys](shared_ptr const& account, Context const& ctx, QDBusMessage const& message) { auto f = account->provider().create_folder( parent_id.toStdString(), name.toStdString(), to_vector(keys), ctx); return f.then( EXEC_IN_MAIN [account, message](decltype(f) f) -> QDBusMessage { auto item = f.get(); return message.createReply(QVariant::fromValue(item)); }); }); return {}; } QString ProviderInterface::CreateFile(QString const& parent_id, QString const& name, int64_t size, QString const& content_type, bool allow_overwrite, QList const& keys, QDBusUnixFileDescriptor& /*file_descriptor*/) { queue_request([parent_id, name, size, content_type, allow_overwrite, keys](shared_ptr const& account, Context const& ctx, QDBusMessage const& message) { auto f = account->provider().create_file( parent_id.toStdString(), name.toStdString(), size, content_type.toStdString(), allow_overwrite, to_vector(keys), ctx); return f.then( EXEC_IN_MAIN [account, message](decltype(f) f) -> QDBusMessage { auto job = f.get(); job->p_->set_activity(account->inactivity_timer()); auto upload_id = QString::fromStdString(job->upload_id()); QDBusUnixFileDescriptor file_desc; int fd = job->p_->take_write_socket(); file_desc.setFileDescriptor(fd); close(fd); account->jobs().add_upload(message.service(), std::move(job)); return message.createReply({ QVariant(upload_id), QVariant::fromValue(file_desc), }); }); }); return ""; } QString ProviderInterface::Update(QString const& item_id, int64_t size, QString const& old_etag, QList const& keys, QDBusUnixFileDescriptor& /*file_descriptor*/) { queue_request([item_id, size, old_etag, keys](shared_ptr const& account, Context const& ctx, QDBusMessage const& message) { auto f = account->provider().update( item_id.toStdString(), size, old_etag.toStdString(), to_vector(keys), ctx); return f.then( EXEC_IN_MAIN [account, message](decltype(f) f) -> QDBusMessage { auto job = f.get(); job->p_->set_activity(account->inactivity_timer()); auto upload_id = QString::fromStdString(job->upload_id()); QDBusUnixFileDescriptor file_desc; int fd = job->p_->take_write_socket(); file_desc.setFileDescriptor(fd); close(fd); account->jobs().add_upload(message.service(), std::move(job)); return message.createReply({ QVariant(upload_id), QVariant::fromValue(file_desc), }); }); }); return ""; } ProviderInterface::IMD ProviderInterface::FinishUpload(QString const& upload_id) { queue_request([upload_id](shared_ptr const& account, Context const& /*ctx*/, QDBusMessage const& message) { // FIXME: removing the job at this point means we can't // cancel during finish(). // Throws if job is not available auto job = account->jobs().remove_upload(message.service(), upload_id.toStdString()); auto f = job->p_->finish(*job); return f.then( EXEC_IN_MAIN [account, message, job](decltype(f) f) -> QDBusMessage { auto item = f.get(); return message.createReply(QVariant::fromValue(item)); }); }); return {}; } void ProviderInterface::CancelUpload(QString const& upload_id) { queue_request([upload_id](shared_ptr const& account, Context const& /*ctx*/, QDBusMessage const& message) { // Throws if job is not available auto job = account->jobs().remove_upload(message.service(), upload_id.toStdString()); auto f = job->p_->cancel(*job); return f.then( EXEC_IN_MAIN [account, message, job](decltype(f) f) -> QDBusMessage { f.get(); return message.createReply(); }); }); } QString ProviderInterface::Download(QString const& item_id, QString const& match_etag, QDBusUnixFileDescriptor& /*file_descriptor*/) { queue_request([item_id, match_etag](shared_ptr const& account, Context const& ctx, QDBusMessage const& message) { auto f = account->provider().download( item_id.toStdString(), match_etag.toStdString(), ctx); return f.then( EXEC_IN_MAIN [account, message](decltype(f) f) -> QDBusMessage { auto job = f.get(); job->p_->set_activity(account->inactivity_timer()); auto download_id = QString::fromStdString(job->download_id()); QDBusUnixFileDescriptor file_desc; int fd = job->p_->take_read_socket(); file_desc.setFileDescriptor(fd); close(fd); account->jobs().add_download(message.service(), std::move(job)); return message.createReply({ QVariant(download_id), QVariant::fromValue(file_desc), }); }); }); return ""; } void ProviderInterface::FinishDownload(QString const& download_id) { queue_request([download_id](shared_ptr const& account, Context const& /*ctx*/, QDBusMessage const& message) { // FIXME: removing the job at this point means we can't // cancel during finish(). // Throws if job is not available auto job = account->jobs().remove_download(message.service(), download_id.toStdString()); auto f = job->p_->finish(*job); return f.then( EXEC_IN_MAIN [account, message, job](decltype(f) f) -> QDBusMessage { f.get(); return message.createReply(); }); }); } void ProviderInterface::Delete(QString const& item_id) { queue_request([item_id](shared_ptr const& account, Context const& ctx, QDBusMessage const& message) { auto f = account->provider().delete_item( item_id.toStdString(), ctx); return f.then( EXEC_IN_MAIN [account, message](decltype(f) f) -> QDBusMessage { f.get(); return message.createReply(); }); }); } ProviderInterface::IMD ProviderInterface::Move(QString const& item_id, QString const& new_parent_id, QString const& new_name, QList const& keys) { queue_request([item_id, new_parent_id, new_name, keys](shared_ptr const& account, Context const& ctx, QDBusMessage const& message) { auto f = account->provider().move( item_id.toStdString(), new_parent_id.toStdString(), new_name.toStdString(), to_vector(keys), ctx); return f.then( EXEC_IN_MAIN [account, message](decltype(f) f) -> QDBusMessage { auto item = f.get(); return message.createReply(QVariant::fromValue(item)); }); }); return {}; } ProviderInterface::IMD ProviderInterface::Copy(QString const& item_id, QString const& new_parent_id, QString const& new_name, QList const& keys) { queue_request([item_id, new_parent_id, new_name, keys](shared_ptr const& account, Context const& ctx, QDBusMessage const& message) { auto f = account->provider().copy( item_id.toStdString(), new_parent_id.toStdString(), new_name.toStdString(), to_vector(keys), ctx); return f.then( EXEC_IN_MAIN [account, message](decltype(f) f) -> QDBusMessage { auto item = f.get(); return message.createReply(QVariant::fromValue(item)); }); }); return {}; } } } } } lomiri-storage-framework-0.5.0/src/provider/internal/ServerImpl.cpp000066400000000000000000000144311521521330000254700ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #include #include #include #include #include #include #include #include "provideradaptor.h" #include using namespace std; using lomiri::storage::internal::EnvVars; using lomiri::storage::internal::InactivityTimer; namespace lomiri { namespace storage { namespace provider { namespace internal { ServerImpl::ServerImpl(ServerBase* server, string const& bus_name, string const& account_service_id) : server_(server) , bus_name_(bus_name) , service_id_(account_service_id) , trace_message_handler_("storage_provider") { qRegisterMetaType(); qDBusRegisterMetaType(); qDBusRegisterMetaType>(); } ServerImpl::~ServerImpl() = default; void ServerImpl::init(int& argc, char **argv, QDBusConnection *bus) { if (bus) { bus_.reset(new QDBusConnection(*bus)); } else { // Only initialise QCoreApplication if we haven't been passed // in an existing bus connection. app_.reset(new QCoreApplication(argc, argv)); bus_.reset(new QDBusConnection(QDBusConnection::sessionBus())); } int const timeout = EnvVars::provider_timeout_ms(); inactivity_timer_ = make_shared(timeout); connect(inactivity_timer_.get(), &InactivityTimer::timeout, this, &ServerImpl::on_timeout); dbus_peer_ = make_shared(*bus_); #ifdef SF_SUPPORTS_EXECUTORS // Ensure the executor is instantiated in the main thread. MainLoopExecutor::instance(); #endif if (service_id_.empty()) { // If we have an empty service ID, create a single instance of // the provider which doesn't interact with online-accounts. add_account(nullptr); register_bus_name(); } else { // Otherwise use online-accounts to discover all accounts // providing the service ID. manager_.reset(new OnlineAccounts::Manager("", *bus_)); connect(manager_.get(), &OnlineAccounts::Manager::ready, this, &ServerImpl::on_account_manager_ready); connect(manager_.get(), &OnlineAccounts::Manager::accountAvailable, this, &ServerImpl::on_account_available); } } int ServerImpl::run() { return app_->exec(); } void ServerImpl::register_bus_name() { if (!bus_->registerService(QString::fromStdString(bus_name_))) { QString msg = "Could not acquire bus name: " + QString::fromStdString(bus_name_); QString last_error = bus_->lastError().message(); if (!last_error.isEmpty()) { msg += ": " + last_error; } qCritical().noquote() << msg; app_->exit(1); return; } // TODO: claim bus name qDebug() << "Bus unique name:" << bus_->baseService(); } void ServerImpl::add_account(OnlineAccounts::Account* account) { OnlineAccounts::AccountId account_id = 0; shared_ptr account_data; if (account) { account_id = account->id(); // Ignore if we already have access to the account if (interfaces_.find(account_id) != interfaces_.end()) { return; } qDebug() << "Found account" << account->id() << "for service" << account->serviceId(); account_data = make_shared( server_->make_provider(), dbus_peer_, inactivity_timer_, *bus_, account); } else { account_data = make_shared( server_->make_provider(), dbus_peer_, inactivity_timer_, *bus_); } unique_ptr iface( new ProviderInterface(account_data)); // this instance is managed by Qt's parent/child memory management new ProviderAdaptor(iface.get()); bus_->registerObject(QStringLiteral("/provider/%1").arg(account_id), iface.get()); interfaces_.emplace(account_id, std::move(iface)); // watch for account disable signals. if (account) { connect(account, &OnlineAccounts::Account::disabled, this, &ServerImpl::on_account_disabled); } Q_EMIT accountAdded(); } void ServerImpl::remove_account(OnlineAccounts::Account* account) { // Ignore if we don't know about this account if (interfaces_.find(account->id()) == interfaces_.end()) { return; } qDebug() << "Disabled account" << account->id() << "for service" << account->serviceId(); bus_->unregisterObject(QStringLiteral("/provider/%1").arg(account->id())); interfaces_.erase(account->id()); Q_EMIT accountRemoved(); } void ServerImpl::on_account_manager_ready() { for (const auto& account : manager_->availableAccounts(QString::fromStdString(service_id_))) { add_account(account); } register_bus_name(); } void ServerImpl::on_account_available(OnlineAccounts::Account* account) { // Or if the service ID doesn't match if (account->serviceId() != QString::fromStdString(service_id_)) { return; } add_account(account); } void ServerImpl::on_account_disabled() { auto account = static_cast(sender()); remove_account(account); } void ServerImpl::on_timeout() { int const timeout = EnvVars::provider_timeout_ms(); qInfo() << "Exiting after" << timeout << "ms of idle time"; app_->quit(); } } } } } lomiri-storage-framework-0.5.0/src/provider/internal/TempfileUploadJobImpl.cpp000066400000000000000000000052601521521330000275670ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #include #include #include #include using namespace std; namespace lomiri { namespace storage { namespace provider { namespace internal { TempfileUploadJobImpl::TempfileUploadJobImpl(std::string const& upload_id) : UploadJobImpl(upload_id) { } TempfileUploadJobImpl::~TempfileUploadJobImpl() = default; void TempfileUploadJobImpl::complete_init() { tmpfile_.reset(new QTemporaryFile()); reader_.reset(new QLocalSocket()); assert(tmpfile_->open()); reader_->setSocketDescriptor( read_socket_, QLocalSocket::ConnectedState, QIODevice::ReadOnly); read_socket_ = -1; connect(reader_.get(), &QIODevice::readyRead, this, &TempfileUploadJobImpl::on_ready_read); connect(reader_.get(), &QIODevice::readChannelFinished, this, &TempfileUploadJobImpl::on_read_channel_finished); } std::string TempfileUploadJobImpl::file_name() const { if (!tmpfile_) { return ""; } return tmpfile_->fileName().toStdString(); } void TempfileUploadJobImpl::drain() { while (true) { if (!tmpfile_->isOpen()) { break; } if (!reader_->waitForReadyRead(0)) { // Nothing was available to read: is the read channel still open? if (tmpfile_->isOpen()) { throw LogicException("Socket not closed"); } } } } void TempfileUploadJobImpl::on_ready_read() { char buffer[4096]; while (reader_->bytesAvailable() > 0) { qint64 n_read = reader_->read(buffer, sizeof(buffer)); if (n_read > 0) { qint64 n_written = tmpfile_->write(buffer, n_read); assert(n_written == n_read); } } } void TempfileUploadJobImpl::on_read_channel_finished() { // drain the socket and close the tempfile. on_ready_read(); tmpfile_->close(); } } } } } lomiri-storage-framework-0.5.0/src/provider/internal/TestServerImpl.cpp000066400000000000000000000061511521521330000263300ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #include #include #include #include #include #include #include #include #include #include "provideradaptor.h" #include #include using namespace std; using lomiri::storage::internal::InactivityTimer; namespace { constexpr int TIMEOUT = 30000; } namespace lomiri { namespace storage { namespace provider { namespace internal { TestServerImpl::TestServerImpl(shared_ptr const& provider, OnlineAccounts::Account* account, QDBusConnection const& connection, string const& object_path) : connection_(connection), object_path_(object_path), inactivity_timer_(make_shared(TIMEOUT)) { qRegisterMetaType(); qDBusRegisterMetaType(); qDBusRegisterMetaType>(); auto peer_cache = make_shared(connection_); shared_ptr account_data; if (account) { account_data = make_shared( provider, peer_cache, inactivity_timer_, connection_, account); } else { account_data = make_shared( provider, peer_cache, inactivity_timer_, connection_); } interface_.reset(new ProviderInterface(account_data)); new ProviderAdaptor(interface_.get()); if (!connection_.registerObject(QString::fromStdString(object_path_), interface_.get())) { string msg = "Could not register provider on connection: " + connection_.lastError().message().toStdString(); throw ResourceException(msg, int(connection_.lastError().type())); } } TestServerImpl::~TestServerImpl() { connection_.unregisterObject(QString::fromStdString(object_path_)); } QDBusConnection const& TestServerImpl::connection() const { return connection_; } string const& TestServerImpl::object_path() const { return object_path_; } } } } } lomiri-storage-framework-0.5.0/src/provider/internal/UploadJobImpl.cpp000066400000000000000000000100701521521330000260740ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #include #include #include #include #include #include #include #include #include #include using namespace std; using namespace lomiri::storage::internal; using lomiri::storage::internal::ActivityNotifier; using lomiri::storage::internal::InactivityTimer; namespace lomiri { namespace storage { namespace provider { namespace internal { UploadJobImpl::UploadJobImpl(std::string const& upload_id) : upload_id_(upload_id) { int socks[2]; if (socketpair(AF_UNIX, SOCK_STREAM, 0, socks) < 0) { int error_code = errno; string msg = "could not create socketpair: " + safe_strerror(error_code); throw ResourceException(msg, error_code); } read_socket_ = socks[0]; write_socket_ = socks[1]; #if 0 // TODO: We should be able to half-close the write channel of the read socket, and the read channel of // the write socket. But, if we do, QLocalSocket indicates that everything was closed, which causes // failures on the client side. We suspect a QLocalSocket bug -- need to investigate. if (shutdown(read_socket_, SHUT_WR) < 0) { int error_code = errno; string msg = "could not shut down write channel on read socket: " + safe_strerror(error_code); throw ResourceException(msg, error_code); } if (shutdown(write_socket_, SHUT_RD) < 0) { int error_code = errno; string msg = "Could not shut down read channel on write socket" + safe_strerror(error_code); throw ResourceException(msg, error_code); } #endif } UploadJobImpl::~UploadJobImpl() { if (read_socket_ >= 0) { close(read_socket_); } if (write_socket_ >= 0) { close(write_socket_); } } void UploadJobImpl::complete_init() { } string const& UploadJobImpl::upload_id() const { return upload_id_; } int UploadJobImpl::read_socket() const { return read_socket_; } int UploadJobImpl::take_write_socket() { assert(write_socket_ >= 0); int sock = write_socket_; write_socket_ = -1; return sock; } void UploadJobImpl::set_activity(std::shared_ptr const& inactivity_timer) { activity_ = ActivityNotifier(inactivity_timer); } void UploadJobImpl::report_error(exception_ptr p) { if (read_socket_ >= 0) { close(read_socket_); read_socket_ = -1; } lock_guard guard(completion_lock_); completed_ = true; // Convert std::exception_ptr to boost::exception_ptr try { rethrow_exception(p); } catch (StorageException const& e) { completion_promise_.set_exception(e); } catch (...) { completion_promise_.set_exception(boost::current_exception()); } } boost::future UploadJobImpl::finish(UploadJob& job) { lock_guard guard(completion_lock_); if (completed_) { return completion_promise_.get_future(); } return job.finish(); } boost::future UploadJobImpl::cancel(UploadJob& job) { lock_guard guard(completion_lock_); if (completed_) { return boost::make_ready_future(); } return job.cancel(); } } } } } lomiri-storage-framework-0.5.0/src/provider/internal/dbusmarshal.cpp000066400000000000000000000056341521521330000257120ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #include #include #include #include using namespace std; namespace lomiri { namespace storage { namespace provider { namespace { QDBusVariant to_qdbus_variant(MetadataValue const& v) { switch (v.which()) { case 0: return QDBusVariant(QString::fromStdString(boost::get(v))); case 1: return QDBusVariant(qlonglong(boost::get(v))); default: abort(); // Impossible. // LCOV_EXCL_LINE } } } // namespace QDBusArgument& operator<<(QDBusArgument& argument, Item const& item) { argument.beginStructure(); argument << QString::fromStdString(item.item_id); { argument.beginArray(qMetaTypeId()); for (auto const& id : item.parent_ids) { argument << QString::fromStdString(id); } argument.endArray(); } argument << QString::fromStdString(item.name); argument << QString::fromStdString(item.etag); argument << static_cast(item.type); { argument.beginMap(QVariant::String, qMetaTypeId()); for (auto const& pair : item.metadata) { argument.beginMapEntry(); argument << QString::fromStdString(pair.first) << to_qdbus_variant(pair.second); argument.endMapEntry(); } argument.endMap(); } argument.endStructure(); return argument; } QDBusArgument const& operator>>(QDBusArgument const&, Item&) { // We don't expect to ever have to unmarshal anything, only marshal it. qFatal("unexpected call to operator>>(QDBusArgument const&, Item&)"); // LCOV_EXCL_LINE } QDBusArgument& operator<<(QDBusArgument& argument, ItemList const& items) { argument.beginArray(qMetaTypeId()); for (auto const& item : items) { argument << item; } argument.endArray(); return argument; } QDBusArgument const& operator>>(QDBusArgument const&, ItemList&) { // We don't expect to ever have to unmarshal anything, only marshal it. qFatal("unexpected call to operator>>(QDBusArgument const&, ItemList&)"); // LCOV_EXCL_LINE } } } } lomiri-storage-framework-0.5.0/src/provider/lomiri-storage-framework-provider.pc.in000066400000000000000000000004331521521330000305660ustar00rootroot00000000000000Name: @LSF_PROVIDER_NAME@ Description: A library for developing providers for lomiri-storage-framework Version: @PROJECT_VERSION@ Cflags: -I@CMAKE_INSTALL_FULL_INCLUDEDIR@/@LSF_PROVIDER_INCLUDE_NAME@ -DBOOST_THREAD_VERSION=4 Libs: -L@CMAKE_INSTALL_FULL_LIBDIR@ -l@LSF_PROVIDER_NAME@ lomiri-storage-framework-0.5.0/src/provider/testing/000077500000000000000000000000001521521330000225325ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/src/provider/testing/TestServer.cpp000066400000000000000000000030771521521330000253530ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #include #include #include #include using namespace std; namespace lomiri { namespace storage { namespace provider { namespace testing { TestServer::TestServer(shared_ptr const& provider, OnlineAccounts::Account* account, QDBusConnection const& connection, string const& object_path) : p_(new internal::TestServerImpl(provider, account, connection, object_path)) { } TestServer::~TestServer() = default; QDBusConnection const& TestServer::connection() const { return p_->connection(); } string const& TestServer::object_path() const { return p_->object_path(); } } } } } lomiri-storage-framework-0.5.0/src/qt/000077500000000000000000000000001521521330000176475ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/src/qt/Account.cpp000066400000000000000000000057371521521330000217630ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { Account::Account() : p_(make_shared()) { } Account::Account(shared_ptr const& p) : p_(p) { assert(p); } Account::Account(Account const& other) : p_(other.p_) { } Account::Account(Account&& other) : p_(make_shared()) { p_->is_valid_ = false; swap(p_, other.p_); } Account::~Account() = default; Account& Account::operator=(Account const& other) { if (this == &other) { return *this; } p_ = other.p_; return *this; } Account& Account::operator=(Account&& other) { p_->is_valid_ = false; swap(p_, other.p_); return *this; } bool Account::isValid() const { return p_->is_valid_; } QString Account::busName() const { return p_->busName(); } QString Account::objectPath() const { return p_->objectPath(); } QString Account::displayName() const { return p_->displayName(); } QString Account::providerName() const { return p_->providerName(); } QString Account::iconName() const { return p_->iconName(); } ItemListJob* Account::roots(QStringList const& keys) const { return p_->roots(keys); } ItemJob* Account::get(QString const& itemId, QStringList const& keys) const { return p_->get(itemId, keys); } bool Account::operator==(Account const& other) const { return p_->operator==(*other.p_); } bool Account::operator!=(Account const& other) const { return p_->operator!=(*other.p_); } bool Account::operator<(Account const& other) const { return p_->operator<(*other.p_); } bool Account::operator<=(Account const& other) const { return p_->operator<=(*other.p_); } bool Account::operator>(Account const& other) const { return p_->operator>(*other.p_); } bool Account::operator>=(Account const& other) const { return p_->operator>=(*other.p_); } size_t Account::hash() const { return p_->hash(); } // Due to potentially different size of size_t and uint, hash() and qhash() may not return the same value. uint qHash(Account const& acc) { return acc.hash(); } } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/AccountsJob.cpp000066400000000000000000000030311521521330000225620ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include using namespace lomiri::storage::qt; using namespace std; namespace lomiri { namespace storage { namespace qt { AccountsJob::AccountsJob(unique_ptr accounts_job_impl) : p_(move(accounts_job_impl)) { } AccountsJob::~AccountsJob() = default; bool AccountsJob::isValid() const { return p_->isValid(); } AccountsJob::Status AccountsJob::status() const { return p_->status(); } StorageError AccountsJob::error() const { return p_->error(); } QList AccountsJob::accounts() const { return p_->accounts(); } QVariantList AccountsJob::accountsAsVariantList() const { return p_->accountsAsVariantList(); } } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/CMakeLists.txt000066400000000000000000000103701521521330000224100ustar00rootroot00000000000000if(QT_VERSION_MAJOR GREATER_EQUAL 6) set(LSF_CLIENT_NAME lomiri-storage-framework-qt${QT_VERSION_MAJOR}-client-${LSF_CLIENT_API_VERSION}) set(LSF_CLIENT_INCLUDE_NAME ${LSF_CLIENT_NAME}) set(LSF_CLIENT_DEPS_PRIVATE "Qt6Core Qt6Network") else() set(LSF_CLIENT_NAME lomiri-storage-framework-qt-client-${LSF_CLIENT_API_VERSION}) set(LSF_CLIENT_INCLUDE_NAME lomiri-storage-framework-client-${LSF_CLIENT_API_VERSION}) set(LSF_CLIENT_DEPS_PRIVATE "Qt5Core Qt5Network") endif() if(NOT ENABLE_QT6) add_subdirectory(client) endif() set_source_files_properties(${CMAKE_SOURCE_DIR}/data/provider.xml PROPERTIES CLASSNAME ProviderInterface INCLUDE lomiri/storage/internal/dbusmarshal.h ) qt_add_dbus_interface(generated_files ${CMAKE_SOURCE_DIR}/data/provider.xml ProviderInterface ) set_source_files_properties(${CMAKE_SOURCE_DIR}/data/registry.xml PROPERTIES CLASSNAME RegistryInterface INCLUDE lomiri/storage/internal/AccountDetails.h ) qt_add_dbus_interface(generated_files ${CMAKE_SOURCE_DIR}/data/registry.xml RegistryInterface ) set_source_files_properties(${generated_files} dbusmarshal.cpp PROPERTIES COMPILE_FLAGS "-Wno-ctor-dtor-privacy -Wno-missing-field-initializers" GENERATED TRUE ) # Sources for remote client V2 library. set(QT_CLIENT_LIB_V2_SRC Account.cpp AccountsJob.cpp Downloader.cpp Item.cpp ItemJob.cpp ItemListJob.cpp Runtime.cpp StorageError.cpp Uploader.cpp VoidJob.cpp internal/AccountImpl.cpp internal/AccountsJobImpl.cpp internal/DownloaderImpl.cpp internal/HandlerBase.cpp internal/ItemImpl.cpp internal/ItemJobImpl.cpp internal/ItemListJobImpl.cpp internal/ListJobImplBase.cpp internal/MultiItemJobImpl.cpp internal/MultiItemListJobImpl.cpp internal/RuntimeImpl.cpp internal/StorageErrorImpl.cpp internal/unmarshal_error.cpp internal/validate.cpp internal/UploaderImpl.cpp internal/VoidJobImpl.cpp ${generated_files} ${CMAKE_SOURCE_DIR}/include/lomiri/storage/qt/Account.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/qt/AccountsJob.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/qt/Downloader.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/qt/Item.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/qt/ItemJob.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/qt/ItemListJob.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/qt/Runtime.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/qt/StorageError.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/qt/Uploader.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/qt/VoidJob.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/qt/internal/DownloaderImpl.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/qt/internal/AccountsJobImpl.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/qt/internal/HandlerBase.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/qt/internal/ItemJobImpl.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/qt/internal/ItemListJobImpl.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/qt/internal/ListJobImplBase.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/qt/internal/MultiItemJobImpl.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/qt/internal/MultiItemListJobImpl.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/qt/internal/UploaderImpl.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/qt/internal/StorageErrorImpl.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/qt/internal/VoidJobImpl.h ) add_library(lomiri-storage-framework-qt-client-v2 SHARED ${QT_CLIENT_LIB_V2_SRC} ${generated_files} ) set_target_properties(lomiri-storage-framework-qt-client-v2 PROPERTIES AUTOMOC TRUE LINK_FLAGS "-Wl,--no-undefined" OUTPUT_NAME ${LSF_CLIENT_NAME} SOVERSION ${LSF_CLIENT_SOVERSION} VERSION ${LSF_CLIENT_LIBVERSION} ) target_link_libraries(lomiri-storage-framework-qt-client-v2 lomiri-storage-framework-common-internal Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::DBus Qt${QT_VERSION_MAJOR}::Network ) install( TARGETS lomiri-storage-framework-qt-client-v2 LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) configure_file( lomiri-storage-framework-qt-client.pc.in ${LSF_CLIENT_NAME}.pc ) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/${LSF_CLIENT_NAME}.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig ) lomiri-storage-framework-0.5.0/src/qt/Downloader.cpp000066400000000000000000000043551521521330000224600ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { Downloader::Downloader() = default; Downloader::Downloader(unique_ptr p) : p_(move(p)) { assert(p_); } Downloader::~Downloader() = default; bool Downloader::isValid() const { return p_->isValid(); } Downloader::Status Downloader::status() const { return p_->status(); } StorageError Downloader::error() const { return p_->error(); } Item Downloader::item() const { return p_->item(); } void Downloader::cancel() { p_->cancel(); } void Downloader::close() { p_->close(); } qint64 Downloader::bytesAvailable() const { return p_->bytesAvailable(); } qint64 Downloader::bytesToWrite() const { return p_->bytesToWrite(); } bool Downloader::canReadLine() const { return p_->canReadLine(); } bool Downloader::isSequential() const { return p_->isSequential(); } bool Downloader::waitForBytesWritten(int msecs) { return p_->waitForBytesWritten(msecs); } bool Downloader::waitForReadyRead(int msecs) { return p_->waitForReadyRead(msecs); } qint64 Downloader::readData(char* data, qint64 c) { return p_->readData(data, c); } // LCOV_EXCL_START // Never called by QIODevice because device is opened read-only. qint64 Downloader::writeData(char const* data, qint64 c) { return p_->writeData(data, c); } // LCOV_EXCL_STOP } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/Item.cpp000066400000000000000000000103721521521330000212540ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { Item::Item() : p_(make_shared()) { } Item::Item(shared_ptr const& p) : p_(p) { assert(p); } Item::Item(Item const& other) : p_(other.p_) { } Item::Item(Item&& other) : p_(make_shared()) { p_->is_valid_ = false; swap(p_, other.p_); } Item::~Item() = default; Item& Item::operator=(Item const& other) { if (this == &other) { return *this; } p_ = other.p_; return *this; } Item& Item::operator=(Item&& other) { p_->is_valid_ = false; swap(p_, other.p_); return *this; } bool Item::isValid() const { return p_->is_valid_; } QString Item::itemId() const { return p_->itemId(); } QString Item::name() const { return p_->name(); } Account Item::account() const { return p_->account(); } QString Item::etag() const { return p_->etag(); } Item::Type Item::type() const { return p_->type(); } QVariantMap Item::metadata() const { return p_->metadata(); } qint64 Item::sizeInBytes() const { return p_->sizeInBytes(); } QDateTime Item::lastModifiedTime() const { return p_->lastModifiedTime(); } QStringList Item::parentIds() const { return p_->parentIds(); } ItemListJob* Item::parents(QStringList const& keys) const { return p_->parents(keys); } ItemJob* Item::copy(Item const& newParent, QString const& newName, QStringList const& keys) const { return p_->copy(newParent, newName, keys); } ItemJob* Item::move(Item const& newParent, QString const& newName, QStringList const& keys) const { return p_->move(newParent, newName, keys); } VoidJob* Item::deleteItem() const { return p_->deleteItem(); } Uploader* Item::createUploader(ConflictPolicy policy, qint64 sizeInBytes, QStringList const& keys) const { return p_->createUploader(policy, sizeInBytes, keys); } Downloader* Item::createDownloader(ConflictPolicy policy) const { return p_->createDownloader(policy); } ItemListJob* Item::list(QStringList const& keys) const { return p_->list(keys); } ItemListJob* Item::lookup(QString const& name, QStringList const& keys) const { return p_->lookup(name, keys); } ItemJob* Item::createFolder(QString const& name, QStringList const& keys) const { return p_->createFolder(name, keys); } Uploader* Item::createFile(QString const& name, ConflictPolicy policy, qint64 sizeInBytes, QString const& contentType, QStringList const& keys) const { return p_->createFile(name, policy, sizeInBytes, contentType, keys); } bool Item::operator==(Item const& other) const { return p_->operator==(*other.p_); } bool Item::operator!=(Item const& other) const { return p_->operator!=(*other.p_); } bool Item::operator<(Item const& other) const { return p_->operator<(*other.p_); } bool Item::operator<=(Item const& other) const { return p_->operator<=(*other.p_); } bool Item::operator>(Item const& other) const { return p_->operator>(*other.p_); } bool Item::operator>=(Item const& other) const { return p_->operator>=(*other.p_); } size_t Item::hash() const { return p_->hash(); } // Due to potentially different size of size_t and uint, hash() and qhash() may not return the same value. uint qHash(Item const& i) { return i.hash(); } } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/ItemJob.cpp000066400000000000000000000024751521521330000217140ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include using namespace lomiri::storage::qt; using namespace std; namespace lomiri { namespace storage { namespace qt { ItemJob::ItemJob(unique_ptr p) : p_(move(p)) { } ItemJob::~ItemJob() = default; bool ItemJob::isValid() const { return p_->isValid(); } ItemJob::Status ItemJob::status() const { return p_->status(); } StorageError ItemJob::error() const { return p_->error(); } Item ItemJob::item() const { return p_->item(); } } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/ItemListJob.cpp000066400000000000000000000025531521521330000225450ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include using namespace lomiri::storage::qt; using namespace std; namespace lomiri { namespace storage { namespace qt { ItemListJob::ItemListJob(unique_ptr p) : p_(move(p)) { } ItemListJob::~ItemListJob() = default; bool ItemListJob::isValid() const { return p_->isValid(); } ItemListJob::Status ItemListJob::status() const { return p_->status(); } StorageError ItemListJob::error() const { return p_->error(); } } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/Runtime.cpp000066400000000000000000000036211521521330000220000ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { Runtime::Runtime(QObject* parent) : QObject(parent) , p_(new internal::RuntimeImpl) { } Runtime::Runtime(QDBusConnection const& bus, QObject* parent) : QObject(parent) , p_(new internal::RuntimeImpl(bus)) { } Runtime::~Runtime() = default; bool Runtime::isValid() const { return p_->isValid(); } StorageError Runtime::error() const { return p_->error(); } QDBusConnection Runtime::connection() const { return p_->connection(); } StorageError Runtime::shutdown() { return p_->shutdown(); } AccountsJob* Runtime::accounts() const { return p_->accounts(); } Account Runtime::make_test_account(QString const& bus_name, QString const& object_path, quint32 id, QString const& service_id, QString const& display_name) const { return p_->make_test_account(bus_name, object_path, id, service_id, display_name); } } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/StorageError.cpp000066400000000000000000000037571521521330000230050ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { StorageError::StorageError() : p_(new internal::StorageErrorImpl) { } StorageError::StorageError(StorageError const& other) : p_(new internal::StorageErrorImpl(*other.p_)) { } StorageError::StorageError(StorageError&&) = default; StorageError::StorageError(unique_ptr p) : p_(move(p)) { } StorageError::~StorageError() = default; StorageError& StorageError::operator=(StorageError const& other) { *p_ = *other.p_; return *this; } StorageError& StorageError::operator=(StorageError&&) = default; StorageError::Type StorageError::type() const { return p_->type(); } QString StorageError::name() const { return p_->name(); } QString StorageError::message() const { return p_->message(); } QString StorageError::errorString() const { return p_->errorString(); } QString StorageError::itemId() const { return p_->itemId(); } QString StorageError::itemName() const { return p_->itemName(); } int StorageError::errorCode() const { return p_->errorCode(); } } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/Uploader.cpp000066400000000000000000000045221521521330000221310ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { Uploader::Uploader() = default; Uploader::Uploader(unique_ptr p) : p_(move(p)) { assert(p_); } Uploader::~Uploader() = default; bool Uploader::isValid() const { return p_->isValid(); } Uploader::Status Uploader::status() const { return p_->status(); } StorageError Uploader::error() const { return p_->error(); } Item::ConflictPolicy Uploader::policy() const { return p_->policy(); } qint64 Uploader::sizeInBytes() const { return p_->sizeInBytes(); } Item Uploader::item() const { return p_->item(); } void Uploader::cancel() { p_->cancel(); } void Uploader::close() { p_->close(); } qint64 Uploader::bytesAvailable() const { return p_->bytesAvailable(); } qint64 Uploader::bytesToWrite() const { return p_->bytesToWrite(); } bool Uploader::canReadLine() const { return p_->canReadLine(); } bool Uploader::isSequential() const { return p_->isSequential(); } bool Uploader::waitForBytesWritten(int msecs) { return p_->waitForBytesWritten(msecs); } bool Uploader::waitForReadyRead(int msecs) { return p_->waitForReadyRead(msecs); } // LCOV_EXCL_START // Never called by QIODevice because device is opened write-only. qint64 Uploader::readData(char* data, qint64 c) { return p_->readData(data, c); } // LCOV_EXCL_STOP qint64 Uploader::writeData(char const* data, qint64 c) { return p_->writeData(data, c); } } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/VoidJob.cpp000066400000000000000000000024061521521330000217110ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include using namespace lomiri::storage::qt; using namespace std; namespace lomiri { namespace storage { namespace qt { VoidJob::VoidJob(unique_ptr p) : p_(move(p)) { } VoidJob::~VoidJob() = default; bool VoidJob::isValid() const { return p_->isValid(); } VoidJob::Status VoidJob::status() const { return p_->status(); } StorageError VoidJob::error() const { return p_->error(); } } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/000077500000000000000000000000001521521330000211255ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/src/qt/client/Account.cpp000066400000000000000000000027401521521330000232300ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace client { Account::Account(internal::AccountBase* p) : p_(p) { assert(p != nullptr); } Account::~Account() = default; shared_ptr Account::runtime() const { return p_->runtime(); } QString Account::owner() const { return p_->owner(); } QString Account::owner_id() const { return p_->owner_id(); } QString Account::description() const { return p_->description(); } QFuture>> Account::roots() const { return p_->roots(); } } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/CMakeLists.txt000066400000000000000000000070461521521330000236740ustar00rootroot00000000000000# Create an OBJECT library for the files that are used by both # local and remote client libraries, so we don't compile them twice. add_library(qt-client-lib-common OBJECT Account.cpp Downloader.cpp Exceptions.cpp File.cpp Folder.cpp Item.cpp Root.cpp Runtime.cpp Uploader.cpp internal/AccountBase.cpp internal/DownloaderBase.cpp internal/FileBase.cpp internal/FolderBase.cpp internal/ItemBase.cpp internal/RootBase.cpp internal/RuntimeBase.cpp internal/UploaderBase.cpp ${CMAKE_SOURCE_DIR}/include/lomiri/storage/qt/client/internal/DownloaderBase.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/qt/client/internal/UploaderBase.h ) set_target_properties(qt-client-lib-common PROPERTIES AUTOMOC TRUE POSITION_INDEPENDENT_CODE TRUE ) target_link_libraries(qt-client-lib-common PUBLIC Qt${QT_VERSION_MAJOR}::Concurrent Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::DBus Qt${QT_VERSION_MAJOR}::Network ${Boost_LIBRARIES} PkgConfig::GLIB_DEPS ) # We build both local and remote client libraries here instead # of creating OBJECT libraries for them because cmake 3.0.2 (which # is in the Vivid overlay) cannot handle .moc files in OBJECT libraries. # Descend into the child directories first. The CMakeLists.txt files # there set QT_CLIENT_LIB_LOCAL_SRC and QT_CLIENT_LIB_REMOTE_SRC. add_subdirectory(internal) # Build the local loopback client library add_library(lomiri-storage-framework-qt-local-client SHARED ${QT_CLIENT_LIB_LOCAL_SRC} ) set_target_properties(lomiri-storage-framework-qt-local-client PROPERTIES AUTOMOC TRUE LINK_FLAGS "-Wl,--no-undefined" OUTPUT_NAME "lomiri-storage-framework-qt-local-client-1" SOVERSION 0 VERSION 0.0.1 ) target_link_libraries(lomiri-storage-framework-qt-local-client qt-client-lib-common lomiri-storage-framework-common-internal PkgConfig::LIBLOMIRI_API_DEPS ) install( TARGETS lomiri-storage-framework-qt-local-client LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) configure_file( lomiri-storage-framework-qt-local-client.pc.in lomiri-storage-framework-qt-local-client-1.pc ) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/lomiri-storage-framework-qt-local-client-1.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig ) # Build the remote client library set_source_files_properties(${CMAKE_SOURCE_DIR}/data/provider.xml PROPERTIES CLASSNAME ProviderInterface INCLUDE lomiri/storage/internal/dbusmarshal.h ) qt_add_dbus_interface(generated_files ${CMAKE_SOURCE_DIR}/data/provider.xml ProviderInterface ) set_source_files_properties(${generated_files} PROPERTIES COMPILE_FLAGS "-Wno-ctor-dtor-privacy -Wno-missing-field-initializers" GENERATED TRUE ) add_library(lomiri-storage-framework-qt-client SHARED ${QT_CLIENT_LIB_REMOTE_SRC} ${generated_files} ) set_target_properties(lomiri-storage-framework-qt-client PROPERTIES AUTOMOC TRUE LINK_FLAGS "-Wl,--no-undefined" OUTPUT_NAME "lomiri-storage-framework-qt-client-1" SOVERSION 0 VERSION 0.0.1 ) target_link_libraries(lomiri-storage-framework-qt-client qt-client-lib-common lomiri-storage-framework-common-internal PkgConfig::ONLINEACCOUNTS_DEPS ) install( TARGETS lomiri-storage-framework-qt-client LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) configure_file( lomiri-storage-framework-qt-client-1.pc.in lomiri-storage-framework-qt-client-1.pc ) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/lomiri-storage-framework-qt-client-1.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig ) lomiri-storage-framework-0.5.0/src/qt/client/Downloader.cpp000066400000000000000000000025711521521330000237340ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include namespace lomiri { namespace storage { namespace qt { namespace client { Downloader::Downloader(internal::DownloaderBase* p) : p_(p) { } Downloader::~Downloader() = default; std::shared_ptr Downloader::file() const { return p_->file(); } std::shared_ptr Downloader::socket() const { return p_->socket(); } QFuture Downloader::finish_download() { return p_->finish_download(); } QFuture Downloader::cancel() { return p_->cancel(); } } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/Exceptions.cpp000066400000000000000000000146251521521330000237620ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace client { StorageException::StorageException(char const* exception_name, QString const& error_message) : what_string_(string(exception_name) + ": " + error_message.toStdString()) , error_message_(error_message) { } StorageException::~StorageException() = default; char const* StorageException::what() const noexcept { return what_string_.c_str(); } QString StorageException::error_message() const { return error_message_; } LocalCommsException::LocalCommsException(QString const& error_message) : StorageException("LocalCommsException", error_message) { } LocalCommsException::~LocalCommsException() = default; LocalCommsException* LocalCommsException::clone() const { return new LocalCommsException(*this); } void LocalCommsException::raise() const { throw *this; } RemoteCommsException::RemoteCommsException(QString const& error_message) : StorageException("RemoteCommsException", error_message) { } RemoteCommsException::~RemoteCommsException() = default; RemoteCommsException* RemoteCommsException::clone() const { return new RemoteCommsException(*this); } void RemoteCommsException::raise() const { throw *this; } DeletedException::DeletedException(QString const& error_message, QString const& identity) : StorageException("DeletedException", error_message) , identity_(identity) { } DeletedException::~DeletedException() = default; DeletedException* DeletedException::clone() const { return new DeletedException(*this); } void DeletedException::raise() const { throw *this; } QString DeletedException::native_identity() const { return identity_; } RuntimeDestroyedException::RuntimeDestroyedException(QString const& method) : StorageException("RuntimeDestroyedException", method + ": runtime was destroyed previously") { } RuntimeDestroyedException::~RuntimeDestroyedException() = default; RuntimeDestroyedException* RuntimeDestroyedException::clone() const { return new RuntimeDestroyedException(*this); } void RuntimeDestroyedException::raise() const { throw *this; } NotExistsException::NotExistsException(QString const& error_message, QString const& key) : StorageException("NotExistsException", error_message) , key_(key) { } NotExistsException::~NotExistsException() = default; NotExistsException* NotExistsException::clone() const { return new NotExistsException(*this); } void NotExistsException::raise() const { throw *this; } QString NotExistsException::key() const { return key_; } ExistsException::ExistsException(QString const& error_message, QString const& identity, QString const& name) : StorageException("ExistsException", error_message) , identity_(identity) , name_(name) { } ExistsException::~ExistsException() = default; ExistsException* ExistsException::clone() const { return new ExistsException(*this); } void ExistsException::raise() const { throw *this; } QString ExistsException::native_identity() const { return identity_; } QString ExistsException::name() const { return name_; } ConflictException::ConflictException(QString const& error_message) : StorageException("ConflictException", error_message) { } ConflictException::~ConflictException() = default; ConflictException* ConflictException::clone() const { return new ConflictException(*this); } void ConflictException::raise() const { throw *this; } PermissionException::PermissionException(QString const& error_message) : StorageException("PermissionException", error_message) { } PermissionException::~PermissionException() = default; PermissionException* PermissionException::clone() const { return new PermissionException(*this); } void PermissionException::raise() const { throw *this; } QuotaException::QuotaException(QString const& error_message) : StorageException("QuotaException", error_message) { } QuotaException::~QuotaException() = default; QuotaException* QuotaException::clone() const { return new QuotaException(*this); } void QuotaException::raise() const { throw *this; } CancelledException::CancelledException(QString const& error_message) : StorageException("CancelledException", error_message) { } CancelledException::~CancelledException() = default; CancelledException* CancelledException::clone() const { return new CancelledException(*this); } void CancelledException::raise() const { throw *this; } LogicException::LogicException(QString const& error_message) : StorageException("LogicException", error_message) { } LogicException::~LogicException() = default; LogicException* LogicException::clone() const { return new LogicException(*this); } void LogicException::raise() const { throw *this; } InvalidArgumentException::InvalidArgumentException(QString const& error_message) : StorageException("InvalidArgumentException", error_message) { } InvalidArgumentException::~InvalidArgumentException() = default; InvalidArgumentException* InvalidArgumentException::clone() const { return new InvalidArgumentException(*this); } void InvalidArgumentException::raise() const { throw *this; } ResourceException::ResourceException(QString const& error_message, int error_code) : StorageException("ResourceException", error_message) , error_code_(error_code) { } ResourceException::~ResourceException() = default; ResourceException* ResourceException::clone() const { return new ResourceException(*this); } void ResourceException::raise() const { throw *this; } int ResourceException::error_code() const noexcept { return error_code_; } } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/File.cpp000066400000000000000000000027051521521330000225140ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace client { using namespace internal; File::File(FileBase* p) : Item(p) { } File::~File() = default; int64_t File::size() const { return dynamic_cast(p_.get())->size(); } QFuture> File::create_uploader(ConflictPolicy policy, int64_t size) { return dynamic_cast(p_.get())->create_uploader(policy, size); } QFuture> File::create_downloader() { return dynamic_cast(p_.get())->create_downloader(); } } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/Folder.cpp000066400000000000000000000031661521521330000230520ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include namespace lomiri { namespace storage { namespace qt { namespace client { using namespace internal; using namespace std; Folder::Folder(FolderBase* p) : Item(p) { } Folder::~Folder() = default; QFuture> Folder::list() const { return dynamic_cast(p_.get())->list(); } QFuture> Folder::lookup(QString const& name) const { return dynamic_cast(p_.get())->lookup(name); } QFuture Folder::create_folder(QString const& name) { return dynamic_cast(p_.get())->create_folder(name); } QFuture> Folder::create_file(QString const& name, int64_t size) { return dynamic_cast(p_.get())->create_file(name, size); } } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/Item.cpp000066400000000000000000000044771521521330000225430ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace client { Item::Item(internal::ItemBase* p) : p_(p) { assert(p != nullptr); } Item::~Item() = default; QString Item::native_identity() const { return p_->native_identity(); } QString Item::name() const { return p_->name(); } shared_ptr Item::root() const { return p_->root(); } ItemType Item::type() const { return p_->type(); } QString Item::etag() const { return p_->etag(); } QVariantMap Item::metadata() const { return p_->metadata(); } QDateTime Item::last_modified_time() const { return p_->last_modified_time(); } QFuture>> Item::parents() const { return p_->parents(); } QVector Item::parent_ids() const { return p_->parent_ids(); } QFuture Item::copy(std::shared_ptr const& new_parent, QString const& new_name) { return p_->copy(new_parent, new_name); } QFuture Item::move(std::shared_ptr const& new_parent, QString const& new_name) { return p_->move(new_parent, new_name); } QFuture Item::delete_item() { return p_->delete_item(); } QDateTime Item::creation_time() const { return p_->creation_time(); } MetadataMap Item::native_metadata() const { return p_->native_metadata(); } bool Item::equal_to(Item::SPtr const& other) const noexcept { return p_->equal_to(*other->p_); } } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/Root.cpp000066400000000000000000000030371521521330000225570ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include namespace lomiri { namespace storage { namespace qt { namespace client { using namespace internal; using namespace std; Root::Root(RootBase* p) : Folder(p) { } Root::~Root() = default; shared_ptr Root::account() const { return dynamic_cast(p_.get())->account(); } QFuture Root::free_space_bytes() const { return dynamic_cast(p_.get())->free_space_bytes(); } QFuture Root::used_space_bytes() const { return dynamic_cast(p_.get())->used_space_bytes(); } QFuture Root::get(QString native_identity) const { return dynamic_cast(p_.get())->get(native_identity); } } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/Runtime.cpp000066400000000000000000000032651521521330000232620ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace client { // Runtime::SPtr Runtime::create() is defined by local_client and remote_client, respectively. Runtime::Runtime(internal::RuntimeBase* p) : p_(p) { assert(p != nullptr); } Runtime::~Runtime() { shutdown(); } Runtime::SPtr Runtime::create() { return Runtime::create(QDBusConnection::sessionBus()); } void Runtime::shutdown() { p_->shutdown(); } QFuture>> Runtime::accounts() { return p_->accounts(); } shared_ptr Runtime::make_test_account(QString const& bus_name, QString const& object_path) { return p_->make_test_account(bus_name, object_path); } } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/Uploader.cpp000066400000000000000000000026631521521330000234130ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include using namespace std; class QLocalSocket; namespace lomiri { namespace storage { namespace qt { namespace client { Uploader::Uploader(internal::UploaderBase* p) : p_(p) { } Uploader::~Uploader() = default; std::shared_ptr Uploader::socket() const { return p_->socket(); } int64_t Uploader::size() const { return p_->size(); } QFuture> Uploader::finish_upload() { return p_->finish_upload(); } QFuture Uploader::cancel() { return p_->cancel(); } } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/internal/000077500000000000000000000000001521521330000227415ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/src/qt/client/internal/AccountBase.cpp000066400000000000000000000033171521521330000256400ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { AccountBase::AccountBase(weak_ptr const& runtime) : runtime_(runtime) { assert(runtime.lock()); } shared_ptr AccountBase::runtime() const { if (auto runtime = runtime_.lock()) { auto runtime_base = runtime->p_; if (runtime_base->destroyed_) { throw RuntimeDestroyedException("Account::runtime()"); } return runtime; } throw RuntimeDestroyedException("Account::runtime()"); } void AccountBase::set_public_instance(weak_ptr const& p) { public_instance_ = p; } } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/internal/CMakeLists.txt000066400000000000000000000003141521521330000254770ustar00rootroot00000000000000add_subdirectory(local_client) add_subdirectory(remote_client) SET(QT_CLIENT_LIB_REMOTE_SRC ${QT_CLIENT_LIB_REMOTE_SRC} PARENT_SCOPE) SET(QT_CLIENT_LIB_LOCAL_SRC ${QT_CLIENT_LIB_LOCAL_SRC} PARENT_SCOPE) lomiri-storage-framework-0.5.0/src/qt/client/internal/DownloaderBase.cpp000066400000000000000000000021701521521330000263360ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include using namespace std; class QLocalSocket; namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { DownloaderBase::DownloaderBase(weak_ptr file) : file_(file.lock()) { assert(file_); } } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/internal/FileBase.cpp000066400000000000000000000020761521521330000251240ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { FileBase::FileBase(QString const& identity) : ItemBase(identity, ItemType::file) { } } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/internal/FolderBase.cpp000066400000000000000000000022351521521330000254550ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { FolderBase::FolderBase(QString const& identity, ItemType type) : ItemBase(identity, type) { assert(type == ItemType::root || type == ItemType::folder); } } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/internal/ItemBase.cpp000066400000000000000000000050221521521330000251350ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { ItemBase::ItemBase(QString const& identity, ItemType type) : identity_(identity) , type_(type) { assert(!identity.isEmpty()); } ItemBase::~ItemBase() = default; QString ItemBase::native_identity() const { throw_if_destroyed("Item::native_identity()"); return identity_; } ItemType ItemBase::type() const { throw_if_destroyed("Item::type()"); return type_; } shared_ptr ItemBase::root() const { auto root = get_root(); if (!root) { throw RuntimeDestroyedException("Item::root()"); } return root; } void ItemBase::set_root(std::weak_ptr root) { assert(root.lock()); root_ = root; } void ItemBase::set_public_instance(std::weak_ptr p) { assert(p.lock()); public_instance_ = p; } shared_ptr ItemBase::get_root() const noexcept { try { auto root = root_.lock(); if (root) { root->account(); // Throws if either account or runtime has been destroyed. return root; } } catch (RuntimeDestroyedException const&) { } return nullptr; } void ItemBase::throw_if_destroyed(QString const& method) const { if (deleted_) { QString msg = method + ": \"" + identity_ + "\" was deleted previously"; throw DeletedException(msg, identity_); } if (!get_root()) { throw RuntimeDestroyedException(method); } } } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/internal/RootBase.cpp000066400000000000000000000032471521521330000251710ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { RootBase::RootBase(QString const& identity, weak_ptr const& account) : ItemBase(identity, ItemType::folder) , FolderBase(identity, ItemType::folder) , account_(account) { assert(account.lock()); } shared_ptr RootBase::account() const { if (auto acc = account_.lock()) { try { acc->runtime(); } catch (RuntimeDestroyedException const&) { throw RuntimeDestroyedException("Root::account()"); } return acc; } throw RuntimeDestroyedException("Root::account()"); } } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/internal/RuntimeBase.cpp000066400000000000000000000021541521521330000256650ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { void RuntimeBase::set_public_instance(weak_ptr p) { assert(p.lock()); public_instance_ = p; } } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/internal/UploaderBase.cpp000066400000000000000000000025541521521330000260210ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #pragma GCC diagnostic pop #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { UploaderBase::UploaderBase(ConflictPolicy policy, int64_t size) : policy_(policy) , size_(size) { assert(size >= 0); } int64_t UploaderBase::size() const { return size_; } } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/internal/local_client/000077500000000000000000000000001521521330000253715ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/src/qt/client/internal/local_client/AccountImpl.cpp000066400000000000000000000104771521521330000303240ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wold-style-cast" #include #pragma GCC diagnostic pop #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace local_client { namespace { // Return ${STORAGE_FRAMEWORK_ROOT}/storage-framework. If STORAGE_FRAMEWORK_ROOT // is not set, return ${XDG_DATA_HOME}/storage-framework. // ${STORAGE_FRAMEWORK_ROOT} or ${XDG_DATA_HOME} must exist and be a directory. // If the storage-framework underneath that data directory does not exist, it is created. string get_data_dir() { char const* dir = getenv("STORAGE_FRAMEWORK_ROOT"); if (!dir || *dir == '\0') { dir = g_get_user_data_dir(); // Never fails. } boost::system::error_code ec; // The directory must exist. bool is_dir = boost::filesystem::is_directory(dir, ec); if (ec) { QString msg = "Account::roots(): Cannot stat " + QString(dir) + ": " + QString::fromStdString(ec.message()); throw ResourceException(msg, errno); } if (!is_dir) { QString msg = "Account::roots(): Environment variable STORAGE_FRAMEWORK_ROOT must denote a directory"; throw InvalidArgumentException(msg); } // Create the storage-framework directory if it doesn't exist yet. string data_dir(dir); data_dir += "/storage-framework"; if (!boost::filesystem::exists(data_dir)) { boost::filesystem::create_directories(data_dir, ec); if (ec) { QString msg = "Account::roots(): Cannot create " + QString(dir) + ": " + QString::fromStdString(ec.message()); throw ResourceException(msg, ec.value()); } } return data_dir; } } // namespace AccountImpl::AccountImpl(weak_ptr const& runtime, QString const& owner, QString const& owner_id, QString const& description) : AccountBase(runtime) , owner_(owner) , owner_id_(owner_id) , description_(description) { assert(!owner.isEmpty()); assert(!owner_id.isEmpty()); assert(!description.isEmpty()); } QString AccountImpl::owner() const { runtime(); // Throws RuntimeDestroyedException if runtime was destroyed. return owner_; } QString AccountImpl::owner_id() const { runtime(); // Throws RuntimeDestroyedException if runtime was destroyed. return owner_id_; } QString AccountImpl::description() const { runtime(); // Throws RuntimeDestroyedException if runtime was destroyed. return description_; } QFuture> AccountImpl::roots() { try { runtime(); // Throws RuntimeDestroyedException if runtime was destroyed. } catch (RuntimeDestroyedException const& e) { return make_exceptional_future>(e); } if (!roots_.isEmpty()) { return make_ready_future(roots_); } // Create the root on first access. using namespace boost::filesystem; auto rpath = canonical(get_data_dir()).native(); auto root = RootImpl::make_root(QString::fromStdString(rpath), public_instance_); roots_.append(root); return make_ready_future(roots_); } } // namespace local_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/internal/local_client/CMakeLists.txt000066400000000000000000000016651521521330000301410ustar00rootroot00000000000000set(QT_CLIENT_LIB_LOCAL_SRC ${CMAKE_CURRENT_SOURCE_DIR}/AccountImpl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DownloaderImpl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/FileImpl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/FolderImpl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ItemImpl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/RootImpl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Runtime_create.cpp ${CMAKE_CURRENT_SOURCE_DIR}/RuntimeImpl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/storage_exception.cpp ${CMAKE_CURRENT_SOURCE_DIR}/UploaderImpl.cpp ${CMAKE_SOURCE_DIR}/include/lomiri/storage/qt/client/internal/local_client/DownloaderImpl.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/qt/client/internal/local_client/UploaderImpl.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/qt/client/internal/local_client/RuntimeImpl.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/qt/client/internal/RuntimeBase.h PARENT_SCOPE ) lomiri-storage-framework-0.5.0/src/qt/client/internal/local_client/DownloaderImpl.cpp000066400000000000000000000232421521521330000310200ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #include #pragma GCC diagnostic pop #include #include using namespace lomiri::storage::qt::client; using namespace std; namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace local_client { DownloadWorker::DownloadWorker(int write_fd, QString const& filename, QFutureInterface& qf, QFutureInterface& worker_initialized) : write_fd_(write_fd) , filename_(filename) , qf_(qf) , worker_initialized_(worker_initialized) { assert(write_fd >= 0); qf_.reportStarted(); worker_initialized_.reportStarted(); } void DownloadWorker::start_downloading() noexcept { write_socket_.reset(new QLocalSocket); write_socket_->setSocketDescriptor(write_fd_, QLocalSocket::ConnectedState, QIODevice::WriteOnly); // We should be able to close the read channel of the write socket, // but doing this causes the disconnected signal to go AWOL. // Possibly a problem wit QLocalSocket. // shutdown(write_fd_, SHUT_RD); // Monitor write socket for ready-to-write, disconnected, and error events. connect(write_socket_.get(), &QLocalSocket::bytesWritten, this, &DownloadWorker::on_bytes_written); connect(write_socket_.get(), &QLocalSocket::disconnected, this, &DownloadWorker::on_disconnected); connect(write_socket_.get(), &QLocalSocket::errorOccurred, this, &DownloadWorker::on_error); // Open file for reading. input_file_.reset(new QFile(filename_)); if (!input_file_->open(QIODevice::ReadOnly)) { // LCOV_EXCL_START handle_error("cannot open " + filename_ + ": " + input_file_->errorString(), input_file_->error()); return; // LCOV_EXCL_STOP } bytes_to_write_ = input_file_->size(); worker_initialized_.reportFinished(); if (bytes_to_write_ == 0) { write_socket_->disconnectFromServer(); // So the client gets EOF for empty files. } else { read_and_write_chunk(); // Kick off the read-write cycle. } } // Called once we know the outcome of the download, or via a signal when the client // calls finish_download(). This makes the future ready with the appropriate // result or error information. void DownloadWorker::do_finish() { switch (state_) { case in_progress: { if (bytes_to_write_ > 0) { // Still unwrittten data left, caller abandoned download early without cancelling. auto file_size = input_file_->size(); auto written = file_size - bytes_to_write_; QString msg = "Downloader::finish_download(): method called too early, file " + filename_ + " has size " + QString::number(file_size) + ", but only " + QString::number(written) + " byte"; msg += written == 1 ? " was" : "s were"; msg += " consumed."; qf_.reportException(LogicException(msg)); } else { state_ = finalized; } break; } case finalized: { abort(); // LCOV_EXCL_LINE // Impossible. If we get here, our logic is broken. } case cancelled: { QString msg = "Downloader::finish_download(): download of " + filename_ + " was cancelled"; qf_.reportException(CancelledException(msg)); break; } case error: { qf_.reportException(ResourceException(error_msg_, error_code_)); break; } default: { abort(); // LCOV_EXCL_LINE // Impossible } } qf_.reportFinished(); QThread::currentThread()->quit(); } // Called via signal from the client to stop things. void DownloadWorker::do_cancel() { if (state_ == in_progress) { disconnect(write_socket_.get(), nullptr, this, nullptr); write_socket_->abort(); bytes_to_write_ = 0; state_ = cancelled; do_finish(); } } // Called each time we get rid of a chunk of data, to kick off the next chunk. void DownloadWorker::on_bytes_written(qint64 bytes) { bytes_to_write_ -= bytes; assert(bytes_to_write_ >= 0); if (bytes_to_write_ == 0) { input_file_->close(); write_socket_->disconnectFromServer(); } else { read_and_write_chunk(); } } // Sets the outcome of the download in the future once we have written // the last of the data and have disconnected. void DownloadWorker::on_disconnected() { do_finish(); } void DownloadWorker::on_error() { disconnect(write_socket_.get(), nullptr, this, nullptr); handle_error(write_socket_->errorString(), write_socket_->error()); } // Read the next chunk of data from the input file and write it to the socket. void DownloadWorker::read_and_write_chunk() { static qint64 constexpr READ_SIZE = 64 * 1024; QByteArray buf; buf.resize(READ_SIZE); auto bytes_read = input_file_->read(buf.data(), buf.size()); if (bytes_read == -1) { // LCOV_EXCL_START handle_error(filename_ + ": read error: " + input_file_->errorString(), input_file_->error()); return; // LCOV_EXCL_STOP } buf.resize(bytes_read); auto bytes_written = write_socket_->write(buf); if (bytes_written == -1) { // LCOV_EXCL_START handle_error(filename_ + ": socket error: " + write_socket_->errorString(), write_socket_->error()); // LCOV_EXCL_STOP } else if (bytes_written != bytes_read) { // LCOV_EXCL_START QString msg = QStringLiteral("%1: write error, requested %2 B, but wrote only %3 B.") .arg(filename_).arg(bytes_read).arg(bytes_written); handle_error(msg, 0); // LCOV_EXCL_STOP } } void DownloadWorker::handle_error(QString const& msg, int error_code) { if (state_ == in_progress) { write_socket_->abort(); } state_ = error; error_msg_ = "Downloader: " + msg; error_code_ = error_code; do_finish(); } DownloadThread::DownloadThread(DownloadWorker* worker) : worker_(worker) { } void DownloadThread::run() { worker_->start_downloading(); exec(); } DownloaderImpl::DownloaderImpl(weak_ptr file) : DownloaderBase(file) , read_socket_(new QLocalSocket, [](QLocalSocket* s){ s->deleteLater(); }) { // Set up socket pair. int fds[2]; int rc = socketpair(AF_UNIX, SOCK_STREAM, 0, fds); if (rc == -1) { // LCOV_EXCL_START QString msg = "Downloader: cannot create socket pair: " + QString::fromStdString(storage::internal::safe_strerror(errno)); qf_.reportException(ResourceException(msg, errno)); qf_.reportFinished(); return; // LCOV_EXCL_STOP } // Read socket is for the client. read_socket_->setSocketDescriptor(fds[0], QLocalSocket::ConnectedState, QIODevice::ReadOnly); // We should be able to close the write channel of the client-side read socket, but // doing this causes the client to never see the readyRead signal. // Possibly a problem with QLocalSocket. // shutdown(fds[0], SHUT_WR); // Create worker and connect slots, so we can signal the worker when the client calls // finish_download() or cancel(). QFutureInterface worker_initialized; worker_.reset(new DownloadWorker(fds[1], file_->native_identity(), qf_, worker_initialized)); connect(this, &DownloaderImpl::do_finish, worker_.get(), &DownloadWorker::do_finish); connect(this, &DownloaderImpl::do_cancel, worker_.get(), &DownloadWorker::do_cancel); // Create download thread and make sure that worker slots are called from the download thread. download_thread_.reset(new DownloadThread(worker_.get())); worker_->moveToThread(download_thread_.get()); download_thread_->start(); worker_initialized.future().waitForFinished(); } DownloaderImpl::~DownloaderImpl() { if (download_thread_->isRunning()) { Q_EMIT do_cancel(); download_thread_->wait(); } } shared_ptr DownloaderImpl::file() const { return file_; } shared_ptr DownloaderImpl::socket() const { return read_socket_; } QFuture DownloaderImpl::finish_download() { Q_EMIT do_finish(); return qf_.future(); } QFuture DownloaderImpl::cancel() noexcept { Q_EMIT do_cancel(); return qf_.future(); } } // namespace local_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/internal/local_client/FileImpl.cpp000066400000000000000000000072451521521330000276060ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include #include #include #include #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace local_client { FileImpl::FileImpl(QString const& identity) : ItemBase(identity, ItemType::file) , FileBase(identity) , ItemImpl(identity, ItemType::file) { } QString FileImpl::name() const { lock_guard guard(mutex_); throw_if_destroyed("File::name()"); return name_; } int64_t FileImpl::size() const { lock_guard guard(mutex_); throw_if_destroyed("File::size()"); try { boost::filesystem::path p = identity_.toStdString(); return file_size(p); } catch (std::exception const&) { throw_storage_exception(QString("File::size()"), current_exception()); } } QFuture FileImpl::create_uploader(ConflictPolicy policy, int64_t size) { lock_guard guard(mutex_); try { throw_if_destroyed("File::create_uploader()"); } catch (StorageException const& e) { return internal::make_exceptional_future(e); } if (size < 0) { QString msg = "File::create_uploader(): size must be >= 0"; return internal::make_exceptional_future>(InvalidArgumentException(msg)); } auto file = dynamic_pointer_cast(public_instance_.lock()); assert(file); auto impl(new UploaderImpl(file, size, identity_, policy, root_)); Uploader::SPtr ul(new Uploader(impl)); return make_ready_future(ul); } QFuture FileImpl::create_downloader() { lock_guard guard(mutex_); try { throw_if_destroyed("File::create_downloader()"); } catch (StorageException const& e) { return internal::make_exceptional_future(e); } auto pi = public_instance_.lock(); assert(pi); auto file_ptr = static_pointer_cast(pi); auto impl = new DownloaderImpl(file_ptr); Downloader::SPtr dl(new Downloader(impl)); return make_ready_future(dl); } File::SPtr FileImpl::make_file(QString const& identity, weak_ptr root) { auto impl = new FileImpl(identity); File::SPtr file(new File(impl)); impl->set_root(root); impl->set_public_instance(file); return file; } } // namespace local_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/internal/local_client/FolderImpl.cpp000066400000000000000000000216561521521330000301440ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include #include #include #include #include #include #include #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #pragma GCC diagnostic pop using namespace std; namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace local_client { FolderImpl::FolderImpl(QString const& identity) : ItemBase(identity, ItemType::folder) , FolderBase(identity, ItemType::folder) , ItemImpl(identity, ItemType::folder) { } FolderImpl::FolderImpl(QString const& identity, ItemType type) : ItemBase(identity, type) , FolderBase(identity, type) , ItemImpl(identity, type) { } QString FolderImpl::name() const { lock_guard guard(mutex_); throw_if_destroyed("Item::name()"); return name_; } QFuture> FolderImpl::list() const { try { throw_if_destroyed("Folder::list()"); } catch (StorageException const& e) { return internal::make_exceptional_future>(e); } auto This = dynamic_pointer_cast(shared_from_this()); // Keep this folder alive while the lambda is alive. auto list = [This]() { lock_guard guard(This->mutex_); This->throw_if_destroyed("Folder::list()"); try { using namespace boost::filesystem; auto root = This->root_.lock(); QVector results; for (directory_iterator it(This->native_identity().toStdString()); it != directory_iterator(); ++it) { auto dirent = *it; file_status s = dirent.status(); if (is_reserved_path(dirent.path())) { continue; // Hide temp files that we create during copy() and move(). } QString path = QString::fromStdString(dirent.path().native()); if (is_directory(s)) { results.append(make_folder(path, root)); } else if (is_regular_file(s)) { results.append(FileImpl::make_file(path, root)); } else { // Ignore everything that's not a directory or file. } } return results; } catch (std::exception const&) { throw_storage_exception("Folder::list()", current_exception()); } }; return QtConcurrent::run(list); } QFuture> FolderImpl::lookup(QString const& name) const { try { throw_if_destroyed("Folder::lookup()"); } catch (StorageException const& e) { return internal::make_exceptional_future>(e); } auto This = dynamic_pointer_cast(shared_from_this()); // Keep this folder alive while the lambda is alive. auto lookup = [This, name]() -> QVector { lock_guard guard(This->mutex_); This->throw_if_destroyed("Folder::lookup()"); // LCOV_EXCL_LINE try { using namespace boost::filesystem; auto root = This->root_.lock(); path p = This->native_identity().toStdString(); auto sanitized_name = sanitize(name, "Folder::lookup()"); if (is_reserved_path(sanitized_name)) { throw NotExistsException("Folder::lookup(): no such item: \"" + name + "\"", name); } p /= sanitized_name; file_status s = status(p); if (is_directory(s)) { QVector v; v.append(make_folder(QString::fromStdString(p.native()), root)); return v; } if (is_regular_file(s)) { QVector v; v.append(FileImpl::make_file(QString::fromStdString(p.native()), root)); return v; } throw NotExistsException("Folder::lookup(): no such item: \"" + name + "\"", name); } catch (std::exception const&) { throw_storage_exception("Folder::lookup()", current_exception()); } }; return QtConcurrent::run(lookup); } QFuture FolderImpl::create_folder(QString const& name) { lock_guard guard(mutex_); try { throw_if_destroyed("Folder::create_folder()"); } catch (StorageException const& e) { return internal::make_exceptional_future(e); } try { using namespace boost::filesystem; path p = native_identity().toStdString(); auto sanitized_name = sanitize(name, "Folder::create_folder()"); if (is_reserved_path(sanitized_name)) { QString msg = "Folder::create_folder(): names beginning with \"" + QString(TMPFILE_PREFIX) + "\" are reserved"; throw InvalidArgumentException(msg); } p /= sanitized_name; if (exists(p)) { QString msg = "Folder::create_folder(): item with name \"" + name + "\" exists already"; throw ExistsException(msg, native_identity() + "/" + name, name); } create_directory(p); return make_ready_future(make_folder(QString::fromStdString(p.native()), root_)); } catch (std::exception const&) { return make_exceptional_future("Folder::create_folder()", current_exception()); } } QFuture> FolderImpl::create_file(QString const& name, int64_t size) { lock_guard guard(mutex_); try { throw_if_destroyed("Folder::create_file()"); } catch (StorageException const& e) { return internal::make_exceptional_future>(e); } if (size < 0) { QString msg = "Folder::create_file(): size must be >= 0"; return internal::make_exceptional_future>(InvalidArgumentException(msg)); } try { using namespace boost::filesystem; path p = native_identity().toStdString(); auto sanitized_name = sanitize(name, "Folder::create_file()"); if (is_reserved_path(sanitized_name)) { QString msg = "Folder::create_file(): names beginning with \"" + QString(TMPFILE_PREFIX) + "\" are reserved"; throw InvalidArgumentException(msg); } p /= sanitized_name; if (exists(p)) { QString msg = "Folder::create_file(): item with name \"" + name + "\" exists already"; throw ExistsException(msg, native_identity() + "/" + name, name); } auto impl = new UploaderImpl(shared_ptr(), size, QString::fromStdString(p.native()), ConflictPolicy::error_if_conflict, root_); Uploader::SPtr uploader(new Uploader(impl)); return make_ready_future(uploader); } catch (std::exception const&) { return make_exceptional_future("Folder::create_file()", current_exception()); } } Folder::SPtr FolderImpl::make_folder(QString const& identity, weak_ptr root) { auto impl = new FolderImpl(identity); Folder::SPtr folder(new Folder(impl)); impl->set_root(root); impl->set_public_instance(folder); return folder; } } // namespace local_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/internal/local_client/ItemImpl.cpp000066400000000000000000000405721521521330000276250ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include #include #include #include #include #include #include #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #pragma GCC diagnostic pop #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace local_client { ItemImpl::ItemImpl(QString const& identity, ItemType type) : ItemBase(identity, type) { assert(!identity.isEmpty()); auto path = boost::filesystem::canonical(identity.toStdString()); name_ = QString::fromStdString(path.filename().native()); set_timestamps(); } ItemImpl::~ItemImpl() = default; QString ItemImpl::etag() const { lock_guard guard(mutex_); throw_if_destroyed("Item::etag()"); return etag_; } QVariantMap ItemImpl::metadata() const { lock_guard guard(mutex_); throw_if_destroyed("Item::metadata()"); return metadata_; } QDateTime ItemImpl::last_modified_time() const { lock_guard guard(mutex_); throw_if_destroyed("Item::last_modified_time()"); return modified_time_; } QFuture> ItemImpl::copy(shared_ptr const& new_parent, QString const& new_name) { if (!new_parent) { QString msg = "Item::copy(): new_parent cannot be nullptr"; return internal::make_exceptional_future>(InvalidArgumentException(msg)); } auto new_parent_impl = dynamic_pointer_cast(new_parent->p_); lock(mutex_, new_parent_impl->mutex_); lock_guard this_guard(mutex_, std::adopt_lock); lock_guard other_guard(new_parent_impl->mutex_, adopt_lock); try { throw_if_destroyed("Item::copy()"); new_parent_impl->throw_if_destroyed("Item::copy()"); } catch (StorageException const& e) { return internal::make_exceptional_future>(e); } auto This = dynamic_pointer_cast(shared_from_this()); // Keep this item alive while the lambda is alive. auto copy = [This, new_parent, new_name]() -> Item::SPtr { auto new_parent_impl = dynamic_pointer_cast(new_parent->p_); lock(This->mutex_, new_parent_impl->mutex_); lock_guard this_guard(This->mutex_, std::adopt_lock); lock_guard other_guard(new_parent_impl->mutex_, adopt_lock); This->throw_if_destroyed("Item::copy()"); new_parent_impl->throw_if_destroyed("Item::copy()"); // TODO: This needs to deeply compare account identity because the client may have refreshed the accounts list. if (This->root()->account() != new_parent->root()->account()) // Throws if account or runtime were destroyed. { // Can't do cross-account copy. QString msg = QString("Item::copy(): source (") + This->name_ + ") and target (" + new_name + ") must belong to the same account"; throw LogicException(msg); } try { using namespace boost::filesystem; path source_path = This->native_identity().toStdString(); path parent_path = new_parent->native_identity().toStdString(); path target_path = parent_path; path sanitized_name = sanitize(new_name, "Item::copy()"); target_path /= sanitized_name; if (is_reserved_path(target_path)) { QString msg = "Item::copy(): names beginning with \"" + QString(TMPFILE_PREFIX) + "\" are reserved"; throw InvalidArgumentException(msg); } if (exists(target_path)) { QString msg = "Item::copy(): item with name \"" + new_name + "\" exists already"; throw ExistsException(msg, This->identity_, This->name_); } if (This->type_ == ItemType::file) { copy_file(source_path, target_path); return FileImpl::make_file(QString::fromStdString(target_path.native()), new_parent_impl->root_); } // For recursive copy, we create a temporary directory in lieu of target_path and recursively copy // everything into the temporary directory. This ensures that we don't invalidate directory iterators // by creating things while we are iterating, potentially getting trapped in an infinite loop. path tmp_path = canonical(parent_path); tmp_path /= unique_path(TMPFILE_PREFIX "%%%%-%%%%-%%%%-%%%%"); create_directories(tmp_path); for (directory_iterator it(source_path); it != directory_iterator(); ++it) { if (is_reserved_path(it->path())) { continue; // Don't recurse into the temporary directory } file_status s = it->status(); if (is_directory(s) || is_regular_file(s)) { path source_entry = it->path(); path target_entry = tmp_path; target_entry /= source_entry.filename(); ItemImpl::copy_recursively(source_entry, target_entry); } } rename(tmp_path, target_path); return FolderImpl::make_folder(QString::fromStdString(target_path.native()), new_parent_impl->root_); } catch (std::exception const&) { throw_storage_exception("Item::copy()", current_exception()); } }; return QtConcurrent::run(copy); } QFuture> ItemImpl::move(shared_ptr const& new_parent, QString const& new_name) { if (!new_parent) { QString msg = "Item::move(): new_parent cannot be nullptr"; return internal::make_exceptional_future>(InvalidArgumentException(msg)); } auto new_parent_impl = dynamic_pointer_cast(new_parent->p_); lock(mutex_, new_parent_impl->mutex_); lock_guard this_guard(mutex_, std::adopt_lock); lock_guard other_guard(new_parent_impl->mutex_, adopt_lock); try { throw_if_destroyed("Item::move()"); new_parent_impl->throw_if_destroyed("Item::move()"); } catch (StorageException const& e) { return internal::make_exceptional_future>(e); } auto This = dynamic_pointer_cast(shared_from_this()); // Keep this item alive while the lambda is alive. auto move = [This, new_parent, new_name]() -> Item::SPtr { auto new_parent_impl = dynamic_pointer_cast(new_parent->p_); lock(This->mutex_, new_parent_impl->mutex_); lock_guard this_guard(This->mutex_, std::adopt_lock); lock_guard other_guard(new_parent_impl->mutex_, adopt_lock); This->throw_if_destroyed("Item::move()"); new_parent_impl->throw_if_destroyed("Item::move()"); // TODO: This needs to deeply compare account identity because the client may have refreshed the accounts list. if (This->root()->account() != new_parent->root()->account()) // Throws if account or runtime were destroyed. { // Can't do cross-account move. QString msg = QString("Item::move(): source (") + This->name_ + ") and target (" + new_name + ") must belong to the same account"; throw LogicException(msg); } if (This->type_ == ItemType::root) { // Can't move a root. throw LogicException("Item::move(): cannot move root folder"); } try { using namespace boost::filesystem; path target_path = new_parent->native_identity().toStdString(); target_path /= sanitize(new_name, "Item::move()"); if (exists(target_path)) { QString msg = "Item::move(): item with name \"" + new_name + "\" exists already"; throw ExistsException(msg, This->identity_, This->name_); } if (is_reserved_path(target_path)) { QString msg = "Item::move(): names beginning with \"" + QString(TMPFILE_PREFIX) + "\" are reserved"; throw InvalidArgumentException(msg); } rename(This->native_identity().toStdString(), target_path); This->deleted_ = true; if (This->type_ == ItemType::folder) { return FolderImpl::make_folder(QString::fromStdString(target_path.native()), new_parent_impl->root_); } return FileImpl::make_file(QString::fromStdString(target_path.native()), new_parent_impl->root_); } catch (std::exception const&) { throw_storage_exception(QString("Item::move(): "), current_exception()); } }; return QtConcurrent::run(move); } QFuture> ItemImpl::parents() const { lock_guard guard(mutex_); try { throw_if_destroyed("Item::parents()"); } catch (StorageException const& e) { return internal::make_exceptional_future>(e); } using namespace boost::filesystem; // We do this synchronously because we don't need to hit the file system. path p = native_identity().toStdString(); QString parent_path = QString::fromStdString(p.parent_path().native()); auto root = root_.lock(); QVector results; if (parent_path != root->native_identity()) { results.append(FolderImpl::make_folder(parent_path, root)); } else { results.append(root); } return make_ready_future(results); } QVector ItemImpl::parent_ids() const { lock_guard guard(mutex_); throw_if_destroyed("Item::parent_ids()"); using namespace boost::filesystem; // We do this synchronously because we don't need to hit the file system. path p = native_identity().toStdString(); QString parent_path = QString::fromStdString(p.parent_path().native()); QVector results; results.append(parent_path); return results; } QFuture ItemImpl::delete_item() { lock_guard guard(mutex_); try { throw_if_destroyed("Item::delete_item()"); } catch (StorageException const& e) { return internal::make_exceptional_future(e); } auto This = dynamic_pointer_cast(shared_from_this()); // Keep this item alive while the lambda is alive. auto destroy = [This]() { lock_guard guard(This->mutex_); This->throw_if_destroyed("Item::delete_item()"); try { boost::filesystem::remove_all(This->native_identity().toStdString()); This->deleted_ = true; } catch (std::exception const&) { throw_storage_exception(QString("Item::delete_item()"), current_exception()); } }; return QtConcurrent::run(destroy); } QDateTime ItemImpl::creation_time() const { lock_guard guard(mutex_); throw_if_destroyed("Item::creation_time()"); return QDateTime(); } MetadataMap ItemImpl::native_metadata() const { lock_guard guard(mutex_); throw_if_destroyed("Item::native_metadata()"); return MetadataMap(); } bool ItemImpl::equal_to(ItemBase const& other) const noexcept { auto other_impl = dynamic_cast(&other); assert(other_impl); if (this == other_impl) { return true; } lock(mutex_, other_impl->mutex_); lock_guard this_guard(mutex_, std::adopt_lock); lock_guard other_guard(other_impl->mutex_, adopt_lock); if (deleted_ || other_impl->deleted_) { return false; } return identity_ == other_impl->identity_; } void ItemImpl::set_timestamps() noexcept { lock_guard guard(mutex_); string id = identity_.toStdString(); // Use nano-second resolution for the ETag, if the file system supports it. struct stat st; if (stat(id.c_str(), &st) == -1) { // TODO: log this error modified_time_ = QDateTime::fromSecsSinceEpoch(0); etag_ = ""; } modified_time_ = QDateTime::fromMSecsSinceEpoch(int64_t(st.st_mtim.tv_sec) * 1000 + st.st_mtim.tv_nsec / 1000000); etag_ = QString::number(int64_t(st.st_mtim.tv_sec) * 1000000000 + st.st_mtim.tv_nsec); } bool ItemImpl::has_conflict() const noexcept { lock_guard guard(mutex_); string id = identity_.toStdString(); struct stat st; if (stat(id.c_str(), &st) == -1) { // TODO: log this error return true; } auto new_etag = QString::number(int64_t(st.st_mtim.tv_sec) * 1000000000 + st.st_mtim.tv_nsec); return etag_ != new_etag; } // Throw if name contains more than one path component. // Otherwise, return the relative path for the name. // This is to make sure that calling, say, create_file() // with a name such as "../../whatever" cannot lead // outside the root. boost::filesystem::path ItemImpl::sanitize(QString const& name, QString const& method) { using namespace boost::filesystem; path p = name.toStdString(); if (!p.parent_path().empty()) { // name contains more than one component. QString msg = method + ": name \"" + name + "\" contains more than one path component"; throw InvalidArgumentException(msg); } path filename = p.filename(); if (filename.empty() || filename == "." || filename == "..") { // Not an allowable file name. QString msg = method + ": invalid name: \"" + name + "\""; throw InvalidArgumentException(msg); } return p; } // Return true if the name uses the temp file prefix. bool ItemImpl::is_reserved_path(boost::filesystem::path const& path) noexcept { string filename = path.filename().native(); return boost::starts_with(filename, TMPFILE_PREFIX); } void ItemImpl::copy_recursively(boost::filesystem::path const& source, boost::filesystem::path const& target) { using namespace boost::filesystem; if (is_reserved_path(source)) { return; // Don't copy temporary directories. } auto s = status(source); if (is_regular_file(s)) { copy_file(source, target); } else if (is_directory(s)) { copy_directory(source, target); // Poorly named in boost; this creates the target dir without recursion for (directory_iterator it(source); it != directory_iterator(); ++it) { path source_entry = it->path(); path target_entry = target; target_entry /= source_entry.filename(); copy_recursively(source_entry, target_entry); } } else { // Ignore everything that's not a directory or file. } } } // namespace local_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/internal/local_client/RootImpl.cpp000066400000000000000000000173261521521330000276530ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace local_client { RootImpl::RootImpl(QString const& identity, weak_ptr const& account) : ItemBase(identity, ItemType::root) , FolderBase(identity, ItemType::root) , RootBase(identity, account) , ItemImpl(identity, ItemType::root) , FolderImpl(identity, ItemType::root) { using namespace boost::filesystem; path id_path = path(identity.toStdString()); if (!id_path.is_absolute()) { QString msg = QString("Root: root path \"") + identity + "\" must be absolute"; throw InvalidArgumentException(msg); } path can_path = canonical(id_path); auto id_len = std::distance(id_path.begin(), id_path.end()); auto can_len = std::distance(can_path.begin(), can_path.end()); if (id_len != can_len) { // identity denotes a weird path that we won't trust because // it might contain ".." or similar. QString msg = QString("Root: root path \"") + identity + "\" cannot contain \".\" or \"..\" components"; throw InvalidArgumentException(msg); } assert(account.lock()); } QString RootImpl::name() const { lock_guard guard(mutex_); throw_if_destroyed("Item::name()"); return ""; } QFuture> RootImpl::parents() const { lock_guard guard(mutex_); try { throw_if_destroyed("Item::parents()"); } catch (StorageException const& e) { return internal::make_exceptional_future>(e); } return make_ready_future(QVector()); // For the root, we return an empty vector. } QVector RootImpl::parent_ids() const { lock_guard guard(mutex_); throw_if_destroyed("Item::parent_ids()"); return QVector(); // For the root, we return an empty vector. } QFuture RootImpl::delete_item() { lock_guard guard(mutex_); try { throw_if_destroyed("Item::delete_item()"); } catch (StorageException const& e) { return internal::make_exceptional_future(e); } // Cannot delete root. return internal::make_exceptional_future(LogicException("Item::delete_item(): cannot delete root folder")); } QFuture RootImpl::free_space_bytes() const { lock_guard guard(mutex_); try { throw_if_destroyed("Root::free_space_bytes()"); } catch (StorageException const& e) { return internal::make_exceptional_future(e); } using namespace boost::filesystem; try { space_info si = space(identity_.toStdString()); return make_ready_future(si.available); } // LCOV_EXCL_START catch (std::exception const&) { return make_exceptional_future(QString("Root::free_space_bytes()"), current_exception()); } // LCOV_EXCL_STOP } QFuture RootImpl::used_space_bytes() const { lock_guard guard(mutex_); try { throw_if_destroyed("Root::used_space_bytes()"); } catch (StorageException const& e) { return internal::make_exceptional_future(e); } using namespace boost::filesystem; try { space_info si = space(identity_.toStdString()); return make_ready_future(si.capacity - si.available); } // LCOV_EXCL_START catch (std::exception const&) { return make_exceptional_future(QString("Root::used_space_bytes()"), current_exception()); } // LCOV_EXCL_STOP } QFuture RootImpl::get(QString native_identity) const { lock_guard guard(mutex_); try { throw_if_destroyed("Root::get()"); } catch (StorageException const& e) { return internal::make_exceptional_future(e); } auto root = get_root(); if (!root) { return internal::make_exceptional_future(RuntimeDestroyedException("Root::get()")); } using namespace boost::filesystem; QFutureInterface qf; try { path id_path = native_identity.toStdString(); if (!id_path.is_absolute()) { QString msg = "Root::get(): identity \"" + native_identity + "\" must be an absolute path"; throw InvalidArgumentException(msg); } // Make sure that native_identity is contained in or equal to the root path. id_path = canonical(id_path); auto root_path = path(root->native_identity().toStdString()); auto id_len = std::distance(id_path.begin(), id_path.end()); auto root_len = std::distance(root_path.begin(), root_path.end()); if (id_len < root_len || !std::equal(root_path.begin(), root_path.end(), id_path.begin())) { // Too few components, or wrong path prefix. Therefore, native_identity can't // possibly point at something below the root. QString msg = QString("Root::get(): identity \"") + native_identity + "\" points outside the root folder"; throw InvalidArgumentException(msg); } // Don't allow reserved files to be found. if (is_reserved_path(id_path)) { QString msg = "Root::get(): no such item: \"" + native_identity + "\""; throw NotExistsException(msg, native_identity); } file_status s = status(id_path); QString path = QString::fromStdString(id_path.native()); if (is_directory(s)) { if (id_path == root_path) { return make_ready_future(make_root(path, account())); } return make_ready_future(make_folder(path, root)); } if (is_regular_file(s)) { return make_ready_future(FileImpl::make_file(path, root)); } QString msg = "Root::get(): no such item: \"" + native_identity + "\""; throw NotExistsException(msg, native_identity); } catch (std::exception const&) { return make_exceptional_future(QString("Root::get()"), current_exception(), native_identity); } } Root::SPtr RootImpl::make_root(QString const& identity, std::weak_ptr const& account) { assert(account.lock()); auto impl = new RootImpl(identity, account); Root::SPtr root(new Root(impl)); impl->set_root(root); impl->set_public_instance(root); return root; } } // namespace local_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/internal/local_client/RuntimeImpl.cpp000066400000000000000000000056511521521330000303510ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include #include #include #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wold-style-cast" #include #pragma GCC diagnostic pop #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace local_client { RuntimeImpl::RuntimeImpl() { qRegisterMetaType(); } RuntimeImpl::~RuntimeImpl() { try { shutdown(); } catch (std::exception const&) { } } void RuntimeImpl::shutdown() { if (destroyed_) { return; } destroyed_ = true; } QFuture> RuntimeImpl::accounts() { if (destroyed_) { return internal::make_exceptional_future>(RuntimeDestroyedException("Runtime::accounts()")); } char const* user = g_get_user_name(); assert(*user != '\0'); QString owner = user; QString owner_id; owner_id.setNum(getuid()); QString description = "Account for " + owner + " (" + owner_id + ")"; QFutureInterface> qf; if (!accounts_.isEmpty()) { return make_ready_future(accounts_); } // Create accounts_ on first access. auto impl = new AccountImpl(public_instance_, owner, owner_id, description); Account::SPtr acc(new Account(impl)); impl->set_public_instance(acc); accounts_.append(acc); return make_ready_future(accounts_); } shared_ptr RuntimeImpl::make_test_account(QString const& bus_name, QString const& object_path) { Q_UNUSED(bus_name); Q_UNUSED(object_path); throw LocalCommsException("Can not create test account with local client"); } } // namespace local_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/internal/local_client/Runtime_create.cpp000066400000000000000000000024261521521330000310470ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace client { Runtime::SPtr Runtime::create(QDBusConnection const&) { auto impl = new internal::local_client::RuntimeImpl; Runtime::SPtr runtime(new Runtime(impl)); impl->set_public_instance(weak_ptr(runtime)); return runtime; } } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/internal/local_client/UploaderImpl.cpp000066400000000000000000000342261521521330000305010ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include #include #include #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #include #pragma GCC diagnostic pop #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace local_client { UploadWorker::UploadWorker(int read_fd, weak_ptr file, int64_t size, QString const& path, ConflictPolicy policy, weak_ptr root, QFutureInterface>& qf, QFutureInterface& worker_initialized) : read_fd_(read_fd) , file_(file) , size_(size) , bytes_read_(0) , path_(path) , root_(root) , tmp_fd_([](int fd){ if (fd != -1) ::close(fd); }) , policy_(policy) , qf_(qf) , worker_initialized_(worker_initialized) { assert(read_fd > 0); assert(size >= 0); qf_.reportStarted(); worker_initialized_.reportStarted(); } UploadWorker::~UploadWorker() { if (state_ == error && !use_linkat_ && output_file_) { output_file_->remove(); // LCOV_EXCL_LINE } } void UploadWorker::start_uploading() noexcept { read_socket_.reset(new QLocalSocket); read_socket_->setSocketDescriptor(read_fd_, QLocalSocket::ConnectedState, QIODevice::ReadOnly); // We should be able to close the write channel of the client-side read socket, but // doing this causes the client to never see the readyRead signal. // Possibly a problem with QLocalSocket. // shutdown(read_fd_, SHUT_WR); // Monitor read socket for ready-to-read, disconnected, and error events. connect(read_socket_.get(), &QLocalSocket::readyRead, this, &UploadWorker::on_bytes_ready); connect(read_socket_.get(), &QIODevice::readChannelFinished, this, &UploadWorker::on_read_channel_finished); using namespace boost::filesystem; // Open tmp file for writing. auto parent_path = path(path_.toStdString()).parent_path(); tmp_fd_.reset(open(parent_path.native().c_str(), O_TMPFILE | O_WRONLY, 0600)); if (tmp_fd_.get() == -1) { // LCOV_EXCL_START // Some kernels on the phones don't support O_TMPFILE and return various errno values when this fails. // So, if anything at all goes wrong, we fall back on conventional temp file creation and // produce a hard error if that doesn't work either. // Note that, in this case, the temp file retains its name in the file system. Not nice because, // if the client dies at the wrong moment, we leave the temp file behind. use_linkat_ = false; string tmpfile = parent_path.native() + "/" + TMPFILE_PREFIX + "XXXXXX"; tmp_fd_.reset(mkstemp(const_cast(tmpfile.data()))); if (tmp_fd_.get() == -1) { int error_code = errno; worker_initialized_.reportFinished(); QString msg = "cannot create temp file \"" + QString::fromStdString(tmpfile) + "\": " + QString::fromStdString(storage::internal::safe_strerror(errno)); handle_error(msg, error_code); return; } output_file_.reset(new QFile(QString::fromStdString(tmpfile))); output_file_->open(QIODevice::WriteOnly); // LCOV_EXCL_STOP } else { output_file_.reset(new QFile); output_file_->open(tmp_fd_.get(), QIODevice::WriteOnly, QFileDevice::DontCloseHandle); } worker_initialized_.reportFinished(); } // Called once we know the outcome of the upload, or via a signal when the client // calls finish_upload(). This makes the future ready with the appropriate // result or error information. If the client has not disconnected // yet, we don't touch the future; it becomes ready once we receive the disconnected signal. void UploadWorker::do_finish() { switch (state_) { case in_progress: { if (bytes_read_ != size_) { QString msg = "Uploader::finish_upload(): " + path_ + ": upload size of " + QString::number(size_) + " does not match actual number of bytes read: " + QString::number(bytes_read_); qf_.reportException(LogicException(msg)); qf_.reportFinished(); } else { state_ = finalized; finalize(); } break; } case finalized: { abort(); // LCOV_EXCL_LINE // Impossible. If we get here, our logic is broken. } case cancelled: { QString msg = "Uploader::finish_upload(): upload was cancelled"; qf_.reportException(CancelledException(msg)); qf_.reportFinished(); break; } case error: { // LCOV_EXCL_START qf_.reportException(ResourceException(error_msg_, error_code_)); qf_.reportFinished(); break; // LCOV_EXCL_STOP } default: { abort(); // LCOV_EXCL_LINE // Impossible } } if (qf_.future().isFinished()) { QThread::currentThread()->quit(); } } // Called via signal from the client to stop things. void UploadWorker::do_cancel() { if (state_ == in_progress) { disconnect(read_socket_.get(), nullptr, this, nullptr); read_socket_->abort(); state_ = cancelled; do_finish(); } } void UploadWorker::on_bytes_ready() { auto buf = read_socket_->read(read_socket_->bytesAvailable()); if (buf.size() != 0) { bytes_read_ += buf.size(); auto bytes_written = output_file_->write(buf); if (bytes_written == -1) { handle_error("socket error: " + output_file_->errorString(), output_file_->error()); // LCOV_EXCL_LINE } else if (bytes_written != buf.size()) { // LCOV_EXCL_START QString msg = QStringLiteral("write error, requested %1 B, but wrote only %2 B.") .arg(QString::number(buf.size())).arg(bytes_written); handle_error(msg, 0); // LCOV_EXCL_STOP } } } void UploadWorker::on_read_channel_finished() { on_bytes_ready(); // In case there is still buffered data to be read. do_finish(); } void UploadWorker::finalize() { auto file = file_.lock(); shared_ptr impl; if (file) { // Upload is for a pre-existing file. impl = dynamic_pointer_cast(file->p_); if (impl->has_conflict()) { state_ = error; qf_.reportException(ConflictException("Uploader::finish_upload(): ETag mismatch")); qf_.reportFinished(); return; } } else { // This uploader was returned by FolderImpl::create_file(). int fd = open(path_.toStdString().c_str(), O_WRONLY | O_CREAT | O_EXCL, 0600); // Fails if path already exists. if (fd == -1) { state_ = error; QString msg = "Uploader::finish_upload(): item with name \"" + path_ + "\" exists already"; QString name = QString::fromStdString(boost::filesystem::path(path_.toStdString()).filename().native()); qf_.reportException(ExistsException(msg, path_, name)); qf_.reportFinished(); return; } if (close(fd) == -1) { // LCOV_EXCL_START state_ = error; QString msg = "Uploader::finish_upload(): cannot close tmp file: " + QString::fromStdString(storage::internal::safe_strerror(errno)); qf_.reportException(ResourceException(msg, errno)); qf_.reportFinished(); return; // LCOV_EXCL_STOP } file = FileImpl::make_file(path_, root_); impl = dynamic_pointer_cast(file->p_); } if (!output_file_->flush()) { // LCOV_EXCL_START state_ = error; QString msg = "Uploader::finish_upload(): cannot flush output file: " + output_file_->errorString(); qf_.reportException(ResourceException(msg, output_file_->error())); qf_.reportFinished(); return; // LCOV_EXCL_STOP } // Link the anonymous tmp file into the file system. auto new_path = file->native_identity().toStdString(); if (use_linkat_) { auto old_path = string("/proc/self/fd/") + std::to_string(tmp_fd_.get()); ::unlink(new_path.c_str()); // linkat() will not remove existing file: http://lwn.net/Articles/559969/ if (linkat(-1, old_path.c_str(), tmp_fd_.get(), new_path.c_str(), AT_SYMLINK_FOLLOW) == -1) { // LCOV_EXCL_START int error_code = errno; state_ = error; QString msg = "Uploader::finish_upload(): linkat \"" + QString::fromStdString(old_path) + "\" to \"" + file->native_identity() + "\" failed: " + QString::fromStdString(storage::internal::safe_strerror(errno)); qf_.reportException(ResourceException(msg, error_code)); qf_.reportFinished(); return; // LCOV_EXCL_STOP } } else { // LCOV_EXCL_START auto old_path = output_file_->fileName().toStdString(); if (rename(old_path.c_str(), new_path.c_str()) == -1) { int error_code = errno; state_ = error; QString msg = "Uploader::finish_upload(): rename \"" + QString::fromStdString(old_path) + "\" to \"" + file->native_identity() + "\" failed: " + QString::fromStdString(storage::internal::safe_strerror(errno)); qf_.reportException(ResourceException(msg, error_code)); qf_.reportFinished(); return; } // LCOV_EXCL_STOP } state_ = finalized; output_file_->close(); impl->set_timestamps(); qf_.reportResult(file); qf_.reportFinished(); } // LCOV_EXCL_START void UploadWorker::handle_error(QString const& msg, int error_code) { if (state_ == in_progress) { output_file_->close(); read_socket_->abort(); } state_ = error; error_msg_ = "Uploader: " + msg; error_code_ = error_code; do_finish(); } // LCOV_EXCL_STOP UploadThread::UploadThread(UploadWorker* worker) : worker_(worker) { } void UploadThread::run() { worker_->start_uploading(); exec(); } UploaderImpl::UploaderImpl(weak_ptr file, int64_t size, QString const& path, ConflictPolicy policy, weak_ptr root) : UploaderBase(policy, size) , write_socket_(new QLocalSocket, [](QLocalSocket* s){ s->deleteLater(); }) { // Set up socket pair. int fds[2]; int rc = socketpair(AF_UNIX, SOCK_STREAM, 0, fds); if (rc == -1) { // LCOV_EXCL_START QString msg = "Uploader: cannot create socket pair: " + QString::fromStdString(storage::internal::safe_strerror(errno)); qf_.reportException(ResourceException(msg, errno)); qf_.reportFinished(); return; // LCOV_EXCL_STOP } // Write socket is for the client. write_socket_->setSocketDescriptor(fds[1], QLocalSocket::ConnectedState, QIODevice::WriteOnly); // We should be able to close the read channel of the write socket, // but doing this causes the disconnected signal to go AWOL. // Possibly a problem wit QLocalSocket. // shutdown(fds[1], SHUT_RD); // Create worker and connect slots, so we can signal the worker when the client calls // finish_download() or cancel(); QFutureInterface worker_initialized; worker_.reset(new UploadWorker(fds[0], file, size, path, policy, root, qf_, worker_initialized)); connect(this, &UploaderImpl::do_finish, worker_.get(), &UploadWorker::do_finish); connect(this, &UploaderImpl::do_cancel, worker_.get(), &UploadWorker::do_cancel); // Create upload thread and make sure that worker slots are called from the upload thread. upload_thread_.reset(new UploadThread(worker_.get())); worker_->moveToThread(upload_thread_.get()); upload_thread_->start(); worker_initialized.waitForFinished(); } UploaderImpl::~UploaderImpl() { if (upload_thread_->isRunning()) { Q_EMIT do_cancel(); upload_thread_->wait(); } } shared_ptr UploaderImpl::socket() const { return write_socket_; } QFuture UploaderImpl::finish_upload() { if (write_socket_->state() == QLocalSocket::ConnectedState) { write_socket_->disconnectFromServer(); } return qf_.future(); } QFuture UploaderImpl::cancel() noexcept { Q_EMIT do_cancel(); write_socket_->abort(); return qf_.future(); } } // namespace local_client } // namespace intternal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/internal/local_client/storage_exception.cpp000066400000000000000000000047041521521330000316240ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace local_client { using namespace boost::filesystem; void throw_storage_exception(QString const& method, std::exception_ptr ep) { int error_code = errno; try { std::rethrow_exception(ep); } catch (StorageException const&) { throw; } catch (filesystem_error const& e) { QString msg = method + ": " + e.what(); switch (e.code().value()) { case EACCES: case EPERM: { throw PermissionException(msg); } case EDQUOT: case ENOSPC: { throw QuotaException(msg); // Too messy to cover with a test case. // LCOV_EXCL_LINE } default: { throw ResourceException(msg, e.code().value()); } } } // LCOV_EXCL_START catch (std::exception const& e) { QString msg = method + ": " + e.what(); throw ResourceException(msg, error_code); } // LCOV_EXCL_STOP } void throw_storage_exception(QString const& method, std::exception_ptr ep, QString const& key) { try { std::rethrow_exception(ep); } catch (filesystem_error const& e) { if (e.code().value() == ENOENT) { throw NotExistsException(method + ": " + e.what(), key); } } throw_storage_exception(method, ep); } } // namespace local_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/internal/remote_client/000077500000000000000000000000001521521330000255725ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/src/qt/client/internal/remote_client/AccountImpl.cpp000066400000000000000000000072751521521330000305270ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include "ProviderInterface.h" #include #include #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace remote_client { AccountImpl::AccountImpl(weak_ptr const& runtime, QString const& bus_name, QString const& object_path, QString const& owner, QString const& owner_id, QString const& description) : AccountBase(runtime) , owner_(owner) , owner_id_(owner_id) , description_(description) { auto rt_impl = dynamic_pointer_cast(runtime.lock()->p_); assert(rt_impl); provider_.reset(new ProviderInterface(bus_name, object_path, rt_impl->connection())); } QString AccountImpl::owner() const { runtime(); // Throws if runtime was destroyed. return owner_; } QString AccountImpl::owner_id() const { runtime(); // Throws if runtime was destroyed. return owner_id_; } QString AccountImpl::description() const { runtime(); // Throws if runtime was destroyed. return description_; } QFuture> AccountImpl::roots() { try { runtime(); // Throws if runtime was destroyed. } catch (RuntimeDestroyedException const&) { return make_exceptional_future>(RuntimeDestroyedException("Account::roots()")); } auto reply = provider_->Roots(QList()); auto process_reply = [this](decltype(reply) const& reply, QFutureInterface>& qf) { try { this->runtime(); } catch (RuntimeDestroyedException const& e) { qf.reportException(RuntimeDestroyedException("Account::roots()")); qf.reportFinished(); return; } QVector> roots; auto metadata = reply.value(); for (auto const& md : metadata) { if (md.type != ItemType::root) { // TODO: log impossible item type here continue; // LCOV_EXCL_LINE } auto root = RootImpl::make_root(md, public_instance_); roots.append(root); } roots_ = roots; qf.reportResult(roots); qf.reportFinished(); }; auto handler = new Handler>(this, reply, process_reply); return handler->future(); } shared_ptr AccountImpl::provider() const noexcept { return provider_; } } // namespace local_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/internal/remote_client/CMakeLists.txt000066400000000000000000000015351521521330000303360ustar00rootroot00000000000000set(QT_CLIENT_LIB_REMOTE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/AccountImpl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/dbusmarshal.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DownloaderImpl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/FileImpl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/FolderImpl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/HandlerBase.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ItemImpl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/RootImpl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Runtime_create.cpp ${CMAKE_CURRENT_SOURCE_DIR}/RuntimeImpl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/UploaderImpl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/validate.cpp ${CMAKE_SOURCE_DIR}/include/lomiri/storage/qt/client/internal/remote_client/HandlerBase.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/qt/client/internal/remote_client/RuntimeImpl.h PARENT_SCOPE) lomiri-storage-framework-0.5.0/src/qt/client/internal/remote_client/DownloaderImpl.cpp000066400000000000000000000063271521521330000312260ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include "ProviderInterface.h" #include #include #include #include #include using namespace lomiri::storage::qt::client; using namespace std; namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace remote_client { DownloaderImpl::DownloaderImpl(QString const& download_id, QDBusUnixFileDescriptor fd, shared_ptr const& file, shared_ptr const& provider) : DownloaderBase(file) , download_id_(download_id) , fd_(fd) , file_(file) , provider_(provider) , read_socket_(new QLocalSocket, [](QLocalSocket* s){ s->deleteLater(); }) { assert(!download_id.isEmpty()); assert(fd.isValid()); assert(provider); read_socket_->setSocketDescriptor(fd.fileDescriptor(), QLocalSocket::ConnectedState, QIODevice::ReadOnly); } DownloaderImpl::~DownloaderImpl() { read_socket_->abort(); } shared_ptr DownloaderImpl::file() const { return file_; } shared_ptr DownloaderImpl::socket() const { return read_socket_; } QFuture DownloaderImpl::finish_download() { auto reply = provider_->FinishDownload(download_id_); auto process_reply = [this](decltype(reply) const&, QFutureInterface& qf) { qf.reportFinished(); }; auto handler = new Handler(this, reply, process_reply); return handler->future(); } QFuture DownloaderImpl::cancel() noexcept { read_socket_->abort(); QString msg = "Downloader::finish_download(): download of " + file_->name() + " was cancelled"; return make_exceptional_future(CancelledException(msg)); } Downloader::SPtr DownloaderImpl::make_downloader(QString const& download_id, QDBusUnixFileDescriptor fd, shared_ptr const& file, shared_ptr const& provider) { auto impl = new DownloaderImpl(download_id, fd, file, provider); Downloader::SPtr downloader(new Downloader(impl)); return downloader; } } // namespace remote_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/internal/remote_client/FileImpl.cpp000066400000000000000000000126341521521330000300050ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include "ProviderInterface.h" #include #include #include #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace remote_client { FileImpl::FileImpl(storage::internal::ItemMetadata const& md) : ItemBase(md.item_id, ItemType::file) , FileBase(md.item_id) , ItemImpl(md, ItemType::file) { } int64_t FileImpl::size() const { throw_if_destroyed("File::size()"); return md_.metadata.value(metadata::SIZE_IN_BYTES).toLongLong(); } QFuture> FileImpl::create_uploader(ConflictPolicy policy, int64_t size) { try { throw_if_destroyed("File::create_uploader()"); } catch (StorageException const& e) { return make_exceptional_future>(e); } if (size < 0) { QString msg = "File::create_uploader(): size must be >= 0"; return make_exceptional_future>(InvalidArgumentException(msg)); } QString old_etag = policy == ConflictPolicy::overwrite ? "" : md_.etag; auto prov = provider(); auto reply = prov->Update(md_.item_id, size, old_etag, QList()); auto process_reply = [this, size, old_etag, prov](decltype(reply) const& reply, QFutureInterface>& qf) { auto root = get_root(); if (!root) { qf.reportException(RuntimeDestroyedException("File::create_uploader()")); qf.reportFinished(); return; } auto upload_id = reply.argumentAt<0>(); auto fd = reply.argumentAt<1>(); if (fd.fileDescriptor() < 0) { // TODO: log server error here QString msg = "File::create_uploader(): impossible file descriptor returned by server: " + QString::number(fd.fileDescriptor()); qf.reportException(LocalCommsException(msg)); qf.reportFinished(); return; } auto uploader = UploaderImpl::make_uploader(upload_id, fd, size, old_etag, root, prov); qf.reportResult(uploader); qf.reportFinished(); }; auto handler = new Handler>(this, reply, process_reply); return handler->future(); } QFuture> FileImpl::create_downloader() { try { throw_if_destroyed("File::create_downloader()"); } catch (StorageException const& e) { return make_exceptional_future>(e); } auto prov = provider(); auto reply = prov->Download(md_.item_id, ""); auto process_reply = [this, prov](QDBusPendingReply const& reply, QFutureInterface>& qf) { try { throw_if_destroyed("File::create_downloader()"); } catch (StorageException const& e) { qf.reportException(e); qf.reportFinished(); return; } auto download_id = reply.argumentAt<0>(); auto fd = reply.argumentAt<1>(); if (fd.fileDescriptor() < 0) { // TODO: log server error here QString msg = "File::create_downloader(): impossible file descriptor returned by server: " + QString::number(fd.fileDescriptor()); qf.reportException(LocalCommsException(msg)); qf.reportFinished(); return; } auto file = dynamic_pointer_cast(public_instance_.lock()); // TODO: provider may not be around anymore if the runtime was destroyed. auto downloader = DownloaderImpl::make_downloader(download_id, fd, file, prov); qf.reportResult(downloader); qf.reportFinished(); }; auto handler = new Handler>(this, reply, process_reply); return handler->future(); } File::SPtr FileImpl::make_file(storage::internal::ItemMetadata const& md, weak_ptr root) { auto impl = new FileImpl(md); File::SPtr file(new File(impl)); impl->set_root(root); impl->set_public_instance(file); return file; } } // namespace remote_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/internal/remote_client/FolderImpl.cpp000066400000000000000000000214131521521330000303340ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include "ProviderInterface.h" #include #include #include #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace remote_client { FolderImpl::FolderImpl(storage::internal::ItemMetadata const& md) : ItemBase(md.item_id, ItemType::folder) , FolderBase(md.item_id, ItemType::folder) , ItemImpl(md, ItemType::folder) { } FolderImpl::FolderImpl(storage::internal::ItemMetadata const& md, ItemType type) : ItemBase(md.item_id, type) , FolderBase(md.item_id, type) , ItemImpl(md, type) { } QFuture>> FolderImpl::list() const { try { throw_if_destroyed("Folder::list()"); } catch (StorageException const& e) { return make_exceptional_future>>(e); } auto prov = provider(); auto reply = prov->List(md_.item_id, "", QList()); // Sorry for the mess, but we can't use auto for the lambda because it calls itself, // and the compiler can't deduce the type of the lambda while it's still parsing the lambda body. function>>&)> process_reply = [this, prov, &process_reply](decltype(reply) const& reply, QFutureInterface>>& qf) { auto root = get_root(); if (!root) { qf.reportException(RuntimeDestroyedException("Folder::list()")); qf.reportFinished(); return; } QVector> items; auto metadata = reply.argumentAt<0>(); for (auto const& md : metadata) { try { validate("Folder::list()", md); } catch (StorageException const& e) { qf.reportException(e); qf.reportFinished(); return; } if (md.type == ItemType::root) { // TODO: log server error here continue; } items.append(ItemImpl::make_item(md, root)); } qf.reportResult(items, qf.resultCount()); QString token = reply.argumentAt<1>(); if (token.isEmpty()) { qf.reportFinished(); // This was the last lot of results. } else { // Request next lot. auto next_reply = prov->List(md_.item_id, token, QList()); new Handler>>(const_cast(this), next_reply, process_reply); } }; auto handler = new Handler>>(const_cast(this), reply, process_reply); return handler->future(); } QFuture>> FolderImpl::lookup(QString const& name) const { try { throw_if_destroyed("Folder::lookup()"); } catch (StorageException const& e) { return make_exceptional_future>>(e); } auto prov = provider(); auto reply = prov->Lookup(md_.item_id, name, QList()); auto process_reply = [this, name](decltype(reply) const& reply, QFutureInterface>>& qf) { auto root = get_root(); if (!root) { qf.reportException(RuntimeDestroyedException("Folder::lookup()")); qf.reportFinished(); return; } QVector items; auto metadata = reply.value(); for (auto const& md : metadata) { try { validate("Folder::lookup()", md); } catch (StorageException const& e) { qf.reportException(e); qf.reportFinished(); return; } if (md.type == ItemType::root) { // TODO: log server error here continue; } items.append(ItemImpl::make_item(md, root)); } if (items.isEmpty()) { qf.reportException(NotExistsException("Folder::lookup(): no such item: " + name, name)); qf.reportFinished(); return; } qf.reportResult(items); qf.reportFinished(); }; auto handler = new Handler>>(const_cast(this), reply, process_reply); return handler->future(); } QFuture> FolderImpl::create_folder(QString const& name) { try { throw_if_destroyed("Folder::create_folder()"); } catch (StorageException const& e) { return make_exceptional_future>(e); } auto prov = provider(); auto reply = prov->CreateFolder(md_.item_id, name, QList()); auto process_reply = [this](decltype(reply) const& reply, QFutureInterface>& qf) { auto root = get_root(); if (!root) { qf.reportException(RuntimeDestroyedException("Folder::create_folder()")); qf.reportFinished(); return; } shared_ptr item; auto md = reply.value(); try { validate("Folder::create_folder()", md); } catch (StorageException const& e) { qf.reportException(e); qf.reportFinished(); return; } if (md.type != ItemType::folder) { // TODO: log server error here QString msg = "File::create_folder(): impossible item type returned by server: " + QString::number(int(md.type)); qf.reportException(LocalCommsException(msg)); qf.reportFinished(); return; } qf.reportResult(FolderImpl::make_folder(md, root)); qf.reportFinished(); }; auto handler = new Handler>(this, reply, process_reply); return handler->future(); } QFuture> FolderImpl::create_file(QString const& name, int64_t size) { try { throw_if_destroyed("Folder::create_file()"); } catch (StorageException const& e) { return make_exceptional_future>(e); } if (size < 0) { QString msg = "Folder::create_file(): size must be >= 0"; return make_exceptional_future>(InvalidArgumentException(msg)); } auto prov = provider(); auto reply = prov->CreateFile(md_.item_id, name, size, "application/octet-stream", false, QList()); auto process_reply = [this, size](decltype(reply) const& reply, QFutureInterface>& qf) { auto root = get_root(); if (!root) { qf.reportException(RuntimeDestroyedException("Folder::create_file()")); qf.reportFinished(); return; } auto upload_id = reply.argumentAt<0>(); auto fd = reply.argumentAt<1>(); auto uploader = UploaderImpl::make_uploader(upload_id, fd, size, "", root, provider()); qf.reportResult(uploader); qf.reportFinished(); }; auto handler = new Handler>(this, reply, process_reply); return handler->future(); } shared_ptr FolderImpl::make_folder(storage::internal::ItemMetadata const& md, weak_ptr root) { assert(md.type == ItemType::folder); assert(root.lock()); auto impl = new FolderImpl(md); shared_ptr folder(new Folder(impl)); impl->set_root(root); impl->set_public_instance(folder); return folder; } } // namespace remote_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/internal/remote_client/HandlerBase.cpp000066400000000000000000000032301521521330000304440ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #pragma GCC diagnostic pop #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace remote_client { HandlerBase::HandlerBase(QObject* parent, QDBusPendingCall const& call, function const& closure) : QObject(parent) , watcher_(call) , closure_(closure) { assert(closure); connect(&watcher_, &QDBusPendingCallWatcher::finished, this, &HandlerBase::finished); } void HandlerBase::finished(QDBusPendingCallWatcher* call) { deleteLater(); closure_(*call); } } // namespace remote_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/internal/remote_client/ItemImpl.cpp000066400000000000000000000217101521521330000300170ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include "ProviderInterface.h" #include #include #include #include #include #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace remote_client { ItemImpl::ItemImpl(storage::internal::ItemMetadata const& md, ItemType type) : ItemBase(md.item_id, type) , md_(md) { } QString ItemImpl::name() const { throw_if_destroyed("Item::name()"); return md_.name; } QString ItemImpl::etag() const { throw_if_destroyed("Item::etag()"); return md_.etag; } QVariantMap ItemImpl::metadata() const { throw_if_destroyed("Item::metadata()"); // TODO: need to agree on metadata representation return QVariantMap(); } QDateTime ItemImpl::last_modified_time() const { throw_if_destroyed("Item::last_modified_time()"); return QDateTime::fromString(md_.metadata.value(metadata::LAST_MODIFIED_TIME).toString(), Qt::ISODate); } QFuture> ItemImpl::copy(shared_ptr const& new_parent, QString const& new_name) { if (!new_parent) { QString msg = "Item::copy(): new_parent cannot be nullptr"; return internal::make_exceptional_future>(InvalidArgumentException(msg)); } auto new_parent_impl = dynamic_pointer_cast(new_parent->p_); try { throw_if_destroyed("Item::copy()"); new_parent_impl->throw_if_destroyed("Item::copy()"); } catch (StorageException const& e) { return make_exceptional_future>(e); } auto prov = provider(); auto reply = prov->Copy(md_.item_id, new_parent->native_identity(), new_name, QList()); auto process_reply = [this](decltype(reply) const& reply, QFutureInterface>& qf) { auto root = get_root(); if (!root) { qf.reportException(RuntimeDestroyedException("Item::copy()")); qf.reportFinished(); return; } auto md = reply.value(); try { validate("Item::copy()", md); } catch (StorageException const& e) { qf.reportException(e); qf.reportFinished(); return; } if (md.type == ItemType::root) { // TODO: log server error here QString msg = "File::create_folder(): impossible item type returned by server: " + QString::number(int(md.type)); qf.reportException(LocalCommsException(msg)); qf.reportFinished(); return; } qf.reportResult(ItemImpl::make_item(md, root)); qf.reportFinished(); return; }; auto handler = new Handler>(this, reply, process_reply); return handler->future(); } QFuture> ItemImpl::move(shared_ptr const& new_parent, QString const& new_name) { if (!new_parent) { QString msg = "Item::move(): new_parent cannot be nullptr"; return internal::make_exceptional_future>(InvalidArgumentException(msg)); } auto new_parent_impl = dynamic_pointer_cast(new_parent->p_); try { throw_if_destroyed("Item::move()"); new_parent_impl->throw_if_destroyed("Item::move()"); } catch (StorageException const& e) { return make_exceptional_future>(e); } auto prov = provider(); if (!prov) { return make_exceptional_future>(RuntimeDestroyedException("Item::move()")); } auto reply = prov->Move(md_.item_id, new_parent->native_identity(), new_name, QList()); auto process_reply = [this](decltype(reply) const& reply, QFutureInterface>& qf) { auto root = get_root(); if (!root) { qf.reportException(RuntimeDestroyedException("Item::move()")); qf.reportFinished(); return; } auto md = reply.value(); try { validate("Item::move()", md); } catch (StorageException const& e) { qf.reportException(e); qf.reportFinished(); return; } if (md.type == ItemType::root) { // TODO: log server error here QString msg = "Item::move(): impossible root item returned by server"; qf.reportException(LocalCommsException(msg)); qf.reportFinished(); return; } qf.reportResult(ItemImpl::make_item(md, root)); qf.reportFinished(); }; auto handler = new Handler>(this, reply, process_reply); return handler->future(); } QFuture> ItemImpl::parents() const { try { throw_if_destroyed("Item::parents()"); } catch (StorageException const& e) { return make_exceptional_future>(e); } // TODO, need different metadata representation, affects xml return QFuture>(); } QVector ItemImpl::parent_ids() const { throw_if_destroyed("Item::parent_ids()"); // TODO, need different metadata representation, affects xml // We changed ItemMetadata to contain a QList for the v2 API, // so we copy here. QVector ids; for (auto const& id : md_.parent_ids) { ids.append(id); } return ids; } QFuture ItemImpl::delete_item() { try { throw_if_destroyed("Item::delete_item()"); } catch (StorageException const& e) { return internal::make_exceptional_future(e); } auto prov = provider(); auto reply = prov->Delete(md_.item_id); auto process_reply = [this](decltype(reply) const&, QFutureInterface& qf) { deleted_ = true; qf.reportFinished(); }; auto handler = new Handler(this, reply, process_reply); return handler->future(); } QDateTime ItemImpl::creation_time() const { throw_if_destroyed("Item::creation_time()"); return QDateTime::fromString(md_.metadata.value(metadata::CREATION_TIME).toString(), Qt::ISODate); } MetadataMap ItemImpl::native_metadata() const { throw_if_destroyed("Item::native_metadata()"); // TODO: need to agree on metadata representation return MetadataMap(); } bool ItemImpl::equal_to(ItemBase const& other) const noexcept { auto other_impl = dynamic_cast(&other); assert(other_impl); if (this == other_impl) { return true; } if (deleted_ || other_impl->deleted_) { return false; } return identity_ == other_impl->identity_; } shared_ptr ItemImpl::provider() const noexcept { auto root = dynamic_pointer_cast(root_.lock()); if (!root) { return nullptr; } auto root_impl = dynamic_pointer_cast(root->p_); auto account = root_impl->account_.lock(); if (!account) { return nullptr; } auto account_impl = dynamic_pointer_cast(account->p_); return account_impl->provider(); } shared_ptr ItemImpl::make_item(storage::internal::ItemMetadata const& md, std::weak_ptr root) { assert(md.type == ItemType::file || md.type == ItemType::folder); shared_ptr item; switch (md.type) { case ItemType::file: { item = FileImpl::make_file(md, root); break; } case ItemType::folder: { item = FolderImpl::make_folder(md, root); break; } default: { abort(); // LCOV_EXCL_LINE // Impossible } } assert(item); return item; } } // namespace remote_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/internal/remote_client/RootImpl.cpp000066400000000000000000000115231521521330000300450ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include "ProviderInterface.h" #include #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace remote_client { RootImpl::RootImpl(storage::internal::ItemMetadata const& md, weak_ptr const& account) : ItemBase(md.item_id, ItemType::root) , FolderBase(md.item_id, ItemType::root) , RootBase(md.item_id, account) , ItemImpl(md, ItemType::root) , FolderImpl(md, ItemType::root) { } QFuture> RootImpl::parents() const { try { throw_if_destroyed("Root::parents()"); } catch (StorageException const& e) { return make_exceptional_future>(e); } return make_ready_future(QVector()); // For the root, we return an empty vector. } QVector RootImpl::parent_ids() const { throw_if_destroyed("Root::parent_ids()"); return QVector(); // For the root, we return an empty vector. } QFuture RootImpl::delete_item() { try { throw_if_destroyed("Item::delete_item()"); } catch (StorageException const& e) { return make_exceptional_future(e); } // Cannot delete root. return make_exceptional_future(LogicException("Item::delete_item(): cannot delete root folder")); } QFuture RootImpl::free_space_bytes() const { try { throw_if_destroyed("Root::free_space_bytes()"); } catch (StorageException const& e) { return make_exceptional_future(e); } // TODO, need to refresh metadata here instead. return make_ready_future(int64_t(1)); } QFuture RootImpl::used_space_bytes() const { try { throw_if_destroyed("Root::used_space_bytes()"); } catch (StorageException const& e) { return make_exceptional_future(e); } // TODO, need to refresh metadata here instead. return make_ready_future(int64_t(1)); } QFuture RootImpl::get(QString native_identity) const { try { throw_if_destroyed("Root::get()"); } catch (StorageException const& e) { return make_exceptional_future(e); } auto prov = provider(); auto reply = prov->Metadata(native_identity, QList()); auto process_reply = [this](decltype(reply) const& reply, QFutureInterface& qf) { shared_ptr acc; try { acc = account(); } catch (RuntimeDestroyedException const&) { qf.reportException(RuntimeDestroyedException("Root::get()")); qf.reportFinished(); return; } auto md = reply.value(); try { validate("Root::get()", md); } catch (StorageException const& e) { qf.reportException(e); qf.reportFinished(); return; } Item::SPtr item; if (md.type == ItemType::root) { item = make_root(md, acc); } else { // acc owns the root, so the root weak_ptr is guaranteed to be lockable. item = ItemImpl::make_item(md, root_); } qf.reportResult(item); qf.reportFinished(); }; auto handler = new Handler(const_cast(this), reply, process_reply); return handler->future(); } Root::SPtr RootImpl::make_root(storage::internal::ItemMetadata const& md, weak_ptr const& account) { assert(md.type == ItemType::root); assert(account.lock()); auto impl = new RootImpl(md, account); Root::SPtr root(new Root(impl)); impl->set_root(root); impl->set_public_instance(root); return root; } } // namespace remote_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/internal/remote_client/RuntimeImpl.cpp000066400000000000000000000136351521521330000305530ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include #include #include #include #include #include // TODO: Hack until we can use the registry instead #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wold-style-cast" #include #pragma GCC diagnostic pop #include #include #include using namespace std; namespace { static const map BUS_NAMES = { { "storage-provider-test", "com.lomiri.StorageFramework.Provider.ProviderTest" }, { "storage-provider-mcloud", "com.lomiri.StorageFramework.Provider.McloudProvider" }, { "storage-provider-owncloud", "com.lomiri.StorageFramework.Provider.OwnCloud" }, { "storage-provider-onedrive", "com.lomiri.StorageFramework.Provider.OnedriveProvider" }, { "storage-provider-gdrive", "com.lomiri.StorageFramework.Provider.GdriveProvider" }, { "storage-provider-nextcloud", "com.lomiri.StorageFramework.Provider.Nextcloud" }, }; } // namespace namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace remote_client { RuntimeImpl::RuntimeImpl(QDBusConnection const& bus) : conn_(bus) { if (!conn_.isConnected()) { throw LocalCommsException("Runtime: cannot connect to session bus"); // LCOV_EXCL_LINE } qDBusRegisterMetaType(); qDBusRegisterMetaType>(); } RuntimeImpl::~RuntimeImpl() { try { shutdown(); } // LCOV_EXCL_START catch (std::exception const& e) { qCritical() << "shutdown error" << e.what(); } // LCOV_EXCL_STOP } void RuntimeImpl::shutdown() { if (destroyed_) { return; } destroyed_ = true; conn_.disconnectFromBus(conn_.name()); } QFuture> RuntimeImpl::accounts() { if (destroyed_) { qf_.reportException(RuntimeDestroyedException("Runtime::accounts()")); qf_.reportFinished(); return qf_.future(); } if (!manager_) { manager_.reset(new OnlineAccounts::Manager("", conn_)); connect(manager_.get(), &OnlineAccounts::Manager::ready, this, &RuntimeImpl::manager_ready); connect(&timer_, &QTimer::timeout, this, &RuntimeImpl::timeout); timer_.setSingleShot(true); timer_.start(5000); } qf_.reportStarted(); return qf_.future(); } QDBusConnection& RuntimeImpl::connection() { return conn_; } void RuntimeImpl::manager_ready() { if (destroyed_) { // LCOV_EXCL_START qf_.reportException(RuntimeDestroyedException("Runtime::accounts()")); qf_.reportFinished(); return; // LCOV_EXCL_STOP } timer_.stop(); try { QVector accounts; for (auto const map_entry : BUS_NAMES) { auto service_id = map_entry.first; for (auto const& a : manager_->availableAccounts(service_id)) { auto object_path = QStringLiteral("/provider/%1").arg(a->id()); try { auto bus_name = map_entry.second; accounts.append(make_account(bus_name, object_path, "", a->serviceId(), a->displayName())); } catch (LocalCommsException const& e) { qDebug() << "RuntimeImpl: ignoring non-existent provider" << a->serviceId(); } } } accounts_ = accounts; qf_.reportResult(accounts); } // LCOV_EXCL_START catch (StorageException const& e) { qf_.reportException(e); } // LCOV_EXCL_STOP qf_.reportFinished(); } // LCOV_EXCL_START void RuntimeImpl::timeout() { qf_.reportException(ResourceException("Runtime::accounts(): timeout retrieving Online accounts", 0)); qf_.reportFinished(); } // LCOV_EXCL_STOP shared_ptr RuntimeImpl::make_test_account(QString const& bus_name, QString const& object_path) { return make_account(bus_name, object_path, "", "", ""); } shared_ptr RuntimeImpl::make_account(QString const& bus_name, QString const& object_path, QString const& owner, QString const& owner_id, QString const& description) { auto impl = new AccountImpl(public_instance_, bus_name, object_path, owner, owner_id, description); Account::SPtr acc(new Account(impl)); impl->set_public_instance(acc); return acc; } } // namespace local_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/internal/remote_client/Runtime_create.cpp000066400000000000000000000024051521521330000312450ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace client { Runtime::SPtr Runtime::create(QDBusConnection const& bus) { auto impl = new internal::remote_client::RuntimeImpl(bus); Runtime::SPtr runtime(new Runtime(impl)); impl->set_public_instance(weak_ptr(runtime)); return runtime; } } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/internal/remote_client/UploaderImpl.cpp000066400000000000000000000110071521521330000306720ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include "ProviderInterface.h" #include #include #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace remote_client { UploaderImpl::UploaderImpl(QString const& upload_id, QDBusUnixFileDescriptor fd, int64_t size, QString const& old_etag, weak_ptr root, shared_ptr const& provider) : UploaderBase(old_etag == "" ? ConflictPolicy::overwrite : ConflictPolicy::error_if_conflict, size) , upload_id_(upload_id) , fd_(fd) , old_etag_(old_etag) , root_(root.lock()) , provider_(provider) , write_socket_(new QLocalSocket, [](QLocalSocket* s){ s->deleteLater(); }) , state_(uploading) { assert(!upload_id.isEmpty()); assert(fd.isValid()); assert(size >= 0); assert(root_); assert(provider); assert(fd.isValid()); write_socket_->setSocketDescriptor(fd_.fileDescriptor(), QLocalSocket::ConnectedState, QIODevice::WriteOnly); } UploaderImpl::~UploaderImpl() { if (state_ == uploading) { provider_->CancelUpload(upload_id_); } } shared_ptr UploaderImpl::socket() const { return write_socket_; } QFuture> UploaderImpl::finish_upload() { state_ = finalized; auto reply = provider_->FinishUpload(upload_id_); auto process_reply = [this](decltype(reply) const& reply, QFutureInterface>& qf) { auto md = reply.value(); try { validate("Uploader::finish_upload()", md); } catch (StorageException const& e) { qf.reportException(e); qf.reportFinished(); return; } if (md.type != ItemType::file) { // TODO: log server error here QString msg = "Uploader::finish_upload(): impossible item type returned by server: " + QString::number(int(md.type)); qf.reportException(LocalCommsException(msg)); qf.reportFinished(); return; } qf.reportResult(FileImpl::make_file(md, root_)); qf.reportFinished(); }; write_socket_->disconnectFromServer(); auto handler = new Handler>(this, reply, process_reply); return handler->future(); } QFuture UploaderImpl::cancel() noexcept { state_ = finalized; auto reply = provider_->CancelUpload(upload_id_); auto process_reply = [this](decltype(reply) const&, QFutureInterface& qf) { qf.reportFinished(); }; write_socket_->abort(); auto handler = new Handler(this, reply, process_reply); return handler->future(); } Uploader::SPtr UploaderImpl::make_uploader(QString const& upload_id, QDBusUnixFileDescriptor fd, int64_t size, QString const& old_etag, weak_ptr root, shared_ptr const& provider) { assert(provider); auto impl = new UploaderImpl(upload_id, fd, size, old_etag, root, provider); Uploader::SPtr uploader(new Uploader(impl)); return uploader; } } // namespace remote_client } // namespace intternal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/internal/remote_client/dbusmarshal.cpp000066400000000000000000000110001521521330000305730ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include #include #include #include #include #include #include #include using namespace lomiri::storage::internal; using namespace std; namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace remote_client { namespace { template exception_ptr make_exception(QDBusPendingCallWatcher const& call) { QDBusPendingReply reply = call; auto msg = reply.argumentAt<0>(); return make_exception_ptr(T(msg)); } template<> exception_ptr make_exception(QDBusPendingCallWatcher const& call) { QDBusPendingReply reply = call; auto msg = reply.argumentAt<0>(); auto key = reply.argumentAt<1>(); return make_exception_ptr(NotExistsException(msg, key)); } template<> exception_ptr make_exception(QDBusPendingCallWatcher const& call) { QDBusPendingReply reply = call; auto msg = reply.argumentAt<0>(); auto id = reply.argumentAt<1>(); auto name = reply.argumentAt<2>(); return make_exception_ptr(ExistsException(msg, id, name)); } template<> exception_ptr make_exception(QDBusPendingCallWatcher const& call) { QDBusPendingReply reply = call; auto msg = reply.argumentAt<0>(); auto error_code = reply.argumentAt<1>(); return make_exception_ptr(ResourceException(msg, error_code)); } static const map> exception_factories = { { "NotExistsException", make_exception }, { "ExistsException", make_exception }, { "ResourceException", make_exception }, { "RemoteCommsException", make_exception }, { "ConflictException", make_exception }, { "PermissionException", make_exception }, { "QuotaException", make_exception }, { "CancelledException", make_exception }, { "LogicException", make_exception }, { "InvalidArgumentException", make_exception }, { "UnknownException", make_exception } // Yes, LocalCommsException is intentional }; } // namespace std::exception_ptr unmarshal_exception(QDBusPendingCallWatcher const& call) { assert(call.isError()); int err = call.error().type(); if (err != QDBusError::Other) { return make_exception_ptr(LocalCommsException(call.error().message())); } auto exception_type = call.error().name(); if (!exception_type.startsWith(DBUS_ERROR_PREFIX)) { QString msg = "unmarshal_exception(): unknown exception type received from server: " + exception_type + ": " + call.error().message(); return make_exception_ptr(LocalCommsException(msg)); } exception_type = exception_type.remove(0, strlen(DBUS_ERROR_PREFIX)); auto factory_it = exception_factories.find(exception_type); if (factory_it == exception_factories.end()) { QString msg = "unmarshal_exception(): unknown exception type received from server: " + exception_type + ": " + call.error().message(); return make_exception_ptr(LocalCommsException(msg)); } return factory_it->second(call); } } // namespace remote_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/internal/remote_client/validate.cpp000066400000000000000000000126551521521330000301000ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include #include #include #include using namespace lomiri::storage::internal; using namespace std; namespace lomiri { namespace storage { namespace qt { namespace client { namespace internal { namespace remote_client { namespace { // Check that actual type and value match the expect type and value for a particular metadata entry. void validate_type_and_value(QString const& prefix, QMapIterator actual, unordered_map::const_iterator known) { using namespace lomiri::storage::metadata; switch (known->second) { case MetadataType::iso_8601_date_time: { if (actual.value().type() != QVariant::String) { throw LocalCommsException(prefix + actual.key() + ": expected value of type String, " " but received value of type " + actual.value().typeName()); } QDateTime dt = QDateTime::fromString(actual.value().toString(), Qt::ISODate); if (!dt.isValid()) { throw LocalCommsException(prefix + actual.key() + ": value \"" + actual.value().toString() + "\" does not parse as ISO-8601 date"); } auto timespec = dt.timeSpec(); if (timespec == Qt::LocalTime) { throw LocalCommsException(prefix + actual.key() + ": value \"" + actual.value().toString() + "\" lacks a time zone specification"); } break; } case MetadataType::non_zero_pos_int64: { if (actual.value().type() != QVariant::LongLong) { throw LocalCommsException(prefix + actual.key() + ": expected value of type LongLong, " " but received value of type " + actual.value().typeName()); } break; } case MetadataType::string: case MetadataType::boolean: { break; } default: { abort(); // Impossible. // LCOV_EXCL_LINE } } } } void validate(QString const& method, ItemMetadata const& md) { using namespace lomiri::storage::metadata; QString prefix = method + ": received invalid metadata from server: "; // Basic sanity checks for mandatory fields. if (md.item_id.isEmpty()) { throw LocalCommsException(prefix + "item_id cannot be empty"); } if (md.type != ItemType::root) { if (md.parent_ids.isEmpty()) { throw LocalCommsException(prefix + "file or folder must have at least one parent ID"); } for (int i = 0; i < md.parent_ids.size(); ++i) { if (md.parent_ids.at(i).isEmpty()) { throw LocalCommsException(prefix + "parent_id of file or folder cannot be empty"); } } } if (md.type == ItemType::root && !md.parent_ids.isEmpty()) { throw LocalCommsException(prefix + "metadata: parent_ids of root must be empty"); } if (md.name.isEmpty()) { throw LocalCommsException(prefix + "name cannot be empty"); } if (md.type == ItemType::file && md.etag.isEmpty()) { throw LocalCommsException(prefix + "etag of file cannot be empty"); } // Sanity check metadata to make sure only known metadata keys appear. QMapIterator actual(md.metadata); while (actual.hasNext()) { actual.next(); auto known = known_metadata.find(actual.key().toStdString()); if (known == known_metadata.end()) { qWarning() << prefix << "unknown metadata key:" << actual.key(); } else { validate_type_and_value(prefix, actual, known); } } // Sanity check metadata to make sure that mandatory fields are present. if (md.type == ItemType::file) { if (!md.metadata.contains(metadata::SIZE_IN_BYTES)) { throw LocalCommsException(prefix + "missing key " + metadata::SIZE_IN_BYTES + " in metadata for " + md.item_id); } if (!md.metadata.contains(metadata::LAST_MODIFIED_TIME)) { throw LocalCommsException(prefix + "missing key " + metadata::LAST_MODIFIED_TIME + " in metadata for " + md.item_id); } } } } // namespace remote_client } // namespace internal } // namespace client } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/client/lomiri-storage-framework-qt-client-1.pc.in000066400000000000000000000005001521521330000310350ustar00rootroot00000000000000Name: storage-framework-qt-client-1 Description: A Qt client library for the storage framework (soon to be deprecated) Version: 0.1 Requires.private: Qt5Core Qt5Network Cflags: -I@CMAKE_INSTALL_FULL_INCLUDEDIR@/lomiri-storage-framework-client-1 Libs: -L@CMAKE_INSTALL_FULL_LIBDIR@ -llomiri-storage-framework-qt-client-1 lomiri-storage-framework-0.5.0/src/qt/client/lomiri-storage-framework-qt-local-client.pc.in000066400000000000000000000005321521521330000317740ustar00rootroot00000000000000Name: storage-framework-qt-local-client-1 Description: A Qt client library for the storage framework (soon to be deprecated) Version: @PROJECT_VERSION@ Requires.private: Qt5Core Qt5Network Cflags: -I@CMAKE_INSTALL_FULL_INCLUDEDIR@/lomiri-storage-framework-client-1 Libs: -L@CMAKE_INSTALL_FULL_LIBDIR@ -llomiri-storage-framework-qt-local-client-1 lomiri-storage-framework-0.5.0/src/qt/internal/000077500000000000000000000000001521521330000214635ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/src/qt/internal/AccountImpl.cpp000066400000000000000000000133111521521330000244040ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include "ProviderInterface.h" #include #include #include #include #include #include #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace internal { AccountImpl::AccountImpl() : is_valid_(false) { } AccountImpl::AccountImpl(shared_ptr const& runtime_impl, storage::internal::AccountDetails const& details) : is_valid_(true) , details_(details) , runtime_impl_(runtime_impl) , provider_(new ProviderInterface(details.busName, details.objectPath.path(), runtime_impl->connection())) { assert(!details.busName.isEmpty()); assert(!details.objectPath.path().isEmpty()); } QString AccountImpl::busName() const { return is_valid_ ? details_.busName : ""; } QString AccountImpl::objectPath() const { return is_valid_ ? details_.objectPath.path() : QDBusObjectPath().path(); } QString AccountImpl::displayName() const { return is_valid_ ? details_.displayName : ""; } QString AccountImpl::providerName() const { return is_valid_ ? details_.providerName : ""; } QString AccountImpl::iconName() const { return is_valid_ ? details_.iconName : ""; } ItemListJob* AccountImpl::roots(QStringList const& keys) const { QString const method = "Account::roots()"; auto runtime = runtime_impl_.lock(); if (!is_valid_) { auto e = StorageErrorImpl::logic_error(method + ": cannot create job from invalid account"); return ItemListJobImpl::make_job(e); } if (!runtime || !runtime->isValid()) { auto e = StorageErrorImpl::runtime_destroyed_error(method + ": Runtime was destroyed previously"); return ItemListJobImpl::make_job(e); } auto validate = [method](storage::internal::ItemMetadata const& md) { if (md.type != ItemType::root) { QString msg = method + ": provider returned non-root item type: " + QString::number(int(md.type)) + " (id = " + md.item_id + ")"; qCritical().noquote() << msg; throw StorageErrorImpl::local_comms_error(msg); } }; auto reply = provider_->Roots(keys); auto This = const_pointer_cast(shared_from_this()); return ItemListJobImpl::make_job(This, method, reply, validate); } ItemJob* AccountImpl::get(QString const& itemId, QStringList const& keys) const { QString const method = "Account::get()"; if (!is_valid_) { auto e = StorageErrorImpl::logic_error(method + ": cannot create job from invalid account"); return ItemJobImpl::make_job(e); } auto runtime = runtime_impl_.lock(); if (!runtime || !runtime->isValid()) { auto e = StorageErrorImpl::runtime_destroyed_error(method + ": Runtime was destroyed previously"); return ItemJobImpl::make_job(e); } auto validate = [](storage::internal::ItemMetadata const&) { }; auto reply = provider_->Metadata(itemId, keys); auto This = const_pointer_cast(shared_from_this()); return ItemJobImpl::make_job(This, method, reply, validate); } bool AccountImpl::operator==(AccountImpl const& other) const { if (is_valid_) { return other.is_valid_ && details_ == other.details_; } return !other.is_valid_; } bool AccountImpl::operator!=(AccountImpl const& other) const { return !operator==(other); } bool AccountImpl::operator<(AccountImpl const& other) const { if (!is_valid_) { return other.is_valid_; } if (is_valid_ && !other.is_valid_) { return false; } return details_ < other.details_; } bool AccountImpl::operator<=(AccountImpl const& other) const { return operator<(other) || operator==(other); } bool AccountImpl::operator>(AccountImpl const& other) const { return !operator<=(other); } bool AccountImpl::operator>=(AccountImpl const& other) const { return !operator<(other); } shared_ptr AccountImpl::runtime_impl() const { return runtime_impl_.lock(); } shared_ptr AccountImpl::provider() const { return provider_; } size_t AccountImpl::hash() const { if (!is_valid_) { return 0; } size_t hash = details_.id; boost::hash_combine(hash, qHash(details_.serviceId)); boost::hash_combine(hash, qHash(details_.displayName)); return hash; } Account AccountImpl::make_account(shared_ptr const& runtime, storage::internal::AccountDetails const& details) { shared_ptr p(new AccountImpl(runtime, details)); return Account(p); } } // namespace internal } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/internal/AccountsJobImpl.cpp000066400000000000000000000124331521521330000252260ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include "RegistryInterface.h" #include #include #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace internal { AccountsJobImpl::AccountsJobImpl(shared_ptr const& runtime_impl, QString const& method, QDBusPendingReply> const& reply) : status_(AccountsJob::Status::Loading) , runtime_impl_(runtime_impl) { assert(runtime_impl); auto process_reply = [this, method](decltype(reply)& r) { auto runtime = get_runtime_impl(method); if (!runtime || !runtime->isValid()) { return; } for (auto const& ad : r.value()) { auto a = AccountImpl::make_account(runtime, ad); accounts_.append(a); } status_ = AccountsJob::Status::Finished; Q_EMIT public_instance_->statusChanged(status_); }; auto process_error = [this](StorageError const& error) { error_ = error; status_ = AccountsJob::Status::Error; Q_EMIT public_instance_->statusChanged(status_); }; new Handler>(this, reply, process_reply, process_error); } AccountsJobImpl::AccountsJobImpl(StorageError const& error) : status_(AccountsJob::Status::Error) , error_(error) { assert(error.type() != StorageError::Type::NoError); status_ = emit_status_changed(AccountsJob::Status::Error); } bool AccountsJobImpl::isValid() const { return status_ != AccountsJob::Status::Error; } AccountsJob::Status AccountsJobImpl::status() const { return status_; } StorageError AccountsJobImpl::error() const { return error_; } QList AccountsJobImpl::accounts() const { auto runtime = get_runtime_impl("AccountsJob::accounts()"); if (!runtime) { return QList(); } if (status_ != AccountsJob::Status::Finished) { return QList(); } return accounts_; } QVariantList AccountsJobImpl::accountsAsVariantList() const { QVariantList account_list; for (auto const& a : accounts()) { account_list.append(QVariant::fromValue(a)); } return account_list; } AccountsJob* AccountsJobImpl::make_job(shared_ptr const& runtime, QString const& method, QDBusPendingReply> const& reply) { unique_ptr impl(new AccountsJobImpl(runtime, method, reply)); auto job = new AccountsJob(move(impl)); job->p_->public_instance_ = job; return job; } AccountsJob* AccountsJobImpl::make_job(StorageError const& error) { unique_ptr impl(new AccountsJobImpl(error)); auto job = new AccountsJob(move(impl)); job->p_->public_instance_ = job; QMetaObject::invokeMethod(job, "statusChanged", Qt::QueuedConnection, Q_ARG(lomiri::storage::qt::AccountsJob::Status, job->p_->status_)); return job; } AccountsJob::Status AccountsJobImpl::emit_status_changed(AccountsJob::Status new_status) const { if (status_ == AccountsJob::Status::Loading) // Once in a final state, we don't emit the signal again. { // We defer emission of the signal so the client gets a chance to connect to the signal // in case we emit the signal from the constructor. QMetaObject::invokeMethod(public_instance_, "statusChanged", Qt::QueuedConnection, Q_ARG(lomiri::storage::qt::AccountsJob::Status, new_status)); } return new_status; } shared_ptr AccountsJobImpl::get_runtime_impl(QString const& method) const { auto runtime = runtime_impl_.lock(); if (!runtime || !runtime->isValid()) { QString msg = method + ": Runtime was destroyed previously"; auto This = const_cast(this); This->error_ = StorageErrorImpl::runtime_destroyed_error(msg); This->status_ = emit_status_changed(AccountsJob::Status::Error); return nullptr; } return runtime; } } // namespace internal } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/internal/DownloaderImpl.cpp000066400000000000000000000246711521521330000251210ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include "ProviderInterface.h" #include #include #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace internal { DownloaderImpl::DownloaderImpl(shared_ptr const& item_impl, QString const& method, QDBusPendingReply const& reply) : status_(Downloader::Status::Loading) , item_impl_(item_impl) { assert(item_impl); assert(!method.isEmpty()); auto process_reply = [this, method](decltype(reply)& r) { if (status_ != Downloader::Status::Loading) { return; // Don't transition to a final state more than once. } auto runtime = item_impl_->runtime_impl(); if (!runtime || !runtime->isValid()) { QString msg = method + ": Runtime was destroyed previously"; error_ = StorageErrorImpl::runtime_destroyed_error(msg); socket_.abort(); public_instance_->setErrorString(msg); status_ = Downloader::Status::Error; Q_EMIT public_instance_->statusChanged(status_); return; } download_id_ = r.argumentAt<0>(); fd_ = r.argumentAt<1>(); if (fd_.fileDescriptor() < 0) { // LCOV_EXCL_START QString msg = method + ": invalid file descriptor returned by provider"; qCritical().noquote() << msg; error_ = StorageErrorImpl::local_comms_error(msg); socket_.abort(); public_instance_->setErrorString(msg); status_ = Downloader::Status::Error; Q_EMIT public_instance_->statusChanged(status_); return; // LCOV_EXCL_STOP } // We forward any QIODevice signals emitted by the socket to the public instance. connect(&socket_, &QIODevice::aboutToClose, public_instance_, &QIODevice::aboutToClose); connect(&socket_, &QIODevice::bytesWritten, public_instance_, &QIODevice::bytesWritten); connect(&socket_, &QIODevice::readChannelFinished, public_instance_, &QIODevice::readChannelFinished); connect(&socket_, &QIODevice::readyRead, public_instance_, &QIODevice::readyRead); #if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) connect(&socket_, &QIODevice::channelBytesWritten, public_instance_, &QIODevice::channelBytesWritten); connect(&socket_, &QIODevice::channelReadyRead, public_instance_, &QIODevice::channelReadyRead); #endif socket_.setSocketDescriptor(fd_.fileDescriptor(), QLocalSocket::ConnectedState, QIODevice::ReadOnly); status_ = Downloader::Status::Ready; Q_EMIT public_instance_->statusChanged(status_); }; auto process_error = [this](StorageError const& error) { // TODO: This does not set the method error_ = error; status_ = Downloader::Status::Error; socket_.abort(); public_instance_->setErrorString(error.errorString()); Q_EMIT public_instance_->statusChanged(status_); }; new Handler>(this, reply, process_reply, process_error); } DownloaderImpl::DownloaderImpl(StorageError const& e) : status_(Downloader::Status::Error) , error_(e) { } DownloaderImpl::~DownloaderImpl() { switch (status_) { case Downloader::Status::Loading: case Downloader::Status::Finished: case Downloader::Status::Cancelled: case Downloader::Status::Error: break; case Downloader::Status::Ready: cancel(); break; default: abort(); // Impossible. // LCOV_EXCL_LINE } } bool DownloaderImpl::isValid() const { return status_ != Downloader::Status::Error && status_ != Downloader::Status::Cancelled; } Downloader::Status DownloaderImpl::status() const { return status_; } StorageError DownloaderImpl::error() const { return error_; } Item DownloaderImpl::item() const { if (status_ == Downloader::Status::Error) { return Item(); } return Item(item_impl_); } void DownloaderImpl::cancel() { static QString const method = "Downloader::cancel()"; // If we are in a final state already, ignore the call. if ( status_ == Downloader::Status::Error || status_ == Downloader::Status::Finished || status_ == Downloader::Status::Cancelled) { return; } auto runtime = item_impl_->runtime_impl(); if (!runtime || !runtime->isValid()) { QString msg = method + ": Runtime was destroyed previously"; error_ = StorageErrorImpl::runtime_destroyed_error(msg); status_ = Downloader::Status::Error; Q_EMIT public_instance_->statusChanged(status_); return; } QString msg = method + ": download was cancelled"; error_ = StorageErrorImpl::cancelled_error(msg); socket_.abort(); public_instance_->setErrorString(msg); status_ = Downloader::Status::Cancelled; Q_EMIT public_instance_->statusChanged(status_); } void DownloaderImpl::close() { static QString const method = "Downloader::close()"; // If we encountered an error earlier or were cancelled, or if close() was // called already, we ignore the call. if (status_ == Downloader::Status::Error || status_ == Downloader::Status::Cancelled || finalizing_) { return; } // Complain if we are asked to finalize while in the Loading or Finished state. if (status_ != Downloader::Ready) { QString msg = method + ": cannot finalize while Downloader is not in the Ready state"; error_ = StorageErrorImpl::logic_error(msg); socket_.abort(); public_instance_->setErrorString(msg); status_ = Downloader::Status::Error; Q_EMIT public_instance_->statusChanged(status_); return; } auto runtime = item_impl_->runtime_impl(); if (!runtime || !runtime->isValid()) { QString msg = method + ": Runtime was destroyed previously"; error_ = StorageErrorImpl::runtime_destroyed_error(msg); status_ = Downloader::Status::Error; Q_EMIT public_instance_->statusChanged(status_); return; } finalizing_ = true; auto reply = item_impl_->account_impl()->provider()->FinishDownload(download_id_); auto process_reply = [this](decltype(reply) const&) { if (status_ == Downloader::Status::Cancelled || status_ == Downloader::Status::Error) { return; // Don't transition to a final state more than once. } auto runtime = item_impl_->runtime_impl(); if (!runtime || !runtime->isValid()) { QString msg = method + ": Runtime was destroyed previously"; error_ = StorageErrorImpl::runtime_destroyed_error(msg); socket_.abort(); public_instance_->setErrorString(msg); status_ = Downloader::Status::Error; Q_EMIT public_instance_->statusChanged(status_); return; } status_ = Downloader::Status::Finished; Q_EMIT public_instance_->statusChanged(status_); }; auto process_error = [this](StorageError const& error) { if (status_ != Downloader::Status::Ready) { return; // Don't transition to a final state more than once. } // TODO: this doesn't set the method error_ = error; socket_.abort(); public_instance_->setErrorString(error.errorString()); status_ = Downloader::Status::Error; Q_EMIT public_instance_->statusChanged(status_); }; new Handler(this, reply, process_reply, process_error); } qint64 DownloaderImpl::bytesAvailable() const { return socket_.bytesAvailable(); } qint64 DownloaderImpl::bytesToWrite() const { return socket_.bytesToWrite(); } bool DownloaderImpl::canReadLine() const { return socket_.canReadLine(); } bool DownloaderImpl::isSequential() const { return socket_.isSequential(); } bool DownloaderImpl::waitForBytesWritten(int msecs) { return socket_.waitForBytesWritten(msecs); } bool DownloaderImpl::waitForReadyRead(int msecs) { return socket_.waitForReadyRead(msecs); } qint64 DownloaderImpl::readData(char* data, qint64 c) { return socket_.read(data, c); } // LCOV_EXCL_START // Never called by QIODevice because device is opened read-only. qint64 DownloaderImpl::writeData(char const* data, qint64 c) { return socket_.write(data, c); } // LCOV_EXCL_STOP Downloader* DownloaderImpl::make_job(shared_ptr const& item_impl, QString const& method, QDBusPendingReply const& reply) { unique_ptr impl(new DownloaderImpl(item_impl, method, reply)); auto downloader = new Downloader(move(impl)); downloader->open(QIODevice::ReadOnly); downloader->p_->public_instance_ = downloader; return downloader; } Downloader* DownloaderImpl::make_job(StorageError const& e) { unique_ptr impl(new DownloaderImpl(e)); auto downloader = new Downloader(move(impl)); downloader->open(QIODevice::ReadOnly); downloader->p_->public_instance_ = downloader; QMetaObject::invokeMethod(downloader, "statusChanged", Qt::QueuedConnection, Q_ARG(lomiri::storage::qt::Downloader::Status, downloader->p_->status_)); return downloader; } } // namespace internal } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/internal/HandlerBase.cpp000066400000000000000000000031701521521330000243400ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #pragma GCC diagnostic pop #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace internal { HandlerBase::HandlerBase(QObject* parent, QDBusPendingCall const& call, function const& closure) : QObject(parent) , watcher_(call) , closure_(closure) { assert(closure); connect(&watcher_, &QDBusPendingCallWatcher::finished, this, &HandlerBase::finished); } void HandlerBase::finished(QDBusPendingCallWatcher* call) { deleteLater(); disconnect(&watcher_, &QDBusPendingCallWatcher::finished, this, &HandlerBase::finished); closure_(*call); } } // namespace internal } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/internal/ItemImpl.cpp000066400000000000000000000374211521521330000237160ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include "ProviderInterface.h" #include #include #include #include #include #include #include #include #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace internal { ItemImpl::ItemImpl() : is_valid_(false) { md_.type = storage::ItemType::file; } ItemImpl::ItemImpl(storage::internal::ItemMetadata const& md, std::shared_ptr const& account_impl) : is_valid_(true) , md_(md) , account_impl_(account_impl) { assert(account_impl); } QString ItemImpl::itemId() const { return is_valid_ ? md_.item_id : ""; } QString ItemImpl::name() const { return is_valid_ ? md_.name : ""; } Account ItemImpl::account() const { return is_valid_ ? account_impl_ : Account(); } QString ItemImpl::etag() const { return is_valid_ ? md_.etag : ""; } Item::Type ItemImpl::type() const { switch (md_.type) { case storage::ItemType::file: return Item::Type::File; case storage::ItemType::folder: return Item::Type::Folder; case storage::ItemType::root: return Item::Type::Root; default: abort(); // Impossible. // LCOV_EXCL_LINE } } QVariantMap ItemImpl::metadata() const { return is_valid_ ? md_.metadata : QVariantMap(); } qint64 ItemImpl::sizeInBytes() const { if (!is_valid_ || md_.type != ItemType::file) { return 0; } auto variant = md_.metadata.value(metadata::SIZE_IN_BYTES); assert(variant.isValid()); return variant.toLongLong(); } QDateTime ItemImpl::lastModifiedTime() const { return is_valid_ ? QDateTime::fromString(md_.metadata.value(metadata::LAST_MODIFIED_TIME).toString(), Qt::ISODate) : QDateTime(); } QList ItemImpl::parentIds() const { if (!is_valid_ || md_.type == storage::ItemType::root) { return QList(); } return md_.parent_ids; } ItemListJob* ItemImpl::parents(QStringList const& keys) const { QString const method = "Item::parents()"; auto invalid_job = check_invalid_or_destroyed(method); if (invalid_job) { return invalid_job; } if (md_.type == storage::ItemType::root) { return ListJobImplBase::make_empty_job(); // Root has no parents. } assert(!md_.parent_ids.isEmpty()); QList> replies; for (auto const& id : md_.parent_ids) { auto reply = account_impl_->provider()->Metadata(id, keys); replies.append(reply); } auto validate = [method](storage::internal::ItemMetadata const& md) { if (md.type == ItemType::file) { QString msg = method + ": provider returned a file as a parent (id = " + md.item_id + ")"; qCritical().noquote() << msg; throw StorageErrorImpl::local_comms_error(msg); } }; return MultiItemJobImpl::make_job(account_impl_, method, replies, validate); } ItemJob* ItemImpl::copy(Item const& newParent, QString const& newName, QStringList const& keys) const { QString const method = "Item::copy()"; auto invalid_job = check_copy_move_precondition(method, newParent, newName); if (invalid_job) { return invalid_job; } auto validate = [this, method](storage::internal::ItemMetadata const& md) { if ((md_.type == ItemType::file && md.type != ItemType::file) || (md_.type != ItemType::file && md.type == ItemType::file)) { QString msg = method + "provider error: source and target item type differ (source id = " + md_.item_id + ", target id = " + md.item_id + ")"; qCritical().noquote() << msg; throw StorageErrorImpl::local_comms_error(msg); } }; auto reply = account_impl_->provider()->Copy(md_.item_id, newParent.itemId(), newName, keys); auto This = const_pointer_cast(shared_from_this()); return ItemJobImpl::make_job(This, method, reply, validate); } ItemJob* ItemImpl::move(Item const& newParent, QString const& newName, QStringList const& keys) const { QString const method = "Item::move()"; auto invalid_job = check_copy_move_precondition(method, newParent, newName); if (invalid_job) { return invalid_job; } auto validate = [this, method](storage::internal::ItemMetadata const& md) { if (md.type == ItemType::root) { QString msg = method + ": impossible root item returned by provider (id = " + md.item_id + ")"; qCritical().noquote() << msg; throw StorageErrorImpl::local_comms_error(msg); } if ((md_.type == ItemType::file && md.type != ItemType::file) || (md_.type != ItemType::file && md.type == ItemType::file)) { QString msg = method + ": provider error: source and target item type differ (source id = " + md_.item_id + ", target id = " + md.item_id + ")"; qCritical().noquote() << msg; throw StorageErrorImpl::local_comms_error(msg); } }; auto reply = account_impl_->provider()->Move(md_.item_id, newParent.itemId(), newName, keys); auto This = const_pointer_cast(shared_from_this()); return ItemJobImpl::make_job(This, method, reply, validate); } VoidJob* ItemImpl::deleteItem() const { QString const method = "Item::deleteItem()"; auto invalid_job = check_invalid_or_destroyed(method); if (invalid_job) { return invalid_job; } if (md_.type == storage::ItemType::root) { auto e = StorageErrorImpl::logic_error(method + ": cannot delete root"); return VoidJobImpl::make_job(e); } auto reply = account_impl_->provider()->Delete(md_.item_id); auto This = const_pointer_cast(shared_from_this()); return VoidJobImpl::make_job(This, method, reply); } Uploader* ItemImpl::createUploader(Item::ConflictPolicy policy, qint64 sizeInBytes, QStringList const& keys) const { QString const method = "Item::createUploader()"; auto invalid_job = check_invalid_or_destroyed(method); if (invalid_job) { return invalid_job; } if (md_.type != storage::ItemType::file) { auto e = StorageErrorImpl::logic_error(method + ": cannot upload to a folder"); return UploaderImpl::make_job(e); } if (sizeInBytes < 0) { auto e = StorageErrorImpl::invalid_argument_error(method + ": size must be >= 0"); return UploaderImpl::make_job(e); } auto validate = [method](storage::internal::ItemMetadata const& md) { if (md.type != storage::ItemType::file) { QString msg = method + ": impossible folder item returned by provider (id = " + md.item_id + ")"; qCritical().noquote() << msg; throw StorageErrorImpl::local_comms_error(msg); } }; auto etag = policy == Item::ConflictPolicy::IgnoreConflict ? "" : md_.etag; auto reply = account_impl_->provider()->Update(md_.item_id, sizeInBytes, etag, keys); auto This = const_pointer_cast(shared_from_this()); return UploaderImpl::make_job(This, method, reply, validate, policy, sizeInBytes); } Downloader* ItemImpl::createDownloader(Item::ConflictPolicy policy) const { QString const method = "Item::createDownloader()"; auto invalid_job = check_invalid_or_destroyed(method); if (invalid_job) { return invalid_job; } if (md_.type != storage::ItemType::file) { auto e = StorageErrorImpl::logic_error(method + ": cannot download a folder"); return DownloaderImpl::make_job(e); } auto etag = policy == Item::ConflictPolicy::IgnoreConflict ? "" : md_.etag; auto reply = account_impl_->provider()->Download(md_.item_id, etag); auto This = const_pointer_cast(shared_from_this()); return DownloaderImpl::make_job(This, method, reply); } ItemListJob* ItemImpl::list(QStringList const& keys) const { QString const method = "Item::list()"; auto invalid_job = check_invalid_or_destroyed(method); if (invalid_job) { return invalid_job; } if (md_.type == storage::ItemType::file) { auto e = StorageErrorImpl::logic_error(method + ": cannot perform list on a file"); return ItemListJobImpl::make_job(e); } auto validate = [method](storage::internal::ItemMetadata const& md) { if (md.type == storage::ItemType::root) { QString msg = method + ": impossible root item returned by provider (id = " + md.item_id + ")"; qCritical().noquote() << msg; throw StorageErrorImpl::local_comms_error(msg); } }; auto fetch_next = [this, keys](QString const& page_token) { return account_impl_->provider()->List(md_.item_id, page_token, keys); }; auto reply = account_impl_->provider()->List(md_.item_id, "", keys); auto This = const_pointer_cast(shared_from_this()); return MultiItemListJobImpl::make_job(This, method, reply, validate, fetch_next); } ItemListJob* ItemImpl::lookup(QString const& name, QStringList const& keys) const { QString const method = "Item::lookup()"; auto invalid_job = check_invalid_or_destroyed(method); if (invalid_job) { return invalid_job; } if (md_.type == storage::ItemType::file) { auto e = StorageErrorImpl::logic_error(method + ": cannot perform lookup on a file"); return ItemListJobImpl::make_job(e); } auto validate = [](storage::internal::ItemMetadata const&) { }; auto reply = account_impl_->provider()->Lookup(md_.item_id, name, keys); auto This = const_pointer_cast(shared_from_this()); return ItemListJobImpl::make_job(This, method, reply, validate); } ItemJob* ItemImpl::createFolder(QString const& name, QStringList const& keys) const { QString const method = "Item::createFolder()"; auto invalid_job = check_invalid_or_destroyed(method); if (invalid_job) { return invalid_job; } if (md_.type == storage::ItemType::file) { auto e = StorageErrorImpl::logic_error(method + ": cannot create a folder with a file as the parent"); return ItemJobImpl::make_job(e); } auto validate = [method](storage::internal::ItemMetadata const& md) { if (md.type != storage::ItemType::file) { return; } QString msg = method + ": impossible file item returned by provider (id = " + md.item_id + ")"; qCritical().noquote() << msg; throw StorageErrorImpl::local_comms_error(msg); }; auto reply = account_impl_->provider()->CreateFolder(md_.item_id, name, keys); auto This = const_pointer_cast(shared_from_this()); return ItemJobImpl::make_job(This, method, reply, validate); } Uploader* ItemImpl::createFile(QString const& name, Item::ConflictPolicy policy, qint64 sizeInBytes, QString const& contentType, QStringList const& keys) const { QString const method = "Item::createFile()"; auto invalid_job = check_invalid_or_destroyed(method); if (invalid_job) { return invalid_job; } if (md_.type == storage::ItemType::file) { auto e = StorageErrorImpl::logic_error(method + ": cannot create a file with a file as the parent"); return UploaderImpl::make_job(e); } if (sizeInBytes < 0) { auto e = StorageErrorImpl::invalid_argument_error(method + ": size must be >= 0"); return UploaderImpl::make_job(e); } if (name.isEmpty()) { auto e = StorageErrorImpl::invalid_argument_error(method + ": name cannot be empty"); return UploaderImpl::make_job(e); } // contentType can be empty, so not checked here. auto validate = [method](storage::internal::ItemMetadata const& md) { if (md.type != storage::ItemType::file) { QString msg = method + ": impossible folder item returned by provider (id = " + md.item_id + ")"; qCritical().noquote() << msg; throw StorageErrorImpl::local_comms_error(msg); } }; bool allow_overwrite = policy == Item::ConflictPolicy::IgnoreConflict; auto reply = account_impl_->provider()->CreateFile(md_.item_id, name, sizeInBytes, contentType, allow_overwrite, keys); auto This = const_pointer_cast(shared_from_this()); return UploaderImpl::make_job(This, method, reply, validate, policy, sizeInBytes); } bool ItemImpl::operator==(ItemImpl const& other) const { if (is_valid_) { return other.is_valid_ && *account_impl_ == *other.account_impl_ && md_.item_id == other.md_.item_id; } return !other.is_valid_; } bool ItemImpl::operator!=(ItemImpl const& other) const { return !operator==(other); } bool ItemImpl::operator<(ItemImpl const& other) const { if (!is_valid_) { return other.is_valid_; } if (is_valid_ && !other.is_valid_) { return false; } assert(is_valid_ && other.is_valid_); if (*account_impl_ < *other.account_impl_) { return true; } if (*account_impl_ > *other.account_impl_) { return false; } return md_.item_id < other.md_.item_id; } bool ItemImpl::operator<=(ItemImpl const& other) const { return operator<(other) or operator==(other); } bool ItemImpl::operator>(ItemImpl const& other) const { return !operator<=(other); } bool ItemImpl::operator>=(ItemImpl const& other) const { return !operator<(other); } size_t ItemImpl::hash() const { if (!is_valid_) { return 0; } size_t hash = 0; boost::hash_combine(hash, account_impl_->hash()); boost::hash_combine(hash, qHash(md_.item_id)); return hash; } Item ItemImpl::make_item(QString const& method, storage::internal::ItemMetadata const& md, std::shared_ptr const& account_impl) { validate(method, md); // Throws if no good. auto p = make_shared(md, account_impl); return Item(p); } shared_ptr ItemImpl::runtime_impl() const { return account_impl_->runtime_impl(); } shared_ptr ItemImpl::account_impl() const { return account_impl_; } } // namespace internal } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/internal/ItemJobImpl.cpp000066400000000000000000000122301521521330000243400ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include #include #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace internal { ItemJobImpl::ItemJobImpl(shared_ptr const& account_impl, QString const& method, QDBusPendingReply const& reply, std::function const& validate) : status_(ItemJob::Status::Loading) , method_(method) , account_impl_(account_impl) , validate_(validate) { assert(!method.isEmpty()); assert(account_impl); assert(validate); auto process_reply = [this](decltype(reply)& r) { auto runtime = account_impl_->runtime_impl(); if (!runtime || !runtime->isValid()) { error_ = StorageErrorImpl::runtime_destroyed_error(method_ + ": Runtime was destroyed previously"); status_ = ItemJob::Status::Error; Q_EMIT public_instance_->statusChanged(status_); return; } auto metadata = r.value(); try { validate_(metadata); item_ = ItemImpl::make_item(method_, metadata, account_impl_); status_ = ItemJob::Status::Finished; } catch (StorageError const& e) { // Bad metadata received from provider, validate_() or make_item() have logged it. // TODO: This does not set the method. error_ = e; status_ = ItemJob::Status::Error; } Q_EMIT public_instance_->statusChanged(status_); }; auto process_error = [this](StorageError const& error) { error_ = error; status_ = ItemJob::Status::Error; Q_EMIT public_instance_->statusChanged(status_); }; new Handler(this, reply, process_reply, process_error); } ItemJobImpl::ItemJobImpl(shared_ptr const& item, QString const& method, QDBusPendingReply const& reply, std::function const& validate) : ItemJobImpl(item->account_impl(), method, reply, validate) { item_impl_= item; } ItemJobImpl::ItemJobImpl(StorageError const& error) : status_(ItemJob::Status::Error) , error_(error) { } bool ItemJobImpl::isValid() const { return status_ != ItemJob::Status::Error; } ItemJob::Status ItemJobImpl::status() const { return status_; } StorageError ItemJobImpl::error() const { return error_; } Item ItemJobImpl::item() const { return item_; } ItemJob* ItemJobImpl::make_job(shared_ptr const& account, QString const& method, QDBusPendingReply const& reply, std::function const& validate) { unique_ptr impl(new ItemJobImpl(account, method, reply, validate)); auto job = new ItemJob(move(impl)); job->p_->public_instance_ = job; return job; } ItemJob* ItemJobImpl::make_job(shared_ptr const& item, QString const& method, QDBusPendingReply const& reply, std::function const& validate) { unique_ptr impl(new ItemJobImpl(item, method, reply, validate)); auto job = new ItemJob(move(impl)); job->p_->public_instance_ = job; return job; } ItemJob* ItemJobImpl::make_job(StorageError const& error) { unique_ptr impl(new ItemJobImpl(error)); auto job = new ItemJob(move(impl)); job->p_->public_instance_ = job; QMetaObject::invokeMethod(job, "statusChanged", Qt::QueuedConnection, Q_ARG(lomiri::storage::qt::ItemJob::Status, job->p_->status_)); return job; } } // namespace internal } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/internal/ItemListJobImpl.cpp000066400000000000000000000113361521521330000252020ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace internal { ItemListJobImpl::ItemListJobImpl(shared_ptr const& account_impl, QString const& method, QDBusPendingReply> const& reply, std::function const& validate) : ListJobImplBase(account_impl, method, validate) { auto process_reply = [this](decltype(reply) const& r) { auto runtime = account_impl_->runtime_impl(); if (!runtime || !runtime->isValid()) { error_ = StorageErrorImpl::runtime_destroyed_error(method_ + ": Runtime was destroyed previously"); status_ = ItemListJob::Status::Error; Q_EMIT public_instance_->statusChanged(status_); return; } QList items; auto metadata = r.value(); for (auto const& md : metadata) { try { validate_(md); auto item = ItemImpl::make_item(method_, md, account_impl_); items.append(item); } catch (StorageError const& e) { // Bad metadata received from provider, validate_() or make_item() have logged it. error_ = e; } } status_ = error_.type() == StorageError::NoError ? ItemListJob::Finished : ItemListJob::Error; Q_EMIT public_instance_->itemsReady(items); Q_EMIT public_instance_->statusChanged(status_); }; auto process_error = [this](StorageError const& error) { // TODO: method name is not being set this way. error_ = error; status_ = ItemListJob::Status::Error; Q_EMIT public_instance_->statusChanged(status_); }; new Handler>(this, reply, process_reply, process_error); } ItemListJobImpl::ItemListJobImpl(shared_ptr const& item_impl, QString const& method, QDBusPendingReply> const& reply, std::function const& validate) : ItemListJobImpl(item_impl->account_impl(), method, reply, validate) { item_impl_ = item_impl; } ItemListJob* ItemListJobImpl::make_job(shared_ptr const& account_impl, QString const& method, QDBusPendingReply> const& reply, std::function const& validate) { unique_ptr impl(new ItemListJobImpl(account_impl, method, reply, validate)); auto job = new ItemListJob(move(impl)); job->p_->set_public_instance(job); return job; } ItemListJob* ItemListJobImpl::make_job(shared_ptr const& item_impl, QString const& method, QDBusPendingReply> const& reply, std::function const& validate) { unique_ptr impl(new ItemListJobImpl(item_impl, method, reply, validate)); auto job = new ItemListJob(move(impl)); job->p_->set_public_instance(job); return job; } ItemListJob* ItemListJobImpl::make_job(StorageError const& error) { return ListJobImplBase::make_job(error); } } // namespace internal } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/internal/ListJobImplBase.cpp000066400000000000000000000061351521521330000251570ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include #include #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace internal { ListJobImplBase::ListJobImplBase() : status_(ItemListJob::Status::Finished) { } ListJobImplBase::ListJobImplBase(shared_ptr const& account_impl, QString const& method, std::function const& validate) : status_(ItemListJob::Status::Loading) , method_(method) , account_impl_(account_impl) , validate_(validate) { assert(!method.isEmpty()); assert(account_impl); assert(validate); } ListJobImplBase::ListJobImplBase(StorageError const& error) : status_(ItemListJob::Status::Error) , error_(error) { } bool ListJobImplBase::isValid() const { return status_ != ItemListJob::Status::Error; } ItemListJob::Status ListJobImplBase::status() const { return status_; } StorageError ListJobImplBase::error() const { return error_; } void ListJobImplBase::set_public_instance(ItemListJob* p) { assert(p); public_instance_ = p; } ItemListJob* ListJobImplBase::make_job(StorageError const& error) { unique_ptr impl(new ListJobImplBase(error)); auto job = new ItemListJob(move(impl)); job->p_->public_instance_ = job; QMetaObject::invokeMethod(job, "statusChanged", Qt::QueuedConnection, Q_ARG(lomiri::storage::qt::ItemListJob::Status, job->status())); return job; } ItemListJob* ListJobImplBase::make_empty_job() { unique_ptr impl(new ListJobImplBase()); auto job = new ItemListJob(move(impl)); job->p_->public_instance_ = job; QMetaObject::invokeMethod(job, "statusChanged", Qt::QueuedConnection, Q_ARG(lomiri::storage::qt::ItemListJob::Status, job->status())); return job; } } // namespace internal } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/internal/MultiItemJobImpl.cpp000066400000000000000000000104221521521330000253540ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace internal { MultiItemJobImpl::MultiItemJobImpl(shared_ptr const& account_impl, QString const& method, ReplyType const& replies, ValidateFunc const& validate) : ListJobImplBase(account_impl, method, validate) , replies_remaining_(replies.size()) { assert(!method.isEmpty()); assert(account_impl); assert(validate); // We ask the provider for the metadata for each of this item's parents. // As the replies trickle in, we track when the last reply has arrived and // signal that the job is complete. // If anything goes wrong at all, we report the first error and then ignore all // other replies. auto process_reply = [this](QDBusPendingReply const& r) { if (status_ != ItemListJob::Status::Loading) { return; } --replies_remaining_; auto runtime = account_impl_->runtime_impl(); if (!runtime || !runtime->isValid()) { error_ = StorageErrorImpl::runtime_destroyed_error(method_ + ": Runtime was destroyed previously"); status_ = ItemListJob::Status::Error; Q_EMIT public_instance_->statusChanged(status_); return; } auto metadata = r.value(); Item item; try { validate_(metadata); item = ItemImpl::make_item(method_, metadata, account_impl_); } catch (StorageError const& e) { // Bad metadata received from provider, validate_() or make_item() have logged it. status_ = ItemListJob::Status::Error; error_ = e; Q_EMIT public_instance_->statusChanged(status_); return; } QList items; items.append(item); if (replies_remaining_ == 0) { status_ = ItemListJob::Status::Finished; } Q_EMIT public_instance_->itemsReady(items); if (replies_remaining_ == 0) { Q_EMIT public_instance_->statusChanged(status_); } }; auto process_error = [this](StorageError const& error) { if (status_ != ItemListJob::Status::Loading) { return; } // TODO: method name is not being set this way. error_ = error; status_ = ItemListJob::Status::Error; Q_EMIT public_instance_->statusChanged(status_); }; for (auto const& reply : replies) { new Handler(this, reply, process_reply, process_error); } } ItemListJob* MultiItemJobImpl::make_job(shared_ptr const& account_impl, QString const& method, ReplyType const& replies, ValidateFunc const& validate) { unique_ptr impl(new MultiItemJobImpl(account_impl, method, replies, validate)); auto job = new ItemListJob(move(impl)); job->p_->set_public_instance(job); return job; } } // namespace internal } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/internal/MultiItemListJobImpl.cpp000066400000000000000000000105151521521330000262130ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace internal { MultiItemListJobImpl::MultiItemListJobImpl(shared_ptr const& item_impl, QString const& method, ReplyType const& reply, ValidateFunc const& validate, FetchFunc const& fetch_next) : ListJobImplBase(item_impl->account_impl(), method, validate) , fetch_next_(fetch_next) { assert(fetch_next); item_impl_ = item_impl; process_reply_ = [this](ReplyType const& r) { if (status_ != ItemListJob::Status::Loading) { return; } auto runtime = item_impl_->account_impl()->runtime_impl(); if (!runtime || !runtime->isValid()) { error_ = StorageErrorImpl::runtime_destroyed_error(method_ + ": Runtime was destroyed previously"); status_ = ItemListJob::Status::Error; Q_EMIT public_instance_->statusChanged(status_); return; } QList items; auto metadata = r.argumentAt<0>(); for (auto const& md : metadata) { try { validate_(md); auto item = ItemImpl::make_item(method_, md, account_impl_); items.append(item); } catch (StorageError const& e) { // Bad metadata received from provider, validate_() or make_item() have logged it. error_ = e; status_ = ItemListJob::Status::Error; Q_EMIT public_instance_->statusChanged(status_); return; } } QString token = r.argumentAt<1>(); if (token.isEmpty()) { status_ = ItemListJob::Status::Finished; } Q_EMIT public_instance_->itemsReady(items); if (token.isEmpty()) { Q_EMIT public_instance_->statusChanged(status_); } else { new Handler(this, fetch_next_(token), process_reply_, process_error_); } }; process_error_ = [this](StorageError const& error) { assert(status_ == ItemListJob::Status::Loading); // TODO: method name is not being set this way. error_ = error; status_ = ItemListJob::Status::Error; Q_EMIT public_instance_->statusChanged(status_); }; new Handler(this, reply, process_reply_, process_error_); } ItemListJob* MultiItemListJobImpl::make_job(shared_ptr const& item, QString const& method, ReplyType const& reply, ValidateFunc const& validate, FetchFunc const& fetch_next) { unique_ptr impl(new MultiItemListJobImpl(item, method, reply, validate, fetch_next)); auto job = new ItemListJob(move(impl)); job->p_->set_public_instance(job); return job; } ItemListJob* MultiItemListJobImpl::make_job(StorageError const& error) { return ListJobImplBase::make_job(error); } } // namespace internal } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/internal/RuntimeImpl.cpp000066400000000000000000000104601521521330000244350ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include "RegistryInterface.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace internal { namespace { void register_meta_types() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType>(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType>(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qDBusRegisterMetaType(); qDBusRegisterMetaType>(); qDBusRegisterMetaType(); qDBusRegisterMetaType>(); } } RuntimeImpl::RuntimeImpl() : RuntimeImpl(QDBusConnection::sessionBus()) { } RuntimeImpl::RuntimeImpl(QDBusConnection const& conn) : is_valid_(true) , conn_(conn) , registry_(new RegistryInterface(storage::registry::BUS_NAME, storage::registry::OBJECT_PATH, conn_)) { register_meta_types(); } RuntimeImpl::~RuntimeImpl() { shutdown(); } bool RuntimeImpl::isValid() const { return is_valid_; } StorageError RuntimeImpl::error() const { return error_; } QDBusConnection RuntimeImpl::connection() const { return conn_; } AccountsJob* RuntimeImpl::accounts() const { QString const method = "Runtime::accounts()"; if (!is_valid_) { QString msg = "Runtime::accounts(): Runtime was destroyed previously"; return AccountsJobImpl::make_job(StorageErrorImpl::runtime_destroyed_error(msg)); } auto reply = registry_->ListAccounts(); auto This = const_pointer_cast(shared_from_this()); return AccountsJobImpl::make_job(This, method, reply); } StorageError RuntimeImpl::shutdown() { if (is_valid_) { is_valid_ = false; return StorageError(); } error_ = StorageErrorImpl::runtime_destroyed_error("Runtime::shutdown(): Runtime was destroyed previously"); return error_; } Account RuntimeImpl::make_test_account(QString const& bus_name, QString const& object_path, quint32 id, QString const& service_id, QString const& name) { storage::internal::AccountDetails ad{bus_name, QDBusObjectPath(object_path), id, service_id, name, "", ""}; return AccountImpl::make_account(shared_from_this(), ad); } } // namespace internal } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/internal/StorageErrorImpl.cpp000066400000000000000000000132441521521330000254330ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace internal { namespace { static const QString ERROR_NAMES[StorageError::Type::__LAST_STORAGE_ERROR] = { QStringLiteral("NoError"), QStringLiteral("LocalCommsError"), QStringLiteral("RemoteCommsError"), QStringLiteral("RuntimeDestroyed"), QStringLiteral("NotExists"), QStringLiteral("Exists"), QStringLiteral("Conflict"), QStringLiteral("PermissionDenied"), QStringLiteral("Cancelled"), QStringLiteral("LogicError"), QStringLiteral("InvalidArgument"), QStringLiteral("ResourceError"), QStringLiteral("Unauthorized"), }; } // namespace StorageErrorImpl::StorageErrorImpl(StorageError::Type type) : type_(type) , name_(ERROR_NAMES[type_]) , error_code_(0) { } StorageErrorImpl::StorageErrorImpl() : StorageErrorImpl(StorageError::Type::NoError) { message_ = "No error"; } StorageErrorImpl::StorageErrorImpl(StorageError::Type type, QString const& msg) : StorageErrorImpl(type) { assert( type == StorageError::Type::LocalCommsError || type == StorageError::Type::RemoteCommsError || type == StorageError::Type::RuntimeDestroyed || type == StorageError::Type::Conflict || type == StorageError::Type::PermissionDenied || type == StorageError::Type::Cancelled || type == StorageError::Type::LogicError || type == StorageError::Type::InvalidArgument); message_ = msg; } StorageErrorImpl::StorageErrorImpl(StorageError::Type type, QString const& msg, QString const& key) : StorageErrorImpl(type) { assert(type == StorageError::Type::NotExists); message_ = msg; item_id_ = key; if (type == StorageError::Type::NotExists) { item_name_ = key; } } StorageErrorImpl::StorageErrorImpl(StorageError::Type type, QString const& msg, QString const& item_id, QString const& item_name) : StorageErrorImpl(type) { assert(type == StorageError::Type::Exists); message_ = msg; item_id_ = item_id; item_name_ = item_name; } StorageErrorImpl::StorageErrorImpl(StorageError::Type type, QString const& msg, int error_code) : StorageErrorImpl(type) { assert(type == StorageError::Type::ResourceError); message_ = msg; error_code_ = error_code; } StorageError::Type StorageErrorImpl::type() const { return type_; } QString StorageErrorImpl::name() const { return name_; } QString StorageErrorImpl::message() const { return message_; } QString StorageErrorImpl::errorString() const { return name_ + ": " + message_; } QString StorageErrorImpl::itemId() const { return item_id_; } QString StorageErrorImpl::itemName() const { return item_name_; } int StorageErrorImpl::errorCode() const { return error_code_; } StorageError StorageErrorImpl::make_error(StorageError::Type type, QString const& msg) { unique_ptr p(new StorageErrorImpl(type, msg)); return StorageError(move(p)); } StorageError StorageErrorImpl::local_comms_error(QString const& msg) { unique_ptr p(new StorageErrorImpl(StorageError::Type::LocalCommsError, msg)); return StorageError(move(p)); } StorageError StorageErrorImpl::runtime_destroyed_error(QString const& msg) { unique_ptr p(new StorageErrorImpl(StorageError::Type::RuntimeDestroyed, msg)); return StorageError(move(p)); } StorageError StorageErrorImpl::not_exists_error(QString const& msg, QString const& key) { unique_ptr p(new StorageErrorImpl(StorageError::Type::NotExists, msg, key)); return StorageError(move(p)); } StorageError StorageErrorImpl::exists_error(QString const& msg, QString const& item_id, QString const& item_name) { unique_ptr p(new StorageErrorImpl(StorageError::Type::Exists, msg, item_id, item_name)); return StorageError(move(p)); } StorageError StorageErrorImpl::cancelled_error(QString const& msg) { unique_ptr p(new StorageErrorImpl(StorageError::Type::Cancelled, msg)); return StorageError(move(p)); } StorageError StorageErrorImpl::logic_error(QString const& msg) { unique_ptr p(new StorageErrorImpl(StorageError::Type::LogicError, msg)); return StorageError(move(p)); } StorageError StorageErrorImpl::invalid_argument_error(QString const& msg) { unique_ptr p(new StorageErrorImpl(StorageError::Type::InvalidArgument, msg)); return StorageError(move(p)); } StorageError StorageErrorImpl::resource_error(QString const& msg, int error_code) { unique_ptr p(new StorageErrorImpl(StorageError::Type::ResourceError, msg, error_code)); return StorageError(move(p)); } } // namespace internal } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/internal/UploaderImpl.cpp000066400000000000000000000331631521521330000245720ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include "ProviderInterface.h" //#include #include #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace internal { UploaderImpl::UploaderImpl(shared_ptr const& item_impl, QString const& method, QDBusPendingReply const& reply, std::function const& validate, Item::ConflictPolicy policy, qint64 size_in_bytes) : status_(Uploader::Status::Loading) , method_(method) , item_impl_(item_impl) , validate_(validate) , policy_(policy) , size_in_bytes_(size_in_bytes) { assert(item_impl); assert(validate); assert(!method.isEmpty()); assert(size_in_bytes >= 0); auto process_reply = [this, method](QDBusPendingReply const& r) { if (status_ != Uploader::Status::Loading) { return; // Don't transition to a final state more than once. } auto runtime = item_impl_->runtime_impl(); if (!runtime || !runtime->isValid()) { QString msg = method + ": Runtime was destroyed previously"; error_ = StorageErrorImpl::runtime_destroyed_error(msg); socket_.abort(); public_instance_->setErrorString(msg); status_ = Uploader::Status::Error; Q_EMIT public_instance_->statusChanged(status_); return; } upload_id_ = r.argumentAt<0>(); fd_ = r.argumentAt<1>(); if (fd_.fileDescriptor() < 0) { // LCOV_EXCL_START QString msg = method + ": invalid file descriptor returned by provider"; qCritical().noquote() << msg; error_ = StorageErrorImpl::local_comms_error(msg); socket_.abort(); public_instance_->setErrorString(msg); status_ = Uploader::Status::Error; Q_EMIT public_instance_->statusChanged(status_); return; // LCOV_EXCL_STOP } // We forward any QIODevice signals emitted by the socket to the public instance. connect(&socket_, &QIODevice::aboutToClose, public_instance_, &QIODevice::aboutToClose); connect(&socket_, &QIODevice::bytesWritten, public_instance_, &QIODevice::bytesWritten); connect(&socket_, &QIODevice::readChannelFinished, public_instance_, &QIODevice::readChannelFinished); connect(&socket_, &QIODevice::readyRead, public_instance_, &QIODevice::readyRead); #if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) connect(&socket_, &QIODevice::channelBytesWritten, public_instance_, &QIODevice::channelBytesWritten); connect(&socket_, &QIODevice::channelReadyRead, public_instance_, &QIODevice::channelReadyRead); #endif socket_.setSocketDescriptor(fd_.fileDescriptor(), QLocalSocket::ConnectedState, QIODevice::WriteOnly); flush_buffer(); status_ = Uploader::Status::Ready; Q_EMIT public_instance_->statusChanged(status_); }; auto process_error = [this](StorageError const& error) { // TODO: This does not set the method error_ = error; status_ = Uploader::Status::Error; socket_.abort(); public_instance_->setErrorString(error.errorString()); Q_EMIT public_instance_->statusChanged(status_); }; handler_ = new Handler>(this, reply, process_reply, process_error); } UploaderImpl::UploaderImpl(StorageError const& e) : status_(Uploader::Status::Error) , error_(e) { } UploaderImpl::~UploaderImpl() { switch (status_) { case Uploader::Status::Loading: case Uploader::Status::Finished: case Uploader::Status::Cancelled: case Uploader::Status::Error: break; case Uploader::Status::Ready: cancel(); break; default: abort(); // Impossible. // LCOV_EXCL_LINE } } bool UploaderImpl::isValid() const { return status_ != Uploader::Status::Error && status_ != Uploader::Status::Cancelled; } Uploader::Status UploaderImpl::status() const { return status_; } StorageError UploaderImpl::error() const { return error_; } Item::ConflictPolicy UploaderImpl::policy() const { return policy_; } qint64 UploaderImpl::sizeInBytes() const { return size_in_bytes_; } Item UploaderImpl::item() const { if (status_ != Uploader::Status::Finished) { return Item(); } return Item(item_impl_); } void UploaderImpl::cancel() { static QString const method = "Uploader::cancel()"; // If we are in a final state already, ignore the call. if ( status_ == Uploader::Status::Error || status_ == Uploader::Status::Finished || status_ == Uploader::Status::Cancelled) { return; } auto runtime = item_impl_->runtime_impl(); if (!runtime || !runtime->isValid()) { QString msg = method + ": Runtime was destroyed previously"; error_ = StorageErrorImpl::runtime_destroyed_error(msg); status_ = Uploader::Status::Error; Q_EMIT public_instance_->statusChanged(status_); return; } if (!upload_id_.isEmpty()) { // We just send the cancel and ignore any reply because it is best-effort only. const auto reply = item_impl_->account_impl()->provider()->CancelUpload(upload_id_); auto process_reply = [](decltype(reply)&) { }; auto process_error = [](StorageError const&) { }; new Handler(this, reply, process_reply, process_error); } QString msg = method + ": upload was cancelled"; error_ = StorageErrorImpl::cancelled_error(msg); socket_.abort(); public_instance_->setErrorString(msg); status_ = Uploader::Status::Cancelled; Q_EMIT public_instance_->statusChanged(status_); } void UploaderImpl::close() { static QString const method = "Uploader::close()"; // If we encountered an error earlier or were cancelled, or if close() was // called already, we ignore the call. if (status_ == Uploader::Status::Error || status_ == Uploader::Status::Cancelled || finalizing_) { return; } // Complain if we are asked to finalize while in the Loading or Finished state. if (status_ != Uploader::Ready) { QString msg = method + ": cannot finalize while Uploader is not in the Ready state"; error_ = StorageErrorImpl::logic_error(msg); socket_.abort(); public_instance_->setErrorString(msg); status_ = Uploader::Status::Error; Q_EMIT public_instance_->statusChanged(status_); return; } auto runtime = item_impl_->runtime_impl(); if (!runtime || !runtime->isValid()) { QString msg = method + ": Runtime was destroyed previously"; error_ = StorageErrorImpl::runtime_destroyed_error(msg); status_ = Uploader::Status::Error; Q_EMIT public_instance_->statusChanged(status_); return; } finalizing_ = true; flush_buffer(); socket_.disconnectFromServer(); const auto reply = item_impl_->account_impl()->provider()->FinishUpload(upload_id_); auto process_reply = [this](decltype(reply)& r) { if (status_ == Uploader::Status::Cancelled || status_ == Uploader::Status::Error) { return; // Don't transition to a final state more than once. } auto runtime = item_impl_->runtime_impl(); if (!runtime || !runtime->isValid()) { QString msg = method + ": Runtime was destroyed previously"; error_ = StorageErrorImpl::runtime_destroyed_error(msg); socket_.abort(); public_instance_->setErrorString(msg); status_ = Uploader::Status::Error; Q_EMIT public_instance_->statusChanged(status_); return; } auto metadata = r.value(); try { validate_(metadata); item_impl_ = make_shared(metadata, item_impl_->account_impl()); status_ = Uploader::Status::Finished; } catch (StorageError const& e) { // Bad metadata received from provider, validate_() or make_item() have logged it. // TODO: This does not set the method. error_ = e; status_ = Uploader::Status::Error; } Q_EMIT public_instance_->statusChanged(status_); }; auto process_error = [this](StorageError const& error) { if (status_ != Uploader::Status::Ready) { return; // Don't transition to a final state more than once. } // TODO: this doesn't set the method error_ = error; socket_.abort(); public_instance_->setErrorString(error.errorString()); status_ = Uploader::Status::Error; Q_EMIT public_instance_->statusChanged(status_); }; new Handler(this, reply, process_reply, process_error); } qint64 UploaderImpl::bytesAvailable() const { return socket_.bytesAvailable(); } qint64 UploaderImpl::bytesToWrite() const { return socket_.bytesToWrite(); } bool UploaderImpl::canReadLine() const { return socket_.canReadLine(); } bool UploaderImpl::isSequential() const { return socket_.isSequential(); } bool UploaderImpl::waitForBytesWritten(int msecs) { if (status_ == Uploader::Status::Loading) { // Unfortunately, QDBusPendingReply::waitForFinished() does not accept a timeout. // The next-best thing we can do is to simply wait without a timeout. The DBus // method will finish eventually, even though it might take a lot longer than msecs. handler_->wait_and_process_now(); } if (flush_buffer() == -1) { return false; } return socket_.waitForBytesWritten(msecs); } bool UploaderImpl::waitForReadyRead(int msecs) { return socket_.waitForReadyRead(msecs); } // LCOV_EXCL_START // Never called by QIODevice because device is opened write-only. qint64 UploaderImpl::readData(char* data, qint64 c) { return socket_.read(data, c); } // LCOV_EXCL_STOP qint64 UploaderImpl::writeData(char const* data, qint64 c) { switch (status_) { case Uploader::Status::Loading: { // Client is writing before we have received the file descriptor from the provider. buffer_.append(data, c); return c; } case Uploader::Status::Ready: { if (flush_buffer() == -1) { return -1; } return socket_.write(data, c); } case Uploader::Status::Cancelled: case Uploader::Status::Finished: case Uploader::Status::Error: { return -1; // Can't write to an already-finalized uploader. } default: { abort(); // Impossible // LCOV_EXCL_LINE } } // NOTREACHED } Uploader* UploaderImpl::make_job(shared_ptr const& item_impl, QString const& method, QDBusPendingReply const& reply, std::function const& validate, Item::ConflictPolicy policy, qint64 size_in_bytes) { unique_ptr impl(new UploaderImpl(item_impl, method, reply, validate, policy, size_in_bytes)); auto uploader = new Uploader(move(impl)); uploader->open(QIODevice::WriteOnly); uploader->p_->public_instance_ = uploader; return uploader; } Uploader* UploaderImpl::make_job(StorageError const& e) { unique_ptr impl(new UploaderImpl(e)); auto uploader = new Uploader(move(impl)); uploader->open(QIODevice::WriteOnly); uploader->p_->public_instance_ = uploader; QMetaObject::invokeMethod(uploader, "statusChanged", Qt::QueuedConnection, Q_ARG(lomiri::storage::qt::Uploader::Status, uploader->p_->status_)); return uploader; } qint64 UploaderImpl::flush_buffer() { qint64 bytes_written = 0; auto bytes_to_write = buffer_.size(); if (bytes_to_write > 0) { auto bytes_written = socket_.write(buffer_.data(), bytes_to_write); if (bytes_written != bytes_to_write) { return -1; // Not exactly detailed, but that's the best we can do. // LCOV_EXCL_LINE } buffer_.resize(0); } return bytes_written; } } // namespace internal } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/internal/VoidJobImpl.cpp000066400000000000000000000064251521521330000243540ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include #include using namespace std; namespace lomiri { namespace storage { namespace qt { namespace internal { VoidJobImpl::VoidJobImpl(shared_ptr const& item_impl, QString const& method, QDBusPendingReply const& reply) : status_(VoidJob::Status::Loading) , method_(method) , item_impl_(item_impl) { assert(!method_.isEmpty()); assert(item_impl); auto process_reply = [this](decltype(reply) const&) { auto runtime = item_impl_->runtime_impl(); if (!runtime || !runtime->isValid()) { error_ = StorageErrorImpl::runtime_destroyed_error(method_ + ": Runtime was destroyed previously"); status_ = VoidJob::Status::Error; Q_EMIT public_instance_->statusChanged(status_); return; } status_ = VoidJob::Status::Finished; Q_EMIT public_instance_->statusChanged(status_); }; auto process_error = [this](StorageError const& error) { error_ = error; status_ = VoidJob::Status::Error; Q_EMIT public_instance_->statusChanged(status_); }; new Handler(this, reply, process_reply, process_error); } VoidJobImpl::VoidJobImpl(StorageError const& error) : status_(VoidJob::Status::Error) , error_(error) { } bool VoidJobImpl::isValid() const { return status_ != VoidJob::Status::Error; } VoidJob::Status VoidJobImpl::status() const { return status_; } StorageError VoidJobImpl::error() const { return error_; } VoidJob* VoidJobImpl::make_job(shared_ptr const& item, QString const& method, QDBusPendingReply const& reply) { unique_ptr impl(new VoidJobImpl(item, method, reply)); auto job = new VoidJob(move(impl)); job->p_->public_instance_ = job; return job; } VoidJob* VoidJobImpl::make_job(StorageError const& error) { unique_ptr impl(new VoidJobImpl(error)); auto job = new VoidJob(move(impl)); job->p_->public_instance_ = job; QMetaObject::invokeMethod(job, "statusChanged", Qt::QueuedConnection, Q_ARG(lomiri::storage::qt::VoidJob::Status, job->p_->status_)); return job; } } // namespace internal } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/internal/unmarshal_error.cpp000066400000000000000000000112441521521330000253740ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include #include #include #include using namespace lomiri::storage::internal; using namespace std; namespace lomiri { namespace storage { namespace qt { namespace internal { namespace { template StorageError make_error(QDBusPendingCallWatcher const& call) { QDBusPendingReply reply = call; auto msg = reply.argumentAt<0>(); return StorageErrorImpl::make_error(T, msg); } template<> StorageError make_error(QDBusPendingCallWatcher const& call) { QDBusPendingReply reply = call; auto msg = reply.argumentAt<0>(); auto key = reply.argumentAt<1>(); return StorageErrorImpl::not_exists_error(msg, key); } template<> StorageError make_error(QDBusPendingCallWatcher const& call) { QDBusPendingReply reply = call; auto msg = reply.argumentAt<0>(); auto id = reply.argumentAt<1>(); auto name = reply.argumentAt<2>(); return StorageErrorImpl::exists_error(msg, id, name); } template<> StorageError make_error(QDBusPendingCallWatcher const& call) { QDBusPendingReply reply = call; auto msg = reply.argumentAt<0>(); auto error_code = reply.argumentAt<1>(); return StorageErrorImpl::resource_error(msg, error_code); } static const map> exception_factories = { { "RemoteCommsException", make_error }, { "NotExistsException", make_error }, { "ExistsException", make_error }, { "ConflictException", make_error }, { "UnauthorizedException", make_error }, { "PermissionException", make_error }, { "CancelledException", make_error }, { "LogicException", make_error }, { "InvalidArgumentException", make_error }, { "ResourceException", make_error }, { "QuotaException", make_error }, { "UnknownException", make_error } // Yes, LocalCommsError is intentional }; } // namespace StorageError unmarshal_error(QDBusPendingCallWatcher const& call) { assert(call.isError()); int err = call.error().type(); if (err != QDBusError::Other) { // Some DBus error that doesn't represent a StorageError. return StorageErrorImpl::local_comms_error(call.error().message()); } auto exception_type = call.error().name(); if (!exception_type.startsWith(DBUS_ERROR_PREFIX)) { // Some error with the wrong prefix (should never happen unless the server is broken). QString msg = "unmarshal_exception(): unknown exception type received from server: " + exception_type + ": " + call.error().message(); return StorageErrorImpl::local_comms_error(msg); } exception_type = exception_type.remove(0, strlen(DBUS_ERROR_PREFIX)); auto factory_it = exception_factories.find(exception_type); if (factory_it == exception_factories.end()) { // Some StorageError that we don't recognize. QString msg = "unmarshal_exception(): unknown exception type received from server: " + exception_type + ": " + call.error().message(); return StorageErrorImpl::local_comms_error(msg); } return factory_it->second(call); } } // namespace internal } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/internal/validate.cpp000066400000000000000000000147301521521330000237650ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include #include #include #include #include using namespace lomiri::storage::internal; using namespace std; namespace lomiri { namespace storage { namespace qt { namespace internal { namespace { // Check that actual type and value match the expect type and value for a particular metadata entry. void validate_type_and_value(QString const& prefix, QMapIterator actual, unordered_map::const_iterator known) { using namespace lomiri::storage::metadata; switch (known->second) { case MetadataType::iso_8601_date_time: { if (actual.value().type() != QVariant::String) { QString msg = prefix + actual.key() + ": expected value of type QString, but received value of type " + actual.value().typeName(); throw StorageErrorImpl::local_comms_error(msg); } QDateTime dt = QDateTime::fromString(actual.value().toString(), Qt::ISODate); if (!dt.isValid()) { QString msg = prefix + actual.key() + ": value \"" + actual.value().toString() + "\" does not parse as ISO-8601 date"; throw StorageErrorImpl::local_comms_error(msg); } auto timespec = dt.timeSpec(); if (timespec == Qt::LocalTime) { QString msg = prefix + actual.key() + ": value \"" + actual.value().toString() + "\" lacks a time zone specification"; throw StorageErrorImpl::local_comms_error(msg); } break; } case MetadataType::non_zero_pos_int64: { auto variant = actual.value(); if (variant.type() != QVariant::LongLong) { QString msg = prefix + actual.key() + ": expected value of type qlonglong, but received value of type " + variant.typeName(); throw StorageErrorImpl::local_comms_error(msg); } qint64 val = variant.toLongLong(); if (val < 0) { QString msg = prefix + actual.key() + ": expected value >= 0, but received " + QString::number(val); throw StorageErrorImpl::local_comms_error(msg); } break; } case MetadataType::string: case MetadataType::boolean: { break; } default: { abort(); // Impossible. // LCOV_EXCL_LINE } } } } // namespace void validate(QString const& method, ItemMetadata const& md) { using namespace lomiri::storage::metadata; QString prefix = method + ": received invalid metadata from provider"; if (!md.item_id.isEmpty()) { prefix += " (id = " + md.item_id + ")"; } prefix += ": "; try { // Basic sanity checks for mandatory fields. if (md.item_id.isEmpty()) { throw StorageErrorImpl::local_comms_error(prefix + "item_id cannot be empty"); } if (md.type != ItemType::root) { if (md.parent_ids.isEmpty()) { throw StorageErrorImpl::local_comms_error(prefix + "file or folder must have at least one parent ID"); } for (int i = 0; i < md.parent_ids.size(); ++i) { if (md.parent_ids.at(i).isEmpty()) { throw StorageErrorImpl::local_comms_error(prefix + "parent_id of file or folder cannot be empty"); } } } if (md.type == ItemType::root && !md.parent_ids.isEmpty()) { throw StorageErrorImpl::local_comms_error(prefix + "parent_ids of root must be empty"); } if (md.type != ItemType::root) // Dropbox does not support metadata for roots. { if (md.name.isEmpty()) { throw StorageErrorImpl::local_comms_error(prefix + "name cannot be empty"); } } if (md.type == ItemType::file && md.etag.isEmpty()) // WebDav doesn't do etag for folders. { throw StorageErrorImpl::local_comms_error(prefix + "etag of a file cannot be empty"); } // Sanity check metadata to make sure only known metadata keys appear. QMapIterator actual(md.metadata); while (actual.hasNext()) { actual.next(); auto known = known_metadata.find(actual.key().toStdString()); if (known == known_metadata.end()) { qWarning().noquote().nospace() << prefix << "unknown metadata key: \"" << actual.key() << "\""; } else { validate_type_and_value(prefix, actual, known); } } // Sanity check metadata to make sure that mandatory fields are present. if (md.type == ItemType::file) { if (!md.metadata.contains(metadata::SIZE_IN_BYTES) || !md.metadata.contains(metadata::LAST_MODIFIED_TIME)) { QString msg = prefix + "missing key \"" + metadata::SIZE_IN_BYTES + "\" in metadata"; throw StorageErrorImpl::local_comms_error(msg); } } } catch (StorageError const& e) { qCritical().noquote() << e.errorString(); throw; } } } // namespace internal } // namespace qt } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/qt/lomiri-storage-framework-qt-client.pc.in000066400000000000000000000004261521521330000274300ustar00rootroot00000000000000Name: @LSF_CLIENT_NAME@ Description: A Qt client library for the storage framework Version: @PROJECT_VERSION@ Requires.private: @LSF_CLIENT_DEPS_PRIVATE@ Cflags: -I@CMAKE_INSTALL_FULL_INCLUDEDIR@/@LSF_CLIENT_INCLUDE_NAME@ Libs: -L@CMAKE_INSTALL_FULL_LIBDIR@ -l@LSF_CLIENT_NAME@ lomiri-storage-framework-0.5.0/src/registry/000077500000000000000000000000001521521330000210735ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/src/registry/CMakeLists.txt000066400000000000000000000026741521521330000236440ustar00rootroot00000000000000set(TARGET "lomiri-storage-framework-registry") qt_add_dbus_adaptor(adaptor_files ${CMAKE_SOURCE_DIR}/data/registry.xml lomiri/storage/registry/internal/RegistryAdaptor.h lomiri::storage::registry::internal::RegistryAdaptor ) set_source_files_properties(${adaptor_files} PROPERTIES COMPILE_FLAGS "-Wno-ctor-dtor-privacy -Wno-missing-field-initializers" GENERATED TRUE ) add_library(registry-static STATIC internal/ListAccountsHandler.cpp internal/qdbus-last-error-msg.cpp internal/RegistryAdaptor.cpp ${CMAKE_SOURCE_DIR}/include/lomiri/storage/registry/internal/ListAccountsHandler.h ${CMAKE_SOURCE_DIR}/include/lomiri/storage/registry/internal/RegistryAdaptor.h ${adaptor_files}) set_target_properties(registry-static PROPERTIES AUTOMOC TRUE) target_link_libraries(registry-static PUBLIC lomiri-storage-framework-common-internal Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::DBus PkgConfig::GLIB_DEPS PkgConfig::ONLINEACCOUNTS_DEPS ) add_executable(${TARGET} main.cpp ) target_link_libraries(${TARGET} registry-static ) install( TARGETS ${TARGET} RUNTIME DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/${PROJECT_NAME} ) configure_file(com.lomiri.StorageFramework.Registry.service.in com.lomiri.StorageFramework.Registry.service) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/com.lomiri.StorageFramework.Registry.service DESTINATION ${CMAKE_INSTALL_DATADIR}/dbus-1/services ) lomiri-storage-framework-0.5.0/src/registry/com.lomiri.StorageFramework.Registry.service.in000066400000000000000000000002601521521330000322200ustar00rootroot00000000000000[D-BUS Service] Name=com.lomiri.StorageFramework.Registry Exec=@CMAKE_INSTALL_FULL_LIBEXECDIR@/@PROJECT_NAME@/lomiri-storage-framework-registry AssumedAppArmorLabel=unconfined lomiri-storage-framework-0.5.0/src/registry/internal/000077500000000000000000000000001521521330000227075ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/src/registry/internal/ListAccountsHandler.cpp000066400000000000000000000112211521521330000273210ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wold-style-cast" #include #pragma GCC diagnostic pop #include #include using namespace std; namespace lomiri { namespace storage { namespace registry { namespace internal { ListAccountsHandler::ListAccountsHandler(QDBusConnection const& conn, QDBusMessage const& msg, shared_ptr const& timer) : conn_(conn) , msg_(msg) , manager_("", conn) , activity_notifier_(timer) { connect(&manager_, &OnlineAccounts::Manager::ready, this, &ListAccountsHandler::manager_ready); connect(&timer_, &QTimer::timeout, this, &ListAccountsHandler::timeout); timer_.setSingleShot(true); timer_.start(25000); // TODO: Need config for this eventually. } ListAccountsHandler::~ListAccountsHandler() = default; namespace { // TODO: This is a hack until Online Accounts is updated to give us the provider ID, provider name, and icon name. struct ProviderDetails { char const* bus_name; char const* provider_name; }; static map const BUS_NAMES = { { "storage-provider-test", { "com.lomiri.StorageFramework.Provider.ProviderTest", "Test Provider" } }, { "storage-provider-mcloud", { "com.lomiri.StorageFramework.Provider.McloudProvider", "mcloud" } }, { "storage-provider-owncloud", { "com.lomiri.StorageFramework.Provider.OwnCloud", "ownCloud" } }, { "storage-provider-onedrive", { "com.lomiri.StorageFramework.Provider.OnedriveProvider", "OneDrive" } }, { "storage-provider-gdrive", { "com.lomiri.StorageFramework.Provider.GdriveProvider", "GDrive" } }, { "storage-provider-nextcloud", { "com.lomiri.StorageFramework.Provider.Nextcloud", "NextCloud" } }, }; } // namespace void ListAccountsHandler::manager_ready() { timer_.stop(); disconnect(this); deleteLater(); QList accounts; for (auto const& acct : manager_.availableAccounts()) { auto const it = BUS_NAMES.find(acct->serviceId()); if (it == BUS_NAMES.end()) { continue; } storage::internal::AccountDetails ad; ad.busName = it->second.bus_name; ad.objectPath = QDBusObjectPath(QStringLiteral("/provider/%1").arg(acct->id())); ad.id = acct->id(); ad.serviceId = acct->serviceId(); ad.displayName = acct->displayName(); ad.providerName = it->second.provider_name; ad.iconName = ""; accounts.append(ad); } // Add an entry for the local provider, which Online Accounts doesn't know about. storage::internal::AccountDetails ad; ad.busName = "com.lomiri.StorageFramework.Provider.Local"; ad.objectPath = QDBusObjectPath(QStringLiteral("/provider/0")); ad.id = 0; ad.serviceId = ""; ad.displayName = g_get_user_name(); ad.providerName = "Local Provider"; ad.iconName = ""; accounts.append(ad); if (!conn_.send(msg_.createReply(QVariant::fromValue(accounts)))) { auto msg = last_error_msg(conn_); qCritical().noquote() << "ListAccounts(): could not send DBus reply" + msg; } } void ListAccountsHandler::timeout() { disconnect(this); deleteLater(); QString err = QString("cannot contact Online Accounts: request timed out after ") + QString::number(timer_.interval()) + " ms"; qCritical().noquote() << err; if (!conn_.send(msg_.createErrorReply(QDBusError::Other, err))) { auto msg = last_error_msg(conn_); qCritical().noquote() << "ListAccounts(): could not send DBus error reply" + msg; } } } // namespace internal } // namespace registry } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/registry/internal/RegistryAdaptor.cpp000066400000000000000000000031271521521330000265410ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include using namespace std; namespace lomiri { namespace storage { namespace registry { namespace internal { RegistryAdaptor::RegistryAdaptor(QDBusConnection const& conn, shared_ptr const& timer, QObject* parent) : QObject(parent) , conn_(conn) , timer_(timer) { } RegistryAdaptor::~RegistryAdaptor() = default; QList RegistryAdaptor::ListAccounts() { new ListAccountsHandler(conn_, message(), timer_); // Handler deletes itself once done. setDelayedReply(true); return QList(); } } // namespace internal } // namespace registry } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/registry/internal/qdbus-last-error-msg.cpp000066400000000000000000000024361521521330000274120ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #pragma GCC diagnostic pop namespace lomiri { namespace storage { namespace registry { namespace internal { QString last_error_msg(QDBusConnection const& conn) { auto msg = conn.lastError().message(); if (!msg.isEmpty()) { msg = ": " + msg; } return msg; } } // namespace internal } // namespace registry } // namespace storage } // namespace lomiri lomiri-storage-framework-0.5.0/src/registry/main.cpp000066400000000000000000000066621521521330000225350ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include "registryadaptor.h" #include #include #include #include #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #include #include #include #pragma GCC diagnostic pop using namespace lomiri::storage; using namespace lomiri::storage::registry::internal; using namespace std; int main(int argc, char* argv[]) { auto const prog_name = QFileInfo(argv[0]).fileName(); internal::TraceMessageHandler message_handler(prog_name); int rc = 1; try { QCoreApplication app(argc, argv); auto conn = QDBusConnection::sessionBus(); int const timeout_ms = internal::EnvVars::registry_timeout_ms(); auto inactivity_timer = make_shared(timeout_ms); QObject::connect( inactivity_timer.get(), &lomiri::storage::internal::InactivityTimer::timeout, [&app, timeout_ms] { qInfo().noquote().nospace() << "Exiting after " << QString::number(timeout_ms) << " ms of idle time"; app.quit(); }); registry::internal::RegistryAdaptor registry_adaptor(conn, inactivity_timer); new ::RegistryAdaptor(®istry_adaptor); auto const& object_path = registry::OBJECT_PATH; if (!conn.registerObject(object_path, ®istry_adaptor)) { auto msg = last_error_msg(conn); throw runtime_error(string("Could not register object path ") + object_path.toStdString() + msg.toStdString()); } qDBusRegisterMetaType(); qDBusRegisterMetaType>(); auto const& bus_name = registry::BUS_NAME; if (!conn.registerService(bus_name)) { auto msg = last_error_msg(conn); throw runtime_error(string("Could not acquire DBus name ") + bus_name.toStdString() + msg.toStdString()); } rc = app.exec(); if (!conn.unregisterService(bus_name)) { auto msg = last_error_msg(conn); throw runtime_error(string("Could not release DBus name ") + bus_name.toStdString() + msg.toStdString()); } } catch (std::exception const& e) { qCritical().noquote() << e.what(); } return rc; } lomiri-storage-framework-0.5.0/tests/000077500000000000000000000000001521521330000175765ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/tests/CMakeLists.txt000066400000000000000000000020201521521330000223300ustar00rootroot00000000000000find_package(GTest REQUIRED) configure_file(testsetup.h.in testsetup.h @ONLY) include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) add_subdirectory(utils) set(unit_test_dirs registry local-provider remote-client provider-AccountData provider-DBusPeerCache provider-ProviderInterface provider-Server ) if(QT_VERSION_MAJOR LESS 6) set(unit_test_dirs ${unit_test_dirs} local-client remote-client-v1 ) endif() set(slow_test_dirs ) set(UNIT_TEST_TARGETS "") foreach(dir ${unit_test_dirs}) add_subdirectory(${dir}) list(APPEND UNIT_TEST_TARGETS "${dir}_test") endforeach() if (${slowtests}) foreach(dir ${slow_test_dirs}) add_subdirectory(${dir}) list(APPEND UNIT_TEST_TARGETS "${dir}_test") endforeach() add_subdirectory(headers) endif() set(UNIT_TEST_TARGETS ${UNIT_TEST_TARGETS} PARENT_SCOPE) if (${slowtests}) add_subdirectory(copyright) endif() add_subdirectory(whitespace) add_subdirectory(debian-version) lomiri-storage-framework-0.5.0/tests/copyright/000077500000000000000000000000001521521330000216065ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/tests/copyright/CMakeLists.txt000066400000000000000000000005251521521330000243500ustar00rootroot00000000000000# # Test that all source files contain a copyright header. # set(CHECK_COPYRIGHT_IGNORE ${CMAKE_BINARY_DIR} CACHE STRING "Directories ignored by the copyright check") add_test(copyright ${CMAKE_CURRENT_SOURCE_DIR}/check_copyright.sh ${CMAKE_SOURCE_DIR} ${CHECK_COPYRIGHT_IGNORE}) set_tests_properties(copyright PROPERTIES TIMEOUT 900) lomiri-storage-framework-0.5.0/tests/copyright/check_copyright.sh000077500000000000000000000033641521521330000253200ustar00rootroot00000000000000#!/bin/sh # # Copyright (C) 2013 Canonical Ltd # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License version 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that 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 program. If not, see . # # Authored by: Michi Henning # # # Check that we have acceptable license information in our source files. # set -eu usage() { echo "usage: check_copyright dir [ignore_dir]" >&2 exit 2 } [ $# -lt 1 ] && usage [ $# -gt 2 ] && usage source_dir="$1" ignore_dir="${2:-}" ignore_pat="/parts/|/stage/|/prime/|~$|\\.sci$|\\.swp$|\\.git|debian|qmldir|AUTHOR|ChangeLog|HACKING|ubsan-suppress|valgrind-suppress|\\.txt$|\\.xml$|\\.in$|\\.dox$|\\.yaml$" # # We don't use the -i option of licensecheck to add ignore_dir to the pattern because Jenkins creates directories # with names that contain regex meta-characters, such as "." and "+". Instead, if ignore_dir is set, we post-filter # the output with grep -F, so we don't get false positives from licensecheck. # licensecheck -i "$ignore_pat" -r "$source_dir" > licensecheck.log if [ -n "$ignore_dir" ]; then cat licensecheck.log | grep -v -F "$ignore_dir" | grep "No copyright" > filtered.log || : else cat licensecheck.log | grep "No copyright" > filtered.log || : fi if [ -s filtered.log ]; then cat filtered.log exit 1 fi exit 0 lomiri-storage-framework-0.5.0/tests/debian-version/000077500000000000000000000000001521521330000225035ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/tests/debian-version/CMakeLists.txt000066400000000000000000000002131521521330000252370ustar00rootroot00000000000000add_test( NAME debian-version COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/check-debian-version.sh "${PROJECT_VERSION}" "${CMAKE_SOURCE_DIR}" ) lomiri-storage-framework-0.5.0/tests/debian-version/check-debian-version.sh000077500000000000000000000017721521521330000270310ustar00rootroot00000000000000#!/bin/sh project_version="$1" srcdir="$2" echo "Project version is $project_version" if [ ! -f "$srcdir/debian/changelog" ]; then echo "Skipping test: Debian packaging files not found" exit 0 fi parsed_changelog="$(dpkg-parsechangelog -l "$srcdir/debian/changelog")" if [ $? -ne 0 ]; then echo "Skipping test: could not parse change log" exit 0 fi debian_version="$(echo "$parsed_changelog" | sed -n 's/^Version: //p')" debian_upstream="$(echo "$debian_version" | sed 's/-.*//')" debian_release="$(echo "$debian_version" | sed 's/^[^-]*-//')" echo "Debian package version is ${debian_upstream} with release ${debian_release}" # The CI system augments the upstream portion of the version number # with something like "+16.04.20160701", which we want to ignore for # the sake of this comparison. stripped_upstream="$(echo "$debian_upstream" | sed 's/\+.*//')" if [ "$project_version" != "$stripped_upstream" ]; then echo "Debian package version does not match project version" exit 1 fi lomiri-storage-framework-0.5.0/tests/headers/000077500000000000000000000000001521521330000212115ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/tests/headers/CMakeLists.txt000066400000000000000000000046701521521330000237600ustar00rootroot00000000000000# # Test that all header files compile stand-alone and that no public header includes an internal one. # set(root_inc_dir ${CMAKE_SOURCE_DIR}/include) set(subdirs lomiri/storage lomiri/storage/provider lomiri/storage/qt lomiri/storage/qt/client lomiri/storage/qt/client/internal/local_client lomiri/storage/qt/client/internal/remote_client ) set(extra_inc_dirs "${Qt${QT_VERSION_MAJOR}Core_INCLUDE_DIRS}") set(extra_inc_dirs "${extra_inc_dirs};${Qt${QT_VERSION_MAJOR}DBus_INCLUDE_DIRS}") set(extra_inc_dirs "${extra_inc_dirs};${Qt${QT_VERSION_MAJOR}Network_INCLUDE_DIRS}") set(extra_inc_dirs "${extra_inc_dirs};${GIO_DEPS_INCLUDE_DIRS}") set(extra_inc_dirs "${extra_inc_dirs};${ONLINEACCOUNTS_DEPS_INCLUDE_DIRS}") set(extra_inc_dirs "${extra_inc_dirs};${LIBLOMIRI_API_DEPS_INCLUDE_DIRS}") set(extra_defines "-D BOOST_THREAD_VERSION=4") # Replace ; with -I as the include directories are separated by ";" string(REPLACE ";" " -I/" extra_inc_dirs "${extra_inc_dirs}") set(extra_inc_dirs " -I${extra_inc_dirs}") foreach(dir ${OTHER_INCLUDE_DIRS}) set(other_inc_dirs "${other_inc_dirs} -I${dir}") endforeach() set(other_inc_dirs "${other_inc_dirs} -I${CMAKE_BINARY_DIR}/include -I${CMAKE_BINARY_DIR}") foreach(dir ${subdirs}) string(REPLACE "/" "-" location ${dir}) set(public_inc_dir ${root_inc_dir}/${dir}) set(internal_inc_dir ${public_inc_dir}/internal) # Test that each public header compiles stand-alone. add_test(stand-alone-${location}-headers ${CMAKE_CURRENT_SOURCE_DIR}/compile_headers.py ${extra_defines} ${public_inc_dir} ${CMAKE_CXX_COMPILER} "${CMAKE_CXX_COMPILER_ARG1} -fPIC -I${root_inc_dir} -I${public_inc_dir} ${other_inc_dirs} ${CMAKE_CXX_FLAGS} ${extra_inc_dirs}") # Test that each internal header compiles stand-alone. if (IS_DIRECTORY ${internal_inc_dir}) add_test(stand-alone-${location}-internal-headers ${CMAKE_CURRENT_SOURCE_DIR}/compile_headers.py ${extra_defines} ${internal_inc_dir} ${CMAKE_CXX_COMPILER} "${CMAKE_CXX_COMPILER_ARG1} -fPIC -I${root_inc_dir} -I${internal_inc_dir} ${other_inc_dirs} ${CMAKE_CXX_FLAGS} ${extra_inc_dirs}") endif() if (NOT ${public_inc_dir} MATCHES "/internal/") # Test that no public header includes an internal header add_test(clean-public-${location}-headers ${CMAKE_CURRENT_SOURCE_DIR}/check_public_headers.py ${public_inc_dir}) endif() endforeach() lomiri-storage-framework-0.5.0/tests/headers/check_public_headers.py000077500000000000000000000070001521521330000256710ustar00rootroot00000000000000#! /usr/bin/env python3 # # Copyright (C) 2013 Canonical Ltd # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License version 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that 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 program. If not, see . # # Authored by: Michi Henning # # # Little helper program to test that public header files don't include internal header files. # # Usage: check_public_headers.py directory # # The directory specifies the location of the header files. All files in that directory ending in .h (but not # in subdirectories) are tested, provided they do not contain "internal" as a path component. # import argparse import os import sys import re # # Write the supplied message to stderr, preceded by the program name. # def error(msg): print(os.path.basename(sys.argv[0]) + ": " + msg, file=sys.stderr) # # Write the supplied message to stdout, preceded by the program name. # def message(msg): print(os.path.basename(sys.argv[0]) + ": " + msg) def split(path): (head, tail) = os.path.split(path) while (head != os.sep): (head, tail) = os.path.split(head) yield tail # # For each of the supplied headers, check whether that header includes something in an internal directory, # provided the header path does not contain "internal" as a path component. # Return the count of headers that do this. # def test_files(hdr_dir, hdrs): num_errs = 0 for dir in split(hdr_dir): if dir == 'internal': return 0 for hdr in hdrs: try: hdr_name = os.path.join(hdr_dir, hdr) file = open(hdr_name, 'r', encoding = 'utf=8') except OSError as e: error("cannot open \"" + hdr_name + "\": " + e.strerror) sys.exit(1) include_pat = re.compile(r'#[ \t]*include[ \t]+[<"](.*?)[>"]') lines = file.readlines() line_num = 0 for l in lines: line_num += 1 include_mo = include_pat.match(l) if include_mo: hdr_path = include_mo.group(1) if 'internal/' in hdr_path: num_errs += 1 # Yes, write to stdout because this is expected output message(hdr_name + " includes an internal header at line " + str(line_num) + ": " + hdr_path) return num_errs def run(): # # Parse arguments. # parser = argparse.ArgumentParser(description = 'Test that no public header includes an internal header.') parser.add_argument('dir', nargs = 1, help = 'The directory to look for header files ending in ".h"') args = parser.parse_args() # # Find all the .h files in specified directory and look for #include directives that mention "internal/". # hdr_dir = args.dir[0] try: files = os.listdir(hdr_dir) except OSError as e: error("cannot open \"" + hdr_dir + "\": " + e.strerror) sys.exit(1) hdrs = [hdr for hdr in files if hdr.endswith('.h')] if test_files(hdr_dir, hdrs) != 0: sys.exit(1) # Errors were reported earlier if __name__ == '__main__': run() lomiri-storage-framework-0.5.0/tests/headers/compile_headers.py000077500000000000000000000150601521521330000247130ustar00rootroot00000000000000#! /usr/bin/env python3 # # Copyright (C) 2013 Canonical Ltd # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License version 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that 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 program. If not, see . # # Authored by: Michi Henning # # # Little helper program to test that header files are stand-alone compilable (and therefore don't depend on # other headers being included first). # # Usage: compile_headers.py directory compiler [compiler_flags] # # The directory specifies the location of the header files. All files in that directory ending in .h (but not # in subdirectories) are tested. # # The compiler argument specifies the compiler to use (such as "gcc"), and the compiler_flags argument (which # must be a single string argument, not a bunch of separate strings) specifies any additional flags, such # as "-I -g". The flags need not include "-c". # # For each header file in the specified directory, the script create a corresponding .cpp that includes the # header file. The .cpp file is created in the current directory (which isn't necessarily the same one as # the directory the header files are in). The script runs the compiler on the generated .cpp file and, if the # compiler returns non-zero exit status, it prints a message (on stdout) reporting the failure. # # The script does not stop if a file fails to compile. If all source files compile successfully, no output (other # than the output from the compiler) is written, and the exit status is zero. If one or more files do not compile, # or there are any other errors, such as not being able to open a file, exit status is non-zero. # # Messages about files that fail to compile are written to stdout. Message about other problems, such as non-existent # files and the like, are written to stderr. # # The compiler's output goes to whatever stream the compiler writes to and is left alone. # import argparse import os import re import shlex import subprocess import sys import concurrent.futures, multiprocessing # # Write the supplied message to stderr, preceded by the program name. # def error(msg): print(os.path.basename(sys.argv[0]) + ": " + msg, file=sys.stderr) # # Write the supplied message to stdout, preceded by the program name. # def message(msg): print(os.path.basename(sys.argv[0]) + ": " + msg) # # Create a source file in the current directory that includes the specified header, compile it, # and check exit status from the compiler. Throw if the compile command itself fails, # return False if the compile command worked but reported errors, True if the compile succeeded. # def run_compiler(hdr, compiler, copts, define, verbose, hdr_dir): try: compile_dir = "./.header_tests" os.makedirs(compile_dir, exist_ok=True) src_name = os.path.join(compile_dir, hdr) + ".cpp" if not os.path.exists(src_name): src_fd = os.open(src_name, os.O_WRONLY | os.O_CREAT) src = os.fdopen(src_fd, 'w') src.write("#include <" + hdr + ">" + "\n") src.write("#include <" + hdr + ">" + "\n") # To test that double-inclusion is safe # Add any extra defines to the command line. for flag in define: copts = "-D" + flag + " " + copts if verbose: print(compiler + " -c " + src_name + " " + copts) status = subprocess.call([compiler] + shlex.split(copts) + ["-c", src_name]) if status != 0: message("cannot compile \"" + hdr + "\"") # Yes, write to stdout because this is expected output obj = hdr + ".o" try: os.unlink(obj) except: pass gcov = hdr + ".gcno" try: os.unlink(gcov) except: pass return status == 0 except OSError as e: error(e.strerror) raise # # For each of the supplied headers, create a source file in the current directory that includes the header # and then try to compile the header. Returns normally if all files could be compiled successfully and # throws, otherwise. # def test_files(hdrs, compiler, copts, define, verbose, hdr_dir): num_errs = 0 executor = concurrent.futures.ThreadPoolExecutor(max_workers=multiprocessing.cpu_count()) futures = [executor.submit(run_compiler, h, compiler, copts, define, verbose, hdr_dir) for h in hdrs] for f in futures: try: if not f.result(): num_errs += 1 except OSError: num_errs += 1 pass # Error reported already if num_errs != 0: msg = str(num_errs) + " file" if num_errs != 1: msg += "s" msg += " failed to compile" message(msg) # Yes, write to stdout because this is expected output sys.exit(1) def run(): # # Parse arguments. # parser = argparse.ArgumentParser(description = 'Test that all headers in the passed directory compile stand-alone.') parser.add_argument('-v', '--verbose', action='store_true', help = 'Trace invocations of the compiler') parser.add_argument('-D', '--define', action='append', default=[], help = 'Additional -D directives to be added to the compiler command line') parser.add_argument('dir', nargs = 1, help = 'The directory to look for header files ending in ".h"') parser.add_argument('compiler', nargs = 1, help = 'The compiler executable, such as "gcc"') parser.add_argument('copts', nargs = '?', default="", help = 'The compiler options (excluding -c), such as "-g -Wall -I.", as a single string') args = parser.parse_args() # # Find all the .h files in specified directory and do the compilation for each one. # hdr_dir = args.dir[0] try: files = os.listdir(hdr_dir) except OSError as e: msg = "cannot open \"" + hdr_dir + "\": " + e.strerror error(msg) sys.exit(1) hdrs = [hdr for hdr in files if hdr.endswith('.h')] try: test_files(hdrs, args.compiler[0], args.copts, args.define, args.verbose, hdr_dir) except OSError: sys.exit(1) # Errors were written earlier if __name__ == '__main__': run() lomiri-storage-framework-0.5.0/tests/local-client/000077500000000000000000000000001521521330000221445ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/tests/local-client/CMakeLists.txt000066400000000000000000000007411521521330000247060ustar00rootroot00000000000000add_executable(local-client_test local-client_test.cpp) set_target_properties(local-client_test PROPERTIES AUTOMOC TRUE) add_definitions(-DTEST_DIR="${CMAKE_CURRENT_BINARY_DIR}") target_link_libraries(local-client_test PRIVATE lomiri-storage-framework-qt-local-client Qt${QT_VERSION_MAJOR}::Test GTest::gtest ) add_dependencies(local-client_test qt-client-all-headers) gtest_discover_tests(local-client_test) set(UNIT_TEST_TARGETS ${UNIT_TEST_TARGETS} PARENT_SCOPE) lomiri-storage-framework-0.5.0/tests/local-client/local-client_test.cpp000066400000000000000000002125101521521330000262560ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #pragma GCC diagnostic pop #include #include #include #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wold-style-cast" #include #pragma GCC diagnostic pop #include #ifndef LSF_LOCAL_CLIENT_LOW_PRIO_TESTS #define LSF_LOCAL_CLIENT_LOW_PRIO_TESTS 0 #endif Q_DECLARE_METATYPE(QLocalSocket::LocalSocketState) using namespace lomiri::storage; using namespace lomiri::storage::qt::client; using namespace std; // Yes, that's ridiculously long, but the builders in Jenkins and the CI Train // are stupifyingly slow at times. static constexpr int SIGNAL_WAIT_TIME = 30000; // Bunch of helper functions to reduce the amount of noise in the tests. template void wait(T fut) { QFutureWatcher w; QSignalSpy spy(&w, &decltype(w)::finished); w.setFuture(fut); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } template<> void wait(QFuture fut) { QFutureWatcher w; QSignalSpy spy(&w, &decltype(w)::finished); w.setFuture(fut); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } template T call(QFuture fut) { wait(fut); return fut.result(); } template <> void call(QFuture fut) { wait(fut); fut.waitForFinished(); } Account::SPtr get_account(Runtime::SPtr const& runtime) { auto accounts = call(runtime->accounts()); return accounts[0]; } Root::SPtr get_root(Runtime::SPtr const& runtime) { auto acc = get_account(runtime); auto roots = call(acc->roots()); return roots[0]; } Folder::SPtr get_parent(Item::SPtr const& item) { assert(item->type() != ItemType::root); auto parents = call(item->parents()); return parents[0]; } void clear_folder(Folder::SPtr folder) { auto items = call(folder->list()); for (auto i : items) { i->delete_item().waitForFinished(); } } bool content_matches(File::SPtr const& file, QByteArray const& expected) { QFile f(file->native_identity()); assert(f.open(QIODevice::ReadOnly)); QByteArray buf = f.readAll(); return buf == expected; } File::SPtr write_file(Folder::SPtr const& folder, QString const& name, QByteArray const& contents) { QString ofile = folder->native_identity() + "/" + name; QFile f(ofile); assert(f.open(QIODevice::Truncate | QIODevice::WriteOnly)); if (!contents.isEmpty()) { assert(f.write(contents)); } f.close(); auto items = call(folder->lookup(name)); return dynamic_pointer_cast(items[0]); } File::SPtr make_deleted_file(Folder::SPtr parent, QString const& name) { auto file = write_file(parent, name, "bytes"); call(file->delete_item()); return file; } Folder::SPtr make_deleted_folder(Folder::SPtr parent, QString const& name) { auto folder = call(parent->create_folder(name)); call(folder->delete_item()); return folder; } TEST(Runtime, lifecycle) { auto runtime = Runtime::create(); runtime->shutdown(); runtime->shutdown(); // Just to show that this is safe. } TEST(Runtime, basic) { auto runtime = Runtime::create(); auto acc = get_account(runtime); EXPECT_EQ(runtime, acc->runtime()); auto owner = acc->owner(); EXPECT_EQ(QString(g_get_user_name()), owner); auto owner_id = acc->owner_id(); EXPECT_EQ(QString::number(getuid()), owner_id); auto description = acc->description(); EXPECT_EQ(description, QString("Account for ") + owner + " (" + owner_id + ")"); } TEST(Runtime, accounts) { auto runtime = Runtime::create(); auto acc = get_account(runtime); auto roots = call(acc->roots()); EXPECT_EQ(1, roots.size()); // Get roots again, to get coverage for lazy initialization. roots = call(acc->roots()); ASSERT_EQ(1, roots.size()); } TEST(Root, basic) { auto runtime = Runtime::create(); auto acc = get_account(runtime); auto root = get_root(runtime); EXPECT_EQ(acc, root->account()); EXPECT_EQ(ItemType::root, root->type()); EXPECT_EQ("", root->name()); EXPECT_NE("", root->etag()); { auto parents = call(root->parents()); EXPECT_TRUE(parents.isEmpty()); EXPECT_TRUE(root->parent_ids().isEmpty()); } { // get() must return the root. auto item = call(root->get(root->native_identity())); EXPECT_NE(nullptr, dynamic_pointer_cast(item)); EXPECT_TRUE(root->equal_to(item)); } // Free and used space can be anything, but must be > 0. { auto free_space = call(root->free_space_bytes()); cerr << "bytes free: " << free_space << endl; EXPECT_GT(free_space, 0); } { auto used_space = call(root->used_space_bytes()); cerr << "bytes used: " << used_space << endl; EXPECT_GT(used_space, 0); } } TEST(Folder, basic) { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); auto items = call(root->list()); EXPECT_TRUE(items.isEmpty()); // Create a file and check that it was created with correct type, name, and size 0. auto uploader = call(root->create_file("file1", 0)); auto file = call(uploader->finish_upload()); EXPECT_EQ(ItemType::file, file->type()); EXPECT_EQ("file1", file->name()); EXPECT_EQ(0, file->size()); EXPECT_EQ(root->native_identity() + "/file1", file->native_identity()); // Create a folder and check that it was created with correct type and name. auto folder = call(root->create_folder("folder1")); EXPECT_EQ(ItemType::folder, folder->type()); EXPECT_EQ("folder1", folder->name()); EXPECT_EQ(root->native_identity() + "/folder1", folder->native_identity()); // Check that we can find both file1 and folder1. auto item = call(root->lookup("file1"))[0]; file = dynamic_pointer_cast(item); ASSERT_NE(nullptr, file); EXPECT_EQ("file1", file->name()); EXPECT_EQ(0, file->size()); item = call(root->lookup("folder1"))[0]; folder = dynamic_pointer_cast(item); ASSERT_NE(nullptr, folder); ASSERT_EQ(nullptr, dynamic_pointer_cast(folder)); EXPECT_EQ("folder1", folder->name()); item = call(root->get(file->native_identity())); file = dynamic_pointer_cast(item); ASSERT_NE(nullptr, file); EXPECT_EQ("file1", file->name()); EXPECT_EQ(0, file->size()); item = call(root->get(folder->native_identity())); folder = dynamic_pointer_cast(item); ASSERT_NE(nullptr, folder); ASSERT_EQ(nullptr, dynamic_pointer_cast(folder)); EXPECT_EQ("folder1", folder->name()); // Check that list() returns file1 and folder1. items = root->list().result(); ASSERT_EQ(2, items.size()); auto left = items[0]; auto right = items[1]; ASSERT_TRUE((dynamic_pointer_cast(left) && dynamic_pointer_cast(right)) || (dynamic_pointer_cast(right) && dynamic_pointer_cast(left))); if (dynamic_pointer_cast(left)) { file = dynamic_pointer_cast(left); folder = dynamic_pointer_cast(right); } else { file = dynamic_pointer_cast(right); folder = dynamic_pointer_cast(left); } EXPECT_EQ("file1", file->name()); EXPECT_EQ("folder1", folder->name()); EXPECT_TRUE(file->root()->equal_to(root)); EXPECT_TRUE(folder->root()->equal_to(root)); // Parent of both file and folder must be the root. EXPECT_TRUE(root->equal_to(get_parent(file))); EXPECT_TRUE(root->equal_to(get_parent(folder))); EXPECT_EQ(root->native_identity(), file->parent_ids()[0]); EXPECT_EQ(root->native_identity(), folder->parent_ids()[0]); // Delete the file and check that only the directory is left. call(file->delete_item()); items = call(root->list()); ASSERT_EQ(1, items.size()); folder = dynamic_pointer_cast(items[0]); ASSERT_NE(nullptr, folder); EXPECT_EQ("folder1", folder->name());; // Delete the folder and check that the root is empty. folder->delete_item().waitForFinished(); items = call(root->list()); ASSERT_EQ(0, items.size()); } TEST(Folder, nested) { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); auto d1 = call(root->create_folder("d1")); auto d2 = call(d1->create_folder("d2")); // Parent of d2 must be d1. EXPECT_TRUE(get_parent(d2)->equal_to(d1)); EXPECT_TRUE(d2->parent_ids()[0] == d1->native_identity()); // Delete is recursive d1->delete_item().waitForFinished(); auto items = call(root->list()); ASSERT_EQ(0, items.size()); } TEST(File, upload) { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); { // Upload a few bytes. QByteArray const contents = "Hello\n"; auto uploader = call(root->create_file("new_file", contents.size())); auto written = uploader->socket()->write(contents); ASSERT_EQ(contents.size(), written); auto file = call(uploader->finish_upload()); EXPECT_EQ(contents.size(), file->size()); ASSERT_TRUE(content_matches(file, contents)); // Calling finish_upload() more than once must return the original future. auto file2 = call(uploader->finish_upload()); EXPECT_TRUE(file2->equal_to(file)); // Calling cancel() after finish_upload must do nothing. uploader->cancel(); file2 = call(uploader->finish_upload()); EXPECT_TRUE(file2->equal_to(file)); call(file->delete_item()); } { // Upload exactly 64 KB. QByteArray const contents(64 * 1024, 'a'); auto uploader = call(root->create_file("new_file", contents.size())); auto written = uploader->socket()->write(contents); ASSERT_EQ(contents.size(), written); auto file = call(uploader->finish_upload()); EXPECT_EQ(contents.size(), file->size()); ASSERT_TRUE(content_matches(file, contents)); call(file->delete_item()); } { // Upload 1000 KBj QByteArray const contents(1000 * 1024, 'a'); auto uploader = call(root->create_file("new_file", contents.size())); auto written = uploader->socket()->write(contents); ASSERT_EQ(contents.size(), written); auto file = call(uploader->finish_upload()); EXPECT_EQ(contents.size(), file->size()); ASSERT_TRUE(content_matches(file, contents)); call(file->delete_item()); } { // Upload empty file. auto uploader = call(root->create_file("new_file", 0)); auto file = call(uploader->finish_upload()); ASSERT_EQ(0, file->size()); // Again, and check that the ETag is different. auto old_etag = file->etag(); sleep(1); uploader = call(file->create_uploader(ConflictPolicy::overwrite, 0)); file = call(uploader->finish_upload()); EXPECT_NE(old_etag, file->etag()); call(file->delete_item()); } { // Let the uploader go out of scope and check that the file was not created. call(root->create_file("new_file", 0)); boost::filesystem::path path(TEST_DIR "/lomiri-storage-framework/new_file"); auto status = boost::filesystem::status(path); ASSERT_FALSE(boost::filesystem::exists(status)); } } TEST(File, create_uploader) { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); // Make a new file first. auto uploader = call(root->create_file("new_file", 0)); EXPECT_EQ(0, uploader->size()); auto file = call(uploader->finish_upload()); EXPECT_EQ(0, file->size()); auto old_etag = file->etag(); // Create uploader for the file and write nothing. uploader = call(file->create_uploader(ConflictPolicy::overwrite, 0)); file = call(uploader->finish_upload()); EXPECT_EQ(0, file->size()); // Same test again, but this time, we write a bunch of data. std::string s(1000000, 'a'); uploader = call(file->create_uploader(ConflictPolicy::overwrite, s.size())); EXPECT_EQ(1000000, uploader->size()); uploader->socket()->write(&s[0], s.size()); uploader->socket()->waitForBytesWritten(SIGNAL_WAIT_TIME); // Need to sleep here, otherwise it is possible for the // upload to finish within the granularity of the file system time stamps. sleep(1); file = call(uploader->finish_upload()); EXPECT_EQ(1000000, file->size()); EXPECT_NE(old_etag, file->etag()); file->delete_item().waitForFinished(); } TEST(File, cancel_upload) { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); { auto uploader = call(root->create_file("new_file", 20)); // We haven't called finish_upload(), so the cancel is guaranteed // to catch the uploader in the in_progress state. uploader->cancel(); EXPECT_THROW(call(uploader->finish_upload()), CancelledException); boost::filesystem::path path(TEST_DIR "/lomiri-storage-framework/new_file"); auto status = boost::filesystem::status(path); ASSERT_FALSE(boost::filesystem::exists(status)); } { // Create a file with a few bytes. QByteArray original_contents = "Hello World!\n"; auto file = write_file(root, "new_file", original_contents); // Create an uploader for the file and write a bunch of bytes. auto uploader = call(file->create_uploader(ConflictPolicy::overwrite, original_contents.size())); QByteArray const contents(1024 * 1024, 'a'); auto written = uploader->socket()->write(contents); ASSERT_EQ(contents.size(), written); // No finish_upload() here, so the transfer is still in progress. Now cancel. uploader->cancel(); // finish_upload() must indicate that the upload was cancelled. EXPECT_THROW(call(uploader->finish_upload()), CancelledException); // The original file contents must still be intact. EXPECT_EQ(original_contents.size(), file->size()); ASSERT_TRUE(content_matches(file, original_contents)); call(file->delete_item()); } } TEST(File, upload_conflict) { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); // Make a new file on disk. QByteArray const contents = ""; auto file = write_file(root, "new_file", contents); auto uploader = call(file->create_uploader(ConflictPolicy::error_if_conflict, contents.size())); // Touch the file on disk to give it a new time stamp. sleep(1); ASSERT_EQ(0, system((string("touch ") + file->native_identity().toStdString()).c_str())); try { // Must get an exception because the time stamps no longer match. call(uploader->finish_upload()); FAIL(); } catch (ConflictException const&) { // TODO: check exception details. } call(file->delete_item()); } TEST(File, upload_error) { if (!LSF_LOCAL_CLIENT_LOW_PRIO_TESTS) { GTEST_SKIP() << "Skip known-broken test."; } auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); auto uploader = call(root->create_file("new_file", 0)); // Make new_file, so it gets in the way during finish_upload(). write_file(root, "new_file", ""); try { call(uploader->finish_upload()); FAIL(); } catch (ExistsException const& e) { EXPECT_TRUE(e.error_message().startsWith("Uploader::finish_upload(): item with name \"")); EXPECT_TRUE(e.error_message().endsWith("\" exists already")); EXPECT_EQ(TEST_DIR "/lomiri-storage-framework/new_file", e.native_identity()) << e.native_identity().toStdString(); EXPECT_EQ("new_file", e.name()); } } TEST(File, upload_bad_size) { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); // Uploader expects 100 bytes, but we write only 50. { auto uploader = call(root->create_file("file50", 100)); auto socket = uploader->socket(); QByteArray const contents(50, 'x'); auto written = socket->write(contents); ASSERT_EQ(50, written); try { call(uploader->finish_upload()); FAIL(); } catch (LogicException const& e) { EXPECT_TRUE(e.error_message().startsWith("Uploader::finish_upload(): ")); EXPECT_TRUE(e.error_message().endsWith(": upload size of 100 does not match actual number of bytes read: 50")); } } // Uploader expects 100 bytes, but we write 101. { auto uploader = call(root->create_file("file100", 100)); auto socket = uploader->socket(); QByteArray const contents(101, 'x'); auto written = socket->write(contents); ASSERT_EQ(101, written); try { call(uploader->finish_upload()); FAIL(); } catch (LogicException const& e) { EXPECT_TRUE(e.error_message().startsWith("Uploader::finish_upload(): ")); EXPECT_TRUE(e.error_message().endsWith(": upload size of 100 does not match actual number of bytes read: 101")); } // Calling finish_upload() again must return the same future as the first time. try { call(uploader->finish_upload()); FAIL(); } catch (LogicException const& e) { EXPECT_TRUE(e.error_message().startsWith("Uploader::finish_upload(): ")); EXPECT_TRUE(e.error_message().endsWith(": upload size of 100 does not match actual number of bytes read: 101")); } } } TEST(File, create_uploader_bad_arg) { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); auto file = write_file(root, "new_file", 0); try { call(file->create_uploader(ConflictPolicy::overwrite, -1)); } catch (InvalidArgumentException const& e) { EXPECT_EQ("File::create_uploader(): size must be >= 0", e.error_message()); } } TEST(File, download) { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); { // Download a few bytes. QByteArray const contents = "Hello\n"; auto file = write_file(root, "file", contents); auto downloader = call(file->create_downloader()); EXPECT_TRUE(file->equal_to(downloader->file())); auto socket = downloader->socket(); QByteArray buf; do { // Need to pump the event loop while the socket does its thing. QSignalSpy spy(socket.get(), &QIODevice::readyRead); auto bytes_to_read = socket->bytesAvailable(); if (bytes_to_read == 0) { ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } buf.append(socket->read(bytes_to_read)); } while (buf.size() < contents.size()); // Wait for disconnected signal. QSignalSpy spy(socket.get(), &QLocalSocket::disconnected); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); ASSERT_NO_THROW(call(downloader->finish_download())); // Contents must match. EXPECT_EQ(contents, buf); } { // Download exactly 64 KB. QByteArray const contents(64 * 1024, 'a'); auto file = write_file(root, "file", contents); auto downloader = call(file->create_downloader()); EXPECT_TRUE(file->equal_to(downloader->file())); auto socket = downloader->socket(); QByteArray buf; do { // Need to pump the event loop while the socket does its thing. QSignalSpy spy(socket.get(), &QIODevice::readyRead); auto bytes_to_read = socket->bytesAvailable(); if (bytes_to_read == 0) { ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } buf.append(socket->read(bytes_to_read)); } while (buf.size() < contents.size()); // Wait for disconnected signal. QSignalSpy spy(socket.get(), &QLocalSocket::disconnected); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); ASSERT_NO_THROW(call(downloader->finish_download())); // Contents must match EXPECT_EQ(contents, buf); } { // Download 1 MB + 1 bytes. QByteArray const contents(1024 * 1024 + 1, 'a'); auto file = write_file(root, "file", contents); auto downloader = call(file->create_downloader()); EXPECT_TRUE(file->equal_to(downloader->file())); auto socket = downloader->socket(); QByteArray buf; do { // Need to pump the event loop while the socket does its thing. QSignalSpy spy(socket.get(), &QIODevice::readyRead); auto bytes_to_read = socket->bytesAvailable(); if (bytes_to_read == 0) { ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } buf.append(socket->read(bytes_to_read)); } while (buf.size() < contents.size()); // Wait for disconnected signal. QSignalSpy spy(socket.get(), &QLocalSocket::disconnected); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); ASSERT_NO_THROW(call(downloader->finish_download())); // Contents must match EXPECT_EQ(contents, buf); } { // Download file containing zero bytes QByteArray const contents; auto file = write_file(root, "file", contents); auto downloader = call(file->create_downloader()); EXPECT_TRUE(file->equal_to(downloader->file())); auto socket = downloader->socket(); // No readyRead ever arrives in this case, just wait for disconnected. QSignalSpy spy(socket.get(), &QLocalSocket::disconnected); if (socket->state() != QLocalSocket::UnconnectedState) { ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } ASSERT_NO_THROW(call(downloader->finish_download())); } { // Don't ever call read on empty file. QByteArray const contents; auto file = write_file(root, "file", contents); auto downloader = call(file->create_downloader()); EXPECT_TRUE(file->equal_to(downloader->file())); auto socket = downloader->socket(); // No readyRead ever arrives in this case, just wait for disconnected. QSignalSpy spy(socket.get(), &QLocalSocket::disconnected); if (socket->state() != QLocalSocket::UnconnectedState) { ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } // This succeeds because the provider disconnects as soon // as it realizes that there is nothing to write. ASSERT_NO_THROW(call(downloader->finish_download())); } { // Don't ever call read on small file. QByteArray const contents("some contents"); auto file = write_file(root, "file", contents); auto downloader = call(file->create_downloader()); EXPECT_TRUE(file->equal_to(downloader->file())); auto socket = downloader->socket(); // Wait for disconnected. if (socket->state() != QLocalSocket::UnconnectedState) { QSignalSpy spy(socket.get(), &QLocalSocket::disconnected); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } // This succeeds because the provider has written everything and disconnected. ASSERT_NO_THROW(call(downloader->finish_download())); } { // Don't ever call read on large file. QByteArray const contents(1024 * 1024, 'a'); auto file = write_file(root, "file", contents); auto downloader = call(file->create_downloader()); EXPECT_TRUE(file->equal_to(downloader->file())); auto socket = downloader->socket(); // Wait for first readyRead. Not all data fits into the socket buffer. QSignalSpy spy(socket.get(), &QLocalSocket::readyRead); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); // This fails because the provider still has data left to write. try { call(downloader->finish_download()); FAIL(); } catch (StorageException const& e) { // TODO: check exception details } } { // Let downloader go out of scope. QByteArray const contents(1024 * 1024, 'a'); auto file = write_file(root, "file", contents); auto downloader = call(file->create_downloader()); } { // Let downloader future go out of scope. QByteArray const contents(1024 * 1024, 'a'); auto file = write_file(root, "file", contents); file->create_downloader(); } } TEST(File, cancel_download) { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); { // Download enough bytes to prevent a single write in the provider from completing the download. QByteArray const contents(1024 * 1024, 'a'); auto file = write_file(root, "file", contents); auto downloader = call(file->create_downloader()); // We haven't read anything, so the cancel is guaranteed to catch the // downloader in the in_progress state. downloader->cancel(); ASSERT_THROW(call(downloader->finish_download()), CancelledException); } { // Download a few bytes. QByteArray const contents = "Hello\n"; auto file = write_file(root, "file", contents); // Finish the download. auto downloader = call(file->create_downloader()); auto socket = downloader->socket(); QByteArray buf; do { // Need to pump the event loop while the socket does its thing. QSignalSpy spy(socket.get(), &QIODevice::readyRead); auto bytes_to_read = socket->bytesAvailable(); if (bytes_to_read == 0) { ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } buf.append(socket->read(bytes_to_read)); } while (buf.size() < contents.size()); // Wait for disconnected signal. if (socket->state() != QLocalSocket::UnconnectedState) { QSignalSpy spy(socket.get(), &QLocalSocket::disconnected); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } // Now send the cancel. The download is finished already, and the cancel // is too late, so finish_download() must report that the download // worked OK. downloader->cancel(); ASSERT_NO_THROW(call(downloader->finish_download())); } } TEST(File, download_error) { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); QByteArray const contents(1024 * 1024, 'a'); auto file = write_file(root, "file", contents); auto downloader = call(file->create_downloader()); EXPECT_TRUE(file->equal_to(downloader->file())); auto socket = downloader->socket(); { // Wait for first readyRead. Not all data fits into the socket buffer. QSignalSpy spy(socket.get(), &QLocalSocket::readyRead); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } { // Now close the socket, to force an error at the writing end. // This gives us coverage of the error handling logic in the download worker. socket->abort(); // Wait a little, to give the worker a chance to notice the problem. // We don't wait for a signal here because all attempts to disable the // socket (via close(), abort(), or disconnectFromServer() also // stop the stateChanged signal from arriving. QTimer timer; QSignalSpy spy(&timer, &QTimer::timeout); timer.start(1000); spy.wait(); } try { call(downloader->finish_download()); FAIL(); } catch (ResourceException const& e) { EXPECT_TRUE(e.error_message().startsWith("Downloader: QLocalSocket: ")); } } TEST(File, size_error) { if (!LSF_LOCAL_CLIENT_LOW_PRIO_TESTS) { GTEST_SKIP() << "Skip known-broken test."; } auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); auto file = write_file(root, "file", ""); ASSERT_EQ(0, system("rm " TEST_DIR "/lomiri-storage-framework/file")); EXPECT_THROW(file->size(), ResourceException); } TEST(Item, move) { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); // Check that rename works within the same folder. QByteArray const contents = "Hello\n"; auto f1 = write_file(root, "f1", contents); auto f2 = call(f1->move(root, "f2")); // File must be found under new name. auto items = call(root->list()); ASSERT_EQ(1, items.size()); f2 = dynamic_pointer_cast(items[0]); ASSERT_FALSE(f2 == nullptr); // Make a folder and move f2 into it. auto folder = call(root->create_folder("folder")); f2 = call(f2->move(folder, "f2")); EXPECT_TRUE(get_parent(f2)->equal_to(folder)); // Move the folder auto item = call(folder->move(root, "folder2")); folder = dynamic_pointer_cast(item); EXPECT_EQ("folder2", folder->name()); } TEST(Item, copy) { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); QByteArray const contents = "hello\n"; auto item = write_file(root, "file", contents); auto copied_item = call(item->copy(root, "copy_of_file")); EXPECT_EQ("copy_of_file", copied_item->name()); File::SPtr copied_file = dynamic_pointer_cast(item); ASSERT_NE(nullptr, copied_file); EXPECT_TRUE(content_matches(copied_file, contents)); } TEST(Item, recursive_copy) { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); // Create the following structure: // folder // folder/empty_folder // folder/non_empty_folder // folder/non_empty_folder/nested_file // folder/non_empty_folder/1234-1234-1234-1234 // folder/file // folder/1234-1234-1234-1234 string root_path = root->native_identity().toStdString(); ASSERT_EQ(0, mkdir((root_path + "/folder").c_str(), 0700)); ASSERT_EQ(0, mkdir((root_path + "/folder/empty_folder").c_str(), 0700)); ASSERT_EQ(0, mkdir((root_path + "/folder/non_empty_folder").c_str(), 0700)); ofstream(root_path + "/folder/non_empty_folder/nested_file"); ofstream(root_path + "/folder/file"); // Add dirs that look like a tmp dirs, to get coverage on skipping those. ASSERT_EQ(0, mkdir((root_path + "/folder/" + TMPFILE_PREFIX "1234-1234-1234-1234").c_str(), 0700)); ASSERT_EQ(0, mkdir((root_path + "/folder/non_empty_folder/" + TMPFILE_PREFIX "1234-1234-1234-1234").c_str(), 0700)); // Copy folder to folder2 auto folder = dynamic_pointer_cast(call(root->lookup("folder"))[0]); ASSERT_NE(nullptr, folder); auto item = call(folder->copy(root, "folder2")); // Verify that folder2 now contains the same structure as folder. auto folder2 = dynamic_pointer_cast(item); ASSERT_NE(nullptr, folder2); EXPECT_NO_THROW(call(folder2->lookup("empty_folder"))[0]); item = call(folder2->lookup("non_empty_folder"))[0]; auto non_empty_folder = dynamic_pointer_cast(item); ASSERT_NE(nullptr, non_empty_folder); EXPECT_NO_THROW(call(non_empty_folder->lookup("nested_file"))[0]); EXPECT_NO_THROW(call(folder2->lookup("file"))[0]); } TEST(Item, time) { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); auto now = QDateTime::currentDateTimeUtc(); sleep(1); auto uploader = call(root->create_file("file", 0)); auto file = call(uploader->finish_upload()); auto t = file->last_modified_time(); // Rough check that the time is sane. EXPECT_LE(now, t); EXPECT_LE(t, now.addSecs(5)); auto creation_time = file->creation_time(); EXPECT_FALSE(creation_time.isValid()); } TEST(Item, comparison) { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); // Create two files. auto uploader = call(root->create_file("file1", 0)); auto file1 = call(uploader->finish_upload()); uploader = call(root->create_file("file2", 0)); auto file2 = call(uploader->finish_upload()); EXPECT_FALSE(file1->equal_to(file2)); // Retrieve file1 via lookup, so we get a different proxy. auto item = call(root->lookup("file1"))[0]; auto other_file1 = dynamic_pointer_cast(item); EXPECT_NE(file1, other_file1); // Compares shared_ptr values EXPECT_TRUE(file1->equal_to(other_file1)); // Deep comparison // Comparing against a deleted file must return false. call(file1->delete_item()); EXPECT_FALSE(file1->equal_to(file2)); EXPECT_FALSE(file2->equal_to(file1)); // Delete file2 as well and compare again. call(file2->delete_item()); EXPECT_FALSE(file1->equal_to(file2)); } TEST(Item, exceptions) { if (!LSF_LOCAL_CLIENT_LOW_PRIO_TESTS) { GTEST_SKIP() << "Skip known-broken test."; } auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); try { call(root->copy(nullptr, "new name")); FAIL(); } catch (InvalidArgumentException const& e) { EXPECT_EQ("Item::copy(): new_parent cannot be nullptr", e.error_message()); } auto file = write_file(root, "file", 0); try { call(file->copy(root, TMPFILE_PREFIX "copy_of_file")); FAIL(); } catch (InvalidArgumentException const& e) { EXPECT_EQ("Item::copy(): names beginning with \".lomiri-storage-framework-\" are reserved", e.error_message()); } try { call(file->copy(root, file->name())); FAIL(); } catch (ExistsException const& e) { EXPECT_EQ("Item::copy(): item with name \"file\" exists already", e.error_message()); EXPECT_EQ("file", e.name()); EXPECT_EQ(TEST_DIR "/lomiri-storage-framework/file", e.native_identity()); } try { auto file = write_file(root, "file", ""); ASSERT_EQ(0, unlink(file->native_identity().toStdString().c_str())); call(file->copy(root, file->name())); FAIL(); } catch (ResourceException const& e) { EXPECT_TRUE(e.error_message().startsWith("Item::copy(): ")); } try { auto file = write_file(root, "file", ""); ASSERT_EQ(0, unlink(file->native_identity().toStdString().c_str())); call(file->move(root, "new_name")); FAIL(); } catch (ResourceException const& e) { EXPECT_TRUE(e.error_message().startsWith("Item::move(): ")); } try { auto file = write_file(root, "file", ""); ASSERT_EQ(0, unlink(file->native_identity().toStdString().c_str())); ASSERT_EQ(0, system("chmod -x " TEST_DIR "/lomiri-storage-framework")); call(file->delete_item()); FAIL(); } catch (PermissionException const& e) { ASSERT_EQ(0, system("chmod +x " TEST_DIR "/lomiri-storage-framework")); EXPECT_TRUE(e.error_message().startsWith("Item::delete_item(): ")); } catch (std::exception const&) { ASSERT_EQ(0, system("chmod +x " TEST_DIR "/lomiri-storage-framework")); FAIL(); } ASSERT_EQ(0, system("chmod +x " TEST_DIR "/lomiri-storage-framework")); try { call(root->move(nullptr, "new name")); FAIL(); } catch (InvalidArgumentException const& e) { EXPECT_EQ("Item::move(): new_parent cannot be nullptr", e.error_message()); } try { call(root->move(root, "new name")); FAIL(); } catch (LogicException const& e) { EXPECT_EQ("Item::move(): cannot move root folder", e.error_message()); } try { auto file = write_file(root, "file", ""); call(file->move(root, file->name())); FAIL(); } catch (ExistsException const& e) { EXPECT_EQ("Item::move(): item with name \"file\" exists already", e.error_message()); EXPECT_EQ("file", e.name()); EXPECT_EQ(TEST_DIR "/lomiri-storage-framework/file", e.native_identity()); } try { call(file->move(root, TMPFILE_PREFIX "copy_of_file")); FAIL(); } catch (InvalidArgumentException const& e) { EXPECT_EQ("Item::move(): names beginning with \".lomiri-storage-framework-\" are reserved", e.error_message()); } try { call(root->lookup("abc/def")); } catch (InvalidArgumentException const& e) { EXPECT_EQ("Folder::lookup(): name \"abc/def\" contains more than one path component", e.error_message()) << e.what(); } try { call(root->create_folder("..")); } catch (InvalidArgumentException const& e) { EXPECT_EQ("Folder::create_folder(): invalid name: \"..\"", e.error_message()) << e.what(); } } TEST(Folder, exceptions) { if (!LSF_LOCAL_CLIENT_LOW_PRIO_TESTS) { GTEST_SKIP() << "Skip known-broken test."; } auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); try { write_file(root, TMPFILE_PREFIX "file", ""); call(root->lookup(TMPFILE_PREFIX "file")); FAIL(); } catch (NotExistsException const& e) { string cmd = "rm "; cmd += string(TEST_DIR) + "/lomiri-storage-framework/" + TMPFILE_PREFIX "file"; ASSERT_EQ(0, system(cmd.c_str())); EXPECT_EQ("Folder::lookup(): no such item: \".lomiri-storage-framework-file\"", e.error_message()); EXPECT_EQ(".lomiri-storage-framework-file", e.key()); } { auto fifo_id = root->native_identity() + "/fifo"; string cmd = "mkfifo " + fifo_id.toStdString(); ASSERT_EQ(0, system(cmd.c_str())); try { call(root->lookup("fifo")); FAIL(); } catch (NotExistsException const& e) { EXPECT_EQ("Folder::lookup(): no such item: \"fifo\"", e.error_message()) << e.what(); EXPECT_EQ("fifo", e.key()); } cmd = "rm " + fifo_id.toStdString(); ASSERT_EQ(0, system(cmd.c_str())); } try { call(root->lookup("no_such_file")); FAIL(); } catch (NotExistsException const& e) { EXPECT_EQ("Folder::lookup(): no such item: \"no_such_file\"", e.error_message()); EXPECT_EQ("no_such_file", e.key()); } try { call(root->create_folder(TMPFILE_PREFIX "folder")); FAIL(); } catch (InvalidArgumentException const& e) { EXPECT_EQ("Folder::create_folder(): names beginning with \".lomiri-storage-framework-\" are reserved", e.error_message()); } try { EXPECT_NO_THROW(call(root->create_folder("folder"))); call(root->create_folder("folder")); FAIL(); } catch (ExistsException const& e) { EXPECT_EQ("Folder::create_folder(): item with name \"folder\" exists already", e.error_message()); EXPECT_EQ("folder", e.name()); EXPECT_EQ(TEST_DIR "/lomiri-storage-framework/folder", e.native_identity()); } try { call(root->create_file("new_file", -1)); FAIL(); } catch (InvalidArgumentException const& e) { EXPECT_EQ("Folder::create_file(): size must be >= 0", e.error_message()); } try { call(root->create_file(TMPFILE_PREFIX "new_file", 0)); FAIL(); } catch (InvalidArgumentException const& e) { EXPECT_EQ("Folder::create_file(): names beginning with \".lomiri-storage-framework-\" are reserved", e.error_message()); } try { write_file(root, "file", ""); call(root->create_file("file", 0)); FAIL(); } catch (ExistsException const& e) { EXPECT_EQ("Folder::create_file(): item with name \"file\" exists already", e.error_message()); EXPECT_EQ("file", e.name()); EXPECT_EQ(TEST_DIR "/lomiri-storage-framework/file", e.native_identity()); } ASSERT_EQ(0, system("chmod -x " TEST_DIR "/lomiri-storage-framework")); try { call(root->create_file("new_file", 0)); FAIL(); } catch (PermissionException const& e) { EXPECT_TRUE(e.error_message().startsWith("Folder::create_file(): ")); ASSERT_EQ(0, system("chmod +x " TEST_DIR "/lomiri-storage-framework")); } catch (std::exception const& e) { ASSERT_EQ(0, system("chmod +x " TEST_DIR "/lomiri-storage-framework")); FAIL(); } ASSERT_EQ(0, system("chmod +x " TEST_DIR "/lomiri-storage-framework")); ASSERT_EQ(0, system("chmod -x " TEST_DIR "/lomiri-storage-framework")); try { call(root->create_folder("new_folder")); FAIL(); } catch (PermissionException const& e) { EXPECT_TRUE(e.error_message().startsWith("Folder::create_folder(): ")); ASSERT_EQ(0, system("chmod +x " TEST_DIR "/lomiri-storage-framework")); } catch (std::exception const& e) { ASSERT_EQ(0, system("chmod +x " TEST_DIR "/lomiri-storage-framework")); FAIL(); } ASSERT_EQ(0, system("chmod +x " TEST_DIR "/lomiri-storage-framework")); try { call(root->create_file("new_file", -1)); } catch (InvalidArgumentException const& e) { EXPECT_EQ("Folder::create_file(): size must be >= 0", e.error_message()); } } TEST(Root, root_exceptions) { if (!LSF_LOCAL_CLIENT_LOW_PRIO_TESTS) { GTEST_SKIP() << "Skip known-broken test."; } auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); try { call(root->delete_item()); FAIL(); } catch (LogicException const& e) { EXPECT_EQ("Item::delete_item(): cannot delete root folder", e.error_message()); } try { call(root->get("abc")); FAIL(); } catch (InvalidArgumentException const& e) { EXPECT_EQ("Root::get(): identity \"abc\" must be an absolute path", e.error_message()); } try { call(root->get("/etc")); FAIL(); } catch (InvalidArgumentException const& e) { EXPECT_EQ("Root::get(): identity \"/etc\" points outside the root folder", e.error_message()); } { auto folder = call(root->create_folder("folder")); auto file = write_file(folder, "testfile", "hello"); // Remove permission from folder. string cmd = "chmod -x " + folder->native_identity().toStdString(); ASSERT_EQ(0, system(cmd.c_str())); try { file = dynamic_pointer_cast(call(root->get(file->native_identity()))); FAIL(); } catch (PermissionException const& e) { EXPECT_TRUE(e.error_message().startsWith("Root::get(): ")); EXPECT_TRUE(e.error_message().contains("Permission denied")); } catch (...) { cmd = "chmod +x " + folder->native_identity().toStdString(); ASSERT_EQ(0, system(cmd.c_str())); } cmd = "chmod +x " + folder->native_identity().toStdString(); ASSERT_EQ(0, system(cmd.c_str())); clear_folder(root); } { auto file = write_file(root, "testfile", "hello"); QString id = file->native_identity(); id.append("_doesnt_exist"); try { file = dynamic_pointer_cast(call(root->get(id))); FAIL(); } catch (NotExistsException const& e) { EXPECT_EQ(id, e.key()); } clear_folder(root); } { auto fifo_id = root->native_identity() + "/fifo"; string cmd = "mkfifo " + fifo_id.toStdString(); ASSERT_EQ(0, system(cmd.c_str())); try { call(root->get(fifo_id)); FAIL(); } catch (NotExistsException const& e) { EXPECT_EQ(fifo_id, e.key()); } cmd = "rm " + fifo_id.toStdString(); ASSERT_EQ(0, system(cmd.c_str())); } { string reserved_name = TMPFILE_PREFIX "somefile"; string full_path = string(TEST_DIR) + "/lomiri-storage-framework/" + reserved_name; string cmd = "touch "; cmd += full_path; ASSERT_EQ(0, system(cmd.c_str())); auto reserved_id = QString::fromStdString(full_path); try { call(root->get(reserved_id)); FAIL(); } catch (NotExistsException const& e) { EXPECT_EQ(reserved_id, e.key()); } clear_folder(root); } } TEST(Item, deleted_exceptions) { if (!LSF_LOCAL_CLIENT_LOW_PRIO_TESTS) { GTEST_SKIP() << "Skip known-broken test."; } auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); try { auto file = make_deleted_file(root, "file"); file->etag(); FAIL(); } catch (DeletedException const& e) { EXPECT_TRUE(e.error_message().startsWith("Item::etag(): ")); EXPECT_TRUE(e.error_message().endsWith(" was deleted previously")); EXPECT_EQ(TEST_DIR "/lomiri-storage-framework/file", e.native_identity()); } try { auto file = make_deleted_file(root, "file"); file->metadata(); FAIL(); } catch (DeletedException const& e) { EXPECT_TRUE(e.error_message().startsWith("Item::metadata(): ")); } try { auto file = make_deleted_file(root, "file"); file->last_modified_time(); FAIL(); } catch (DeletedException const& e) { EXPECT_TRUE(e.error_message().startsWith("Item::last_modified_time(): ")); } try { // Copying deleted file must fail. auto file = make_deleted_file(root, "file"); call(file->copy(root, "copy_of_file")); FAIL(); } catch (DeletedException const& e) { EXPECT_TRUE(e.error_message().startsWith("Item::copy(): ")); } try { // Copying file into deleted folder must fail. // Make target folder. auto folder = call(root->create_folder("folder")); // Make a file in the root. auto uploader = call(root->create_file("file", 0)); auto file = call(uploader->finish_upload()); // Delete folder. call(folder->delete_item()); call(file->copy(folder, "file")); FAIL(); } catch (DeletedException const& e) { EXPECT_TRUE(e.error_message().startsWith("Item::copy(): ")); } clear_folder(root); try { // Moving deleted file must fail. auto file = make_deleted_file(root, "file"); call(file->move(root, "moved_file")); FAIL(); } catch (DeletedException const& e) { EXPECT_TRUE(e.error_message().startsWith("Item::move(): ")); } try { // Moving file into deleted folder must fail. // Make target folder. auto folder = call(root->create_folder("folder")); // Make a file in the root. auto uploader = call(root->create_file("file", 0)); auto file = call(uploader->finish_upload()); // Delete folder. call(folder->delete_item()); call(file->move(folder, "file")); FAIL(); } catch (DeletedException const& e) { EXPECT_TRUE(e.error_message().startsWith("Item::move(): ")); } clear_folder(root); try { auto file = make_deleted_file(root, "file"); call(file->parents()); FAIL(); } catch (DeletedException const& e) { EXPECT_TRUE(e.error_message().startsWith("Item::parents(): ")); } try { auto file = make_deleted_file(root, "file"); file->parent_ids(); FAIL(); } catch (DeletedException const& e) { EXPECT_TRUE(e.error_message().startsWith("Item::parent_ids(): ")); } try { // Deleting a deleted item must fail. auto file = make_deleted_file(root, "file"); call(file->delete_item()); FAIL(); } catch (DeletedException const& e) { EXPECT_TRUE(e.error_message().startsWith("Item::delete_item(): ")); } } TEST(Folder, deleted_exceptions) { if (!LSF_LOCAL_CLIENT_LOW_PRIO_TESTS) { GTEST_SKIP() << "Skip known-broken test."; } auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); try { auto folder = make_deleted_folder(root, "folder"); folder->name(); FAIL(); } catch (DeletedException const& e) { EXPECT_TRUE(e.error_message().startsWith("Item::name(): ")); } try { auto folder = make_deleted_folder(root, "folder"); call(folder->list()); FAIL(); } catch (DeletedException const& e) { EXPECT_TRUE(e.error_message().startsWith("Folder::list(): ")); } try { auto folder = make_deleted_folder(root, "folder"); call(folder->lookup("something")); FAIL(); } catch (DeletedException const& e) { EXPECT_TRUE(e.error_message().startsWith("Folder::lookup(): ")); } try { auto folder = make_deleted_folder(root, "folder"); call(folder->list()); FAIL(); } catch (DeletedException const& e) { EXPECT_TRUE(e.error_message().startsWith("Folder::list(): ")); } try { auto folder = make_deleted_folder(root, "folder"); call(folder->create_folder("nested_folder")); FAIL(); } catch (DeletedException const& e) { EXPECT_TRUE(e.error_message().startsWith("Folder::create_folder(): ")); } try { auto folder = make_deleted_folder(root, "folder"); call(folder->create_file("nested_file", 0)); FAIL(); } catch (DeletedException const& e) { EXPECT_TRUE(e.error_message().startsWith("Folder::create_file(): ")); } try { auto folder = make_deleted_folder(root, "folder"); folder->creation_time(); FAIL(); } catch (DeletedException const& e) { EXPECT_TRUE(e.error_message().startsWith("Item::creation_time(): ")) << e.what(); } try { auto folder = make_deleted_folder(root, "folder"); folder->native_metadata(); FAIL(); } catch (DeletedException const& e) { EXPECT_TRUE(e.error_message().startsWith("Item::native_metadata(): ")) << e.what(); } } TEST(File, deleted_exceptions) { if (!LSF_LOCAL_CLIENT_LOW_PRIO_TESTS) { GTEST_SKIP() << "Skip known-broken test."; } auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); try { auto file = make_deleted_file(root, "file"); file->name(); FAIL(); } catch (DeletedException const& e) { EXPECT_TRUE(e.error_message().startsWith("File::name(): ")); EXPECT_TRUE(e.error_message().endsWith(" was deleted previously")); EXPECT_EQ(TEST_DIR "/lomiri-storage-framework/file", e.native_identity()); } try { auto file = make_deleted_file(root, "file"); file->size(); FAIL(); } catch (DeletedException const& e) { EXPECT_TRUE(e.error_message().startsWith("File::size(): ")); EXPECT_TRUE(e.error_message().endsWith(" was deleted previously")); EXPECT_EQ(TEST_DIR "/lomiri-storage-framework/file", e.native_identity()); } try { auto file = make_deleted_file(root, "file"); call(file->create_uploader(ConflictPolicy::overwrite, 0)); FAIL(); } catch (DeletedException const& e) { EXPECT_TRUE(e.error_message().startsWith("File::create_uploader(): ")); EXPECT_TRUE(e.error_message().endsWith(" was deleted previously")); EXPECT_EQ(TEST_DIR "/lomiri-storage-framework/file", e.native_identity()); } try { auto file = make_deleted_file(root, "file"); call(file->create_downloader()); FAIL(); } catch (DeletedException const& e) { EXPECT_TRUE(e.error_message().startsWith("File::create_downloader(): ")); EXPECT_TRUE(e.error_message().endsWith(" was deleted previously")); EXPECT_EQ(TEST_DIR "/lomiri-storage-framework/file", e.native_identity()); } } TEST(Runtime, runtime_destroyed_exceptions) { // Gettting an account after shutting down the runtime must fail. { auto runtime = Runtime::create(); auto acc = get_account(runtime); runtime->shutdown(); try { acc->runtime(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Account::runtime(): runtime was destroyed previously", e.error_message()); } } // Getting an account after destroying the runtime must fail. { auto runtime = Runtime::create(); auto acc = get_account(runtime); runtime.reset(); try { acc->runtime(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Account::runtime(): runtime was destroyed previously", e.error_message()); } } // Getting accounts after shutting down the runtime must fail. { auto runtime = Runtime::create(); runtime->shutdown(); try { call(runtime->accounts()); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Runtime::accounts(): runtime was destroyed previously", e.error_message()); } } // Getting the account from a root with a destroyed runtime must fail. { auto runtime = Runtime::create(); auto root = get_root(runtime); runtime.reset(); try { root->account(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Root::account(): runtime was destroyed previously", e.error_message()); } } // Getting the account from a root with a destroyed account must fail. { auto runtime = Runtime::create(); auto acc = get_account(runtime); auto root = get_root(runtime); runtime.reset(); acc.reset(); try { root->account(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Root::account(): runtime was destroyed previously", e.error_message()); } } // Getting the root from an item with a destroyed runtime must fail. { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); auto file = write_file(root, "file", ""); runtime.reset(); try { file->root(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::root(): runtime was destroyed previously", e.error_message()); } } // Getting the root from an item with a destroyed root must fail. { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); auto file = write_file(root, "file", ""); runtime.reset(); root.reset(); try { file->root(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::root(): runtime was destroyed previously", e.error_message()); } } // etag() with destroyed runtime must fail. { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); auto file = write_file(root, "file", ""); runtime->shutdown(); try { file->etag(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::etag(): runtime was destroyed previously", e.error_message()); } } // metadata() with destroyed runtime must fail. { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); auto file = write_file(root, "file", ""); runtime->shutdown(); try { file->metadata(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::metadata(): runtime was destroyed previously", e.error_message()); } } // last_modified_time() with destroyed runtime must fail. { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); auto file = write_file(root, "file", ""); runtime->shutdown(); try { file->last_modified_time(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::last_modified_time(): runtime was destroyed previously", e.error_message()); } } // copy() with destroyed runtime must fail. { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); auto file = write_file(root, "file", ""); runtime->shutdown(); try { call(file->copy(root, "file2")); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::copy(): runtime was destroyed previously", e.error_message()); } } // move() with destroyed runtime must fail. { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); auto file = write_file(root, "file", ""); runtime->shutdown(); try { call(file->move(root, "file2")); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::move(): runtime was destroyed previously", e.error_message()); } } // parents() on root with destroyed runtime must fail. { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); runtime->shutdown(); try { call(root->parents()); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::parents(): runtime was destroyed previously", e.error_message()); } } // parents() on file with destroyed runtime must fail. { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); auto file = write_file(root, "file", ""); runtime->shutdown(); try { call(file->parents()); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::parents(): runtime was destroyed previously", e.error_message()); } } // parent_ids() with destroyed runtime must fail. { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); auto file = write_file(root, "file", ""); runtime->shutdown(); try { file->parent_ids(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::parent_ids(): runtime was destroyed previously", e.error_message()); } } // parent_ids() on root with destroyed runtime must fail. { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); runtime->shutdown(); try { root->parent_ids(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::parent_ids(): runtime was destroyed previously", e.error_message()); } } // delete_item() with destroyed runtime must fail. { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); auto file = write_file(root, "file", ""); runtime->shutdown(); try { call(file->delete_item()); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::delete_item(): runtime was destroyed previously", e.error_message()); } } // delete_item() on root with destroyed runtime must fail. { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); runtime->shutdown(); try { call(root->delete_item()); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::delete_item(): runtime was destroyed previously", e.error_message()); } } // creation_time() with destroyed runtime must fail. { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); auto file = write_file(root, "file", ""); runtime->shutdown(); try { file->creation_time(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::creation_time(): runtime was destroyed previously", e.error_message()); } } // native_metadata() with destroyed runtime must fail. { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); auto file = write_file(root, "file", ""); runtime->shutdown(); try { file->native_metadata(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::native_metadata(): runtime was destroyed previously", e.error_message()); } } // name() on root with destroyed runtime must fail. { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); runtime->shutdown(); try { root->name(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::name(): runtime was destroyed previously", e.error_message()); } } // name() on folder with destroyed runtime must fail. { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); auto folder = call(root->create_folder("folder")); runtime->shutdown(); try { folder->name(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::name(): runtime was destroyed previously", e.error_message()); } } // name() on file with destroyed runtime must fail. { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); auto file = write_file(root, "file", ""); runtime->shutdown(); try { file->name(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("File::name(): runtime was destroyed previously", e.error_message()); } } // list() with destroyed runtime must fail. { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); runtime->shutdown(); try { call(root->list()); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Folder::list(): runtime was destroyed previously", e.error_message()); } } // lookup() with destroyed runtime must fail. { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); runtime->shutdown(); try { call(root->lookup("file")); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Folder::lookup(): runtime was destroyed previously", e.error_message()); } } // create_folder() with destroyed runtime must fail. { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); runtime->shutdown(); try { call(root->create_folder("folder")); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Folder::create_folder(): runtime was destroyed previously", e.error_message()); } } // create_file() with destroyed runtime must fail. { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); runtime->shutdown(); try { call(root->create_file("file", 0)); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Folder::create_file(): runtime was destroyed previously", e.error_message()); } } // size() with destroyed runtime must fail. { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); auto file = write_file(root, "file", ""); runtime->shutdown(); try { file->size(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("File::size(): runtime was destroyed previously", e.error_message()); } } // create_uploader() with destroyed runtime must fail. { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); auto file = write_file(root, "file", ""); runtime->shutdown(); try { call(file->create_uploader(ConflictPolicy::overwrite, 0)); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("File::create_uploader(): runtime was destroyed previously", e.error_message()); } } // create_downloader() with destroyed runtime must fail. { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); auto file = write_file(root, "file", ""); runtime->shutdown(); try { call(file->create_downloader()); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("File::create_downloader(): runtime was destroyed previously", e.error_message()); } } // free_space_bytes() with destroyed runtime must fail. { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); runtime->shutdown(); try { call(root->free_space_bytes()); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Root::free_space_bytes(): runtime was destroyed previously", e.error_message()); } } // used_space_bytes() with destroyed runtime must fail. { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); runtime->shutdown(); try { call(root->used_space_bytes()); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Root::used_space_bytes(): runtime was destroyed previously", e.error_message()); } } // get() with destroyed runtime must fail. { auto runtime = Runtime::create(); auto root = get_root(runtime); clear_folder(root); runtime->shutdown(); try { call(root->get("some_id")); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Root::get(): runtime was destroyed previously", e.error_message()); } } } int main(int argc, char** argv) { boost::system::error_code ec; boost::filesystem::remove_all(TEST_DIR "/lomiri-storage-framework", ec); setenv("STORAGE_FRAMEWORK_ROOT", TEST_DIR, true); QCoreApplication app(argc, argv); qRegisterMetaType(); ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } lomiri-storage-framework-0.5.0/tests/local-provider/000077500000000000000000000000001521521330000225205ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/tests/local-provider/CMakeLists.txt000066400000000000000000000010271521521330000252600ustar00rootroot00000000000000add_executable(local-provider_test local-provider_test.cpp) add_definitions(-DTEST_DIR="${CMAKE_CURRENT_BINARY_DIR}" -DBOOST_THREAD_VERSION=4) target_link_libraries(local-provider_test PRIVATE local-provider-lib lomiri-storage-framework-provider lomiri-storage-framework-qt-client-v2 Qt${QT_VERSION_MAJOR}::Test ${Boost_LIBRARIES} PkgConfig::GLIB_DEPS PkgConfig::GIO_DEPS testutils GTest::gtest ) gtest_discover_tests(local-provider_test) set(UNIT_TEST_TARGETS ${UNIT_TEST_TARGETS} PARENT_SCOPE) lomiri-storage-framework-0.5.0/tests/local-provider/local-provider_test.cpp000066400000000000000000001400361521521330000272110ustar00rootroot00000000000000/* * Copyright (C) 2017 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include "../../src/local-provider/LocalDownloadJob.h" #include "../../src/local-provider/LocalProvider.h" #include "../../src/local-provider/LocalUploadJob.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef LSF_LOCAL_CLIENT_LOW_PRIO_TESTS #define LSF_LOCAL_CLIENT_LOW_PRIO_TESTS 0 #endif using namespace lomiri::storage; using namespace std; namespace { int64_t nanosecs_now() { return chrono::system_clock::now().time_since_epoch() / chrono::nanoseconds(1); } class LocalProviderTest : public ProviderFixture { protected: void SetUp() override { tmp_dir_.reset(new QTemporaryDir(TEST_DIR "/data.XXXXXX")); ASSERT_TRUE(tmp_dir_->isValid()); setenv("SF_LOCAL_PROVIDER_ROOT", ROOT_DIR().c_str(), true); ProviderFixture::SetUp(); runtime_.reset(new qt::Runtime(connection())); acc_ = runtime_->make_test_account(service_connection_->baseService(), object_path()); } void TearDown() override { runtime_.reset(); ProviderFixture::TearDown(); if (HasFailure()) { tmp_dir_->setAutoRemove(false); } tmp_dir_.reset(); } std::string ROOT_DIR() const { return tmp_dir_->path().toStdString(); } std::unique_ptr tmp_dir_; unique_ptr runtime_; qt::Account acc_; }; constexpr int SIGNAL_WAIT_TIME = 30000; template void wait(Job* job) { QSignalSpy spy(job, &Job::statusChanged); while (job->status() == Job::Loading) { if (!spy.wait(SIGNAL_WAIT_TIME)) { throw runtime_error("Wait for statusChanged signal timed out"); } } } qt::Item get_root(qt::Account const& account) { unique_ptr j(account.roots()); assert(j->isValid()); QSignalSpy ready_spy(j.get(), &qt::ItemListJob::itemsReady); assert(ready_spy.wait(SIGNAL_WAIT_TIME)); auto arg = ready_spy.takeFirst(); auto items = qvariant_cast>(arg.at(0)); assert(items.size() == 1); return items[0]; } QList get_items(qt::ItemListJob *job) { QList items; auto connection = QObject::connect( job, &qt::ItemListJob::itemsReady, [&](QList const& new_items) { items.append(new_items); }); try { wait(job); } catch (...) { QObject::disconnect(connection); throw; } QObject::disconnect(connection); return items; } const string file_contents = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut " "enim ad minim veniam, quis nostrud exercitation ullamco laboris " "nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor " "in reprehenderit in voluptate velit esse cillum dolore eu fugiat " "nulla pariatur. Excepteur sint occaecat cupidatat non proident, " "sunt in culpa qui officia deserunt mollit anim id est laborum.\n"; } // namespace TEST(Directories, env_vars) { if (!LSF_LOCAL_CLIENT_LOW_PRIO_TESTS) { GTEST_SKIP() << "Skip known-broken test."; } // These tests cause the constructor to throw, so we instantiate the provider directly. { EnvVarGuard env("SF_LOCAL_PROVIDER_ROOT", "/no_such_dir"); try { LocalProvider(); FAIL(); } catch (provider::InvalidArgumentException const& e) { EXPECT_STREQ("InvalidArgumentException: LocalProvider(): Environment variable " "SF_LOCAL_PROVIDER_ROOT must denote an existing directory", e.what()); } } { EnvVarGuard env("SF_LOCAL_PROVIDER_ROOT", TEST_DIR "/Makefile"); try { LocalProvider(); FAIL(); } catch (provider::InvalidArgumentException const& e) { EXPECT_STREQ("InvalidArgumentException: LocalProvider(): Environment variable " "SF_LOCAL_PROVIDER_ROOT must denote an existing directory", e.what()); } } { string const dir = TEST_DIR "/noperm"; mkdir(dir.c_str(), 0555); ASSERT_EQ(0, chmod(dir.c_str(), 0555)); // In case dir was there already. EnvVarGuard env1("SF_LOCAL_PROVIDER_ROOT", nullptr); EnvVarGuard env2("XDG_DATA_HOME", dir.c_str()); using namespace boost::filesystem; try { LocalProvider(); ASSERT_EQ(0, chmod(dir.c_str(), 0775)); remove_all(dir); FAIL(); } catch (provider::PermissionException const& e) { EXPECT_EQ(string("PermissionException: LocalProvider(): \"") + dir + "/lomiri-storage-framework\": " "boost::filesystem::create_directories: Permission denied: \"" + dir + "/lomiri-storage-framework\"", e.what()); } ASSERT_EQ(0, chmod(dir.c_str(), 0775)); ASSERT_TRUE(remove_all(dir)); // Try again, which must succeed now (for coverage). LocalProvider(); ASSERT_TRUE(is_directory(dir + "/lomiri-storage-framework/local")); ASSERT_TRUE(remove_all(dir)); } { string const dir = TEST_DIR "/snap_user_common"; mkdir(dir.c_str(), 0775); EnvVarGuard env1("SF_LOCAL_PROVIDER_ROOT", nullptr); EnvVarGuard env2("SNAP_USER_COMMON", dir.c_str()); using namespace boost::filesystem; LocalProvider(); ASSERT_TRUE(exists(dir + "/lomiri-storage-framework/local")); ASSERT_TRUE(remove_all(dir)); } } TEST_F(LocalProviderTest, basic) { using namespace lomiri::storage::qt; set_provider(unique_ptr(new LocalProvider)); // Basic sanity check, get the root. unique_ptr j(acc_.roots()); EXPECT_TRUE(j->isValid()); QSignalSpy ready_spy(j.get(), &ItemListJob::itemsReady); ASSERT_TRUE(ready_spy.wait(SIGNAL_WAIT_TIME)); ASSERT_EQ(1, ready_spy.count()); auto arg = ready_spy.takeFirst(); auto items = qvariant_cast>(arg.at(0)); ASSERT_EQ(1, items.size()); // Check contents of returned item. auto root = items[0]; EXPECT_TRUE(root.isValid()); EXPECT_EQ(Item::Type::Root, root.type()); EXPECT_EQ(ROOT_DIR(), root.itemId().toStdString()); EXPECT_EQ("/", root.name()); EXPECT_EQ("", root.etag()); EXPECT_EQ(QList(), root.parentIds()); qDebug() << root.lastModifiedTime(); EXPECT_TRUE(root.lastModifiedTime().isValid()); EXPECT_EQ(acc_, root.account()); ASSERT_EQ(5, root.metadata().size()); auto free_space_bytes = root.metadata().value("free_space_bytes").toULongLong(); cout << "free_space_bytes: " << free_space_bytes << endl; EXPECT_GT(free_space_bytes, 0); auto used_space_bytes = root.metadata().value("used_space_bytes").toULongLong(); cout << "used_space_bytes: " << used_space_bytes << endl; EXPECT_GT(used_space_bytes, 0); auto content_type = root.metadata().value("content_type").toString(); EXPECT_EQ("inode/directory", content_type); auto writable = root.metadata().value("writable").toBool(); EXPECT_TRUE(writable); // yyyy-mm-ddThh:mm:ssZ string const date_time_fmt = "^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]Z$"; string mtime = root.metadata().value("last_modified_time").toString().toStdString(); cout << "last_modified_time: " << mtime << endl; regex re(date_time_fmt); EXPECT_TRUE(regex_match(mtime, re)); } TEST_F(LocalProviderTest, create_folder) { if (!LSF_LOCAL_CLIENT_LOW_PRIO_TESTS) { GTEST_SKIP() << "Skip known-broken test."; } { using namespace lomiri::storage::qt; set_provider(unique_ptr(new LocalProvider)); auto root = get_root(acc_); unique_ptr job(root.createFolder("child")); wait(job.get()); ASSERT_EQ(ItemJob::Finished, job->status()) << job->error().errorString().toStdString(); Item child = job->item(); EXPECT_EQ(ROOT_DIR() + "/child", child.itemId().toStdString()); EXPECT_EQ("child", child.name().toStdString()); ASSERT_EQ(1, child.parentIds().size()); EXPECT_EQ(ROOT_DIR(), child.parentIds().at(0).toStdString()); EXPECT_EQ("", child.etag()); EXPECT_EQ(Item::Type::Folder, child.type()); EXPECT_EQ(5, child.metadata().size()); struct stat st; ASSERT_EQ(0, stat(child.itemId().toStdString().c_str(), &st)); EXPECT_TRUE(S_ISDIR(st.st_mode)); // Again, to get coverage for a StorageException caught in invoke_async(). job.reset(root.createFolder("child")); wait(job.get()); ASSERT_EQ(ItemJob::Error, job->status()) << job->error().errorString().toStdString(); EXPECT_EQ(string("Exists: create_folder(): \"") + ROOT_DIR() + "/child\" exists already", job->error().errorString().toStdString()); // Again, without write permission on the root dir, to get coverage for a filesystem_error in invoke_async(). ASSERT_EQ(0, ::rmdir((ROOT_DIR() + "/child").c_str())); ASSERT_EQ(0, ::chmod(ROOT_DIR().c_str(), 0555)); job.reset(root.createFolder("child")); wait(job.get()); ::chmod(ROOT_DIR().c_str(), 0755); ASSERT_EQ(ItemJob::Error, job->status()) << job->error().errorString().toStdString(); EXPECT_EQ(string("PermissionDenied: create_folder(): \"") + ROOT_DIR() + "/child\": boost::filesystem::create_directory: Permission denied: \"" + ROOT_DIR() + "/child\"", job->error().errorString().toStdString()); } } TEST_F(LocalProviderTest, delete_item) { { using namespace lomiri::storage::qt; set_provider(unique_ptr(new LocalProvider)); auto root = get_root(acc_); unique_ptr job(root.createFolder("child")); wait(job.get()); ASSERT_EQ(ItemJob::Finished, job->status()) << job->error().errorString().toStdString(); Item child = job->item(); unique_ptr delete_job(child.deleteItem()); wait(delete_job.get()); ASSERT_EQ(ItemJob::Finished, delete_job->status()) << delete_job->error().errorString().toStdString(); struct stat st; ASSERT_EQ(-1, stat(child.itemId().toStdString().c_str(), &st)); EXPECT_EQ(ENOENT, errno); } } TEST_F(LocalProviderTest, delete_item_noperm) { { using namespace lomiri::storage::qt; set_provider(unique_ptr(new LocalProvider)); auto root = get_root(acc_); unique_ptr job(root.createFolder("child")); wait(job.get()); ASSERT_EQ(ItemJob::Finished, job->status()) << job->error().errorString().toStdString(); } } TEST_F(LocalProviderTest, delete_root) { // Client-side API does not allow us to try to delete the root, so we talk to the provider directly. auto p = make_shared(); auto fut = p->delete_item(ROOT_DIR(), provider::Context()); try { fut.get(); FAIL(); } catch (provider::LogicException const& e) { EXPECT_STREQ("LogicException: delete_item(): cannot delete root", e.what()); } } TEST_F(LocalProviderTest, metadata) { // Client-side API does not call the Metadata DBus method (except as part of parents()), // so we talk to the provider directly. auto p = make_shared(); auto fut = p->metadata(ROOT_DIR(), {}, provider::Context()); auto item = fut.get(); EXPECT_EQ(5, item.metadata.size()); // Again, to get coverage for the "not file or folder" case in make_item(). ASSERT_EQ(0, mknod((ROOT_DIR() + "/pipe").c_str(), S_IFIFO | 06666, 0)); try { auto fut = p->metadata(ROOT_DIR() + "/pipe", {}, provider::Context()); fut.get(); FAIL(); } catch (provider::NotExistsException const& e) { EXPECT_EQ(string("NotExistsException: metadata(): \"") + ROOT_DIR() + "/pipe\" is neither a file nor a folder", e.what()); } } TEST_F(LocalProviderTest, lookup) { if (!LSF_LOCAL_CLIENT_LOW_PRIO_TESTS) { GTEST_SKIP() << "Skip known-broken test."; } using namespace lomiri::storage::qt; set_provider(unique_ptr(new LocalProvider)); auto root = get_root(acc_); { unique_ptr job(root.createFolder("child")); wait(job.get()); ASSERT_EQ(ItemJob::Finished, job->status()) << job->error().errorString().toStdString(); } unique_ptr job(root.lookup("child")); auto items = get_items(job.get()); ASSERT_EQ(1, items.size()); auto child = items.at(0); EXPECT_EQ(ROOT_DIR() + "/child", child.itemId().toStdString()); EXPECT_EQ("child", child.name().toStdString()); ASSERT_EQ(1, child.parentIds().size()); EXPECT_EQ(ROOT_DIR(), child.parentIds().at(0).toStdString()); EXPECT_EQ("", child.etag()); EXPECT_EQ(Item::Type::Folder, child.type()); EXPECT_EQ(5, child.metadata().size()); // Remove the child again and try the lookup once more. ASSERT_EQ(0, rmdir((ROOT_DIR() + "/child").c_str())); job.reset(root.lookup("child")); wait(job.get()); EXPECT_EQ(ItemJob::Error, job->status()); EXPECT_EQ(string("NotExists: lookup(): \"") + ROOT_DIR() + "/child\": boost::filesystem::canonical: " + "No such file or directory: \"" + ROOT_DIR() + "/child\"", job->error().errorString().toStdString()); } TEST_F(LocalProviderTest, list) { if (!LSF_LOCAL_CLIENT_LOW_PRIO_TESTS) { GTEST_SKIP() << "Skip known-broken test."; } using namespace lomiri::storage::qt; set_provider(unique_ptr(new LocalProvider)); auto root = get_root(acc_); { unique_ptr job(root.createFolder("child")); wait(job.get()); ASSERT_EQ(ItemJob::Finished, job->status()) << job->error().errorString().toStdString(); } // Make a weird item that will be ignored (for coverage). ASSERT_EQ(0, mknod((ROOT_DIR() + "/pipe").c_str(), S_IFIFO | 06666, 0)); // Make a file that starts with the temp file prefix (for coverage). int fd = creat((ROOT_DIR() + "/.lomiri-storage-framework").c_str(), 0755); ASSERT_GT(fd, 0); close(fd); unique_ptr job(root.list()); auto items = get_items(job.get()); ASSERT_EQ(1, items.size()); auto child = items.at(0); EXPECT_EQ(ROOT_DIR() + "/child", child.itemId().toStdString()); EXPECT_EQ("child", child.name().toStdString()); ASSERT_EQ(1, child.parentIds().size()); EXPECT_EQ(ROOT_DIR(), child.parentIds().at(0).toStdString()); EXPECT_EQ("", child.etag()); EXPECT_EQ(Item::Type::Folder, child.type()); EXPECT_EQ(5, child.metadata().size()); } void make_hierarchy(string const& root_dir) { // Make a small tree so we have something to test with for move() and copy(). ASSERT_EQ(0, mkdir((root_dir + "/a").c_str(), 0755)); ASSERT_EQ(0, mkdir((root_dir + "/a/b").c_str(), 0755)); string cmd = string("echo hello >") + root_dir + "/hello"; ASSERT_EQ(0, system(cmd.c_str())); cmd = string("echo text >") + root_dir + "/a/foo.txt"; ASSERT_EQ(0, system(cmd.c_str())); ASSERT_EQ(0, mknod((root_dir + "/a/pipe").c_str(), S_IFIFO | 06666, 0)); ASSERT_EQ(0, mkdir((root_dir + "/a/.lomiri-storage-framework-").c_str(), 0755)); ASSERT_EQ(0, mkdir((root_dir + "/a/b/.lomiri-storage-framework-").c_str(), 0755)); ASSERT_EQ(0, mknod((root_dir + "/a/b/pipe").c_str(), S_IFIFO | 06666, 0)); } TEST_F(LocalProviderTest, move) { using namespace lomiri::storage::qt; set_provider(unique_ptr(new LocalProvider)); auto start_time = nanosecs_now(); make_hierarchy(ROOT_DIR()); auto root = get_root(acc_); qt::Item hello; { unique_ptr job(root.lookup("hello")); auto items = get_items(job.get()); ASSERT_EQ(ItemListJob::Finished, job->status()) << job->error().errorString().toStdString(); ASSERT_EQ(1, items.size()); hello = items.at(0); } struct stat st; ASSERT_EQ(0, stat(hello.itemId().toStdString().c_str(), &st)); auto old_ino = st.st_ino; // Check metadata. EXPECT_EQ("hello", hello.name()); ASSERT_EQ(1, hello.parentIds().size()); EXPECT_EQ(ROOT_DIR(), hello.parentIds().at(0).toStdString()); EXPECT_EQ(Item::Type::File, hello.type()); ASSERT_EQ(6, hello.metadata().size()); auto free_space_bytes = hello.metadata().value("free_space_bytes").toLongLong(); cout << "free_space_bytes: " << free_space_bytes << endl; EXPECT_GT(free_space_bytes, 0); auto used_space_bytes = hello.metadata().value("used_space_bytes").toLongLong(); cout << "used_space_bytes: " << used_space_bytes << endl; EXPECT_GT(used_space_bytes, 0); auto content_type = hello.metadata().value("content_type").toString(); EXPECT_EQ("application/octet-stream", content_type); auto writable = hello.metadata().value("writable").toBool(); EXPECT_TRUE(writable); auto size = hello.metadata().value("size_in_bytes").toLongLong(); EXPECT_EQ(6, size); // yyyy-mm-ddThh:mm:ssZ string const date_time_fmt = "^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]Z$"; string date_time = hello.metadata().value("last_modified_time").toString().toStdString(); cout << "last_modified_time: " << date_time << endl; regex re(date_time_fmt); EXPECT_TRUE(regex_match(date_time, re)); // Check that the file was modified in the last two seconds. // Because the system clock can tick a lot more frequently than the file system time stamp, // we allow the mtime to be up to one second *earlier* than the time we started the operation. string mtime_str = hello.etag().toStdString(); char* end; int64_t mtime = strtoll(mtime_str.c_str(), &end, 10); EXPECT_LE(start_time - 1000000000, mtime); EXPECT_LT(mtime, start_time + 2000000000); // Move hello -> world qt::Item world; { unique_ptr job(hello.move(root, "world")); wait(job.get()); ASSERT_EQ(ItemJob::Finished, job->status()) << job->error().errorString().toStdString(); world = job->item(); } EXPECT_FALSE(boost::filesystem::exists(hello.itemId().toStdString())); EXPECT_EQ(ROOT_DIR() + "/world", world.itemId().toStdString()); ASSERT_EQ(0, stat(world.itemId().toStdString().c_str(), &st)); auto new_ino = st.st_ino; EXPECT_EQ(old_ino, new_ino); // For coverage: try moving world -> a (which must fail) unique_ptr job(world.move(root, "a")); wait(job.get()); ASSERT_EQ(ItemJob::Error, job->status()) << job->error().errorString().toStdString(); EXPECT_EQ(string("Exists: move(): \"") + ROOT_DIR() + "/a\" exists already", job->error().errorString().toStdString()); } TEST_F(LocalProviderTest, copy_file) { using namespace lomiri::storage::qt; set_provider(unique_ptr(new LocalProvider)); make_hierarchy(ROOT_DIR()); auto root = get_root(acc_); // Copy hello -> world qt::Item hello; { unique_ptr job(root.lookup("hello")); auto items = get_items(job.get()); ASSERT_EQ(ItemListJob::Finished, job->status()) << job->error().errorString().toStdString(); ASSERT_EQ(1, items.size()); hello = items.at(0); } struct stat st; ASSERT_EQ(0, stat(hello.itemId().toStdString().c_str(), &st)); auto old_ino = st.st_ino; qt::Item world; { unique_ptr job(hello.copy(root, "world")); wait(job.get()); ASSERT_EQ(ItemJob::Finished, job->status()) << job->error().errorString().toStdString(); world = job->item(); } EXPECT_TRUE(boost::filesystem::exists(hello.itemId().toStdString())); EXPECT_EQ(ROOT_DIR() + "/world", world.itemId().toStdString()); ASSERT_EQ(0, stat(world.itemId().toStdString().c_str(), &st)); auto new_ino = st.st_ino; EXPECT_NE(old_ino, new_ino); } TEST_F(LocalProviderTest, copy_tree) { using namespace lomiri::storage::qt; using namespace boost::filesystem; set_provider(unique_ptr(new LocalProvider)); make_hierarchy(ROOT_DIR()); auto root = get_root(acc_); // Copy a -> c qt::Item a; { unique_ptr job(root.lookup("a")); auto items = get_items(job.get()); ASSERT_EQ(ItemListJob::Finished, job->status()) << job->error().errorString().toStdString(); ASSERT_EQ(1, items.size()); a = items.at(0); } qt::Item c; { unique_ptr job(a.copy(root, "c")); wait(job.get()); ASSERT_EQ(ItemJob::Finished, job->status()) << job->error().errorString().toStdString(); c = job->item(); } EXPECT_TRUE(exists(c.itemId().toStdString())); // Check that we only copied regular files and directories, but not a pipe or anything starting with // the temp file prefix. EXPECT_TRUE(exists(ROOT_DIR() + "/c/b")); EXPECT_TRUE(exists(ROOT_DIR() + "/c/foo.txt")); EXPECT_FALSE(exists(ROOT_DIR() + "/c/pipe")); EXPECT_FALSE(exists(ROOT_DIR() + "/c/lomiri-storage-framework-")); EXPECT_FALSE(exists(ROOT_DIR() + "/c/b/pipe")); EXPECT_FALSE(exists(ROOT_DIR() + "/c/b/lomiri-storage-framework-")); // Copy c -> a. This must fail because a exists. { unique_ptr job(c.copy(root, "a")); wait(job.get()); ASSERT_EQ(ItemJob::Error, job->status()) << job->error().errorString().toStdString(); EXPECT_EQ(string("Exists: copy(): \"") + ROOT_DIR() + "/a\" exists already", job->error().errorString().toStdString()); } } TEST_F(LocalProviderTest, download) { using namespace lomiri::storage::qt; set_provider(unique_ptr(new LocalProvider)); int const segments = 10000; string large_contents; large_contents.reserve(file_contents.size() * segments); for (int i = 0; i < segments; i++) { large_contents += file_contents; } string const full_path = ROOT_DIR() + "/foo.txt"; { int fd = open(full_path.c_str(), O_WRONLY | O_CREAT | O_EXCL, 0644); ASSERT_GT(fd, 0); ASSERT_EQ(ssize_t(large_contents.size()), write(fd, &large_contents[0], large_contents.size())) << strerror(errno); ASSERT_EQ(0, close(fd)); } unique_ptr job(acc_.get(QString::fromStdString(full_path))); wait(job.get()); EXPECT_TRUE(job->isValid()); auto file = job->item(); unique_ptr downloader(file.createDownloader(Item::ErrorIfConflict)); int64_t n_read = 0; QObject::connect(downloader.get(), &QIODevice::readyRead, [&]() { auto bytes = downloader->readAll(); string const expected = large_contents.substr(n_read, bytes.size()); EXPECT_EQ(expected, bytes.toStdString()); n_read += bytes.size(); }); QSignalSpy read_finished_spy(downloader.get(), &QIODevice::readChannelFinished); ASSERT_TRUE(read_finished_spy.wait(SIGNAL_WAIT_TIME)); QSignalSpy status_spy(downloader.get(), &Downloader::statusChanged); downloader->close(); while (downloader->status() == Downloader::Ready) { ASSERT_TRUE(status_spy.wait(SIGNAL_WAIT_TIME)); } ASSERT_EQ(Downloader::Finished, downloader->status()) << downloader->error().errorString().toStdString(); EXPECT_EQ(int64_t(large_contents.size()), n_read); } TEST_F(LocalProviderTest, download_short_read) { using namespace lomiri::storage::qt; set_provider(unique_ptr(new LocalProvider)); int const segments = 10000; string const full_path = ROOT_DIR() + "/foo.txt"; { int fd = open(full_path.c_str(), O_WRONLY | O_CREAT | O_EXCL, 0644); ASSERT_GT(fd, 0); for (int i = 0; i < segments; i++) { ASSERT_EQ(ssize_t(file_contents.size()), write(fd, &file_contents[0], file_contents.size())) << strerror(errno); } ASSERT_EQ(0, close(fd)); } unique_ptr job(acc_.get(QString::fromStdString(full_path))); wait(job.get()); ASSERT_EQ(ItemJob::Finished, job->status()) << job->error().errorString().toStdString(); auto file = job->item(); unique_ptr downloader(file.createDownloader(Item::ErrorIfConflict)); QSignalSpy spy(downloader.get(), &Downloader::statusChanged); while (downloader->status() == Downloader::Loading) { ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } downloader->close(); while (downloader->status() == Downloader::Ready) { ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } ASSERT_EQ(Downloader::Error, downloader->status()) << downloader->error().errorString().toStdString(); auto error = downloader->error(); EXPECT_EQ(qt::StorageError::LogicError, error.type()); cout << error.message().toStdString() << endl; EXPECT_TRUE(boost::starts_with(error.message().toStdString(), "finish() method called too early, file \"" + full_path + "\" has size 4460000 but only")); } TEST_F(LocalProviderTest, download_etag_mismatch) { using namespace lomiri::storage::qt; set_provider(unique_ptr(new LocalProvider)); string const full_path = ROOT_DIR() + "/foo.txt"; string cmd = string("echo hello >") + full_path; ASSERT_EQ(0, system(cmd.c_str())); unique_ptr job(acc_.get(QString::fromStdString(full_path))); wait(job.get()); ASSERT_EQ(ItemJob::Finished, job->status()) << job->error().errorString().toStdString(); auto file = job->item(); sleep(1); cmd = string("touch ") + full_path; ASSERT_EQ(0, system(cmd.c_str())); unique_ptr downloader(file.createDownloader(Item::ErrorIfConflict)); QSignalSpy spy(downloader.get(), &Downloader::statusChanged); while (downloader->status() != Downloader::Error) { ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } auto error = downloader->error(); EXPECT_EQ(qt::StorageError::Conflict, error.type()); EXPECT_EQ("download(): etag mismatch", error.message().toStdString()); } TEST_F(LocalProviderTest, download_wrong_file_type) { // We can't try a download for a directory via the client API, so we use the LocalDownloadJob directly. auto p = make_shared(); string const dir = ROOT_DIR() + "/dir"; ASSERT_EQ(0, mkdir(dir.c_str(), 0755)); try { LocalDownloadJob(p, dir, "some_etag"); FAIL(); } catch (provider::InvalidArgumentException const& e) { EXPECT_EQ(string("InvalidArgumentException: download(): \"" + dir + "\" is not a file"), e.what()); } } TEST_F(LocalProviderTest, download_no_permission) { using namespace lomiri::storage::qt; set_provider(unique_ptr(new LocalProvider)); string const full_path = ROOT_DIR() + "/foo.txt"; string cmd = string("echo hello >") + full_path; ASSERT_EQ(0, system(cmd.c_str())); unique_ptr job(acc_.get(QString::fromStdString(full_path))); wait(job.get()); ASSERT_EQ(ItemJob::Finished, job->status()) << job->error().errorString().toStdString(); auto file = job->item(); ASSERT_EQ(0, chmod(full_path.c_str(), 0244)); unique_ptr downloader(file.createDownloader(Item::ErrorIfConflict)); QSignalSpy spy(downloader.get(), &Downloader::statusChanged); while (downloader->status() != Downloader::Error) { ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } auto error = downloader->error(); EXPECT_EQ(qt::StorageError::ResourceError, error.type()); EXPECT_EQ(string("download(): : cannot open \"") + full_path + "\": Permission denied (QFileDevice::FileError = 5)", error.message().toStdString()); } TEST_F(LocalProviderTest, download_no_such_file) { // We can't try a download for a non-existent file via the client API, so we use the LocalDownloadJob directly. auto p = make_shared(); try { LocalDownloadJob(p, ROOT_DIR() + "/no_such_file", "some_etag"); FAIL(); } catch (provider::NotExistsException const&) { } } TEST_F(LocalProviderTest, update) { using namespace lomiri::storage::qt; set_provider(unique_ptr(new LocalProvider)); auto full_path = ROOT_DIR() + "/foo.txt"; auto cmd = string("echo hello >") + full_path; ASSERT_EQ(0, system(cmd.c_str())); unique_ptr job(acc_.get(QString::fromStdString(full_path))); wait(job.get()); EXPECT_TRUE(job->isValid()); auto file = job->item(); auto old_etag = file.etag(); int const segments = 50; unique_ptr uploader(file.createUploader(Item::ErrorIfConflict, file_contents.size() * segments)); int count = 0; QTimer timer; timer.setSingleShot(false); timer.setInterval(10); QObject::connect(&timer, &QTimer::timeout, [&] { uploader->write(&file_contents[0], file_contents.size()); count++; if (count == segments) { uploader->close(); } }); QSignalSpy spy(uploader.get(), &Uploader::statusChanged); timer.start(); while (uploader->status() == Uploader::Loading || uploader->status() == Uploader::Ready) { ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } ASSERT_EQ(Uploader::Finished, uploader->status()) << uploader->error().errorString().toStdString(); file = uploader->item(); EXPECT_NE(old_etag, file.etag()); EXPECT_EQ(int64_t(file_contents.size() * segments), file.sizeInBytes()); } TEST_F(LocalProviderTest, update_empty) { using namespace lomiri::storage::qt; set_provider(unique_ptr(new LocalProvider)); auto full_path = ROOT_DIR() + "/foo.txt"; auto cmd = string("echo hello >") + full_path; ASSERT_EQ(0, system(cmd.c_str())); unique_ptr job(acc_.get(QString::fromStdString(full_path))); wait(job.get()); EXPECT_TRUE(job->isValid()); auto file = job->item(); auto old_etag = file.etag(); sleep(1); // Make sure mtime changes. unique_ptr uploader(file.createUploader(Item::ErrorIfConflict, 0)); { QSignalSpy spy(uploader.get(), &Uploader::statusChanged); while (uploader->status() != Uploader::Ready) { ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } } QSignalSpy spy(uploader.get(), &Uploader::statusChanged); uploader->close(); while (uploader->status() != Uploader::Finished) { ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } file = uploader->item(); EXPECT_NE(old_etag, file.etag()); EXPECT_EQ(int64_t(0), file.sizeInBytes()); } TEST_F(LocalProviderTest, update_cancel) { using namespace lomiri::storage::qt; set_provider(unique_ptr(new LocalProvider)); auto full_path = ROOT_DIR() + "/foo.txt"; auto cmd = string("echo hello >") + full_path; ASSERT_EQ(0, system(cmd.c_str())); unique_ptr job(acc_.get(QString::fromStdString(full_path))); wait(job.get()); EXPECT_TRUE(job->isValid()); auto file = job->item(); auto old_etag = file.etag(); int const segments = 50; unique_ptr uploader(file.createUploader(Item::ErrorIfConflict, file_contents.size() * segments)); int count = 0; QTimer timer; timer.setSingleShot(false); timer.setInterval(10); QObject::connect(&timer, &QTimer::timeout, [&] { uploader->write(&file_contents[0], file_contents.size()); count++; if (count == segments / 2) { uploader->cancel(); } else if (count == segments) { uploader->close(); } }); QSignalSpy spy(uploader.get(), &Uploader::statusChanged); timer.start(); while (uploader->status() != Uploader::Cancelled) { ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } } TEST_F(LocalProviderTest, update_file_touched_before_uploading) { using namespace lomiri::storage::qt; set_provider(unique_ptr(new LocalProvider)); auto full_path = ROOT_DIR() + "/foo.txt"; auto cmd = string("echo hello >") + full_path; ASSERT_EQ(0, system(cmd.c_str())); unique_ptr job(acc_.get(QString::fromStdString(full_path))); wait(job.get()); EXPECT_TRUE(job->isValid()); auto file = job->item(); auto old_etag = file.etag(); sleep(1); // Make sure mtime changes. cmd = string("touch ") + full_path; ASSERT_EQ(0, system(cmd.c_str())); unique_ptr uploader(file.createUploader(Item::ErrorIfConflict, 0)); QSignalSpy spy(uploader.get(), &Uploader::statusChanged); while (uploader->status() != Uploader::Error) { ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } EXPECT_EQ("update(): etag mismatch", uploader->error().message().toStdString()); } TEST_F(LocalProviderTest, update_file_touched_while_uploading) { using namespace lomiri::storage::qt; set_provider(unique_ptr(new LocalProvider)); auto full_path = ROOT_DIR() + "/foo.txt"; auto cmd = string("echo hello >") + full_path; ASSERT_EQ(0, system(cmd.c_str())); unique_ptr job(acc_.get(QString::fromStdString(full_path))); wait(job.get()); EXPECT_TRUE(job->isValid()); auto file = job->item(); auto old_etag = file.etag(); int const segments = 50; unique_ptr uploader(file.createUploader(Item::ErrorIfConflict, file_contents.size() * segments)); int count = 0; QTimer timer; timer.setSingleShot(false); timer.setInterval(10); QObject::connect(&timer, &QTimer::timeout, [&] { uploader->write(&file_contents[0], file_contents.size()); count++; if (count == segments / 2) { sleep(1); cmd = string("touch ") + full_path; ASSERT_EQ(0, system(cmd.c_str())); } else if (count == segments) { uploader->close(); } }); QSignalSpy spy(uploader.get(), &Uploader::statusChanged); timer.start(); while (uploader->status() != Uploader::Error) { ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } ASSERT_EQ(Uploader::Error, uploader->status()); EXPECT_EQ("update(): etag mismatch", uploader->error().message().toStdString()); } TEST_F(LocalProviderTest, update_ignore_etag_mismatch) { using namespace lomiri::storage::qt; set_provider(unique_ptr(new LocalProvider)); auto full_path = ROOT_DIR() + "/foo.txt"; auto cmd = string("echo hello >") + full_path; ASSERT_EQ(0, system(cmd.c_str())); unique_ptr job(acc_.get(QString::fromStdString(full_path))); wait(job.get()); EXPECT_TRUE(job->isValid()); auto file = job->item(); auto old_etag = file.etag(); sleep(1); // Make sure mtime changes. cmd = string("touch ") + full_path; unique_ptr uploader(file.createUploader(Item::IgnoreConflict, 0)); { QSignalSpy spy(uploader.get(), &Uploader::statusChanged); while (uploader->status() != Uploader::Ready) { ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } } QSignalSpy spy(uploader.get(), &Uploader::statusChanged); uploader->close(); while (uploader->status() != Uploader::Finished) { ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } file = uploader->item(); EXPECT_NE(old_etag, file.etag()); EXPECT_EQ(int64_t(0), file.sizeInBytes()); } TEST_F(LocalProviderTest, update_close_too_soon) { using namespace lomiri::storage::qt; set_provider(unique_ptr(new LocalProvider)); auto full_path = ROOT_DIR() + "/foo.txt"; auto cmd = string("echo hello >") + full_path; ASSERT_EQ(0, system(cmd.c_str())); unique_ptr job(acc_.get(QString::fromStdString(full_path))); wait(job.get()); EXPECT_TRUE(job->isValid()); auto file = job->item(); auto old_etag = file.etag(); int const segments = 50; unique_ptr uploader(file.createUploader(Item::ErrorIfConflict, file_contents.size() * segments)); int count = 0; QTimer timer; timer.setSingleShot(false); timer.setInterval(10); QObject::connect(&timer, &QTimer::timeout, [&] { uploader->write(&file_contents[0], file_contents.size()); count++; if (count == segments - 1) { uploader->close(); } }); QSignalSpy spy(uploader.get(), &Uploader::statusChanged); timer.start(); while (uploader->status() != Uploader::Error) { ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } ASSERT_EQ(Uploader::Error, uploader->status()) << uploader->error().errorString().toStdString(); EXPECT_EQ("LogicError: finish() method called too early, size was given as 22300 but only 21854 bytes were received", uploader->error().errorString().toStdString()); } TEST_F(LocalProviderTest, update_write_too_much) { using namespace lomiri::storage::qt; set_provider(unique_ptr(new LocalProvider)); auto full_path = ROOT_DIR() + "/foo.txt"; auto cmd = string("echo hello >") + full_path; ASSERT_EQ(0, system(cmd.c_str())); unique_ptr job(acc_.get(QString::fromStdString(full_path))); wait(job.get()); EXPECT_TRUE(job->isValid()); auto file = job->item(); auto old_etag = file.etag(); int const segments = 50; // We write more than this many bytes below. unique_ptr uploader(file.createUploader(Item::ErrorIfConflict, file_contents.size() * segments - 1)); int count = 0; QTimer timer; timer.setSingleShot(false); timer.setInterval(10); QObject::connect(&timer, &QTimer::timeout, [&] { uploader->write(&file_contents[0], file_contents.size()); count++; if (count == segments) { uploader->close(); } }); QSignalSpy spy(uploader.get(), &Uploader::statusChanged); timer.start(); while (uploader->status() != Uploader::Error) { ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } ASSERT_EQ(Uploader::Error, uploader->status()); EXPECT_EQ("update(): received more than the expected number (22299) of bytes", uploader->error().message().toStdString()); } TEST_F(LocalProviderTest, upload_wrong_file_type) { // We can't try an upload for a directory via the client API, so we use the LocalUploadJob directly. auto p = make_shared(); string const dir = ROOT_DIR() + "/dir"; ASSERT_EQ(0, mkdir(dir.c_str(), 0755)); try { LocalUploadJob(p, dir, 0, ""); FAIL(); } catch (provider::InvalidArgumentException const& e) { EXPECT_EQ(string("InvalidArgumentException: update(): \"" + dir + "\" is not a file"), e.what()); } } TEST_F(LocalProviderTest, upload_root_noperm) { if (!LSF_LOCAL_CLIENT_LOW_PRIO_TESTS) { GTEST_SKIP() << "Skip known-broken test."; } // Force an error in prepare_channels when creating temp file. auto p = make_shared(); ASSERT_EQ(0, chmod(ROOT_DIR().c_str(), 0644)); try { LocalUploadJob(p, ROOT_DIR(), "name", 0, true); chmod(ROOT_DIR().c_str(), 0755); FAIL(); } catch (provider::ResourceException const& e) { EXPECT_EQ(string("ResourceException: create_file(): cannot create temp file \"") + ROOT_DIR() + "/.lomiri-storage-framework-%%%%-%%%%-%%%%-%%%%\": Invalid argument", e.what()); } chmod(ROOT_DIR().c_str(), 0755); } TEST_F(LocalProviderTest, sanitize) { if (!LSF_LOCAL_CLIENT_LOW_PRIO_TESTS) { GTEST_SKIP() << "Skip known-broken test."; } // Force various errors in sanitize() for coverage. auto p = make_shared(); try { LocalUploadJob(p, ROOT_DIR(), "a/b", 0, true); FAIL(); } catch (provider::InvalidArgumentException const& e) { EXPECT_STREQ("InvalidArgumentException: create_file(): name \"a/b\" cannot contain a slash", e.what()); } try { LocalUploadJob(p, ROOT_DIR(), "..", 0, true); FAIL(); } catch (provider::InvalidArgumentException const& e) { EXPECT_STREQ("InvalidArgumentException: create_file(): invalid name: \"..\"", e.what()); } try { LocalUploadJob(p, ROOT_DIR(), ".lomiri-storage-framework", 0, true); FAIL(); } catch (provider::InvalidArgumentException const& e) { EXPECT_STREQ("InvalidArgumentException: create_file(): names beginning with \".lomiri-storage-framework\" are reserved", e.what()); } } TEST_F(LocalProviderTest, throw_if_not_valid) { // Make sure that we can't escape the root. auto p = make_shared(); try { LocalUploadJob(p, ROOT_DIR() + "/..", "a", 0, true); FAIL(); } catch (provider::InvalidArgumentException const& e) { EXPECT_EQ(string("InvalidArgumentException: create_file(): invalid id: \"") + ROOT_DIR() + "/..\"", e.what()); } try { LocalUploadJob(p, "/bin" , "a", 0, true); FAIL(); } catch (provider::InvalidArgumentException const& e) { EXPECT_STREQ("InvalidArgumentException: create_file(): invalid id: \"/bin\"", e.what()); } } TEST_F(LocalProviderTest, create_file) { using namespace lomiri::storage::qt; set_provider(unique_ptr(new LocalProvider)); auto root = get_root(acc_); int const segments = 50; unique_ptr uploader(root.createFile("foo.txt", Item::ErrorIfConflict, file_contents.size() * segments, "text/plain")); int count = 0; QTimer timer; timer.setSingleShot(false); timer.setInterval(10); QObject::connect(&timer, &QTimer::timeout, [&] { uploader->write(&file_contents[0], file_contents.size()); count++; if (count == segments) { uploader->close(); } }); QSignalSpy spy(uploader.get(), &Uploader::statusChanged); while (uploader->status() != Uploader::Ready) { ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } timer.start(); while (uploader->status() != Uploader::Finished) { ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } auto file = uploader->item(); EXPECT_EQ(int64_t(file_contents.size() * segments), file.sizeInBytes()); } TEST_F(LocalProviderTest, create_file_ignore_conflict) { using namespace lomiri::storage::qt; set_provider(unique_ptr(new LocalProvider)); auto full_path = ROOT_DIR() + "/foo.txt"; auto cmd = string("echo hello >") + full_path; ASSERT_EQ(0, system(cmd.c_str())); auto root = get_root(acc_); int const segments = 50; unique_ptr uploader(root.createFile("foo.txt", Item::IgnoreConflict, file_contents.size() * segments, "text/plain")); int count = 0; QTimer timer; timer.setSingleShot(false); timer.setInterval(10); QObject::connect(&timer, &QTimer::timeout, [&] { uploader->write(&file_contents[0], file_contents.size()); count++; if (count == segments) { uploader->close(); } }); QSignalSpy spy(uploader.get(), &Uploader::statusChanged); while (uploader->status() != Uploader::Ready) { ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } timer.start(); while (uploader->status() != Uploader::Finished) { ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } auto file = uploader->item(); EXPECT_EQ(int64_t(file_contents.size() * segments), file.sizeInBytes()); } TEST_F(LocalProviderTest, create_file_error_if_conflict) { using namespace lomiri::storage::qt; set_provider(unique_ptr(new LocalProvider)); auto full_path = ROOT_DIR() + "/foo.txt"; auto cmd = string("echo hello >") + full_path; ASSERT_EQ(0, system(cmd.c_str())); auto root = get_root(acc_); int const segments = 50; unique_ptr uploader(root.createFile("foo.txt", Item::ErrorIfConflict, file_contents.size() * segments, "text/plain")); QSignalSpy spy(uploader.get(), &Uploader::statusChanged); while (uploader->status() != Uploader::Error) { ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } EXPECT_EQ(string("create_file(): \"") + full_path + "\" exists already", uploader->error().message().toStdString()); } TEST_F(LocalProviderTest, create_file_created_during_upload) { using namespace lomiri::storage::qt; set_provider(unique_ptr(new LocalProvider)); auto root = get_root(acc_); int const segments = 50; string full_path = ROOT_DIR() + "/foo.txt"; unique_ptr uploader(root.createFile("foo.txt", Item::ErrorIfConflict, file_contents.size() * segments, "text/plain")); int count = 0; QTimer timer; timer.setSingleShot(false); timer.setInterval(10); QObject::connect(&timer, &QTimer::timeout, [&] { uploader->write(&file_contents[0], file_contents.size()); count++; if (count == segments / 2) { string cmd = "touch " + full_path; ASSERT_EQ(0, system(cmd.c_str())); } else if (count == segments) { uploader->close(); } }); QSignalSpy spy(uploader.get(), &Uploader::statusChanged); while (uploader->status() != Uploader::Ready) { ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } timer.start(); while (uploader->status() != Uploader::Error) { ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } EXPECT_EQ(string("create_file(): \"") + full_path + "\" exists already", uploader->error().message().toStdString()); } int main(int argc, char** argv) { setenv("LANG", "C", true); // Test test fixture repeatedly creates and tears down the dbus connection. // The provider calls g_file_new_for_path() which talks to the GVfs backend // via dbus. If the dbus connection disappears, that causes GIO to send a // a SIGTERM, killing the test. // Setting GIO_USE_VFS variable to "local" disables sending the signal. setenv("GIO_USE_VFS", "local", true); QCoreApplication app(argc, argv); ::testing::InitGoogleTest(&argc, argv); int rc = RUN_ALL_TESTS(); // Process any pending events to avoid bogus leak reports from valgrind. QCoreApplication::sendPostedEvents(); QCoreApplication::processEvents(); return rc; } lomiri-storage-framework-0.5.0/tests/provider-AccountData/000077500000000000000000000000001521521330000236145ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/tests/provider-AccountData/AccountData_test.cpp000066400000000000000000000151271521521330000275530ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #include #include #include #include #include #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #pragma GCC diagnostic pop #include #include #include #include #include using namespace std; using namespace lomiri::storage::provider; using lomiri::storage::internal::InactivityTimer; class AccountDataTest : public ::testing::Test { public: QDBusConnection const& connection() { return dbus_->connection(); } protected: void SetUp() override { dbus_.reset(new DBusEnvironment); dbus_->start_services(); } void TearDown() override { dbus_.reset(); } private: unique_ptr dbus_; }; TEST_F(AccountDataTest, oauth1_credentials) { OnlineAccounts::Manager manager("", connection()); manager.waitForReady(); ASSERT_TRUE(manager.isReady()); auto accounts = manager.availableAccounts("oauth1-service"); ASSERT_EQ(1, accounts.size()); internal::OnlineAccountData account(unique_ptr(), shared_ptr(), shared_ptr(), connection(), accounts[0]); QSignalSpy spy(&account, &internal::AccountData::authenticated); account.authenticate(true); ASSERT_TRUE(spy.wait()); ASSERT_TRUE(account.has_credentials()); auto creds = boost::get(account.credentials()); EXPECT_EQ("consumer_key", creds.consumer_key); EXPECT_EQ("consumer_secret", creds.consumer_secret); EXPECT_EQ("token", creds.token); EXPECT_EQ("token_secret", creds.token_secret); } TEST_F(AccountDataTest, oauth2_credentials) { OnlineAccounts::Manager manager("", connection()); manager.waitForReady(); ASSERT_TRUE(manager.isReady()); auto accounts = manager.availableAccounts("oauth2-service"); ASSERT_EQ(1, accounts.size()); internal::OnlineAccountData account(unique_ptr(), shared_ptr(), shared_ptr(), connection(), accounts[0]); QSignalSpy spy(&account, &internal::AccountData::authenticated); account.authenticate(true); ASSERT_TRUE(spy.wait()); ASSERT_TRUE(account.has_credentials()); auto creds = boost::get(account.credentials()); EXPECT_EQ("access_token", creds.access_token); } TEST_F(AccountDataTest, password_credentials) { OnlineAccounts::Manager manager("", connection()); manager.waitForReady(); ASSERT_TRUE(manager.isReady()); auto accounts = manager.availableAccounts("password-service"); ASSERT_EQ(1, accounts.size()); internal::OnlineAccountData account(unique_ptr(), shared_ptr(), shared_ptr(), connection(), accounts[0]); QSignalSpy spy(&account, &internal::AccountData::authenticated); account.authenticate(true); ASSERT_TRUE(spy.wait()); ASSERT_TRUE(account.has_credentials()); auto creds = boost::get(account.credentials()); EXPECT_EQ("user", creds.username); EXPECT_EQ("pass", creds.password); EXPECT_EQ("", creds.host); } TEST_F(AccountDataTest, password_credentials_host) { OnlineAccounts::Manager manager("", connection()); manager.waitForReady(); ASSERT_TRUE(manager.isReady()); auto accounts = manager.availableAccounts("password-host-service"); ASSERT_EQ(1, accounts.size()); internal::OnlineAccountData account(unique_ptr(), shared_ptr(), shared_ptr(), connection(), accounts[0]); QSignalSpy spy(&account, &internal::AccountData::authenticated); account.authenticate(true); ASSERT_TRUE(spy.wait()); ASSERT_TRUE(account.has_credentials()); auto creds = boost::get(account.credentials()); // Host extracted from account settings. EXPECT_EQ("joe", creds.username); EXPECT_EQ("secret", creds.password); EXPECT_EQ("http://www.example.com/", creds.host); } TEST_F(AccountDataTest, fixed_account_data) { internal::FixedAccountData account(unique_ptr(), shared_ptr(), shared_ptr(), connection()); QSignalSpy spy(&account, &internal::AccountData::authenticated); account.authenticate(true); ASSERT_TRUE(spy.wait()); ASSERT_TRUE(account.has_credentials()); auto creds = boost::get(account.credentials()); ASSERT_EQ(boost::blank(), creds); } int main(int argc, char **argv) { QCoreApplication app(argc, argv); ::testing::InitGoogleTest(&argc, argv); int rc = RUN_ALL_TESTS(); // Process any pending events to avoid bogus leak reports from valgrind. QCoreApplication::sendPostedEvents(); QCoreApplication::processEvents(); return rc; } lomiri-storage-framework-0.5.0/tests/provider-AccountData/CMakeLists.txt000066400000000000000000000004171521521330000263560ustar00rootroot00000000000000add_executable(provider-AccountData_test AccountData_test.cpp) target_link_libraries(provider-AccountData_test PRIVATE lomiri-storage-framework-provider-static Qt${QT_VERSION_MAJOR}::Test testutils GTest::gtest ) gtest_discover_tests(provider-AccountData_test) lomiri-storage-framework-0.5.0/tests/provider-DBusPeerCache/000077500000000000000000000000001521521330000240235ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/tests/provider-DBusPeerCache/CMakeLists.txt000066400000000000000000000004271521521330000265660ustar00rootroot00000000000000add_executable(provider-DBusPeerCache_test DBusPeerCache_test.cpp) target_link_libraries(provider-DBusPeerCache_test PRIVATE lomiri-storage-framework-provider-static Qt${QT_VERSION_MAJOR}::Test testutils GTest::gtest ) gtest_discover_tests(provider-DBusPeerCache_test) lomiri-storage-framework-0.5.0/tests/provider-DBusPeerCache/DBusPeerCache_test.cpp000066400000000000000000000074561521521330000301770ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #pragma GCC diagnostic pop #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; using namespace lomiri::storage::provider; class DBusPeerCacheTest : public ::testing::Test { public: QDBusConnection const& connection() { return dbus_->connection(); } protected: void SetUp() override { dbus_.reset(new DBusEnvironment); dbus_->start_services(); } void TearDown() override { dbus_.reset(); } unique_ptr dbus_; }; template struct future_result { mutex lock; T result; exception_ptr error; }; // Wait on a boost::future using the event loop template T wait_on_future(boost::future &f) { struct future_result { mutex lock; bool complete = false; T result; exception_ptr error; }; auto r = make_shared(); boost::future f2 = f.then([r](boost::future f) { lock_guard guard(r->lock); r->complete = true; try { r->result = f.get(); } catch (...) { r->error = current_exception(); } QMetaObject::invokeMethod(QCoreApplication::instance(), "quit", Qt::QueuedConnection); }); QCoreApplication::instance()->exec(); lock_guard guard(r->lock); if (!r->complete) { throw runtime_error("Future did not complete"); } if (r->error) { rethrow_exception(r->error); } return r->result; } TEST_F(DBusPeerCacheTest, get_credentials) { // Get the unique name of the Online Accounts manager interface QDBusReply reply = connection().interface()->serviceOwner( "com.lomiri.OnlineAccounts.Manager"); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); auto peer_name = reply.value(); internal::DBusPeerCache cache(connection()); auto f = cache.get(peer_name); auto creds = wait_on_future(f); EXPECT_TRUE(creds.valid); EXPECT_EQ(geteuid(), creds.uid); EXPECT_EQ(dbus_->accounts_service_process().processId(), creds.pid); // If AppArmor is disabled, this gets filled in with "unconfined" anyway. EXPECT_EQ("unconfined", creds.label); // repeat f = cache.get(peer_name); creds = wait_on_future(f); EXPECT_TRUE(creds.valid); EXPECT_EQ(dbus_->accounts_service_process().processId(), creds.pid); } int main(int argc, char **argv) { QCoreApplication app(argc, argv); ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } lomiri-storage-framework-0.5.0/tests/provider-ProviderInterface/000077500000000000000000000000001521521330000250415ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/tests/provider-ProviderInterface/CMakeLists.txt000066400000000000000000000007061521521330000276040ustar00rootroot00000000000000add_executable(provider-ProviderInterface_test ProviderInterface_test.cpp TestProvider.cpp ${generated_files} ) set_target_properties(provider-ProviderInterface_test PROPERTIES AUTOMOC TRUE ) target_link_libraries(provider-ProviderInterface_test PRIVATE lomiri-storage-framework-common-internal lomiri-storage-framework-provider Qt${QT_VERSION_MAJOR}::Test testutils GTest::gtest ) gtest_discover_tests(provider-ProviderInterface_test) lomiri-storage-framework-0.5.0/tests/provider-ProviderInterface/ProviderInterface_test.cpp000066400000000000000000001007071521521330000322240ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #include #include #include #include #include "TestProvider.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; using lomiri::storage::ItemType; using lomiri::storage::provider::ProviderBase; using lomiri::storage::provider::Context; using lomiri::storage::provider::Item; using lomiri::storage::provider::ItemList; using lomiri::storage::provider::PasswordCredentials; using lomiri::storage::provider::UnauthorizedException; using lomiri::storage::provider::testing::TestServer; namespace { const auto SECOND_CONNECTION_NAME = QStringLiteral("second-bus-connection"); const QString PROVIDER_ERROR = lomiri::storage::internal::DBUS_ERROR_PREFIX; const string file_contents = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut " "enim ad minim veniam, quis nostrud exercitation ullamco laboris " "nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor " "in reprehenderit in voluptate velit esse cillum dolore eu fugiat " "nulla pariatur. Excepteur sint occaecat cupidatat non proident, " "sunt in culpa qui officia deserunt mollit anim id est laborum."; } class ProviderInterfaceTest : public ProviderFixture { protected: void SetUp() override { ProviderFixture::SetUp(); client_.reset(new ProviderClient(bus_name(), object_path(), connection())); } void TearDown() override { client_.reset(); ProviderFixture::TearDown(); } std::unique_ptr client_; }; TEST_F(ProviderInterfaceTest, roots) { set_provider(unique_ptr(new TestProvider)); auto reply = client_->Roots(QList()); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); EXPECT_EQ(1, reply.value().size()); auto root = reply.value()[0]; EXPECT_EQ("root_id", root.item_id); EXPECT_EQ(QList(), root.parent_ids); EXPECT_EQ("Root", root.name); EXPECT_EQ("etag", root.etag); EXPECT_EQ(ItemType::root, root.type); } TEST_F(ProviderInterfaceTest, list) { set_provider(unique_ptr(new TestProvider)); auto reply = client_->List("root_id", "", QList()); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); auto items = reply.argumentAt<0>(); QString page_token = reply.argumentAt<1>(); ASSERT_EQ(2, items.size()); EXPECT_EQ("child1_id", items[0].item_id); EXPECT_EQ("child2_id", items[1].item_id); EXPECT_EQ("page_token", page_token); reply = client_->List("root_id", page_token, QList()); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); items = reply.argumentAt<0>(); page_token = reply.argumentAt<1>(); ASSERT_EQ(2, items.size()); EXPECT_EQ("child3_id", items[0].item_id); EXPECT_EQ("child4_id", items[1].item_id); EXPECT_EQ("", page_token); // Try a bad page token reply = client_->List("root_id", "bad_page_token", QList()); wait_for(reply); EXPECT_TRUE(reply.isError()); EXPECT_EQ(PROVIDER_ERROR + "LogicException", reply.error().name()) << reply.error().name().toStdString(); EXPECT_EQ("Unknown page token", reply.error().message()) << reply.error().message().toStdString(); reply = client_->List("no_such_folder_id", "", QList()); wait_for(reply); EXPECT_TRUE(reply.isError()); EXPECT_EQ(PROVIDER_ERROR + "NotExistsException", reply.error().name()); EXPECT_EQ("Unknown folder", reply.error().message()); } TEST_F(ProviderInterfaceTest, lookup) { set_provider(unique_ptr(new TestProvider)); auto reply = client_->Lookup("root_id", "Filename", QList()); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); auto items = reply.value(); ASSERT_EQ(1, items.size()); auto item = items[0]; EXPECT_EQ("child_id", item.item_id); EXPECT_EQ(QList{ "root_id"}, item.parent_ids); EXPECT_EQ("Filename", item.name); EXPECT_EQ(ItemType::file, item.type); } TEST_F(ProviderInterfaceTest, metadata) { set_provider(unique_ptr(new TestProvider)); auto reply = client_->Metadata("root_id", QList()); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); auto item = reply.value(); EXPECT_EQ("root_id", item.item_id); EXPECT_EQ(QList(), item.parent_ids); EXPECT_EQ("Root", item.name); EXPECT_EQ(ItemType::root, item.type); } TEST_F(ProviderInterfaceTest, create_folder) { set_provider(unique_ptr(new TestProvider)); auto reply = client_->CreateFolder("root_id", "New Folder", QList()); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); auto item = reply.value(); EXPECT_EQ("new_folder_id", item.item_id); EXPECT_EQ(QList{ "root_id" }, item.parent_ids); EXPECT_EQ("New Folder", item.name); EXPECT_EQ(ItemType::folder, item.type); } TEST_F(ProviderInterfaceTest, create_file) { set_provider(unique_ptr(new TestProvider)); QString upload_id; QDBusUnixFileDescriptor socket; { auto reply = client_->CreateFile("parent_id", "file name", file_contents.size(), "text/plain", false, QList()); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); upload_id = reply.argumentAt<0>(); socket = reply.argumentAt<1>(); } auto app = QCoreApplication::instance(); QSocketNotifier notifier(socket.fileDescriptor(), QSocketNotifier::Write); size_t total_written = 0; QObject::connect( ¬ifier, &QSocketNotifier::activated, [app, ¬ifier, &total_written](int fd) { ssize_t n_written = write(fd, file_contents.data() + total_written, file_contents.size() - total_written); if (n_written < 0) { // Error writing notifier.setEnabled(false); app->quit(); } total_written += n_written; if (total_written == file_contents.size()) { notifier.setEnabled(false); app->quit(); } }); notifier.setEnabled(true); app->exec(); // File descriptor is owned by QDBusUnixFileDescriptor, so using // shutdown() to make sure the write channel is closed. ASSERT_EQ(0, shutdown(socket.fileDescriptor(), SHUT_WR)); auto reply = client_->FinishUpload(upload_id); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); auto item = reply.value(); EXPECT_EQ("new_file_id", item.item_id); EXPECT_EQ(QList{ "parent_id" }, item.parent_ids); EXPECT_EQ("file name", item.name); } TEST_F(ProviderInterfaceTest, update) { set_provider(unique_ptr(new TestProvider)); QString upload_id; QDBusUnixFileDescriptor socket; { auto reply = client_->Update("item_id", file_contents.size(), "old_etag", QList()); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); upload_id = reply.argumentAt<0>(); socket = reply.argumentAt<1>(); } auto app = QCoreApplication::instance(); QSocketNotifier notifier(socket.fileDescriptor(), QSocketNotifier::Write); size_t total_written = 0; QObject::connect( ¬ifier, &QSocketNotifier::activated, [app, ¬ifier, &total_written](int fd) { ssize_t n_written = write(fd, file_contents.data() + total_written, file_contents.size() - total_written); if (n_written < 0) { // Error writing notifier.setEnabled(false); app->quit(); } total_written += n_written; if (total_written == file_contents.size()) { notifier.setEnabled(false); app->quit(); } }); notifier.setEnabled(true); app->exec(); // File descriptor is owned by QDBusUnixFileDescriptor, so using // shutdown() to make sure the write channel is closed. ASSERT_EQ(0, shutdown(socket.fileDescriptor(), SHUT_WR)); auto reply = client_->FinishUpload(upload_id); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); auto item = reply.value(); EXPECT_EQ("item_id", item.item_id); } TEST_F(ProviderInterfaceTest, upload_short_write) { set_provider(unique_ptr(new TestProvider)); QString upload_id; QDBusUnixFileDescriptor socket; { auto reply = client_->Update("item_id", 100, "old_etag", QList()); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); upload_id = reply.argumentAt<0>(); socket = reply.argumentAt<1>(); } // File descriptor is owned by QDBusUnixFileDescriptor, so using // shutdown() to make sure the write channel is closed. ASSERT_EQ(0, shutdown(socket.fileDescriptor(), SHUT_WR)); auto reply = client_->FinishUpload(upload_id); wait_for(reply); ASSERT_TRUE(reply.isError()); EXPECT_EQ(PROVIDER_ERROR + "LogicException", reply.error().name()); EXPECT_EQ("wrong number of bytes", reply.error().message()); } TEST_F(ProviderInterfaceTest, upload_long_write) { set_provider(unique_ptr(new TestProvider)); QString upload_id; QDBusUnixFileDescriptor socket; { auto reply = client_->Update("item_id", file_contents.size() - 5, "old_etag", QList()); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); upload_id = reply.argumentAt<0>(); socket = reply.argumentAt<1>(); } auto app = QCoreApplication::instance(); QSocketNotifier notifier(socket.fileDescriptor(), QSocketNotifier::Write); size_t total_written = 0; QObject::connect( ¬ifier, &QSocketNotifier::activated, [app, ¬ifier, &total_written](int fd) { ssize_t n_written = write(fd, file_contents.data() + total_written, file_contents.size() - total_written); if (n_written < 0) { // Error writing notifier.setEnabled(false); app->quit(); } total_written += n_written; if (total_written == file_contents.size()) { notifier.setEnabled(false); app->quit(); } }); notifier.setEnabled(true); app->exec(); // File descriptor is owned by QDBusUnixFileDescriptor, so using // shutdown() to make sure the write channel is closed. ASSERT_EQ(0, shutdown(socket.fileDescriptor(), SHUT_WR)); auto reply = client_->FinishUpload(upload_id); wait_for(reply); ASSERT_TRUE(reply.isError()); EXPECT_EQ(PROVIDER_ERROR + "LogicException", reply.error().name()); EXPECT_EQ("too many bytes written", reply.error().message().toStdString()); } TEST_F(ProviderInterfaceTest, upload_not_closed) { set_provider(unique_ptr(new TestProvider)); QString upload_id; QDBusUnixFileDescriptor socket; { auto reply = client_->Update("item_id", 100, "old_etag", QList()); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); upload_id = reply.argumentAt<0>(); socket = reply.argumentAt<1>(); } auto reply = client_->FinishUpload(upload_id); wait_for(reply); ASSERT_TRUE(reply.isError()); EXPECT_EQ(PROVIDER_ERROR + "LogicException", reply.error().name()); EXPECT_EQ("Socket not closed", reply.error().message()); } TEST_F(ProviderInterfaceTest, cancel_upload) { set_provider(unique_ptr(new TestProvider)); QString upload_id; QDBusUnixFileDescriptor socket; { auto reply = client_->Update("item_id", 100, "old_etag", QList()); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); upload_id = reply.argumentAt<0>(); socket = reply.argumentAt<1>(); } auto reply = client_->CancelUpload(upload_id); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); } TEST_F(ProviderInterfaceTest, cancel_upload_wrong_connection) { set_provider(unique_ptr(new TestProvider)); auto upload_reply = client_->Update("item_id", 100, "old_etag", QList()); wait_for(upload_reply); ASSERT_TRUE(upload_reply.isValid()) << upload_reply.error().message().toStdString(); auto upload_id = upload_reply.argumentAt<0>(); // Try to finish download using a second connection QDBusConnection connection2 = QDBusConnection::connectToBus(dbus_->busAddress(), SECOND_CONNECTION_NAME); QDBusConnection::disconnectFromBus(SECOND_CONNECTION_NAME); ProviderClient client2(bus_name(), object_path(), connection2); auto reply = client2.CancelUpload(upload_id); wait_for(reply); ASSERT_FALSE(reply.isValid()); EXPECT_EQ(PROVIDER_ERROR + "LogicException", reply.error().name()); EXPECT_TRUE(reply.error().message().startsWith("No such upload: ")) << reply.error().message().toStdString(); } TEST_F(ProviderInterfaceTest, cancel_upload_on_disconnect) { set_provider(unique_ptr(new TestProvider)); QDBusServiceWatcher service_watcher; service_watcher.setConnection(*service_connection_); service_watcher.setWatchMode(QDBusServiceWatcher::WatchForUnregistration); QSignalSpy service_spy( &service_watcher, &QDBusServiceWatcher::serviceUnregistered); QDBusUnixFileDescriptor socket; { QDBusConnection connection2 = QDBusConnection::connectToBus(dbus_->busAddress(), SECOND_CONNECTION_NAME); QDBusConnection::disconnectFromBus(SECOND_CONNECTION_NAME); service_watcher.addWatchedService(connection2.baseService()); ProviderClient client2(bus_name(), object_path(), connection2); auto reply = client2.Update("item_id", 100, "old_etag", QList()); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); // Store socket so it will remain open past the closing of the // D-Bus connection. socket = reply.argumentAt<1>(); } // Wait until we're sure the fact that connection2 closed has // reached the service's connection, and then a little more to // ensure it is triggered. if (service_spy.count() == 0) { ASSERT_TRUE(service_spy.wait()); } QTimer timer; timer.setSingleShot(true); timer.setInterval(100); timer.start(); QSignalSpy timer_spy(&timer, &QTimer::timeout); ASSERT_TRUE(timer_spy.wait()); } TEST_F(ProviderInterfaceTest, finish_upload_unknown) { set_provider(unique_ptr(new TestProvider)); auto reply = client_->FinishUpload("no-such-upload"); wait_for(reply); ASSERT_TRUE(reply.isError()); EXPECT_EQ(PROVIDER_ERROR + "LogicException", reply.error().name()); EXPECT_EQ("No such upload: no-such-upload", reply.error().message()); } TEST_F(ProviderInterfaceTest, finish_upload_wrong_connection) { set_provider(unique_ptr(new TestProvider)); auto upload_reply = client_->Update("item_id", 100, "old_etag", QList()); wait_for(upload_reply); ASSERT_TRUE(upload_reply.isValid()) << upload_reply.error().message().toStdString(); auto upload_id = upload_reply.argumentAt<0>(); // Try to finish download using a second connection QDBusConnection connection2 = QDBusConnection::connectToBus(dbus_->busAddress(), SECOND_CONNECTION_NAME); QDBusConnection::disconnectFromBus(SECOND_CONNECTION_NAME); ProviderClient client2(bus_name(), object_path(), connection2); auto reply = client2.FinishUpload(upload_id); wait_for(reply); ASSERT_FALSE(reply.isValid()); EXPECT_EQ(PROVIDER_ERROR + "LogicException", reply.error().name()); EXPECT_TRUE(reply.error().message().startsWith("No such upload: ")) << reply.error().message().toStdString(); } TEST_F(ProviderInterfaceTest, tempfile_upload) { set_provider(unique_ptr(new TestProvider)); QString upload_id; QDBusUnixFileDescriptor socket; { auto reply = client_->Update("tempfile_item_id", file_contents.size(), "old_etag", QList()); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); upload_id = reply.argumentAt<0>(); socket = reply.argumentAt<1>(); } auto app = QCoreApplication::instance(); QSocketNotifier notifier(socket.fileDescriptor(), QSocketNotifier::Write); size_t total_written = 0; QObject::connect( ¬ifier, &QSocketNotifier::activated, [app, ¬ifier, &total_written](int fd) { ssize_t n_written = write(fd, file_contents.data() + total_written, file_contents.size() - total_written); if (n_written < 0) { // Error writing notifier.setEnabled(false); app->quit(); } total_written += n_written; if (total_written == file_contents.size()) { notifier.setEnabled(false); app->quit(); } }); notifier.setEnabled(true); app->exec(); // File descriptor is owned by QDBusUnixFileDescriptor, so using // shutdown() to make sure the write channel is closed. ASSERT_EQ(0, shutdown(socket.fileDescriptor(), SHUT_WR)); auto reply = client_->FinishUpload(upload_id); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); auto item = reply.value(); EXPECT_EQ("item_id", item.item_id); } TEST_F(ProviderInterfaceTest, tempfile_upload_short_write) { set_provider(unique_ptr(new TestProvider)); QString upload_id; QDBusUnixFileDescriptor socket; { auto reply = client_->Update("tempfile_item_id", 100, "old_etag", QList()); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); upload_id = reply.argumentAt<0>(); socket = reply.argumentAt<1>(); } // File descriptor is owned by QDBusUnixFileDescriptor, so using // shutdown() to make sure the write channel is closed. ASSERT_EQ(0, shutdown(socket.fileDescriptor(), SHUT_WR)); auto reply = client_->FinishUpload(upload_id); wait_for(reply); ASSERT_TRUE(reply.isError()); EXPECT_EQ(PROVIDER_ERROR + "LogicException", reply.error().name()); EXPECT_EQ("wrong number of bytes written", reply.error().message().toStdString()); } TEST_F(ProviderInterfaceTest, tempfile_upload_long_write) { set_provider(unique_ptr(new TestProvider)); QString upload_id; QDBusUnixFileDescriptor socket; { auto reply = client_->Update("tempfile_item_id", file_contents.size() - 5, "old_etag", QList()); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); upload_id = reply.argumentAt<0>(); socket = reply.argumentAt<1>(); } auto app = QCoreApplication::instance(); QSocketNotifier notifier(socket.fileDescriptor(), QSocketNotifier::Write); size_t total_written = 0; QObject::connect( ¬ifier, &QSocketNotifier::activated, [app, ¬ifier, &total_written](int fd) { ssize_t n_written = write(fd, file_contents.data() + total_written, file_contents.size() - total_written); if (n_written < 0) { // Error writing notifier.setEnabled(false); app->quit(); } total_written += n_written; if (total_written == file_contents.size()) { notifier.setEnabled(false); app->quit(); } }); notifier.setEnabled(true); app->exec(); // File descriptor is owned by QDBusUnixFileDescriptor, so using // shutdown() to make sure the write channel is closed. ASSERT_EQ(0, shutdown(socket.fileDescriptor(), SHUT_WR)); auto reply = client_->FinishUpload(upload_id); wait_for(reply); ASSERT_TRUE(reply.isError()); EXPECT_EQ(PROVIDER_ERROR + "LogicException", reply.error().name()); EXPECT_EQ("wrong number of bytes written", reply.error().message()); } TEST_F(ProviderInterfaceTest, tempfile_upload_not_closed) { set_provider(unique_ptr(new TestProvider)); QString upload_id; QDBusUnixFileDescriptor socket; { auto reply = client_->Update("tempfile_item_id", 100, "old_etag", QList()); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); upload_id = reply.argumentAt<0>(); socket = reply.argumentAt<1>(); } auto reply = client_->FinishUpload(upload_id); wait_for(reply); ASSERT_TRUE(reply.isError()); EXPECT_EQ(PROVIDER_ERROR + "LogicException", reply.error().name()); EXPECT_EQ("Socket not closed", reply.error().message()); } TEST_F(ProviderInterfaceTest, download) { set_provider(unique_ptr(new TestProvider)); QString download_id; QDBusUnixFileDescriptor socket; { auto reply = client_->Download("item_id", ""); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); download_id = reply.argumentAt<0>(); socket = reply.argumentAt<1>(); } std::string data; auto app = QCoreApplication::instance(); QSocketNotifier notifier(socket.fileDescriptor(), QSocketNotifier::Read); QObject::connect( ¬ifier, &QSocketNotifier::activated, [&data, app, ¬ifier](int fd) { char buf[1024]; ssize_t n_read = read(fd, buf, sizeof(buf)); if (n_read <= 0) { // Error or end of file notifier.setEnabled(false); app->quit(); } else { data += string(buf, n_read); } }); notifier.setEnabled(true); app->exec(); auto reply = client_->FinishDownload(download_id); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); // Also check that we got the expected data from the socket. EXPECT_EQ("Hello world", data); } TEST_F(ProviderInterfaceTest, download_short_read) { set_provider(unique_ptr(new TestProvider)); QString download_id; QDBusUnixFileDescriptor socket; { auto reply = client_->Download("item_id", ""); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); download_id = reply.argumentAt<0>(); socket = reply.argumentAt<1>(); } auto reply = client_->FinishDownload(download_id); wait_for(reply); ASSERT_TRUE(reply.isError()); EXPECT_EQ(PROVIDER_ERROR + "LogicException", reply.error().name()); EXPECT_EQ("Not all data read", reply.error().message()); } TEST_F(ProviderInterfaceTest, finish_download_unknown) { set_provider(unique_ptr(new TestProvider)); auto reply = client_->FinishDownload("no-such-download"); wait_for(reply); ASSERT_TRUE(reply.isError()); EXPECT_EQ(PROVIDER_ERROR + "LogicException", reply.error().name()); EXPECT_EQ("No such download: no-such-download", reply.error().message()); } TEST_F(ProviderInterfaceTest, finish_download_wrong_connection) { set_provider(unique_ptr(new TestProvider)); auto download_reply = client_->Download("item_id", ""); wait_for(download_reply); ASSERT_TRUE(download_reply.isValid()) << download_reply.error().message().toStdString(); auto download_id = download_reply.argumentAt<0>(); // Try to finish download using a second connection QDBusConnection connection2 = QDBusConnection::connectToBus(dbus_->busAddress(), SECOND_CONNECTION_NAME); QDBusConnection::disconnectFromBus(SECOND_CONNECTION_NAME); ProviderClient client2(bus_name(), object_path(), connection2); auto reply = client2.FinishDownload(download_id); wait_for(reply); ASSERT_FALSE(reply.isValid()); EXPECT_EQ(PROVIDER_ERROR + "LogicException", reply.error().name()); EXPECT_TRUE(reply.error().message().startsWith("No such download: ")) << reply.error().message().toStdString(); } TEST_F(ProviderInterfaceTest, cancel_download_on_disconnect) { set_provider(unique_ptr(new TestProvider)); QDBusServiceWatcher service_watcher; service_watcher.setConnection(*service_connection_); service_watcher.setWatchMode(QDBusServiceWatcher::WatchForUnregistration); QSignalSpy service_spy( &service_watcher, &QDBusServiceWatcher::serviceUnregistered); QDBusUnixFileDescriptor socket; { QDBusConnection connection2 = QDBusConnection::connectToBus(dbus_->busAddress(), SECOND_CONNECTION_NAME); QDBusConnection::disconnectFromBus(SECOND_CONNECTION_NAME); service_watcher.addWatchedService(connection2.baseService()); ProviderClient client2(bus_name(), object_path(), connection2); auto reply = client2.Download("item_id", ""); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); // Store socket so it will remain open past the closing of the // D-Bus connection. socket = reply.argumentAt<1>(); } // Wait until we're sure the fact that connection2 closed has // reached the service's connection, and then a little more to // ensure it is triggered. if (service_spy.count() == 0) { ASSERT_TRUE(service_spy.wait()); } QTimer timer; timer.setSingleShot(true); timer.setInterval(100); timer.start(); QSignalSpy timer_spy(&timer, &QTimer::timeout); ASSERT_TRUE(timer_spy.wait()); } TEST_F(ProviderInterfaceTest, delete_) { set_provider(unique_ptr(new TestProvider)); auto reply = client_->Delete("item_id"); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); } TEST_F(ProviderInterfaceTest, move) { set_provider(unique_ptr(new TestProvider)); auto reply = client_->Move("child_id", "new_parent_id", "New name", QList()); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); auto item = reply.value(); EXPECT_EQ("child_id", item.item_id); EXPECT_EQ(QList{ "new_parent_id" }, item.parent_ids); EXPECT_EQ("New name", item.name); EXPECT_EQ(ItemType::file, item.type); } TEST_F(ProviderInterfaceTest, copy) { set_provider(unique_ptr(new TestProvider)); auto reply = client_->Copy("child_id", "new_parent_id", "New name", QList()); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); auto item = reply.value(); EXPECT_EQ("new_id", item.item_id); EXPECT_EQ(QList{ "new_parent_id" }, item.parent_ids); EXPECT_EQ("New name", item.name); EXPECT_EQ(ItemType::file, item.type); } class ReauthenticateProvider : public TestProvider { boost::future roots(vector const& metadata_keys, Context const& ctx) override { Q_UNUSED(metadata_keys); auto password = boost::get(ctx.credentials).password; boost::promise p; p.set_value({ {"root_id", {}, password, "etag", ItemType::root, {}} }); return p.get_future(); } boost::future metadata(string const& item_id, vector const& metadata_keys, Context const& ctx) override { Q_UNUSED(metadata_keys); auto password = boost::get(ctx.credentials).password; boost::promise p; if (password != "refresh") { p.set_exception(UnauthorizedException("bad password")); } else { p.set_value( {item_id, {"root_id"}, password, "etag", ItemType::file, {}}); } return p.get_future(); } boost::future lookup(string const& parent_id, string const& name, vector const& metadata_keys, Context const& ctx) override { Q_UNUSED(parent_id); Q_UNUSED(name); Q_UNUSED(metadata_keys); Q_UNUSED(ctx); boost::promise p; p.set_exception(UnauthorizedException("bad password")); return p.get_future(); } }; TEST_F(ProviderInterfaceTest, need_interactive_auth) { // Account #10 fails to authenticate unless done interactively set_provider(unique_ptr(new ReauthenticateProvider), 10); auto reply = client_->Roots(QList()); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); EXPECT_EQ(1, reply.value().size()); auto root = reply.value()[0]; // Password returned as item name EXPECT_EQ("interactive", root.name); } TEST_F(ProviderInterfaceTest, unauthorized_exception_causes_refresh) { set_provider(unique_ptr(new ReauthenticateProvider), 10); // The Metadata() call will throw UnauthorizedException unless the // password is "refresh". This will cause the request to be // retried after authenticating a second time while invalidating // stored credentials. auto reply = client_->Metadata("item_id", QList()); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); auto item = reply.value(); // Password returned as item name EXPECT_EQ("refresh", item.name); } TEST_F(ProviderInterfaceTest, always_unauthorized) { set_provider(unique_ptr(new ReauthenticateProvider), 10); // lookup() will always throw UnauthorizedException. Rather than // looping endlessly, the exception is returned to the client. auto reply = client_->Lookup("parent_id", "name", QList()); wait_for(reply); ASSERT_FALSE(reply.isValid()); EXPECT_EQ(PROVIDER_ERROR + "UnauthorizedException", reply.error().name()); } TEST_F(ProviderInterfaceTest, user_canceled_auth) { // Account #11 always returns a UserCanceled error when trying to // authenticate. set_provider(unique_ptr(new ReauthenticateProvider), 11); auto reply = client_->Roots(QList()); wait_for(reply); ASSERT_FALSE(reply.isValid()); EXPECT_EQ(PROVIDER_ERROR + "UnauthorizedException", reply.error().name()); } int main(int argc, char **argv) { QCoreApplication app(argc, argv); qDBusRegisterMetaType(); qDBusRegisterMetaType>(); ::testing::InitGoogleTest(&argc, argv); int rc = RUN_ALL_TESTS(); // Process any pending events to avoid bogus leak reports from valgrind. QCoreApplication::sendPostedEvents(); QCoreApplication::processEvents(); return rc; } lomiri-storage-framework-0.5.0/tests/provider-ProviderInterface/TestProvider.cpp000066400000000000000000000303231521521330000302000ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #include "TestProvider.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; using namespace lomiri::storage; using namespace lomiri::storage::internal; using namespace lomiri::storage::provider; class TestUploadJob : public UploadJob { public: TestUploadJob(std::string const& upload_id, Item const& item, int64_t size); boost::future cancel() override; boost::future finish() override; private: void drain(); void read_some(); Item const item_; int64_t const size_; QSocketNotifier notifier_; int64_t bytes_read_ = 0; bool closed_ = false; }; TestUploadJob::TestUploadJob(std::string const& upload_id, Item const& item, int64_t size) : UploadJob(upload_id), item_(item), size_(size), notifier_(read_socket(), QSocketNotifier::Read) { QObject::connect( ¬ifier_, &QSocketNotifier::activated, [this]() { try { read_some(); } catch (...) { report_error(current_exception()); } }); notifier_.setEnabled(true); } boost::future TestUploadJob::cancel() { boost::promise p; notifier_.setEnabled(false); p.set_value(); return p.get_future(); } boost::future TestUploadJob::finish() { boost::promise p; printf("TestUploadJob::finish(): %d read of expected %d\n", int(bytes_read_), int(size_)); notifier_.setEnabled(false); drain(); if (bytes_read_ == size_) { p.set_value(item_); } else { p.set_exception(LogicException("wrong number of bytes written")); } return p.get_future(); } void TestUploadJob::drain() { while (true) { if (closed_ || read_socket() == -1) { break; } int nfds; fd_set rfds; struct timeval tv; nfds = read_socket() + 1; FD_ZERO(&rfds); FD_SET(read_socket(), &rfds); tv.tv_sec = 0; tv.tv_usec = 0; int ret = select(nfds, &rfds, nullptr, nullptr, &tv); if (ret > 0) { read_some(); } else if (ret == 0) { throw LogicException("Socket not closed"); } else if (ret < 0) { int error_code = errno; throw ResourceException("Select failure: " + safe_strerror(error_code), error_code); } } } void TestUploadJob::read_some() { printf("TestUploadJob::read_some(): %d read of expected %d\n", int(bytes_read_), int(size_)); char buf[5]; ssize_t n_read = read(read_socket(), buf, sizeof(buf)); if (n_read < 0) { int error_code = errno; notifier_.setEnabled(false); throw ResourceException("Read failure: " + safe_strerror(error_code), error_code); } else if (n_read == 0) { closed_ = true; notifier_.setEnabled(false); if (bytes_read_ != size_) { throw LogicException("wrong number of bytes"); } } else { bytes_read_ += n_read; if (bytes_read_ > size_) { notifier_.setEnabled(false); throw LogicException("too many bytes written"); } } } class TestTempfileUploadJob : public TempfileUploadJob { public: TestTempfileUploadJob(std::string const& upload_id, Item const& item, int64_t size); boost::future cancel() override; boost::future finish() override; private: Item const item_; int64_t const size_; }; TestTempfileUploadJob::TestTempfileUploadJob(std::string const& upload_id, Item const& item, int64_t size) : TempfileUploadJob(upload_id), item_(item), size_(size) { } boost::future TestTempfileUploadJob::cancel() { boost::promise p; p.set_value(); return p.get_future(); } boost::future TestTempfileUploadJob::finish() { drain(); boost::promise p; struct stat buf; if (stat(file_name().c_str(), &buf) < 0) { p.set_exception(ResourceException("Could not stat temp file", errno)); } else if (buf.st_size == size_) { p.set_value(item_); } else { p.set_exception(LogicException("wrong number of bytes written")); } return p.get_future(); } class TestDownloadJob : public DownloadJob { public: TestDownloadJob(std::string const& download_id, std::string const& data); boost::future cancel() override; boost::future finish() override; private: void write_some(); std::string const data_; ssize_t bytes_written_ = 0; QTimer timer_; }; TestDownloadJob::TestDownloadJob(std::string const& download_id, std::string const& data) : DownloadJob(download_id), data_(data) { timer_.setSingleShot(false); timer_.setInterval(10); QObject::connect(&timer_, &QTimer::timeout, [this]() { write_some(); }); timer_.start(); } boost::future TestDownloadJob::cancel() { timer_.stop(); boost::promise p; p.set_value(); return p.get_future(); } boost::future TestDownloadJob::finish() { boost::promise p; if (bytes_written_ < ssize_t(data_.size())) { p.set_exception(LogicException("Not all data read")); } else { p.set_value(); } return p.get_future(); } void TestDownloadJob::write_some() { if (bytes_written_ >= ssize_t(data_.size())) { report_complete(); timer_.stop(); return; } ssize_t n_written = write(write_socket(), data_.data() + bytes_written_, min(data_.size() - bytes_written_, size_t(2))); if (n_written < 0) { int error_code = errno; string msg = string("Write failure: ") + safe_strerror(error_code); report_error(make_exception_ptr(ResourceException(msg, error_code))); timer_.stop(); } else { bytes_written_ += n_written; } } boost::future TestProvider::roots(vector const& keys, Context const& ctx) { Q_UNUSED(keys); Q_UNUSED(ctx); ItemList roots = { {"root_id", {}, "Root", "etag", ItemType::root, {}}, }; boost::promise p; p.set_value(roots); return p.get_future(); } boost::future> TestProvider::list( string const& item_id, string const& page_token, vector const& keys, Context const& ctx) { Q_UNUSED(keys); Q_UNUSED(ctx); boost::promise> p; if (item_id != "root_id") { p.set_exception(NotExistsException("Unknown folder", item_id)); } else if (page_token == "") { ItemList children = { {"child1_id", { "root_id" }, "Child 1", "etag", ItemType::file, {}}, {"child2_id", { "root_id" }, "Child 2", "etag", ItemType::file, {}}, }; p.set_value(make_tuple(children, "page_token")); } else if (page_token == "page_token") { ItemList children = { {"child3_id", { "root_id" }, "Child 4", "etag", ItemType::file, {}}, {"child4_id", { "root_id" }, "Child 3", "etag", ItemType::file, {}}, }; p.set_value(make_tuple(children, "")); } else { p.set_exception(LogicException("Unknown page token")); } return p.get_future(); } boost::future TestProvider::lookup( string const& parent_id, string const& name, vector const& keys, Context const& ctx) { Q_UNUSED(keys); Q_UNUSED(ctx); boost::promise p; ItemList items = { {"child_id", { parent_id }, name, "etag", ItemType::file, {}}, }; p.set_value(items); return p.get_future(); } boost::future TestProvider::metadata( string const& item_id, vector const& keys, Context const& ctx) { Q_UNUSED(keys); Q_UNUSED(ctx); boost::promise p; if (item_id == "root_id") { Item item = {"root_id", {}, "Root", "etag", ItemType::root, {}}; p.set_value(item); } else { p.set_exception(NotExistsException("Unknown item", item_id)); } return p.get_future(); } boost::future TestProvider::create_folder( string const& parent_id, string const& name, vector const& keys, Context const& ctx) { Q_UNUSED(keys); Q_UNUSED(ctx); boost::promise p; Item item = {"new_folder_id", { parent_id }, name, "etag", ItemType::folder, {}}; p.set_value(item); return p.get_future(); } boost::future> TestProvider::create_file( string const& parent_id, string const& name, int64_t size, string const& content_type, bool allow_overwrite, vector const& keys, Context const& ctx) { Q_UNUSED(content_type); Q_UNUSED(allow_overwrite); Q_UNUSED(keys); Q_UNUSED(ctx); boost::promise> p; Item item = {"new_file_id", { parent_id }, name, "etag", ItemType::file, {}}; p.set_value(unique_ptr(new TestUploadJob("upload_id", item, size))); return p.get_future(); } boost::future> TestProvider::update( string const& item_id, int64_t size, string const& old_etag, vector const& keys, Context const& ctx) { Q_UNUSED(item_id); Q_UNUSED(old_etag); Q_UNUSED(keys); Q_UNUSED(ctx); boost::promise> p; Item item = {"item_id", { "parent_id" }, "file name", "etag", ItemType::file, {}}; if (item_id == "tempfile_item_id") { p.set_value(unique_ptr(new TestTempfileUploadJob("tempfile_upload_id", item, size))); } else { p.set_value(unique_ptr(new TestUploadJob("upload_id", item, size))); } return p.get_future(); } boost::future> TestProvider::download( string const& item_id, string const& match_etag, Context const& ctx) { Q_UNUSED(item_id); Q_UNUSED(match_etag); Q_UNUSED(ctx); boost::promise> p; p.set_value(unique_ptr( new TestDownloadJob("download_id", "Hello world"))); return p.get_future(); } boost::future TestProvider::delete_item( string const& item_id, Context const& ctx) { Q_UNUSED(ctx); boost::promise p; if (item_id == "item_id") { p.set_value(); } else { p.set_exception(NotExistsException("Bad filename", item_id)); } return p.get_future(); } boost::future TestProvider::move( string const& item_id, string const& new_parent_id, string const& new_name, vector const& keys, Context const& ctx) { Q_UNUSED(keys); Q_UNUSED(ctx); boost::promise p; Item item = {item_id, { new_parent_id }, new_name, "etag", ItemType::file, {}}; p.set_value(item); return p.get_future(); } boost::future TestProvider::copy( string const& item_id, string const& new_parent_id, string const& new_name, vector const& keys, Context const& ctx) { Q_UNUSED(item_id); Q_UNUSED(keys); Q_UNUSED(ctx); boost::promise p; Item item = {"new_id", { new_parent_id }, new_name, "etag", ItemType::file, {}}; p.set_value(item); return p.get_future(); } lomiri-storage-framework-0.5.0/tests/provider-ProviderInterface/TestProvider.h000066400000000000000000000066211521521330000276510ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #pragma once #include class TestProvider : public lomiri::storage::provider::ProviderBase { public: boost::future roots( std::vector const& keys, lomiri::storage::provider::Context const& ctx) override; boost::future> list( std::string const& item_id, std::string const& page_token, std::vector const& keys, lomiri::storage::provider::Context const& ctx) override; boost::future lookup( std::string const& parent_id, std::string const& name, std::vector const& keys, lomiri::storage::provider::Context const& ctx) override; boost::future metadata( std::string const& item_id, std::vector const& keys, lomiri::storage::provider::Context const& ctx) override; boost::future create_folder( std::string const& parent_id, std::string const& name, std::vector const& keys, lomiri::storage::provider::Context const& ctx) override; boost::future> create_file( std::string const& parent_id, std::string const& name, int64_t size, std::string const& content_type, bool allow_overwrite, std::vector const& keys, lomiri::storage::provider::Context const& ctx) override; boost::future> update( std::string const& item_id, int64_t size, std::string const& old_etag, std::vector const& keys, lomiri::storage::provider::Context const& ctx) override; boost::future> download( std::string const& item_id, std::string const& match_etag, lomiri::storage::provider::Context const& ctx) override; boost::future delete_item( std::string const& item_id, lomiri::storage::provider::Context const& ctx) override; boost::future move( std::string const& item_id, std::string const& new_parent_id, std::string const& new_name, std::vector const& keys, lomiri::storage::provider::Context const& ctx) override; boost::future copy( std::string const& item_id, std::string const& new_parent_id, std::string const& new_name, std::vector const& keys, lomiri::storage::provider::Context const& ctx) override; }; lomiri-storage-framework-0.5.0/tests/provider-Server/000077500000000000000000000000001521521330000226745ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/tests/provider-Server/CMakeLists.txt000066400000000000000000000005641521521330000254410ustar00rootroot00000000000000add_executable(provider-Server_test Server_test.cpp ../provider-ProviderInterface/TestProvider.cpp ) set_target_properties(provider-Server_test PROPERTIES AUTOMOC TRUE ) target_link_libraries(provider-Server_test PRIVATE lomiri-storage-framework-provider-static Qt${QT_VERSION_MAJOR}::Test testutils GTest::gtest ) gtest_discover_tests(provider-Server_test) lomiri-storage-framework-0.5.0/tests/provider-Server/Server_test.cpp000066400000000000000000000220551521521330000257110ustar00rootroot00000000000000/* * Copyright (C) 2017 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #include #include #include #include #include "../provider-ProviderInterface/TestProvider.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; using lomiri::storage::ItemType; using lomiri::storage::provider::Context; using lomiri::storage::provider::ItemList; using lomiri::storage::provider::PasswordCredentials; using lomiri::storage::provider::Server; using lomiri::storage::provider::internal::ServerImpl; using lomiri::storage::provider::testing::TestServer; namespace { const auto SECOND_CONNECTION_NAME = QStringLiteral("second-bus-connection"); const char BUS_NAME[] = "org.example.TestProvider"; const char SERVICE_ID[] = "oauth2-service"; const char OA_BUS_NAME[] = "com.lomiri.OnlineAccounts.Manager"; const char OA_OBJECT_PATH[] = "/com/lomiri/OnlineAccounts/Manager"; const char OA_TEST_IFACE[] = "com.lomiri.StorageFramework.Testing"; const string file_contents = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut " "enim ad minim veniam, quis nostrud exercitation ullamco laboris " "nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor " "in reprehenderit in voluptate velit esse cillum dolore eu fugiat " "nulla pariatur. Excepteur sint occaecat cupidatat non proident, " "sunt in culpa qui officia deserunt mollit anim id est laborum."; } class ServerTest : public ProviderFixture { protected: void update_account(const char* account_data) { auto msg = QDBusMessage::createMethodCall( OA_BUS_NAME, OA_OBJECT_PATH, OA_TEST_IFACE, "UpdateAccount"); msg << account_data; QDBusPendingReply reply = connection().asyncCall(msg); wait_for(reply); if (!reply.isValid()) { throw runtime_error(reply.error().message().toStdString()); } } void remove_account(uint32_t account_id, const char* service_id) { auto msg = QDBusMessage::createMethodCall( OA_BUS_NAME, OA_OBJECT_PATH, OA_TEST_IFACE, "RemoveAccount"); msg << account_id << service_id; QDBusPendingReply reply = connection().asyncCall(msg); wait_for(reply); if (!reply.isValid()) { throw runtime_error(reply.error().message().toStdString()); } } }; TEST_F(ServerTest, accounts_available_on_start) { unique_ptr> server( new Server(BUS_NAME, SERVICE_ID)); unique_ptr impl( new ServerImpl(server.get(), BUS_NAME, SERVICE_ID)); QSignalSpy added_spy(impl.get(), &ServerImpl::accountAdded); char *argv[1]; int argc = 0; impl->init(argc, argv, service_connection_.get()); if (added_spy.count() == 0) { added_spy.wait(); } ProviderClient client(BUS_NAME, "/provider/2", connection()); auto reply = client.Roots(QList()); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); } TEST_F(ServerTest, add_account) { unique_ptr> server( new Server(BUS_NAME, SERVICE_ID)); unique_ptr impl( new ServerImpl(server.get(), BUS_NAME, SERVICE_ID)); QSignalSpy added_spy(impl.get(), &ServerImpl::accountAdded); char *argv[1]; int argc = 0; impl->init(argc, argv, service_connection_.get()); if (added_spy.count() == 0) { added_spy.wait(); } // Add a second account added_spy.clear(); update_account(R"(Account(20, 'new account', 'oauth2-service', OAuth2('access_token', 0, [])))"); if (added_spy.count() == 0) { added_spy.wait(); } ProviderClient client(BUS_NAME, "/provider/20", connection()); auto reply = client.Roots(QList()); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); } TEST_F(ServerTest, remove_account) { unique_ptr> server( new Server(BUS_NAME, SERVICE_ID)); unique_ptr impl( new ServerImpl(server.get(), BUS_NAME, SERVICE_ID)); QSignalSpy added_spy(impl.get(), &ServerImpl::accountAdded); QSignalSpy removed_spy(impl.get(), &ServerImpl::accountRemoved); char *argv[1]; int argc = 0; impl->init(argc, argv, service_connection_.get()); if (added_spy.count() == 0) { added_spy.wait(); } ProviderClient client(BUS_NAME, "/provider/2", connection()); auto reply = client.Roots(QList()); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); // Remove the account remove_account(2, SERVICE_ID); if (removed_spy.count() == 0) { removed_spy.wait(); } // And note that new method calls are rejected reply = client.Roots(QList()); wait_for(reply); ASSERT_FALSE(reply.isValid()); EXPECT_EQ("No such object path '/provider/2'", reply.error().message().toStdString()); } class DataChangeProvider : public TestProvider { boost::future roots(vector const& metadata_keys, Context const& ctx) override { Q_UNUSED(metadata_keys); auto host = boost::get(ctx.credentials).host; boost::promise p; p.set_value({ {"root_id", {}, host, "etag", ItemType::root, {}} }); return p.get_future(); } }; TEST_F(ServerTest, account_data_changed) { unique_ptr> server( new Server(BUS_NAME, "password-host-service")); unique_ptr impl( new ServerImpl(server.get(), BUS_NAME, "password-host-service")); QSignalSpy added_spy(impl.get(), &ServerImpl::accountAdded); char *argv[1]; int argc = 0; impl->init(argc, argv, service_connection_.get()); if (added_spy.count() == 0) { added_spy.wait(); } ProviderClient client(BUS_NAME, "/provider/4", connection()); auto reply = client.Roots(QList()); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); ASSERT_EQ(1, reply.value().size()); EXPECT_EQ("http://www.example.com/", reply.value()[0].name); update_account(R"(Account(4, 'description', 'password-host-service', Password('joe', 'secret'), {'host': 'http://new.example.com/'}))"); reply = client.Roots(QList()); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); ASSERT_EQ(1, reply.value().size()); EXPECT_EQ("http://new.example.com/", reply.value()[0].name); } TEST_F(ServerTest, fixed_account) { unique_ptr> server( new Server(BUS_NAME, "")); unique_ptr impl( new ServerImpl(server.get(), BUS_NAME, "")); QSignalSpy added_spy(impl.get(), &ServerImpl::accountAdded); char *argv[1]; int argc = 0; impl->init(argc, argv, service_connection_.get()); if (added_spy.count() == 0) { added_spy.wait(); } ProviderClient client(BUS_NAME, "/provider/0", connection()); auto reply = client.Roots(QList()); wait_for(reply); ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); } int main(int argc, char **argv) { QCoreApplication app(argc, argv); qDBusRegisterMetaType(); qDBusRegisterMetaType>(); ::testing::InitGoogleTest(&argc, argv); int rc = RUN_ALL_TESTS(); // Process any pending events to avoid bogus leak reports from valgrind. QCoreApplication::sendPostedEvents(); QCoreApplication::processEvents(); return rc; } lomiri-storage-framework-0.5.0/tests/registry/000077500000000000000000000000001521521330000214465ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/tests/registry/CMakeLists.txt000066400000000000000000000006461521521330000242140ustar00rootroot00000000000000add_executable(registry_test registry_test.cpp) # for the generated D-Bus skeleton adaptor target_include_directories(registry_test PRIVATE ${CMAKE_BINARY_DIR}/src/registry ) target_link_libraries(registry_test PRIVATE registry-static Qt${QT_VERSION_MAJOR}::DBus Qt${QT_VERSION_MAJOR}::Test testutils GTest::gtest ) gtest_discover_tests(registry_test) set(UNIT_TEST_TARGETS ${UNIT_TEST_TARGETS} PARENT_SCOPE) lomiri-storage-framework-0.5.0/tests/registry/registry_test.cpp000066400000000000000000000116171521521330000250670ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include "registryadaptor.h" #include #include #include #include #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wold-style-cast" #include #pragma GCC diagnostic pop #include #include #include #include #include #include using namespace std; using namespace lomiri::storage; using lomiri::storage::internal::InactivityTimer; namespace { const auto SERVICE_CONNECTION_NAME = QStringLiteral("service-session-bus"); } class RegistryTests : public ::testing::Test { public: void SetUp() override { dbus_.reset(new DBusEnvironment); dbus_->start_services(); // Set up registry service_connection_.reset(new QDBusConnection( QDBusConnection::connectToBus(dbus_->busAddress(), SERVICE_CONNECTION_NAME))); registry_.reset( new registry::internal::RegistryAdaptor(*service_connection_, make_shared(0))); new ::RegistryAdaptor(registry_.get()); ASSERT_TRUE(service_connection_->registerObject( registry::OBJECT_PATH, registry_.get())); } void TearDown() override { registry_.reset(); service_connection_.reset(); QDBusConnection::disconnectFromBus(SERVICE_CONNECTION_NAME); dbus_.reset(); } unique_ptr dbus_; unique_ptr service_connection_; unique_ptr registry_; }; TEST_F(RegistryTests, list_accounts) { auto message = QDBusMessage::createMethodCall( service_connection_->baseService(), registry::OBJECT_PATH, registry::INTERFACE, QStringLiteral("ListAccounts")); QDBusPendingReply> reply = dbus_->connection().asyncCall(message); { QDBusPendingCallWatcher watcher(reply); QSignalSpy spy(&watcher, &QDBusPendingCallWatcher::finished); ASSERT_TRUE(spy.wait()); } ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString(); auto accounts = reply.value(); ASSERT_EQ(3, accounts.size()); auto test_account = accounts[0]; EXPECT_EQ("com.lomiri.StorageFramework.Provider.ProviderTest", test_account.busName); EXPECT_EQ("/provider/42", test_account.objectPath.path()); EXPECT_EQ(42u, test_account.id); EXPECT_EQ("storage-provider-test", test_account.serviceId); EXPECT_EQ("Fake test account", test_account.displayName); EXPECT_EQ("Test Provider", test_account.providerName); EXPECT_EQ("", test_account.iconName); auto mcloud_account = accounts[1]; EXPECT_EQ("com.lomiri.StorageFramework.Provider.McloudProvider", mcloud_account.busName); EXPECT_EQ("/provider/99", mcloud_account.objectPath.path()); EXPECT_EQ(99u, mcloud_account.id); EXPECT_EQ("storage-provider-mcloud", mcloud_account.serviceId); EXPECT_EQ("Fake mcloud account", mcloud_account.displayName); EXPECT_EQ("mcloud", mcloud_account.providerName); EXPECT_EQ("", mcloud_account.iconName); auto local_account = accounts[2]; EXPECT_EQ("com.lomiri.StorageFramework.Provider.Local", local_account.busName); EXPECT_EQ("/provider/0", local_account.objectPath.path()); EXPECT_EQ(0u, local_account.id); EXPECT_EQ("", local_account.serviceId); EXPECT_EQ(g_get_user_name(), local_account.displayName); EXPECT_EQ("Local Provider", local_account.providerName); EXPECT_EQ("", local_account.iconName); } int main(int argc, char** argv) { QCoreApplication app(argc, argv); qDBusRegisterMetaType(); qDBusRegisterMetaType>(); ::testing::InitGoogleTest(&argc, argv); int rc = RUN_ALL_TESTS(); // Process any pending events to avoid bogus leak reports from valgrind. QCoreApplication::sendPostedEvents(); QCoreApplication::processEvents(); return rc; } lomiri-storage-framework-0.5.0/tests/remote-client-v1/000077500000000000000000000000001521521330000226715ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/tests/remote-client-v1/CMakeLists.txt000066400000000000000000000012621521521330000254320ustar00rootroot00000000000000add_executable(remote-client-v1_test remote-client-v1_test.cpp MockProvider.cpp) set_target_properties(remote-client-v1_test PROPERTIES AUTOMOC TRUE) add_definitions(-DTEST_DIR="${CMAKE_CURRENT_BINARY_DIR}" -DBOOST_THREAD_VERSION=4) target_link_libraries(remote-client-v1_test PRIVATE lomiri-storage-framework-provider lomiri-storage-framework-qt-client Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::Test ${Boost_LIBRARIES} PkgConfig::GLIB_DEPS testutils GTest::gtest ) add_dependencies(remote-client-v1_test qt-client-all-headers provider-test) gtest_discover_tests(remote-client-v1_test) set(UNIT_TEST_TARGETS ${UNIT_TEST_TARGETS} PARENT_SCOPE) lomiri-storage-framework-0.5.0/tests/remote-client-v1/MockProvider.cpp000066400000000000000000000160031521521330000260010ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include "MockProvider.h" #include #include #include #include #include #include #include using namespace lomiri::storage; using namespace lomiri::storage::provider; using namespace std; using boost::make_exceptional_future; using boost::make_ready_future; MockProvider::MockProvider() { } MockProvider::MockProvider(string const& cmd) : cmd_(cmd) { } boost::future MockProvider::roots(vector const&, Context const&) { ItemList roots = { {"root_id", {}, "Root", "etag", ItemType::root, {}} }; return make_ready_future(roots); } boost::future> MockProvider::list( string const& item_id, string const& page_token, vector const&, Context const&) { if (item_id != "root_id") { string msg = string("Item::list(): no such item: \"") + item_id + "\""; return make_exceptional_future>(NotExistsException(msg, item_id)); } if (page_token != "") { string msg = string("Item::list(): invalid page token: \"") + page_token + "\""; return make_exceptional_future>(LogicException("invalid page token")); } ItemList children = { { "child_id", { "root_id" }, "Child", "etag", ItemType::file, { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } } } }; boost::promise> p; p.set_value(make_tuple(children, string())); return p.get_future(); } boost::future MockProvider::lookup( string const& parent_id, string const& name, vector const&, Context const&) { if (parent_id != "root_id") { string msg = string("Folder::lookup(): no such item: \"") + parent_id + "\""; return make_exceptional_future(NotExistsException(msg, parent_id)); } if (name != "Child") { string msg = string("Folder::lookup(): no such item: \"") + name + "\""; return make_exceptional_future(NotExistsException(msg, name)); } ItemList children = { { "child_id", { "root_id" }, "Child", "etag", ItemType::file, { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } } } }; return make_ready_future(children); } boost::future MockProvider::metadata(string const& item_id, vector const&, Context const&) { if (item_id == "root_id") { Item metadata{"root_id", {}, "Root", "etag", ItemType::root, {}}; return make_ready_future(metadata); } else if (item_id == "child_id") { Item metadata { "child_id", { "root_id" }, "Child", "etag", ItemType::file, { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } } }; return make_ready_future(metadata); } else if (item_id == "child_folder_id") { Item metadata{"child_folder_id", { "root_id" }, "Child_Folder", "etag", ItemType::folder, {}}; return make_ready_future(metadata); } return make_exceptional_future(NotExistsException("metadata(): no such item: " + item_id, item_id)); } boost::future MockProvider::create_folder( string const& parent_id, string const& name, vector const&, Context const&) { Item metadata{"new_folder_id", { parent_id }, name, "etag", ItemType::folder, {}}; return make_ready_future(metadata); } string make_job_id() { static int last_job_id = 0; return to_string(++last_job_id); } boost::future> MockProvider::create_file( string const&, string const&, int64_t, string const&, bool, vector const&, Context const&) { return make_ready_future>(new MockUploadJob(make_job_id())); } boost::future> MockProvider::update( string const&, int64_t, string const&, vector const&, Context const&) { return make_ready_future>(new MockUploadJob(make_job_id())); } boost::future> MockProvider::download( string const&, string const&, Context const&) { unique_ptr job(new MockDownloadJob(make_job_id())); const char contents[] = "Hello world"; if (write(job->write_socket(), contents, sizeof(contents)) != sizeof(contents)) { ResourceException e("download(): write failed", errno); job->report_error(make_exception_ptr(e)); return make_exceptional_future>(e); } job->report_complete(); return make_ready_future(std::move(job)); } boost::future MockProvider::delete_item( string const&, Context const&) { return make_ready_future(); } boost::future MockProvider::move( string const& item_id, string const& new_parent_id, string const& new_name, vector const&, Context const&) { Item metadata{item_id, { new_parent_id }, new_name, "etag", ItemType::file, {}}; return make_ready_future(metadata); } boost::future MockProvider::copy( string const&, string const& new_parent_id, string const& new_name, vector const&, Context const&) { Item metadata{"new_item_id", { new_parent_id }, new_name, "etag", ItemType::file, {}}; return make_ready_future(metadata); } MockUploadJob::MockUploadJob() : UploadJob("some_id") { } MockUploadJob::MockUploadJob(string const& cmd) : UploadJob("some_id") , cmd_(cmd) { } boost::future MockUploadJob::cancel() { return make_ready_future(); } boost::future MockUploadJob::finish() { Item metadata { "some_id", { "root_id" }, "some_upload", "etag", ItemType::file, { { metadata::SIZE_IN_BYTES, 10 }, { metadata::LAST_MODIFIED_TIME, "2011-04-05T14:30:10.005Z" } } }; return make_ready_future(metadata); } MockDownloadJob::MockDownloadJob() : DownloadJob("some_id") { } MockDownloadJob::MockDownloadJob(string const& cmd) : DownloadJob("some_id") , cmd_(cmd) { } boost::future MockDownloadJob::cancel() { return make_ready_future(); } boost::future MockDownloadJob::finish() { return make_ready_future(); } lomiri-storage-framework-0.5.0/tests/remote-client-v1/MockProvider.h000066400000000000000000000102771521521330000254550ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include #include class MockProvider : public lomiri::storage::provider::ProviderBase { public: MockProvider(); MockProvider(std::string const& cmd); boost::future roots(std::vector const& keys, lomiri::storage::provider::Context const& ctx) override; boost::future> list( std::string const& item_id, std::string const& page_token, std::vector const& keys, lomiri::storage::provider::Context const& ctx) override; boost::future lookup( std::string const& parent_id, std::string const& name, std::vector const& keys, lomiri::storage::provider::Context const& ctx) override; boost::future metadata( std::string const& item_id, std::vector const& keys, lomiri::storage::provider::Context const& ctx) override; boost::future create_folder( std::string const& parent_id, std::string const& name, std::vector const& keys, lomiri::storage::provider::Context const& ctx) override; boost::future> create_file( std::string const& parent_id, std::string const& name, int64_t size, std::string const& content_type, bool allow_overwrite, std::vector const& keys, lomiri::storage::provider::Context const& ctx) override; boost::future> update( std::string const& item_id, int64_t size, std::string const& old_etag, std::vector const& keys, lomiri::storage::provider::Context const& ctx) override; boost::future> download( std::string const& item_id, std::string const& match_etag, lomiri::storage::provider::Context const& ctx) override; boost::future delete_item( std::string const& item_id, lomiri::storage::provider::Context const& ctx) override; boost::future move( std::string const& item_id, std::string const& new_parent_id, std::string const& new_name, std::vector const& keys, lomiri::storage::provider::Context const& ctx) override; boost::future copy( std::string const& item_id, std::string const& new_parent_id, std::string const& new_name, std::vector const& keys, lomiri::storage::provider::Context const& ctx) override; private: std::string cmd_; }; class MockUploadJob : public lomiri::storage::provider::UploadJob { public: using UploadJob::UploadJob; MockUploadJob(); MockUploadJob(std::string const& cmd); boost::future cancel() override; boost::future finish() override; private: std::string cmd_; }; class MockDownloadJob : public lomiri::storage::provider::DownloadJob { public: using DownloadJob::DownloadJob; MockDownloadJob(); MockDownloadJob(const std::string& cmd); boost::future cancel() override; boost::future finish() override; private: std::string cmd_; }; lomiri-storage-framework-0.5.0/tests/remote-client-v1/remote-client-v1_test.cpp000066400000000000000000000756551521521330000275510ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include #include #include #include #include #include #include #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wold-style-cast" #include #pragma GCC diagnostic pop #include using namespace lomiri::storage; using namespace lomiri::storage::qt::client; using namespace std; // Yes, that's ridiculously long, but the builders in Jenkins and the CI Train // are stupifyingly slow at times. static constexpr int SIGNAL_WAIT_TIME = 30000; class RemoteClientTest : public ::testing::Test { public: QDBusConnection const& connection() { return dbus_->connection(); } protected: void SetUp() override { dbus_.reset(new DBusEnvironment); dbus_->add_demo_provider("storage-provider-test"); dbus_->start_services(); } void TearDown() override { dbus_.reset(); } private: unique_ptr dbus_; }; class RuntimeTest : public RemoteClientTest {}; class RootTest : public RemoteClientTest {}; class FolderTest : public RemoteClientTest {}; class FileTest : public RemoteClientTest {}; class ItemTest : public RemoteClientTest {}; class DestroyedTest : public ProviderFixture { protected: void SetUp() override { ProviderFixture::SetUp(); runtime_ = Runtime::create(connection()); //acc_ = runtime_->make_test_account(service_connection_->baseService(), impossible_name()); acc_ = runtime_->make_test_account(bus_name(), object_path()); } void TearDown() override { ProviderFixture::TearDown(); } Runtime::SPtr runtime_; Account::SPtr acc_; }; // Bunch of helper functions to reduce the amount of noise in the tests. template bool wait(T fut) { QFutureWatcher w; QSignalSpy spy(&w, &decltype(w)::finished); w.setFuture(fut); bool rc = spy.wait(SIGNAL_WAIT_TIME); EXPECT_TRUE(rc); return rc; } template<> bool wait(QFuture fut) { QFutureWatcher w; QSignalSpy spy(&w, &decltype(w)::finished); w.setFuture(fut); bool rc = spy.wait(SIGNAL_WAIT_TIME); EXPECT_TRUE(rc); return rc; } template T call(QFuture fut) { if (!wait(fut)) { throw runtime_error("call timed out"); } return fut.result(); } template <> void call(QFuture fut) { if (!wait(fut)) { throw runtime_error("call timed out"); } fut.waitForFinished(); } Account::SPtr get_account(Runtime::SPtr const& runtime) { auto accounts = call(runtime->accounts()); if (accounts.size() == 0) { qCritical() << "Cannot find any online account"; qCritical() << "Configure at least one online account for a provider in System Settings -> Online Accounts"; return nullptr; } for (auto acc : accounts) { if (acc->owner_id() == "storage-provider-test") { return acc; } } abort(); // Impossible } Root::SPtr get_root(Runtime::SPtr const& runtime) { auto acc = get_account(runtime); auto roots = call(acc->roots()); assert(roots.size() == 1); return roots[0]; } Folder::SPtr get_parent(Item::SPtr const& item) { assert(item->type() != ItemType::root); auto parents = call(item->parents()); assert(parents.size() >= 1); return parents[0]; } void clear_folder(Folder::SPtr const& folder) { auto items = call(folder->list()); assert(items.size() != 0); // TODO: temporary hack for use with demo provider for (auto i : items) { call(i->delete_item()); } } TEST_F(RuntimeTest, lifecycle) { auto runtime = Runtime::create(connection()); runtime->shutdown(); runtime->shutdown(); // Just to show that this is safe. } TEST_F(RuntimeTest, basic) { auto runtime = Runtime::create(connection()); auto acc = get_account(runtime); EXPECT_EQ(runtime, acc->runtime()); EXPECT_EQ("", acc->owner()); EXPECT_EQ("storage-provider-test", acc->owner_id()); EXPECT_EQ("Fake test account", acc->description()); } TEST_F(RuntimeTest, roots) { auto runtime = Runtime::create(connection()); auto acc = get_account(runtime); ASSERT_NE(nullptr, acc); auto roots = call(acc->roots()); ASSERT_GE(roots.size(), 0); EXPECT_EQ("root_id", roots[0]->native_identity()); } TEST_F(RootTest, basic) { auto runtime = Runtime::create(connection()); auto acc = get_account(runtime); auto root = get_root(runtime); EXPECT_EQ("root_id", root->native_identity()); EXPECT_EQ(acc, root->account()); EXPECT_EQ(ItemType::root, root->type()); EXPECT_EQ("Root", root->name()); EXPECT_NE("", root->etag()); auto parents = call(root->parents()); EXPECT_TRUE(parents.isEmpty()); EXPECT_TRUE(root->parent_ids().isEmpty()); // get() must return the root. auto item = call(root->get(root->native_identity())); EXPECT_NE(nullptr, dynamic_pointer_cast(item)); EXPECT_TRUE(root->equal_to(item)); // Free and used space can be anything, but must be > 0. auto free_space = call(root->free_space_bytes()); cerr << "bytes free: " << free_space << endl; EXPECT_GT(free_space, 0); auto used_space = call(root->used_space_bytes()); cerr << "bytes used: " << used_space << endl; EXPECT_GT(used_space, 0); } TEST_F(FolderTest, basic) { auto runtime = Runtime::create(connection()); auto root = get_root(runtime); clear_folder(root); auto items = call(root->list()); ASSERT_EQ(1, items.size()); // Create a file and check that it was created with correct type, name, and size 0. auto uploader = call(root->create_file("file1", 10)); EXPECT_EQ(10, uploader->size()); auto file = call(uploader->finish_upload()); EXPECT_EQ(ItemType::file, file->type()); EXPECT_EQ("some_upload", file->name()); EXPECT_EQ(10, file->size()); EXPECT_EQ("some_id", file->native_identity()); // For coverage: getting a file must return the correct one. file = dynamic_pointer_cast(call(root->get("child_id"))); EXPECT_EQ("child_id", file->native_identity()); EXPECT_EQ("Child", file->name()); } TEST_F(FileTest, upload) { auto runtime = Runtime::create(connection()); auto root = get_root(runtime); clear_folder(root); // Get a file. auto children = call(root->lookup("Child")); ASSERT_EQ(1, children.size()); auto file = dynamic_pointer_cast(children[0]); EXPECT_EQ("child_id", file->native_identity()); EXPECT_EQ("Child", file->name()); auto uploader = call(file->create_uploader(ConflictPolicy::error_if_conflict, 0)); EXPECT_EQ(0, uploader->size()); auto uploaded_file = call(uploader->finish_upload()); EXPECT_EQ("some_id", uploaded_file->native_identity()); EXPECT_EQ("some_upload", uploaded_file->name()); } TEST_F(RootTest, root_exceptions) { auto runtime = Runtime::create(connection()); auto root = get_root(runtime); clear_folder(root); try { call(root->delete_item()); FAIL(); } catch (LogicException const& e) { EXPECT_EQ("Item::delete_item(): cannot delete root folder", e.error_message()) << e.what(); } { try { call(root->get("no_such_file_id")); FAIL(); } catch (NotExistsException const& e) { EXPECT_EQ("no_such_file_id", e.key()); } } } TEST_F(RuntimeTest, runtime_destroyed_exceptions) { // Getting the runtime from an account after shutting down the runtime must fail. { auto runtime = Runtime::create(connection()); auto acc = get_account(runtime); runtime->shutdown(); try { acc->runtime(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Account::runtime(): runtime was destroyed previously", e.error_message()); } } // Getting the runtime from an account after destroying the runtime must fail. { auto runtime = Runtime::create(connection()); auto acc = get_account(runtime); runtime.reset(); try { acc->runtime(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Account::runtime(): runtime was destroyed previously", e.error_message()); } } // Getting accounts after shutting down the runtime must fail. { auto runtime = Runtime::create(connection()); runtime->shutdown(); try { call(runtime->accounts()); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Runtime::accounts(): runtime was destroyed previously", e.error_message()); } } // Getting roots from an account after shutting down the runtime must fail. { auto runtime = Runtime::create(connection()); auto acc = get_account(runtime); runtime->shutdown(); try { call(acc->roots()); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Account::roots(): runtime was destroyed previously", e.error_message()); } } // Getting the account from a root with a destroyed runtime must fail. { auto runtime = Runtime::create(connection()); auto root = get_root(runtime); runtime.reset(); try { root->account(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Root::account(): runtime was destroyed previously", e.error_message()); } } // Getting the account from a root with a destroyed account must fail. { auto runtime = Runtime::create(connection()); auto acc = get_account(runtime); auto root = get_root(runtime); runtime.reset(); acc.reset(); try { root->account(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Root::account(): runtime was destroyed previously", e.error_message()); } } // Getting the root from an item with a destroyed runtime must fail. { auto runtime = Runtime::create(connection()); auto root = get_root(runtime); clear_folder(root); auto file = dynamic_pointer_cast(call(root->get("child_id"))); runtime.reset(); try { file->root(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::root(): runtime was destroyed previously", e.error_message()); } } // Getting the root from an item with a destroyed root must fail. { auto runtime = Runtime::create(connection()); auto acc = get_account(runtime); auto root = get_root(runtime); clear_folder(root); auto file = dynamic_pointer_cast(call(root->get("child_id"))); runtime.reset(); acc.reset(); root.reset(); try { file->root(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::root(): runtime was destroyed previously", e.error_message()); } } // etag() with destroyed runtime must fail. { auto runtime = Runtime::create(connection()); auto root = get_root(runtime); clear_folder(root); auto file = dynamic_pointer_cast(call(root->get("child_id"))); runtime->shutdown(); try { file->etag(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::etag(): runtime was destroyed previously", e.error_message()); } } // metadata() with destroyed runtime must fail. { auto runtime = Runtime::create(connection()); auto root = get_root(runtime); clear_folder(root); auto file = dynamic_pointer_cast(call(root->get("child_id"))); runtime->shutdown(); try { file->metadata(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::metadata(): runtime was destroyed previously", e.error_message()); } } // last_modified_time() with destroyed runtime must fail. { auto runtime = Runtime::create(connection()); auto root = get_root(runtime); clear_folder(root); auto file = dynamic_pointer_cast(call(root->get("child_id"))); runtime->shutdown(); try { file->last_modified_time(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::last_modified_time(): runtime was destroyed previously", e.error_message()); } } // copy() with destroyed runtime must fail. { auto runtime = Runtime::create(connection()); auto root = get_root(runtime); clear_folder(root); auto file = dynamic_pointer_cast(call(root->get("child_id"))); runtime->shutdown(); try { call(file->copy(root, "file2")); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::copy(): runtime was destroyed previously", e.error_message()); } } // move() with destroyed runtime must fail. { auto runtime = Runtime::create(connection()); auto root = get_root(runtime); clear_folder(root); auto file = dynamic_pointer_cast(call(root->get("child_id"))); runtime->shutdown(); try { call(file->move(root, "file2")); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::move(): runtime was destroyed previously", e.error_message()); } } // parents() on root with destroyed runtime must fail. { auto runtime = Runtime::create(connection()); auto root = get_root(runtime); clear_folder(root); runtime->shutdown(); try { call(root->parents()); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Root::parents(): runtime was destroyed previously", e.error_message()); } } // parents() on file with destroyed runtime must fail. { auto runtime = Runtime::create(connection()); auto root = get_root(runtime); clear_folder(root); auto file = dynamic_pointer_cast(call(root->get("child_id"))); runtime->shutdown(); try { call(file->parents()); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::parents(): runtime was destroyed previously", e.error_message()); } } // parent_ids() with destroyed runtime must fail. { auto runtime = Runtime::create(connection()); auto root = get_root(runtime); clear_folder(root); auto file = dynamic_pointer_cast(call(root->get("child_id"))); runtime->shutdown(); try { file->parent_ids(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::parent_ids(): runtime was destroyed previously", e.error_message()); } } // parent_ids() on root with destroyed runtime must fail. { auto runtime = Runtime::create(connection()); auto root = get_root(runtime); clear_folder(root); runtime->shutdown(); try { root->parent_ids(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Root::parent_ids(): runtime was destroyed previously", e.error_message()); } } // delete_item() with destroyed runtime must fail. { auto runtime = Runtime::create(connection()); auto root = get_root(runtime); clear_folder(root); auto file = dynamic_pointer_cast(call(root->get("child_id"))); runtime->shutdown(); try { call(file->delete_item()); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::delete_item(): runtime was destroyed previously", e.error_message()); } } // delete_item() on root with destroyed runtime must fail. { auto runtime = Runtime::create(connection()); auto root = get_root(runtime); clear_folder(root); runtime->shutdown(); try { call(root->delete_item()); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::delete_item(): runtime was destroyed previously", e.error_message()); } } // creation_time() with destroyed runtime must fail. { auto runtime = Runtime::create(connection()); auto root = get_root(runtime); clear_folder(root); auto file = dynamic_pointer_cast(call(root->get("child_id"))); runtime->shutdown(); try { file->creation_time(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::creation_time(): runtime was destroyed previously", e.error_message()); } } // native_metadata() with destroyed runtime must fail. { auto runtime = Runtime::create(connection()); auto root = get_root(runtime); clear_folder(root); auto file = dynamic_pointer_cast(call(root->get("child_id"))); runtime->shutdown(); try { file->native_metadata(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::native_metadata(): runtime was destroyed previously", e.error_message()); } } // name() on root with destroyed runtime must fail. { auto runtime = Runtime::create(connection()); auto root = get_root(runtime); clear_folder(root); runtime->shutdown(); try { root->name(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::name(): runtime was destroyed previously", e.error_message()); } } // name() on folder with destroyed runtime must fail. { auto runtime = Runtime::create(connection()); auto root = get_root(runtime); clear_folder(root); auto folder = dynamic_pointer_cast(call(root->get("child_folder_id"))); runtime->shutdown(); try { folder->name(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::name(): runtime was destroyed previously", e.error_message()); } } // name() on file with destroyed runtime must fail. { auto runtime = Runtime::create(connection()); auto root = get_root(runtime); clear_folder(root); auto file = dynamic_pointer_cast(call(root->get("child_id"))); runtime->shutdown(); try { file->name(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::name(): runtime was destroyed previously", e.error_message()); } } // list() with destroyed runtime must fail. { auto runtime = Runtime::create(connection()); auto root = get_root(runtime); clear_folder(root); runtime->shutdown(); try { call(root->list()); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Folder::list(): runtime was destroyed previously", e.error_message()); } } // lookup() with destroyed runtime must fail. { auto runtime = Runtime::create(connection()); auto root = get_root(runtime); clear_folder(root); runtime->shutdown(); try { call(root->lookup("file")); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Folder::lookup(): runtime was destroyed previously", e.error_message()); } } // create_folder() with destroyed runtime must fail. { auto runtime = Runtime::create(connection()); auto root = get_root(runtime); clear_folder(root); runtime->shutdown(); try { call(root->create_folder("folder")); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Folder::create_folder(): runtime was destroyed previously", e.error_message()); } } // create_file() with destroyed runtime must fail. { auto runtime = Runtime::create(connection()); auto root = get_root(runtime); clear_folder(root); runtime->shutdown(); try { call(root->create_file("file", 0)); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Folder::create_file(): runtime was destroyed previously", e.error_message()); } } // size() with destroyed runtime must fail. { auto runtime = Runtime::create(connection()); auto root = get_root(runtime); clear_folder(root); auto file = dynamic_pointer_cast(call(root->get("child_id"))); runtime->shutdown(); try { file->size(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("File::size(): runtime was destroyed previously", e.error_message()); } } // create_uploader() with destroyed runtime must fail. { auto runtime = Runtime::create(connection()); auto root = get_root(runtime); clear_folder(root); auto file = dynamic_pointer_cast(call(root->get("child_id"))); runtime->shutdown(); try { call(file->create_uploader(ConflictPolicy::overwrite, 0)); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("File::create_uploader(): runtime was destroyed previously", e.error_message()) << e.what(); } } // create_downloader() with destroyed runtime must fail. { auto runtime = Runtime::create(connection()); auto root = get_root(runtime); clear_folder(root); auto file = dynamic_pointer_cast(call(root->get("child_id"))); runtime->shutdown(); try { call(file->create_downloader()); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("File::create_downloader(): runtime was destroyed previously", e.error_message()); } } // free_space_bytes() with destroyed runtime must fail. { auto runtime = Runtime::create(connection()); auto root = get_root(runtime); clear_folder(root); runtime->shutdown(); try { call(root->free_space_bytes()); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Root::free_space_bytes(): runtime was destroyed previously", e.error_message()); } } // used_space_bytes() with destroyed runtime must fail. { auto runtime = Runtime::create(connection()); auto root = get_root(runtime); clear_folder(root); runtime->shutdown(); try { call(root->used_space_bytes()); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Root::used_space_bytes(): runtime was destroyed previously", e.error_message()); } } // get() with destroyed runtime must fail. { auto runtime = Runtime::create(connection()); auto root = get_root(runtime); clear_folder(root); runtime->shutdown(); try { call(root->get("some_id")); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Root::get(): runtime was destroyed previously", e.error_message()); } } } TEST_F(DestroyedTest, roots_destroyed_while_reply_outstanding) { set_provider(unique_ptr(new MockProvider)); auto fut = acc_->roots(); runtime_->shutdown(); try { ASSERT_TRUE(wait(fut)); fut.result(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Account::roots(): runtime was destroyed previously", e.error_message()); } } TEST_F(DestroyedTest, get_destroyed_while_reply_outstanding) { set_provider(unique_ptr(new MockProvider("metadata slow"))); auto root = call(acc_->roots())[0]; auto fut = root->get("root_id"); runtime_->shutdown(); try { ASSERT_TRUE(wait(fut)); fut.result(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Root::get(): runtime was destroyed previously", e.error_message()); } } TEST_F(DestroyedTest, copy_destroyed_while_reply_outstanding) { set_provider(unique_ptr(new MockProvider())); auto root = call(acc_->roots())[0]; auto fut = root->copy(root, "new name"); runtime_->shutdown(); try { ASSERT_TRUE(wait(fut)); fut.result(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::copy(): runtime was destroyed previously", e.error_message()); } } TEST_F(DestroyedTest, move_destroyed_while_reply_outstanding) { set_provider(unique_ptr(new MockProvider("move slow"))); auto root = call(acc_->roots())[0]; auto file = dynamic_pointer_cast(call(root->get("child_id"))); auto fut = file->move(root, "new name"); runtime_->shutdown(); try { ASSERT_TRUE(wait(fut)); fut.result(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Item::move(): runtime was destroyed previously", e.error_message()); } } TEST_F(DestroyedTest, list_destroyed_while_reply_outstanding) { set_provider(unique_ptr(new MockProvider("list slow"))); auto root = call(acc_->roots())[0]; auto fut = root->list(); runtime_->shutdown(); try { ASSERT_TRUE(wait(fut)); fut.result(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Folder::list(): runtime was destroyed previously", e.error_message()); } } TEST_F(DestroyedTest, lookup_destroyed_while_reply_outstanding) { set_provider(unique_ptr(new MockProvider("lookup slow"))); auto root = call(acc_->roots())[0]; auto fut = root->lookup("Child"); runtime_->shutdown(); try { ASSERT_TRUE(wait(fut)); fut.result(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Folder::lookup(): runtime was destroyed previously", e.error_message()); } } TEST_F(DestroyedTest, create_folder_destroyed_while_reply_outstanding) { set_provider(unique_ptr(new MockProvider("create_folder slow"))); auto root = call(acc_->roots())[0]; auto fut = root->create_folder("Child"); runtime_->shutdown(); try { ASSERT_TRUE(wait(fut)); fut.result(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Folder::create_folder(): runtime was destroyed previously", e.error_message()); } } TEST_F(DestroyedTest, create_file_destroyed_while_reply_outstanding) { set_provider(unique_ptr(new MockProvider("create_file slow"))); auto root = call(acc_->roots())[0]; auto fut = root->create_file("Child", 0); runtime_->shutdown(); try { ASSERT_TRUE(wait(fut)); fut.result(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("Folder::create_file(): runtime was destroyed previously", e.error_message()); } } TEST_F(DestroyedTest, create_uploader_destroyed_while_reply_outstanding) { set_provider(unique_ptr(new MockProvider("create_file slow"))); auto root = call(acc_->roots())[0]; auto file = dynamic_pointer_cast(call(root->get("child_id"))); auto fut = file->create_uploader(ConflictPolicy::overwrite, 0); runtime_->shutdown(); try { ASSERT_TRUE(wait(fut)); fut.result(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("File::create_uploader(): runtime was destroyed previously", e.error_message()); } } TEST_F(DestroyedTest, create_downloader_destroyed_while_reply_outstanding) { set_provider(unique_ptr(new MockProvider("create_file slow"))); auto root = call(acc_->roots())[0]; auto file = dynamic_pointer_cast(call(root->get("child_id"))); auto fut = file->create_downloader(); runtime_->shutdown(); try { ASSERT_TRUE(wait(fut)); fut.result(); FAIL(); } catch (RuntimeDestroyedException const& e) { EXPECT_EQ("File::create_downloader(): runtime was destroyed previously", e.error_message()); } } int main(int argc, char** argv) { QCoreApplication app(argc, argv); ::testing::InitGoogleTest(&argc, argv); int rc = RUN_ALL_TESTS(); // Process any pending events to avoid bogus leak reports from valgrind. QCoreApplication::sendPostedEvents(); QCoreApplication::processEvents(); return rc; } lomiri-storage-framework-0.5.0/tests/remote-client/000077500000000000000000000000001521521330000223455ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/tests/remote-client/CMakeLists.txt000066400000000000000000000012671521521330000251130ustar00rootroot00000000000000add_executable(remote-client_test remote-client_test.cpp MockProvider.cpp) set_target_properties(remote-client_test PROPERTIES AUTOMOC TRUE) add_definitions(-DTEST_DIR="${CMAKE_CURRENT_BINARY_DIR}" -DBOOST_THREAD_VERSION=4) target_link_libraries(remote-client_test PRIVATE lomiri-storage-framework-provider lomiri-storage-framework-qt-client-v2 Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::Test ${Boost_LIBRARIES} PkgConfig::GLIB_DEPS testutils GTest::gtest ) add_dependencies(remote-client_test qt-client-all-headers lomiri-storage-framework-registry) gtest_discover_tests(remote-client_test) set(UNIT_TEST_TARGETS ${UNIT_TEST_TARGETS} PARENT_SCOPE) lomiri-storage-framework-0.5.0/tests/remote-client/MockProvider.cpp000066400000000000000000000443151521521330000254640ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include "MockProvider.h" #include #include #include #include #include #include #include using namespace lomiri::storage; using namespace lomiri::storage::provider; using namespace std; using boost::make_exceptional_future; using boost::make_ready_future; MockProvider::MockProvider() { } MockProvider::MockProvider(string const& cmd) : cmd_(cmd) { } boost::future MockProvider::roots(vector const& /* keys */, Context const&) { if (cmd_ == "roots_slow") { this_thread::sleep_for(chrono::seconds(1)); } if (cmd_ == "not_a_root") { ItemList roots = { {"root_id", {}, "Root", "etag", ItemType::file, {}} }; return make_ready_future(roots); } if (cmd_ == "roots_throw") { string msg = "roots(): I'm sorry Dave, I'm afraid I can't do that."; return make_exceptional_future(PermissionException(msg)); } ItemList roots = { {"root_id", {}, "Root", "etag", ItemType::root, {}} }; return make_ready_future(roots); } boost::future> MockProvider::list( string const& item_id, string const& page_token, vector const& /* keys */, Context const&) { if (cmd_ == "list_slow") { this_thread::sleep_for(chrono::seconds(1)); } if (cmd_ == "list_empty") { boost::promise> p; p.set_value(make_tuple(ItemList(), string())); return p.get_future(); } if (cmd_ == "list_no_permission") { string msg = string("permission denied"); return make_exceptional_future>(PermissionException(msg)); } if (cmd_ == "list_return_root") { ItemList children = { { "child_id", { "root_id" }, "Child", "etag", ItemType::root, { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } } } }; boost::promise> p; p.set_value(make_tuple(children, string())); return p.get_future(); } if (cmd_ == "list_two_children") { ItemList children; string next_token; if (page_token == "") { next_token = "next"; children = { { "child_id", { "root_id" }, "Child", "etag", ItemType::file, { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } } } }; } else { next_token = ""; children = { { "child2_id", { "root_id" }, "Child2", "etag", ItemType::file, { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } } } }; } boost::promise> p; p.set_value(make_tuple(children, next_token)); return p.get_future(); } if (item_id != "root_id") { string msg = string("Item::list(): no such item: \"") + item_id + "\""; return make_exceptional_future>(NotExistsException(msg, item_id)); } if (page_token != "") { string msg = string("Item::list(): invalid page token: \"") + page_token + "\""; return make_exceptional_future>(LogicException("invalid page token")); } ItemList children = { { "child_id", { "root_id" }, "Child", "etag", ItemType::file, { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } } } }; boost::promise> p; p.set_value(make_tuple(children, string())); return p.get_future(); } boost::future MockProvider::lookup( string const& parent_id, string const& name, vector const& /* keys */, Context const&) { if (parent_id != "root_id") { string msg = string("Folder::lookup(): no such item: \"") + parent_id + "\""; return make_exceptional_future(NotExistsException(msg, parent_id)); } if (name != "Child") { string msg = string("Folder::lookup(): no such item: \"") + name + "\""; return make_exceptional_future(NotExistsException(msg, name)); } ItemList children = { { "child_id", { "root_id" }, "Child", "etag", ItemType::file, { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } } } }; return make_ready_future(children); } boost::future MockProvider::metadata(string const& item_id, vector const& /* keys */, Context const&) { static int num_calls = 0; if (cmd_ == "slow_metadata") { this_thread::sleep_for(chrono::seconds(1)); } if (cmd_ == "empty_id") { Item metadata{"", {}, "Root", "etag", ItemType::root, {}}; return make_ready_future(metadata); } if (cmd_== "two_parents_throw") { ++num_calls; switch (num_calls) { case 2: return make_exceptional_future(ResourceException("metadata(): weird error", 42)); case 3: num_calls = 0; return make_exceptional_future(RemoteCommsException("metadata(): HTTP broken")); default: break; } } if (item_id == "root_id") { if (cmd_ == "bad_parent_metadata_from_child") { Item metadata{"root_id", {}, "Root", "etag", ItemType::file, {}}; return make_ready_future(metadata); } if (cmd_ == "root_with_parent") { Item metadata{"root_id", { "this shouldn't be here" }, "Root", "etag", ItemType::root, {}}; return make_ready_future(metadata); } Item metadata{"root_id", {}, "Root", "etag", ItemType::root, {}}; return make_ready_future(metadata); } if (item_id == "child_id") { if (cmd_ == "no_parents") { Item metadata { "child_id", {}, "Child", "etag", ItemType::file, { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } } }; return make_ready_future(metadata); } if (cmd_ == "empty_name") { Item metadata { "child_id", { "root_id" }, "", "etag", ItemType::file, { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } } }; return make_ready_future(metadata); } if (cmd_ == "empty_etag") { Item metadata { "child_id", { "root_id" }, "Child", "", ItemType::file, { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } } }; return make_ready_future(metadata); } if (cmd_ == "unknown_key") { Item metadata { "child_id", { "root_id" }, "Child", "etag", ItemType::file, { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" }, { metadata::DESCRIPTION, "child test file" }, // For coverage { metadata::WRITABLE, true }, // For coverage { "unknown_key", "" } } }; return make_ready_future(metadata); } if (cmd_ == "missing_key") { Item metadata { "child_id", { "root_id" }, "Child", "etag", ItemType::file, { { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } } }; return make_ready_future(metadata); } if (cmd_ == "wrong_type_for_time") { Item metadata { "child_id", { "root_id" }, "Child", "etag", ItemType::file, { { metadata::SIZE_IN_BYTES, 10 }, { metadata::LAST_MODIFIED_TIME, true } } }; return make_ready_future(metadata); } if (cmd_ == "bad_parse_for_time") { Item metadata { "child_id", { "root_id" }, "Child", "etag", ItemType::file, { { metadata::SIZE_IN_BYTES, 10 }, { metadata::LAST_MODIFIED_TIME, "xyz" } } }; return make_ready_future(metadata); } if (cmd_ == "missing_timezone") { Item metadata { "child_id", { "root_id" }, "Child", "etag", ItemType::file, { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30" } } }; return make_ready_future(metadata); } if (cmd_ == "wrong_type_for_size") { Item metadata { "child_id", { "root_id" }, "Child", "etag", ItemType::file, { { metadata::SIZE_IN_BYTES, "10" }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } } }; return make_ready_future(metadata); } if (cmd_ == "negative_size") { Item metadata { "child_id", { "root_id" }, "Child", "etag", ItemType::file, { { metadata::SIZE_IN_BYTES, -1 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } } }; return make_ready_future(metadata); } if (cmd_ == "empty_parent") { Item metadata { "child_id", { "" }, "Child", "etag", ItemType::file, { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } } }; return make_ready_future(metadata); } if (cmd_ == "two_parents" || cmd_ == "two_parents_throw") { Item metadata { "child_id", { "root_id", "child_folder_id" }, "Child", "etag", ItemType::file, { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } } }; return make_ready_future(metadata); } Item metadata { "child_id", { "root_id" }, "Child", "etag", ItemType::file, { { metadata::SIZE_IN_BYTES, 10 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } } }; return make_ready_future(metadata); } if (item_id == "child_folder_id") { Item metadata{"child_folder_id", { "root_id" }, "Child_Folder", "etag", ItemType::folder, {}}; return make_ready_future(metadata); } return make_exceptional_future(NotExistsException("metadata(): no such item: " + item_id, item_id)); } boost::future MockProvider::create_folder( string const& parent_id, string const& name, vector const& /* keys */, Context const&) { if (cmd_ == "create_folder_returns_file") { Item metadata{"new_folder_id", { parent_id }, name, "etag", ItemType::file, {}}; return make_ready_future(metadata); } Item metadata{"new_folder_id", { parent_id }, name, "etag", ItemType::folder, {}}; return make_ready_future(metadata); } boost::future> MockProvider::create_file( string const&, string const&, int64_t, string const&, bool, vector const&, Context const&) { return make_ready_future>(new MockUploadJob(cmd_)); } boost::future> MockProvider::update( string const&, int64_t, string const&, vector const&, Context const&) { if (cmd_ == "upload_slow") { this_thread::sleep_for(chrono::seconds(1)); } if (cmd_ == "upload_error") { unique_ptr job(new MockUploadJob()); ConflictException e("version mismatch"); job->report_error(make_exception_ptr(e)); return make_exceptional_future>(e); } return make_ready_future>(new MockUploadJob(cmd_)); } boost::future> MockProvider::download( string const&, string const& match_etag, Context const&) { if (cmd_ == "download_slow") { this_thread::sleep_for(chrono::seconds(1)); } if (!match_etag.empty()) { ConflictException e("download(): etag mismatch"); return make_exceptional_future>(e); } if (cmd_ == "download_error") { unique_ptr job(new MockDownloadJob()); ResourceException e("test error", 42); job->report_error(make_exception_ptr(e)); return make_exceptional_future>(e); } unique_ptr job(new MockDownloadJob(cmd_)); const char contents[] = "Hello world"; if (write(job->write_socket(), contents, strlen(contents)) != int(strlen(contents))) { ResourceException e("download(): write failed", errno); job->report_error(make_exception_ptr(e)); return make_exceptional_future>(e); } if (cmd_ != "finish_download_error" && cmd_ != "finish_download_slow_error") { job->report_complete(); } return make_ready_future(std::move(job)); } boost::future MockProvider::delete_item( string const& item_id, Context const&) { if (cmd_ == "slow_delete") { this_thread::sleep_for(chrono::seconds(1)); } if (cmd_ == "delete_no_such_item") { string msg = "delete_item(): no such item: " + item_id; return make_exceptional_future(NotExistsException(msg, item_id)); } return make_ready_future(); } boost::future MockProvider::move( string const& item_id, string const& new_parent_id, string const& new_name, vector const& /* keys */, Context const&) { if (cmd_ == "move_returns_root") { Item metadata { "root_id", { new_parent_id }, new_name, "etag", ItemType::root, { { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } } }; return make_ready_future(metadata); } if (cmd_ == "move_type_mismatch") { Item metadata { item_id, { new_parent_id }, new_name, "etag", ItemType::folder, { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } } }; return make_ready_future(metadata); } Item metadata { item_id, { new_parent_id }, new_name, "etag", ItemType::file, { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } } }; return make_ready_future(metadata); } boost::future MockProvider::copy( string const&, string const& new_parent_id, string const& new_name, vector const& /* keys */, Context const&) { if (cmd_ == "copy_type_mismatch") { Item metadata { "new_item_id", { new_parent_id }, new_name, "etag", ItemType::folder, { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } } }; return make_ready_future(metadata); } Item metadata { "new_item_id", { new_parent_id }, new_name, "etag", ItemType::file, { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } } }; return make_ready_future(metadata); } MockUploadJob::MockUploadJob() : UploadJob("some_id") { } MockUploadJob::MockUploadJob(string const& cmd) : UploadJob("some_id") , cmd_(cmd) { } boost::future MockUploadJob::cancel() { return make_ready_future(); } boost::future MockUploadJob::finish() { if (cmd_ == "finish_upload_slow" || cmd_ == "finish_upload_slow_error") { this_thread::sleep_for(chrono::seconds(1)); } if (cmd_ == "finish_upload_error" || cmd_ == "finish_upload_slow_error") { return make_exceptional_future(ResourceException("out of memory", 99)); } if (cmd_ == "create_file_exists") { ExistsException e("file exists", "child_id", "Child"); return make_exceptional_future(e); } if (cmd_ == "upload_returns_dir") { Item metadata{"some_id", { "root_id" }, "some_upload", "etag", ItemType::folder, {}}; return make_ready_future(metadata); } Item metadata { "child_id", { "root_id" }, "some_upload", "etag", ItemType::file, { { metadata::SIZE_IN_BYTES, 10 }, { metadata::LAST_MODIFIED_TIME, "2011-04-05T14:30:10.005Z" } } }; return make_ready_future(metadata); } MockDownloadJob::MockDownloadJob() : DownloadJob("some_id") { } MockDownloadJob::MockDownloadJob(string const& cmd) : DownloadJob("some_id") , cmd_(cmd) { } boost::future MockDownloadJob::cancel() { return make_ready_future(); } boost::future MockDownloadJob::finish() { if (cmd_ == "finish_download_slow" || cmd_ == "finish_download_slow_error") { this_thread::sleep_for(chrono::seconds(1)); } if (cmd_ == "finish_download_error" || cmd_ == "finish_download_slow_error") { return make_exceptional_future(NotExistsException("no such item", "item_id")); } return make_ready_future(); } lomiri-storage-framework-0.5.0/tests/remote-client/MockProvider.h000066400000000000000000000103001521521330000251140ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #pragma once #include #include #include class MockProvider : public lomiri::storage::provider::ProviderBase { public: MockProvider(); MockProvider(std::string const& cmd); boost::future roots(std::vector const& keys, lomiri::storage::provider::Context const& ctx) override; boost::future> list( std::string const& item_id, std::string const& page_token, std::vector const& keys, lomiri::storage::provider::Context const& ctx) override; boost::future lookup( std::string const& parent_id, std::string const& name, std::vector const& keys, lomiri::storage::provider::Context const& ctx) override; boost::future metadata( std::string const& item_id, std::vector const& keys, lomiri::storage::provider::Context const& ctx) override; boost::future create_folder( std::string const& parent_id, std::string const& name, std::vector const& keys, lomiri::storage::provider::Context const& ctx) override; boost::future> create_file( std::string const& parent_id, std::string const& name, int64_t size, std::string const& content_type, bool allow_overwrite, std::vector const& keys, lomiri::storage::provider::Context const& ctx) override; boost::future> update( std::string const& item_id, int64_t size, std::string const& old_etag, std::vector const& keys, lomiri::storage::provider::Context const& ctx) override; boost::future> download( std::string const& item_id, std::string const& match_etag, lomiri::storage::provider::Context const& ctx) override; boost::future delete_item( std::string const& item_id, lomiri::storage::provider::Context const& ctx) override; boost::future move( std::string const& item_id, std::string const& new_parent_id, std::string const& new_name, std::vector const& keys, lomiri::storage::provider::Context const& ctx) override; boost::future copy( std::string const& item_id, std::string const& new_parent_id, std::string const& new_name, std::vector const& keys, lomiri::storage::provider::Context const& ctx) override; private: std::string cmd_; }; class MockUploadJob : public lomiri::storage::provider::UploadJob { public: using UploadJob::UploadJob; MockUploadJob(); MockUploadJob(std::string const& cmd); boost::future cancel() override; boost::future finish() override; private: std::string cmd_; }; class MockDownloadJob : public lomiri::storage::provider::DownloadJob { public: using DownloadJob::DownloadJob; MockDownloadJob(); MockDownloadJob(const std::string& cmd); boost::future cancel() override; boost::future finish() override; private: std::string cmd_; }; lomiri-storage-framework-0.5.0/tests/remote-client/remote-client_test.cpp000066400000000000000000004027541521521330000266730ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: Michi Henning */ #include #include "MockProvider.h" #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #pragma GCC diagnostic pop #include #include using namespace lomiri::storage; using namespace lomiri::storage::qt; using namespace std; // Yes, that's ridiculously long, but the builders in Jenkins and the CI Train // are stupifyingly slow at times. static constexpr int SIGNAL_WAIT_TIME = 30000; class RemoteClientTest : public ProviderFixture { protected: void SetUp() override { ProviderFixture::SetUp(); runtime_.reset(new Runtime(connection())); acc_ = runtime_->make_test_account(service_connection_->baseService(), object_path()); } void TearDown() override { runtime_.reset(); ProviderFixture::TearDown(); } unique_ptr runtime_; Account acc_; }; class RuntimeTest : public ProviderFixture {}; class AccountTest : public RemoteClientTest {}; class CopyTest : public RemoteClientTest {}; class CreateFileTest : public RemoteClientTest {}; class CreateFolderTest : public RemoteClientTest {}; class DeleteTest : public RemoteClientTest {}; class DownloadTest : public RemoteClientTest {}; class GetTest : public RemoteClientTest {}; class ItemTest : public RemoteClientTest {}; class ListTest : public RemoteClientTest {}; class LookupTest : public RemoteClientTest {}; class MetadataTest : public RemoteClientTest {}; class MoveTest : public RemoteClientTest {}; class ParentsTest : public RemoteClientTest {}; class RootsTest : public RemoteClientTest {}; class UploadTest : public RemoteClientTest {}; TEST(Runtime, lifecycle) { Runtime runtime; EXPECT_TRUE(runtime.isValid()); EXPECT_EQ(StorageError::Type::NoError, runtime.error().type()); EXPECT_EQ(StorageError::Type::NoError, runtime.shutdown().type()); EXPECT_FALSE(runtime.isValid()); EXPECT_EQ(StorageError::Type::NoError, runtime.error().type()); // Check that a second shutdown sets the error. EXPECT_EQ(StorageError::Type::RuntimeDestroyed, runtime.shutdown().type()); EXPECT_FALSE(runtime.isValid()); EXPECT_EQ(StorageError::Type::RuntimeDestroyed, runtime.error().type()); EXPECT_EQ("Runtime::shutdown(): Runtime was destroyed previously", runtime.error().message()); } #if 0 // TODO, how to test this? TEST_F(RuntimeTest, init_error) { QDBusConnection conn(connection()); EXPECT_TRUE(conn.isConnected()); dbus_.reset(); // Destroying the DBusEnvironment in the fixture forces disconnection. EXPECT_FALSE(conn.isConnected()); Runtime rt(conn); EXPECT_FALSE(rt.isValid()); EXPECT_FALSE(rt.connection().isConnected()); auto e = rt.error(); EXPECT_EQ(StorageError::Type::LocalCommsError, e.type()); EXPECT_EQ("Runtime(): DBus connection is not connected", e.message()); } #endif TEST_F(AccountTest, basic) { { // Default constructor. Account a; EXPECT_FALSE(a.isValid()); EXPECT_EQ("", a.busName()); EXPECT_EQ("", a.objectPath()); EXPECT_EQ("", a.displayName()); } { auto acc = runtime_->make_test_account(service_connection_->baseService(), object_path(), 99, "sid", "name"); EXPECT_TRUE(acc.isValid()); EXPECT_EQ(service_connection_->baseService(), acc.busName()); EXPECT_EQ(object_path(), acc.objectPath()); EXPECT_EQ("name", acc.displayName()); // Copy constructor Account a2(acc); EXPECT_TRUE(a2.isValid()); EXPECT_EQ(service_connection_->baseService(), a2.busName()); EXPECT_EQ(object_path(), a2.objectPath()); EXPECT_EQ("name", a2.displayName()); // Move constructor Account a3(move(a2)); EXPECT_TRUE(a3.isValid()); EXPECT_EQ(service_connection_->baseService(), a3.busName()); EXPECT_EQ(object_path(), a3.objectPath()); EXPECT_EQ("name", a3.displayName()); // Moved-from object must be invalid EXPECT_FALSE(a2.isValid()); // Moved-from object must be assignable auto a4 = runtime_->make_test_account(service_connection_->baseService(), object_path(), 99, "sid4", "name4"); a2 = a4; EXPECT_TRUE(a2.isValid()); EXPECT_EQ(service_connection_->baseService(), a2.busName()); EXPECT_EQ(object_path(), a2.objectPath()); EXPECT_EQ("name4", a2.displayName()); } { auto a1 = runtime_->make_test_account(service_connection_->baseService(), object_path(), 99, "sid", "dn"); auto a2 = runtime_->make_test_account(service_connection_->baseService(), object_path(), 100, "sid2", "n2"); // Copy assignment a1 = a2; EXPECT_TRUE(a2.isValid()); EXPECT_EQ(service_connection_->baseService(), a1.busName()); EXPECT_EQ(object_path(), a1.objectPath()); EXPECT_EQ("n2", a1.displayName()); // Self-assignment a2 = a2; EXPECT_TRUE(a2.isValid()); EXPECT_EQ(service_connection_->baseService(), a1.busName()); EXPECT_EQ(object_path(), a1.objectPath()); EXPECT_EQ("n2", a1.displayName()); // Move assignment auto a3 = runtime_->make_test_account(service_connection_->baseService(), object_path(), 101, "sid3", "n3"); a1 = move(a3); EXPECT_TRUE(a1.isValid()); EXPECT_EQ(service_connection_->baseService(), a1.busName()); EXPECT_EQ(object_path(), a1.objectPath()); EXPECT_EQ("n3", a1.displayName()); // Moved-from object must be invalid EXPECT_FALSE(a3.isValid()); // Moved-from object must be assignable auto a4 = runtime_->make_test_account(service_connection_->baseService(), object_path(), 102, "sid4", "n4"); a2 = a4; EXPECT_TRUE(a2.isValid()); EXPECT_EQ(service_connection_->baseService(), a2.busName()); EXPECT_EQ(object_path(), a2.objectPath()); EXPECT_EQ("n4", a2.displayName()); } } TEST_F(AccountTest, comparison) { { // Both accounts invalid. Account a1; Account a2; EXPECT_TRUE(a1 == a2); EXPECT_FALSE(a1 != a2); EXPECT_FALSE(a1 < a2); EXPECT_TRUE(a1 <= a2); EXPECT_FALSE(a1 > a2); EXPECT_TRUE(a1 >= a2); } { // a1 valid, a2 invalid auto a1 = runtime_->make_test_account(service_connection_->baseService(), object_path()); Account a2; EXPECT_FALSE(a1 == a2); EXPECT_TRUE(a1 != a2); EXPECT_FALSE(a1 < a2); EXPECT_FALSE(a1 <= a2); EXPECT_TRUE(a1 > a2); EXPECT_TRUE(a1 >= a2); // And with swapped operands: EXPECT_FALSE(a2 == a1); EXPECT_TRUE(a2 != a1); EXPECT_TRUE(a2 < a1); EXPECT_TRUE(a2 <= a1); EXPECT_FALSE(a2 > a1); EXPECT_FALSE(a2 >= a1); } { // a1 < a2 for ID auto a1 = runtime_->make_test_account(service_connection_->baseService(), object_path(), 1, "x", "x"); auto a2 = runtime_->make_test_account(service_connection_->baseService(), object_path(), 2, "x", "x"); EXPECT_FALSE(a1 == a2); EXPECT_TRUE(a1 != a2); EXPECT_TRUE(a1 < a2); EXPECT_TRUE(a1 <= a2); EXPECT_FALSE(a1 > a2); EXPECT_FALSE(a1 >= a2); // And with swapped operands: EXPECT_FALSE(a2 == a1); EXPECT_TRUE(a2 != a1); EXPECT_FALSE(a2 < a1); EXPECT_FALSE(a2 <= a1); EXPECT_TRUE(a2 > a1); EXPECT_TRUE(a2 >= a1); } { // a1 < a2 for service ID auto a1 = runtime_->make_test_account(service_connection_->baseService(), object_path(), 1, "a", "x"); auto a2 = runtime_->make_test_account(service_connection_->baseService(), object_path(), 1, "b", "x"); EXPECT_FALSE(a1 == a2); EXPECT_TRUE(a1 != a2); EXPECT_TRUE(a1 < a2); EXPECT_TRUE(a1 <= a2); EXPECT_FALSE(a1 > a2); EXPECT_FALSE(a1 >= a2); // And with swapped operands: EXPECT_FALSE(a2 == a1); EXPECT_TRUE(a2 != a1); EXPECT_FALSE(a2 < a1); EXPECT_FALSE(a2 <= a1); EXPECT_TRUE(a2 > a1); EXPECT_TRUE(a2 >= a1); } { // a1 < a2 for display name auto a1 = runtime_->make_test_account(service_connection_->baseService(), object_path(), 1, "a", "a"); auto a2 = runtime_->make_test_account(service_connection_->baseService(), object_path(), 1, "a", "b"); EXPECT_FALSE(a1 == a2); EXPECT_TRUE(a1 != a2); EXPECT_TRUE(a1 < a2); EXPECT_TRUE(a1 <= a2); EXPECT_FALSE(a1 > a2); EXPECT_FALSE(a1 >= a2); // And with swapped operands: EXPECT_FALSE(a2 == a1); EXPECT_TRUE(a2 != a1); EXPECT_FALSE(a2 < a1); EXPECT_FALSE(a2 <= a1); EXPECT_TRUE(a2 > a1); EXPECT_TRUE(a2 >= a1); } { // a1 == a2 auto a1 = runtime_->make_test_account(service_connection_->baseService(), object_path(), 1, "a", "a"); auto a2 = runtime_->make_test_account(service_connection_->baseService(), object_path(), 1, "a", "a"); EXPECT_TRUE(a1 == a2); EXPECT_FALSE(a1 != a2); EXPECT_FALSE(a1 < a2); EXPECT_TRUE(a1 <= a2); EXPECT_FALSE(a1 > a2); EXPECT_TRUE(a1 >= a2); // And with swapped operands: EXPECT_TRUE(a2 == a1); EXPECT_FALSE(a2 != a1); EXPECT_FALSE(a2 < a1); EXPECT_TRUE(a2 <= a1); EXPECT_FALSE(a2 > a1); EXPECT_TRUE(a2 >= a1); } } TEST_F(AccountTest, hash) { unordered_set(); // Just to show that this works. Account a1; EXPECT_EQ(0u, hash()(a1)); EXPECT_EQ(0u, a1.hash()); EXPECT_EQ(0u, qHash(a1)); auto a2 = runtime_->make_test_account(service_connection_->baseService(), object_path(), 1, "a", "a"); // Due to different return types (size_t vs uint), hash() and qHash() do not return the same value. EXPECT_NE(0u, a2.hash()); EXPECT_NE(0u, qHash(a2)); } TEST_F(AccountTest, accounts) { unique_ptr j(runtime_->accounts()); EXPECT_TRUE(j->isValid()); EXPECT_EQ(AccountsJob::Status::Loading, j->status()); EXPECT_EQ(StorageError::Type::NoError, j->error().type()); EXPECT_EQ(QList(), j->accounts()); // We haven't waited for the result yet. QSignalSpy spy(j.get(), &AccountsJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); EXPECT_EQ(AccountsJob::Status::Finished, qvariant_cast(arg.at(0))); EXPECT_TRUE(j->isValid()); EXPECT_EQ(AccountsJob::Status::Finished, j->status()); EXPECT_EQ(StorageError::Type::NoError, j->error().type()); auto accounts = j->accounts(); EXPECT_GT(accounts.size(), 0); // The fake online accounts service includes a "com.lomiri.StorageFramework.Provider.ProviderTest" account. bool found = false; for (auto const& a : accounts) { qDebug() << a.busName(); if (a.busName() == "com.lomiri.StorageFramework.Provider.ProviderTest") { found = true; EXPECT_EQ("Test Provider", a.providerName()); // TODO: add tests for the other account properties. break; } } EXPECT_TRUE(found); } TEST_F(AccountTest, runtime_destroyed) { EXPECT_TRUE(runtime_->connection().isConnected()); // Just for coverage. EXPECT_EQ(StorageError::Type::NoError, runtime_->shutdown().type()); // Destroy runtime. unique_ptr j(runtime_->accounts()); EXPECT_FALSE(j->isValid()); EXPECT_EQ(AccountsJob::Status::Error, j->status()); EXPECT_EQ(StorageError::Type::RuntimeDestroyed, j->error().type()); EXPECT_EQ("Runtime::accounts(): Runtime was destroyed previously", j->error().message()); EXPECT_EQ(QList(), j->accounts()); // Signal must be received. QSignalSpy spy(j.get(), &AccountsJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); EXPECT_EQ(AccountsJob::Status::Error, qvariant_cast(arg.at(0))); } TEST_F(RootsTest, roots) { set_provider(unique_ptr(new MockProvider)); unique_ptr j(acc_.roots()); EXPECT_TRUE(j->isValid()); EXPECT_EQ(ItemListJob::Status::Loading, j->status()); EXPECT_EQ(StorageError::Type::NoError, j->error().type()); // Check that we get the statusChanged and itemsReady signals. QSignalSpy ready_spy(j.get(), &ItemListJob::itemsReady); QSignalSpy status_spy(j.get(), &ItemListJob::statusChanged); ASSERT_TRUE(ready_spy.wait(SIGNAL_WAIT_TIME)); ASSERT_EQ(1, ready_spy.count()); auto arg = ready_spy.takeFirst(); auto items = qvariant_cast>(arg.at(0)); ASSERT_EQ(1, items.size()); ASSERT_EQ(1, status_spy.count()); arg = status_spy.takeFirst(); EXPECT_EQ(ItemListJob::Status::Finished, qvariant_cast(arg.at(0))); EXPECT_EQ(StorageError::Type::NoError, j->error().type()); EXPECT_TRUE(j->isValid()); EXPECT_EQ(ItemListJob::Status::Finished, j->status()); EXPECT_EQ(StorageError::Type::NoError, j->error().type()); // Check contents of returned item. auto root = items[0]; EXPECT_TRUE(root.isValid()); EXPECT_EQ(Item::Type::Root, root.type()); EXPECT_EQ("root_id", root.itemId()); EXPECT_EQ("Root", root.name()); EXPECT_EQ("etag", root.etag()); EXPECT_EQ(QList(), root.parentIds()); EXPECT_FALSE(root.lastModifiedTime().isValid()); EXPECT_EQ(acc_, root.account()); } TEST_F(RootsTest, runtime_destroyed) { EXPECT_EQ(StorageError::Type::NoError, runtime_->shutdown().type()); // Destroy runtime. unique_ptr j(acc_.roots()); EXPECT_FALSE(j->isValid()); EXPECT_EQ(ItemListJob::Status::Error, j->status()); EXPECT_EQ(StorageError::Type::RuntimeDestroyed, j->error().type()); EXPECT_EQ("Account::roots(): Runtime was destroyed previously", j->error().message()); // Signal must be received. QSignalSpy spy(j.get(), &ItemListJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); EXPECT_EQ(ItemListJob::Status::Error, qvariant_cast(arg.at(0))); } TEST_F(RootsTest, runtime_destroyed_while_item_list_job_running) { set_provider(unique_ptr(new MockProvider("roots_slow"))); unique_ptr j(acc_.roots()); EXPECT_TRUE(j->isValid()); EXPECT_EQ(ItemListJob::Status::Loading, j->status()); EXPECT_EQ(StorageError::Type::NoError, j->error().type()); EXPECT_EQ(StorageError::Type::NoError, runtime_->shutdown().type()); // Destroy runtime, provider still sleeping // Signal must be received. QSignalSpy spy(j.get(), &ItemListJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); EXPECT_EQ(ItemListJob::Status::Error, qvariant_cast(arg.at(0))); EXPECT_EQ("Account::roots(): Runtime was destroyed previously", j->error().message()); } TEST_F(RootsTest, invalid_account) { Account a; unique_ptr j(a.roots()); EXPECT_FALSE(j->isValid()); EXPECT_EQ(ItemListJob::Status::Error, j->status()); EXPECT_EQ(StorageError::Type::LogicError, j->error().type()); EXPECT_EQ("Account::roots(): cannot create job from invalid account", j->error().message()); // Signal must be received. QSignalSpy spy(j.get(), &ItemListJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); EXPECT_EQ(ItemListJob::Status::Error, qvariant_cast(arg.at(0))); EXPECT_EQ("Account::roots(): cannot create job from invalid account", j->error().message()); } TEST_F(RootsTest, exception) { set_provider(unique_ptr(new MockProvider("roots_throw"))); unique_ptr j(acc_.roots()); EXPECT_TRUE(j->isValid()); EXPECT_EQ(ItemListJob::Status::Loading, j->status()); EXPECT_EQ(StorageError::Type::NoError, j->error().type()); EXPECT_EQ("No error", j->error().message()); QSignalSpy spy(j.get(), &ItemListJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); EXPECT_EQ(ItemListJob::Status::Error, qvariant_cast(arg.at(0))); EXPECT_EQ(ItemListJob::Status::Error, j->status()); EXPECT_EQ(StorageError::Type::PermissionDenied, j->error().type()); EXPECT_EQ("PermissionDenied: roots(): I'm sorry Dave, I'm afraid I can't do that.", j->error().errorString()); } TEST_F(RootsTest, not_a_root) { set_provider(unique_ptr(new MockProvider("not_a_root"))); unique_ptr j(acc_.roots()); QSignalSpy ready_spy(j.get(), &ItemListJob::itemsReady); QSignalSpy status_spy(j.get(), &ItemListJob::statusChanged); status_spy.wait(SIGNAL_WAIT_TIME); { auto arg = status_spy.takeFirst(); EXPECT_EQ(ItemListJob::Status::Error, qvariant_cast(arg.at(0))); } if (ready_spy.count() != 1) { ready_spy.wait(SIGNAL_WAIT_TIME); } auto arg = ready_spy.takeFirst(); auto items = qvariant_cast>(arg.at(0)); EXPECT_EQ(0, items.size()); EXPECT_EQ(ItemListJob::Status::Error, j->status()); EXPECT_EQ(StorageError::Type::LocalCommsError, j->error().type()); EXPECT_EQ("LocalCommsError: Account::roots(): provider returned non-root item type: 0 (id = root_id)", j->error().errorString()); } TEST_F(GetTest, basic) { set_provider(unique_ptr(new MockProvider())); // Get root. { unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); EXPECT_EQ("root_id", j->item().itemId()); EXPECT_EQ("Root", j->item().name()); EXPECT_EQ(Item::Type::Root, j->item().type()); } // Get a file. { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); EXPECT_EQ("child_id", j->item().itemId()); EXPECT_EQ("Child", j->item().name()); EXPECT_EQ(Item::Type::File, j->item().type()); } // Get a folder. { unique_ptr j(acc_.get("child_folder_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); EXPECT_EQ("child_folder_id", j->item().itemId()); EXPECT_EQ("Child_Folder", j->item().name()); EXPECT_EQ(Item::Type::Folder, j->item().type()); } } TEST_F(GetTest, runtime_destroyed) { EXPECT_EQ(StorageError::Type::NoError, runtime_->shutdown().type()); // Destroy runtime. unique_ptr j(acc_.get("root_id")); EXPECT_FALSE(j->isValid()); EXPECT_EQ(ItemJob::Status::Error, j->status()); EXPECT_EQ(StorageError::Type::RuntimeDestroyed, j->error().type()); EXPECT_EQ("Account::get(): Runtime was destroyed previously", j->error().message()); // Signal must be received. QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); auto arg = spy.takeFirst(); EXPECT_EQ(ItemJob::Status::Error, qvariant_cast(arg.at(0))); EXPECT_EQ("Account::get(): Runtime was destroyed previously", j->error().message()); } TEST_F(GetTest, runtime_destroyed_while_item_job_running) { set_provider(unique_ptr(new MockProvider("slow_metadata"))); unique_ptr j(acc_.get("child_folder_id")); EXPECT_TRUE(j->isValid()); EXPECT_EQ(StorageError::Type::NoError, runtime_->shutdown().type()); // Destroy runtime, provider still sleeping QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); auto arg = spy.takeFirst(); EXPECT_EQ(ItemJob::Status::Error, qvariant_cast(arg.at(0))); EXPECT_EQ("Account::get(): Runtime was destroyed previously", j->error().message()); } TEST_F(GetTest, invalid_account) { Account a; unique_ptr j(a.get("child_Id")); EXPECT_FALSE(j->isValid()); EXPECT_EQ(ItemJob::Status::Error, j->status()); EXPECT_EQ(StorageError::Type::LogicError, j->error().type()); EXPECT_EQ("Account::get(): cannot create job from invalid account", j->error().message()); // Signal must be received. QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); EXPECT_EQ(ItemJob::Status::Error, qvariant_cast(arg.at(0))); EXPECT_EQ("Account::get(): cannot create job from invalid account", j->error().message()); } TEST_F(GetTest, empty_id_from_provider) { set_provider(unique_ptr(new MockProvider("empty_id"))); unique_ptr j(acc_.get("child_folder_id")); EXPECT_TRUE(j->isValid()); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); auto arg = spy.takeFirst(); EXPECT_EQ(ItemJob::Status::Error, qvariant_cast(arg.at(0))); EXPECT_EQ("Account::get(): received invalid metadata from provider: item_id cannot be empty", j->error().message()); } TEST_F(GetTest, no_such_id) { set_provider(unique_ptr(new MockProvider())); unique_ptr j(acc_.get("no_such_id")); EXPECT_TRUE(j->isValid()); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); auto arg = spy.takeFirst(); EXPECT_EQ(ItemJob::Status::Error, qvariant_cast(arg.at(0))); // TODO: This is missing the method name EXPECT_EQ("NotExists: metadata(): no such item: no_such_id", j->error().errorString()); EXPECT_EQ("no_such_id", j->error().itemId()); } TEST_F(MetadataTest, basic) { set_provider(unique_ptr(new MockProvider())); { Item i; EXPECT_EQ(0, i.metadata().size()); } { unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); EXPECT_EQ(0, j->item().sizeInBytes()); EXPECT_EQ(0, j->item().metadata().size()); } { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); EXPECT_EQ(10, j->item().sizeInBytes()); EXPECT_EQ(2, j->item().metadata().size()); } } TEST_F(MetadataTest, no_parents) { set_provider(unique_ptr(new MockProvider("no_parents"))); unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); EXPECT_EQ(ItemJob::Status::Error, j->status()); EXPECT_EQ("LocalCommsError: Account::get(): received invalid metadata from provider (id = child_id): " "file or folder must have at least one parent ID" , j->error().errorString()); } TEST_F(MetadataTest, empty_parent) { set_provider(unique_ptr(new MockProvider("empty_parent"))); unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); EXPECT_EQ(ItemJob::Status::Error, j->status()); EXPECT_EQ("LocalCommsError: Account::get(): received invalid metadata from provider (id = child_id): " "parent_id of file or folder cannot be empty", j->error().errorString()); } TEST_F(MetadataTest, root_with_parent) { set_provider(unique_ptr(new MockProvider("root_with_parent"))); unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); EXPECT_EQ(ItemJob::Status::Error, j->status()); EXPECT_EQ("LocalCommsError: Account::get(): received invalid metadata from provider (id = root_id): " "parent_ids of root must be empty", j->error().errorString()); } TEST_F(MetadataTest, empty_name) { set_provider(unique_ptr(new MockProvider("empty_name"))); unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); EXPECT_EQ(ItemJob::Status::Error, j->status()); EXPECT_EQ("LocalCommsError: Account::get(): received invalid metadata from provider (id = child_id): " "name cannot be empty", j->error().errorString()); } TEST_F(MetadataTest, empty_etag) { set_provider(unique_ptr(new MockProvider("empty_etag"))); unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); EXPECT_EQ(ItemJob::Status::Error, j->status()); EXPECT_EQ("LocalCommsError: Account::get(): received invalid metadata from provider (id = child_id): " "etag of a file cannot be empty", j->error().errorString()); } TEST_F(MetadataTest, unknown_key) { set_provider(unique_ptr(new MockProvider("unknown_key"))); unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); // We only emit a warning for unknown keys. EXPECT_EQ(ItemJob::Status::Finished, j->status()); } TEST_F(MetadataTest, missing_size) { set_provider(unique_ptr(new MockProvider("missing_key"))); unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); EXPECT_EQ(ItemJob::Status::Error, j->status()); EXPECT_EQ("LocalCommsError: Account::get(): received invalid metadata from provider (id = child_id): " "missing key \"size_in_bytes\" in metadata", j->error().errorString()); } TEST_F(MetadataTest, wrong_type_for_time) { set_provider(unique_ptr(new MockProvider("wrong_type_for_time"))); unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); EXPECT_EQ(ItemJob::Status::Error, j->status()); EXPECT_EQ("LocalCommsError: Account::get(): received invalid metadata from provider (id = child_id): " "last_modified_time: expected value of type QString, but received value of type qlonglong", j->error().errorString()); } TEST_F(MetadataTest, bad_parse_for_time) { set_provider(unique_ptr(new MockProvider("bad_parse_for_time"))); unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); EXPECT_EQ(ItemJob::Status::Error, j->status()); EXPECT_EQ("LocalCommsError: Account::get(): received invalid metadata from provider (id = child_id): " "last_modified_time: value \"xyz\" does not parse as ISO-8601 date", j->error().errorString()); } TEST_F(MetadataTest, missing_timezone) { set_provider(unique_ptr(new MockProvider("missing_timezone"))); unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); EXPECT_EQ(ItemJob::Status::Error, j->status()); EXPECT_EQ("LocalCommsError: Account::get(): received invalid metadata from provider (id = child_id): " "last_modified_time: value \"2007-04-05T14:30\" lacks a time zone specification", j->error().errorString()); } TEST_F(MetadataTest, wrong_type_for_size) { set_provider(unique_ptr(new MockProvider("wrong_type_for_size"))); unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); EXPECT_EQ(ItemJob::Status::Error, j->status()); EXPECT_EQ("LocalCommsError: Account::get(): received invalid metadata from provider (id = child_id): " "size_in_bytes: expected value of type qlonglong, but received value of type QString", j->error().errorString()); } TEST_F(MetadataTest, negative_size) { set_provider(unique_ptr(new MockProvider("negative_size"))); unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); EXPECT_EQ(ItemJob::Status::Error, j->status()); EXPECT_EQ("LocalCommsError: Account::get(): received invalid metadata from provider (id = child_id): " "size_in_bytes: expected value >= 0, but received -1", j->error().errorString()); } TEST_F(DeleteTest, basic) { set_provider(unique_ptr(new MockProvider)); Item item; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); item = j->item(); } unique_ptr j(item.deleteItem()); EXPECT_TRUE(j->isValid()); EXPECT_EQ(VoidJob::Status::Loading, j->status()); EXPECT_EQ(StorageError::Type::NoError, j->error().type()); EXPECT_EQ("child_id", item.itemId()); QSignalSpy spy(j.get(), &VoidJob::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); EXPECT_EQ(VoidJob::Status::Finished, j->status()); EXPECT_TRUE(j->isValid()); EXPECT_EQ(StorageError::Type::NoError, j->error().type()); EXPECT_EQ(VoidJob::Status::Finished, j->status()); } TEST_F(DeleteTest, no_such_item) { set_provider(unique_ptr(new MockProvider("delete_no_such_item"))); Item item; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); item = j->item(); } unique_ptr j(item.deleteItem()); EXPECT_TRUE(j->isValid()); EXPECT_EQ(VoidJob::Status::Loading, j->status()); EXPECT_EQ(StorageError::Type::NoError, j->error().type()); EXPECT_EQ("child_id", item.itemId()); QSignalSpy spy(j.get(), &VoidJob::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); EXPECT_EQ(VoidJob::Status::Error, j->status()); EXPECT_FALSE(j->isValid()); EXPECT_EQ(StorageError::Type::NotExists, j->error().type()); EXPECT_EQ("delete_item(): no such item: child_id", j->error().message()); } TEST_F(DeleteTest, delete_root) { set_provider(unique_ptr(new MockProvider)); Item item; { unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); item = j->item(); } unique_ptr j(item.deleteItem()); EXPECT_FALSE(j->isValid()); EXPECT_EQ(VoidJob::Status::Error, j->status()); EXPECT_EQ(StorageError::Type::LogicError, j->error().type()); // Signal must be received. QSignalSpy spy(j.get(), &VoidJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); auto arg = spy.takeFirst(); EXPECT_EQ(VoidJob::Status::Error, qvariant_cast(arg.at(0))); EXPECT_EQ("Item::deleteItem(): cannot delete root", j->error().message()); } TEST_F(DeleteTest, runtime_destroyed) { set_provider(unique_ptr(new MockProvider)); Item item; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); item = j->item(); } EXPECT_EQ(StorageError::Type::NoError, runtime_->shutdown().type()); // Destroy runtime. unique_ptr j(item.deleteItem()); EXPECT_FALSE(j->isValid()); EXPECT_EQ(VoidJob::Status::Error, j->status()); EXPECT_EQ(StorageError::Type::RuntimeDestroyed, j->error().type()); EXPECT_EQ("Item::deleteItem(): Runtime was destroyed previously", j->error().message()); // Signal must be received. QSignalSpy spy(j.get(), &VoidJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); auto arg = spy.takeFirst(); EXPECT_EQ(VoidJob::Status::Error, qvariant_cast(arg.at(0))); EXPECT_EQ("Item::deleteItem(): Runtime was destroyed previously", j->error().message()); } TEST_F(DeleteTest, runtime_destroyed_while_void_job_running) { set_provider(unique_ptr(new MockProvider("slow_delete"))); Item item; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); item = j->item(); } unique_ptr j(item.deleteItem()); EXPECT_TRUE(j->isValid()); EXPECT_EQ(VoidJob::Status::Loading, j->status()); EXPECT_EQ(StorageError::Type::NoError, j->error().type()); EXPECT_EQ(StorageError::Type::NoError, runtime_->shutdown().type()); // Destroy runtime. // Signal must be received. QSignalSpy spy(j.get(), &VoidJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); auto arg = spy.takeFirst(); EXPECT_EQ(VoidJob::Status::Error, qvariant_cast(arg.at(0))); EXPECT_EQ("Item::deleteItem(): Runtime was destroyed previously", j->error().message()); } TEST_F(DeleteTest, invalid_item) { Item i; unique_ptr j(i.deleteItem()); EXPECT_FALSE(j->isValid()); EXPECT_EQ(VoidJob::Status::Error, j->status()); EXPECT_EQ(StorageError::Type::LogicError, j->error().type()); EXPECT_EQ("Item::deleteItem(): cannot create job from invalid item", j->error().message()); // Signal must be received. QSignalSpy spy(j.get(), &VoidJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); EXPECT_EQ(VoidJob::Status::Error, qvariant_cast(arg.at(0))); EXPECT_EQ("Item::deleteItem(): cannot create job from invalid item", j->error().message()); } TEST_F(ItemTest, basic) { set_provider(unique_ptr(new MockProvider())); { // Default constructor. Item i; EXPECT_FALSE(i.isValid()); EXPECT_EQ("", i.itemId()); EXPECT_EQ("", i.name()); EXPECT_EQ("", i.etag()); EXPECT_EQ(Item::Type::File, i.type()); EXPECT_EQ(0, i.metadata().size()); EXPECT_EQ(0, i.sizeInBytes()); auto mtime = i.lastModifiedTime(); EXPECT_FALSE(mtime.isValid()); auto pids = i.parentIds(); EXPECT_EQ(0, pids.size()); } { unique_ptr j(acc_.get("child_id")); { QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); } Item i = j->item(); EXPECT_TRUE(i.isValid()); EXPECT_EQ("child_id", i.itemId()); EXPECT_EQ("Child", i.name()); EXPECT_EQ(10, i.sizeInBytes()); EXPECT_TRUE(i.account().isValid()); EXPECT_EQ("etag", i.etag()); EXPECT_EQ(Item::Type::File, i.type()); // Copy constructor Item i2(i); EXPECT_EQ(i, i2); // Move constructor Item i3(move(i2)); EXPECT_TRUE(i3.isValid()); EXPECT_EQ(i, i3); // Moved-from object must be invalid EXPECT_FALSE(i2.isValid()); // Moved-from object must be assignable j.reset(acc_.get("child_id")); { QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); } auto i4 = j->item(); i2 = i4; EXPECT_EQ(i4, i2); } { unique_ptr j1(acc_.get("child_id")); unique_ptr j2(acc_.get("root_id")); QSignalSpy spy1(j1.get(), &ItemJob::statusChanged); QSignalSpy spy2(j2.get(), &ItemJob::statusChanged); spy2.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy1.count()); auto i1 = j1->item(); auto i2 = j2->item(); // Copy assignment i1 = i2; EXPECT_TRUE(i2.isValid()); EXPECT_EQ(i2, i1); // Self-assignment i2 = i2; EXPECT_TRUE(i2.isValid()); EXPECT_EQ("root_id", i2.itemId()); EXPECT_EQ("Root", i2.name()); EXPECT_TRUE(i2.account().isValid()); EXPECT_EQ("etag", i2.etag()); EXPECT_EQ(Item::Type::Root, i2.type()); // Move assignment unique_ptr j3(acc_.get("child_folder_id")); QSignalSpy spy(j3.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy1.count()); auto i3 = j3->item(); i1 = move(i3); EXPECT_TRUE(i1.isValid()); EXPECT_EQ("child_folder_id", i1.itemId()); EXPECT_EQ("Child_Folder", i1.name()); EXPECT_EQ(i1.account(), i1.account()); EXPECT_EQ("etag", i1.etag()); EXPECT_EQ(Item::Type::Folder, i1.type()); // Moved-from object must be invalid EXPECT_FALSE(i3.isValid()); // Moved-from object must be assignable i3 = i2; EXPECT_EQ(i2, i3); } } TEST_F(ItemTest, comparison_and_hash) { set_provider(unique_ptr(new MockProvider)); { // Both items invalid. Item i1; Item i2; EXPECT_TRUE(i1 == i2); EXPECT_FALSE(i1 != i2); EXPECT_FALSE(i1 < i2); EXPECT_TRUE(i1 <= i2); EXPECT_FALSE(i1 > i2); EXPECT_TRUE(i1 >= i2); unordered_set(); // Just to show that this works. EXPECT_EQ(0u, hash()(i1)); EXPECT_EQ(0u, i1.hash()); EXPECT_EQ(0u, qHash(i1)); } { // i1 valid, i2 invalid unique_ptr j(acc_.roots()); QSignalSpy ready_spy(j.get(), &ItemListJob::itemsReady); ASSERT_TRUE(ready_spy.wait(SIGNAL_WAIT_TIME)); ASSERT_EQ(1, ready_spy.count()); auto arg = ready_spy.takeFirst(); auto items = qvariant_cast>(arg.at(0)); ASSERT_EQ(1, items.size()); auto i1 = items[0]; Item i2; EXPECT_FALSE(i1 == i2); EXPECT_TRUE(i1 != i2); EXPECT_FALSE(i1 < i2); EXPECT_FALSE(i1 <= i2); EXPECT_TRUE(i1 > i2); EXPECT_TRUE(i1 >= i2); // And with swapped operands: EXPECT_FALSE(i2 == i1); EXPECT_TRUE(i2 != i1); EXPECT_TRUE(i2 < i1); EXPECT_TRUE(i2 <= i1); EXPECT_FALSE(i2 > i1); EXPECT_FALSE(i2 >= i1); EXPECT_NE(0u, i1.hash()); EXPECT_NE(0u, qHash(i1)); } { // Both items valid with identical metadata, but different accounts (a1 < a2). auto a1 = runtime_->make_test_account(service_connection_->baseService(), object_path(), 1, "x", "x"); auto a2 = runtime_->make_test_account(service_connection_->baseService(), object_path(), 2, "x", "x"); Item i1; Item i2; { unique_ptr j(a1.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); i1 = j->item(); } { unique_ptr j(a2.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); i2 = j->item(); } ASSERT_EQ(i1.itemId(), i2.itemId()); EXPECT_FALSE(i1 == i2); EXPECT_TRUE(i1 != i2); EXPECT_TRUE(i1 < i2); EXPECT_TRUE(i1 <= i2); EXPECT_FALSE(i1 > i2); EXPECT_FALSE(i1 >= i2); // And with swapped operands: EXPECT_FALSE(i2 == i1); EXPECT_TRUE(i2 != i1); EXPECT_FALSE(i2 < i1); EXPECT_FALSE(i2 <= i1); EXPECT_TRUE(i2 > i1); EXPECT_TRUE(i2 >= i1); } { // Both items valid with identical metadata, but different instances, so we do deep comparison. Item i1; Item i2; { unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); i1 = j->item(); } { unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); i2 = j->item(); } EXPECT_TRUE(i1 == i2); EXPECT_FALSE(i1 != i2); EXPECT_FALSE(i1 < i2); EXPECT_TRUE(i1 <= i2); EXPECT_FALSE(i1 > i2); EXPECT_TRUE(i1 >= i2); // And with swapped operands: EXPECT_TRUE(i2 == i1); EXPECT_FALSE(i2 != i1); EXPECT_FALSE(i2 < i1); EXPECT_TRUE(i2 <= i1); EXPECT_FALSE(i2 > i1); EXPECT_TRUE(i2 >= i1); EXPECT_EQ(i1.hash(), i2.hash()); EXPECT_EQ(qHash(i1), qHash(i2)); } } TEST_F(ParentsTest, basic) { set_provider(unique_ptr(new MockProvider())); Item root; { unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); root = j->item(); } { // Getting parents from root does not call the provider and returns // no parents immediately. unique_ptr j(root.parents()); EXPECT_TRUE(j->isValid()); EXPECT_EQ(ItemListJob::Status::Finished, j->status()); EXPECT_EQ(StorageError::Type::NoError, j->error().type()); // Signal must be received. QSignalSpy spy(j.get(), &ItemListJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); EXPECT_EQ(ItemListJob::Status::Finished, qvariant_cast(arg.at(0))); } Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } QList parents; { unique_ptr j(child.parents()); EXPECT_TRUE(j->isValid()); EXPECT_EQ(ItemListJob::Status::Loading, j->status()); EXPECT_EQ(StorageError::Type::NoError, j->error().type()); QSignalSpy ready_spy(j.get(), &ItemListJob::itemsReady); QSignalSpy status_spy(j.get(), &ItemListJob::statusChanged); ready_spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, ready_spy.count()); auto list_arg = ready_spy.takeFirst(); parents = qvariant_cast>(list_arg.at(0)); // When the signal for the final item arrives, status must be Finished. EXPECT_EQ(ItemListJob::Status::Finished, j->status()); // Finished signal must be received. if (status_spy.count() == 0) { status_spy.wait(SIGNAL_WAIT_TIME); } ASSERT_EQ(1, status_spy.count()); auto status_arg = status_spy.takeFirst(); EXPECT_EQ(ItemListJob::Status::Finished, qvariant_cast(status_arg.at(0))); // Child must have one parent, namely the root. ASSERT_EQ(1, parents.size()); EXPECT_EQ(root, parents[0]); } } TEST_F(ParentsTest, two_parents) { set_provider(unique_ptr(new MockProvider("two_parents"))); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } QList parents; unique_ptr j(child.parents()); EXPECT_TRUE(j->isValid()); QSignalSpy ready_spy(j.get(), &ItemListJob::itemsReady); QSignalSpy status_spy(j.get(), &ItemListJob::statusChanged); ready_spy.wait(SIGNAL_WAIT_TIME); auto list_arg = ready_spy.takeFirst(); auto this_parent = qvariant_cast>(list_arg.at(0)); parents.append(this_parent); while (ready_spy.count() < 1) { ready_spy.wait(SIGNAL_WAIT_TIME); } list_arg = ready_spy.takeFirst(); this_parent = qvariant_cast>(list_arg.at(0)); parents.append(this_parent); // Finished signal must be received. if (status_spy.count() == 0) { status_spy.wait(SIGNAL_WAIT_TIME); } ASSERT_EQ(1, status_spy.count()); auto status_arg = status_spy.takeFirst(); EXPECT_EQ(ItemListJob::Status::Finished, qvariant_cast(status_arg.at(0))); // Child must have two parents. ASSERT_EQ(2, parents.size()); EXPECT_EQ("root_id", parents[0].itemId()); EXPECT_EQ("child_folder_id", parents[1].itemId()); } TEST_F(ParentsTest, two_parents_throw) { set_provider(unique_ptr(new MockProvider("two_parents_throw"))); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } QList parents; { unique_ptr j(child.parents()); EXPECT_TRUE(j->isValid()); QSignalSpy status_spy(j.get(), &ItemListJob::statusChanged); QSignalSpy ready_spy(j.get(), &ItemListJob::itemsReady); status_spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, status_spy.count()); auto status_arg = status_spy.takeFirst(); EXPECT_EQ(ItemListJob::Status::Error, qvariant_cast(status_arg.at(0))); EXPECT_EQ(StorageError::Type::ResourceError, j->error().type()); EXPECT_EQ("ResourceError: metadata(): weird error", j->error().errorString()); EXPECT_EQ(42, j->error().errorCode()); // We wait here to allow the error return for the second parent to arrive in MultiItemJobImpl. // This gives us coverage on the early return in the process_error lambda, when the job is // already in the error state. EXPECT_FALSE(ready_spy.wait(1000)); } } TEST_F(ParentsTest, invalid_item) { set_provider(unique_ptr(new MockProvider())); Item invalid; unique_ptr j(invalid.parents()); EXPECT_FALSE(j->isValid()); EXPECT_EQ(ItemListJob::Status::Error, j->status()); EXPECT_EQ(StorageError::Type::LogicError, j->error().type()); EXPECT_EQ("Item::parents(): cannot create job from invalid item", j->error().message()); // Signal must be received. QSignalSpy spy(j.get(), &ItemListJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); EXPECT_EQ(ItemListJob::Status::Error, qvariant_cast(arg.at(0))); } TEST_F(ParentsTest, runtime_destroyed) { set_provider(unique_ptr(new MockProvider())); Item root; { unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); root = j->item(); } EXPECT_EQ(StorageError::Type::NoError, runtime_->shutdown().type()); // Destroy runtime. unique_ptr j(root.parents()); EXPECT_FALSE(j->isValid()); EXPECT_EQ(ItemListJob::Status::Error, j->status()); EXPECT_EQ(StorageError::Type::RuntimeDestroyed, j->error().type()); EXPECT_EQ("Item::parents(): Runtime was destroyed previously", j->error().message()); // Signal must be received. QSignalSpy spy(j.get(), &ItemListJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); auto arg = spy.takeFirst(); EXPECT_EQ(ItemListJob::Status::Error, qvariant_cast(arg.at(0))); EXPECT_EQ("Item::parents(): Runtime was destroyed previously", j->error().message()); } TEST_F(ParentsTest, runtime_destroyed_while_item_list_job_running) { set_provider(unique_ptr(new MockProvider("slow_metadata"))); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr j(child.parents()); EXPECT_EQ(StorageError::Type::NoError, runtime_->shutdown().type()); // Destroy runtime, provider still sleeping // Signal must be received. QSignalSpy spy(j.get(), &ItemListJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); EXPECT_EQ(ItemListJob::Status::Error, qvariant_cast(arg.at(0))); EXPECT_EQ("Item::parents(): Runtime was destroyed previously", j->error().message()); } TEST_F(ParentsTest, bad_metadata) { set_provider(unique_ptr(new MockProvider("bad_parent_metadata_from_child"))); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } { unique_ptr j(child.parents()); EXPECT_TRUE(j->isValid()); EXPECT_EQ(ItemListJob::Status::Loading, j->status()); EXPECT_EQ(StorageError::Type::NoError, j->error().type()); QSignalSpy spy(j.get(), &ItemListJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); EXPECT_EQ(ItemListJob::Status::Error, qvariant_cast(arg.at(0))); EXPECT_EQ("Item::parents(): provider returned a file as a parent (id = root_id)", j->error().message()); } } TEST_F(CopyTest, basic) { set_provider(unique_ptr(new MockProvider())); Item root; { unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); root = j->item(); } Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr j(child.copy(root, "copied_item")); EXPECT_TRUE(j->isValid()); EXPECT_EQ(ItemJob::Status::Loading, j->status()); EXPECT_EQ(StorageError::Type::NoError, j->error().type()); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); auto status = qvariant_cast(arg.at(0)); EXPECT_EQ(ItemJob::Status::Finished, status); EXPECT_TRUE(j->isValid()); EXPECT_EQ(ItemJob::Status::Finished, j->status()); EXPECT_EQ(StorageError::Type::NoError, j->error().type()); auto copied_file = j->item(); EXPECT_EQ("new_item_id", copied_file.itemId()); EXPECT_EQ(root.itemId(), copied_file.parentIds()[0]); EXPECT_EQ("copied_item", copied_file.name()); } TEST_F(CopyTest, invalid) { set_provider(unique_ptr(new MockProvider())); Item root; { unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); root = j->item(); } Item child; unique_ptr j(child.copy(root, "copied_item")); EXPECT_FALSE(j->isValid()); EXPECT_EQ(ItemJob::Status::Error, j->status()); EXPECT_EQ(StorageError::Type::LogicError, j->error().type()); EXPECT_EQ("LogicError", j->error().name()); EXPECT_EQ("LogicError: Item::copy(): cannot create job from invalid item", j->error().errorString()); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); auto status = qvariant_cast(arg.at(0)); EXPECT_EQ(ItemJob::Status::Error, status); EXPECT_FALSE(j->isValid()); EXPECT_EQ(ItemJob::Status::Error, j->status()); EXPECT_EQ(StorageError::Type::LogicError, j->error().type()); EXPECT_EQ("LogicError", j->error().name()); EXPECT_EQ("LogicError: Item::copy(): cannot create job from invalid item", j->error().errorString()); } TEST_F(CopyTest, invalid_parent) { set_provider(unique_ptr(new MockProvider())); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } Item invalid_parent; unique_ptr j(child.copy(invalid_parent, "copied_item")); EXPECT_FALSE(j->isValid()); EXPECT_EQ(ItemJob::Status::Error, j->status()); EXPECT_EQ(StorageError::Type::InvalidArgument, j->error().type()); EXPECT_EQ("InvalidArgument: Item::copy(): newParent is invalid", j->error().errorString()); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); auto status = qvariant_cast(arg.at(0)); EXPECT_EQ(ItemJob::Status::Error, status); EXPECT_FALSE(j->isValid()); EXPECT_EQ(ItemJob::Status::Error, j->status()); EXPECT_EQ(StorageError::Type::InvalidArgument, j->error().type()); EXPECT_EQ("InvalidArgument: Item::copy(): newParent is invalid", j->error().errorString()); } TEST_F(CopyTest, empty_name) { set_provider(unique_ptr(new MockProvider())); Item root; { unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); root = j->item(); } Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr j(child.copy(root, "")); EXPECT_FALSE(j->isValid()); EXPECT_EQ(ItemJob::Status::Error, j->status()); EXPECT_EQ(StorageError::Type::InvalidArgument, j->error().type()); EXPECT_EQ("InvalidArgument: Item::copy(): newName cannot be empty", j->error().errorString()); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); auto status = qvariant_cast(arg.at(0)); EXPECT_EQ(ItemJob::Status::Error, status); EXPECT_FALSE(j->isValid()); EXPECT_EQ(ItemJob::Status::Error, j->status()); EXPECT_EQ(StorageError::Type::InvalidArgument, j->error().type()); EXPECT_EQ("InvalidArgument: Item::copy(): newName cannot be empty", j->error().errorString()); } TEST_F(CopyTest, wrong_account) { set_provider(unique_ptr(new MockProvider())); auto acc1 = runtime_->make_test_account(service_connection_->baseService(), object_path(), 1); auto acc2 = runtime_->make_test_account(service_connection_->baseService(), object_path(), 2); Item root1; { unique_ptr j(acc1.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); root1 = j->item(); EXPECT_TRUE(root1.isValid()); } Item root2; { unique_ptr j(acc2.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); root2 = j->item(); EXPECT_TRUE(root2.isValid()); } Item child; { unique_ptr j(acc2.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); EXPECT_TRUE(child.isValid()); } unique_ptr j(child.copy(root1, "copied_Item")); EXPECT_FALSE(j->isValid()); EXPECT_EQ(ItemJob::Status::Error, j->status()); EXPECT_EQ(StorageError::Type::LogicError, j->error().type()); EXPECT_EQ("LogicError: Item::copy(): source and target must belong to the same account", j->error().errorString()); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); auto status = qvariant_cast(arg.at(0)); EXPECT_EQ(ItemJob::Status::Error, status); EXPECT_FALSE(j->isValid()); EXPECT_EQ(ItemJob::Status::Error, j->status()); EXPECT_EQ(StorageError::Type::LogicError, j->error().type()); EXPECT_EQ("LogicError: Item::copy(): source and target must belong to the same account", j->error().errorString()); } TEST_F(CopyTest, wrong_type) { set_provider(unique_ptr(new MockProvider())); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr j(child.copy(child, "copied_Item")); EXPECT_FALSE(j->isValid()); EXPECT_EQ(ItemJob::Status::Error, j->status()); EXPECT_EQ(StorageError::Type::LogicError, j->error().type()); EXPECT_EQ("LogicError: Item::copy(): newParent cannot be a file", j->error().errorString()); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); auto status = qvariant_cast(arg.at(0)); EXPECT_EQ(ItemJob::Status::Error, status); EXPECT_FALSE(j->isValid()); EXPECT_EQ(ItemJob::Status::Error, j->status()); EXPECT_EQ(StorageError::Type::LogicError, j->error().type()); EXPECT_EQ("LogicError: Item::copy(): newParent cannot be a file", j->error().errorString()); } TEST_F(CopyTest, type_mismatch) { set_provider(unique_ptr(new MockProvider("copy_type_mismatch"))); Item root; { unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); root = j->item(); } Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr j(child.copy(root, "copied_Item")); EXPECT_TRUE(j->isValid()); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); auto status = qvariant_cast(arg.at(0)); EXPECT_EQ(ItemJob::Status::Error, status); EXPECT_FALSE(j->isValid()); EXPECT_EQ(ItemJob::Status::Error, j->status()); EXPECT_EQ(StorageError::Type::LocalCommsError, j->error().type()); EXPECT_EQ("LocalCommsError: Item::copy()provider error: source and target item type differ " "(source id = child_id, target id = new_item_id)", j->error().errorString()); } TEST_F(MoveTest, basic) { set_provider(unique_ptr(new MockProvider())); Item root; { unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); root = j->item(); } Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr j(child.move(root, "moved_item")); EXPECT_TRUE(j->isValid()); EXPECT_EQ(ItemJob::Status::Loading, j->status()); EXPECT_EQ(StorageError::Type::NoError, j->error().type()); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); auto status = qvariant_cast(arg.at(0)); EXPECT_EQ(ItemJob::Status::Finished, status); EXPECT_TRUE(j->isValid()); EXPECT_EQ(ItemJob::Status::Finished, j->status()); EXPECT_EQ(StorageError::Type::NoError, j->error().type()); auto moved_file = j->item(); EXPECT_EQ("child_id", moved_file.itemId()); EXPECT_EQ(root.itemId(), moved_file.parentIds()[0]); EXPECT_EQ("moved_item", moved_file.name()); } TEST_F(MoveTest, invalid) { set_provider(unique_ptr(new MockProvider())); Item root; { unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); root = j->item(); } Item child; unique_ptr j(child.move(root, "moved_item")); EXPECT_FALSE(j->isValid()); EXPECT_EQ(ItemJob::Status::Error, j->status()); EXPECT_EQ(StorageError::Type::LogicError, j->error().type()); EXPECT_EQ("LogicError", j->error().name()); EXPECT_EQ("LogicError: Item::move(): cannot create job from invalid item", j->error().errorString()); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); auto status = qvariant_cast(arg.at(0)); EXPECT_EQ(ItemJob::Status::Error, status); EXPECT_FALSE(j->isValid()); EXPECT_EQ(ItemJob::Status::Error, j->status()); EXPECT_EQ(StorageError::Type::LogicError, j->error().type()); EXPECT_EQ("LogicError", j->error().name()); EXPECT_EQ("LogicError: Item::move(): cannot create job from invalid item", j->error().errorString()); } TEST_F(MoveTest, root_returned) { set_provider(unique_ptr(new MockProvider("move_returns_root"))); Item root; { unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); root = j->item(); } Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr j(child.move(root, "moved_item")); EXPECT_TRUE(j->isValid()); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); auto status = qvariant_cast(arg.at(0)); EXPECT_EQ(ItemJob::Status::Error, status); EXPECT_FALSE(j->isValid()); EXPECT_EQ(ItemJob::Status::Error, j->status()); EXPECT_EQ(StorageError::Type::LocalCommsError, j->error().type()); EXPECT_EQ("LocalCommsError", j->error().name()); EXPECT_EQ("LocalCommsError: Item::move(): impossible root item returned by provider (id = root_id)", j->error().errorString()); } TEST_F(MoveTest, type_mismatch) { set_provider(unique_ptr(new MockProvider("move_type_mismatch"))); Item root; { unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); root = j->item(); } Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr j(child.move(root, "moved_Item")); EXPECT_TRUE(j->isValid()); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); auto status = qvariant_cast(arg.at(0)); EXPECT_EQ(ItemJob::Status::Error, status); EXPECT_FALSE(j->isValid()); EXPECT_EQ(ItemJob::Status::Error, j->status()); EXPECT_EQ(StorageError::Type::LocalCommsError, j->error().type()); EXPECT_EQ("LocalCommsError: Item::move(): provider error: source and target item type differ " "(source id = child_id, target id = child_id)", j->error().errorString()); } TEST_F(LookupTest, basic) { set_provider(unique_ptr(new MockProvider())); Item root; { unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); root = j->item(); } unique_ptr j(root.lookup("Child")); EXPECT_TRUE(j->isValid()); EXPECT_EQ(ItemListJob::Status::Loading, j->status()); EXPECT_EQ(StorageError::Type::NoError, j->error().type()); QSignalSpy ready_spy(j.get(), &ItemListJob::itemsReady); QSignalSpy status_spy(j.get(), &ItemListJob::statusChanged); status_spy.wait(SIGNAL_WAIT_TIME); auto list_arg = ready_spy.takeFirst(); auto list = qvariant_cast>(list_arg.at(0)); ASSERT_EQ(1, list.size()); auto child = list[0]; ASSERT_EQ("child_id", child.itemId()); ASSERT_EQ("Child", child.name()); auto arg = status_spy.takeFirst(); auto status = qvariant_cast(arg.at(0)); EXPECT_EQ(ItemListJob::Status::Finished, status); EXPECT_TRUE(j->isValid()); EXPECT_EQ(ItemListJob::Status::Finished, j->status()); EXPECT_EQ(StorageError::Type::NoError, j->error().type()); } TEST_F(LookupTest, invalid) { set_provider(unique_ptr(new MockProvider())); Item root; unique_ptr j(root.lookup("Child")); EXPECT_FALSE(j->isValid()); EXPECT_EQ(ItemListJob::Status::Error, j->status()); EXPECT_EQ(StorageError::Type::LogicError, j->error().type()); EXPECT_EQ("LogicError: Item::lookup(): cannot create job from invalid item", j->error().errorString()); // Signal must be received. QSignalSpy spy(j.get(), &ItemListJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); EXPECT_EQ(ItemListJob::Status::Error, qvariant_cast(arg.at(0))); EXPECT_EQ("LogicError: Item::lookup(): cannot create job from invalid item", j->error().errorString()); } TEST_F(LookupTest, wrong_type) { set_provider(unique_ptr(new MockProvider())); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr j(child.lookup("Child")); EXPECT_FALSE(j->isValid()); EXPECT_EQ(ItemListJob::Status::Error, j->status()); EXPECT_EQ(StorageError::Type::LogicError, j->error().type()); EXPECT_EQ("LogicError: Item::lookup(): cannot perform lookup on a file", j->error().errorString()); // Signal must be received. QSignalSpy spy(j.get(), &ItemListJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); EXPECT_EQ(ItemListJob::Status::Error, qvariant_cast(arg.at(0))); EXPECT_EQ("LogicError: Item::lookup(): cannot perform lookup on a file", j->error().errorString()); } TEST_F(CreateFolderTest, basic) { set_provider(unique_ptr(new MockProvider())); Item root; { unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); root = j->item(); } unique_ptr j(root.createFolder("new_folder")); EXPECT_TRUE(j->isValid()); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); auto status = qvariant_cast(arg.at(0)); EXPECT_EQ(ItemJob::Status::Finished, status); EXPECT_TRUE(j->isValid()); EXPECT_EQ(ItemJob::Status::Finished, j->status()); EXPECT_EQ(StorageError::Type::NoError, j->error().type()); auto new_folder = j->item(); EXPECT_EQ("new_folder", new_folder.name()); EXPECT_EQ(Item::Type::Folder, new_folder.type()); } TEST_F(CreateFolderTest, invalid) { set_provider(unique_ptr(new MockProvider())); Item root; unique_ptr j(root.createFolder("new_folder")); EXPECT_FALSE(j->isValid()); EXPECT_EQ(ItemJob::Status::Error, j->status()); EXPECT_EQ(StorageError::Type::LogicError, j->error().type()); EXPECT_EQ("LogicError: Item::createFolder(): cannot create job from invalid item", j->error().errorString()); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); auto status = qvariant_cast(arg.at(0)); EXPECT_EQ(ItemJob::Status::Error, status); EXPECT_EQ("LogicError: Item::createFolder(): cannot create job from invalid item", j->error().errorString()); } TEST_F(CreateFolderTest, wrong_type) { set_provider(unique_ptr(new MockProvider())); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr j(child.createFolder("new_folder")); EXPECT_FALSE(j->isValid()); EXPECT_EQ(ItemJob::Status::Error, j->status()); EXPECT_EQ(StorageError::Type::LogicError, j->error().type()); EXPECT_EQ("LogicError: Item::createFolder(): cannot create a folder with a file as the parent", j->error().errorString()); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); auto status = qvariant_cast(arg.at(0)); EXPECT_EQ(ItemJob::Status::Error, status); EXPECT_EQ("LogicError: Item::createFolder(): cannot create a folder with a file as the parent", j->error().errorString()); } TEST_F(CreateFolderTest, wrong_return_type) { set_provider(unique_ptr(new MockProvider("create_folder_returns_file"))); Item root; { unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); root = j->item(); } unique_ptr j(root.createFolder("new_folder")); EXPECT_TRUE(j->isValid()); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); auto status = qvariant_cast(arg.at(0)); EXPECT_EQ(ItemJob::Status::Error, status); EXPECT_EQ("LocalCommsError: Item::createFolder(): impossible file item returned by provider (id = new_folder_id)", j->error().errorString()); } TEST_F(ListTest, basic) { set_provider(unique_ptr(new MockProvider())); Item root; { unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); root = j->item(); } unique_ptr j(root.list()); EXPECT_TRUE(j->isValid()); EXPECT_EQ(ItemListJob::Status::Loading, j->status()); EXPECT_EQ(StorageError::Type::NoError, j->error().type()); QSignalSpy ready_spy(j.get(), &ItemListJob::itemsReady); QSignalSpy status_spy(j.get(), &ItemListJob::statusChanged); ready_spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, ready_spy.count()); auto list_arg = ready_spy.takeFirst(); auto items = qvariant_cast>(list_arg.at(0)); ASSERT_EQ(1, items.size()); EXPECT_EQ("child_id", items[0].itemId()); // When the signal for the final item arrives, status must be Finished. EXPECT_EQ(ItemListJob::Status::Finished, j->status()); // Finished signal must be received. if (status_spy.count() == 0) { status_spy.wait(SIGNAL_WAIT_TIME); } ASSERT_EQ(1, status_spy.count()); auto status_arg = status_spy.takeFirst(); EXPECT_EQ(ItemListJob::Status::Finished, qvariant_cast(status_arg.at(0))); } TEST_F(ListTest, empty_list) { set_provider(unique_ptr(new MockProvider("list_empty"))); Item root; { unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); root = j->item(); } unique_ptr j(root.list()); EXPECT_TRUE(j->isValid()); EXPECT_EQ(ItemListJob::Status::Loading, j->status()); EXPECT_EQ(StorageError::Type::NoError, j->error().type()); QSignalSpy ready_spy(j.get(), &ItemListJob::itemsReady); QSignalSpy status_spy(j.get(), &ItemListJob::statusChanged); status_spy.wait(SIGNAL_WAIT_TIME); if (ready_spy.count() == 0) { ASSERT_TRUE(ready_spy.wait(SIGNAL_WAIT_TIME)); } EXPECT_EQ(ItemListJob::Status::Finished, j->status()); ASSERT_EQ(1, status_spy.count()); auto status_arg = status_spy.takeFirst(); EXPECT_EQ(ItemListJob::Status::Finished, qvariant_cast(status_arg.at(0))); } TEST_F(ListTest, two_children) { set_provider(unique_ptr(new MockProvider("list_two_children"))); Item root; { unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); root = j->item(); } unique_ptr j(root.list()); EXPECT_TRUE(j->isValid()); EXPECT_EQ(ItemListJob::Status::Loading, j->status()); EXPECT_EQ(StorageError::Type::NoError, j->error().type()); QSignalSpy ready_spy(j.get(), &ItemListJob::itemsReady); QSignalSpy status_spy(j.get(), &ItemListJob::statusChanged); QList items; ready_spy.wait(SIGNAL_WAIT_TIME); auto list_arg = ready_spy.takeFirst(); auto this_item = qvariant_cast>(list_arg.at(0)); items.append(this_item); if (ready_spy.count() < 1) { ASSERT_TRUE(ready_spy.wait(SIGNAL_WAIT_TIME)); } list_arg = ready_spy.takeFirst(); this_item = qvariant_cast>(list_arg.at(0)); items.append(this_item); // Finished signal must be received. if (status_spy.count() == 0) { status_spy.wait(SIGNAL_WAIT_TIME); } ASSERT_EQ(1, status_spy.count()); auto status_arg = status_spy.takeFirst(); EXPECT_EQ(ItemListJob::Status::Finished, qvariant_cast(status_arg.at(0))); // Must have two children. ASSERT_EQ(2, items.size()); EXPECT_EQ("child_id", items[0].itemId()); EXPECT_EQ("child2_id", items[1].itemId()); } TEST_F(ListTest, job_out_of_scope) { set_provider(unique_ptr(new MockProvider("list_slow"))); Item root; { unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); root = j->item(); } unique_ptr j(root.list()); // Just to show that it's safe to drop the job on the floor while // operation is in progress. } TEST_F(ListTest, invalid) { set_provider(unique_ptr(new MockProvider())); Item root; unique_ptr j(root.list()); EXPECT_FALSE(j->isValid()); EXPECT_EQ(ItemListJob::Status::Error, j->status()); EXPECT_EQ(StorageError::Type::LogicError, j->error().type()); EXPECT_EQ("LogicError: Item::list(): cannot create job from invalid item", j->error().errorString()); QSignalSpy spy(j.get(), &ItemListJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); auto status = qvariant_cast(arg.at(0)); EXPECT_EQ(ItemListJob::Status::Error, status); EXPECT_EQ("LogicError: Item::list(): cannot create job from invalid item", j->error().errorString()); } TEST_F(ListTest, wrong_type) { set_provider(unique_ptr(new MockProvider())); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr j(child.list()); EXPECT_FALSE(j->isValid()); EXPECT_EQ(ItemListJob::Status::Error, j->status()); EXPECT_EQ(StorageError::Type::LogicError, j->error().type()); EXPECT_EQ("LogicError: Item::list(): cannot perform list on a file", j->error().errorString()); QSignalSpy spy(j.get(), &ItemListJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); auto status = qvariant_cast(arg.at(0)); EXPECT_EQ(ItemListJob::Status::Error, status); EXPECT_EQ("LogicError: Item::list(): cannot perform list on a file", j->error().errorString()); } TEST_F(ListTest, wrong_return_type) { set_provider(unique_ptr(new MockProvider("list_return_root"))); Item root; { unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); root = j->item(); } unique_ptr j(root.list()); EXPECT_TRUE(j->isValid()); EXPECT_EQ(ItemListJob::Status::Loading, j->status()); EXPECT_EQ(StorageError::Type::NoError, j->error().type()); QSignalSpy spy(j.get(), &ItemListJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); EXPECT_EQ(ItemListJob::Status::Error, qvariant_cast(arg.at(0))); EXPECT_EQ("LocalCommsError: Item::list(): impossible root item returned by provider (id = child_id)", j->error().errorString()); } TEST_F(ListTest, runtime_destroyed_while_item_list_job_running) { set_provider(unique_ptr(new MockProvider("list_slow"))); Item root; { unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); root = j->item(); } unique_ptr j(root.list()); EXPECT_EQ(StorageError::Type::NoError, runtime_->shutdown().type()); // Destroy runtime, provider still sleeping // Signal must be received. QSignalSpy spy(j.get(), &ItemListJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); EXPECT_EQ(ItemListJob::Status::Error, qvariant_cast(arg.at(0))); EXPECT_EQ("Item::list(): Runtime was destroyed previously", j->error().message()); } TEST_F(ListTest, no_permission) { set_provider(unique_ptr(new MockProvider("list_no_permission"))); Item root; { unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); root = j->item(); } unique_ptr j(root.list()); EXPECT_TRUE(j->isValid()); // Signal must be received. QSignalSpy spy(j.get(), &ItemListJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); EXPECT_EQ(ItemListJob::Status::Error, qvariant_cast(arg.at(0))); EXPECT_EQ("permission denied", j->error().message()); EXPECT_EQ(StorageError::Type::PermissionDenied, j->error().type()); } TEST_F(DownloadTest, basic) { set_provider(unique_ptr(new MockProvider())); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr downloader(child.createDownloader(Item::ConflictPolicy::IgnoreConflict)); EXPECT_TRUE(downloader->isValid()); EXPECT_EQ(Downloader::Status::Loading, downloader->status()); EXPECT_EQ(StorageError::NoError, downloader->error().type()); EXPECT_EQ(child, downloader->item()); QSignalSpy status_spy(downloader.get(), &Downloader::statusChanged); { QSignalSpy read_spy(downloader.get(), &Downloader::readyRead); ASSERT_TRUE(status_spy.wait(SIGNAL_WAIT_TIME)); auto arg = status_spy.takeFirst(); EXPECT_EQ(Downloader::Status::Ready, qvariant_cast(arg.at(0))); if (read_spy.count() != 1) { read_spy.wait(SIGNAL_WAIT_TIME); } } EXPECT_EQ(11, downloader->bytesAvailable()); EXPECT_EQ(0, downloader->bytesToWrite()); EXPECT_EQ(-1, downloader->write("a", 1)); EXPECT_FALSE(downloader->waitForBytesWritten(1)); EXPECT_FALSE(downloader->waitForReadyRead(1)); auto data = downloader->readAll(); EXPECT_EQ(QByteArray("Hello world", -1), data); downloader->close(); ASSERT_TRUE(status_spy.wait(SIGNAL_WAIT_TIME)); auto arg = status_spy.takeFirst(); EXPECT_EQ(Downloader::Status::Finished, qvariant_cast(arg.at(0))); } // TODO: This leaks: // ==4645== 1,369 (272 direct, 1,097 indirect) bytes in 1 blocks are definitely lost in loss record 193 of 203 // ==4645== at 0x4C2E0EF: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) // ==4645== by 0x4FD9D0A: boost::promise::promise() (future.hpp:2309) // ==4645== by 0x4FDA6F2: boost::make_ready_future() (future.hpp:3935) // ==4645== by 0x4FD96B0: lomiri::storage::provider::internal::DownloadJobImpl::cancel(lomiri::storage::provider::DownloadJob&) (DownloadJobImpl.cpp:161) // ==4645== by 0x4FEE198: void lomiri::storage::provider::internal::PendingJobs::cancel_job(std::shared_ptr const&, std::__cxx11::basic_string, std::allocator > const&) (PendingJobs.cpp:181) // ==4645== by 0x4FEA3BF: lomiri::storage::provider::internal::PendingJobs::~PendingJobs() (PendingJobs.cpp:55) // ==4645== by 0x4FEA6CD: lomiri::storage::provider::internal::PendingJobs::~PendingJobs() (PendingJobs.cpp:61) // ==4645== by 0x4F8E927: std::default_delete::operator()(lomiri::storage::provider::internal::PendingJobs*) const (unique_ptr.h:76) // ==4645== by 0x4F8D85B: std::unique_ptr >::~unique_ptr() (unique_ptr.h:236) // ==4645== by 0x4F89449: lomiri::storage::provider::internal::AccountData::~AccountData() (AccountData.h:51) // ==4645== by 0x504D9A6: void __gnu_cxx::new_allocator::destroy(lomiri::storage::provider::internal::AccountData*) (new_allocator.h:124) // ==4645== by 0x504D8AA: void std::allocator_traits >::destroy(std::allocator&, lomiri::storage::provider::internal::AccountData*) (alloc_traits.h:542) TEST_F(DownloadTest, abandoned) { set_provider(unique_ptr(new MockProvider())); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr downloader(child.createDownloader(Item::ConflictPolicy::IgnoreConflict)); EXPECT_TRUE(downloader->isValid()); { QSignalSpy spy(downloader.get(), &Downloader::statusChanged); spy.wait(SIGNAL_WAIT_TIME); auto arg = spy.takeFirst(); EXPECT_EQ(Downloader::Status::Ready, qvariant_cast(arg.at(0))); } EXPECT_TRUE(downloader->waitForReadyRead(SIGNAL_WAIT_TIME)); } TEST_F(DownloadTest, runtime_destroyed) { set_provider(unique_ptr(new MockProvider())); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } EXPECT_EQ(StorageError::Type::NoError, runtime_->shutdown().type()); unique_ptr downloader(child.createDownloader(Item::ConflictPolicy::IgnoreConflict)); EXPECT_FALSE(downloader->isValid()); EXPECT_EQ(Downloader::Status::Error, downloader->status()); EXPECT_EQ(StorageError::RuntimeDestroyed, downloader->error().type()); EXPECT_EQ("RuntimeDestroyed: Item::createDownloader(): Runtime was destroyed previously", downloader->error().errorString()); EXPECT_EQ(Item(), downloader->item()); // Signal must arrive. { QSignalSpy spy(downloader.get(), &Downloader::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Downloader::Status::Error, qvariant_cast(arg.at(0))); } } TEST_F(DownloadTest, runtime_destroyed_while_download_running) { set_provider(unique_ptr(new MockProvider("download_slow"))); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr downloader(child.createDownloader(Item::ConflictPolicy::IgnoreConflict)); EXPECT_TRUE(downloader->isValid()); EXPECT_EQ(StorageError::Type::NoError, runtime_->shutdown().type()); // Destroy runtime, provider still sleeping // Signal must arrive. { QSignalSpy spy(downloader.get(), &Downloader::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Downloader::Status::Error, qvariant_cast(arg.at(0))); } EXPECT_FALSE(downloader->isValid()); EXPECT_EQ(Downloader::Status::Error, downloader->status()); EXPECT_EQ(StorageError::RuntimeDestroyed, downloader->error().type()); EXPECT_EQ("RuntimeDestroyed: Item::createDownloader(): Runtime was destroyed previously", downloader->error().errorString()); EXPECT_EQ(Item(), downloader->item()); } TEST_F(DownloadTest, download_error) { set_provider(unique_ptr(new MockProvider("download_error"))); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr downloader(child.createDownloader(Item::ConflictPolicy::IgnoreConflict)); EXPECT_TRUE(downloader->isValid()); // Signal must arrive. { QSignalSpy spy(downloader.get(), &Downloader::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Downloader::Status::Error, qvariant_cast(arg.at(0))); } EXPECT_FALSE(downloader->isValid()); EXPECT_EQ(Downloader::Status::Error, downloader->status()); EXPECT_EQ(StorageError::ResourceError, downloader->error().type()); EXPECT_EQ("ResourceError: test error", downloader->error().errorString()); EXPECT_EQ(42, downloader->error().errorCode()); EXPECT_EQ(Item(), downloader->item()); // For coverage: call close() while in the Error state. { QSignalSpy spy(downloader.get(), &Downloader::statusChanged); downloader->close(); EXPECT_FALSE(spy.wait(1000)); } } TEST_F(DownloadTest, finish_too_soon) { set_provider(unique_ptr(new MockProvider())); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr downloader(child.createDownloader(Item::ConflictPolicy::IgnoreConflict)); EXPECT_TRUE(downloader->isValid()); QSignalSpy spy(downloader.get(), &Downloader::statusChanged); downloader->close(); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); EXPECT_EQ(Downloader::Status::Error, qvariant_cast(arg.at(0))); EXPECT_FALSE(downloader->isValid()); EXPECT_EQ(Downloader::Status::Error, downloader->status()); EXPECT_EQ(StorageError::LogicError, downloader->error().type()); EXPECT_EQ("LogicError: Downloader::close(): cannot finalize while Downloader is not in the Ready state", downloader->error().errorString()); } TEST_F(DownloadTest, finish_runtime_destroyed) { set_provider(unique_ptr(new MockProvider())); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr downloader(child.createDownloader(Item::ConflictPolicy::IgnoreConflict)); EXPECT_TRUE(downloader->isValid()); { QSignalSpy spy(downloader.get(), &Downloader::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Downloader::Status::Ready, qvariant_cast(arg.at(0))); } QSignalSpy spy(downloader.get(), &Downloader::statusChanged); EXPECT_EQ(StorageError::Type::NoError, runtime_->shutdown().type()); // Destroy runtime downloader->close(); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); EXPECT_EQ(Downloader::Status::Error, qvariant_cast(arg.at(0))); EXPECT_FALSE(downloader->isValid()); EXPECT_EQ(Downloader::Status::Error, downloader->status()); EXPECT_EQ(StorageError::RuntimeDestroyed, downloader->error().type()); EXPECT_EQ("Downloader::close(): Runtime was destroyed previously", downloader->error().message()); } TEST_F(DownloadTest, finish_runtime_destroyed_while_reply_outstanding) { set_provider(unique_ptr(new MockProvider("finish_download_slow"))); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr downloader(child.createDownloader(Item::ConflictPolicy::IgnoreConflict)); EXPECT_TRUE(downloader->isValid()); { QSignalSpy spy(downloader.get(), &Downloader::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Downloader::Status::Ready, qvariant_cast(arg.at(0))); } QSignalSpy spy(downloader.get(), &Downloader::statusChanged); downloader->close(); EXPECT_EQ(StorageError::Type::NoError, runtime_->shutdown().type()); // Destroy runtime, provider still sleeping ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Downloader::Status::Error, qvariant_cast(arg.at(0))); EXPECT_FALSE(downloader->isValid()); EXPECT_EQ(Downloader::Status::Error, downloader->status()); EXPECT_EQ(StorageError::RuntimeDestroyed, downloader->error().type()); EXPECT_EQ("Downloader::close(): Runtime was destroyed previously", downloader->error().message()); } TEST_F(DownloadTest, finish_twice) { set_provider(unique_ptr(new MockProvider())); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr downloader(child.createDownloader(Item::ConflictPolicy::IgnoreConflict)); EXPECT_TRUE(downloader->isValid()); { QSignalSpy spy(downloader.get(), &Downloader::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Downloader::Status::Ready, qvariant_cast(arg.at(0))); } QSignalSpy spy(downloader.get(), &Downloader::statusChanged); downloader->close(); downloader->close(); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Downloader::Status::Finished, qvariant_cast(arg.at(0))); EXPECT_EQ(Downloader::Status::Finished, downloader->status()); } TEST_F(DownloadTest, finish_error) { set_provider(unique_ptr(new MockProvider("finish_download_error"))); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr downloader(child.createDownloader(Item::ConflictPolicy::IgnoreConflict)); EXPECT_TRUE(downloader->isValid()); { QSignalSpy spy(downloader.get(), &Downloader::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Downloader::Status::Ready, qvariant_cast(arg.at(0))); } QSignalSpy spy(downloader.get(), &Downloader::statusChanged); downloader->close(); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Downloader::Status::Error, qvariant_cast(arg.at(0))); EXPECT_FALSE(downloader->isValid()); EXPECT_EQ(Downloader::Status::Error, downloader->status()); EXPECT_EQ(StorageError::NotExists, downloader->error().type()); EXPECT_EQ("no such item", downloader->error().message()); EXPECT_EQ("item_id", downloader->error().itemId()); EXPECT_EQ("item_id", downloader->error().itemName()); EXPECT_EQ("NotExists", downloader->error().name()); } TEST_F(DownloadTest, wrong_type) { set_provider(unique_ptr(new MockProvider())); Item root; { unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); root = j->item(); } unique_ptr downloader(root.createDownloader(Item::ConflictPolicy::IgnoreConflict)); EXPECT_FALSE(downloader->isValid()); EXPECT_EQ(Downloader::Status::Error, downloader->status()); EXPECT_EQ(StorageError::Type::LogicError, downloader->error().type()); EXPECT_EQ("Item::createDownloader(): cannot download a folder", downloader->error().message()); { QSignalSpy spy(downloader.get(), &Downloader::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Downloader::Status::Error, qvariant_cast(arg.at(0))); } } TEST_F(DownloadTest, conflict) { set_provider(unique_ptr(new MockProvider())); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr downloader(child.createDownloader(Item::ConflictPolicy::ErrorIfConflict)); EXPECT_TRUE(downloader->isValid()); { QSignalSpy spy(downloader.get(), &Downloader::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Downloader::Status::Error, qvariant_cast(arg.at(0))); } EXPECT_EQ(Downloader::Status::Error, downloader->status()); EXPECT_EQ(StorageError::Type::Conflict, downloader->error().type()); EXPECT_EQ("download(): etag mismatch", downloader->error().message()); } TEST_F(DownloadTest, cancel) { set_provider(unique_ptr(new MockProvider("finish_download_slow_error"))); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr downloader(child.createDownloader(Item::ConflictPolicy::IgnoreConflict)); EXPECT_TRUE(downloader->isValid()); { QSignalSpy spy(downloader.get(), &Downloader::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Downloader::Status::Ready, qvariant_cast(arg.at(0))); } QSignalSpy spy(downloader.get(), &Downloader::statusChanged); downloader->close(); downloader->cancel(); downloader->cancel(); // Second time for coverage downloader->close(); // Second time for coverage EXPECT_EQ(1, spy.count()); auto arg = spy.takeFirst(); EXPECT_EQ(Downloader::Status::Cancelled, qvariant_cast(arg.at(0))); EXPECT_EQ(Downloader::Status::Cancelled, downloader->status()); EXPECT_EQ(StorageError::Type::Cancelled, downloader->error().type()); EXPECT_EQ("Downloader::cancel(): download was cancelled", downloader->error().message()); // We wait here to get coverage for when the reply to a FinishDownload() call // finds the downloader in a final state. EXPECT_FALSE(spy.wait(2000)); } TEST_F(DownloadTest, cancel_runtime_destroyed) { set_provider(unique_ptr(new MockProvider("finish_download_slow_error"))); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr downloader(child.createDownloader(Item::ConflictPolicy::IgnoreConflict)); EXPECT_TRUE(downloader->isValid()); { QSignalSpy spy(downloader.get(), &Downloader::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Downloader::Status::Ready, qvariant_cast(arg.at(0))); } QSignalSpy spy(downloader.get(), &Downloader::statusChanged); EXPECT_EQ(StorageError::Type::NoError, runtime_->shutdown().type()); // Destroy runtime downloader->cancel(); if (spy.count() == 0) { ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } auto arg = spy.takeFirst(); EXPECT_EQ(Downloader::Status::Error, qvariant_cast(arg.at(0))); EXPECT_EQ(Downloader::Status::Error, downloader->status()); EXPECT_EQ(StorageError::Type::RuntimeDestroyed, downloader->error().type()); EXPECT_EQ("Downloader::cancel(): Runtime was destroyed previously", downloader->error().message()); } TEST_F(UploadTest, basic) { set_provider(unique_ptr(new MockProvider())); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } QByteArray contents("Hello world", -1); unique_ptr uploader(child.createUploader(Item::ConflictPolicy::IgnoreConflict, contents.size())); EXPECT_TRUE(uploader->isValid()); EXPECT_EQ(Uploader::Status::Loading, uploader->status()); EXPECT_EQ(StorageError::NoError, uploader->error().type()); EXPECT_EQ(Item(), uploader->item()); EXPECT_EQ(Item::ConflictPolicy::IgnoreConflict, uploader->policy()); EXPECT_EQ(contents.size(), uploader->sizeInBytes()); { QSignalSpy spy(uploader.get(), &Uploader::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Ready, qvariant_cast(arg.at(0))); } EXPECT_EQ(0, uploader->bytesAvailable()); EXPECT_EQ(0, uploader->bytesToWrite()); char buf; EXPECT_EQ(-1, uploader->read(&buf, 1)); EXPECT_FALSE(uploader->waitForReadyRead(1)); EXPECT_EQ(contents.size(), uploader->write(contents)); EXPECT_TRUE(uploader->waitForBytesWritten(contents.size())); QSignalSpy spy(uploader.get(), &Uploader::statusChanged); uploader->close(); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Finished, qvariant_cast(arg.at(0))); EXPECT_EQ(Uploader::Status::Finished, uploader->status()); EXPECT_EQ(child, uploader->item()); } #if 0 // TODO: This test is currently disabled because a synchronous wait in the client // blocks the single event loop that is shared by the client and the mock provider. // We need to change the test harness to run a separate event loop for the provider. // TEST_F(UploadTest, write_before_ready_and_wait) { set_provider(unique_ptr(new MockProvider("upload_slow"))); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } QByteArray contents("Hello world", -1); unique_ptr uploader(child.createUploader(Item::ConflictPolicy::IgnoreConflict, contents.size())); EXPECT_TRUE(uploader->isValid()); // Don't wait for ready state. EXPECT_EQ(contents.size(), uploader->write(contents)); EXPECT_TRUE(uploader->waitForBytesWritten(1000)); QSignalSpy spy(uploader.get(), &Uploader::statusChanged); uploader->close(); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Finished, qvariant_cast(arg.at(0))); EXPECT_EQ(Uploader::Status::Finished, uploader->status()); EXPECT_EQ(child, uploader->item()); } #endif TEST_F(UploadTest, write_before_ready) { set_provider(unique_ptr(new MockProvider("upload_slow"))); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } QByteArray contents("Hello world", -1); unique_ptr uploader(child.createUploader(Item::ConflictPolicy::IgnoreConflict, contents.size())); EXPECT_TRUE(uploader->isValid()); // Don't wait for ready state. EXPECT_EQ(contents.size(), uploader->write(contents)); // Wait until we get confirmation that the contents were written. { QSignalSpy spy(uploader.get(), &Uploader::bytesWritten); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_TRUE(qvariant_cast(arg.at(0))); } QSignalSpy spy(uploader.get(), &Uploader::statusChanged); uploader->close(); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Finished, qvariant_cast(arg.at(0))); EXPECT_EQ(Uploader::Status::Finished, uploader->status()); EXPECT_EQ(child, uploader->item()); } TEST_F(UploadTest, abandoned) { set_provider(unique_ptr(new MockProvider())); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr uploader(child.createUploader(Item::ConflictPolicy::IgnoreConflict, 5)); EXPECT_TRUE(uploader->isValid()); { QSignalSpy spy(uploader.get(), &Uploader::statusChanged); spy.wait(SIGNAL_WAIT_TIME); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Ready, qvariant_cast(arg.at(0))); } EXPECT_EQ(1, uploader->write("a", 1)); EXPECT_TRUE(uploader->waitForBytesWritten(SIGNAL_WAIT_TIME)); } TEST_F(UploadTest, runtime_destroyed) { set_provider(unique_ptr(new MockProvider())); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } EXPECT_EQ(StorageError::Type::NoError, runtime_->shutdown().type()); unique_ptr uploader(child.createUploader(Item::ConflictPolicy::IgnoreConflict, 20)); EXPECT_FALSE(uploader->isValid()); EXPECT_EQ(Uploader::Status::Error, uploader->status()); EXPECT_EQ(StorageError::RuntimeDestroyed, uploader->error().type()); EXPECT_EQ("RuntimeDestroyed: Item::createUploader(): Runtime was destroyed previously", uploader->error().errorString()); EXPECT_EQ(Item(), uploader->item()); // Signal must arrive. { QSignalSpy spy(uploader.get(), &Uploader::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Error, qvariant_cast(arg.at(0))); } } TEST_F(UploadTest, runtime_destroyed_while_upload_running) { set_provider(unique_ptr(new MockProvider("upload_slow"))); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr uploader(child.createUploader(Item::ConflictPolicy::ErrorIfConflict, 20)); EXPECT_TRUE(uploader->isValid()); EXPECT_EQ(StorageError::Type::NoError, runtime_->shutdown().type()); // Destroy runtime, provider still sleeping // Signal must arrive. { QSignalSpy spy(uploader.get(), &Uploader::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Error, qvariant_cast(arg.at(0))); } EXPECT_FALSE(uploader->isValid()); EXPECT_EQ(Uploader::Status::Error, uploader->status()); EXPECT_EQ(StorageError::RuntimeDestroyed, uploader->error().type()); EXPECT_EQ("RuntimeDestroyed: Item::createUploader(): Runtime was destroyed previously", uploader->error().errorString()); EXPECT_EQ(Item(), uploader->item()); } TEST_F(UploadTest, upload_error) { set_provider(unique_ptr(new MockProvider("upload_error"))); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr uploader(child.createUploader(Item::ConflictPolicy::ErrorIfConflict, 100)); EXPECT_TRUE(uploader->isValid()); // Signal must arrive. { QSignalSpy spy(uploader.get(), &Uploader::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Error, qvariant_cast(arg.at(0))); } EXPECT_FALSE(uploader->isValid()); EXPECT_EQ(Uploader::Status::Error, uploader->status()); EXPECT_EQ(StorageError::Conflict, uploader->error().type()); EXPECT_EQ("Conflict: version mismatch", uploader->error().errorString()); EXPECT_EQ(Item(), uploader->item()); // For coverage: call close() while in the Error state. { QSignalSpy spy(uploader.get(), &Uploader::statusChanged); uploader->close(); EXPECT_FALSE(spy.wait(1000)); } } TEST_F(UploadTest, finish_too_soon) { set_provider(unique_ptr(new MockProvider())); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr uploader(child.createUploader(Item::ConflictPolicy::IgnoreConflict, 0)); EXPECT_TRUE(uploader->isValid()); QSignalSpy spy(uploader.get(), &Uploader::statusChanged); uploader->close(); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Error, qvariant_cast(arg.at(0))); EXPECT_FALSE(uploader->isValid()); EXPECT_EQ(Uploader::Status::Error, uploader->status()); EXPECT_EQ(StorageError::LogicError, uploader->error().type()); EXPECT_EQ("LogicError: Uploader::close(): cannot finalize while Uploader is not in the Ready state", uploader->error().errorString()); } TEST_F(UploadTest, finish_runtime_destroyed) { set_provider(unique_ptr(new MockProvider())); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr uploader(child.createUploader(Item::ConflictPolicy::IgnoreConflict, 0)); EXPECT_TRUE(uploader->isValid()); { QSignalSpy spy(uploader.get(), &Uploader::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Ready, qvariant_cast(arg.at(0))); } QSignalSpy spy(uploader.get(), &Uploader::statusChanged); EXPECT_EQ(StorageError::Type::NoError, runtime_->shutdown().type()); // Destroy runtime uploader->close(); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Error, qvariant_cast(arg.at(0))); EXPECT_FALSE(uploader->isValid()); EXPECT_EQ(Uploader::Status::Error, uploader->status()); EXPECT_EQ(StorageError::RuntimeDestroyed, uploader->error().type()); EXPECT_EQ("Uploader::close(): Runtime was destroyed previously", uploader->error().message()); } TEST_F(UploadTest, finish_runtime_destroyed_while_reply_outstanding) { set_provider(unique_ptr(new MockProvider("finish_upload_slow"))); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr uploader(child.createUploader(Item::ConflictPolicy::IgnoreConflict, 0)); EXPECT_TRUE(uploader->isValid()); { QSignalSpy spy(uploader.get(), &Uploader::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Ready, qvariant_cast(arg.at(0))); } QSignalSpy spy(uploader.get(), &Uploader::statusChanged); uploader->close(); EXPECT_EQ(StorageError::Type::NoError, runtime_->shutdown().type()); // Destroy runtime, provider still sleeping ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Error, qvariant_cast(arg.at(0))); EXPECT_FALSE(uploader->isValid()); EXPECT_EQ(Uploader::Status::Error, uploader->status()); EXPECT_EQ(StorageError::RuntimeDestroyed, uploader->error().type()); EXPECT_EQ("Uploader::close(): Runtime was destroyed previously", uploader->error().message()); } TEST_F(UploadTest, finish_twice) { set_provider(unique_ptr(new MockProvider())); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr uploader(child.createUploader(Item::ConflictPolicy::IgnoreConflict, 0)); EXPECT_TRUE(uploader->isValid()); { QSignalSpy spy(uploader.get(), &Uploader::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Ready, qvariant_cast(arg.at(0))); } QSignalSpy spy(uploader.get(), &Uploader::statusChanged); uploader->close(); uploader->close(); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Finished, qvariant_cast(arg.at(0))); EXPECT_EQ(Uploader::Status::Finished, uploader->status()); } TEST_F(UploadTest, finish_error) { set_provider(unique_ptr(new MockProvider("finish_upload_error"))); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr uploader(child.createUploader(Item::ConflictPolicy::IgnoreConflict, 0)); EXPECT_TRUE(uploader->isValid()); { QSignalSpy spy(uploader.get(), &Uploader::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Ready, qvariant_cast(arg.at(0))); } QSignalSpy spy(uploader.get(), &Uploader::statusChanged); uploader->close(); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Error, qvariant_cast(arg.at(0))); EXPECT_FALSE(uploader->isValid()); EXPECT_EQ(Uploader::Status::Error, uploader->status()); EXPECT_EQ(StorageError::ResourceError, uploader->error().type()); EXPECT_EQ("out of memory", uploader->error().message()); EXPECT_EQ(99, uploader->error().errorCode()); } TEST_F(UploadTest, wrong_type) { set_provider(unique_ptr(new MockProvider())); Item root; { unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); root = j->item(); } unique_ptr uploader(root.createUploader(Item::ConflictPolicy::IgnoreConflict, 0)); EXPECT_FALSE(uploader->isValid()); EXPECT_EQ(Uploader::Status::Error, uploader->status()); EXPECT_EQ(StorageError::Type::LogicError, uploader->error().type()); EXPECT_EQ("Item::createUploader(): cannot upload to a folder", uploader->error().message()); { QSignalSpy spy(uploader.get(), &Uploader::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Error, qvariant_cast(arg.at(0))); } } TEST_F(UploadTest, wrong_size) { set_provider(unique_ptr(new MockProvider())); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr uploader(child.createUploader(Item::ConflictPolicy::IgnoreConflict, -1)); EXPECT_FALSE(uploader->isValid()); EXPECT_EQ(Uploader::Status::Error, uploader->status()); EXPECT_EQ(StorageError::Type::InvalidArgument, uploader->error().type()); EXPECT_EQ("Item::createUploader(): size must be >= 0", uploader->error().message()); { QSignalSpy spy(uploader.get(), &Uploader::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Error, qvariant_cast(arg.at(0))); } } TEST_F(UploadTest, wrong_return_type) { set_provider(unique_ptr(new MockProvider("upload_returns_dir"))); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr uploader(child.createUploader(Item::ConflictPolicy::IgnoreConflict, 0)); EXPECT_TRUE(uploader->isValid()); { QSignalSpy spy(uploader.get(), &Uploader::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Ready, qvariant_cast(arg.at(0))); } QSignalSpy spy(uploader.get(), &Uploader::statusChanged); uploader->close(); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Error, qvariant_cast(arg.at(0))); EXPECT_EQ(Uploader::Status::Error, uploader->status()); EXPECT_EQ(StorageError::Type::LocalCommsError, uploader->error().type()); EXPECT_EQ("Item::createUploader(): impossible folder item returned by provider (id = some_id)", uploader->error().message()); } TEST_F(UploadTest, cancel_success) { set_provider(unique_ptr(new MockProvider())); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr uploader(child.createUploader(Item::ConflictPolicy::IgnoreConflict, 0)); EXPECT_TRUE(uploader->isValid()); { QSignalSpy spy(uploader.get(), &Uploader::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Ready, qvariant_cast(arg.at(0))); } QSignalSpy spy(uploader.get(), &Uploader::statusChanged); uploader->cancel(); EXPECT_EQ(1, spy.count()); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Cancelled, qvariant_cast(arg.at(0))); EXPECT_EQ(Uploader::Status::Cancelled, uploader->status()); EXPECT_EQ(StorageError::Type::Cancelled, uploader->error().type()); EXPECT_EQ("Uploader::cancel(): upload was cancelled", uploader->error().message()); // We wait here to get coverage for when the successful reply for the cancel message. EXPECT_FALSE(spy.wait(2000)); } TEST_F(UploadTest, cancel_error) { set_provider(unique_ptr(new MockProvider("finish_upload_slow_error"))); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr uploader(child.createUploader(Item::ConflictPolicy::IgnoreConflict, 0)); EXPECT_TRUE(uploader->isValid()); { QSignalSpy spy(uploader.get(), &Uploader::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Ready, qvariant_cast(arg.at(0))); } QSignalSpy spy(uploader.get(), &Uploader::statusChanged); uploader->close(); uploader->cancel(); uploader->cancel(); // Second time for coverage uploader->close(); // Second time for coverage EXPECT_EQ(1, spy.count()); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Cancelled, qvariant_cast(arg.at(0))); EXPECT_EQ(Uploader::Status::Cancelled, uploader->status()); EXPECT_EQ(StorageError::Type::Cancelled, uploader->error().type()); EXPECT_EQ("Uploader::cancel(): upload was cancelled", uploader->error().message()); // We wait here to get coverage for when the reply to a FinishUpload() call // finds the uploader in a final state. EXPECT_FALSE(spy.wait(2000)); } TEST_F(UploadTest, cancel_runtime_destroyed) { set_provider(unique_ptr(new MockProvider("finish_upload_slow_error"))); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } unique_ptr uploader(child.createUploader(Item::ConflictPolicy::IgnoreConflict, 0)); EXPECT_TRUE(uploader->isValid()); { QSignalSpy spy(uploader.get(), &Uploader::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Ready, qvariant_cast(arg.at(0))); } QSignalSpy spy(uploader.get(), &Uploader::statusChanged); EXPECT_EQ(StorageError::Type::NoError, runtime_->shutdown().type()); // Destroy runtime uploader->cancel(); if (spy.count() == 0) { ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); } auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Error, qvariant_cast(arg.at(0))); EXPECT_EQ(Uploader::Status::Error, uploader->status()); EXPECT_EQ(StorageError::Type::RuntimeDestroyed, uploader->error().type()); EXPECT_EQ("Uploader::cancel(): Runtime was destroyed previously", uploader->error().message()); } TEST_F(CreateFileTest, basic) { set_provider(unique_ptr(new MockProvider())); Item root; { unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); root = j->item(); } Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } QByteArray contents("Hello world", -1); unique_ptr uploader(root.createFile("Child", Item::ConflictPolicy::IgnoreConflict, contents.size(), "")); EXPECT_TRUE(uploader->isValid()); EXPECT_EQ(Uploader::Status::Loading, uploader->status()); EXPECT_EQ(StorageError::NoError, uploader->error().type()); EXPECT_EQ(Item(), uploader->item()); EXPECT_EQ(Item::ConflictPolicy::IgnoreConflict, uploader->policy()); EXPECT_EQ(contents.size(), uploader->sizeInBytes()); { QSignalSpy spy(uploader.get(), &Uploader::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Ready, qvariant_cast(arg.at(0))); } EXPECT_EQ(contents.size(), uploader->write(contents)); QSignalSpy spy(uploader.get(), &Uploader::statusChanged); uploader->close(); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Finished, qvariant_cast(arg.at(0))); EXPECT_EQ(Uploader::Status::Finished, uploader->status()); EXPECT_EQ(child, uploader->item()); } TEST_F(CreateFileTest, runtime_destroyed) { set_provider(unique_ptr(new MockProvider())); Item root; { unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); root = j->item(); } EXPECT_EQ(StorageError::Type::NoError, runtime_->shutdown().type()); // Destroy runtime QByteArray contents("Hello world", -1); unique_ptr uploader(root.createFile("Child", Item::ConflictPolicy::IgnoreConflict, contents.size(), "")); EXPECT_FALSE(uploader->isValid()); EXPECT_EQ(Uploader::Status::Error, uploader->status()); EXPECT_EQ(StorageError::Type::RuntimeDestroyed, uploader->error().type()); EXPECT_EQ("Item::createFile(): Runtime was destroyed previously", uploader->error().message()); // Signal must be received. QSignalSpy spy(uploader.get(), &Uploader::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Error, qvariant_cast(arg.at(0))); } TEST_F(CreateFileTest, wrong_type) { set_provider(unique_ptr(new MockProvider())); Item child; { unique_ptr j(acc_.get("child_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); child = j->item(); } QByteArray contents("Hello world", -1); unique_ptr uploader(child.createFile("somefile", Item::ConflictPolicy::IgnoreConflict, contents.size(), "")); EXPECT_FALSE(uploader->isValid()); EXPECT_EQ(Uploader::Status::Error, uploader->status()); EXPECT_EQ(StorageError::Type::LogicError, uploader->error().type()); EXPECT_EQ("Item::createFile(): cannot create a file with a file as the parent", uploader->error().message()); // Signal must be received. QSignalSpy spy(uploader.get(), &Uploader::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Error, qvariant_cast(arg.at(0))); } TEST_F(CreateFileTest, bad_name) { set_provider(unique_ptr(new MockProvider())); Item root; { unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); root = j->item(); } QByteArray contents("Hello world", -1); unique_ptr uploader(root.createFile("", Item::ConflictPolicy::IgnoreConflict, contents.size(), "")); EXPECT_FALSE(uploader->isValid()); EXPECT_EQ(Uploader::Status::Error, uploader->status()); EXPECT_EQ(StorageError::Type::InvalidArgument, uploader->error().type()); EXPECT_EQ("Item::createFile(): name cannot be empty", uploader->error().message()); // Signal must be received. QSignalSpy spy(uploader.get(), &Uploader::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Error, qvariant_cast(arg.at(0))); } TEST_F(CreateFileTest, bad_size) { set_provider(unique_ptr(new MockProvider())); Item root; { unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); root = j->item(); } QByteArray contents("Hello world", -1); unique_ptr uploader(root.createFile("some_file", Item::ConflictPolicy::IgnoreConflict, -1, "")); EXPECT_FALSE(uploader->isValid()); EXPECT_EQ(Uploader::Status::Error, uploader->status()); EXPECT_EQ(StorageError::Type::InvalidArgument, uploader->error().type()); EXPECT_EQ("Item::createFile(): size must be >= 0", uploader->error().message()); // Signal must be received. QSignalSpy spy(uploader.get(), &Uploader::statusChanged); spy.wait(SIGNAL_WAIT_TIME); ASSERT_EQ(1, spy.count()); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Error, qvariant_cast(arg.at(0))); } TEST_F(CreateFileTest, bad_return_type) { set_provider(unique_ptr(new MockProvider("upload_returns_dir"))); Item root; { unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); root = j->item(); } QByteArray contents("Hello world", -1); unique_ptr uploader(root.createFile("some_file", Item::ConflictPolicy::IgnoreConflict, contents.size(), "")); EXPECT_TRUE(uploader->isValid()); { QSignalSpy spy(uploader.get(), &Uploader::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Ready, qvariant_cast(arg.at(0))); } EXPECT_EQ(contents.size(), uploader->write(contents)); QSignalSpy spy(uploader.get(), &Uploader::statusChanged); uploader->close(); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Error, qvariant_cast(arg.at(0))); EXPECT_EQ(StorageError::Type::LocalCommsError, uploader->error().type()); EXPECT_EQ("Item::createFile(): impossible folder item returned by provider (id = some_id)", uploader->error().message()); } TEST_F(CreateFileTest, exists) { set_provider(unique_ptr(new MockProvider("create_file_exists"))); Item root; { unique_ptr j(acc_.get("root_id")); QSignalSpy spy(j.get(), &ItemJob::statusChanged); spy.wait(SIGNAL_WAIT_TIME); root = j->item(); } QByteArray contents("Hello world", -1); unique_ptr uploader(root.createFile("Child", Item::ConflictPolicy::ErrorIfConflict, contents.size(), "")); EXPECT_TRUE(uploader->isValid()); { QSignalSpy spy(uploader.get(), &Uploader::statusChanged); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Ready, qvariant_cast(arg.at(0))); } EXPECT_EQ(contents.size(), uploader->write(contents)); QSignalSpy spy(uploader.get(), &Uploader::statusChanged); uploader->close(); ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); auto arg = spy.takeFirst(); EXPECT_EQ(Uploader::Status::Error, qvariant_cast(arg.at(0))); EXPECT_EQ(StorageError::Type::Exists, uploader->error().type()); EXPECT_EQ("file exists", uploader->error().message()); EXPECT_EQ("child_id", uploader->error().itemId()); EXPECT_EQ("Child", uploader->error().itemName()); EXPECT_EQ(Item::ConflictPolicy::ErrorIfConflict, uploader->policy()); } int main(int argc, char** argv) { QCoreApplication app(argc, argv); ::testing::InitGoogleTest(&argc, argv); int rc = RUN_ALL_TESTS(); // Process any pending events to avoid bogus leak reports from valgrind. QCoreApplication::sendPostedEvents(); QCoreApplication::processEvents(); return rc; } lomiri-storage-framework-0.5.0/tests/testsetup.h.in000066400000000000000000000001621521521330000224130ustar00rootroot00000000000000#pragma once #define TEST_SRC_DIR "@CMAKE_CURRENT_SOURCE_DIR@" #define TEST_BIN_DIR "@CMAKE_CURRENT_BINARY_DIR@" lomiri-storage-framework-0.5.0/tests/utils/000077500000000000000000000000001521521330000207365ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/tests/utils/CMakeLists.txt000066400000000000000000000016051521521330000235000ustar00rootroot00000000000000if(ENABLE_QT6) pkg_check_modules(QTDBUSTEST_DEPS REQUIRED IMPORTED_TARGET libqtdbustest-qt6) else() pkg_check_modules(QTDBUSTEST_DEPS REQUIRED IMPORTED_TARGET libqtdbustest-1) endif() set_source_files_properties(${CMAKE_SOURCE_DIR}/data/provider.xml PROPERTIES CLASSNAME ProviderClient INCLUDE lomiri/storage/internal/dbusmarshal.h ) qt_add_dbus_interface(generated_files ${CMAKE_SOURCE_DIR}/data/provider.xml ProviderClient ) set_source_files_properties(${generated_files} PROPERTIES GENERATED TRUE) add_library(testutils STATIC DBusEnvironment.cpp ProviderFixture.cpp gtest_printer.cpp ${generated_files} ) target_link_libraries(testutils PUBLIC Qt${QT_VERSION_MAJOR}::DBus Qt${QT_VERSION_MAJOR}::Core PRIVATE Qt${QT_VERSION_MAJOR}::Test PkgConfig::QTDBUSTEST_DEPS PkgConfig::ONLINEACCOUNTS_DEPS ) add_definitions(-DBOOST_THREAD_VERSION=4) lomiri-storage-framework-0.5.0/tests/utils/DBusEnvironment.cpp000066400000000000000000000057161521521330000245350ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #include "DBusEnvironment.h" #include #include #include #include namespace { char const ACCOUNTS_BUS_NAME[] = "com.lomiri.OnlineAccounts.Manager"; char const FAKE_ACCOUNTS_SERVICE[] = TEST_SRC_DIR "/utils/fake-online-accounts-daemon.py"; char const REGISTRY_TEST_BUS_NAME[] = "com.ubuntu.StorageFramework.TestRegistry"; char const REGISTRY_SERVICE[] = TEST_BIN_DIR "/../src/registry/lomiri-storage-framework-registry"; char const DEMO_PROVIDER_BUS_NAME[] = "com.lomiri.StorageFramework.Provider.ProviderTest"; char const DEMO_PROVIDER_SERVICE[] = TEST_BIN_DIR "/../demo/provider_test/provider-test"; } DBusEnvironment::DBusEnvironment() { runner_.reset(new QtDBusTest::DBusTestRunner()); accounts_service_.reset(new QtDBusTest::QProcessDBusService( ACCOUNTS_BUS_NAME, QDBusConnection::SessionBus, FAKE_ACCOUNTS_SERVICE, {})); runner_->registerService(accounts_service_); registry_service_.reset(new QtDBusTest::QProcessDBusService( lomiri::storage::registry::BUS_NAME, QDBusConnection::SessionBus, REGISTRY_SERVICE, {})); runner_->registerService(registry_service_); } DBusEnvironment::~DBusEnvironment() { #if 0 // TODO: implement graceful shutdown if (accounts_service_process().state() == QProcess::Running) { } #endif runner_.reset(); } QDBusConnection const& DBusEnvironment::connection() const { return runner_->sessionConnection(); } QString const& DBusEnvironment::busAddress() const { return runner_->sessionBus(); } void DBusEnvironment::add_demo_provider(char const* service_id) { demo_provider_.reset( new QtDBusTest::QProcessDBusService( DEMO_PROVIDER_BUS_NAME, QDBusConnection::SessionBus, DEMO_PROVIDER_SERVICE, {service_id})); runner_->registerService(demo_provider_); } void DBusEnvironment::start_services() { runner_->startServices(); } QProcess& DBusEnvironment::accounts_service_process() { // We need a non-const version to access waitForFinished() return const_cast(accounts_service_->underlyingProcess()); } lomiri-storage-framework-0.5.0/tests/utils/DBusEnvironment.h000066400000000000000000000026761521521330000242040ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #pragma once #include #include #include #include namespace QtDBusTest { class DBusTestRunner; class QProcessDBusService; } class DBusEnvironment final { public: DBusEnvironment(); ~DBusEnvironment(); QDBusConnection const& connection() const; QString const& busAddress() const; void add_demo_provider(char const* service_id); void start_services(); QProcess& accounts_service_process(); private: std::unique_ptr runner_; QSharedPointer accounts_service_; QSharedPointer registry_service_; QSharedPointer demo_provider_; }; lomiri-storage-framework-0.5.0/tests/utils/ProviderFixture.cpp000066400000000000000000000050351521521330000246060ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #include "ProviderFixture.h" #include #include using namespace lomiri::storage::provider; using namespace std; namespace { const auto SERVICE_CONNECTION_NAME = QStringLiteral("service-session-bus"); const auto OBJECT_PATH = QStringLiteral("/provider"); } // namespace void ProviderFixture::SetUp() { dbus_.reset(new DBusEnvironment); dbus_->start_services(); service_connection_.reset(new QDBusConnection(QDBusConnection::connectToBus(dbus_->busAddress(), SERVICE_CONNECTION_NAME))); account_manager_.reset(new OnlineAccounts::Manager("", *service_connection_)); } void ProviderFixture::TearDown() { test_server_.reset(); account_manager_.reset(); service_connection_.reset(); QDBusConnection::disconnectFromBus(SERVICE_CONNECTION_NAME); dbus_.reset(); } QDBusConnection const& ProviderFixture::connection() const { return dbus_->connection(); } void ProviderFixture::set_provider(unique_ptr&& provider, unsigned int account_id) { account_manager_->waitForReady(); OnlineAccounts::Account* account = account_manager_->account(account_id); ASSERT_NE(nullptr, account); test_server_.reset( new lomiri::storage::provider::testing::TestServer(move(provider), account, *service_connection_, OBJECT_PATH.toStdString())); } void ProviderFixture::wait_for(QDBusPendingCall const& call) { QDBusPendingCallWatcher watcher(call); QSignalSpy spy(&watcher, &QDBusPendingCallWatcher::finished); ASSERT_TRUE(spy.wait()); } QString ProviderFixture::bus_name() const { return service_connection_->baseService(); } QString ProviderFixture::object_path() const { return OBJECT_PATH; } lomiri-storage-framework-0.5.0/tests/utils/ProviderFixture.h000066400000000000000000000035101521521330000242470ustar00rootroot00000000000000/* * Copyright (C) 2016 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that 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 program. If not, see . * * Authors: James Henstridge */ #pragma once #include #include #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #include #pragma GCC diagnostic pop #include #include #include #include class ProviderFixture : public ::testing::Test { public: virtual void SetUp() override; virtual void TearDown() override; QDBusConnection const& connection() const; void set_provider(std::unique_ptr&& provider, unsigned int account_id = 2); void wait_for(QDBusPendingCall const& call); QString bus_name() const; QString object_path() const; protected: std::unique_ptr dbus_; std::unique_ptr service_connection_; std::unique_ptr account_manager_; std::unique_ptr test_server_; }; lomiri-storage-framework-0.5.0/tests/utils/env_var_guard.h000066400000000000000000000033671521521330000237420ustar00rootroot00000000000000/* * Copyright (C) 2015 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Michi Henning */ #pragma once #include #include #include class EnvVarGuard final { public: // Set environment variable 'name' to 'val'. // To clear the variable, pass nullptr for 'val'. // The destructor restores the original setting. EnvVarGuard(char const* name, char const* val) : name_(name) { assert(name && *name != '\0'); auto const old_val = getenv(name); if ((was_set_ = old_val != nullptr)) { old_value_ = old_val; } if (val) { setenv(name, val, true); } else { unsetenv(name); } } // Restore the original setting. ~EnvVarGuard() { if (was_set_) { setenv(name_.c_str(), old_value_.c_str(), true); } else { unsetenv(name_.c_str()); } } EnvVarGuard(const EnvVarGuard&) = delete; EnvVarGuard& operator=(const EnvVarGuard&) = delete; private: std::string name_; std::string old_value_; bool was_set_; }; lomiri-storage-framework-0.5.0/tests/utils/fake-online-accounts-daemon.py000077500000000000000000000227241521521330000265700ustar00rootroot00000000000000#!/usr/bin/python3 # Copyright (C) 2016 Canonical Ltd # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License version 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that 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 program. If not, see . # # Authors: James Henstridge """A fake version of the OnlineAccounts D-Bus service.""" import sys import dbus.service import dbus.mainloop.glib from gi.repository import GLib BUS_NAME = "com.lomiri.OnlineAccounts.Manager" OBJECT_PATH = "/com/lomiri/OnlineAccounts/Manager" OA_IFACE = "com.lomiri.OnlineAccounts.Manager" TEST_IFACE = "com.lomiri.StorageFramework.Testing" AUTH_OAUTH1 = 1 AUTH_OAUTH2 = 2 AUTH_PASSWORD = 3 AUTH_SASL = 4 CHANGE_TYPE_ENABLED = 0 CHANGE_TYPE_DISABLED = 1 CHANGE_TYPE_CHANGED = 2 class OAuth1: method = AUTH_OAUTH1 def __init__(self, consumer_key, consumer_secret, token, token_secret, signature_method="HMAC-SHA1"): self.consumer_key = consumer_key self.consumer_secret = consumer_secret self.token = token self.token_secret = token_secret self.signature_method = signature_method def serialise(self, interactive, invalidate): return dbus.Dictionary({ "ConsumerKey": dbus.String(self.consumer_key), "ConsumerSecret": dbus.String(self.consumer_secret), "Token": dbus.String(self.token), "TokenSecret": dbus.String(self.token_secret), "SignatureMethod": dbus.String(self.signature_method), }, signature="sv") class OAuth2: method = AUTH_OAUTH2 def __init__(self, access_token, expires_in=0, granted_scopes=[]): self.access_token = access_token self.expires_in = expires_in self.granted_scopes = granted_scopes def serialise(self, interactive, invalidate): return dbus.Dictionary({ "AccessToken": dbus.String(self.access_token), "ExpiresIn": dbus.Int32(self.expires_in), "GrantedScopes": dbus.Array(self.granted_scopes, signature="s"), }, signature="sv") class Password: method = AUTH_PASSWORD def __init__(self, username, password): self.username = username self.password = password def serialise(self, interactive, invalidate): return dbus.Dictionary({ "Username": dbus.String(self.username), "Password": dbus.String(self.password), }, signature="sv") class CredentialsError: def __init__(self, method, error): assert error in {"NoAccount", "UserCanceled", "PermissionDenied", "InteractionRequired"} self.method = method self.error = "com.lomiri.OnlineAccounts.Error." + error def serialise(self, interactive, invalidate): raise dbus.DBusException("Error", name=self.error) class CredentialsByMode: def __init__(self, noninteractive, interactive, refresh): self.method = noninteractive.method self.noninteractive = noninteractive self.interactive = interactive self.refresh = refresh def serialise(self, interactive, invalidate): if invalidate: return self.refresh.serialise(interactive, invalidate) elif interactive: return self.interactive.serialise(interactive, invalidate) else: return self.noninteractive.serialise(interactive, invalidate) class Account: def __init__(self, account_id, name, service_id, credentials, settings=None): self.account_id = account_id self.name = name self.service_id = service_id self.credentials = credentials self.settings = settings def serialise(self): account_info = dbus.Dictionary({ "displayName": dbus.String(self.name), "serviceId": dbus.String(self.service_id), "authMethod": dbus.Int32(self.credentials.method), }, signature="sv") if self.settings is not None: for key, value in self.settings.items(): account_info['settings/' + key] = value return (dbus.UInt32(self.account_id), account_info) class Manager(dbus.service.Object): def __init__(self, connection, object_path, accounts): super(Manager, self).__init__(connection, object_path) self.accounts = accounts @dbus.service.method(dbus_interface=OA_IFACE, in_signature="a{sv}", out_signature="a(ua{sv})aa{sv}") def GetAccounts(self, filters): #print("GetAccounts %r" % filters) sys.stdout.flush() return dbus.Array([a.serialise() for a in self.accounts.values()], signature="a(ua{sv})"), dbus.Array(signature="a{sv}") @dbus.service.method(dbus_interface=OA_IFACE, in_signature="usbba{sv}", out_signature="a{sv}") def Authenticate(self, account_id, service_id, interactive, invalidate, parameters): #print("Authenticate %r %r %r %r %r" % (account_id, service_id, interactive, invalidate, parameters)) sys.stdout.flush() account = self.accounts[account_id, service_id] return account.credentials.serialise(interactive, invalidate) @dbus.service.method(dbus_interface=OA_IFACE, in_signature="sa{sv}", out_signature="(ua{sv})a{sv}") def RequestAccess(self, service_id, parameters): #print("RequestAccess %r %r" % (service_id, parameters)) sys.stdout.flush() for account in self.accounts.values(): if account.service_id == service_id: return (account.serialise(), account.credentials.serialise(True, False)) else: raise KeyError(service_id) @dbus.service.signal(dbus_interface=OA_IFACE, signature="s(ua{sv})") def AccountChanged(self, service_id, account): pass @dbus.service.method(dbus_interface=TEST_IFACE, in_signature="s", out_signature="") def UpdateAccount(self, account_data): account = eval(account_data) key = (account.account_id, account.service_id) exists = key in self.accounts self.accounts[key] = account info = account.serialise() info[1]["changeType"] = dbus.UInt32( CHANGE_TYPE_CHANGED if exists else CHANGE_TYPE_ENABLED) self.AccountChanged(account.service_id, info) @dbus.service.method(dbus_interface=TEST_IFACE, in_signature="us", out_signature="") def RemoveAccount(self, account_id, service_id): account = self.accounts.pop((account_id, service_id), None) if account is not None: info = account.serialise() info[1]["changeType"] = dbus.UInt32(CHANGE_TYPE_DISABLED) self.AccountChanged(account.service_id, info) class Server: def __init__(self, accounts): self.accounts = dict(((a.account_id, a.service_id), a) for a in accounts) dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) self.main_loop = GLib.MainLoop() self.connection = dbus.SessionBus() # Quit when the bus disconnectes self.connection.add_signal_receiver( self.main_loop.quit, signal_name="Disconnected", path="/org/freedesktop/DBus/Local", dbus_interface="org.freedesktop.DBus.Local") self.manager = Manager(self.connection, OBJECT_PATH, self.accounts) self.bus_name = dbus.service.BusName(BUS_NAME, self.connection, allow_replacement=True, replace_existing=True, do_not_queue=True) def run(self): try: self.main_loop.run() except KeyboardInterrupt: pass if __name__ == "__main__": accounts = [ Account(1, "OAuth1 account", "oauth1-service", OAuth1("consumer_key", "consumer_secret", "token", "token_secret")), Account(2, "OAuth2 account", "oauth2-service", OAuth2("access_token", 0, ["scope1", "scope2"])), Account(3, "Password account", "password-service", Password("user", "pass")), Account(4, "Password host account", "password-host-service", Password("joe", "secret"), {"host": "http://www.example.com/"}), Account(10, "Mode dependent account", "mode-service", CredentialsByMode( noninteractive=CredentialsError(AUTH_PASSWORD, "InteractionRequired"), interactive=Password("user", "interactive"), refresh=Password("user", "refresh")), {"host": "http://www.example.com/"}), Account(11, "User cancel account", "user-cancel-service", CredentialsError(AUTH_PASSWORD, "UserCanceled")), Account(42, "Fake test account", "storage-provider-test", OAuth2("fake-test-access-token", 0, [])), Account(99, "Fake mcloud account", "storage-provider-mcloud", OAuth2("fake-mcloud-access-token", 0, [])), ] server = Server(accounts) server.run() lomiri-storage-framework-0.5.0/tests/utils/gtest_printer.cpp000066400000000000000000000020341521521330000243320ustar00rootroot00000000000000/* * Copyright (C) 2015 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Michi Henning */ #include "gtest_printer.h" #include #include using namespace std; ostream& operator<<(ostream& stream, const QString& s) { return stream << s.toUtf8().constData(); } ostream& operator<<(ostream& stream, const char* s) { return std::operator<<(stream, s); } void PrintTo(const QString& s, ostream* stream) { *stream << s; } lomiri-storage-framework-0.5.0/tests/utils/gtest_printer.h000066400000000000000000000020541521521330000240010ustar00rootroot00000000000000/* * Copyright (C) 2015 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Michi Henning */ #pragma once #include class QString; // Helper for gtest to allow us to insert QString into a stream. std::ostream& operator<<(std::ostream& stream, const QString& s); // Needed because the QString version will take precedence over the std:: one std::ostream& operator<<(std::ostream& stream, const char* s); void PrintTo(const QString& s, std::ostream* stream); lomiri-storage-framework-0.5.0/tests/whitespace/000077500000000000000000000000001521521330000217325ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/tests/whitespace/CMakeLists.txt000066400000000000000000000005501521521330000244720ustar00rootroot00000000000000# # Test that all source files, cmakefiles, etc. do not contain trailing whitespace. # set(CHECK_WHITESPACE_IGNORE ${CMAKE_BINARY_DIR} CACHE STRING "Directories ignored by the whitespace check") add_test(whitespace ${CMAKE_CURRENT_SOURCE_DIR}/check_whitespace.py ${CMAKE_SOURCE_DIR} ${CHECK_WHITESPACE_IGNORE} ${CMAKE_SOURCE_DIR}/parts ) lomiri-storage-framework-0.5.0/tests/whitespace/check_whitespace.py000077500000000000000000000100201521521330000255710ustar00rootroot00000000000000#! /usr/bin/env python3 # # Copyright (C) 2013 Canonical Ltd # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License version 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that 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 program. If not, see . # # Authored by: Michi Henning # # # Little helper program to test that source files do not contain trailing whitespace # or tab indentation. # # Usage: check_whitespace.py directory [ignore_prefix] # # The directory specifies the (recursive) location of the source files. Any # files with a path that starts with ignore_prefix are not checked. This is # useful to exclude files that are generated into the build directory. # # See the file_pat definition below for a list of files that are checked. # import argparse import os import re import sys print(sys.argv) # Print msg on stderr, preceded by program name and followed by newline def error(msg): print(os.path.basename(sys.argv[0]) + ": " + msg, file=sys.stderr) # Function to raise errors encountered by os.walk def raise_error(e): raise e # Scan lines in file_path for bad whitespace. For each file, # print the line numbers that have whitespace issues whitespace_pat = re.compile(r'.*[ \t]$') tab_indent_pat = re.compile(r'^ *\t') def scan_for_bad_whitespace(file_path): global tab_indent_pat, whitespace_pat errors = [] newlines_at_end = 0 with open(file_path, 'rt', encoding='utf-8') as ifile: for lino, line in enumerate(ifile, start=1): if whitespace_pat.match(line) or tab_indent_pat.match(line): errors.append(lino) if line == "\n" and lino != 1: # Don't complain about empty file with only a single line newlines_at_end += 1 else: newlines_at_end = 0 if 0 < len(errors) <= 10: if len(errors) > 1: plural = 's' else: plural = '' print("%s: bad whitespace in line%s %s" % (file_path, plural, ", ".join((str(i) for i in errors)))) elif errors: print("%s: bad whitespace in multiple lines" % file_path) if newlines_at_end: print("%s: multiple new lines at end of file" % file_path) return bool(errors) or newlines_at_end # Parse args parser = argparse.ArgumentParser(description = 'Test that source files do not contain trailing whitespace.') parser.add_argument('dir', nargs = 1, help = 'The directory to (recursively) search for source files') parser.add_argument('ignore_prefix', nargs = '+', default=None, help = 'Ignore source files with a path that starts with the given prefix.') args = parser.parse_args() # Files we want to check for trailing whitespace. file_pat = r'(.*\.(c|cpp|h|hpp|hh|in|install|js|py|qml|sh)$)|(.*CMakeLists\.txt$)' pat = re.compile(file_pat) # Find all the files with matching file extension in the specified # directory and check them for trailing whitespace. directory = os.path.abspath(args.dir[0]) ignores = args.ignore_prefix and args.ignore_prefix or [] found_whitespace = False try: for root, dirs, files in os.walk(directory, onerror = raise_error): for file in files: path = os.path.join(root, file) ignored = False for ignore in ignores: if ignore and path.startswith(os.path.abspath(ignore)): ignored = True break if not ignored and pat.match(file) and scan_for_bad_whitespace(path): found_whitespace = True except OSError as e: error("cannot create file list for \"" + dir + "\": " + e.strerror) sys.exit(1) if found_whitespace: sys.exit(1) lomiri-storage-framework-0.5.0/tools/000077500000000000000000000000001521521330000175745ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/tools/CMakeLists.txt000066400000000000000000000001421521521330000223310ustar00rootroot00000000000000if("${SNAP_BUILD}") install(PROGRAMS snap-launch DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) endif() lomiri-storage-framework-0.5.0/tools/create_globalheader.py000077500000000000000000000026521521521330000241120ustar00rootroot00000000000000#!/usr/bin/env python3 # # Copyright (C) 2014 Canonical Ltd # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License version 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that 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 program. If not, see . # # Authored by: Jussi Pakkanen # Michi Henning from glob import glob import sys, os def build_header(outfile, prefix, incroots): ofile = open(outfile, 'w') ofile.write("#pragma once\n\n") headers = [] for r in incroots: headers += glob(os.path.join(r, '*.h')) headers = [os.path.split(f)[1] for f in headers] headers.sort() for f in headers: line = '#include <%s>\n' % os.path.join(prefix, f) ofile.write(line) if __name__ == '__main__': if len(sys.argv) <= 3: print(sys.argv[0], 'outfile prefix include_roots') sys.exit(1) outfile = sys.argv[1] prefix = sys.argv[2] incroots = sys.argv[3:] build_header(outfile, prefix, incroots) lomiri-storage-framework-0.5.0/tools/snap-launch000077500000000000000000000016011521521330000217310ustar00rootroot00000000000000#!/bin/bash # Copyright (C) 2017 Canonical Ltd # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License version 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that 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 program. If not, see . # # Authors: Michi Henning [ $# -lt 2 ] && { echo "usage: $(basename "$0") library_path program [args...]" >&2 exit 1 } export LD_LIBRARY_PATH=${1}${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH} shift exec desktop-launch "$@" lomiri-storage-framework-0.5.0/ubsan-suppress000066400000000000000000000000001521521330000213370ustar00rootroot00000000000000lomiri-storage-framework-0.5.0/valgrind-suppress000066400000000000000000000000001521521330000220350ustar00rootroot00000000000000