Merge lp:~michihenning/storage-framework/merge-devel into lp:storage-framework
- merge-devel
- Merge into trunk
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 |
Related bugs: |
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.
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(®istry_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.
Looks good.