Merge lp:~michihenning/storage-framework/merge-devel into lp:storage-framework

Proposed by Michi Henning
Status: Merged
Approved by: James Henstridge
Approved revision: 17
Merged at revision: 17
Proposed branch: lp:~michihenning/storage-framework/merge-devel
Merge into: lp:storage-framework
Diff against target: 6994 lines (+5263/-411)
79 files modified
.bzrignore (+4/-0)
CMakeLists.txt (+12/-2)
CTestCustom.cmake.in (+16/-0)
debian/changelog (+21/-0)
debian/control.in (+2/-1)
debian/storage-framework-registry.install (+2/-0)
demo/provider_test/provider-test.cpp (+1/-1)
include/unity/storage/internal/ActivityNotifier.h (+20/-1)
include/unity/storage/internal/EnvVars.h (+7/-0)
include/unity/storage/internal/InactivityTimer.h (+3/-5)
include/unity/storage/internal/gobj_memory.h (+212/-0)
include/unity/storage/provider/Exceptions.h (+31/-1)
include/unity/storage/provider/Item.h (+54/-0)
include/unity/storage/provider/ProviderBase.h (+1/-16)
include/unity/storage/provider/Server.h (+1/-1)
include/unity/storage/provider/internal/AccountData.h (+12/-19)
include/unity/storage/provider/internal/DownloadJobImpl.h (+5/-0)
include/unity/storage/provider/internal/FixedAccountData.h (+56/-0)
include/unity/storage/provider/internal/Handler.h (+7/-1)
include/unity/storage/provider/internal/OnlineAccountData.h (+75/-0)
include/unity/storage/provider/internal/ServerImpl.h (+17/-3)
include/unity/storage/provider/internal/TestServerImpl.h (+7/-0)
include/unity/storage/provider/internal/UploadJobImpl.h (+5/-0)
include/unity/storage/qt/StorageError.h (+3/-2)
include/unity/storage/registry/Registry.h (+1/-0)
snap/snapcraft.yaml (+128/-0)
src/CMakeLists.txt (+1/-0)
src/internal/EnvVars.cpp (+38/-28)
src/internal/InactivityTimer.cpp (+5/-24)
src/local-provider/CMakeLists.txt (+43/-0)
src/local-provider/LocalDownloadJob.cpp (+179/-0)
src/local-provider/LocalDownloadJob.h (+54/-0)
src/local-provider/LocalProvider.cpp (+586/-0)
src/local-provider/LocalProvider.h (+84/-0)
src/local-provider/LocalUploadJob.cpp (+337/-0)
src/local-provider/LocalUploadJob.h (+79/-0)
src/local-provider/com.canonical.StorageFramework.Provider.Local.service.in (+3/-0)
src/local-provider/main.cpp (+52/-0)
src/local-provider/utils.cpp (+146/-0)
src/local-provider/utils.h (+45/-0)
src/provider/CMakeLists.txt (+4/-0)
src/provider/Exceptions.cpp (+7/-0)
src/provider/Server.cpp (+2/-2)
src/provider/internal/AccountData.cpp (+9/-114)
src/provider/internal/DownloadJobImpl.cpp (+5/-0)
src/provider/internal/FixedAccountData.cpp (+62/-0)
src/provider/internal/Handler.cpp (+56/-3)
src/provider/internal/OnlineAccountData.cpp (+198/-0)
src/provider/internal/ProviderInterface.cpp (+4/-12)
src/provider/internal/ServerImpl.cpp (+149/-35)
src/provider/internal/TestServerImpl.cpp (+23/-4)
src/provider/internal/UploadJobImpl.cpp (+7/-0)
src/qt/client/internal/remote_client/RuntimeImpl.cpp (+2/-1)
src/qt/internal/RuntimeImpl.cpp (+4/-0)
src/qt/internal/StorageErrorImpl.cpp (+2/-7)
src/qt/internal/unmarshal_error.cpp (+1/-0)
src/registry/CMakeLists.txt (+17/-10)
src/registry/internal/ListAccountsHandler.cpp (+18/-1)
src/registry/main.cpp (+8/-6)
tests/CMakeLists.txt (+2/-0)
tests/copyright/check_copyright.sh (+1/-1)
tests/local-provider/CMakeLists.txt (+18/-0)
tests/local-provider/local-provider_test.cpp (+1476/-0)
tests/provider-AccountData/AccountData_test.cpp (+47/-17)
tests/provider-ProviderInterface/ProviderInterface_test.cpp (+112/-1)
tests/provider-ProviderInterface/TestProvider.h (+43/-40)
tests/provider-Server/CMakeLists.txt (+14/-0)
tests/provider-Server/Server_test.cpp (+284/-0)
tests/registry/CMakeLists.txt (+12/-17)
tests/registry/registry_test.cpp (+120/-3)
tests/remote-client-v1/remote-client-v1_test.cpp (+7/-1)
tests/remote-client/remote-client_test.cpp (+9/-3)
tests/utils/ProviderFixture.cpp (+4/-2)
tests/utils/ProviderFixture.h (+2/-1)
tests/utils/env_var_guard.h (+70/-0)
tests/utils/fake-online-accounts-daemon.py (+77/-23)
tests/whitespace/CMakeLists.txt (+3/-2)
tools/CMakeLists.txt (+3/-0)
tools/snap-launch (+26/-0)
To merge this branch: bzr merge lp:~michihenning/storage-framework/merge-devel
Reviewer Review Type Date Requested Status
James Henstridge Approve
Review via email: mp+320296@code.launchpad.net

Commit message

Merged devel at r118 for 0.3 release.

Description of the change

Merged devel at r118 for 0.3 release.

To post a comment you must log in.
Revision history for this message
James Henstridge (jamesh) wrote :

Looks good.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2016-11-04 10:27:54 +0000
3+++ .bzrignore 2017-03-20 04:58:30 +0000
4@@ -1,3 +1,7 @@
5 build
6 debian/libstorage-framework-provider-1-*.install
7 debian/libstorage-framework-provider-1-*.shlibs
8+./parts
9+./prime
10+./stage
11+./storage-framework*.snap
12
13=== modified file 'CMakeLists.txt'
14--- CMakeLists.txt 2016-12-12 02:18:21 +0000
15+++ CMakeLists.txt 2017-03-20 04:58:30 +0000
16@@ -5,7 +5,7 @@
17 endif()
18
19 cmake_minimum_required(VERSION 3.0.2)
20-project(storage-framework VERSION "0.2" LANGUAGES C CXX)
21+project(storage-framework VERSION "0.3" LANGUAGES C CXX)
22
23 # These variables should be incremented when we wish to create a new
24 # source incompatible version of the library where users of the
25@@ -43,7 +43,7 @@
26 include_directories(include)
27 include_directories(${CMAKE_BINARY_DIR}/include) # For generated headers
28
29-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC --std=c++11 -Wall -pedantic -Wextra -fvisibility=hidden")
30+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC --std=c++14 -Wall -pedantic -Wextra -fvisibility=hidden")
31
32 # Some additional warnings not included by the general flags set above.
33 set(EXTRA_C_WARNINGS "-Wcast-align -Wcast-qual -Wformat -Wredundant-decls -Wswitch-default")
34@@ -106,6 +106,11 @@
35
36 find_package(CoverageReport)
37
38+option(SNAP_BUILD "Build for snap release")
39+if("${SNAP_BUILD}")
40+ add_definitions(-DSNAP_BUILD=1)
41+endif()
42+
43 include(GNUInstallDirs)
44
45 find_package(Boost 1.56 COMPONENTS filesystem system thread REQUIRED)
46@@ -118,6 +123,7 @@
47
48 include(FindPkgConfig)
49 pkg_check_modules(APPARMOR_DEPS REQUIRED libapparmor)
50+pkg_check_modules(GIO_DEPS REQUIRED gio-2.0 gio-unix-2.0)
51 pkg_check_modules(GLIB_DEPS REQUIRED glib-2.0)
52 pkg_check_modules(ONLINEACCOUNTS_DEPS REQUIRED OnlineAccountsQt)
53
54@@ -128,9 +134,11 @@
55 add_subdirectory(plugins/Ubuntu/StorageFramework)
56 add_subdirectory(tests)
57 add_subdirectory(demo)
58+add_subdirectory(tools)
59
60 enable_coverage_report(
61 TARGETS
62+ local-provider-lib
63 qt-client-lib-common
64 storage-framework-common-internal
65 storage-framework-qt-client
66@@ -138,7 +146,9 @@
67 storage-framework-qt-local-client
68 sf-provider-objects
69 storage-framework-provider
70+ registry-static
71 storage-framework-registry
72+ storage-provider-local
73 FILTER
74 ${CMAKE_SOURCE_DIR}/tests/*
75 ${CMAKE_BINARY_DIR}/*
76
77=== modified file 'CTestCustom.cmake.in'
78--- CTestCustom.cmake.in 2016-04-20 00:53:53 +0000
79+++ CTestCustom.cmake.in 2017-03-20 04:58:30 +0000
80@@ -7,5 +7,21 @@
81 #
82
83 SET(CTEST_CUSTOM_MEMCHECK_IGNORE
84+ clean-public-unity-storage-headers
85+ clean-public-unity-storage-provider-headers
86+ clean-public-unity-storage-qt-client-headers
87+ clean-public-unity-storage-qt-headers
88+ copyright
89+ debian-version
90+ stand-alone-unity-storage-headers
91+ stand-alone-unity-storage-internal-headers
92+ stand-alone-unity-storage-provider-headers
93+ stand-alone-unity-storage-provider-internal-headers
94+ stand-alone-unity-storage-qt-client-headers
95+ stand-alone-unity-storage-qt-client-internal-headers
96+ stand-alone-unity-storage-qt-client-internal-local_client-headers
97+ stand-alone-unity-storage-qt-client-internal-remote_client-headers
98+ stand-alone-unity-storage-qt-headers
99+ stand-alone-unity-storage-qt-internal-headers
100 whitespace
101 )
102
103=== modified file 'debian/changelog'
104--- debian/changelog 2016-12-12 02:54:47 +0000
105+++ debian/changelog 2017-03-20 04:58:30 +0000
106@@ -1,3 +1,24 @@
107+storage-framework (0.3-0ubuntu1) UNRELEASED; urgency=medium
108+
109+ [ James Henstridge ]
110+ * Make providers exit after 30 seconds of inactivity. (LP: #1616758)
111+ * Add UnauthorizedException to the provider library, so the provider
112+ can trigger reauthentication of the account and have the request
113+ restarted. (LP: #1616756)
114+ * Dynamically add and remove providers as the associated accounts
115+ are enabled and disabled. (LP: #1616757)
116+ * Allow creation of storage providers that don't use online-accounts.
117+ Thisis likely only of interest to the local storage provider.
118+
119+ [ Michi Henning ]
120+ * Add a storage provider backed by the local file system.
121+ * Move unity::storage::provider::Item to its own header file. (LP:
122+ #1668872)
123+ * If a provider can't acquire its D-Bus well known name, exit rather
124+ than throwing a (usually uncaught) exception. (LP: #1604640)
125+
126+ -- James Henstridge <james.henstridge@canonical.com> Tue, 20 Dec 2016 11:46:21 +0800
127+
128 storage-framework (0.2+17.04.20161212.1-0ubuntu1) zesty; urgency=medium
129
130 * Fix for lp:1644577, fail list job if metadata for any item is incorrect.
131
132=== modified file 'debian/control.in'
133--- debian/control.in 2016-12-12 02:18:21 +0000
134+++ debian/control.in 2017-03-20 04:58:30 +0000
135@@ -2,7 +2,7 @@
136 Section: libs
137 Priority: optional
138 Maintainer: Ubuntu Core Developers <ubuntu-devel-discuss@lists.ubuntu.com>
139-Standards-Version: 3.9.6
140+Standards-Version: 3.9.7
141 Build-Depends: cmake,
142 cmake-extras (>= 0.10),
143 debhelper (>= 9),
144@@ -58,6 +58,7 @@
145 online-accounts-daemon,
146 Description: Registry for the Storage Framework
147 DBus service that provides access to provider account information.
148+ Includes a local storage provider.
149
150 Package: libstorage-framework-qt-client-2-0
151 Architecture: any
152
153=== modified file 'debian/storage-framework-registry.install'
154--- debian/storage-framework-registry.install 2016-11-21 04:37:37 +0000
155+++ debian/storage-framework-registry.install 2017-03-20 04:58:30 +0000
156@@ -1,2 +1,4 @@
157 usr/lib/*/*/storage-framework-registry
158 usr/share/dbus-1/services/com.canonical.StorageFramework.Registry.service
159+usr/lib/*/*/storage-provider-local
160+usr/share/dbus-1/services/com.canonical.StorageFramework.Provider.Local.service
161
162=== modified file 'demo/provider_test/provider-test.cpp'
163--- demo/provider_test/provider-test.cpp 2016-11-15 05:48:12 +0000
164+++ demo/provider_test/provider-test.cpp 2017-03-20 04:58:30 +0000
165@@ -321,5 +321,5 @@
166 }
167 Server<MyProvider> server(bus_name, account_service_id);
168 server.init(argc, argv);
169- server.run();
170+ return server.run();
171 }
172
173=== modified file 'include/unity/storage/internal/ActivityNotifier.h'
174--- include/unity/storage/internal/ActivityNotifier.h 2016-11-08 11:17:52 +0000
175+++ include/unity/storage/internal/ActivityNotifier.h 2017-03-20 04:58:30 +0000
176@@ -35,6 +35,8 @@
177 class ActivityNotifier
178 {
179 public:
180+ ActivityNotifier() = default;
181+
182 ActivityNotifier(std::shared_ptr<InactivityTimer> const& timer)
183 : timer_(timer)
184 {
185@@ -43,9 +45,26 @@
186 timer_->request_started();
187 }
188
189+ ActivityNotifier(ActivityNotifier&& other)
190+ : timer_(std::move(other.timer_))
191+ {
192+ }
193+
194+ ActivityNotifier& operator=(ActivityNotifier&& other)
195+ {
196+ timer_ = std::move(other.timer_);
197+ return *this;
198+ }
199+
200+ ActivityNotifier(ActivityNotifier const&) = delete;
201+ ActivityNotifier& operator=(ActivityNotifier const&) = delete;
202+
203 ~ActivityNotifier()
204 {
205- timer_->request_finished();
206+ if (timer_)
207+ {
208+ timer_->request_finished();
209+ }
210 }
211
212 private:
213
214=== modified file 'include/unity/storage/internal/EnvVars.h'
215--- include/unity/storage/internal/EnvVars.h 2016-11-21 13:29:39 +0000
216+++ include/unity/storage/internal/EnvVars.h 2017-03-20 04:58:30 +0000
217@@ -32,6 +32,9 @@
218 constexpr char const* REGISTRY_IDLE_TIMEOUT = "SF_REGISTRY_IDLE_TIMEOUT"; // Seconds, 0 means "never"
219 constexpr int REGISTRY_IDLE_TIMEOUT_DFLT = 30;
220
221+constexpr char PROVIDER_IDLE_TIMEOUT[] = "SF_PROVIDER_IDLE_TIMEOUT";
222+constexpr int PROVIDER_IDLE_TIMEOUT_DFLT = 30;
223+
224 // Helper class to make retrieval of environment variables type-safe and
225 // to sanity check the setting, if applicable. Also returns a default
226 // setting, if applicable.
227@@ -40,10 +43,14 @@
228 {
229 public:
230 static int registry_timeout_ms();
231+ static int provider_timeout_ms();
232
233 // Returns value of var_name in the environment, if set, and an empty string otherwise.
234 // Can be used for any environment variable, not just the ones defined above.
235 static std::string get(char const* var_name);
236+
237+private:
238+ static int get_timeout_ms(char const* var_name, int dflt);
239 };
240
241 } // namespace internal
242
243=== modified file 'include/unity/storage/internal/InactivityTimer.h'
244--- include/unity/storage/internal/InactivityTimer.h 2016-11-08 11:17:52 +0000
245+++ include/unity/storage/internal/InactivityTimer.h 2017-03-20 04:58:30 +0000
246@@ -39,20 +39,18 @@
247 Q_OBJECT
248
249 public:
250- InactivityTimer(int timeout_ms, std::function<void()> timeout_func);
251+ InactivityTimer(int timeout_ms);
252 ~InactivityTimer();
253
254 void request_started();
255 void request_finished();
256
257-private Q_SLOTS:
258+Q_SIGNALS:
259 void timeout();
260
261 private:
262- int timeout_ms_;
263- std::function<void()> timeout_func_;
264 QTimer timer_;
265- int32_t num_requests_;
266+ int32_t num_requests_ = 0;
267 };
268
269 } // namespace internal
270
271=== added file 'include/unity/storage/internal/gobj_memory.h'
272--- include/unity/storage/internal/gobj_memory.h 1970-01-01 00:00:00 +0000
273+++ include/unity/storage/internal/gobj_memory.h 2017-03-20 04:58:30 +0000
274@@ -0,0 +1,212 @@
275+/*
276+ * Copyright (C) 2013 Canonical Ltd.
277+ *
278+ * This program is free software: you can redistribute it and/or modify
279+ * it under the terms of the GNU Lesser General Public License version 3 as
280+ * published by the Free Software Foundation.
281+ *
282+ * This program is distributed in the hope that it will be useful,
283+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
284+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
285+ * GNU Lesser General Public License for more details.
286+ *
287+ * You should have received a copy of the GNU Lesser General Public License
288+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
289+ *
290+ * Authored by: Jussi Pakkanen <jussi.pakkanen@canonical.com>
291+ */
292+
293+#pragma once
294+
295+#include <stdexcept>
296+
297+#pragma GCC diagnostic push
298+#pragma GCC diagnostic ignored "-Wold-style-cast"
299+#pragma GCC diagnostic ignored "-Wcast-qual"
300+#include <glib-object.h>
301+
302+namespace unity
303+{
304+
305+namespace storage
306+{
307+
308+namespace internal
309+{
310+
311+/**
312+ * This class is meant for automatically managing the lifetime of C objects derived
313+ * from gobject. Its API perfectly mirrors the API of unique_ptr except that you
314+ * can't define your own deleter function as it is always g_object_unref.
315+ *
316+ * API/ABI stability is not guaranteed. If you need to pass the object across an ABI
317+ * boundary, pass the plain gobject.
318+ *
319+ * This is how you would use gobj_ptr 99% of the time:
320+ *
321+ * gobj_ptr<GSomeType> o(g_some_type_new(...));
322+ *
323+ * More specifically, the object will decrement the gobject reference count
324+ * of the object it points to when it goes out of scope. It will never increment it.
325+ * Thus you should only assign to it when already holding a reference. gobj_ptr
326+ * will then take ownership of that particular reference.
327+ *
328+ * Floating gobjects can not be put in this container as they are meant to be put
329+ * into native gobject aware containers immediately upon construction. Trying to insert
330+ * a floating gobject into a gobj_ptr will throw an invalid_argument exception. To
331+ * prevent accidental memory leaks, the floating gobject is unreffed in this case.
332+ */
333+template <typename T>
334+class gobj_ptr final
335+{
336+private:
337+ T* u;
338+
339+ void validate_float(T* t)
340+ {
341+ if (t != nullptr && g_object_is_floating(G_OBJECT(t)))
342+ {
343+ // LCOV_EXCL_START // False negative from gcovr.
344+ throw std::invalid_argument("Tried to add a floating gobject into a gobj_ptr.");
345+ // LCOV_EXCL_STOP
346+ }
347+ }
348+
349+public:
350+ typedef T element_type;
351+ typedef T* pointer;
352+ typedef decltype(g_object_unref) deleter_type;
353+
354+ constexpr gobj_ptr() noexcept : u(nullptr)
355+ {
356+ }
357+ explicit gobj_ptr(T* t)
358+ : u(t)
359+ {
360+ // What should we do if validate throws? Unreffing unknown objs
361+ // is dodgy but not unreffing runs the risk of
362+ // memory leaks. Currently unrefs as u is destroyed
363+ // when this exception is thrown.
364+ validate_float(t);
365+ }
366+ constexpr gobj_ptr(std::nullptr_t) noexcept : u(nullptr){};
367+ gobj_ptr(gobj_ptr&& o) noexcept
368+ {
369+ u = o.u;
370+ o.u = nullptr;
371+ }
372+ gobj_ptr(const gobj_ptr& o)
373+ : u(nullptr)
374+ {
375+ *this = o;
376+ }
377+ gobj_ptr& operator=(const gobj_ptr& o)
378+ {
379+ if (o.u != nullptr)
380+ {
381+ g_object_ref(o.u);
382+ }
383+ reset(o.u);
384+ return *this;
385+ }
386+ ~gobj_ptr()
387+ {
388+ reset();
389+ }
390+
391+ deleter_type& get_deleter() noexcept
392+ {
393+ return g_object_unref;
394+ }
395+ deleter_type& get_deleter() const noexcept
396+ {
397+ return g_object_unref;
398+ }
399+
400+ void swap(gobj_ptr<T>& o) noexcept
401+ {
402+ T* tmp = u;
403+ u = o.u;
404+ o.u = tmp;
405+ }
406+ void reset(pointer p = pointer())
407+ {
408+ if (u != nullptr)
409+ {
410+ g_object_unref(G_OBJECT(u));
411+ u = nullptr;
412+ }
413+ // Same throw dilemma as in pointer constructor.
414+ u = p;
415+ validate_float(p);
416+ }
417+
418+ T* release() noexcept
419+ {
420+ T* r = u;
421+ u = nullptr;
422+ return r;
423+ }
424+ T* get() const noexcept
425+ {
426+ return u;
427+ }
428+
429+ T& operator*() const
430+ {
431+ return *u;
432+ }
433+ T* operator->() const noexcept
434+ {
435+ return u;
436+ }
437+ explicit operator bool() const noexcept
438+ {
439+ return u != nullptr;
440+ }
441+
442+ gobj_ptr& operator=(gobj_ptr&& o) noexcept
443+ {
444+ reset();
445+ u = o.u;
446+ o.u = nullptr;
447+ return *this;
448+ }
449+ gobj_ptr& operator=(std::nullptr_t) noexcept
450+ {
451+ reset();
452+ return *this;
453+ }
454+ bool operator==(const gobj_ptr<T>& o) const noexcept
455+ {
456+ return u == o.u;
457+ }
458+ bool operator!=(const gobj_ptr<T>& o) const noexcept
459+ {
460+ return u != o.u;
461+ }
462+ bool operator<(const gobj_ptr<T>& o) const noexcept
463+ {
464+ return u < o.u;
465+ }
466+ bool operator<=(const gobj_ptr<T>& o) const noexcept
467+ {
468+ return u <= o.u;
469+ }
470+ bool operator>(const gobj_ptr<T>& o) const noexcept
471+ {
472+ return u > o.u;
473+ }
474+ bool operator>=(const gobj_ptr<T>& o) const noexcept
475+ {
476+ return u >= o.u;
477+ }
478+};
479+
480+} // namespace internal
481+
482+} // namespace storage
483+
484+} // namespace unity
485+
486+#pragma GCC diagnostic pop
487
488=== modified file 'include/unity/storage/provider/Exceptions.h'
489--- include/unity/storage/provider/Exceptions.h 2016-08-05 05:37:23 +0000
490+++ include/unity/storage/provider/Exceptions.h 2017-03-20 04:58:30 +0000
491@@ -95,7 +95,7 @@
492 };
493
494 /**
495-\brief Indicates that an upload detected a version mismatch.
496+\brief Indicates that an upload or download detected a version mismatch.
497 */
498 class UNITY_STORAGE_EXPORT ConflictException : public StorageException
499 {
500@@ -105,7 +105,37 @@
501 };
502
503 /**
504+\brief Indicates that an operation failed because the authentication credentials are invalid or expired.
505+
506+A provider implementation must throw this exception if it cannot reach
507+its provider because the credentials are invalid. Do not throw this
508+exception if the credentials are valid, but an operation failed due to
509+insufficient permission for an item (such as an attempt to write to a
510+read-only file).
511+
512+Typically, this will cause the request to be retried after refreshing
513+the authentication credentials, but may be returned to the client on
514+repeated failures.
515+
516+\see PermissionException
517+*/
518+class UNITY_STORAGE_EXPORT UnauthorizedException : public StorageException
519+{
520+public:
521+ UnauthorizedException(std::string const& error_message);
522+ ~UnauthorizedException();
523+};
524+
525+/**
526 \brief Indicates that an operation failed because of a permission problem.
527+
528+A provider implementation must throw this exception if it can
529+authenticate with its provider, but the provider denied the operation
530+due to insufficient permission for an item (such as an attempt to
531+write to a read-only file). Do not throw this exception for failure to
532+authenticate with the provider.
533+
534+\see UnauthorizedException
535 */
536 class UNITY_STORAGE_EXPORT PermissionException : public StorageException
537 {
538
539=== added file 'include/unity/storage/provider/Item.h'
540--- include/unity/storage/provider/Item.h 1970-01-01 00:00:00 +0000
541+++ include/unity/storage/provider/Item.h 2017-03-20 04:58:30 +0000
542@@ -0,0 +1,54 @@
543+/*
544+ * Copyright (C) 2016 Canonical Ltd
545+ *
546+ * This program is free software: you can redistribute it and/or modify
547+ * it under the terms of the GNU Lesser General Public License version 3 as
548+ * published by the Free Software Foundation.
549+ *
550+ * This program is distributed in the hope that it will be useful,
551+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
552+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
553+ * GNU Lesser General Public License for more details.
554+ *
555+ * You should have received a copy of the GNU Lesser General Public License
556+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
557+ *
558+ * Authors: James Henstridge <james.henstridge@canonical.com>
559+ */
560+
561+#pragma once
562+
563+#include <unity/storage/common.h>
564+#include <unity/storage/visibility.h>
565+
566+#include <boost/variant.hpp>
567+
568+#include <map>
569+#include <vector>
570+
571+namespace unity
572+{
573+namespace storage
574+{
575+namespace provider
576+{
577+
578+// Note: When growing the set of supported variant types, add new types
579+// to the *end* of the list, and update the marshaling code in dbusmarshal.cpp.
580+typedef boost::variant<std::string, int64_t> MetadataValue;
581+
582+struct UNITY_STORAGE_EXPORT Item
583+{
584+ std::string item_id;
585+ std::vector<std::string> parent_ids;
586+ std::string name;
587+ std::string etag;
588+ unity::storage::ItemType type;
589+ std::map<std::string, MetadataValue> metadata;
590+};
591+
592+typedef std::vector<Item> ItemList;
593+
594+}
595+}
596+}
597
598=== modified file 'include/unity/storage/provider/ProviderBase.h'
599--- include/unity/storage/provider/ProviderBase.h 2016-11-03 02:43:43 +0000
600+++ include/unity/storage/provider/ProviderBase.h 2017-03-20 04:58:30 +0000
601@@ -21,6 +21,7 @@
602 #include <unity/storage/common.h>
603 #include <unity/storage/visibility.h>
604 #include <unity/storage/provider/Credentials.h>
605+#include <unity/storage/provider/Item.h>
606
607 #include <boost/thread/future.hpp>
608 #include <boost/variant.hpp>
609@@ -50,22 +51,6 @@
610 Credentials credentials;
611 };
612
613-// Note: When growing the set of supported variant types, add new types
614-// to the *end* of the list, and update the marshaling code in dbusmarshal.cpp.
615-typedef boost::variant<std::string, int64_t> MetadataValue;
616-
617-struct UNITY_STORAGE_EXPORT Item
618-{
619- std::string item_id;
620- std::vector<std::string> parent_ids;
621- std::string name;
622- std::string etag;
623- unity::storage::ItemType type;
624- std::map<std::string, MetadataValue> metadata;
625-};
626-
627-typedef std::vector<Item> ItemList;
628-
629 class UNITY_STORAGE_EXPORT ProviderBase : public std::enable_shared_from_this<ProviderBase>
630 {
631 public:
632
633=== modified file 'include/unity/storage/provider/Server.h'
634--- include/unity/storage/provider/Server.h 2016-09-28 07:04:41 +0000
635+++ include/unity/storage/provider/Server.h 2017-03-20 04:58:30 +0000
636@@ -44,7 +44,7 @@
637 virtual ~ServerBase();
638
639 void init(int& argc, char** argv);
640- void run();
641+ int run();
642
643 protected:
644 virtual std::shared_ptr<ProviderBase> make_provider() = 0;
645
646=== modified file 'include/unity/storage/provider/internal/AccountData.h'
647--- include/unity/storage/provider/internal/AccountData.h 2016-09-28 10:03:40 +0000
648+++ include/unity/storage/provider/internal/AccountData.h 2017-03-20 04:58:30 +0000
649@@ -24,19 +24,20 @@
650 #pragma GCC diagnostic ignored "-Wcast-align"
651 #pragma GCC diagnostic ignored "-Wctor-dtor-privacy"
652 #pragma GCC diagnostic ignored "-Wswitch-default"
653-#include <OnlineAccounts/Account>
654-#include <OnlineAccounts/PendingCallWatcher>
655 #include <QObject>
656 #include <QDBusConnection>
657-#include <QPointer>
658 #pragma GCC diagnostic pop
659
660-#include <string>
661-
662 namespace unity
663 {
664 namespace storage
665 {
666+namespace internal
667+{
668+
669+class InactivityTimer;
670+
671+}
672 namespace provider
673 {
674
675@@ -54,37 +55,29 @@
676 public:
677 AccountData(std::shared_ptr<ProviderBase> const& provider,
678 std::shared_ptr<DBusPeerCache> const& dbus_peer,
679+ std::shared_ptr<unity::storage::internal::InactivityTimer> const& inactivity_timer,
680 QDBusConnection const& bus,
681- OnlineAccounts::Account* account,
682 QObject* parent=nullptr);
683 virtual ~AccountData();
684
685- void authenticate(bool interactive);
686- bool has_credentials();
687- Credentials const& credentials();
688+ virtual void authenticate(bool interactive, bool invalidate_cache=false) = 0;
689+ virtual bool has_credentials() = 0;
690+ virtual Credentials const& credentials() = 0;
691
692 ProviderBase& provider();
693 DBusPeerCache& dbus_peer();
694+ std::shared_ptr<unity::storage::internal::InactivityTimer> inactivity_timer();
695 PendingJobs& jobs();
696
697 Q_SIGNALS:
698 void authenticated();
699
700-private Q_SLOTS:
701- void on_authenticated();
702-
703 private:
704 std::shared_ptr<ProviderBase> const provider_;
705 std::shared_ptr<DBusPeerCache> const dbus_peer_;
706+ std::shared_ptr<unity::storage::internal::InactivityTimer> const inactivity_timer_;
707 std::unique_ptr<PendingJobs> const jobs_;
708
709- QPointer<OnlineAccounts::Account> const account_;
710- std::unique_ptr<OnlineAccounts::PendingCallWatcher> auth_watcher_;
711- bool authenticating_interactively_ = false;
712-
713- bool credentials_valid_ = false;
714- Credentials credentials_;
715-
716 Q_DISABLE_COPY(AccountData)
717 };
718
719
720=== modified file 'include/unity/storage/provider/internal/DownloadJobImpl.h'
721--- include/unity/storage/provider/internal/DownloadJobImpl.h 2016-09-02 13:51:37 +0000
722+++ include/unity/storage/provider/internal/DownloadJobImpl.h 2017-03-20 04:58:30 +0000
723@@ -18,6 +18,8 @@
724
725 #pragma once
726
727+#include <unity/storage/internal/ActivityNotifier.h>
728+
729 #include <QObject>
730
731 #include <boost/thread/future.hpp>
732@@ -47,6 +49,7 @@
733 std::string const& download_id() const;
734 int write_socket() const;
735 int take_read_socket();
736+ void set_activity(std::shared_ptr<unity::storage::internal::InactivityTimer> const& inactivity_timer);
737
738 void report_complete();
739 void report_error(std::exception_ptr p);
740@@ -65,6 +68,8 @@
741 bool completed_ = false;
742 boost::promise<void> completion_promise_;
743
744+ unity::storage::internal::ActivityNotifier activity_;
745+
746 Q_DISABLE_COPY(DownloadJobImpl)
747 };
748
749
750=== added file 'include/unity/storage/provider/internal/FixedAccountData.h'
751--- include/unity/storage/provider/internal/FixedAccountData.h 1970-01-01 00:00:00 +0000
752+++ include/unity/storage/provider/internal/FixedAccountData.h 2017-03-20 04:58:30 +0000
753@@ -0,0 +1,56 @@
754+/*
755+ * Copyright (C) 2017 Canonical Ltd
756+ *
757+ * This program is free software: you can redistribute it and/or modify
758+ * it under the terms of the GNU Lesser General Public License version 3 as
759+ * published by the Free Software Foundation.
760+ *
761+ * This program is distributed in the hope that it will be useful,
762+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
763+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
764+ * GNU Lesser General Public License for more details.
765+ *
766+ * You should have received a copy of the GNU Lesser General Public License
767+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
768+ *
769+ * Authors: James Henstridge <james.henstridge@canonical.com>
770+ */
771+
772+#pragma once
773+
774+#include <unity/storage/provider/internal/AccountData.h>
775+
776+namespace unity
777+{
778+namespace storage
779+{
780+namespace provider
781+{
782+namespace internal
783+{
784+
785+class FixedAccountData : public AccountData
786+{
787+ Q_OBJECT
788+public:
789+ FixedAccountData(std::shared_ptr<ProviderBase> const& provider,
790+ std::shared_ptr<DBusPeerCache> const& dbus_peer,
791+ std::shared_ptr<unity::storage::internal::InactivityTimer> const& inactivity_timer,
792+ QDBusConnection const& bus,
793+ QObject* parent=nullptr);
794+ virtual ~FixedAccountData();
795+
796+ void authenticate(bool interactive, bool invalidate_cache=false) override;
797+ bool has_credentials() override;
798+ Credentials const& credentials() override;
799+
800+private:
801+ Credentials credentials_ = boost::blank();
802+
803+ Q_DISABLE_COPY(FixedAccountData)
804+};
805+
806+}
807+}
808+}
809+}
810
811=== modified file 'include/unity/storage/provider/internal/Handler.h'
812--- include/unity/storage/provider/internal/Handler.h 2016-08-05 05:37:23 +0000
813+++ include/unity/storage/provider/internal/Handler.h 2017-03-20 04:58:30 +0000
814@@ -18,6 +18,7 @@
815
816 #pragma once
817
818+#include <unity/storage/internal/ActivityNotifier.h>
819 #include <unity/storage/provider/ProviderBase.h>
820 #include <unity/storage/provider/internal/DBusPeerCache.h>
821
822@@ -52,11 +53,12 @@
823 Callback const& callback,
824 QDBusConnection const& bus, QDBusMessage const& message);
825
826-public Q_SLOTS:
827 void begin();
828
829 private Q_SLOTS:
830+ void on_authenticated();
831 void credentials_received();
832+ void handle_unauthorized(std::exception_ptr ep);
833 void send_reply();
834
835 Q_SIGNALS:
836@@ -69,11 +71,13 @@
837 Callback const callback_;
838 QDBusConnection const bus_;
839 QDBusMessage const message_;
840+ unity::storage::internal::ActivityNotifier activity_;
841
842 boost::future<void> creds_future_;
843 boost::future<void> reply_future_;
844 Context context_;
845 QDBusMessage reply_;
846+ bool retry_ = false;
847
848 Q_DISABLE_COPY(Handler)
849 };
850@@ -82,3 +86,5 @@
851 }
852 }
853 }
854+
855+Q_DECLARE_METATYPE(std::exception_ptr)
856
857=== added file 'include/unity/storage/provider/internal/OnlineAccountData.h'
858--- include/unity/storage/provider/internal/OnlineAccountData.h 1970-01-01 00:00:00 +0000
859+++ include/unity/storage/provider/internal/OnlineAccountData.h 2017-03-20 04:58:30 +0000
860@@ -0,0 +1,75 @@
861+/*
862+ * Copyright (C) 2017 Canonical Ltd
863+ *
864+ * This program is free software: you can redistribute it and/or modify
865+ * it under the terms of the GNU Lesser General Public License version 3 as
866+ * published by the Free Software Foundation.
867+ *
868+ * This program is distributed in the hope that it will be useful,
869+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
870+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
871+ * GNU Lesser General Public License for more details.
872+ *
873+ * You should have received a copy of the GNU Lesser General Public License
874+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
875+ *
876+ * Authors: James Henstridge <james.henstridge@canonical.com>
877+ */
878+
879+#pragma once
880+
881+#include <unity/storage/provider/internal/AccountData.h>
882+
883+#pragma GCC diagnostic push
884+#pragma GCC diagnostic ignored "-Wcast-align"
885+#pragma GCC diagnostic ignored "-Wctor-dtor-privacy"
886+#pragma GCC diagnostic ignored "-Wswitch-default"
887+#include <OnlineAccounts/Account>
888+#include <OnlineAccounts/PendingCallWatcher>
889+#include <QPointer>
890+#pragma GCC diagnostic pop
891+
892+namespace unity
893+{
894+namespace storage
895+{
896+namespace provider
897+{
898+namespace internal
899+{
900+
901+class OnlineAccountData : public AccountData
902+{
903+ Q_OBJECT
904+public:
905+ OnlineAccountData(std::shared_ptr<ProviderBase> const& provider,
906+ std::shared_ptr<DBusPeerCache> const& dbus_peer,
907+ std::shared_ptr<unity::storage::internal::InactivityTimer> const& inactivity_timer,
908+ QDBusConnection const& bus,
909+ OnlineAccounts::Account* account,
910+ QObject* parent=nullptr);
911+ virtual ~OnlineAccountData();
912+
913+ void authenticate(bool interactive, bool invalidate_cache=false) override;
914+ bool has_credentials() override;
915+ Credentials const& credentials() override;
916+
917+private Q_SLOTS:
918+ void on_authenticated();
919+ void on_changed();
920+
921+private:
922+ QPointer<OnlineAccounts::Account> const account_;
923+ std::unique_ptr<OnlineAccounts::PendingCallWatcher> auth_watcher_;
924+ bool authenticating_interactively_ = false;
925+ bool authenticating_invalidate_cache_ = false;
926+
927+ Credentials credentials_ = boost::blank();
928+
929+ Q_DISABLE_COPY(OnlineAccountData)
930+};
931+
932+}
933+}
934+}
935+}
936
937=== modified file 'include/unity/storage/provider/internal/ServerImpl.h'
938--- include/unity/storage/provider/internal/ServerImpl.h 2016-08-12 06:19:22 +0000
939+++ include/unity/storage/provider/internal/ServerImpl.h 2017-03-20 04:58:30 +0000
940@@ -19,6 +19,7 @@
941 #pragma once
942
943 #include <unity/storage/provider/Server.h>
944+#include <unity/storage/internal/InactivityTimer.h>
945 #include <unity/storage/internal/TraceMessageHandler.h>
946 #include <unity/storage/provider/internal/DBusPeerCache.h>
947 #include <unity/storage/provider/internal/ProviderInterface.h>
948@@ -49,19 +50,32 @@
949 ServerImpl(ServerBase* server, std::string const& bus_name, std::string const& account_service_id);
950 ~ServerImpl();
951
952- void init(int& argc, char **argv);
953- void run();
954+ void init(int& argc, char **argv, QDBusConnection *bus = nullptr);
955+ int run();
956
957 private Q_SLOTS:
958- void account_manager_ready();
959+ void on_account_manager_ready();
960+ void on_account_available(OnlineAccounts::Account* account);
961+ void on_account_disabled();
962+ void on_timeout();
963+
964+Q_SIGNALS:
965+ void accountAdded();
966+ void accountRemoved();
967
968 private:
969+ void register_bus_name();
970+ void add_account(OnlineAccounts::Account* account);
971+ void remove_account(OnlineAccounts::Account* account);
972+
973 ServerBase* const server_;
974 std::string const bus_name_;
975 std::string const service_id_;
976 unity::storage::internal::TraceMessageHandler trace_message_handler_;
977
978 std::unique_ptr<QCoreApplication> app_;
979+ std::unique_ptr<QDBusConnection> bus_;
980+ std::shared_ptr<unity::storage::internal::InactivityTimer> inactivity_timer_;
981 std::unique_ptr<OnlineAccounts::Manager> manager_;
982 std::shared_ptr<DBusPeerCache> dbus_peer_;
983 std::map<OnlineAccounts::AccountId,std::unique_ptr<ProviderInterface>> interfaces_;
984
985=== modified file 'include/unity/storage/provider/internal/TestServerImpl.h'
986--- include/unity/storage/provider/internal/TestServerImpl.h 2016-09-28 07:04:41 +0000
987+++ include/unity/storage/provider/internal/TestServerImpl.h 2017-03-20 04:58:30 +0000
988@@ -33,6 +33,12 @@
989 {
990 namespace storage
991 {
992+namespace internal
993+{
994+
995+class InactivityTimer;
996+
997+}
998 namespace provider
999 {
1000 namespace internal
1001@@ -56,6 +62,7 @@
1002 QDBusConnection connection_;
1003 std::string const object_path_;
1004
1005+ std::shared_ptr<unity::storage::internal::InactivityTimer> inactivity_timer_;
1006 std::unique_ptr<ProviderInterface> interface_;
1007 };
1008
1009
1010=== modified file 'include/unity/storage/provider/internal/UploadJobImpl.h'
1011--- include/unity/storage/provider/internal/UploadJobImpl.h 2016-09-02 13:51:37 +0000
1012+++ include/unity/storage/provider/internal/UploadJobImpl.h 2017-03-20 04:58:30 +0000
1013@@ -18,6 +18,7 @@
1014
1015 #pragma once
1016
1017+#include <unity/storage/internal/ActivityNotifier.h>
1018 #include <unity/storage/provider/ProviderBase.h>
1019
1020 #include <boost/thread/future.hpp>
1021@@ -28,6 +29,7 @@
1022 #pragma GCC diagnostic pop
1023
1024 #include <exception>
1025+#include <memory>
1026 #include <mutex>
1027 #include <string>
1028
1029@@ -52,6 +54,7 @@
1030 std::string const& upload_id() const;
1031 int read_socket() const;
1032 int take_write_socket();
1033+ void set_activity(std::shared_ptr<unity::storage::internal::InactivityTimer> const& inactivity_timer);
1034
1035 void report_error(std::exception_ptr p);
1036 boost::future<Item> finish(UploadJob& job);
1037@@ -69,6 +72,8 @@
1038 bool completed_ = false;
1039 boost::promise<Item> completion_promise_;
1040
1041+ unity::storage::internal::ActivityNotifier activity_;
1042+
1043 Q_DISABLE_COPY(UploadJobImpl)
1044 };
1045
1046
1047=== modified file 'include/unity/storage/qt/StorageError.h'
1048--- include/unity/storage/qt/StorageError.h 2016-10-12 06:05:29 +0000
1049+++ include/unity/storage/qt/StorageError.h 2017-03-20 04:58:30 +0000
1050@@ -58,8 +58,9 @@
1051
1052 enum Type
1053 {
1054- NoError, LocalCommsError, RemoteCommsError, RuntimeDestroyed, NotExists, Exists, Conflict,
1055- PermissionDenied, Cancelled, LogicError, InvalidArgument, ResourceError, QuotaExceeded,
1056+ NoError, LocalCommsError, RemoteCommsError, RuntimeDestroyed,
1057+ NotExists, Exists, Conflict, PermissionDenied, Cancelled, LogicError,
1058+ InvalidArgument, ResourceError, QuotaExceeded, Unauthorized,
1059 __LAST_STORAGE_ERROR
1060 };
1061 Q_ENUMS(Type)
1062
1063=== modified file 'include/unity/storage/registry/Registry.h'
1064--- include/unity/storage/registry/Registry.h 2016-11-22 07:23:48 +0000
1065+++ include/unity/storage/registry/Registry.h 2017-03-20 04:58:30 +0000
1066@@ -29,6 +29,7 @@
1067
1068 static QString const BUS_NAME(QStringLiteral("com.canonical.StorageFramework.Registry"));
1069 static QString const OBJECT_PATH(QStringLiteral("/com/canonical/StorageFramework/Registry"));
1070+static QString const INTERFACE(QStringLiteral("com.canonical.StorageFramework.Registry"));
1071
1072 } // namespace registry
1073 } // namespace storage
1074
1075=== added directory 'snap'
1076=== added file 'snap/snapcraft.yaml'
1077--- snap/snapcraft.yaml 1970-01-01 00:00:00 +0000
1078+++ snap/snapcraft.yaml 2017-03-20 04:58:30 +0000
1079@@ -0,0 +1,128 @@
1080+name: storage-framework-service
1081+version: '0.2'
1082+summary: Service for accessing cloud-based storage providers.
1083+description: >
1084+ This snap provides a service to access cloud-based storage providers, such as
1085+ OneDrive, Owncloud, Google Drive, or mCloud, plus a client-side API for applications.
1086+ See lp:storage-framework for details on the framework, as well as
1087+ lp:storage-provider-onedrive, lp:storage-provider-webdav, lp:storage-provider-gdrive,
1088+ and lp:mcloud.
1089+grade: devel
1090+confinement: strict
1091+
1092+slots:
1093+ storage-framework-service:
1094+ interface: storage-framework-service
1095+ # Allow clients access to the client library
1096+ client-libs:
1097+ interface: content
1098+ content: client-libs
1099+ read:
1100+ - $SNAP/client
1101+ # Allow providers access to the provider library
1102+ provider-libs:
1103+ interface: content
1104+ content: provider-libs
1105+ read:
1106+ - $SNAP/provider
1107+
1108+plugs:
1109+ platform:
1110+ interface: content
1111+ content: ubuntu-app-platform1
1112+ target: ubuntu-app-platform
1113+ default-provider: ubuntu-app-platform
1114+
1115+apps:
1116+ storage-framework-registry:
1117+ command: desktop-launch $SNAP/lib/storage-framework/storage-framework-registry
1118+ plugs:
1119+ - platform
1120+ slots:
1121+ - storage-framework-service
1122+ storage-provider-owncloud:
1123+ # snap-launch sets LD_LIBRARY_PATH to include the provider directory (exposed via content interface).
1124+ command: snap-launch $SNAP/provider/lib $SNAP/lib/storage-provider-webdav/storage-provider-owncloud
1125+ plugs:
1126+ - platform
1127+ - network
1128+ slots:
1129+ - storage-framework-service
1130+
1131+parts:
1132+ storage-framework:
1133+ plugin: cmake
1134+ configflags:
1135+ - -DSNAP_BUILD=1
1136+ source: .
1137+ after:
1138+ - desktop-ubuntu-app-platform
1139+ organize:
1140+ lib/libstorage-framework-qt*: client/lib/
1141+ lib/libstorage-framework-provider*: provider/lib/
1142+ filesets:
1143+ binaries:
1144+ - bin/snap-launch
1145+ - lib/storage-framework/*
1146+ - client/*
1147+ - provider/*
1148+ - -lib/pkgconfig
1149+ dbus:
1150+ - share/dbus-1/services/com.canonical.StorageFramework.*
1151+ install: |
1152+ # Make sure we have a mount point for ubuntu-app-platform
1153+ mkdir -p $SNAPCRAFT_PART_INSTALL/ubuntu-app-platform
1154+ # Fix pkgconfig files to point at the parts subtree so
1155+ # the providers will build correctly.
1156+ sed -e "s@-I/include@-I${SNAPCRAFT_PART_INSTALL}/include@" \
1157+ -e "s@-L/lib@-L${SNAPCRAFT_PART_INSTALL}/provider/lib@" \
1158+ -i $SNAPCRAFT_PART_INSTALL/lib/pkgconfig/storage-framework-provider-1.pc
1159+ sed -e "s@-I/include@-I${SNAPCRAFT_PART_INSTALL}/include@" \
1160+ -e "s@-L/lib@-L${SNAPCRAFT_PART_INSTALL}/client/lib@" \
1161+ -i $SNAPCRAFT_PART_INSTALL/lib/pkgconfig/storage-framework-qt-local-client-1.pc
1162+ sed -e "s@-I/include@-I${SNAPCRAFT_PART_INSTALL}/include@" \
1163+ -e "s@-L/lib@-L${SNAPCRAFT_PART_INSTALL}/client/lib@" \
1164+ -i $SNAPCRAFT_PART_INSTALL/lib/pkgconfig/storage-framework-qt-client-1.pc
1165+ sed -e "s@-I/include@-I${SNAPCRAFT_PART_INSTALL}/include@" \
1166+ -e "s@-L/lib@-L${SNAPCRAFT_PART_INSTALL}/client/lib@" \
1167+ -i $SNAPCRAFT_PART_INSTALL/lib/pkgconfig/storage-framework-qt-client-2.pc
1168+ prime:
1169+ - $binaries
1170+ - ubuntu-app-platform
1171+
1172+ build-packages:
1173+ - cmake-extras
1174+ - doxygen
1175+ - google-mock
1176+ - libapparmor-dev
1177+ - libboost-filesystem-dev
1178+ - libboost-system-dev
1179+ - libboost-thread-dev
1180+ - libglib2.0-dev
1181+ - libgtest-dev
1182+ - libonline-accounts-qt-dev
1183+ - libqtdbustest1-dev
1184+ - libunity-api-dev
1185+ - lsb-release
1186+ - python3-dbus
1187+ - python3-gi
1188+ - qtbase5-dev
1189+ - qtbase5-dev-tools
1190+ - qtdeclarative5-dev
1191+
1192+ # For now, the providers are part of the storage-framework snap. Eventually, they will have to each
1193+ # live inside their own snap.
1194+ storage-provider-owncloud:
1195+ plugin: cmake
1196+ configflags:
1197+ - -DSNAP_BUILD=1
1198+ source: lp:storage-provider-webdav
1199+ after:
1200+ - storage-framework
1201+ filesets:
1202+ binaries:
1203+ - lib/storage-provider-webdav/*
1204+ prime:
1205+ - $binaries
1206+
1207+# TODO: Add other providers (OneDrive, Google Drive, mCloud)
1208
1209=== modified file 'src/CMakeLists.txt'
1210--- src/CMakeLists.txt 2016-11-07 04:49:34 +0000
1211+++ src/CMakeLists.txt 2017-03-20 04:58:30 +0000
1212@@ -2,3 +2,4 @@
1213 add_subdirectory(provider)
1214 add_subdirectory(qt)
1215 add_subdirectory(registry)
1216+add_subdirectory(local-provider)
1217
1218=== modified file 'src/internal/EnvVars.cpp'
1219--- src/internal/EnvVars.cpp 2016-11-21 13:29:39 +0000
1220+++ src/internal/EnvVars.cpp 2017-03-20 04:58:30 +0000
1221@@ -34,34 +34,44 @@
1222
1223 int EnvVars::registry_timeout_ms()
1224 {
1225- int const dflt_val = REGISTRY_IDLE_TIMEOUT_DFLT * 1000;
1226-
1227- auto const val = get(REGISTRY_IDLE_TIMEOUT);
1228- if (val.empty())
1229- {
1230- return dflt_val;
1231- }
1232- try
1233- {
1234- size_t pos;
1235- auto int_val = stoi(val, &pos);
1236- if (pos != val.size())
1237- {
1238- throw invalid_argument("unexpected trailing character(s)");
1239- }
1240- if (int_val < 0)
1241- {
1242- throw invalid_argument("value must be >= 0");
1243- }
1244- return int_val;
1245- }
1246- catch (std::exception const& e)
1247- {
1248- qWarning().noquote().nospace() << "Invalid setting of env var " << QString::fromStdString(REGISTRY_IDLE_TIMEOUT)
1249- << " (\"" << QString::fromStdString(val) << "\"): " << e.what();
1250- qWarning().nospace() << "Using default value of " << REGISTRY_IDLE_TIMEOUT_DFLT;
1251- }
1252- return dflt_val;
1253+ return get_timeout_ms(REGISTRY_IDLE_TIMEOUT, REGISTRY_IDLE_TIMEOUT_DFLT);
1254+}
1255+
1256+int EnvVars::provider_timeout_ms()
1257+{
1258+ return get_timeout_ms(PROVIDER_IDLE_TIMEOUT, PROVIDER_IDLE_TIMEOUT_DFLT);
1259+}
1260+
1261+int EnvVars::get_timeout_ms(char const* var_name, int dflt)
1262+{
1263+ int timeout = dflt;
1264+
1265+ auto const val = get(var_name);
1266+ if (!val.empty())
1267+ {
1268+ try
1269+ {
1270+ size_t pos;
1271+ auto int_val = stoi(val, &pos);
1272+ if (pos != val.size())
1273+ {
1274+ throw invalid_argument("unexpected trailing character(s)");
1275+ }
1276+ if (int_val < 0)
1277+ {
1278+ throw invalid_argument("value must be >= 0");
1279+ }
1280+ timeout = int_val;
1281+ }
1282+ catch (std::exception const& e)
1283+ {
1284+ qWarning().noquote().nospace()
1285+ << "Invalid setting of env var " << var_name
1286+ << " (\"" << QString::fromStdString(val) << "\"): " << e.what();
1287+ qWarning().nospace() << "Using default value of " << dflt;
1288+ }
1289+ }
1290+ return timeout * 1000;
1291 }
1292
1293 string EnvVars::get(char const* var_name)
1294
1295=== modified file 'src/internal/InactivityTimer.cpp'
1296--- src/internal/InactivityTimer.cpp 2016-11-08 11:17:52 +0000
1297+++ src/internal/InactivityTimer.cpp 2017-03-20 04:58:30 +0000
1298@@ -18,8 +18,6 @@
1299
1300 #include <unity/storage/internal/InactivityTimer.h>
1301
1302-#include <QDebug>
1303-
1304 #include <cassert>
1305
1306 namespace unity
1307@@ -29,14 +27,12 @@
1308 namespace internal
1309 {
1310
1311-InactivityTimer::InactivityTimer(int timeout_ms, std::function<void()> timeout_func)
1312- : timeout_ms_(timeout_ms)
1313- , timeout_func_(timeout_func)
1314- , num_requests_(0)
1315+InactivityTimer::InactivityTimer(int timeout_ms)
1316 {
1317- assert(timeout_ms_ >= 0);
1318- assert(timeout_func);
1319+ assert(timeout_ms >= 0);
1320
1321+ timer_.setInterval(timeout_ms);
1322+ timer_.setSingleShot(true);
1323 connect(&timer_, &QTimer::timeout, this, &InactivityTimer::timeout);
1324 }
1325
1326@@ -58,22 +54,7 @@
1327
1328 if (--num_requests_ == 0)
1329 {
1330- timer_.start(timeout_ms_);
1331- }
1332-}
1333-
1334-void InactivityTimer::timeout()
1335-{
1336- timer_.stop();
1337- disconnect(this);
1338- try
1339- {
1340- timeout_func_();
1341- }
1342- catch (std::exception const& e)
1343- {
1344- auto msg = QString("InactivityTimer::timeout(): exception from timeout callback: ") + e.what();
1345- qWarning().nospace() << msg;
1346+ timer_.start();
1347 }
1348 }
1349
1350
1351=== added directory 'src/local-provider'
1352=== added file 'src/local-provider/CMakeLists.txt'
1353--- src/local-provider/CMakeLists.txt 1970-01-01 00:00:00 +0000
1354+++ src/local-provider/CMakeLists.txt 2017-03-20 04:58:30 +0000
1355@@ -0,0 +1,43 @@
1356+add_definitions(-DBOOST_THREAD_VERSION=4)
1357+
1358+add_library(local-provider-lib STATIC
1359+ LocalDownloadJob.cpp
1360+ LocalProvider.cpp
1361+ LocalUploadJob.cpp
1362+ utils.cpp
1363+)
1364+
1365+target_include_directories(local-provider-lib PRIVATE
1366+ ${Qt5Network_INCLUDE_DIRS}
1367+ ${GLIB_DEPS_INCLUDE_DIRS}
1368+ ${GIO_DEPS_INCLUDE_DIRS}
1369+)
1370+
1371+set_target_properties(local-provider-lib PROPERTIES
1372+ AUTOMOC TRUE
1373+ POSITION_INDEPENDENT_CODE TRUE
1374+)
1375+
1376+add_executable(storage-provider-local
1377+ main.cpp
1378+)
1379+
1380+target_link_libraries(storage-provider-local
1381+ local-provider-lib
1382+ storage-framework-provider
1383+ Qt5::Network
1384+ ${GLIB_DEPS_LIBRARIES}
1385+ ${GIO_DEPS_LIBRARIES}
1386+)
1387+
1388+install(
1389+ TARGETS storage-provider-local
1390+ RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME}
1391+)
1392+
1393+configure_file(com.canonical.StorageFramework.Provider.Local.service.in com.canonical.StorageFramework.Provider.Local.service)
1394+
1395+install(
1396+ FILES ${CMAKE_CURRENT_BINARY_DIR}/com.canonical.StorageFramework.Provider.Local.service
1397+ DESTINATION ${CMAKE_INSTALL_DATADIR}/dbus-1/services
1398+)
1399
1400=== added file 'src/local-provider/LocalDownloadJob.cpp'
1401--- src/local-provider/LocalDownloadJob.cpp 1970-01-01 00:00:00 +0000
1402+++ src/local-provider/LocalDownloadJob.cpp 2017-03-20 04:58:30 +0000
1403@@ -0,0 +1,179 @@
1404+/*
1405+ * Copyright (C) 2017 Canonical Ltd
1406+ *
1407+ * This program is free software: you can redistribute it and/or modify
1408+ * it under the terms of the GNU Lesser General Public License version 3 as
1409+ * published by the Free Software Foundation.
1410+ *
1411+ * This program is distributed in the hope that it will be useful,
1412+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1413+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1414+ * GNU Lesser General Public License for more details.
1415+ *
1416+ * You should have received a copy of the GNU Lesser General Public License
1417+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1418+ *
1419+ * Authors: Michi Henning <michi.henning@canonical.com>
1420+ */
1421+
1422+#include "LocalDownloadJob.h"
1423+
1424+#include "LocalProvider.h"
1425+#include "utils.h"
1426+#include <unity/storage/internal/safe_strerror.h>
1427+#include <unity/storage/provider/Exceptions.h>
1428+
1429+using namespace unity::storage::provider;
1430+using namespace std;
1431+
1432+static int next_download_id = 0;
1433+
1434+string const method = "download()";
1435+
1436+LocalDownloadJob::LocalDownloadJob(shared_ptr<LocalProvider> const& provider,
1437+ string const& item_id,
1438+ string const& match_etag)
1439+ : DownloadJob(to_string(++next_download_id))
1440+ , provider_(provider)
1441+ , item_id_(item_id)
1442+{
1443+ using namespace boost::filesystem;
1444+
1445+ // Sanitize parameters.
1446+ provider_->throw_if_not_valid(method, item_id_);
1447+ try
1448+ {
1449+ auto st = status(item_id_);
1450+ if (!is_regular_file(st))
1451+ {
1452+ throw InvalidArgumentException(method + ": \"" + item_id_ + "\" is not a file");
1453+ }
1454+ }
1455+ // LCOV_EXCL_START // Too small a window to hit with a test.
1456+ catch (filesystem_error const& e)
1457+ {
1458+ throw_storage_exception(method, e);
1459+ }
1460+ // LCOV_EXCL_STOP
1461+ if (!match_etag.empty())
1462+ {
1463+ int64_t mtime = get_mtime_nsecs(method, item_id_);
1464+ if (to_string(mtime) != match_etag)
1465+ {
1466+ throw ConflictException(method + ": etag mismatch");
1467+ }
1468+ }
1469+
1470+ // Make input file ready.
1471+ QString filename = QString::fromStdString(item_id);
1472+ file_.reset(new QFile(filename));
1473+ if (!file_->open(QIODevice::ReadOnly))
1474+ {
1475+ throw_storage_exception(method,
1476+ ": cannot open \"" + item_id + "\": " + file_->errorString().toStdString(),
1477+ file_->error());
1478+ }
1479+ bytes_to_write_ = file_->size();
1480+
1481+ // Make write socket ready.
1482+ int dup_fd = dup(write_socket());
1483+ if (dup_fd == -1)
1484+ {
1485+ // LCOV_EXCL_START
1486+ string msg = "LocalDownloadJob(): dup() failed: " + unity::storage::internal::safe_strerror(errno);
1487+ throw ResourceException(msg, errno);
1488+ // LCOV_EXCL_STOP
1489+ }
1490+ write_socket_.setSocketDescriptor(dup_fd, QLocalSocket::ConnectedState, QIODevice::WriteOnly);
1491+ connect(&write_socket_, &QIODevice::bytesWritten, this, &LocalDownloadJob::on_bytes_written);
1492+
1493+ // Kick off the read-write cycle.
1494+ QMetaObject::invokeMethod(this, "read_and_write_chunk", Qt::QueuedConnection);
1495+}
1496+
1497+LocalDownloadJob::~LocalDownloadJob() = default;
1498+
1499+boost::future<void> LocalDownloadJob::cancel()
1500+{
1501+ disconnect(&write_socket_, nullptr, this, nullptr);
1502+ write_socket_.abort();
1503+ file_->close();
1504+ return boost::make_ready_future();
1505+}
1506+
1507+boost::future<void> LocalDownloadJob::finish()
1508+{
1509+ if (bytes_to_write_ > 0)
1510+ {
1511+ auto file_size = file_->size();
1512+ auto written = file_size - bytes_to_write_;
1513+ string msg = "finish() method called too early, file \"" + item_id_ + "\" has size "
1514+ + to_string(file_size) + " but only " + to_string(written) + " bytes were consumed";
1515+ cancel();
1516+ return boost::make_exceptional_future<void>(LogicException(msg));
1517+ }
1518+ // LCOV_EXCL_START
1519+ // Not reachable because we call report_complete() in read_and_write_chunk().
1520+ return boost::make_ready_future();
1521+ // LCOV_EXCL_STOP
1522+}
1523+
1524+void LocalDownloadJob::on_bytes_written(qint64 bytes)
1525+{
1526+ bytes_to_write_ -= bytes;
1527+ assert(bytes_to_write_ >= 0);
1528+ read_and_write_chunk();
1529+}
1530+
1531+void LocalDownloadJob::read_and_write_chunk()
1532+{
1533+ static qint64 constexpr READ_SIZE = 64 * 1024;
1534+
1535+ if (bytes_to_write_ == 0)
1536+ {
1537+ file_->close();
1538+ write_socket_.close();
1539+ report_complete();
1540+ return;
1541+ }
1542+
1543+ QByteArray buf;
1544+ buf.resize(READ_SIZE);
1545+ auto bytes_read = file_->read(buf.data(), buf.size());
1546+ try
1547+ {
1548+ if (bytes_read == -1)
1549+ {
1550+ // LCOV_EXCL_START
1551+ string msg = string("\"") + item_id_ + "\": read error: " + file_->errorString().toStdString();
1552+ throw_storage_exception(method, msg, file_->error());
1553+ // LCOV_EXCL_STOP
1554+ }
1555+ buf.resize(bytes_read);
1556+
1557+ auto bytes_written = write_socket_.write(buf);
1558+ if (bytes_written == -1)
1559+ {
1560+ // LCOV_EXCL_START
1561+ string msg = string("\"") + item_id_ + "\": socket error: " + write_socket_.errorString().toStdString();
1562+ throw_storage_exception(method, msg, write_socket_.error());
1563+ // LCOV_EXCL_STOP
1564+ }
1565+ else if (bytes_written != bytes_read)
1566+ {
1567+ // LCOV_EXCL_START
1568+ string msg = string("\"") + item_id_ + "\": socket write error, requested " + to_string(bytes_read)
1569+ + " B, but wrote only " + to_string(bytes_written) + " B.";
1570+ throw_storage_exception(method, msg, QLocalSocket::UnknownSocketError);
1571+ // LCOV_EXCL_STOP
1572+ }
1573+ }
1574+ // LCOV_EXCL_START
1575+ catch (std::exception const&)
1576+ {
1577+ write_socket_.abort();
1578+ file_->close();
1579+ report_error(current_exception());
1580+ }
1581+ // LCOV_EXCL_STOP
1582+}
1583
1584=== added file 'src/local-provider/LocalDownloadJob.h'
1585--- src/local-provider/LocalDownloadJob.h 1970-01-01 00:00:00 +0000
1586+++ src/local-provider/LocalDownloadJob.h 2017-03-20 04:58:30 +0000
1587@@ -0,0 +1,54 @@
1588+/*
1589+ * Copyright (C) 2017 Canonical Ltd
1590+ *
1591+ * This program is free software: you can redistribute it and/or modify
1592+ * it under the terms of the GNU Lesser General Public License version 3 as
1593+ * published by the Free Software Foundation.
1594+ *
1595+ * This program is distributed in the hope that it will be useful,
1596+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1597+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1598+ * GNU Lesser General Public License for more details.
1599+ *
1600+ * You should have received a copy of the GNU Lesser General Public License
1601+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1602+ *
1603+ * Authors: Michi Henning <michi.henning@canonical.com>
1604+ */
1605+
1606+#pragma once
1607+
1608+#include <unity/storage/provider/DownloadJob.h>
1609+
1610+#pragma GCC diagnostic push
1611+#pragma GCC diagnostic ignored "-Wctor-dtor-privacy"
1612+#pragma GCC diagnostic ignored "-Wswitch-default"
1613+#include <QFile>
1614+#include <QLocalSocket>
1615+#pragma GCC diagnostic pop
1616+
1617+class LocalProvider;
1618+
1619+class LocalDownloadJob : public QObject, public unity::storage::provider::DownloadJob
1620+{
1621+ Q_OBJECT
1622+public:
1623+ LocalDownloadJob(std::shared_ptr<LocalProvider> const& provider,
1624+ std::string const& item_id,
1625+ std::string const& match_etag);
1626+ virtual ~LocalDownloadJob();
1627+
1628+ virtual boost::future<void> cancel() override;
1629+ virtual boost::future<void> finish() override;
1630+
1631+private Q_SLOTS:
1632+ void on_bytes_written(qint64 bytes);
1633+ void read_and_write_chunk();
1634+
1635+private:
1636+ std::shared_ptr<LocalProvider> const provider_;
1637+ std::string const item_id_;
1638+ std::unique_ptr<QFile> file_;
1639+ QLocalSocket write_socket_;
1640+ int64_t bytes_to_write_;
1641+};
1642
1643=== added file 'src/local-provider/LocalProvider.cpp'
1644--- src/local-provider/LocalProvider.cpp 1970-01-01 00:00:00 +0000
1645+++ src/local-provider/LocalProvider.cpp 2017-03-20 04:58:30 +0000
1646@@ -0,0 +1,586 @@
1647+/*
1648+ * Copyright (C) 2017 Canonical Ltd
1649+ *
1650+ * This program is free software: you can redistribute it and/or modify
1651+ * it under the terms of the GNU Lesser General Public License version 3 as
1652+ * published by the Free Software Foundation.
1653+ *
1654+ * This program is distributed in the hope that it will be useful,
1655+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1656+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1657+ * GNU Lesser General Public License for more details.
1658+ *
1659+ * You should have received a copy of the GNU Lesser General Public License
1660+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1661+ *
1662+ * Authors: Michi Henning <michi.henning@canonical.com>
1663+ */
1664+
1665+#include "LocalProvider.h"
1666+
1667+#include "LocalDownloadJob.h"
1668+#include "LocalUploadJob.h"
1669+#include "utils.h"
1670+
1671+#include <unity/storage/internal/gobj_memory.h>
1672+#include <unity/storage/provider/Exceptions.h>
1673+
1674+#include <boost/algorithm/string.hpp>
1675+
1676+#pragma GCC diagnostic push
1677+#pragma GCC diagnostic ignored "-Wold-style-cast"
1678+#include <gio/gio.h>
1679+#include <glib.h>
1680+#pragma GCC diagnostic pop
1681+
1682+using namespace unity::storage::provider;
1683+using namespace std;
1684+
1685+namespace
1686+{
1687+
1688+// Return the root directory where we store files.
1689+// If SF_LOCAL_PROVIDER_ROOT is set (used for testing), any files are created
1690+// directly under the root. E.g., if we do root.createFile("foo.txt", ...), the file
1691+// will be created as ${SF_LOCAL_PROVIDER_ROOT/foo.txt. SF_LOCAL_PROVIDER_ROOT must
1692+// be a pre-existing directory.
1693+//
1694+// Otherwise, the root is determined by SNAP_USER_COMMON or, if that is not set,
1695+// by XDG_DATA_HOME. Either way, files are created in a storage-framework/local
1696+// subdirectory. E.g., if SNAP_USER_COMMON or XDG_DATA_HOME is set to "/tmp" and
1697+// we do root.createFile("foo.txt", ...), the file will be created as
1698+// /tmp/storage-framework/local/foo.txt. If /tmp/storage-framework/local does not exist,
1699+// the directory will be created.
1700+
1701+string get_root_dir(string const& method)
1702+{
1703+ using namespace boost::filesystem;
1704+
1705+ char const* dir = getenv("SF_LOCAL_PROVIDER_ROOT");
1706+ if (dir && *dir != '\0')
1707+ {
1708+ boost::system::error_code ec;
1709+ if (!exists(dir, ec) || !is_directory(dir, ec))
1710+ {
1711+ string msg = method + ": Environment variable SF_LOCAL_PROVIDER_ROOT must denote an existing directory";
1712+ throw InvalidArgumentException(msg);
1713+ }
1714+ return dir;
1715+ }
1716+
1717+ string data_dir;
1718+ dir = getenv("SNAP_USER_COMMON");
1719+ if (dir && *dir != '\0')
1720+ {
1721+ data_dir = dir;
1722+ }
1723+ else
1724+ {
1725+ data_dir = g_get_user_data_dir(); // Never fails.
1726+ }
1727+ data_dir += "/storage-framework/local";
1728+
1729+ try
1730+ {
1731+ create_directories(data_dir);
1732+ }
1733+ catch (filesystem_error const& e)
1734+ {
1735+ throw_storage_exception(method, e);
1736+ }
1737+
1738+ return data_dir;
1739+}
1740+
1741+// Copy a file or directory (recursively). Ignore anything that has the temp file prefix
1742+// or is not a file or directory.
1743+
1744+void copy_recursively(boost::filesystem::path const& source, boost::filesystem::path const& target)
1745+{
1746+ using namespace boost::filesystem;
1747+
1748+ if (is_reserved_path(source))
1749+ {
1750+ return; // Don't copy temporary directories.
1751+ }
1752+
1753+ auto s = status(source);
1754+ if (is_regular_file(s))
1755+ {
1756+ copy_file(source, target);
1757+ }
1758+ else if (is_directory(s))
1759+ {
1760+ copy_directory(source, target); // Poorly named in boost; this creates the target dir without recursion
1761+ for (directory_iterator it(source); it != directory_iterator(); ++it)
1762+ {
1763+ path source_entry = it->path();
1764+ path target_entry = target;
1765+ target_entry /= source_entry.filename();
1766+ copy_recursively(source_entry, target_entry);
1767+ }
1768+ }
1769+ else
1770+ {
1771+ // Ignore everything that's not a directory or file.
1772+ }
1773+}
1774+
1775+// Convert nanoseconds since the epoch into ISO 8601 date-time.
1776+
1777+string make_iso_date(int64_t nsecs_since_epoch)
1778+{
1779+ static char const* const FMT = "%Y-%m-%dT%TZ"; // ISO 8601, no fractional seconds.
1780+
1781+ struct tm time;
1782+ time_t secs_since_epoch = nsecs_since_epoch / 1000000000;
1783+ gmtime_r(&secs_since_epoch, &time);
1784+
1785+ char buf[128];
1786+ strftime(buf, sizeof(buf), FMT, &time);
1787+ return buf;
1788+}
1789+
1790+string get_content_type(string const& filename)
1791+{
1792+ using namespace unity::storage::internal;
1793+
1794+ static string const unknown_content_type = "application/octet-stream";
1795+
1796+ gobj_ptr<GFile> file(g_file_new_for_path(filename.c_str()));
1797+ assert(file); // Cannot fail according to doc.
1798+
1799+ GError* err = nullptr;
1800+ gobj_ptr<GFileInfo> full_info(g_file_query_info(file.get(),
1801+ G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE,
1802+ G_FILE_QUERY_INFO_NONE,
1803+ /* cancellable */ NULL,
1804+ &err));
1805+ if (!full_info)
1806+ {
1807+ return unknown_content_type; // LCOV_EXCL_LINE
1808+ }
1809+
1810+ string content_type = g_file_info_get_attribute_string(full_info.get(), G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE);
1811+ if (content_type.empty())
1812+ {
1813+ return unknown_content_type; // LCOV_EXCL_LINE
1814+ }
1815+ return content_type;
1816+}
1817+
1818+// Simple wrapper template that deals with exception handling so we don't
1819+// have to repeat ourselves endlessly in the various lambdas below.
1820+// The auto deduction of the return type requires C++ 14.
1821+
1822+template<typename F>
1823+auto invoke_async(string const& method, F& functor)
1824+{
1825+ auto lambda = [method, functor]
1826+ {
1827+ try
1828+ {
1829+ return functor();
1830+ }
1831+ catch (StorageException const&)
1832+ {
1833+ throw;
1834+ }
1835+ catch (boost::filesystem::filesystem_error const& e)
1836+ {
1837+ throw_storage_exception(method, e);
1838+ }
1839+ // LCOV_EXCL_START
1840+ catch (std::exception const& e)
1841+ {
1842+ throw boost::enable_current_exception(UnknownException(e.what()));
1843+ }
1844+ // LCOV_EXCL_STOP
1845+ };
1846+ // TODO: boost::async is potentially expensive for some operations. Consider boost::asio thread pool?
1847+ return boost::async(boost::launch::async, lambda);
1848+}
1849+
1850+} // namespace
1851+
1852+LocalProvider::LocalProvider()
1853+ : root_(boost::filesystem::canonical(get_root_dir("LocalProvider()")))
1854+{
1855+}
1856+
1857+LocalProvider::~LocalProvider() = default;
1858+
1859+boost::future<ItemList> LocalProvider::roots(vector<string> const& /* keys */, Context const& /* context */)
1860+{
1861+ vector<Item> roots{ make_item("roots()", root_, status(root_)) };
1862+ return boost::make_ready_future(roots);
1863+}
1864+
1865+boost::future<tuple<ItemList, string>> LocalProvider::list(string const& item_id,
1866+ string const& page_token,
1867+ vector<string> const& /* keys */,
1868+ Context const& /* context */)
1869+{
1870+ string const method = "list()";
1871+
1872+ auto This = dynamic_pointer_cast<LocalProvider>(shared_from_this());
1873+ auto do_list = [This, method, item_id, page_token]
1874+ {
1875+ using namespace boost::filesystem;
1876+
1877+ This->throw_if_not_valid(method, item_id);
1878+ vector<Item> items;
1879+ for (directory_iterator it(item_id); it != directory_iterator(); ++it)
1880+ {
1881+ auto dirent = *it;
1882+ auto path = dirent.path();
1883+ if (is_reserved_path(path))
1884+ {
1885+ continue; // Hide temp files that we create during copy() and move().
1886+ }
1887+ Item i;
1888+ try
1889+ {
1890+ auto st = dirent.status();
1891+ i = This->make_item(method, path, st);
1892+ items.push_back(i);
1893+ }
1894+ catch (std::exception const&)
1895+ {
1896+ // We ignore weird errors (such as entries that are not files or folders).
1897+ }
1898+ }
1899+ return tuple<ItemList, string>(items, "");
1900+ };
1901+
1902+ return invoke_async(method, do_list);
1903+}
1904+
1905+boost::future<ItemList> LocalProvider::lookup(string const& parent_id,
1906+ string const& name,
1907+ vector<string> const& /* keys */,
1908+ Context const& /* context */)
1909+{
1910+ string const method = "lookup()";
1911+
1912+ auto This = dynamic_pointer_cast<LocalProvider>(shared_from_this());
1913+ auto do_lookup = [This, method, parent_id, name]
1914+ {
1915+ using namespace boost::filesystem;
1916+
1917+ This->throw_if_not_valid(method, parent_id);
1918+ auto sanitized_name = sanitize(method, name);
1919+ path p = parent_id;
1920+ p /= sanitized_name;
1921+ This->throw_if_not_valid(method, p.native());
1922+ auto st = status(p);
1923+ return vector<Item>{ This->make_item(method, p, st) };
1924+ };
1925+
1926+ return invoke_async(method, do_lookup);
1927+}
1928+
1929+boost::future<Item> LocalProvider::metadata(string const& item_id,
1930+ vector<string> const& /* keys */,
1931+ Context const& /* context */)
1932+{
1933+ string const method = "metadata()";
1934+
1935+ auto This = dynamic_pointer_cast<LocalProvider>(shared_from_this());
1936+ auto do_metadata = [This, method, item_id]
1937+ {
1938+ using namespace boost::filesystem;
1939+
1940+ This->throw_if_not_valid(method, item_id);
1941+ path p = item_id;
1942+ auto st = status(p);
1943+ return This->make_item(method, p, st);
1944+ };
1945+
1946+ return invoke_async(method, do_metadata);
1947+}
1948+
1949+boost::future<Item> LocalProvider::create_folder(string const& parent_id,
1950+ string const& name,
1951+ vector<string> const& /* keys */,
1952+ Context const& /* context */)
1953+{
1954+ string const method = "create_folder()";
1955+
1956+ auto This = dynamic_pointer_cast<LocalProvider>(shared_from_this());
1957+ auto do_create = [This, method, parent_id, name]
1958+ {
1959+ using namespace boost::filesystem;
1960+
1961+ This->throw_if_not_valid(method, parent_id);
1962+ auto sanitized_name = sanitize(method, name);
1963+ path p = parent_id;
1964+ p /= sanitized_name;
1965+ // create_directory() succeeds if the directory exists already, so we need to check explicitly.
1966+ if (exists(p))
1967+ {
1968+ string msg = method + ": \"" + p.native() + "\" exists already";
1969+ throw boost::enable_current_exception(ExistsException(msg, p.native(), name));
1970+ }
1971+ create_directory(p);
1972+ auto st = status(p);
1973+ return This->make_item(method, p, st);
1974+ };
1975+
1976+ return invoke_async(method, do_create);
1977+}
1978+
1979+boost::future<unique_ptr<UploadJob>> LocalProvider::create_file(string const& parent_id,
1980+ string const& name,
1981+ int64_t size,
1982+ string const& /* content_type */,
1983+ bool allow_overwrite,
1984+ vector<string> const& /* keys */,
1985+ Context const& /* context */)
1986+{
1987+ auto This = dynamic_pointer_cast<LocalProvider>(shared_from_this());
1988+ boost::promise<unique_ptr<UploadJob>> p;
1989+ p.set_value(make_unique<LocalUploadJob>(This, parent_id, name, size, allow_overwrite));
1990+ return p.get_future();
1991+}
1992+
1993+boost::future<unique_ptr<UploadJob>> LocalProvider::update(string const& item_id,
1994+ int64_t size,
1995+ string const& old_etag,
1996+ vector<string> const& /* keys */,
1997+ Context const& /* context */)
1998+{
1999+ auto This = dynamic_pointer_cast<LocalProvider>(shared_from_this());
2000+ boost::promise<unique_ptr<UploadJob>> p;
2001+ p.set_value(make_unique<LocalUploadJob>(This, item_id, size, old_etag));
2002+ return p.get_future();
2003+}
2004+
2005+boost::future<unique_ptr<DownloadJob>> LocalProvider::download(string const& item_id,
2006+ string const& match_etag,
2007+ Context const& /* context */)
2008+{
2009+ auto This = dynamic_pointer_cast<LocalProvider>(shared_from_this());
2010+ boost::promise<unique_ptr<DownloadJob>> p;
2011+ p.set_value(make_unique<LocalDownloadJob>(This, item_id, match_etag));
2012+ return p.get_future();
2013+}
2014+
2015+boost::future<void> LocalProvider::delete_item(string const& item_id, Context const& /* context */)
2016+{
2017+ string const method = "delete_item()";
2018+
2019+ auto This = dynamic_pointer_cast<LocalProvider>(shared_from_this());
2020+ auto do_delete = [This, method, item_id]
2021+ {
2022+ using namespace boost::filesystem;
2023+
2024+ This->throw_if_not_valid(method, item_id);
2025+ if (canonical(item_id).native() == This->root_)
2026+ {
2027+ string msg = method + ": cannot delete root";
2028+ throw boost::enable_current_exception(LogicException(msg));
2029+ }
2030+ remove_all(item_id);
2031+ };
2032+
2033+ return invoke_async(method, do_delete);
2034+}
2035+
2036+boost::future<Item> LocalProvider::move(string const& item_id,
2037+ string const& new_parent_id,
2038+ string const& new_name,
2039+ vector<string> const& /* keys */,
2040+ Context const& /* context */)
2041+{
2042+ string const method = "move()";
2043+
2044+ auto This = dynamic_pointer_cast<LocalProvider>(shared_from_this());
2045+ auto do_move = [This, method, item_id, new_parent_id, new_name]
2046+ {
2047+ using namespace boost::filesystem;
2048+
2049+ This->throw_if_not_valid(method, item_id);
2050+ This->throw_if_not_valid(method, new_parent_id);
2051+ auto sanitized_name = sanitize(method, new_name);
2052+
2053+ path parent_path = new_parent_id;
2054+ path target_path = parent_path / sanitized_name;
2055+
2056+ if (exists(target_path))
2057+ {
2058+ string msg = method + ": \"" + target_path.native() + "\" exists already";
2059+ throw boost::enable_current_exception(ExistsException(msg, target_path.native(), new_name));
2060+ }
2061+
2062+ // Small race condition here: if exists() just said that the target does not exist, it is
2063+ // possible for it to have been created since. If so, if the target is a file or an empty
2064+ // directory, it will be removed. In practice, this is unlikely to happen and, if it does,
2065+ // it is not the end of the world.
2066+ // TODO: deal with EXDEV
2067+ rename(item_id, target_path);
2068+ auto st = status(target_path);
2069+ return This->make_item(method, target_path, st);
2070+ };
2071+
2072+ return invoke_async(method, do_move);
2073+}
2074+
2075+boost::future<Item> LocalProvider::copy(string const& item_id,
2076+ string const& new_parent_id,
2077+ string const& new_name,
2078+ vector<string> const& /* keys */,
2079+ Context const& /* context */)
2080+{
2081+ string const method = "copy()";
2082+
2083+ auto This = dynamic_pointer_cast<LocalProvider>(shared_from_this());
2084+ auto do_copy = [This, method, item_id, new_parent_id, new_name]
2085+ {
2086+ using namespace boost::filesystem;
2087+
2088+ This->throw_if_not_valid(method, item_id);
2089+ This->throw_if_not_valid(method, new_parent_id);
2090+ auto sanitized_name = sanitize(method, new_name);
2091+
2092+ path parent_path = new_parent_id;
2093+ path target_path = parent_path / sanitized_name;
2094+
2095+ if (is_directory(item_id))
2096+ {
2097+ if (exists(target_path))
2098+ {
2099+ string msg = method + ": \"" + target_path.native() + "\" exists already";
2100+ throw boost::enable_current_exception(ExistsException(msg, target_path.native(), new_name));
2101+ }
2102+
2103+ // For recursive copy, we create a temporary directory in lieu of target_path and recursively copy
2104+ // everything into the temporary directory. This ensures that we don't invalidate directory iterators
2105+ // by creating things while we are iterating, potentially getting trapped in an infinite loop.
2106+ path tmp_path = canonical(parent_path);
2107+ tmp_path /= unique_path(string(TMPFILE_PREFIX) + "-%%%%-%%%%-%%%%-%%%%");
2108+ create_directories(tmp_path);
2109+ for (directory_iterator it(item_id); it != directory_iterator(); ++it)
2110+ {
2111+ if (is_reserved_path(it->path()))
2112+ {
2113+ continue; // Don't recurse into the temporary directory
2114+ }
2115+ file_status s = it->status();
2116+ if (is_directory(s) || is_regular_file(s))
2117+ {
2118+ path source_entry = it->path();
2119+ path target_entry = tmp_path;
2120+ target_entry /= source_entry.filename();
2121+ copy_recursively(source_entry, target_entry);
2122+ }
2123+ }
2124+ rename(tmp_path, target_path);
2125+ }
2126+ else
2127+ {
2128+ copy_file(item_id, target_path);
2129+ }
2130+
2131+ auto st = status(target_path);
2132+ return This->make_item(method, target_path, st);
2133+ };
2134+
2135+ return invoke_async(method, do_copy);
2136+}
2137+
2138+// Make sure that id does not point outside the root.
2139+
2140+void LocalProvider::throw_if_not_valid(string const& method, string const& id) const
2141+{
2142+ using namespace boost::filesystem;
2143+
2144+ string suspect_id;
2145+ try
2146+ {
2147+ suspect_id = canonical(id).native();
2148+ }
2149+ catch (filesystem_error const& e)
2150+ {
2151+ throw_storage_exception(method, e);
2152+ }
2153+
2154+ // Disallow things such as <root>/blah/../blah even though they lead to the correct path.
2155+ if (suspect_id != id)
2156+ {
2157+ throw boost::enable_current_exception(InvalidArgumentException(method + ": invalid id: \"" + id + "\""));
2158+ }
2159+ // id must denote the root or have the root as a prefix.
2160+ auto const root_id = root_.native();
2161+ if (id != root_id && !boost::starts_with(id, root_id + "/"))
2162+ {
2163+ throw boost::enable_current_exception(InvalidArgumentException(method + ": invalid id: \"" + id + "\""));
2164+ }
2165+}
2166+
2167+// Return an Item initialized from item_path and st.
2168+
2169+Item LocalProvider::make_item(string const& method,
2170+ boost::filesystem::path const& item_path,
2171+ boost::filesystem::file_status const& st) const
2172+{
2173+ using namespace unity::storage;
2174+ using namespace unity::storage::metadata;
2175+ using namespace boost::filesystem;
2176+
2177+ map<string, MetadataValue> meta;
2178+
2179+ string const item_id = item_path.native();
2180+ int64_t const mtime_nsecs = get_mtime_nsecs(method, item_id);
2181+ string const iso_mtime = make_iso_date(mtime_nsecs);
2182+
2183+ ItemType type;
2184+ string name = item_path.filename().native();
2185+ vector<string> parents{item_path.parent_path().native()};
2186+ string etag;
2187+ switch (st.type())
2188+ {
2189+ case regular_file:
2190+ type = ItemType::file;
2191+ etag = to_string(mtime_nsecs);
2192+ meta.insert({SIZE_IN_BYTES, int64_t(file_size(item_path))});
2193+ break;
2194+ case directory_file:
2195+ if (item_path == root_)
2196+ {
2197+ name = "/";
2198+ parents.clear();
2199+ type = ItemType::root;
2200+ }
2201+ else
2202+ {
2203+ type = ItemType::folder;
2204+ }
2205+ break;
2206+ default:
2207+ throw boost::enable_current_exception(
2208+ NotExistsException(method + ": \"" + item_id + "\" is neither a file nor a folder", item_id));
2209+ }
2210+
2211+
2212+ auto const info = space(item_path);
2213+ meta.insert({FREE_SPACE_BYTES, int64_t(info.available)});
2214+ meta.insert({USED_SPACE_BYTES, int64_t(info.capacity - info.available)});
2215+
2216+ meta.insert({LAST_MODIFIED_TIME, iso_mtime});
2217+ meta.insert({CONTENT_TYPE, get_content_type(item_id)});
2218+
2219+ auto perms = st.permissions();
2220+ bool writable;
2221+ if (type == ItemType::file)
2222+ {
2223+ writable = perms & owner_write;
2224+ }
2225+ else
2226+ {
2227+ writable = perms & owner_write && perms & owner_exe;
2228+ }
2229+ meta.insert({WRITABLE, writable});
2230+
2231+ return Item{ item_id, parents, name, etag, type, meta };
2232+}
2233
2234=== added file 'src/local-provider/LocalProvider.h'
2235--- src/local-provider/LocalProvider.h 1970-01-01 00:00:00 +0000
2236+++ src/local-provider/LocalProvider.h 2017-03-20 04:58:30 +0000
2237@@ -0,0 +1,84 @@
2238+/*
2239+ * Copyright (C) 2017 Canonical Ltd
2240+ *
2241+ * This program is free software: you can redistribute it and/or modify
2242+ * it under the terms of the GNU Lesser General Public License version 3 as
2243+ * published by the Free Software Foundation.
2244+ *
2245+ * This program is distributed in the hope that it will be useful,
2246+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2247+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2248+ * GNU Lesser General Public License for more details.
2249+ *
2250+ * You should have received a copy of the GNU Lesser General Public License
2251+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2252+ *
2253+ * Authors: Michi Henning <michi.henning@canonical.com>
2254+ */
2255+
2256+#pragma once
2257+
2258+#include <unity/storage/provider/ProviderBase.h>
2259+
2260+#include <boost/filesystem.hpp>
2261+
2262+class LocalProvider : public unity::storage::provider::ProviderBase
2263+{
2264+public:
2265+ LocalProvider();
2266+ virtual ~LocalProvider();
2267+
2268+ boost::future<unity::storage::provider::ItemList> roots(
2269+ std::vector<std::string> const& metadata_keys,
2270+ unity::storage::provider::Context const& ctx) override;
2271+ boost::future<std::tuple<unity::storage::provider::ItemList, std::string>> list(
2272+ std::string const& item_id, std::string const& page_token,
2273+ std::vector<std::string> const& metadata_keys,
2274+ unity::storage::provider::Context const& ctx) override;
2275+ boost::future<unity::storage::provider::ItemList> lookup(
2276+ std::string const& parent_id, std::string const& name,
2277+ std::vector<std::string> const& metadata_keys,
2278+ unity::storage::provider::Context const& ctx) override;
2279+ boost::future<unity::storage::provider::Item> metadata(
2280+ std::string const& item_id,
2281+ std::vector<std::string> const& metadata_keys,
2282+ unity::storage::provider::Context const& ctx) override;
2283+ boost::future<unity::storage::provider::Item> create_folder(
2284+ std::string const& parent_id, std::string const& name,
2285+ std::vector<std::string> const& metadata_keys,
2286+ unity::storage::provider::Context const& ctx) override;
2287+ boost::future<std::unique_ptr<unity::storage::provider::UploadJob>> create_file(
2288+ std::string const& parent_id, std::string const& name,
2289+ int64_t size, std::string const& content_type, bool allow_overwrite,
2290+ std::vector<std::string> const& metadata_keys,
2291+ unity::storage::provider::Context const& ctx) override;
2292+ boost::future<std::unique_ptr<unity::storage::provider::UploadJob>> update(
2293+ std::string const& item_id, int64_t size,
2294+ std::string const& old_etag,
2295+ std::vector<std::string> const& metadata_keys,
2296+ unity::storage::provider::Context const& ctx) override;
2297+ boost::future<std::unique_ptr<unity::storage::provider::DownloadJob>> download(
2298+ std::string const& item_id,
2299+ std::string const& match_etag,
2300+ unity::storage::provider::Context const& ctx) override;
2301+ boost::future<void> delete_item(std::string const& item_id,
2302+ unity::storage::provider::Context const& ctx) override;
2303+ boost::future<unity::storage::provider::Item> move(
2304+ std::string const& item_id, std::string const& new_parent_id,
2305+ std::string const& new_name,
2306+ std::vector<std::string> const& metadata_keys,
2307+ unity::storage::provider::Context const& ctx) override;
2308+ boost::future<unity::storage::provider::Item> copy(
2309+ std::string const& item_id, std::string const& new_parent_id,
2310+ std::string const& new_name,
2311+ std::vector<std::string> const& metadata_keys,
2312+ unity::storage::provider::Context const& ctx) override;
2313+
2314+ void throw_if_not_valid(std::string const& method, std::string const& id) const;
2315+ unity::storage::provider::Item make_item(std::string const& method,
2316+ boost::filesystem::path const& item_path,
2317+ boost::filesystem::file_status const& st) const;
2318+
2319+private:
2320+ boost::filesystem::path const root_;
2321+};
2322
2323=== added file 'src/local-provider/LocalUploadJob.cpp'
2324--- src/local-provider/LocalUploadJob.cpp 1970-01-01 00:00:00 +0000
2325+++ src/local-provider/LocalUploadJob.cpp 2017-03-20 04:58:30 +0000
2326@@ -0,0 +1,337 @@
2327+/*
2328+ * Copyright (C) 2017 Canonical Ltd
2329+ *
2330+ * This program is free software: you can redistribute it and/or modify
2331+ * it under the terms of the GNU Lesser General Public License version 3 as
2332+ * published by the Free Software Foundation.
2333+ *
2334+ * This program is distributed in the hope that it will be useful,
2335+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2336+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2337+ * GNU Lesser General Public License for more details.
2338+ *
2339+ * You should have received a copy of the GNU Lesser General Public License
2340+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2341+ *
2342+ * Authors: Michi Henning <michi.henning@canonical.com>
2343+ */
2344+
2345+#include "LocalUploadJob.h"
2346+
2347+#include "LocalProvider.h"
2348+#include "utils.h"
2349+#include <unity/storage/internal/safe_strerror.h>
2350+#include <unity/storage/provider/Exceptions.h>
2351+
2352+#include <fcntl.h>
2353+
2354+using namespace unity::storage::provider;
2355+using namespace std;
2356+
2357+static int next_upload_id = 0;
2358+
2359+LocalUploadJob::LocalUploadJob(shared_ptr<LocalProvider> const& provider, int64_t size, const string& method)
2360+ : UploadJob(to_string(++next_upload_id))
2361+ , provider_(provider)
2362+ , size_(size)
2363+ , bytes_to_write_(size)
2364+ , method_(method)
2365+ , state_(in_progress)
2366+ , tmp_fd_([](int fd){ if (fd != -1) ::close(fd); })
2367+{
2368+}
2369+
2370+LocalUploadJob::LocalUploadJob(shared_ptr<LocalProvider> const& provider,
2371+ string const& parent_id,
2372+ string const& name,
2373+ int64_t size,
2374+ bool allow_overwrite)
2375+ : LocalUploadJob(provider, size, "create_file()")
2376+{
2377+ using namespace boost::filesystem;
2378+
2379+ parent_id_ = parent_id;
2380+ allow_overwrite_ = allow_overwrite;
2381+
2382+ provider_->throw_if_not_valid(method_, parent_id);
2383+
2384+ auto sanitized_name = sanitize(method_, name);
2385+ path p = parent_id;
2386+ p /= sanitized_name;
2387+ item_id_ = p.native();
2388+ if (!allow_overwrite && exists(item_id_))
2389+ {
2390+ string msg = method_ + ": \"" + item_id_ + "\" exists already";
2391+ throw ExistsException(msg, item_id_, sanitized_name.native());
2392+ }
2393+
2394+ prepare_channels();
2395+}
2396+
2397+LocalUploadJob::LocalUploadJob(shared_ptr<LocalProvider> const& provider,
2398+ string const& item_id,
2399+ int64_t size,
2400+ string const& old_etag)
2401+ : LocalUploadJob(provider, size, "update()")
2402+{
2403+ using namespace boost::filesystem;
2404+
2405+ item_id_ = item_id;
2406+ provider_->throw_if_not_valid(method_, item_id);
2407+ try
2408+ {
2409+ auto st = status(item_id);
2410+ if (!is_regular_file(st))
2411+ {
2412+ throw InvalidArgumentException(method_ + ": \"" + item_id + "\" is not a file");
2413+ }
2414+ }
2415+ // LCOV_EXCL_START
2416+ catch (filesystem_error const& e)
2417+ {
2418+ // The call to status could throw if the file is unlinked immediately
2419+ // after the call to throw_if_not_valid.
2420+ throw_storage_exception(method_, e);
2421+ }
2422+ // LCOV_EXCL_STOP
2423+ if (!old_etag.empty())
2424+ {
2425+ int64_t mtime = get_mtime_nsecs(method_, item_id);
2426+ if (to_string(mtime) != old_etag)
2427+ {
2428+ throw ConflictException(method_ + ": etag mismatch");
2429+ }
2430+ }
2431+ old_etag_ = old_etag;
2432+
2433+ prepare_channels();
2434+}
2435+
2436+LocalUploadJob::~LocalUploadJob() = default;
2437+
2438+void LocalUploadJob::prepare_channels()
2439+{
2440+ using namespace boost::filesystem;
2441+
2442+ // Open tmp file for writing.
2443+ auto parent_path = path(item_id_).parent_path();
2444+ tmp_fd_.reset(open(parent_path.native().c_str(), O_TMPFILE | O_WRONLY, 0600));
2445+ if (tmp_fd_.get() == -1)
2446+ {
2447+ // Some kernels on the phones don't support O_TMPFILE and return various errno values when this fails.
2448+ // So, if anything at all goes wrong, we fall back on conventional temp file creation and
2449+ // produce a hard error if that doesn't work either.
2450+ // Note that, in this case, the temp file retains its name in the file system. Not nice because,
2451+ // if this process dies at the wrong moment, we leave the temp file behind.
2452+ use_linkat_ = false;
2453+ string tmpfile = parent_path.native() + "/" + TMPFILE_PREFIX + "-%%%%-%%%%-%%%%-%%%%";
2454+ tmp_fd_.reset(mkstemp(const_cast<char*>(tmpfile.data())));
2455+ if (tmp_fd_.get() == -1)
2456+ {
2457+ string msg = method_ + ": cannot create temp file \"" + tmpfile + "\": "
2458+ + unity::storage::internal::safe_strerror(errno);
2459+ throw ResourceException(msg, errno);
2460+ }
2461+ // LCOV_EXCL_START
2462+ file_.reset(new QFile(QString::fromStdString(tmpfile)));
2463+ file_->open(QIODevice::WriteOnly);
2464+ // LCOV_EXCL_STOP
2465+ }
2466+ else
2467+ {
2468+ use_linkat_ = true;
2469+ file_.reset(new QFile);
2470+ file_->open(tmp_fd_.get(), QIODevice::WriteOnly, QFileDevice::DontCloseHandle);
2471+ }
2472+
2473+ // Make read socket ready.
2474+ int dup_fd = dup(read_socket());
2475+ if (dup_fd == -1)
2476+ {
2477+ // LCOV_EXCL_START
2478+ string msg = method_ + ": dup() failed: " + unity::storage::internal::safe_strerror(errno);
2479+ throw ResourceException(msg, errno);
2480+ // LCOV_EXCL_STOP
2481+ }
2482+ read_socket_.setSocketDescriptor(dup_fd, QLocalSocket::ConnectedState, QIODevice::ReadOnly);
2483+ connect(&read_socket_, &QLocalSocket::readyRead, this, &LocalUploadJob::on_bytes_ready);
2484+ connect(&read_socket_, &QIODevice::readChannelFinished, this, &LocalUploadJob::on_read_channel_finished);
2485+}
2486+
2487+boost::future<void> LocalUploadJob::cancel()
2488+{
2489+ if (state_ == in_progress)
2490+ {
2491+ abort_upload();
2492+ }
2493+ return boost::make_ready_future();
2494+}
2495+
2496+boost::future<Item> LocalUploadJob::finish()
2497+{
2498+ on_bytes_ready(); // Read any remaining unread buffered data.
2499+
2500+ if (bytes_to_write_ > 0)
2501+ {
2502+ string msg = "finish() method called too early, size was given as "
2503+ + to_string(size_) + " but only "
2504+ + to_string(size_ - bytes_to_write_) + " bytes were received";
2505+ return boost::make_exceptional_future<Item>(LogicException(msg));
2506+ }
2507+
2508+ // We are committed to finishing successfully or with an error now.
2509+ state_ = finished;
2510+
2511+ try
2512+ {
2513+ // We check again for an etag mismatch or overwrite, in case the file was updated after the upload started.
2514+ if (!parent_id_.empty())
2515+ {
2516+ // create_file()
2517+ if (!allow_overwrite_ && boost::filesystem::exists(item_id_))
2518+ {
2519+ string msg = method_ + ": \"" + item_id_ + "\" exists already";
2520+ boost::filesystem::path(item_id_).filename().native();
2521+ BOOST_THROW_EXCEPTION(
2522+ ExistsException(msg, item_id_, boost::filesystem::path(item_id_).filename().native()));
2523+ }
2524+ }
2525+ else if (!old_etag_.empty())
2526+ {
2527+ // update()
2528+ int64_t mtime = get_mtime_nsecs(method_, item_id_);
2529+ if (to_string(mtime) != old_etag_)
2530+ {
2531+ BOOST_THROW_EXCEPTION(ConflictException(method_ + ": etag mismatch"));
2532+ }
2533+ }
2534+
2535+ if (!file_->flush()) // Make sure that all buffered data is written.
2536+ {
2537+ // LCOV_EXCL_START
2538+ string msg = "finish(): cannot flush output file: " + file_->errorString().toStdString();
2539+ throw_storage_exception("finish()", msg, file_->error());
2540+ // LCOV_EXCL_STOP
2541+ }
2542+
2543+ // Link the anonymous tmp file into the file system.
2544+ using namespace unity::storage::internal;
2545+
2546+ if (use_linkat_)
2547+ {
2548+ auto old_path = string("/proc/self/fd/") + std::to_string(tmp_fd_.get());
2549+ ::unlink(item_id_.c_str()); // linkat() will not remove existing file: http://lwn.net/Articles/559969/
2550+ if (linkat(-1, old_path.c_str(), tmp_fd_.get(), item_id_.c_str(), AT_SYMLINK_FOLLOW) == -1)
2551+ {
2552+ // LCOV_EXCL_START
2553+ string msg = "finish(): linkat \"" + old_path + "\" to \"" + item_id_ + "\" failed: "
2554+ + safe_strerror(errno);
2555+ BOOST_THROW_EXCEPTION(ResourceException(msg, errno));
2556+ // LCOV_EXCL_STOP
2557+ }
2558+ }
2559+ else
2560+ {
2561+ // LCOV_EXCL_START
2562+ auto old_path = file_->fileName().toStdString();
2563+ if (rename(old_path.c_str(), item_id_.c_str()) == -1)
2564+ {
2565+ string msg = "finish(): rename \"" + old_path + "\" to \"" + item_id_ + "\" failed: "
2566+ + safe_strerror(errno);
2567+ BOOST_THROW_EXCEPTION(ResourceException(msg, errno));
2568+ }
2569+ // LCOV_EXCL_STOP
2570+ }
2571+
2572+ file_->close();
2573+ read_socket_.close();
2574+
2575+ auto st = boost::filesystem::status(item_id_);
2576+ return boost::make_ready_future<Item>(provider_->make_item(method_, item_id_, st));
2577+ }
2578+ catch (StorageException const&)
2579+ {
2580+ return boost::make_exceptional_future<Item>(boost::current_exception());
2581+ }
2582+ // LCOV_EXCL_START
2583+ catch (boost::filesystem::filesystem_error const& e)
2584+ {
2585+ try
2586+ {
2587+ throw_storage_exception("finish()", e);
2588+ }
2589+ catch (StorageException const&)
2590+ {
2591+ return boost::make_exceptional_future<Item>(boost::current_exception());
2592+ }
2593+ }
2594+ catch (std::exception const& e)
2595+ {
2596+ return boost::make_exceptional_future<Item>(UnknownException(e.what()));
2597+ }
2598+ // LCOV_EXCL_STOP
2599+}
2600+
2601+void LocalUploadJob::on_bytes_ready()
2602+{
2603+ if (bytes_to_write_ < 0)
2604+ {
2605+ return; // LCOV_EXCL_LINE // We received too many bytes earlier.
2606+ }
2607+
2608+ try
2609+ {
2610+ auto buf = read_socket_.readAll();
2611+ if (buf.size() != 0)
2612+ {
2613+ bytes_to_write_ -= buf.size();
2614+ if (bytes_to_write_ < 0)
2615+ {
2616+ string msg = method_ + ": received more than the expected number (" + to_string(size_) + ") of bytes";
2617+ throw LogicException(msg);
2618+ }
2619+ auto bytes_written = file_->write(buf);
2620+ if (bytes_written == -1)
2621+ {
2622+ // LCOV_EXCL_START
2623+ string msg = "write error: " + file_->errorString().toStdString();
2624+ throw_storage_exception(method_, msg, file_->error());
2625+ // LCOV_EXCL_STOP
2626+ }
2627+ else if (bytes_written != buf.size())
2628+ {
2629+ // LCOV_EXCL_START
2630+ string msg = "write error, requested " + to_string(buf.size()) + " B, but wrote only "
2631+ + to_string(bytes_written) + " B.";
2632+ throw_storage_exception(method_, msg, QFileDevice::FatalError);
2633+ // LCOV_EXCL_STOP
2634+ }
2635+ }
2636+ }
2637+ catch (std::exception const&)
2638+ {
2639+ abort_upload();
2640+ report_error(current_exception());
2641+ }
2642+}
2643+
2644+void LocalUploadJob::on_read_channel_finished()
2645+{
2646+ on_bytes_ready(); // In case there s still buffered data to be read.
2647+}
2648+
2649+void LocalUploadJob::abort_upload()
2650+{
2651+ state_ = cancelled;
2652+ disconnect(&read_socket_, nullptr, this, nullptr);
2653+ read_socket_.abort();
2654+ file_->close();
2655+ if (!use_linkat_)
2656+ {
2657+ // LCOV_EXCL_START
2658+ string filename = file_->fileName().toStdString();
2659+ ::unlink(filename.c_str()); // Don't leave any temp file behind.
2660+ // LCOV_EXCL_STOP
2661+ }
2662+ bytes_to_write_ = 0;
2663+}
2664
2665=== added file 'src/local-provider/LocalUploadJob.h'
2666--- src/local-provider/LocalUploadJob.h 1970-01-01 00:00:00 +0000
2667+++ src/local-provider/LocalUploadJob.h 2017-03-20 04:58:30 +0000
2668@@ -0,0 +1,79 @@
2669+/*
2670+ * Copyright (C) 2017 Canonical Ltd
2671+ *
2672+ * This program is free software: you can redistribute it and/or modify
2673+ * it under the terms of the GNU Lesser General Public License version 3 as
2674+ * published by the Free Software Foundation.
2675+ *
2676+ * This program is distributed in the hope that it will be useful,
2677+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2678+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2679+ * GNU Lesser General Public License for more details.
2680+ *
2681+ * You should have received a copy of the GNU Lesser General Public License
2682+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2683+ *
2684+ * Authors: Michi Henning <michi.henning@canonical.com>
2685+ */
2686+
2687+#pragma once
2688+
2689+#include <unity/storage/provider/UploadJob.h>
2690+
2691+#include <unity/util/ResourcePtr.h>
2692+
2693+#pragma GCC diagnostic push
2694+#pragma GCC diagnostic ignored "-Wctor-dtor-privacy"
2695+#pragma GCC diagnostic ignored "-Wswitch-default"
2696+#include <QFile>
2697+#include <QLocalSocket>
2698+#pragma GCC diagnostic pop
2699+
2700+class LocalProvider;
2701+
2702+class LocalUploadJob : public QObject, public unity::storage::provider::UploadJob
2703+{
2704+ Q_OBJECT
2705+public:
2706+ LocalUploadJob(std::shared_ptr<LocalProvider> const& provider, int64_t size, const std::string& method);
2707+
2708+ // create_file()
2709+ LocalUploadJob(std::shared_ptr<LocalProvider> const& provider,
2710+ std::string const& parent_id,
2711+ std::string const& name,
2712+ int64_t size,
2713+ bool allow_overwrite);
2714+ // update()
2715+ LocalUploadJob(std::shared_ptr<LocalProvider> const& provider,
2716+ std::string const& item_id,
2717+ int64_t size,
2718+ std::string const& old_etag);
2719+ virtual ~LocalUploadJob();
2720+
2721+ virtual boost::future<void> cancel() override;
2722+ virtual boost::future<unity::storage::provider::Item> finish() override;
2723+
2724+private Q_SLOTS:
2725+ void on_bytes_ready();
2726+ void on_read_channel_finished();
2727+
2728+private:
2729+ enum State { in_progress, finished, cancelled };
2730+
2731+ void prepare_channels();
2732+ void abort_upload();
2733+
2734+ std::shared_ptr<LocalProvider> const provider_;
2735+ int64_t const size_;
2736+ int64_t bytes_to_write_;
2737+ std::unique_ptr<QFile> file_;
2738+ QLocalSocket read_socket_;
2739+ std::string const method_;
2740+ State state_;
2741+ std::string item_id_;
2742+ std::string old_etag_; // Empty for create_file()
2743+ std::string parent_id_; // Empty for update()
2744+ bool allow_overwrite_; // Undefined for update()
2745+ unity::util::ResourcePtr<int, std::function<void(int)>> tmp_fd_;
2746+ bool use_linkat_;
2747+};
2748
2749=== added file 'src/local-provider/com.canonical.StorageFramework.Provider.Local.service.in'
2750--- src/local-provider/com.canonical.StorageFramework.Provider.Local.service.in 1970-01-01 00:00:00 +0000
2751+++ src/local-provider/com.canonical.StorageFramework.Provider.Local.service.in 2017-03-20 04:58:30 +0000
2752@@ -0,0 +1,3 @@
2753+[D-BUS Service]
2754+Name=com.canonical.StorageFramework.Provider.Local
2755+Exec=@CMAKE_INSTALL_FULL_LIBDIR@/@PROJECT_NAME@/storage-provider-local
2756
2757=== added file 'src/local-provider/main.cpp'
2758--- src/local-provider/main.cpp 1970-01-01 00:00:00 +0000
2759+++ src/local-provider/main.cpp 2017-03-20 04:58:30 +0000
2760@@ -0,0 +1,52 @@
2761+/*
2762+ * Copyright (C) 2017 Canonical Ltd
2763+ *
2764+ * This program is free software: you can redistribute it and/or modify
2765+ * it under the terms of the GNU Lesser General Public License version 3 as
2766+ * published by the Free Software Foundation.
2767+ *
2768+ * This program is distributed in the hope that it will be useful,
2769+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2770+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2771+ * GNU Lesser General Public License for more details.
2772+ *
2773+ * You should have received a copy of the GNU Lesser General Public License
2774+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2775+ *
2776+ * Authors: Michi Henning <michi.henning@canonical.com>
2777+ */
2778+
2779+#include <unity/storage/provider/Server.h>
2780+
2781+#include "LocalProvider.h"
2782+
2783+#include <boost/filesystem.hpp>
2784+
2785+#include <iostream>
2786+
2787+using namespace std;
2788+using namespace unity::storage::provider;
2789+
2790+int main(int argc, char* argv[])
2791+{
2792+ using namespace boost::filesystem;
2793+
2794+ string const bus_name = "com.canonical.StorageFramework.Provider.Local";
2795+ string const account_service_id = "";
2796+
2797+ string progname = argv[0];
2798+
2799+ try
2800+ {
2801+ progname = path(progname).filename().native();
2802+
2803+ Server<LocalProvider> server(bus_name, account_service_id);
2804+ server.init(argc, argv);
2805+ server.run();
2806+ }
2807+ catch (std::exception const& e)
2808+ {
2809+ cerr << progname << ": " << e.what() << endl;
2810+ return 1;
2811+ }
2812+}
2813
2814=== added file 'src/local-provider/utils.cpp'
2815--- src/local-provider/utils.cpp 1970-01-01 00:00:00 +0000
2816+++ src/local-provider/utils.cpp 2017-03-20 04:58:30 +0000
2817@@ -0,0 +1,146 @@
2818+/*
2819+ * Copyright (C) 2017 Canonical Ltd
2820+ *
2821+ * This program is free software: you can redistribute it and/or modify
2822+ * it under the terms of the GNU Lesser General Public License version 3 as
2823+ * published by the Free Software Foundation.
2824+ *
2825+ * This program is distributed in the hope that it will be useful,
2826+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2827+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2828+ * GNU Lesser General Public License for more details.
2829+ *
2830+ * You should have received a copy of the GNU Lesser General Public License
2831+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2832+ *
2833+ * Authors: Michi Henning <michi.henning@canonical.com>
2834+ */
2835+
2836+#include "utils.h"
2837+
2838+#include <unity/storage/internal/safe_strerror.h>
2839+#include <unity/storage/provider/Exceptions.h>
2840+
2841+#include <boost/algorithm/string.hpp>
2842+#include <boost/exception/enable_current_exception.hpp>
2843+
2844+#include <sys/stat.h>
2845+
2846+using namespace unity::storage::provider;
2847+using namespace std;
2848+
2849+// Return modification time in nanoseconds since the epoch.
2850+
2851+int64_t get_mtime_nsecs(string const& method, string const& path)
2852+{
2853+ using namespace unity::storage::internal;
2854+
2855+ struct stat st;
2856+ if (stat(path.c_str(), &st) == -1)
2857+ {
2858+ // LCOV_EXCL_START
2859+ string msg = method + ": cannot stat \"" + path + "\": " + safe_strerror(errno);
2860+ throw boost::enable_current_exception(ResourceException(msg, errno));
2861+ // LCOV_EXCL_STOP
2862+ }
2863+ return int64_t(st.st_mtim.tv_sec) * 1000000000 + st.st_mtim.tv_nsec;
2864+}
2865+
2866+// Return true if the path uses the temp file prefix.
2867+
2868+bool is_reserved_path(boost::filesystem::path const& path)
2869+{
2870+ string filename = path.filename().native();
2871+ return boost::starts_with(filename, TMPFILE_PREFIX);
2872+}
2873+
2874+// Check that name is a valid file or directory name, that is, has a single component
2875+// and is not "", ".", or "..". Also check that the name does not start with the
2876+// temp file prefix. Throw if the name is invalid.
2877+
2878+boost::filesystem::path sanitize(string const& method, string const& name)
2879+{
2880+ using namespace boost::filesystem;
2881+
2882+ path p = name;
2883+ if (!p.parent_path().empty())
2884+ {
2885+ // name contains more than one component.
2886+ string msg = method + ": name \"" + name + "\" cannot contain a slash";
2887+ throw boost::enable_current_exception(InvalidArgumentException(msg));
2888+ }
2889+ path filename = p.filename();
2890+ if (filename.empty() || filename == "." || filename == "..")
2891+ {
2892+ // Not an allowable file name.
2893+ string msg = method + ": invalid name: \"" + name + "\"";
2894+ throw boost::enable_current_exception(InvalidArgumentException(msg));
2895+ }
2896+ if (is_reserved_path(filename))
2897+ {
2898+ string msg = string(method + ": names beginning with \"") + TMPFILE_PREFIX + "\" are reserved";
2899+ throw boost::enable_current_exception(InvalidArgumentException(msg));
2900+ }
2901+ return p;
2902+}
2903+
2904+// Throw a StorageException that corresponds to a boost::filesystem_error.
2905+
2906+void throw_storage_exception(string const& method, boost::filesystem::filesystem_error const& e)
2907+{
2908+ using namespace boost::system::errc;
2909+
2910+ string msg = method + ": ";
2911+ string path1 = e.path1().native();
2912+ string path2 = e.path2().native();
2913+ if (!path2.empty())
2914+ {
2915+ msg += "src = \"" + path1 + "\", target = \"" + path2 + "\""; // LCOV_EXCL_LINE
2916+ }
2917+ else
2918+ {
2919+ msg += "\"" + path1 + "\"";
2920+ }
2921+ msg += string(": ") + e.what();
2922+ switch (e.code().value())
2923+ {
2924+ case permission_denied:
2925+ case operation_not_permitted:
2926+ throw boost::enable_current_exception(PermissionException(msg));
2927+ case no_such_file_or_directory:
2928+ throw boost::enable_current_exception(NotExistsException(msg, e.path1().native()));
2929+ // LCOV_EXCL_START
2930+ case file_exists:
2931+ throw boost::enable_current_exception(
2932+ ExistsException(msg, e.path1().native(), e.path1().filename().native()));
2933+ case no_space_on_device:
2934+ throw boost::enable_current_exception(QuotaException(msg));
2935+ default:
2936+ throw boost::enable_current_exception(ResourceException(msg, e.code().value()));
2937+ // LCOV_EXCL_STOP
2938+ }
2939+}
2940+
2941+// Throw a storage exception that corresponds to a FileError.
2942+
2943+void throw_storage_exception(string const& method, string const& msg, QFileDevice::FileError e)
2944+{
2945+ string const error_msg = method + ": " + msg;
2946+ switch (e)
2947+ {
2948+ case QFileDevice::NoError:
2949+ abort(); // LCOV_EXCL_LINE // Precondition violation
2950+ break;
2951+ default:
2952+ throw ResourceException(error_msg + " (QFileDevice::FileError = " + to_string(e) + ")", e);
2953+ }
2954+}
2955+
2956+// Throw a storage exception that corresponds to a LocalSocketError.
2957+
2958+// LCOV_EXCL_START
2959+void throw_storage_exception(string const& method, string const& msg, QLocalSocket::LocalSocketError e)
2960+{
2961+ throw ResourceException(method + ": " + msg + " (QLocalSocket::LocalSocketError = " + to_string(e) + ")", e);
2962+}
2963+// LCOV_EXCL_STOP
2964
2965=== added file 'src/local-provider/utils.h'
2966--- src/local-provider/utils.h 1970-01-01 00:00:00 +0000
2967+++ src/local-provider/utils.h 2017-03-20 04:58:30 +0000
2968@@ -0,0 +1,45 @@
2969+/*
2970+ * Copyright (C) 2017 Canonical Ltd
2971+ *
2972+ * This program is free software: you can redistribute it and/or modify
2973+ * it under the terms of the GNU Lesser General Public License version 3 as
2974+ * published by the Free Software Foundation.
2975+ *
2976+ * This program is distributed in the hope that it will be useful,
2977+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2978+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2979+ * GNU Lesser General Public License for more details.
2980+ *
2981+ * You should have received a copy of the GNU Lesser General Public License
2982+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2983+ *
2984+ * Authors: Michi Henning <michi.henning@canonical.com>
2985+ */
2986+
2987+#pragma once
2988+
2989+#include <boost/filesystem.hpp>
2990+
2991+#pragma GCC diagnostic push
2992+#pragma GCC diagnostic ignored "-Wctor-dtor-privacy"
2993+#include <QFileDevice>
2994+#include <QLocalSocket>
2995+#include <QString>
2996+#pragma GCC diagnostic pop
2997+
2998+#include <string>
2999+
3000+constexpr char const* TMPFILE_PREFIX = ".storage-framework";
3001+
3002+int64_t get_mtime_nsecs(std::string const& method, std::string const& path);
3003+bool is_reserved_path(boost::filesystem::path const& path);
3004+boost::filesystem::path sanitize(std::string const& method, std::string const& name);
3005+
3006+[[ noreturn ]]
3007+void throw_storage_exception(std::string const& method, boost::filesystem::filesystem_error const& e);
3008+
3009+[[ noreturn ]]
3010+void throw_storage_exception(std::string const& method, std::string const& msg, QFileDevice::FileError e);
3011+
3012+[[ noreturn ]]
3013+void throw_storage_exception(std::string const& method, std::string const& msg, QLocalSocket::LocalSocketError e);
3014
3015=== modified file 'src/provider/CMakeLists.txt'
3016--- src/provider/CMakeLists.txt 2016-10-12 08:08:48 +0000
3017+++ src/provider/CMakeLists.txt 2017-03-20 04:58:30 +0000
3018@@ -23,8 +23,10 @@
3019 internal/AccountData.cpp
3020 internal/DBusPeerCache.cpp
3021 internal/DownloadJobImpl.cpp
3022+ internal/FixedAccountData.cpp
3023 internal/Handler.cpp
3024 internal/MainLoopExecutor.cpp
3025+ internal/OnlineAccountData.cpp
3026 internal/PendingJobs.cpp
3027 internal/ProviderInterface.cpp
3028 internal/ServerImpl.cpp
3029@@ -34,8 +36,10 @@
3030 internal/dbusmarshal.cpp
3031 ${CMAKE_SOURCE_DIR}/include/unity/storage/provider/internal/AccountData.h
3032 ${CMAKE_SOURCE_DIR}/include/unity/storage/provider/internal/DownloadJobImpl.h
3033+ ${CMAKE_SOURCE_DIR}/include/unity/storage/provider/internal/FixedAccountData.h
3034 ${CMAKE_SOURCE_DIR}/include/unity/storage/provider/internal/Handler.h
3035 ${CMAKE_SOURCE_DIR}/include/unity/storage/provider/internal/MainLoopExecutor.h
3036+ ${CMAKE_SOURCE_DIR}/include/unity/storage/provider/internal/OnlineAccountData.h
3037 ${CMAKE_SOURCE_DIR}/include/unity/storage/provider/internal/PendingJobs.h
3038 ${CMAKE_SOURCE_DIR}/include/unity/storage/provider/internal/ProviderInterface.h
3039 ${CMAKE_SOURCE_DIR}/include/unity/storage/provider/internal/ServerImpl.h
3040
3041=== modified file 'src/provider/Exceptions.cpp'
3042--- src/provider/Exceptions.cpp 2016-08-05 05:37:23 +0000
3043+++ src/provider/Exceptions.cpp 2017-03-20 04:58:30 +0000
3044@@ -97,6 +97,13 @@
3045
3046 ConflictException::~ConflictException() = default;
3047
3048+UnauthorizedException::UnauthorizedException(string const& error_message)
3049+ : StorageException("UnauthorizedException", error_message)
3050+{
3051+}
3052+
3053+UnauthorizedException::~UnauthorizedException() = default;
3054+
3055 PermissionException::PermissionException(string const& error_message)
3056 : StorageException("PermissionException", error_message)
3057 {
3058
3059=== modified file 'src/provider/Server.cpp'
3060--- src/provider/Server.cpp 2016-07-12 02:22:05 +0000
3061+++ src/provider/Server.cpp 2017-03-20 04:58:30 +0000
3062@@ -42,9 +42,9 @@
3063 p_->init(argc, argv);
3064 }
3065
3066-void ServerBase::run()
3067+int ServerBase::run()
3068 {
3069- p_->run();
3070+ return p_->run();
3071 }
3072
3073 }
3074
3075=== modified file 'src/provider/internal/AccountData.cpp'
3076--- src/provider/internal/AccountData.cpp 2016-09-28 11:58:35 +0000
3077+++ src/provider/internal/AccountData.cpp 2017-03-20 04:58:30 +0000
3078@@ -17,14 +17,15 @@
3079 */
3080
3081 #include <unity/storage/provider/internal/AccountData.h>
3082+#include <unity/storage/internal/InactivityTimer.h>
3083 #include <unity/storage/provider/ProviderBase.h>
3084 #include <unity/storage/provider/internal/DBusPeerCache.h>
3085 #include <unity/storage/provider/internal/PendingJobs.h>
3086
3087-#include <OnlineAccounts/AuthenticationData>
3088 #include <QDebug>
3089
3090 using namespace std;
3091+using unity::storage::internal::InactivityTimer;
3092
3093 namespace unity {
3094 namespace storage {
3095@@ -33,13 +34,12 @@
3096
3097 AccountData::AccountData(shared_ptr<ProviderBase> const& provider,
3098 shared_ptr<DBusPeerCache> const& dbus_peer,
3099+ shared_ptr<InactivityTimer> const& inactivity_timer,
3100 QDBusConnection const& bus,
3101- OnlineAccounts::Account* account,
3102 QObject* parent)
3103 : QObject(parent), provider_(provider), dbus_peer_(dbus_peer),
3104- jobs_(new PendingJobs(bus)), account_(account)
3105+ inactivity_timer_(inactivity_timer), jobs_(new PendingJobs(bus))
3106 {
3107- authenticate(false);
3108 }
3109
3110 AccountData::~AccountData() = default;
3111@@ -54,121 +54,16 @@
3112 return *dbus_peer_;
3113 }
3114
3115+shared_ptr<InactivityTimer> AccountData::inactivity_timer()
3116+{
3117+ return inactivity_timer_;
3118+}
3119+
3120 PendingJobs& AccountData::jobs()
3121 {
3122 return *jobs_;
3123 }
3124
3125-void AccountData::authenticate(bool interactive)
3126-{
3127- // If there is an existing authenticating session running, use
3128- // that existing session (unless it is a non-interactive session
3129- // and we've requested interactivity).
3130- if (auth_watcher_ && (authenticating_interactively_ || !interactive))
3131- {
3132- return;
3133- }
3134-
3135- authenticating_interactively_ = interactive;
3136- credentials_valid_ = false;
3137- credentials_ = boost::blank();
3138-
3139- OnlineAccounts::AuthenticationData auth_data(
3140- account_->authenticationMethod());
3141- auth_data.setInteractive(interactive);
3142- OnlineAccounts::PendingCall call = account_->authenticate(auth_data);
3143- auth_watcher_.reset(new OnlineAccounts::PendingCallWatcher(call));
3144- connect(auth_watcher_.get(), &OnlineAccounts::PendingCallWatcher::finished,
3145- this, &AccountData::on_authenticated);
3146-}
3147-
3148-bool AccountData::has_credentials()
3149-{
3150- return credentials_valid_;
3151-}
3152-
3153-Credentials const& AccountData::credentials()
3154-{
3155- return credentials_;
3156-}
3157-
3158-void AccountData::on_authenticated()
3159-{
3160- credentials_ = boost::blank();
3161- credentials_valid_ = true;
3162- switch (account_->authenticationMethod()) {
3163- case OnlineAccounts::AuthenticationMethodOAuth1:
3164- {
3165- OnlineAccounts::OAuth1Reply reply(*auth_watcher_);
3166- if (reply.hasError())
3167- {
3168- qDebug() << "Failed to authenticate:" << reply.error().text();
3169- }
3170- else
3171- {
3172- credentials_ = OAuth1Credentials{
3173- reply.consumerKey().toStdString(),
3174- reply.consumerSecret().toStdString(),
3175- reply.token().toStdString(),
3176- reply.tokenSecret().toStdString(),
3177- };
3178- }
3179- break;
3180- }
3181- case OnlineAccounts::AuthenticationMethodOAuth2:
3182- {
3183- OnlineAccounts::OAuth2Reply reply(*auth_watcher_);
3184- if (reply.hasError())
3185- {
3186- qDebug() << "Failed to authenticate:" << reply.error().text();
3187- }
3188- else
3189- {
3190- credentials_ = OAuth2Credentials{
3191- reply.accessToken().toStdString(),
3192- };
3193- }
3194- break;
3195- }
3196- case OnlineAccounts::AuthenticationMethodPassword:
3197- {
3198- // Grab hostname from account settings if available
3199- string host = account_->setting("host").toString().toStdString();
3200-
3201- OnlineAccounts::PasswordReply reply(*auth_watcher_);
3202- if (reply.hasError())
3203- {
3204- qDebug() << "Failed to authenticate:" << reply.error().text();
3205- }
3206- else
3207- {
3208- QString username = reply.username();
3209- QString password = reply.password();
3210-
3211- // Work around password credentials bug in online-accounts-service
3212- // https://bugs.launchpad.net/bugs/1628473
3213- if (username.isEmpty() && password.isEmpty())
3214- {
3215- username = reply.data()["UserName"].toString();
3216- password = reply.data()["Secret"].toString();
3217- }
3218- credentials_ = PasswordCredentials{
3219- username.toStdString(),
3220- password.toStdString(),
3221- move(host),
3222- };
3223- }
3224- break;
3225- }
3226- default:
3227- qDebug() << "Unhandled authentication method:"
3228- << account_->authenticationMethod();
3229- }
3230- auth_watcher_.reset();
3231-
3232- Q_EMIT authenticated();
3233-}
3234-
3235 }
3236 }
3237 }
3238
3239=== modified file 'src/provider/internal/DownloadJobImpl.cpp'
3240--- src/provider/internal/DownloadJobImpl.cpp 2016-09-02 13:51:37 +0000
3241+++ src/provider/internal/DownloadJobImpl.cpp 2017-03-20 04:58:30 +0000
3242@@ -105,6 +105,11 @@
3243 return sock;
3244 }
3245
3246+void DownloadJobImpl::set_activity(std::shared_ptr<InactivityTimer> const& inactivity_timer)
3247+{
3248+ activity_ = ActivityNotifier(inactivity_timer);
3249+}
3250+
3251 void DownloadJobImpl::report_complete()
3252 {
3253 if (write_socket_ >= 0)
3254
3255=== added file 'src/provider/internal/FixedAccountData.cpp'
3256--- src/provider/internal/FixedAccountData.cpp 1970-01-01 00:00:00 +0000
3257+++ src/provider/internal/FixedAccountData.cpp 2017-03-20 04:58:30 +0000
3258@@ -0,0 +1,62 @@
3259+/*
3260+ * Copyright (C) 2017 Canonical Ltd
3261+ *
3262+ * This program is free software: you can redistribute it and/or modify
3263+ * it under the terms of the GNU Lesser General Public License version 3 as
3264+ * published by the Free Software Foundation.
3265+ *
3266+ * This program is distributed in the hope that it will be useful,
3267+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3268+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3269+ * GNU Lesser General Public License for more details.
3270+ *
3271+ * You should have received a copy of the GNU Lesser General Public License
3272+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3273+ *
3274+ * Authors: James Henstridge <james.henstridge@canonical.com>
3275+ */
3276+
3277+#include <unity/storage/provider/internal/FixedAccountData.h>
3278+
3279+using namespace std;
3280+using unity::storage::internal::InactivityTimer;
3281+
3282+namespace unity {
3283+namespace storage {
3284+namespace provider {
3285+namespace internal {
3286+
3287+FixedAccountData::FixedAccountData(shared_ptr<ProviderBase> const& provider,
3288+ shared_ptr<DBusPeerCache> const& dbus_peer,
3289+ shared_ptr<InactivityTimer> const& inactivity_timer,
3290+ QDBusConnection const& bus,
3291+ QObject* parent)
3292+ : AccountData(provider, dbus_peer, inactivity_timer, bus, parent)
3293+{
3294+}
3295+
3296+FixedAccountData::~FixedAccountData() = default;
3297+
3298+void FixedAccountData::authenticate(bool interactive, bool invalidate_cache)
3299+{
3300+ Q_UNUSED(interactive);
3301+ Q_UNUSED(invalidate_cache);
3302+
3303+ // Queue an emission of the authenticated signal.
3304+ QMetaObject::invokeMethod(this, "authenticated", Qt::QueuedConnection);
3305+}
3306+
3307+bool FixedAccountData::has_credentials()
3308+{
3309+ return true;
3310+}
3311+
3312+Credentials const& FixedAccountData::credentials()
3313+{
3314+ return credentials_;
3315+}
3316+
3317+}
3318+}
3319+}
3320+}
3321
3322=== modified file 'src/provider/internal/Handler.cpp'
3323--- src/provider/internal/Handler.cpp 2016-10-12 05:25:20 +0000
3324+++ src/provider/internal/Handler.cpp 2017-03-20 04:58:30 +0000
3325@@ -49,12 +49,42 @@
3326 Handler::Handler(shared_ptr<AccountData> const& account,
3327 Callback const& callback,
3328 QDBusConnection const& bus, QDBusMessage const& message)
3329- : account_(account), callback_(callback), bus_(bus), message_(message)
3330+ : account_(account), callback_(callback), bus_(bus), message_(message),
3331+ activity_(account->inactivity_timer())
3332 {
3333 }
3334
3335 void Handler::begin()
3336 {
3337+ // If we have already retrieved credentials from OnlineAccounts,
3338+ // and we aren't retrying the request, go to on_authenticated
3339+ // immediately.
3340+ if (account_->has_credentials() && !retry_)
3341+ {
3342+ on_authenticated();
3343+ return;
3344+ }
3345+
3346+ // Otherwise, try to authenticate and wait for the result.
3347+ account_->authenticate(true, retry_);
3348+ connect(account_.get(), &AccountData::authenticated,
3349+ this, &Handler::on_authenticated);
3350+}
3351+
3352+void Handler::on_authenticated()
3353+{
3354+ disconnect(account_.get(), &AccountData::authenticated,
3355+ this, &Handler::on_authenticated);
3356+ if (!account_->has_credentials())
3357+ {
3358+ string msg = "Handler::begin(): could not retrieve account credentials";
3359+ qDebug() << QString::fromStdString(msg);
3360+ auto ep = make_exception_ptr(UnauthorizedException(msg));
3361+ marshal_exception(ep);
3362+ QMetaObject::invokeMethod(this, "send_reply", Qt::QueuedConnection);
3363+ return;
3364+ }
3365+
3366 // Need to put security check in here.
3367 auto peer_future = account_->dbus_peer().get(message_.service());
3368 creds_future_ = peer_future.then(
3369@@ -71,9 +101,9 @@
3370 }
3371 else
3372 {
3373- string msg = "Handler::begin(): could not retrieve credentials";
3374+ string msg = "Handler::begin(): could not retrieve D-Bus peer credentials";
3375 qDebug() << QString::fromStdString(msg);
3376- auto ep = make_exception_ptr(PermissionException(msg));
3377+ auto ep = make_exception_ptr(UnauthorizedException(msg));
3378 marshal_exception(ep);
3379 QMetaObject::invokeMethod(this, "send_reply",
3380 Qt::QueuedConnection);
3381@@ -103,6 +133,13 @@
3382 {
3383 reply_ = f.get();
3384 }
3385+ catch (UnauthorizedException const& e)
3386+ {
3387+ QMetaObject::invokeMethod(this, "handle_unauthorized",
3388+ Qt::QueuedConnection,
3389+ Q_ARG(std::exception_ptr, current_exception()));
3390+ return;
3391+ }
3392 catch (std::exception const& e)
3393 {
3394 marshal_exception(current_exception());
3395@@ -111,6 +148,22 @@
3396 });
3397 }
3398
3399+void Handler::handle_unauthorized(exception_ptr ep)
3400+{
3401+ if (retry_)
3402+ {
3403+ // We've already retried once, so send error out as is.
3404+ marshal_exception(ep);
3405+ send_reply();
3406+ }
3407+ else
3408+ {
3409+ // Otherwise, restart the request with the retry_ flag set.
3410+ retry_ = true;
3411+ begin();
3412+ }
3413+}
3414+
3415 void Handler::send_reply()
3416 {
3417 bus_.send(reply_);
3418
3419=== added file 'src/provider/internal/OnlineAccountData.cpp'
3420--- src/provider/internal/OnlineAccountData.cpp 1970-01-01 00:00:00 +0000
3421+++ src/provider/internal/OnlineAccountData.cpp 2017-03-20 04:58:30 +0000
3422@@ -0,0 +1,198 @@
3423+/*
3424+ * Copyright (C) 2017 Canonical Ltd
3425+ *
3426+ * This program is free software: you can redistribute it and/or modify
3427+ * it under the terms of the GNU Lesser General Public License version 3 as
3428+ * published by the Free Software Foundation.
3429+ *
3430+ * This program is distributed in the hope that it will be useful,
3431+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3432+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3433+ * GNU Lesser General Public License for more details.
3434+ *
3435+ * You should have received a copy of the GNU Lesser General Public License
3436+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3437+ *
3438+ * Authors: James Henstridge <james.henstridge@canonical.com>
3439+ */
3440+
3441+#include <unity/storage/provider/internal/OnlineAccountData.h>
3442+
3443+#include <OnlineAccounts/AuthenticationData>
3444+#include <QDebug>
3445+
3446+using namespace std;
3447+using unity::storage::internal::InactivityTimer;
3448+
3449+namespace unity {
3450+namespace storage {
3451+namespace provider {
3452+namespace internal {
3453+
3454+OnlineAccountData::OnlineAccountData(shared_ptr<ProviderBase> const& provider,
3455+ shared_ptr<DBusPeerCache> const& dbus_peer,
3456+ shared_ptr<InactivityTimer> const& inactivity_timer,
3457+ QDBusConnection const& bus,
3458+ OnlineAccounts::Account* account,
3459+ QObject* parent)
3460+ : AccountData(provider, dbus_peer, inactivity_timer, bus, parent),
3461+ account_(account)
3462+{
3463+ connect(account_, &OnlineAccounts::Account::changed,
3464+ this, &OnlineAccountData::on_changed);
3465+ authenticate(false);
3466+}
3467+
3468+OnlineAccountData::~OnlineAccountData() = default;
3469+
3470+void OnlineAccountData::authenticate(bool interactive, bool invalidate_cache)
3471+{
3472+ // If there is an existing authentication session running, check
3473+ // if it matches our requirements.
3474+ if (auth_watcher_)
3475+ {
3476+ if (invalidate_cache)
3477+ {
3478+ // If invalidate_cache has been requested, the existing
3479+ // session must also be invalidating the cache.
3480+ if (authenticating_invalidate_cache_)
3481+ {
3482+ return;
3483+ }
3484+ }
3485+ else if (interactive)
3486+ {
3487+ // If interactive has been requested, the existing session
3488+ // must also be interactive.
3489+ if (authenticating_interactively_)
3490+ {
3491+ return;
3492+ }
3493+ }
3494+ else
3495+ {
3496+ // Otherwise, any session will do.
3497+ return;
3498+ }
3499+ }
3500+
3501+ authenticating_interactively_ = interactive;
3502+ authenticating_invalidate_cache_ = invalidate_cache;
3503+ credentials_ = boost::blank();
3504+
3505+ OnlineAccounts::AuthenticationData auth_data(
3506+ account_->authenticationMethod());
3507+ auth_data.setInteractive(interactive);
3508+ if (invalidate_cache)
3509+ {
3510+ auth_data.invalidateCachedReply();
3511+ }
3512+ OnlineAccounts::PendingCall call = account_->authenticate(auth_data);
3513+ auth_watcher_.reset(new OnlineAccounts::PendingCallWatcher(call));
3514+ connect(auth_watcher_.get(), &OnlineAccounts::PendingCallWatcher::finished,
3515+ this, &OnlineAccountData::on_authenticated);
3516+}
3517+
3518+bool OnlineAccountData::has_credentials()
3519+{
3520+ // variant index 0 is boost::blank
3521+ return credentials_.which() != 0;
3522+}
3523+
3524+Credentials const& OnlineAccountData::credentials()
3525+{
3526+ return credentials_;
3527+}
3528+
3529+void OnlineAccountData::on_authenticated()
3530+{
3531+ credentials_ = boost::blank();
3532+ switch (account_->authenticationMethod()) {
3533+ case OnlineAccounts::AuthenticationMethodOAuth1:
3534+ {
3535+ OnlineAccounts::OAuth1Reply reply(*auth_watcher_);
3536+ if (reply.hasError())
3537+ {
3538+ qDebug() << "Failed to authenticate:" << reply.error().text();
3539+ }
3540+ else
3541+ {
3542+ credentials_ = OAuth1Credentials{
3543+ reply.consumerKey().toStdString(),
3544+ reply.consumerSecret().toStdString(),
3545+ reply.token().toStdString(),
3546+ reply.tokenSecret().toStdString(),
3547+ };
3548+ }
3549+ break;
3550+ }
3551+ case OnlineAccounts::AuthenticationMethodOAuth2:
3552+ {
3553+ OnlineAccounts::OAuth2Reply reply(*auth_watcher_);
3554+ if (reply.hasError())
3555+ {
3556+ qDebug() << "Failed to authenticate:" << reply.error().text();
3557+ }
3558+ else
3559+ {
3560+ credentials_ = OAuth2Credentials{
3561+ reply.accessToken().toStdString(),
3562+ };
3563+ }
3564+ break;
3565+ }
3566+ case OnlineAccounts::AuthenticationMethodPassword:
3567+ {
3568+ // Grab hostname from account settings if available
3569+ string host = account_->setting("host").toString().toStdString();
3570+
3571+ OnlineAccounts::PasswordReply reply(*auth_watcher_);
3572+ if (reply.hasError())
3573+ {
3574+ qDebug() << "Failed to authenticate:" << reply.error().text();
3575+ }
3576+ else
3577+ {
3578+ QString username = reply.username();
3579+ QString password = reply.password();
3580+
3581+ // Work around password credentials bug in online-accounts-service
3582+ // https://bugs.launchpad.net/bugs/1628473
3583+ if (username.isEmpty() && password.isEmpty())
3584+ {
3585+ username = reply.data()["UserName"].toString();
3586+ password = reply.data()["Secret"].toString();
3587+ }
3588+ credentials_ = PasswordCredentials{
3589+ username.toStdString(),
3590+ password.toStdString(),
3591+ move(host),
3592+ };
3593+ }
3594+ break;
3595+ }
3596+ default:
3597+ qDebug() << "Unhandled authentication method:"
3598+ << account_->authenticationMethod();
3599+ }
3600+ auth_watcher_.reset();
3601+
3602+ Q_EMIT authenticated();
3603+}
3604+
3605+void OnlineAccountData::on_changed()
3606+{
3607+ // Assume that if we're in the middle of authenticating that we'll
3608+ // receive valid credentials for the changed account.
3609+ if (auth_watcher_)
3610+ {
3611+ return;
3612+ }
3613+ // Otherwise, invalidate the credentials
3614+ credentials_ = boost::blank();
3615+}
3616+
3617+}
3618+}
3619+}
3620+}
3621
3622=== modified file 'src/provider/internal/ProviderInterface.cpp'
3623--- src/provider/internal/ProviderInterface.cpp 2016-11-03 02:43:43 +0000
3624+++ src/provider/internal/ProviderInterface.cpp 2017-03-20 04:58:30 +0000
3625@@ -66,18 +66,7 @@
3626 new Handler(account_, callback, connection(), message()));
3627 connect(handler.get(), &Handler::finished, this, &ProviderInterface::request_finished);
3628 setDelayedReply(true);
3629- // If we haven't retrieved the authentication details from
3630- // OnlineAccounts, delay processing the handler until then.
3631- if (account_->has_credentials())
3632- {
3633- handler->begin();
3634- }
3635- else
3636- {
3637- account_->authenticate(true);
3638- connect(account_.get(), &AccountData::authenticated,
3639- handler.get(), &Handler::begin);
3640- }
3641+ handler->begin();
3642 requests_.emplace(handler.get(), std::move(handler));
3643 }
3644
3645@@ -210,6 +199,7 @@
3646 EXEC_IN_MAIN
3647 [account, message](decltype(f) f) -> QDBusMessage {
3648 auto job = f.get();
3649+ job->p_->set_activity(account->inactivity_timer());
3650 auto upload_id = QString::fromStdString(job->upload_id());
3651 QDBusUnixFileDescriptor file_desc;
3652 int fd = job->p_->take_write_socket();
3653@@ -241,6 +231,7 @@
3654 EXEC_IN_MAIN
3655 [account, message](decltype(f) f) -> QDBusMessage {
3656 auto job = f.get();
3657+ job->p_->set_activity(account->inactivity_timer());
3658 auto upload_id = QString::fromStdString(job->upload_id());
3659 QDBusUnixFileDescriptor file_desc;
3660 int fd = job->p_->take_write_socket();
3661@@ -303,6 +294,7 @@
3662 EXEC_IN_MAIN
3663 [account, message](decltype(f) f) -> QDBusMessage {
3664 auto job = f.get();
3665+ job->p_->set_activity(account->inactivity_timer());
3666 auto download_id = QString::fromStdString(job->download_id());
3667 QDBusUnixFileDescriptor file_desc;
3668 int fd = job->p_->take_read_socket();
3669
3670=== modified file 'src/provider/internal/ServerImpl.cpp'
3671--- src/provider/internal/ServerImpl.cpp 2016-08-12 06:19:56 +0000
3672+++ src/provider/internal/ServerImpl.cpp 2017-03-20 04:58:30 +0000
3673@@ -17,16 +17,19 @@
3674 */
3675
3676 #include <unity/storage/provider/internal/ServerImpl.h>
3677-#include <unity/storage/provider/Exceptions.h>
3678+#include <unity/storage/internal/EnvVars.h>
3679 #include <unity/storage/provider/ProviderBase.h>
3680-#include <unity/storage/provider/internal/AccountData.h>
3681+#include <unity/storage/provider/internal/FixedAccountData.h>
3682 #include <unity/storage/provider/internal/MainLoopExecutor.h>
3683+#include <unity/storage/provider/internal/OnlineAccountData.h>
3684 #include <unity/storage/provider/internal/dbusmarshal.h>
3685 #include "provideradaptor.h"
3686
3687-#include <stdexcept>
3688+#include <QDebug>
3689
3690 using namespace std;
3691+using unity::storage::internal::EnvVars;
3692+using unity::storage::internal::InactivityTimer;
3693
3694 namespace unity
3695 {
3696@@ -43,57 +46,168 @@
3697 , service_id_(account_service_id)
3698 , trace_message_handler_("storage_provider")
3699 {
3700+ qRegisterMetaType<std::exception_ptr>();
3701 qDBusRegisterMetaType<Item>();
3702 qDBusRegisterMetaType<std::vector<Item>>();
3703 }
3704
3705 ServerImpl::~ServerImpl() = default;
3706
3707-void ServerImpl::init(int& argc, char **argv)
3708+void ServerImpl::init(int& argc, char **argv, QDBusConnection *bus)
3709 {
3710- app_.reset(new QCoreApplication(argc, argv));
3711- auto bus = QDBusConnection::sessionBus();
3712- dbus_peer_ = make_shared<DBusPeerCache>(bus);
3713+ if (bus)
3714+ {
3715+ bus_.reset(new QDBusConnection(*bus));
3716+ }
3717+ else
3718+ {
3719+ // Only initialise QCoreApplication if we haven't been passed
3720+ // in an existing bus connection.
3721+ app_.reset(new QCoreApplication(argc, argv));
3722+ bus_.reset(new QDBusConnection(QDBusConnection::sessionBus()));
3723+ }
3724+ int const timeout = EnvVars::provider_timeout_ms();
3725+ inactivity_timer_ = make_shared<InactivityTimer>(timeout);
3726+ connect(inactivity_timer_.get(), &InactivityTimer::timeout,
3727+ this, &ServerImpl::on_timeout);
3728+
3729+ dbus_peer_ = make_shared<DBusPeerCache>(*bus_);
3730
3731 #ifdef SF_SUPPORTS_EXECUTORS
3732 // Ensure the executor is instantiated in the main thread.
3733 MainLoopExecutor::instance();
3734 #endif
3735
3736- manager_.reset(new OnlineAccounts::Manager("", bus));
3737- connect(manager_.get(), &OnlineAccounts::Manager::ready,
3738- this, &ServerImpl::account_manager_ready);
3739-}
3740-
3741-void ServerImpl::run()
3742-{
3743- app_->exec();
3744-}
3745-
3746-void ServerImpl::account_manager_ready()
3747-{
3748- auto bus = QDBusConnection::sessionBus();
3749+ if (service_id_.empty())
3750+ {
3751+ // If we have an empty service ID, create a single instance of
3752+ // the provider which doesn't interact with online-accounts.
3753+ add_account(nullptr);
3754+ register_bus_name();
3755+ }
3756+ else
3757+ {
3758+ // Otherwise use online-accounts to discover all accounts
3759+ // providing the service ID.
3760+ manager_.reset(new OnlineAccounts::Manager("", *bus_));
3761+ connect(manager_.get(), &OnlineAccounts::Manager::ready,
3762+ this, &ServerImpl::on_account_manager_ready);
3763+ connect(manager_.get(), &OnlineAccounts::Manager::accountAvailable,
3764+ this, &ServerImpl::on_account_available);
3765+ }
3766+}
3767+
3768+int ServerImpl::run()
3769+{
3770+ return app_->exec();
3771+}
3772+
3773+void ServerImpl::register_bus_name()
3774+{
3775+ if (!bus_->registerService(QString::fromStdString(bus_name_)))
3776+ {
3777+ QString msg = "Could not acquire bus name: " + QString::fromStdString(bus_name_);
3778+ QString last_error = bus_->lastError().message();
3779+ if (!last_error.isEmpty())
3780+ {
3781+ msg += ": " + last_error;
3782+ }
3783+ qCritical().noquote() << msg;
3784+ app_->exit(1);
3785+ return;
3786+ }
3787+ // TODO: claim bus name
3788+ qDebug() << "Bus unique name:" << bus_->baseService();
3789+}
3790+
3791+void ServerImpl::add_account(OnlineAccounts::Account* account)
3792+{
3793+ OnlineAccounts::AccountId account_id = 0;
3794+ shared_ptr<AccountData> account_data;
3795+
3796+ if (account)
3797+ {
3798+ account_id = account->id();
3799+ // Ignore if we already have access to the account
3800+ if (interfaces_.find(account_id) != interfaces_.end())
3801+ {
3802+ return;
3803+ }
3804+
3805+ qDebug() << "Found account" << account->id() << "for service" << account->serviceId();
3806+ account_data = make_shared<OnlineAccountData>(
3807+ server_->make_provider(), dbus_peer_, inactivity_timer_,
3808+ *bus_, account);
3809+ }
3810+ else
3811+ {
3812+ account_data = make_shared<FixedAccountData>(
3813+ server_->make_provider(), dbus_peer_, inactivity_timer_, *bus_);
3814+ }
3815+ unique_ptr<ProviderInterface> iface(
3816+ new ProviderInterface(account_data));
3817+ // this instance is managed by Qt's parent/child memory management
3818+ new ProviderAdaptor(iface.get());
3819+
3820+ bus_->registerObject(QStringLiteral("/provider/%1").arg(account_id), iface.get());
3821+ interfaces_.emplace(account_id, std::move(iface));
3822+
3823+ // watch for account disable signals.
3824+ if (account)
3825+ {
3826+ connect(account, &OnlineAccounts::Account::disabled,
3827+ this, &ServerImpl::on_account_disabled);
3828+ }
3829+
3830+ Q_EMIT accountAdded();
3831+}
3832+
3833+void ServerImpl::remove_account(OnlineAccounts::Account* account)
3834+{
3835+ // Ignore if we don't know about this account
3836+ if (interfaces_.find(account->id()) == interfaces_.end())
3837+ {
3838+ return;
3839+ }
3840+
3841+ qDebug() << "Disabled account" << account->id() << "for service" << account->serviceId();
3842+ bus_->unregisterObject(QStringLiteral("/provider/%1").arg(account->id()));
3843+ interfaces_.erase(account->id());
3844+
3845+ Q_EMIT accountRemoved();
3846+}
3847+
3848+void ServerImpl::on_account_manager_ready()
3849+{
3850 for (const auto& account : manager_->availableAccounts(QString::fromStdString(service_id_)))
3851 {
3852- qDebug() << "Found account" << account->id() << "for service" << account->serviceId();
3853- auto account_data = make_shared<AccountData>(
3854- server_->make_provider(), dbus_peer_, bus, account);
3855- unique_ptr<ProviderInterface> iface(
3856- new ProviderInterface(account_data));
3857- // this instance is managed by Qt's parent/child memory management
3858- new ProviderAdaptor(iface.get());
3859-
3860- bus.registerObject(QStringLiteral("/provider/%1").arg(account->id()), iface.get());
3861- interfaces_.emplace(account->id(), std::move(iface));
3862+ add_account(account);
3863 }
3864
3865- if (!bus.registerService(QString::fromStdString(bus_name_)))
3866+ register_bus_name();
3867+}
3868+
3869+void ServerImpl::on_account_available(OnlineAccounts::Account* account)
3870+{
3871+ // Or if the service ID doesn't match
3872+ if (account->serviceId() != QString::fromStdString(service_id_))
3873 {
3874- string msg = string("Could not acquire bus name: ") + bus_name_ + ": " + bus.lastError().message().toStdString();
3875- throw ResourceException(msg, int(bus.lastError().type()));
3876+ return;
3877 }
3878- // TODO: claim bus name
3879- qDebug() << "Bus unique name:" << bus.baseService();
3880+ add_account(account);
3881+}
3882+
3883+void ServerImpl::on_account_disabled()
3884+{
3885+ auto account = static_cast<OnlineAccounts::Account*>(sender());
3886+ remove_account(account);
3887+}
3888+
3889+void ServerImpl::on_timeout()
3890+{
3891+ int const timeout = EnvVars::provider_timeout_ms();
3892+ qInfo() << "Exiting after" << timeout << "ms of idle time";
3893+ app_->quit();
3894 }
3895
3896 }
3897
3898=== modified file 'src/provider/internal/TestServerImpl.cpp'
3899--- src/provider/internal/TestServerImpl.cpp 2016-09-28 07:04:41 +0000
3900+++ src/provider/internal/TestServerImpl.cpp 2017-03-20 04:58:30 +0000
3901@@ -17,10 +17,12 @@
3902 */
3903
3904 #include <unity/storage/provider/internal/TestServerImpl.h>
3905+#include <unity/storage/internal/InactivityTimer.h>
3906 #include <unity/storage/provider/Exceptions.h>
3907 #include <unity/storage/provider/ProviderBase.h>
3908-#include <unity/storage/provider/internal/AccountData.h>
3909 #include <unity/storage/provider/internal/DBusPeerCache.h>
3910+#include <unity/storage/provider/internal/FixedAccountData.h>
3911+#include <unity/storage/provider/internal/OnlineAccountData.h>
3912 #include <unity/storage/provider/internal/ProviderInterface.h>
3913 #include <unity/storage/provider/internal/dbusmarshal.h>
3914 #include "provideradaptor.h"
3915@@ -30,6 +32,12 @@
3916 #include <stdexcept>
3917
3918 using namespace std;
3919+using unity::storage::internal::InactivityTimer;
3920+
3921+namespace
3922+{
3923+constexpr int TIMEOUT = 30000;
3924+}
3925
3926 namespace unity
3927 {
3928@@ -44,14 +52,25 @@
3929 OnlineAccounts::Account* account,
3930 QDBusConnection const& connection,
3931 string const& object_path)
3932- : connection_(connection), object_path_(object_path)
3933+ : connection_(connection), object_path_(object_path),
3934+ inactivity_timer_(make_shared<InactivityTimer>(TIMEOUT))
3935 {
3936+ qRegisterMetaType<std::exception_ptr>();
3937 qDBusRegisterMetaType<Item>();
3938 qDBusRegisterMetaType<std::vector<Item>>();
3939
3940 auto peer_cache = make_shared<DBusPeerCache>(connection_);
3941- auto account_data = make_shared<AccountData>(
3942- provider, peer_cache, connection_, account);
3943+ shared_ptr<AccountData> account_data;
3944+ if (account)
3945+ {
3946+ account_data = make_shared<OnlineAccountData>(
3947+ provider, peer_cache, inactivity_timer_, connection_, account);
3948+ }
3949+ else
3950+ {
3951+ account_data = make_shared<FixedAccountData>(
3952+ provider, peer_cache, inactivity_timer_, connection_);
3953+ }
3954 interface_.reset(new ProviderInterface(account_data));
3955 new ProviderAdaptor(interface_.get());
3956
3957
3958=== modified file 'src/provider/internal/UploadJobImpl.cpp'
3959--- src/provider/internal/UploadJobImpl.cpp 2016-09-02 13:51:37 +0000
3960+++ src/provider/internal/UploadJobImpl.cpp 2017-03-20 04:58:30 +0000
3961@@ -30,6 +30,8 @@
3962
3963 using namespace std;
3964 using namespace unity::storage::internal;
3965+using unity::storage::internal::ActivityNotifier;
3966+using unity::storage::internal::InactivityTimer;
3967
3968 namespace unity
3969 {
3970@@ -106,6 +108,11 @@
3971 return sock;
3972 }
3973
3974+void UploadJobImpl::set_activity(std::shared_ptr<InactivityTimer> const& inactivity_timer)
3975+{
3976+ activity_ = ActivityNotifier(inactivity_timer);
3977+}
3978+
3979 void UploadJobImpl::report_error(exception_ptr p)
3980 {
3981 if (read_socket_ >= 0)
3982
3983=== modified file 'src/qt/client/internal/remote_client/RuntimeImpl.cpp'
3984--- src/qt/client/internal/remote_client/RuntimeImpl.cpp 2016-11-15 05:48:12 +0000
3985+++ src/qt/client/internal/remote_client/RuntimeImpl.cpp 2017-03-20 04:58:30 +0000
3986@@ -48,9 +48,10 @@
3987 static const map<QString, QString> BUS_NAMES =
3988 {
3989 { "storage-provider-test", "com.canonical.StorageFramework.Provider.ProviderTest" },
3990- { "com.canonical.scopes.mcloud_mcloud_mcloud", "com.canonical.StorageFramework.Provider.McloudProvider" },
3991+ { "storage-provider-mcloud", "com.canonical.StorageFramework.Provider.McloudProvider" },
3992 { "storage-provider-owncloud", "com.canonical.StorageFramework.Provider.OwnCloud" },
3993 { "storage-provider-onedrive", "com.canonical.StorageFramework.Provider.OnedriveProvider" },
3994+ { "storage-provider-gdrive", "com.canonical.StorageFramework.Provider.GdriveProvider" },
3995 };
3996
3997 } // namespace
3998
3999=== modified file 'src/qt/internal/RuntimeImpl.cpp'
4000--- src/qt/internal/RuntimeImpl.cpp 2016-11-21 13:29:39 +0000
4001+++ src/qt/internal/RuntimeImpl.cpp 2017-03-20 04:58:30 +0000
4002@@ -24,9 +24,11 @@
4003 #include <unity/storage/qt/internal/AccountImpl.h>
4004 #include <unity/storage/qt/internal/AccountsJobImpl.h>
4005 #include <unity/storage/qt/internal/StorageErrorImpl.h>
4006+#include <unity/storage/qt/Downloader.h>
4007 #include <unity/storage/qt/ItemJob.h>
4008 #include <unity/storage/qt/ItemListJob.h>
4009 #include <unity/storage/qt/Runtime.h>
4010+#include <unity/storage/qt/Uploader.h>
4011 #include <unity/storage/qt/VoidJob.h>
4012 #include <unity/storage/registry/Registry.h>
4013
4014@@ -51,10 +53,12 @@
4015 qRegisterMetaType<unity::storage::qt::AccountsJob::Status>();
4016 qRegisterMetaType<unity::storage::qt::Account>();
4017 qRegisterMetaType<QList<unity::storage::qt::Account>>();
4018+ qRegisterMetaType<unity::storage::qt::Downloader::Status>();
4019 qRegisterMetaType<unity::storage::qt::Item>();
4020 qRegisterMetaType<QList<unity::storage::qt::Item>>();
4021 qRegisterMetaType<unity::storage::qt::ItemJob::Status>();
4022 qRegisterMetaType<unity::storage::qt::ItemListJob::Status>();
4023+ qRegisterMetaType<unity::storage::qt::Uploader::Status>();
4024 qRegisterMetaType<unity::storage::qt::VoidJob::Status>();
4025
4026 qDBusRegisterMetaType<unity::storage::internal::ItemMetadata>();
4027
4028=== modified file 'src/qt/internal/StorageErrorImpl.cpp'
4029--- src/qt/internal/StorageErrorImpl.cpp 2016-10-13 06:48:11 +0000
4030+++ src/qt/internal/StorageErrorImpl.cpp 2017-03-20 04:58:30 +0000
4031@@ -48,7 +48,8 @@
4032 QStringLiteral("Cancelled"),
4033 QStringLiteral("LogicError"),
4034 QStringLiteral("InvalidArgument"),
4035- QStringLiteral("ResourceError")
4036+ QStringLiteral("ResourceError"),
4037+ QStringLiteral("Unauthorized"),
4038 };
4039
4040 } // namespace
4041@@ -77,7 +78,6 @@
4042 || type == StorageError::Type::Cancelled
4043 || type == StorageError::Type::LogicError
4044 || type == StorageError::Type::InvalidArgument);
4045- assert(!msg.isEmpty());
4046
4047 message_ = msg;
4048 }
4049@@ -86,7 +86,6 @@
4050 : StorageErrorImpl(type)
4051 {
4052 assert(type == StorageError::Type::NotExists);
4053- assert(!msg.isEmpty());
4054
4055 message_ = msg;
4056 item_id_ = key;
4057@@ -103,9 +102,6 @@
4058 : StorageErrorImpl(type)
4059 {
4060 assert(type == StorageError::Type::Exists);
4061- assert(!msg.isEmpty());
4062- assert(!item_id.isEmpty());
4063- assert(!item_name.isEmpty());
4064
4065 message_ = msg;
4066 item_id_ = item_id;
4067@@ -116,7 +112,6 @@
4068 : StorageErrorImpl(type)
4069 {
4070 assert(type == StorageError::Type::ResourceError);
4071- assert(!msg.isEmpty());
4072
4073 message_ = msg;
4074 error_code_ = error_code;
4075
4076=== modified file 'src/qt/internal/unmarshal_error.cpp'
4077--- src/qt/internal/unmarshal_error.cpp 2016-09-29 03:17:56 +0000
4078+++ src/qt/internal/unmarshal_error.cpp 2017-03-20 04:58:30 +0000
4079@@ -82,6 +82,7 @@
4080 { "NotExistsException", make_error<StorageError::Type::NotExists> },
4081 { "ExistsException", make_error<StorageError::Type::Exists> },
4082 { "ConflictException", make_error<StorageError::Type::Conflict> },
4083+ { "UnauthorizedException", make_error<StorageError::Type::Unauthorized> },
4084 { "PermissionException", make_error<StorageError::Type::PermissionDenied> },
4085 { "CancelledException", make_error<StorageError::Type::Cancelled> },
4086 { "LogicException", make_error<StorageError::Type::LogicError> },
4087
4088=== modified file 'src/registry/CMakeLists.txt'
4089--- src/registry/CMakeLists.txt 2016-11-21 04:24:13 +0000
4090+++ src/registry/CMakeLists.txt 2017-03-20 04:58:30 +0000
4091@@ -1,8 +1,3 @@
4092-include_directories(
4093- ${CMAKE_CURRENT_BINARY_DIR}
4094- ${ONLINEACCOUNTS_DEPS_INCLUDE_DIRS}
4095-)
4096-
4097 set(TARGET "storage-framework-registry")
4098
4099 qt5_add_dbus_adaptor(adaptor_files
4100@@ -16,24 +11,36 @@
4101 GENERATED TRUE
4102 )
4103
4104-add_executable(${TARGET}
4105+add_library(registry-static STATIC
4106 internal/ListAccountsHandler.cpp
4107 internal/qdbus-last-error-msg.cpp
4108 internal/RegistryAdaptor.cpp
4109- main.cpp
4110 ${CMAKE_SOURCE_DIR}/include/unity/storage/registry/internal/ListAccountsHandler.h
4111 ${CMAKE_SOURCE_DIR}/include/unity/storage/registry/internal/RegistryAdaptor.h
4112 ${adaptor_files})
4113
4114-set_target_properties(${TARGET} PROPERTIES AUTOMOC TRUE)
4115-
4116-target_link_libraries(${TARGET}
4117+set_target_properties(registry-static PROPERTIES AUTOMOC TRUE)
4118+
4119+target_include_directories(registry-static PUBLIC
4120+ ${GLIB_DEPS_INCLUDE_DIRS}
4121+ ${ONLINEACCOUNTS_DEPS_INCLUDE_DIRS}
4122+)
4123+
4124+target_link_libraries(registry-static PUBLIC
4125 storage-framework-common-internal
4126 Qt5::Core
4127 Qt5::DBus
4128+ ${GLIB_DEPS_LDFLAGS}
4129 ${ONLINEACCOUNTS_DEPS_LDFLAGS}
4130 )
4131
4132+add_executable(${TARGET}
4133+ main.cpp
4134+)
4135+target_link_libraries(${TARGET}
4136+ registry-static
4137+)
4138+
4139 install(
4140 TARGETS ${TARGET}
4141 RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME}
4142
4143=== modified file 'src/registry/internal/ListAccountsHandler.cpp'
4144--- src/registry/internal/ListAccountsHandler.cpp 2016-11-28 11:04:17 +0000
4145+++ src/registry/internal/ListAccountsHandler.cpp 2017-03-20 04:58:30 +0000
4146@@ -21,6 +21,11 @@
4147 #include <unity/storage/internal/AccountDetails.h>
4148 #include <unity/storage/registry/internal/qdbus-last-error-msg.h>
4149
4150+#pragma GCC diagnostic push
4151+#pragma GCC diagnostic ignored "-Wold-style-cast"
4152+#include <glib.h>
4153+#pragma GCC diagnostic pop
4154+
4155 #include <OnlineAccounts/Account>
4156 #include <QDebug>
4157
4158@@ -65,9 +70,10 @@
4159 static map<QString, ProviderDetails> const BUS_NAMES =
4160 {
4161 { "storage-provider-test", { "com.canonical.StorageFramework.Provider.ProviderTest", "Test Provider" } },
4162- { "com.canonical.scopes.mcloud_mcloud_mcloud", { "com.canonical.StorageFramework.Provider.McloudProvider", "mcloud" } },
4163+ { "storage-provider-mcloud", { "com.canonical.StorageFramework.Provider.McloudProvider", "mcloud" } },
4164 { "storage-provider-owncloud", { "com.canonical.StorageFramework.Provider.OwnCloud", "ownCloud" } },
4165 { "storage-provider-onedrive", { "com.canonical.StorageFramework.Provider.OnedriveProvider", "OneDrive" } },
4166+ { "storage-provider-gdrive", { "com.canonical.StorageFramework.Provider.GdriveProvider", "GDrive" } },
4167 };
4168
4169 } // namespace
4170@@ -99,6 +105,17 @@
4171 accounts.append(ad);
4172 }
4173
4174+ // Add an entry for the local provider, which Online Accounts doesn't know about.
4175+ storage::internal::AccountDetails ad;
4176+ ad.busName = "com.canonical.StorageFramework.Provider.Local";
4177+ ad.objectPath = QDBusObjectPath(QStringLiteral("/provider/0"));
4178+ ad.id = 0;
4179+ ad.serviceId = "";
4180+ ad.displayName = g_get_user_name();
4181+ ad.providerName = "Local Provider";
4182+ ad.iconName = "";
4183+ accounts.append(ad);
4184+
4185 if (!conn_.send(msg_.createReply(QVariant::fromValue(accounts))))
4186 {
4187 auto msg = last_error_msg(conn_);
4188
4189=== modified file 'src/registry/main.cpp'
4190--- src/registry/main.cpp 2016-11-22 07:23:48 +0000
4191+++ src/registry/main.cpp 2017-03-20 04:58:30 +0000
4192@@ -50,12 +50,14 @@
4193 auto conn = QDBusConnection::sessionBus();
4194
4195 int const timeout_ms = internal::EnvVars::registry_timeout_ms();
4196- auto timeout_func = [&app, timeout_ms]
4197- {
4198- qInfo().noquote().nospace() << "Exiting after " << QString::number(timeout_ms) << " ms of idle time";
4199- app.quit();
4200- };
4201- auto inactivity_timer = make_shared<unity::storage::internal::InactivityTimer>(timeout_ms, timeout_func);
4202+ auto inactivity_timer = make_shared<unity::storage::internal::InactivityTimer>(timeout_ms);
4203+ QObject::connect(
4204+ inactivity_timer.get(), &unity::storage::internal::InactivityTimer::timeout,
4205+ [&app, timeout_ms]
4206+ {
4207+ qInfo().noquote().nospace() << "Exiting after " << QString::number(timeout_ms) << " ms of idle time";
4208+ app.quit();
4209+ });
4210
4211 registry::internal::RegistryAdaptor registry_adaptor(conn, inactivity_timer);
4212 new ::RegistryAdaptor(&registry_adaptor);
4213
4214=== modified file 'tests/CMakeLists.txt'
4215--- tests/CMakeLists.txt 2016-11-28 10:54:49 +0000
4216+++ tests/CMakeLists.txt 2017-03-20 04:58:30 +0000
4217@@ -8,11 +8,13 @@
4218 set(unit_test_dirs
4219 registry
4220 local-client
4221+ local-provider
4222 remote-client
4223 remote-client-v1
4224 provider-AccountData
4225 provider-DBusPeerCache
4226 provider-ProviderInterface
4227+ provider-Server
4228 )
4229
4230 set(slow_test_dirs
4231
4232=== modified file 'tests/copyright/check_copyright.sh'
4233--- tests/copyright/check_copyright.sh 2016-11-29 06:39:29 +0000
4234+++ tests/copyright/check_copyright.sh 2017-03-20 04:58:30 +0000
4235@@ -36,7 +36,7 @@
4236 source_dir="$1"
4237 ignore_dir="${2:-}"
4238
4239-ignore_pat="\\.sci$|\\.swp$|\\.bzr|debian|qmldir|HACKING|ubsan-suppress|valgrind-suppress|\\.txt$|\\.xml$|\\.in$|\\.dox$|\\.yaml$"
4240+ignore_pat="/parts/|/stage/|/prime/|~$|\\.sci$|\\.swp$|\\.bzr|debian|qmldir|HACKING|ubsan-suppress|valgrind-suppress|\\.txt$|\\.xml$|\\.in$|\\.dox$|\\.yaml$"
4241
4242 #
4243 # We don't use the -i option of licensecheck to add ignore_dir to the pattern because Jenkins creates directories
4244
4245=== added directory 'tests/local-provider'
4246=== added file 'tests/local-provider/CMakeLists.txt'
4247--- tests/local-provider/CMakeLists.txt 1970-01-01 00:00:00 +0000
4248+++ tests/local-provider/CMakeLists.txt 2017-03-20 04:58:30 +0000
4249@@ -0,0 +1,18 @@
4250+add_executable(local-provider_test local-provider_test.cpp)
4251+
4252+add_definitions(-DTEST_DIR="${CMAKE_CURRENT_BINARY_DIR}" -DBOOST_THREAD_VERSION=4)
4253+
4254+target_link_libraries(local-provider_test
4255+ local-provider-lib
4256+ storage-framework-provider
4257+ storage-framework-qt-client-v2
4258+ Qt5::Test
4259+ ${Boost_LIBRARIES}
4260+ ${GLIB_DEPS_LIBRARIES}
4261+ ${GIO_DEPS_LIBRARIES}
4262+ testutils
4263+ gtest
4264+)
4265+add_test(local-provider local-provider_test)
4266+
4267+set(UNIT_TEST_TARGETS ${UNIT_TEST_TARGETS} PARENT_SCOPE)
4268
4269=== added file 'tests/local-provider/local-provider_test.cpp'
4270--- tests/local-provider/local-provider_test.cpp 1970-01-01 00:00:00 +0000
4271+++ tests/local-provider/local-provider_test.cpp 2017-03-20 04:58:30 +0000
4272@@ -0,0 +1,1476 @@
4273+/*
4274+ * Copyright (C) 2017 Canonical Ltd
4275+ *
4276+ * This program is free software: you can redistribute it and/or modify
4277+ * it under the terms of the GNU Lesser General Public License version 3 as
4278+ * published by the Free Software Foundation.
4279+ *
4280+ * This program is distributed in the hope that it will be useful,
4281+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4282+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4283+ * GNU Lesser General Public License for more details.
4284+ *
4285+ * You should have received a copy of the GNU Lesser General Public License
4286+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4287+ *
4288+ * Authors: Michi Henning <michi.henning@canonical.com>
4289+ */
4290+
4291+#include "../../src/local-provider/LocalDownloadJob.h"
4292+#include "../../src/local-provider/LocalProvider.h"
4293+#include "../../src/local-provider/LocalUploadJob.h"
4294+
4295+#include <unity/storage/provider/DownloadJob.h>
4296+#include <unity/storage/provider/Exceptions.h>
4297+#include <unity/storage/provider/Server.h>
4298+#include <unity/storage/qt/client-api.h>
4299+#include <utils/env_var_guard.h>
4300+#include <utils/ProviderFixture.h>
4301+
4302+#include <boost/algorithm/string.hpp>
4303+#include <gtest/gtest.h>
4304+#include <QCoreApplication>
4305+#include <QSignalSpy>
4306+
4307+#include <chrono>
4308+#include <regex>
4309+
4310+#include <fcntl.h>
4311+
4312+using namespace unity::storage;
4313+using namespace std;
4314+
4315+namespace
4316+{
4317+
4318+int64_t nanosecs_now()
4319+{
4320+ return chrono::system_clock::now().time_since_epoch() / chrono::nanoseconds(1);
4321+}
4322+
4323+class LocalProviderTest : public ProviderFixture
4324+{
4325+protected:
4326+ void SetUp() override
4327+ {
4328+ tmp_dir_.reset(new QTemporaryDir(TEST_DIR "/data.XXXXXX"));
4329+ ASSERT_TRUE(tmp_dir_->isValid());
4330+ setenv("SF_LOCAL_PROVIDER_ROOT", ROOT_DIR().c_str(), true);
4331+
4332+ ProviderFixture::SetUp();
4333+ runtime_.reset(new qt::Runtime(connection()));
4334+ acc_ = runtime_->make_test_account(service_connection_->baseService(), object_path());
4335+ }
4336+
4337+ void TearDown() override
4338+ {
4339+ runtime_.reset();
4340+ ProviderFixture::TearDown();
4341+ if (HasFailure())
4342+ {
4343+ tmp_dir_->setAutoRemove(false);
4344+ }
4345+ tmp_dir_.reset();
4346+ }
4347+
4348+ std::string ROOT_DIR() const
4349+ {
4350+ return tmp_dir_->path().toStdString();
4351+ }
4352+
4353+ std::unique_ptr<QTemporaryDir> tmp_dir_;
4354+ unique_ptr<qt::Runtime> runtime_;
4355+ qt::Account acc_;
4356+};
4357+
4358+constexpr int SIGNAL_WAIT_TIME = 30000;
4359+
4360+template <typename Job>
4361+void wait(Job* job)
4362+{
4363+ QSignalSpy spy(job, &Job::statusChanged);
4364+ while (job->status() == Job::Loading)
4365+ {
4366+ if (!spy.wait(SIGNAL_WAIT_TIME))
4367+ {
4368+ throw runtime_error("Wait for statusChanged signal timed out");
4369+ }
4370+ }
4371+}
4372+
4373+qt::Item get_root(qt::Account const& account)
4374+{
4375+ unique_ptr<qt::ItemListJob> j(account.roots());
4376+ assert(j->isValid());
4377+ QSignalSpy ready_spy(j.get(), &qt::ItemListJob::itemsReady);
4378+ assert(ready_spy.wait(SIGNAL_WAIT_TIME));
4379+ auto arg = ready_spy.takeFirst();
4380+ auto items = qvariant_cast<QList<qt::Item>>(arg.at(0));
4381+ assert(items.size() == 1);
4382+ return items[0];
4383+}
4384+
4385+QList<qt::Item> get_items(qt::ItemListJob *job)
4386+{
4387+ QList<qt::Item> items;
4388+ auto connection = QObject::connect(
4389+ job, &qt::ItemListJob::itemsReady,
4390+ [&](QList<qt::Item> const& new_items)
4391+ {
4392+ items.append(new_items);
4393+ });
4394+ try
4395+ {
4396+ wait(job);
4397+ }
4398+ catch (...)
4399+ {
4400+ QObject::disconnect(connection);
4401+ throw;
4402+ }
4403+ QObject::disconnect(connection);
4404+ return items;
4405+}
4406+
4407+const string file_contents =
4408+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do "
4409+ "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut "
4410+ "enim ad minim veniam, quis nostrud exercitation ullamco laboris "
4411+ "nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor "
4412+ "in reprehenderit in voluptate velit esse cillum dolore eu fugiat "
4413+ "nulla pariatur. Excepteur sint occaecat cupidatat non proident, "
4414+ "sunt in culpa qui officia deserunt mollit anim id est laborum.\n";
4415+
4416+} // namespace
4417+
4418+TEST(Directories, env_vars)
4419+{
4420+ // These tests cause the constructor to throw, so we instantiate the provider directly.
4421+
4422+ {
4423+ EnvVarGuard env("SF_LOCAL_PROVIDER_ROOT", "/no_such_dir");
4424+
4425+ try
4426+ {
4427+ LocalProvider();
4428+ FAIL();
4429+ }
4430+ catch (provider::InvalidArgumentException const& e)
4431+ {
4432+ EXPECT_STREQ("InvalidArgumentException: LocalProvider(): Environment variable "
4433+ "SF_LOCAL_PROVIDER_ROOT must denote an existing directory",
4434+ e.what());
4435+ }
4436+ }
4437+
4438+ {
4439+ EnvVarGuard env("SF_LOCAL_PROVIDER_ROOT", TEST_DIR "/Makefile");
4440+
4441+ try
4442+ {
4443+ LocalProvider();
4444+ FAIL();
4445+ }
4446+ catch (provider::InvalidArgumentException const& e)
4447+ {
4448+ EXPECT_STREQ("InvalidArgumentException: LocalProvider(): Environment variable "
4449+ "SF_LOCAL_PROVIDER_ROOT must denote an existing directory",
4450+ e.what());
4451+ }
4452+ }
4453+
4454+ {
4455+ string const dir = TEST_DIR "/noperm";
4456+
4457+ mkdir(dir.c_str(), 0555);
4458+ ASSERT_EQ(0, chmod(dir.c_str(), 0555)); // In case dir was there already.
4459+
4460+ EnvVarGuard env1("SF_LOCAL_PROVIDER_ROOT", nullptr);
4461+ EnvVarGuard env2("XDG_DATA_HOME", dir.c_str());
4462+
4463+ using namespace boost::filesystem;
4464+
4465+ try
4466+ {
4467+ LocalProvider();
4468+ ASSERT_EQ(0, chmod(dir.c_str(), 0775));
4469+ remove_all(dir);
4470+ FAIL();
4471+ }
4472+ catch (provider::PermissionException const& e)
4473+ {
4474+ EXPECT_EQ(string("PermissionException: LocalProvider(): \"") + dir + "/storage-framework\": "
4475+ "boost::filesystem::create_directories: Permission denied: \"" + dir + "/storage-framework\"",
4476+ e.what());
4477+ }
4478+
4479+ ASSERT_EQ(0, chmod(dir.c_str(), 0775));
4480+ ASSERT_TRUE(remove_all(dir));
4481+
4482+ // Try again, which must succeed now (for coverage).
4483+ LocalProvider();
4484+ ASSERT_TRUE(is_directory(dir + "/storage-framework/local"));
4485+ ASSERT_TRUE(remove_all(dir));
4486+ }
4487+
4488+ {
4489+ string const dir = TEST_DIR "/snap_user_common";
4490+ mkdir(dir.c_str(), 0775);
4491+
4492+ EnvVarGuard env1("SF_LOCAL_PROVIDER_ROOT", nullptr);
4493+ EnvVarGuard env2("SNAP_USER_COMMON", dir.c_str());
4494+
4495+ using namespace boost::filesystem;
4496+
4497+ LocalProvider();
4498+ ASSERT_TRUE(exists(dir + "/storage-framework/local"));
4499+ ASSERT_TRUE(remove_all(dir));
4500+ }
4501+}
4502+
4503+TEST_F(LocalProviderTest, basic)
4504+{
4505+ using namespace unity::storage::qt;
4506+
4507+ set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider));
4508+
4509+ // Basic sanity check, get the root.
4510+ unique_ptr<ItemListJob> j(acc_.roots());
4511+ EXPECT_TRUE(j->isValid());
4512+ QSignalSpy ready_spy(j.get(), &ItemListJob::itemsReady);
4513+ ASSERT_TRUE(ready_spy.wait(SIGNAL_WAIT_TIME));
4514+ ASSERT_EQ(1, ready_spy.count());
4515+ auto arg = ready_spy.takeFirst();
4516+ auto items = qvariant_cast<QList<Item>>(arg.at(0));
4517+ ASSERT_EQ(1, items.size());
4518+
4519+ // Check contents of returned item.
4520+ auto root = items[0];
4521+ EXPECT_TRUE(root.isValid());
4522+ EXPECT_EQ(Item::Type::Root, root.type());
4523+ EXPECT_EQ(ROOT_DIR(), root.itemId().toStdString());
4524+ EXPECT_EQ("/", root.name());
4525+ EXPECT_EQ("", root.etag());
4526+ EXPECT_EQ(QList<QString>(), root.parentIds());
4527+ qDebug() << root.lastModifiedTime();
4528+ EXPECT_TRUE(root.lastModifiedTime().isValid());
4529+ EXPECT_EQ(acc_, root.account());
4530+
4531+ ASSERT_EQ(5, root.metadata().size());
4532+ auto free_space_bytes = root.metadata().value("free_space_bytes").toULongLong();
4533+ cout << "free_space_bytes: " << free_space_bytes << endl;
4534+ EXPECT_GT(free_space_bytes, 0);
4535+ auto used_space_bytes = root.metadata().value("used_space_bytes").toULongLong();
4536+ cout << "used_space_bytes: " << used_space_bytes << endl;
4537+ EXPECT_GT(used_space_bytes, 0);
4538+ auto content_type = root.metadata().value("content_type").toString();
4539+ EXPECT_EQ("inode/directory", content_type);
4540+ auto writable = root.metadata().value("writable").toBool();
4541+ EXPECT_TRUE(writable);
4542+
4543+ // yyyy-mm-ddThh:mm:ssZ
4544+ 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$";
4545+ string mtime = root.metadata().value("last_modified_time").toString().toStdString();
4546+ cout << "last_modified_time: " << mtime << endl;
4547+ regex re(date_time_fmt);
4548+ EXPECT_TRUE(regex_match(mtime, re));
4549+}
4550+
4551+TEST_F(LocalProviderTest, create_folder)
4552+{
4553+ {
4554+ using namespace unity::storage::qt;
4555+
4556+ set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider));
4557+
4558+ auto root = get_root(acc_);
4559+ unique_ptr<ItemJob> job(root.createFolder("child"));
4560+ wait(job.get());
4561+ ASSERT_EQ(ItemJob::Finished, job->status()) << job->error().errorString().toStdString();
4562+
4563+ Item child = job->item();
4564+ EXPECT_EQ(ROOT_DIR() + "/child", child.itemId().toStdString());
4565+ EXPECT_EQ("child", child.name().toStdString());
4566+ ASSERT_EQ(1, child.parentIds().size());
4567+ EXPECT_EQ(ROOT_DIR(), child.parentIds().at(0).toStdString());
4568+ EXPECT_EQ("", child.etag());
4569+ EXPECT_EQ(Item::Type::Folder, child.type());
4570+ EXPECT_EQ(5, child.metadata().size());
4571+
4572+ struct stat st;
4573+ ASSERT_EQ(0, stat(child.itemId().toStdString().c_str(), &st));
4574+ EXPECT_TRUE(S_ISDIR(st.st_mode));
4575+
4576+ // Again, to get coverage for a StorageException caught in invoke_async().
4577+ job.reset(root.createFolder("child"));
4578+ wait(job.get());
4579+ ASSERT_EQ(ItemJob::Error, job->status()) << job->error().errorString().toStdString();
4580+ EXPECT_EQ(string("Exists: create_folder(): \"") + ROOT_DIR() + "/child\" exists already",
4581+ job->error().errorString().toStdString());
4582+
4583+ // Again, without write permission on the root dir, to get coverage for a filesystem_error in invoke_async().
4584+ ASSERT_EQ(0, ::rmdir((ROOT_DIR() + "/child").c_str()));
4585+ ASSERT_EQ(0, ::chmod(ROOT_DIR().c_str(), 0555));
4586+ job.reset(root.createFolder("child"));
4587+ wait(job.get());
4588+ ::chmod(ROOT_DIR().c_str(), 0755);
4589+ ASSERT_EQ(ItemJob::Error, job->status()) << job->error().errorString().toStdString();
4590+ EXPECT_EQ(string("PermissionDenied: create_folder(): \"") + ROOT_DIR()
4591+ + "/child\": boost::filesystem::create_directory: Permission denied: \"" + ROOT_DIR() + "/child\"",
4592+ job->error().errorString().toStdString());
4593+ }
4594+}
4595+
4596+TEST_F(LocalProviderTest, delete_item)
4597+{
4598+ {
4599+ using namespace unity::storage::qt;
4600+
4601+ set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider));
4602+
4603+ auto root = get_root(acc_);
4604+ unique_ptr<ItemJob> job(root.createFolder("child"));
4605+ wait(job.get());
4606+ ASSERT_EQ(ItemJob::Finished, job->status()) << job->error().errorString().toStdString();
4607+
4608+ Item child = job->item();
4609+ unique_ptr<VoidJob> delete_job(child.deleteItem());
4610+ wait(delete_job.get());
4611+ ASSERT_EQ(ItemJob::Finished, delete_job->status()) << delete_job->error().errorString().toStdString();
4612+
4613+ struct stat st;
4614+ ASSERT_EQ(-1, stat(child.itemId().toStdString().c_str(), &st));
4615+ EXPECT_EQ(ENOENT, errno);
4616+ }
4617+}
4618+
4619+TEST_F(LocalProviderTest, delete_item_noperm)
4620+{
4621+ {
4622+ using namespace unity::storage::qt;
4623+
4624+ set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider));
4625+
4626+ auto root = get_root(acc_);
4627+ unique_ptr<ItemJob> job(root.createFolder("child"));
4628+ wait(job.get());
4629+ ASSERT_EQ(ItemJob::Finished, job->status()) << job->error().errorString().toStdString();
4630+ }
4631+}
4632+
4633+TEST_F(LocalProviderTest, delete_root)
4634+{
4635+ // Client-side API does not allow us to try to delete the root, so we talk to the provider directly.
4636+ auto p = make_shared<LocalProvider>();
4637+
4638+ auto fut = p->delete_item(ROOT_DIR(), provider::Context());
4639+ try
4640+ {
4641+ fut.get();
4642+ FAIL();
4643+ }
4644+ catch (provider::LogicException const& e)
4645+ {
4646+ EXPECT_STREQ("LogicException: delete_item(): cannot delete root", e.what());
4647+ }
4648+}
4649+
4650+TEST_F(LocalProviderTest, metadata)
4651+{
4652+ // Client-side API does not call the Metadata DBus method (except as part of parents()),
4653+ // so we talk to the provider directly.
4654+ auto p = make_shared<LocalProvider>();
4655+
4656+ auto fut = p->metadata(ROOT_DIR(), {}, provider::Context());
4657+ auto item = fut.get();
4658+ EXPECT_EQ(5, item.metadata.size());
4659+
4660+ // Again, to get coverage for the "not file or folder" case in make_item().
4661+ ASSERT_EQ(0, mknod((ROOT_DIR() + "/pipe").c_str(), S_IFIFO | 06666, 0));
4662+ try
4663+ {
4664+ auto fut = p->metadata(ROOT_DIR() + "/pipe", {}, provider::Context());
4665+ fut.get();
4666+ FAIL();
4667+ }
4668+ catch (provider::NotExistsException const& e)
4669+ {
4670+ EXPECT_EQ(string("NotExistsException: metadata(): \"") + ROOT_DIR() + "/pipe\" is neither a file nor a folder",
4671+ e.what());
4672+ }
4673+}
4674+
4675+TEST_F(LocalProviderTest, lookup)
4676+{
4677+ using namespace unity::storage::qt;
4678+
4679+ set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider));
4680+
4681+ auto root = get_root(acc_);
4682+ {
4683+ unique_ptr<ItemJob> job(root.createFolder("child"));
4684+ wait(job.get());
4685+ ASSERT_EQ(ItemJob::Finished, job->status()) << job->error().errorString().toStdString();
4686+ }
4687+
4688+ unique_ptr<ItemListJob> job(root.lookup("child"));
4689+ auto items = get_items(job.get());
4690+ ASSERT_EQ(1, items.size());
4691+ auto child = items.at(0);
4692+
4693+ EXPECT_EQ(ROOT_DIR() + "/child", child.itemId().toStdString());
4694+ EXPECT_EQ("child", child.name().toStdString());
4695+ ASSERT_EQ(1, child.parentIds().size());
4696+ EXPECT_EQ(ROOT_DIR(), child.parentIds().at(0).toStdString());
4697+ EXPECT_EQ("", child.etag());
4698+ EXPECT_EQ(Item::Type::Folder, child.type());
4699+ EXPECT_EQ(5, child.metadata().size());
4700+
4701+ // Remove the child again and try the lookup once more.
4702+ ASSERT_EQ(0, rmdir((ROOT_DIR() + "/child").c_str()));
4703+
4704+ job.reset(root.lookup("child"));
4705+ wait(job.get());
4706+ EXPECT_EQ(ItemJob::Error, job->status());
4707+ EXPECT_EQ(string("NotExists: lookup(): \"") + ROOT_DIR() + "/child\": boost::filesystem::canonical: "
4708+ + "No such file or directory: \"" + ROOT_DIR() + "/child\"",
4709+ job->error().errorString().toStdString());
4710+}
4711+
4712+TEST_F(LocalProviderTest, list)
4713+{
4714+ using namespace unity::storage::qt;
4715+
4716+ set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider));
4717+
4718+ auto root = get_root(acc_);
4719+ {
4720+ unique_ptr<ItemJob> job(root.createFolder("child"));
4721+ wait(job.get());
4722+ ASSERT_EQ(ItemJob::Finished, job->status()) << job->error().errorString().toStdString();
4723+ }
4724+
4725+ // Make a weird item that will be ignored (for coverage).
4726+ ASSERT_EQ(0, mknod((ROOT_DIR() + "/pipe").c_str(), S_IFIFO | 06666, 0));
4727+
4728+ // Make a file that starts with the temp file prefix (for coverage).
4729+ int fd = creat((ROOT_DIR() + "/.storage-framework").c_str(), 0755);
4730+ ASSERT_GT(fd, 0);
4731+ close(fd);
4732+
4733+ unique_ptr<ItemListJob> job(root.list());
4734+ auto items = get_items(job.get());
4735+ ASSERT_EQ(1, items.size());
4736+ auto child = items.at(0);
4737+
4738+ EXPECT_EQ(ROOT_DIR() + "/child", child.itemId().toStdString());
4739+ EXPECT_EQ("child", child.name().toStdString());
4740+ ASSERT_EQ(1, child.parentIds().size());
4741+ EXPECT_EQ(ROOT_DIR(), child.parentIds().at(0).toStdString());
4742+ EXPECT_EQ("", child.etag());
4743+ EXPECT_EQ(Item::Type::Folder, child.type());
4744+ EXPECT_EQ(5, child.metadata().size());
4745+}
4746+
4747+void make_hierarchy(string const& root_dir)
4748+{
4749+ // Make a small tree so we have something to test with for move() and copy().
4750+ ASSERT_EQ(0, mkdir((root_dir + "/a").c_str(), 0755));
4751+ ASSERT_EQ(0, mkdir((root_dir + "/a/b").c_str(), 0755));
4752+ string cmd = string("echo hello >") + root_dir + "/hello";
4753+ ASSERT_EQ(0, system(cmd.c_str()));
4754+ cmd = string("echo text >") + root_dir + "/a/foo.txt";
4755+ ASSERT_EQ(0, system(cmd.c_str()));
4756+ ASSERT_EQ(0, mknod((root_dir + "/a/pipe").c_str(), S_IFIFO | 06666, 0));
4757+ ASSERT_EQ(0, mkdir((root_dir + "/a/.storage-framework-").c_str(), 0755));
4758+ ASSERT_EQ(0, mkdir((root_dir + "/a/b/.storage-framework-").c_str(), 0755));
4759+ ASSERT_EQ(0, mknod((root_dir + "/a/b/pipe").c_str(), S_IFIFO | 06666, 0));
4760+}
4761+
4762+TEST_F(LocalProviderTest, move)
4763+{
4764+ using namespace unity::storage::qt;
4765+
4766+ set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider));
4767+
4768+ auto start_time = nanosecs_now();
4769+
4770+ make_hierarchy(ROOT_DIR());
4771+
4772+ auto root = get_root(acc_);
4773+
4774+ qt::Item hello;
4775+ {
4776+ unique_ptr<ItemListJob> job(root.lookup("hello"));
4777+ auto items = get_items(job.get());
4778+ ASSERT_EQ(ItemListJob::Finished, job->status()) << job->error().errorString().toStdString();
4779+ ASSERT_EQ(1, items.size());
4780+ hello = items.at(0);
4781+ }
4782+
4783+ struct stat st;
4784+ ASSERT_EQ(0, stat(hello.itemId().toStdString().c_str(), &st));
4785+ auto old_ino = st.st_ino;
4786+
4787+ // Check metadata.
4788+ EXPECT_EQ("hello", hello.name());
4789+ ASSERT_EQ(1, hello.parentIds().size());
4790+ EXPECT_EQ(ROOT_DIR(), hello.parentIds().at(0).toStdString());
4791+ EXPECT_EQ(Item::Type::File, hello.type());
4792+
4793+ ASSERT_EQ(6, hello.metadata().size());
4794+ auto free_space_bytes = hello.metadata().value("free_space_bytes").toLongLong();
4795+ cout << "free_space_bytes: " << free_space_bytes << endl;
4796+ EXPECT_GT(free_space_bytes, 0);
4797+ auto used_space_bytes = hello.metadata().value("used_space_bytes").toLongLong();
4798+ cout << "used_space_bytes: " << used_space_bytes << endl;
4799+ EXPECT_GT(used_space_bytes, 0);
4800+ auto content_type = hello.metadata().value("content_type").toString();
4801+ EXPECT_EQ("application/octet-stream", content_type);
4802+ auto writable = hello.metadata().value("writable").toBool();
4803+ EXPECT_TRUE(writable);
4804+ auto size = hello.metadata().value("size_in_bytes").toLongLong();
4805+ EXPECT_EQ(6, size);
4806+
4807+ // yyyy-mm-ddThh:mm:ssZ
4808+ 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$";
4809+ string date_time = hello.metadata().value("last_modified_time").toString().toStdString();
4810+ cout << "last_modified_time: " << date_time << endl;
4811+ regex re(date_time_fmt);
4812+ EXPECT_TRUE(regex_match(date_time, re));
4813+
4814+ // Check that the file was modified in the last two seconds.
4815+ // Because the system clock can tick a lot more frequently than the file system time stamp,
4816+ // we allow the mtime to be up to one second *earlier* than the time we started the operation.
4817+ string mtime_str = hello.etag().toStdString();
4818+ char* end;
4819+ int64_t mtime = strtoll(mtime_str.c_str(), &end, 10);
4820+ EXPECT_LE(start_time - 1000000000, mtime);
4821+ EXPECT_LT(mtime, start_time + 2000000000);
4822+
4823+ // Move hello -> world
4824+ qt::Item world;
4825+ {
4826+ unique_ptr<ItemJob> job(hello.move(root, "world"));
4827+ wait(job.get());
4828+ ASSERT_EQ(ItemJob::Finished, job->status()) << job->error().errorString().toStdString();
4829+ world = job->item();
4830+ }
4831+ EXPECT_FALSE(boost::filesystem::exists(hello.itemId().toStdString()));
4832+ EXPECT_EQ(ROOT_DIR() + "/world", world.itemId().toStdString());
4833+
4834+ ASSERT_EQ(0, stat(world.itemId().toStdString().c_str(), &st));
4835+ auto new_ino = st.st_ino;
4836+ EXPECT_EQ(old_ino, new_ino);
4837+
4838+ // For coverage: try moving world -> a (which must fail)
4839+ unique_ptr<ItemJob> job(world.move(root, "a"));
4840+ wait(job.get());
4841+ ASSERT_EQ(ItemJob::Error, job->status()) << job->error().errorString().toStdString();
4842+ EXPECT_EQ(string("Exists: move(): \"") + ROOT_DIR() + "/a\" exists already",
4843+ job->error().errorString().toStdString());
4844+}
4845+
4846+TEST_F(LocalProviderTest, copy_file)
4847+{
4848+ using namespace unity::storage::qt;
4849+
4850+ set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider));
4851+
4852+ make_hierarchy(ROOT_DIR());
4853+
4854+ auto root = get_root(acc_);
4855+
4856+ // Copy hello -> world
4857+ qt::Item hello;
4858+ {
4859+ unique_ptr<ItemListJob> job(root.lookup("hello"));
4860+ auto items = get_items(job.get());
4861+ ASSERT_EQ(ItemListJob::Finished, job->status()) << job->error().errorString().toStdString();
4862+ ASSERT_EQ(1, items.size());
4863+ hello = items.at(0);
4864+ }
4865+
4866+ struct stat st;
4867+ ASSERT_EQ(0, stat(hello.itemId().toStdString().c_str(), &st));
4868+ auto old_ino = st.st_ino;
4869+
4870+ qt::Item world;
4871+ {
4872+ unique_ptr<ItemJob> job(hello.copy(root, "world"));
4873+ wait(job.get());
4874+ ASSERT_EQ(ItemJob::Finished, job->status()) << job->error().errorString().toStdString();
4875+ world = job->item();
4876+ }
4877+ EXPECT_TRUE(boost::filesystem::exists(hello.itemId().toStdString()));
4878+ EXPECT_EQ(ROOT_DIR() + "/world", world.itemId().toStdString());
4879+
4880+ ASSERT_EQ(0, stat(world.itemId().toStdString().c_str(), &st));
4881+ auto new_ino = st.st_ino;
4882+ EXPECT_NE(old_ino, new_ino);
4883+}
4884+
4885+TEST_F(LocalProviderTest, copy_tree)
4886+{
4887+ using namespace unity::storage::qt;
4888+ using namespace boost::filesystem;
4889+
4890+ set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider));
4891+
4892+ make_hierarchy(ROOT_DIR());
4893+
4894+ auto root = get_root(acc_);
4895+
4896+ // Copy a -> c
4897+ qt::Item a;
4898+ {
4899+ unique_ptr<ItemListJob> job(root.lookup("a"));
4900+ auto items = get_items(job.get());
4901+ ASSERT_EQ(ItemListJob::Finished, job->status()) << job->error().errorString().toStdString();
4902+ ASSERT_EQ(1, items.size());
4903+ a = items.at(0);
4904+ }
4905+
4906+ qt::Item c;
4907+ {
4908+ unique_ptr<ItemJob> job(a.copy(root, "c"));
4909+ wait(job.get());
4910+ ASSERT_EQ(ItemJob::Finished, job->status()) << job->error().errorString().toStdString();
4911+ c = job->item();
4912+ }
4913+ EXPECT_TRUE(exists(c.itemId().toStdString()));
4914+
4915+ // Check that we only copied regular files and directories, but not a pipe or anything starting with
4916+ // the temp file prefix.
4917+ EXPECT_TRUE(exists(ROOT_DIR() + "/c/b"));
4918+ EXPECT_TRUE(exists(ROOT_DIR() + "/c/foo.txt"));
4919+ EXPECT_FALSE(exists(ROOT_DIR() + "/c/pipe"));
4920+ EXPECT_FALSE(exists(ROOT_DIR() + "/c/storage-framework-"));
4921+ EXPECT_FALSE(exists(ROOT_DIR() + "/c/b/pipe"));
4922+ EXPECT_FALSE(exists(ROOT_DIR() + "/c/b/storage-framework-"));
4923+
4924+ // Copy c -> a. This must fail because a exists.
4925+ {
4926+ unique_ptr<ItemJob> job(c.copy(root, "a"));
4927+ wait(job.get());
4928+ ASSERT_EQ(ItemJob::Error, job->status()) << job->error().errorString().toStdString();
4929+ EXPECT_EQ(string("Exists: copy(): \"") + ROOT_DIR() + "/a\" exists already",
4930+ job->error().errorString().toStdString());
4931+ }
4932+}
4933+
4934+TEST_F(LocalProviderTest, download)
4935+{
4936+ using namespace unity::storage::qt;
4937+
4938+ set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider));
4939+
4940+ int const segments = 10000;
4941+ string large_contents;
4942+ large_contents.reserve(file_contents.size() * segments);
4943+ for (int i = 0; i < segments; i++)
4944+ {
4945+ large_contents += file_contents;
4946+ }
4947+ string const full_path = ROOT_DIR() + "/foo.txt";
4948+ {
4949+ int fd = open(full_path.c_str(), O_WRONLY | O_CREAT | O_EXCL, 0644);
4950+ ASSERT_GT(fd, 0);
4951+ ASSERT_EQ(ssize_t(large_contents.size()), write(fd, &large_contents[0], large_contents.size())) << strerror(errno);
4952+ ASSERT_EQ(0, close(fd));
4953+ }
4954+
4955+ unique_ptr<ItemJob> job(acc_.get(QString::fromStdString(full_path)));
4956+ wait(job.get());
4957+ EXPECT_TRUE(job->isValid());
4958+
4959+ auto file = job->item();
4960+ unique_ptr<Downloader> downloader(file.createDownloader(Item::ErrorIfConflict));
4961+
4962+ int64_t n_read = 0;
4963+ QObject::connect(downloader.get(), &QIODevice::readyRead,
4964+ [&]() {
4965+ auto bytes = downloader->readAll();
4966+ string const expected = large_contents.substr(n_read, bytes.size());
4967+ EXPECT_EQ(expected, bytes.toStdString());
4968+ n_read += bytes.size();
4969+ });
4970+ QSignalSpy read_finished_spy(downloader.get(), &QIODevice::readChannelFinished);
4971+ ASSERT_TRUE(read_finished_spy.wait(SIGNAL_WAIT_TIME));
4972+
4973+ QSignalSpy status_spy(downloader.get(), &Downloader::statusChanged);
4974+ downloader->close();
4975+ while (downloader->status() == Downloader::Ready)
4976+ {
4977+ ASSERT_TRUE(status_spy.wait(SIGNAL_WAIT_TIME));
4978+ }
4979+ ASSERT_EQ(Downloader::Finished, downloader->status()) << downloader->error().errorString().toStdString();
4980+
4981+ EXPECT_EQ(int64_t(large_contents.size()), n_read);
4982+}
4983+
4984+TEST_F(LocalProviderTest, download_short_read)
4985+{
4986+ using namespace unity::storage::qt;
4987+
4988+ set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider));
4989+
4990+ int const segments = 10000;
4991+ string const full_path = ROOT_DIR() + "/foo.txt";
4992+ {
4993+ int fd = open(full_path.c_str(), O_WRONLY | O_CREAT | O_EXCL, 0644);
4994+ ASSERT_GT(fd, 0);
4995+ for (int i = 0; i < segments; i++)
4996+ {
4997+ ASSERT_EQ(ssize_t(file_contents.size()), write(fd, &file_contents[0], file_contents.size())) << strerror(errno);
4998+ }
4999+ ASSERT_EQ(0, close(fd));
5000+ }
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: