Merge lp:~michihenning/storage-framework/local-provider into lp:storage-framework/devel
- local-provider
- Merge into devel
Status: | Merged |
---|---|
Approved by: | James Henstridge |
Approved revision: | 116 |
Merged at revision: | 113 |
Proposed branch: | lp:~michihenning/storage-framework/local-provider |
Merge into: | lp:storage-framework/devel |
Diff against target: |
3626 lines (+3397/-9) 23 files modified
CMakeLists.txt (+4/-1) debian/control.in (+2/-1) debian/storage-framework-registry.install (+2/-0) include/unity/storage/internal/gobj_memory.h (+212/-0) include/unity/storage/provider/Exceptions.h (+1/-1) src/CMakeLists.txt (+1/-0) 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 (+338/-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/qt/internal/RuntimeImpl.cpp (+4/-0) src/qt/internal/StorageErrorImpl.cpp (+0/-6) tests/CMakeLists.txt (+1/-0) tests/local-provider/CMakeLists.txt (+18/-0) tests/local-provider/local-provider_test.cpp (+1473/-0) tests/utils/env_var_guard.h (+70/-0) |
To merge this branch: | bzr merge lp:~michihenning/storage-framework/local-provider |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
James Henstridge | Approve | ||
unity-api-1-bot | continuous-integration | Approve | |
Review via email:
|
Commit message
Added proper local provider.
Description of the change
Added proper local provider. Still need to update the registry to return an account for the provider, but will do that in a separate MR.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
unity-api-1-bot (unity-api-1-bot) wrote : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
unity-api-1-bot (unity-api-1-bot) wrote : | # |
FAILED: Continuous integration, rev:110
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
Click here to trigger a rebuild:
https:/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
James Henstridge (jamesh) wrote : | # |
Attached is a partial review covering just the provider code. I haven't looked at any of the test changes yet.
- 111. By Michi Henning
-
Review comments from James.
- 112. By Michi Henning
-
Fix debian install file.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Michi Henning (michihenning) wrote : | # |
Thanks for the review! I've responded inline.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
unity-api-1-bot (unity-api-1-bot) wrote : | # |
PASSED: Continuous integration, rev:112
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild:
https:/
- 113. By Michi Henning
-
Using gobj_ptr now.
- 114. By Michi Henning
-
Pay attention to SNAP_USER_DATA, plus other minor changes discussed with James.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
unity-api-1-bot (unity-api-1-bot) wrote : | # |
PASSED: Continuous integration, rev:114
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild:
https:/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
James Henstridge (jamesh) wrote : | # |
Can't easily leave inline comments for a diff this big, so I'll just note them here:
[1]
storage-
Perhaps the storage-
[2]
I don't see a D-Bus service activation file for the local provider. It should be included in the same binary package as the local provider.
[3]
This is a bit of an annoying one since it will be a lot of search and replace, but is STORAGE_
Looking at some of the other environment variables we've got, perhaps SF_LOCAL_
[4]
This can probably be put off post-merge: we should probably have the local-provider tests use a unique temporary directory for each run to ensure that it can't interfere with other tests.
This can wait though, since what you've got is fine for a single serial run of the tests.
[5]
I don't see anything in the storage-
- 115. By Michi Henning
-
Renamed STORAGE_
FRAMEWORK_ ROOT to SF_LOCAL_ PROVIDER_ ROOT.
Added .service file and fixed debian packaging. - 116. By Michi Henning
-
Updated debian Standards-Version.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Michi Henning (michihenning) wrote : | # |
1: Fixed, thanks!
2: Blush... I plain forgot :-(
3: Done, wasn't a big deal, and I agree that this is a better name.
4: The test fixture removes the tree at the start of each test run.
5: I was going to do that in a separate MR.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
unity-api-1-bot (unity-api-1-bot) wrote : | # |
PASSED: Continuous integration, rev:116
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild:
https:/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
James Henstridge (jamesh) wrote : | # |
Looks good.
My point about the temporary directory is that it adds a dependency on a scarce resource when we don't need to. If I ran this test suite twice in parallel, it would fail because one instance would be deleting files the other was expecting to find. The same would happen if we had a test runner that could invoke tests in parallel.
It doesn't have much practical effect right now because we are running tests serially, but I like to avoid these kinds of dependencies when possible.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Michi Henning (michihenning) wrote : | # |
Fair enough. I'll push a separate MR for it.
Preview Diff
1 | === modified file 'CMakeLists.txt' |
2 | --- CMakeLists.txt 2017-02-24 03:01:57 +0000 |
3 | +++ CMakeLists.txt 2017-03-17 05:04:57 +0000 |
4 | @@ -43,7 +43,7 @@ |
5 | include_directories(include) |
6 | include_directories(${CMAKE_BINARY_DIR}/include) # For generated headers |
7 | |
8 | -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC --std=c++11 -Wall -pedantic -Wextra -fvisibility=hidden") |
9 | +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC --std=c++14 -Wall -pedantic -Wextra -fvisibility=hidden") |
10 | |
11 | # Some additional warnings not included by the general flags set above. |
12 | set(EXTRA_C_WARNINGS "-Wcast-align -Wcast-qual -Wformat -Wredundant-decls -Wswitch-default") |
13 | @@ -123,6 +123,7 @@ |
14 | |
15 | include(FindPkgConfig) |
16 | pkg_check_modules(APPARMOR_DEPS REQUIRED libapparmor) |
17 | +pkg_check_modules(GIO_DEPS REQUIRED gio-2.0 gio-unix-2.0) |
18 | pkg_check_modules(GLIB_DEPS REQUIRED glib-2.0) |
19 | pkg_check_modules(ONLINEACCOUNTS_DEPS REQUIRED OnlineAccountsQt) |
20 | |
21 | @@ -137,6 +138,7 @@ |
22 | |
23 | enable_coverage_report( |
24 | TARGETS |
25 | + local-provider-lib |
26 | qt-client-lib-common |
27 | storage-framework-common-internal |
28 | storage-framework-qt-client |
29 | @@ -145,6 +147,7 @@ |
30 | sf-provider-objects |
31 | storage-framework-provider |
32 | storage-framework-registry |
33 | + storage-provider-local |
34 | FILTER |
35 | ${CMAKE_SOURCE_DIR}/tests/* |
36 | ${CMAKE_BINARY_DIR}/* |
37 | |
38 | === modified file 'debian/control.in' |
39 | --- debian/control.in 2016-12-12 02:18:21 +0000 |
40 | +++ debian/control.in 2017-03-17 05:04:57 +0000 |
41 | @@ -2,7 +2,7 @@ |
42 | Section: libs |
43 | Priority: optional |
44 | Maintainer: Ubuntu Core Developers <ubuntu-devel-discuss@lists.ubuntu.com> |
45 | -Standards-Version: 3.9.6 |
46 | +Standards-Version: 3.9.7 |
47 | Build-Depends: cmake, |
48 | cmake-extras (>= 0.10), |
49 | debhelper (>= 9), |
50 | @@ -58,6 +58,7 @@ |
51 | online-accounts-daemon, |
52 | Description: Registry for the Storage Framework |
53 | DBus service that provides access to provider account information. |
54 | + Includes a local storage provider. |
55 | |
56 | Package: libstorage-framework-qt-client-2-0 |
57 | Architecture: any |
58 | |
59 | === modified file 'debian/storage-framework-registry.install' |
60 | --- debian/storage-framework-registry.install 2016-11-21 04:37:37 +0000 |
61 | +++ debian/storage-framework-registry.install 2017-03-17 05:04:57 +0000 |
62 | @@ -1,2 +1,4 @@ |
63 | usr/lib/*/*/storage-framework-registry |
64 | usr/share/dbus-1/services/com.canonical.StorageFramework.Registry.service |
65 | +usr/lib/*/*/storage-provider-local |
66 | +usr/share/dbus-1/services/com.canonical.StorageFramework.Provider.Local.service |
67 | |
68 | === added file 'include/unity/storage/internal/gobj_memory.h' |
69 | --- include/unity/storage/internal/gobj_memory.h 1970-01-01 00:00:00 +0000 |
70 | +++ include/unity/storage/internal/gobj_memory.h 2017-03-17 05:04:57 +0000 |
71 | @@ -0,0 +1,212 @@ |
72 | +/* |
73 | + * Copyright (C) 2013 Canonical Ltd. |
74 | + * |
75 | + * This program is free software: you can redistribute it and/or modify |
76 | + * it under the terms of the GNU Lesser General Public License version 3 as |
77 | + * published by the Free Software Foundation. |
78 | + * |
79 | + * This program is distributed in the hope that it will be useful, |
80 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
81 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
82 | + * GNU Lesser General Public License for more details. |
83 | + * |
84 | + * You should have received a copy of the GNU Lesser General Public License |
85 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
86 | + * |
87 | + * Authored by: Jussi Pakkanen <jussi.pakkanen@canonical.com> |
88 | + */ |
89 | + |
90 | +#pragma once |
91 | + |
92 | +#include <stdexcept> |
93 | + |
94 | +#pragma GCC diagnostic push |
95 | +#pragma GCC diagnostic ignored "-Wold-style-cast" |
96 | +#pragma GCC diagnostic ignored "-Wcast-qual" |
97 | +#include <glib-object.h> |
98 | + |
99 | +namespace unity |
100 | +{ |
101 | + |
102 | +namespace storage |
103 | +{ |
104 | + |
105 | +namespace internal |
106 | +{ |
107 | + |
108 | +/** |
109 | + * This class is meant for automatically managing the lifetime of C objects derived |
110 | + * from gobject. Its API perfectly mirrors the API of unique_ptr except that you |
111 | + * can't define your own deleter function as it is always g_object_unref. |
112 | + * |
113 | + * API/ABI stability is not guaranteed. If you need to pass the object across an ABI |
114 | + * boundary, pass the plain gobject. |
115 | + * |
116 | + * This is how you would use gobj_ptr 99% of the time: |
117 | + * |
118 | + * gobj_ptr<GSomeType> o(g_some_type_new(...)); |
119 | + * |
120 | + * More specifically, the object will decrement the gobject reference count |
121 | + * of the object it points to when it goes out of scope. It will never increment it. |
122 | + * Thus you should only assign to it when already holding a reference. gobj_ptr |
123 | + * will then take ownership of that particular reference. |
124 | + * |
125 | + * Floating gobjects can not be put in this container as they are meant to be put |
126 | + * into native gobject aware containers immediately upon construction. Trying to insert |
127 | + * a floating gobject into a gobj_ptr will throw an invalid_argument exception. To |
128 | + * prevent accidental memory leaks, the floating gobject is unreffed in this case. |
129 | + */ |
130 | +template <typename T> |
131 | +class gobj_ptr final |
132 | +{ |
133 | +private: |
134 | + T* u; |
135 | + |
136 | + void validate_float(T* t) |
137 | + { |
138 | + if (t != nullptr && g_object_is_floating(G_OBJECT(t))) |
139 | + { |
140 | + // LCOV_EXCL_START // False negative from gcovr. |
141 | + throw std::invalid_argument("Tried to add a floating gobject into a gobj_ptr."); |
142 | + // LCOV_EXCL_STOP |
143 | + } |
144 | + } |
145 | + |
146 | +public: |
147 | + typedef T element_type; |
148 | + typedef T* pointer; |
149 | + typedef decltype(g_object_unref) deleter_type; |
150 | + |
151 | + constexpr gobj_ptr() noexcept : u(nullptr) |
152 | + { |
153 | + } |
154 | + explicit gobj_ptr(T* t) |
155 | + : u(t) |
156 | + { |
157 | + // What should we do if validate throws? Unreffing unknown objs |
158 | + // is dodgy but not unreffing runs the risk of |
159 | + // memory leaks. Currently unrefs as u is destroyed |
160 | + // when this exception is thrown. |
161 | + validate_float(t); |
162 | + } |
163 | + constexpr gobj_ptr(std::nullptr_t) noexcept : u(nullptr){}; |
164 | + gobj_ptr(gobj_ptr&& o) noexcept |
165 | + { |
166 | + u = o.u; |
167 | + o.u = nullptr; |
168 | + } |
169 | + gobj_ptr(const gobj_ptr& o) |
170 | + : u(nullptr) |
171 | + { |
172 | + *this = o; |
173 | + } |
174 | + gobj_ptr& operator=(const gobj_ptr& o) |
175 | + { |
176 | + if (o.u != nullptr) |
177 | + { |
178 | + g_object_ref(o.u); |
179 | + } |
180 | + reset(o.u); |
181 | + return *this; |
182 | + } |
183 | + ~gobj_ptr() |
184 | + { |
185 | + reset(); |
186 | + } |
187 | + |
188 | + deleter_type& get_deleter() noexcept |
189 | + { |
190 | + return g_object_unref; |
191 | + } |
192 | + deleter_type& get_deleter() const noexcept |
193 | + { |
194 | + return g_object_unref; |
195 | + } |
196 | + |
197 | + void swap(gobj_ptr<T>& o) noexcept |
198 | + { |
199 | + T* tmp = u; |
200 | + u = o.u; |
201 | + o.u = tmp; |
202 | + } |
203 | + void reset(pointer p = pointer()) |
204 | + { |
205 | + if (u != nullptr) |
206 | + { |
207 | + g_object_unref(G_OBJECT(u)); |
208 | + u = nullptr; |
209 | + } |
210 | + // Same throw dilemma as in pointer constructor. |
211 | + u = p; |
212 | + validate_float(p); |
213 | + } |
214 | + |
215 | + T* release() noexcept |
216 | + { |
217 | + T* r = u; |
218 | + u = nullptr; |
219 | + return r; |
220 | + } |
221 | + T* get() const noexcept |
222 | + { |
223 | + return u; |
224 | + } |
225 | + |
226 | + T& operator*() const |
227 | + { |
228 | + return *u; |
229 | + } |
230 | + T* operator->() const noexcept |
231 | + { |
232 | + return u; |
233 | + } |
234 | + explicit operator bool() const noexcept |
235 | + { |
236 | + return u != nullptr; |
237 | + } |
238 | + |
239 | + gobj_ptr& operator=(gobj_ptr&& o) noexcept |
240 | + { |
241 | + reset(); |
242 | + u = o.u; |
243 | + o.u = nullptr; |
244 | + return *this; |
245 | + } |
246 | + gobj_ptr& operator=(std::nullptr_t) noexcept |
247 | + { |
248 | + reset(); |
249 | + return *this; |
250 | + } |
251 | + bool operator==(const gobj_ptr<T>& o) const noexcept |
252 | + { |
253 | + return u == o.u; |
254 | + } |
255 | + bool operator!=(const gobj_ptr<T>& o) const noexcept |
256 | + { |
257 | + return u != o.u; |
258 | + } |
259 | + bool operator<(const gobj_ptr<T>& o) const noexcept |
260 | + { |
261 | + return u < o.u; |
262 | + } |
263 | + bool operator<=(const gobj_ptr<T>& o) const noexcept |
264 | + { |
265 | + return u <= o.u; |
266 | + } |
267 | + bool operator>(const gobj_ptr<T>& o) const noexcept |
268 | + { |
269 | + return u > o.u; |
270 | + } |
271 | + bool operator>=(const gobj_ptr<T>& o) const noexcept |
272 | + { |
273 | + return u >= o.u; |
274 | + } |
275 | +}; |
276 | + |
277 | +} // namespace internal |
278 | + |
279 | +} // namespace storage |
280 | + |
281 | +} // namespace unity |
282 | + |
283 | +#pragma GCC diagnostic pop |
284 | |
285 | === modified file 'include/unity/storage/provider/Exceptions.h' |
286 | --- include/unity/storage/provider/Exceptions.h 2017-01-27 09:28:03 +0000 |
287 | +++ include/unity/storage/provider/Exceptions.h 2017-03-17 05:04:57 +0000 |
288 | @@ -95,7 +95,7 @@ |
289 | }; |
290 | |
291 | /** |
292 | -\brief Indicates that an upload detected a version mismatch. |
293 | +\brief Indicates that an upload or download detected a version mismatch. |
294 | */ |
295 | class UNITY_STORAGE_EXPORT ConflictException : public StorageException |
296 | { |
297 | |
298 | === modified file 'src/CMakeLists.txt' |
299 | --- src/CMakeLists.txt 2016-11-07 04:49:34 +0000 |
300 | +++ src/CMakeLists.txt 2017-03-17 05:04:57 +0000 |
301 | @@ -2,3 +2,4 @@ |
302 | add_subdirectory(provider) |
303 | add_subdirectory(qt) |
304 | add_subdirectory(registry) |
305 | +add_subdirectory(local-provider) |
306 | |
307 | === added directory 'src/local-provider' |
308 | === added file 'src/local-provider/CMakeLists.txt' |
309 | --- src/local-provider/CMakeLists.txt 1970-01-01 00:00:00 +0000 |
310 | +++ src/local-provider/CMakeLists.txt 2017-03-17 05:04:57 +0000 |
311 | @@ -0,0 +1,43 @@ |
312 | +add_definitions(-DBOOST_THREAD_VERSION=4) |
313 | + |
314 | +add_library(local-provider-lib STATIC |
315 | + LocalDownloadJob.cpp |
316 | + LocalProvider.cpp |
317 | + LocalUploadJob.cpp |
318 | + utils.cpp |
319 | +) |
320 | + |
321 | +target_include_directories(local-provider-lib PRIVATE |
322 | + ${Qt5Network_INCLUDE_DIRS} |
323 | + ${GLIB_DEPS_INCLUDE_DIRS} |
324 | + ${GIO_DEPS_INCLUDE_DIRS} |
325 | +) |
326 | + |
327 | +set_target_properties(local-provider-lib PROPERTIES |
328 | + AUTOMOC TRUE |
329 | + POSITION_INDEPENDENT_CODE TRUE |
330 | +) |
331 | + |
332 | +add_executable(storage-provider-local |
333 | + main.cpp |
334 | +) |
335 | + |
336 | +target_link_libraries(storage-provider-local |
337 | + local-provider-lib |
338 | + storage-framework-provider |
339 | + Qt5::Network |
340 | + ${GLIB_DEPS_LIBRARIES} |
341 | + ${GIO_DEPS_LIBRARIES} |
342 | +) |
343 | + |
344 | +install( |
345 | + TARGETS storage-provider-local |
346 | + RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME} |
347 | +) |
348 | + |
349 | +configure_file(com.canonical.StorageFramework.Provider.Local.service.in com.canonical.StorageFramework.Provider.Local.service) |
350 | + |
351 | +install( |
352 | + FILES ${CMAKE_CURRENT_BINARY_DIR}/com.canonical.StorageFramework.Provider.Local.service |
353 | + DESTINATION ${CMAKE_INSTALL_DATADIR}/dbus-1/services |
354 | +) |
355 | |
356 | === added file 'src/local-provider/LocalDownloadJob.cpp' |
357 | --- src/local-provider/LocalDownloadJob.cpp 1970-01-01 00:00:00 +0000 |
358 | +++ src/local-provider/LocalDownloadJob.cpp 2017-03-17 05:04:57 +0000 |
359 | @@ -0,0 +1,179 @@ |
360 | +/* |
361 | + * Copyright (C) 2017 Canonical Ltd |
362 | + * |
363 | + * This program is free software: you can redistribute it and/or modify |
364 | + * it under the terms of the GNU Lesser General Public License version 3 as |
365 | + * published by the Free Software Foundation. |
366 | + * |
367 | + * This program is distributed in the hope that it will be useful, |
368 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
369 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
370 | + * GNU Lesser General Public License for more details. |
371 | + * |
372 | + * You should have received a copy of the GNU Lesser General Public License |
373 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
374 | + * |
375 | + * Authors: Michi Henning <michi.henning@canonical.com> |
376 | + */ |
377 | + |
378 | +#include "LocalDownloadJob.h" |
379 | + |
380 | +#include "LocalProvider.h" |
381 | +#include "utils.h" |
382 | +#include <unity/storage/internal/safe_strerror.h> |
383 | +#include <unity/storage/provider/Exceptions.h> |
384 | + |
385 | +using namespace unity::storage::provider; |
386 | +using namespace std; |
387 | + |
388 | +static int next_download_id = 0; |
389 | + |
390 | +string const method = "download()"; |
391 | + |
392 | +LocalDownloadJob::LocalDownloadJob(shared_ptr<LocalProvider> const& provider, |
393 | + string const& item_id, |
394 | + string const& match_etag) |
395 | + : DownloadJob(to_string(++next_download_id)) |
396 | + , provider_(provider) |
397 | + , item_id_(item_id) |
398 | +{ |
399 | + using namespace boost::filesystem; |
400 | + |
401 | + // Sanitize parameters. |
402 | + provider_->throw_if_not_valid(method, item_id_); |
403 | + try |
404 | + { |
405 | + auto st = status(item_id_); |
406 | + if (!is_regular_file(st)) |
407 | + { |
408 | + throw InvalidArgumentException(method + ": \"" + item_id_ + "\" is not a file"); |
409 | + } |
410 | + } |
411 | + // LCOV_EXCL_START // Too small a window to hit with a test. |
412 | + catch (filesystem_error const& e) |
413 | + { |
414 | + throw_storage_exception(method, e); |
415 | + } |
416 | + // LCOV_EXCL_STOP |
417 | + if (!match_etag.empty()) |
418 | + { |
419 | + int64_t mtime = get_mtime_nsecs(method, item_id_); |
420 | + if (to_string(mtime) != match_etag) |
421 | + { |
422 | + throw ConflictException(method + ": etag mismatch"); |
423 | + } |
424 | + } |
425 | + |
426 | + // Make input file ready. |
427 | + QString filename = QString::fromStdString(item_id); |
428 | + file_.reset(new QFile(filename)); |
429 | + if (!file_->open(QIODevice::ReadOnly)) |
430 | + { |
431 | + throw_storage_exception(method, |
432 | + ": cannot open \"" + item_id + "\": " + file_->errorString().toStdString(), |
433 | + file_->error()); |
434 | + } |
435 | + bytes_to_write_ = file_->size(); |
436 | + |
437 | + // Make write socket ready. |
438 | + int dup_fd = dup(write_socket()); |
439 | + if (dup_fd == -1) |
440 | + { |
441 | + // LCOV_EXCL_START |
442 | + string msg = "LocalDownloadJob(): dup() failed: " + unity::storage::internal::safe_strerror(errno); |
443 | + throw ResourceException(msg, errno); |
444 | + // LCOV_EXCL_STOP |
445 | + } |
446 | + write_socket_.setSocketDescriptor(dup_fd, QLocalSocket::ConnectedState, QIODevice::WriteOnly); |
447 | + connect(&write_socket_, &QIODevice::bytesWritten, this, &LocalDownloadJob::on_bytes_written); |
448 | + |
449 | + // Kick off the read-write cycle. |
450 | + QMetaObject::invokeMethod(this, "read_and_write_chunk", Qt::QueuedConnection); |
451 | +} |
452 | + |
453 | +LocalDownloadJob::~LocalDownloadJob() = default; |
454 | + |
455 | +boost::future<void> LocalDownloadJob::cancel() |
456 | +{ |
457 | + disconnect(&write_socket_, nullptr, this, nullptr); |
458 | + write_socket_.abort(); |
459 | + file_->close(); |
460 | + return boost::make_ready_future(); |
461 | +} |
462 | + |
463 | +boost::future<void> LocalDownloadJob::finish() |
464 | +{ |
465 | + if (bytes_to_write_ > 0) |
466 | + { |
467 | + auto file_size = file_->size(); |
468 | + auto written = file_size - bytes_to_write_; |
469 | + string msg = "finish() method called too early, file \"" + item_id_ + "\" has size " |
470 | + + to_string(file_size) + " but only " + to_string(written) + " bytes were consumed"; |
471 | + cancel(); |
472 | + return boost::make_exceptional_future<void>(LogicException(msg)); |
473 | + } |
474 | + // LCOV_EXCL_START |
475 | + // Not reachable because we call report_complete() in read_and_write_chunk(). |
476 | + return boost::make_ready_future(); |
477 | + // LCOV_EXCL_STOP |
478 | +} |
479 | + |
480 | +void LocalDownloadJob::on_bytes_written(qint64 bytes) |
481 | +{ |
482 | + bytes_to_write_ -= bytes; |
483 | + assert(bytes_to_write_ >= 0); |
484 | + read_and_write_chunk(); |
485 | +} |
486 | + |
487 | +void LocalDownloadJob::read_and_write_chunk() |
488 | +{ |
489 | + static qint64 constexpr READ_SIZE = 64 * 1024; |
490 | + |
491 | + if (bytes_to_write_ == 0) |
492 | + { |
493 | + file_->close(); |
494 | + write_socket_.close(); |
495 | + report_complete(); |
496 | + return; |
497 | + } |
498 | + |
499 | + QByteArray buf; |
500 | + buf.resize(READ_SIZE); |
501 | + auto bytes_read = file_->read(buf.data(), buf.size()); |
502 | + try |
503 | + { |
504 | + if (bytes_read == -1) |
505 | + { |
506 | + // LCOV_EXCL_START |
507 | + string msg = string("\"") + item_id_ + "\": read error: " + file_->errorString().toStdString(); |
508 | + throw_storage_exception(method, msg, file_->error()); |
509 | + // LCOV_EXCL_STOP |
510 | + } |
511 | + buf.resize(bytes_read); |
512 | + |
513 | + auto bytes_written = write_socket_.write(buf); |
514 | + if (bytes_written == -1) |
515 | + { |
516 | + // LCOV_EXCL_START |
517 | + string msg = string("\"") + item_id_ + "\": socket error: " + write_socket_.errorString().toStdString(); |
518 | + throw_storage_exception(method, msg, write_socket_.error()); |
519 | + // LCOV_EXCL_STOP |
520 | + } |
521 | + else if (bytes_written != bytes_read) |
522 | + { |
523 | + // LCOV_EXCL_START |
524 | + string msg = string("\"") + item_id_ + "\": socket write error, requested " + to_string(bytes_read) |
525 | + + " B, but wrote only " + to_string(bytes_written) + " B."; |
526 | + throw_storage_exception(method, msg, QLocalSocket::UnknownSocketError); |
527 | + // LCOV_EXCL_STOP |
528 | + } |
529 | + } |
530 | + // LCOV_EXCL_START |
531 | + catch (std::exception const&) |
532 | + { |
533 | + write_socket_.abort(); |
534 | + file_->close(); |
535 | + report_error(current_exception()); |
536 | + } |
537 | + // LCOV_EXCL_STOP |
538 | +} |
539 | |
540 | === added file 'src/local-provider/LocalDownloadJob.h' |
541 | --- src/local-provider/LocalDownloadJob.h 1970-01-01 00:00:00 +0000 |
542 | +++ src/local-provider/LocalDownloadJob.h 2017-03-17 05:04:57 +0000 |
543 | @@ -0,0 +1,54 @@ |
544 | +/* |
545 | + * Copyright (C) 2017 Canonical Ltd |
546 | + * |
547 | + * This program is free software: you can redistribute it and/or modify |
548 | + * it under the terms of the GNU Lesser General Public License version 3 as |
549 | + * published by the Free Software Foundation. |
550 | + * |
551 | + * This program is distributed in the hope that it will be useful, |
552 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
553 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
554 | + * GNU Lesser General Public License for more details. |
555 | + * |
556 | + * You should have received a copy of the GNU Lesser General Public License |
557 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
558 | + * |
559 | + * Authors: Michi Henning <michi.henning@canonical.com> |
560 | + */ |
561 | + |
562 | +#pragma once |
563 | + |
564 | +#include <unity/storage/provider/DownloadJob.h> |
565 | + |
566 | +#pragma GCC diagnostic push |
567 | +#pragma GCC diagnostic ignored "-Wctor-dtor-privacy" |
568 | +#pragma GCC diagnostic ignored "-Wswitch-default" |
569 | +#include <QFile> |
570 | +#include <QLocalSocket> |
571 | +#pragma GCC diagnostic pop |
572 | + |
573 | +class LocalProvider; |
574 | + |
575 | +class LocalDownloadJob : public QObject, public unity::storage::provider::DownloadJob |
576 | +{ |
577 | + Q_OBJECT |
578 | +public: |
579 | + LocalDownloadJob(std::shared_ptr<LocalProvider> const& provider, |
580 | + std::string const& item_id, |
581 | + std::string const& match_etag); |
582 | + virtual ~LocalDownloadJob(); |
583 | + |
584 | + virtual boost::future<void> cancel() override; |
585 | + virtual boost::future<void> finish() override; |
586 | + |
587 | +private Q_SLOTS: |
588 | + void on_bytes_written(qint64 bytes); |
589 | + void read_and_write_chunk(); |
590 | + |
591 | +private: |
592 | + std::shared_ptr<LocalProvider> const provider_; |
593 | + std::string const item_id_; |
594 | + std::unique_ptr<QFile> file_; |
595 | + QLocalSocket write_socket_; |
596 | + int64_t bytes_to_write_; |
597 | +}; |
598 | |
599 | === added file 'src/local-provider/LocalProvider.cpp' |
600 | --- src/local-provider/LocalProvider.cpp 1970-01-01 00:00:00 +0000 |
601 | +++ src/local-provider/LocalProvider.cpp 2017-03-17 05:04:57 +0000 |
602 | @@ -0,0 +1,586 @@ |
603 | +/* |
604 | + * Copyright (C) 2017 Canonical Ltd |
605 | + * |
606 | + * This program is free software: you can redistribute it and/or modify |
607 | + * it under the terms of the GNU Lesser General Public License version 3 as |
608 | + * published by the Free Software Foundation. |
609 | + * |
610 | + * This program is distributed in the hope that it will be useful, |
611 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
612 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
613 | + * GNU Lesser General Public License for more details. |
614 | + * |
615 | + * You should have received a copy of the GNU Lesser General Public License |
616 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
617 | + * |
618 | + * Authors: Michi Henning <michi.henning@canonical.com> |
619 | + */ |
620 | + |
621 | +#include "LocalProvider.h" |
622 | + |
623 | +#include "LocalDownloadJob.h" |
624 | +#include "LocalUploadJob.h" |
625 | +#include "utils.h" |
626 | + |
627 | +#include <unity/storage/internal/gobj_memory.h> |
628 | +#include <unity/storage/provider/Exceptions.h> |
629 | + |
630 | +#include <boost/algorithm/string.hpp> |
631 | + |
632 | +#pragma GCC diagnostic push |
633 | +#pragma GCC diagnostic ignored "-Wold-style-cast" |
634 | +#include <gio/gio.h> |
635 | +#include <glib.h> |
636 | +#pragma GCC diagnostic pop |
637 | + |
638 | +using namespace unity::storage::provider; |
639 | +using namespace std; |
640 | + |
641 | +namespace |
642 | +{ |
643 | + |
644 | +// Return the root directory where we store files. |
645 | +// If SF_LOCAL_PROVIDER_ROOT is set (used for testing), any files are created |
646 | +// directly under the root. E.g., if we do root.createFile("foo.txt", ...), the file |
647 | +// will be created as ${SF_LOCAL_PROVIDER_ROOT/foo.txt. SF_LOCAL_PROVIDER_ROOT must |
648 | +// be a pre-existing directory. |
649 | +// |
650 | +// Otherwise, the root is determined by SNAP_USER_COMMON or, if that is not set, |
651 | +// by XDG_DATA_HOME. Either way, files are created in a storage-framework/local |
652 | +// subdirectory. E.g., if SNAP_USER_COMMON or XDG_DATA_HOME is set to "/tmp" and |
653 | +// we do root.createFile("foo.txt", ...), the file will be created as |
654 | +// /tmp/storage-framework/local/foo.txt. If /tmp/storage-framework/local does not exist, |
655 | +// the directory will be created. |
656 | + |
657 | +string get_root_dir(string const& method) |
658 | +{ |
659 | + using namespace boost::filesystem; |
660 | + |
661 | + char const* dir = getenv("SF_LOCAL_PROVIDER_ROOT"); |
662 | + if (dir && *dir != '\0') |
663 | + { |
664 | + boost::system::error_code ec; |
665 | + if (!exists(dir, ec) || !is_directory(dir, ec)) |
666 | + { |
667 | + string msg = method + ": Environment variable SF_LOCAL_PROVIDER_ROOT must denote an existing directory"; |
668 | + throw InvalidArgumentException(msg); |
669 | + } |
670 | + return dir; |
671 | + } |
672 | + |
673 | + string data_dir; |
674 | + dir = getenv("SNAP_USER_COMMON"); |
675 | + if (dir && *dir != '\0') |
676 | + { |
677 | + data_dir = dir; |
678 | + } |
679 | + else |
680 | + { |
681 | + data_dir = g_get_user_data_dir(); // Never fails. |
682 | + } |
683 | + data_dir += "/storage-framework/local"; |
684 | + |
685 | + try |
686 | + { |
687 | + create_directories(data_dir); |
688 | + } |
689 | + catch (filesystem_error const& e) |
690 | + { |
691 | + throw_storage_exception(method, e); |
692 | + } |
693 | + |
694 | + return data_dir; |
695 | +} |
696 | + |
697 | +// Copy a file or directory (recursively). Ignore anything that has the temp file prefix |
698 | +// or is not a file or directory. |
699 | + |
700 | +void copy_recursively(boost::filesystem::path const& source, boost::filesystem::path const& target) |
701 | +{ |
702 | + using namespace boost::filesystem; |
703 | + |
704 | + if (is_reserved_path(source)) |
705 | + { |
706 | + return; // Don't copy temporary directories. |
707 | + } |
708 | + |
709 | + auto s = status(source); |
710 | + if (is_regular_file(s)) |
711 | + { |
712 | + copy_file(source, target); |
713 | + } |
714 | + else if (is_directory(s)) |
715 | + { |
716 | + copy_directory(source, target); // Poorly named in boost; this creates the target dir without recursion |
717 | + for (directory_iterator it(source); it != directory_iterator(); ++it) |
718 | + { |
719 | + path source_entry = it->path(); |
720 | + path target_entry = target; |
721 | + target_entry /= source_entry.filename(); |
722 | + copy_recursively(source_entry, target_entry); |
723 | + } |
724 | + } |
725 | + else |
726 | + { |
727 | + // Ignore everything that's not a directory or file. |
728 | + } |
729 | +} |
730 | + |
731 | +// Convert nanoseconds since the epoch into ISO 8601 date-time. |
732 | + |
733 | +string make_iso_date(int64_t nsecs_since_epoch) |
734 | +{ |
735 | + static char const* const FMT = "%Y-%m-%dT%TZ"; // ISO 8601, no fractional seconds. |
736 | + |
737 | + struct tm time; |
738 | + time_t secs_since_epoch = nsecs_since_epoch / 1000000000; |
739 | + gmtime_r(&secs_since_epoch, &time); |
740 | + |
741 | + char buf[128]; |
742 | + strftime(buf, sizeof(buf), FMT, &time); |
743 | + return buf; |
744 | +} |
745 | + |
746 | +string get_content_type(string const& filename) |
747 | +{ |
748 | + using namespace unity::storage::internal; |
749 | + |
750 | + static string const unknown_content_type = "application/octet-stream"; |
751 | + |
752 | + gobj_ptr<GFile> file(g_file_new_for_path(filename.c_str())); |
753 | + assert(file); // Cannot fail according to doc. |
754 | + |
755 | + GError* err = nullptr; |
756 | + gobj_ptr<GFileInfo> full_info(g_file_query_info(file.get(), |
757 | + G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE, |
758 | + G_FILE_QUERY_INFO_NONE, |
759 | + /* cancellable */ NULL, |
760 | + &err)); |
761 | + if (!full_info) |
762 | + { |
763 | + return unknown_content_type; // LCOV_EXCL_LINE |
764 | + } |
765 | + |
766 | + string content_type = g_file_info_get_attribute_string(full_info.get(), G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE); |
767 | + if (content_type.empty()) |
768 | + { |
769 | + return unknown_content_type; // LCOV_EXCL_LINE |
770 | + } |
771 | + return content_type; |
772 | +} |
773 | + |
774 | +// Simple wrapper template that deals with exception handling so we don't |
775 | +// have to repeat ourselves endlessly in the various lambdas below. |
776 | +// The auto deduction of the return type requires C++ 14. |
777 | + |
778 | +template<typename F> |
779 | +auto invoke_async(string const& method, F& functor) |
780 | +{ |
781 | + auto lambda = [method, functor] |
782 | + { |
783 | + try |
784 | + { |
785 | + return functor(); |
786 | + } |
787 | + catch (StorageException const&) |
788 | + { |
789 | + throw; |
790 | + } |
791 | + catch (boost::filesystem::filesystem_error const& e) |
792 | + { |
793 | + throw_storage_exception(method, e); |
794 | + } |
795 | + // LCOV_EXCL_START |
796 | + catch (std::exception const& e) |
797 | + { |
798 | + throw boost::enable_current_exception(UnknownException(e.what())); |
799 | + } |
800 | + // LCOV_EXCL_STOP |
801 | + }; |
802 | + // TODO: boost::async is potentially expensive for some operations. Consider boost::asio thread pool? |
803 | + return boost::async(boost::launch::async, lambda); |
804 | +} |
805 | + |
806 | +} // namespace |
807 | + |
808 | +LocalProvider::LocalProvider() |
809 | + : root_(boost::filesystem::canonical(get_root_dir("LocalProvider()"))) |
810 | +{ |
811 | +} |
812 | + |
813 | +LocalProvider::~LocalProvider() = default; |
814 | + |
815 | +boost::future<ItemList> LocalProvider::roots(vector<string> const& /* keys */, Context const& /* context */) |
816 | +{ |
817 | + vector<Item> roots{ make_item("roots()", root_, status(root_)) }; |
818 | + return boost::make_ready_future(roots); |
819 | +} |
820 | + |
821 | +boost::future<tuple<ItemList, string>> LocalProvider::list(string const& item_id, |
822 | + string const& page_token, |
823 | + vector<string> const& /* keys */, |
824 | + Context const& /* context */) |
825 | +{ |
826 | + string const method = "list()"; |
827 | + |
828 | + auto This = dynamic_pointer_cast<LocalProvider>(shared_from_this()); |
829 | + auto do_list = [This, method, item_id, page_token] |
830 | + { |
831 | + using namespace boost::filesystem; |
832 | + |
833 | + This->throw_if_not_valid(method, item_id); |
834 | + vector<Item> items; |
835 | + for (directory_iterator it(item_id); it != directory_iterator(); ++it) |
836 | + { |
837 | + auto dirent = *it; |
838 | + auto path = dirent.path(); |
839 | + if (is_reserved_path(path)) |
840 | + { |
841 | + continue; // Hide temp files that we create during copy() and move(). |
842 | + } |
843 | + Item i; |
844 | + try |
845 | + { |
846 | + auto st = dirent.status(); |
847 | + i = This->make_item(method, path, st); |
848 | + items.push_back(i); |
849 | + } |
850 | + catch (std::exception const&) |
851 | + { |
852 | + // We ignore weird errors (such as entries that are not files or folders). |
853 | + } |
854 | + } |
855 | + return tuple<ItemList, string>(items, ""); |
856 | + }; |
857 | + |
858 | + return invoke_async(method, do_list); |
859 | +} |
860 | + |
861 | +boost::future<ItemList> LocalProvider::lookup(string const& parent_id, |
862 | + string const& name, |
863 | + vector<string> const& /* keys */, |
864 | + Context const& /* context */) |
865 | +{ |
866 | + string const method = "lookup()"; |
867 | + |
868 | + auto This = dynamic_pointer_cast<LocalProvider>(shared_from_this()); |
869 | + auto do_lookup = [This, method, parent_id, name] |
870 | + { |
871 | + using namespace boost::filesystem; |
872 | + |
873 | + This->throw_if_not_valid(method, parent_id); |
874 | + auto sanitized_name = sanitize(method, name); |
875 | + path p = parent_id; |
876 | + p /= sanitized_name; |
877 | + This->throw_if_not_valid(method, p.native()); |
878 | + auto st = status(p); |
879 | + return vector<Item>{ This->make_item(method, p, st) }; |
880 | + }; |
881 | + |
882 | + return invoke_async(method, do_lookup); |
883 | +} |
884 | + |
885 | +boost::future<Item> LocalProvider::metadata(string const& item_id, |
886 | + vector<string> const& /* keys */, |
887 | + Context const& /* context */) |
888 | +{ |
889 | + string const method = "metadata()"; |
890 | + |
891 | + auto This = dynamic_pointer_cast<LocalProvider>(shared_from_this()); |
892 | + auto do_metadata = [This, method, item_id] |
893 | + { |
894 | + using namespace boost::filesystem; |
895 | + |
896 | + This->throw_if_not_valid(method, item_id); |
897 | + path p = item_id; |
898 | + auto st = status(p); |
899 | + return This->make_item(method, p, st); |
900 | + }; |
901 | + |
902 | + return invoke_async(method, do_metadata); |
903 | +} |
904 | + |
905 | +boost::future<Item> LocalProvider::create_folder(string const& parent_id, |
906 | + string const& name, |
907 | + vector<string> const& /* keys */, |
908 | + Context const& /* context */) |
909 | +{ |
910 | + string const method = "create_folder()"; |
911 | + |
912 | + auto This = dynamic_pointer_cast<LocalProvider>(shared_from_this()); |
913 | + auto do_create = [This, method, parent_id, name] |
914 | + { |
915 | + using namespace boost::filesystem; |
916 | + |
917 | + This->throw_if_not_valid(method, parent_id); |
918 | + auto sanitized_name = sanitize(method, name); |
919 | + path p = parent_id; |
920 | + p /= sanitized_name; |
921 | + // create_directory() succeeds if the directory exists already, so we need to check explicitly. |
922 | + if (exists(p)) |
923 | + { |
924 | + string msg = method + ": \"" + p.native() + "\" exists already"; |
925 | + throw boost::enable_current_exception(ExistsException(msg, p.native(), name)); |
926 | + } |
927 | + create_directory(p); |
928 | + auto st = status(p); |
929 | + return This->make_item(method, p, st); |
930 | + }; |
931 | + |
932 | + return invoke_async(method, do_create); |
933 | +} |
934 | + |
935 | +boost::future<unique_ptr<UploadJob>> LocalProvider::create_file(string const& parent_id, |
936 | + string const& name, |
937 | + int64_t size, |
938 | + string const& /* content_type */, |
939 | + bool allow_overwrite, |
940 | + vector<string> const& /* keys */, |
941 | + Context const& /* context */) |
942 | +{ |
943 | + auto This = dynamic_pointer_cast<LocalProvider>(shared_from_this()); |
944 | + boost::promise<unique_ptr<UploadJob>> p; |
945 | + p.set_value(make_unique<LocalUploadJob>(This, parent_id, name, size, allow_overwrite)); |
946 | + return p.get_future(); |
947 | +} |
948 | + |
949 | +boost::future<unique_ptr<UploadJob>> LocalProvider::update(string const& item_id, |
950 | + int64_t size, |
951 | + string const& old_etag, |
952 | + vector<string> const& /* keys */, |
953 | + Context const& /* context */) |
954 | +{ |
955 | + auto This = dynamic_pointer_cast<LocalProvider>(shared_from_this()); |
956 | + boost::promise<unique_ptr<UploadJob>> p; |
957 | + p.set_value(make_unique<LocalUploadJob>(This, item_id, size, old_etag)); |
958 | + return p.get_future(); |
959 | +} |
960 | + |
961 | +boost::future<unique_ptr<DownloadJob>> LocalProvider::download(string const& item_id, |
962 | + string const& match_etag, |
963 | + Context const& /* context */) |
964 | +{ |
965 | + auto This = dynamic_pointer_cast<LocalProvider>(shared_from_this()); |
966 | + boost::promise<unique_ptr<DownloadJob>> p; |
967 | + p.set_value(make_unique<LocalDownloadJob>(This, item_id, match_etag)); |
968 | + return p.get_future(); |
969 | +} |
970 | + |
971 | +boost::future<void> LocalProvider::delete_item(string const& item_id, Context const& /* context */) |
972 | +{ |
973 | + string const method = "delete_item()"; |
974 | + |
975 | + auto This = dynamic_pointer_cast<LocalProvider>(shared_from_this()); |
976 | + auto do_delete = [This, method, item_id] |
977 | + { |
978 | + using namespace boost::filesystem; |
979 | + |
980 | + This->throw_if_not_valid(method, item_id); |
981 | + if (canonical(item_id).native() == This->root_) |
982 | + { |
983 | + string msg = method + ": cannot delete root"; |
984 | + throw boost::enable_current_exception(LogicException(msg)); |
985 | + } |
986 | + remove_all(item_id); |
987 | + }; |
988 | + |
989 | + return invoke_async(method, do_delete); |
990 | +} |
991 | + |
992 | +boost::future<Item> LocalProvider::move(string const& item_id, |
993 | + string const& new_parent_id, |
994 | + string const& new_name, |
995 | + vector<string> const& /* keys */, |
996 | + Context const& /* context */) |
997 | +{ |
998 | + string const method = "move()"; |
999 | + |
1000 | + auto This = dynamic_pointer_cast<LocalProvider>(shared_from_this()); |
1001 | + auto do_move = [This, method, item_id, new_parent_id, new_name] |
1002 | + { |
1003 | + using namespace boost::filesystem; |
1004 | + |
1005 | + This->throw_if_not_valid(method, item_id); |
1006 | + This->throw_if_not_valid(method, new_parent_id); |
1007 | + auto sanitized_name = sanitize(method, new_name); |
1008 | + |
1009 | + path parent_path = new_parent_id; |
1010 | + path target_path = parent_path / sanitized_name; |
1011 | + |
1012 | + if (exists(target_path)) |
1013 | + { |
1014 | + string msg = method + ": \"" + target_path.native() + "\" exists already"; |
1015 | + throw boost::enable_current_exception(ExistsException(msg, target_path.native(), new_name)); |
1016 | + } |
1017 | + |
1018 | + // Small race condition here: if exists() just said that the target does not exist, it is |
1019 | + // possible for it to have been created since. If so, if the target is a file or an empty |
1020 | + // directory, it will be removed. In practice, this is unlikely to happen and, if it does, |
1021 | + // it is not the end of the world. |
1022 | + // TODO: deal with EXDEV |
1023 | + rename(item_id, target_path); |
1024 | + auto st = status(target_path); |
1025 | + return This->make_item(method, target_path, st); |
1026 | + }; |
1027 | + |
1028 | + return invoke_async(method, do_move); |
1029 | +} |
1030 | + |
1031 | +boost::future<Item> LocalProvider::copy(string const& item_id, |
1032 | + string const& new_parent_id, |
1033 | + string const& new_name, |
1034 | + vector<string> const& /* keys */, |
1035 | + Context const& /* context */) |
1036 | +{ |
1037 | + string const method = "copy()"; |
1038 | + |
1039 | + auto This = dynamic_pointer_cast<LocalProvider>(shared_from_this()); |
1040 | + auto do_copy = [This, method, item_id, new_parent_id, new_name] |
1041 | + { |
1042 | + using namespace boost::filesystem; |
1043 | + |
1044 | + This->throw_if_not_valid(method, item_id); |
1045 | + This->throw_if_not_valid(method, new_parent_id); |
1046 | + auto sanitized_name = sanitize(method, new_name); |
1047 | + |
1048 | + path parent_path = new_parent_id; |
1049 | + path target_path = parent_path / sanitized_name; |
1050 | + |
1051 | + if (is_directory(item_id)) |
1052 | + { |
1053 | + if (exists(target_path)) |
1054 | + { |
1055 | + string msg = method + ": \"" + target_path.native() + "\" exists already"; |
1056 | + throw boost::enable_current_exception(ExistsException(msg, target_path.native(), new_name)); |
1057 | + } |
1058 | + |
1059 | + // For recursive copy, we create a temporary directory in lieu of target_path and recursively copy |
1060 | + // everything into the temporary directory. This ensures that we don't invalidate directory iterators |
1061 | + // by creating things while we are iterating, potentially getting trapped in an infinite loop. |
1062 | + path tmp_path = canonical(parent_path); |
1063 | + tmp_path /= unique_path(string(TMPFILE_PREFIX) + "-%%%%-%%%%-%%%%-%%%%"); |
1064 | + create_directories(tmp_path); |
1065 | + for (directory_iterator it(item_id); it != directory_iterator(); ++it) |
1066 | + { |
1067 | + if (is_reserved_path(it->path())) |
1068 | + { |
1069 | + continue; // Don't recurse into the temporary directory |
1070 | + } |
1071 | + file_status s = it->status(); |
1072 | + if (is_directory(s) || is_regular_file(s)) |
1073 | + { |
1074 | + path source_entry = it->path(); |
1075 | + path target_entry = tmp_path; |
1076 | + target_entry /= source_entry.filename(); |
1077 | + copy_recursively(source_entry, target_entry); |
1078 | + } |
1079 | + } |
1080 | + rename(tmp_path, target_path); |
1081 | + } |
1082 | + else |
1083 | + { |
1084 | + copy_file(item_id, target_path); |
1085 | + } |
1086 | + |
1087 | + auto st = status(target_path); |
1088 | + return This->make_item(method, target_path, st); |
1089 | + }; |
1090 | + |
1091 | + return invoke_async(method, do_copy); |
1092 | +} |
1093 | + |
1094 | +// Make sure that id does not point outside the root. |
1095 | + |
1096 | +void LocalProvider::throw_if_not_valid(string const& method, string const& id) const |
1097 | +{ |
1098 | + using namespace boost::filesystem; |
1099 | + |
1100 | + string suspect_id; |
1101 | + try |
1102 | + { |
1103 | + suspect_id = canonical(id).native(); |
1104 | + } |
1105 | + catch (filesystem_error const& e) |
1106 | + { |
1107 | + throw_storage_exception(method, e); |
1108 | + } |
1109 | + |
1110 | + // Disallow things such as <root>/blah/../blah even though they lead to the correct path. |
1111 | + if (suspect_id != id) |
1112 | + { |
1113 | + throw boost::enable_current_exception(InvalidArgumentException(method + ": invalid id: \"" + id + "\"")); |
1114 | + } |
1115 | + // id must denote the root or have the root as a prefix. |
1116 | + auto const root_id = root_.native(); |
1117 | + if (id != root_id && !boost::starts_with(id, root_id + "/")) |
1118 | + { |
1119 | + throw boost::enable_current_exception(InvalidArgumentException(method + ": invalid id: \"" + id + "\"")); |
1120 | + } |
1121 | +} |
1122 | + |
1123 | +// Return an Item initialized from item_path and st. |
1124 | + |
1125 | +Item LocalProvider::make_item(string const& method, |
1126 | + boost::filesystem::path const& item_path, |
1127 | + boost::filesystem::file_status const& st) const |
1128 | +{ |
1129 | + using namespace unity::storage; |
1130 | + using namespace unity::storage::metadata; |
1131 | + using namespace boost::filesystem; |
1132 | + |
1133 | + map<string, MetadataValue> meta; |
1134 | + |
1135 | + string const item_id = item_path.native(); |
1136 | + int64_t const mtime_nsecs = get_mtime_nsecs(method, item_id); |
1137 | + string const iso_mtime = make_iso_date(mtime_nsecs); |
1138 | + |
1139 | + ItemType type; |
1140 | + string name = item_path.filename().native(); |
1141 | + vector<string> parents{item_path.parent_path().native()}; |
1142 | + string etag; |
1143 | + switch (st.type()) |
1144 | + { |
1145 | + case regular_file: |
1146 | + type = ItemType::file; |
1147 | + etag = to_string(mtime_nsecs); |
1148 | + meta.insert({SIZE_IN_BYTES, int64_t(file_size(item_path))}); |
1149 | + break; |
1150 | + case directory_file: |
1151 | + if (item_path == root_) |
1152 | + { |
1153 | + name = "/"; |
1154 | + parents.clear(); |
1155 | + type = ItemType::root; |
1156 | + } |
1157 | + else |
1158 | + { |
1159 | + type = ItemType::folder; |
1160 | + } |
1161 | + break; |
1162 | + default: |
1163 | + throw boost::enable_current_exception( |
1164 | + NotExistsException(method + ": \"" + item_id + "\" is neither a file nor a folder", item_id)); |
1165 | + } |
1166 | + |
1167 | + |
1168 | + auto const info = space(item_path); |
1169 | + meta.insert({FREE_SPACE_BYTES, int64_t(info.available)}); |
1170 | + meta.insert({USED_SPACE_BYTES, int64_t(info.capacity - info.available)}); |
1171 | + |
1172 | + meta.insert({LAST_MODIFIED_TIME, iso_mtime}); |
1173 | + meta.insert({CONTENT_TYPE, get_content_type(item_id)}); |
1174 | + |
1175 | + auto perms = st.permissions(); |
1176 | + bool writable; |
1177 | + if (type == ItemType::file) |
1178 | + { |
1179 | + writable = perms & owner_write; |
1180 | + } |
1181 | + else |
1182 | + { |
1183 | + writable = perms & owner_write && perms & owner_exe; |
1184 | + } |
1185 | + meta.insert({WRITABLE, writable}); |
1186 | + |
1187 | + return Item{ item_id, parents, name, etag, type, meta }; |
1188 | +} |
1189 | |
1190 | === added file 'src/local-provider/LocalProvider.h' |
1191 | --- src/local-provider/LocalProvider.h 1970-01-01 00:00:00 +0000 |
1192 | +++ src/local-provider/LocalProvider.h 2017-03-17 05:04:57 +0000 |
1193 | @@ -0,0 +1,84 @@ |
1194 | +/* |
1195 | + * Copyright (C) 2017 Canonical Ltd |
1196 | + * |
1197 | + * This program is free software: you can redistribute it and/or modify |
1198 | + * it under the terms of the GNU Lesser General Public License version 3 as |
1199 | + * published by the Free Software Foundation. |
1200 | + * |
1201 | + * This program is distributed in the hope that it will be useful, |
1202 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1203 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1204 | + * GNU Lesser General Public License for more details. |
1205 | + * |
1206 | + * You should have received a copy of the GNU Lesser General Public License |
1207 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1208 | + * |
1209 | + * Authors: Michi Henning <michi.henning@canonical.com> |
1210 | + */ |
1211 | + |
1212 | +#pragma once |
1213 | + |
1214 | +#include <unity/storage/provider/ProviderBase.h> |
1215 | + |
1216 | +#include <boost/filesystem.hpp> |
1217 | + |
1218 | +class LocalProvider : public unity::storage::provider::ProviderBase |
1219 | +{ |
1220 | +public: |
1221 | + LocalProvider(); |
1222 | + virtual ~LocalProvider(); |
1223 | + |
1224 | + boost::future<unity::storage::provider::ItemList> roots( |
1225 | + std::vector<std::string> const& metadata_keys, |
1226 | + unity::storage::provider::Context const& ctx) override; |
1227 | + boost::future<std::tuple<unity::storage::provider::ItemList, std::string>> list( |
1228 | + std::string const& item_id, std::string const& page_token, |
1229 | + std::vector<std::string> const& metadata_keys, |
1230 | + unity::storage::provider::Context const& ctx) override; |
1231 | + boost::future<unity::storage::provider::ItemList> lookup( |
1232 | + std::string const& parent_id, std::string const& name, |
1233 | + std::vector<std::string> const& metadata_keys, |
1234 | + unity::storage::provider::Context const& ctx) override; |
1235 | + boost::future<unity::storage::provider::Item> metadata( |
1236 | + std::string const& item_id, |
1237 | + std::vector<std::string> const& metadata_keys, |
1238 | + unity::storage::provider::Context const& ctx) override; |
1239 | + boost::future<unity::storage::provider::Item> create_folder( |
1240 | + std::string const& parent_id, std::string const& name, |
1241 | + std::vector<std::string> const& metadata_keys, |
1242 | + unity::storage::provider::Context const& ctx) override; |
1243 | + boost::future<std::unique_ptr<unity::storage::provider::UploadJob>> create_file( |
1244 | + std::string const& parent_id, std::string const& name, |
1245 | + int64_t size, std::string const& content_type, bool allow_overwrite, |
1246 | + std::vector<std::string> const& metadata_keys, |
1247 | + unity::storage::provider::Context const& ctx) override; |
1248 | + boost::future<std::unique_ptr<unity::storage::provider::UploadJob>> update( |
1249 | + std::string const& item_id, int64_t size, |
1250 | + std::string const& old_etag, |
1251 | + std::vector<std::string> const& metadata_keys, |
1252 | + unity::storage::provider::Context const& ctx) override; |
1253 | + boost::future<std::unique_ptr<unity::storage::provider::DownloadJob>> download( |
1254 | + std::string const& item_id, |
1255 | + std::string const& match_etag, |
1256 | + unity::storage::provider::Context const& ctx) override; |
1257 | + boost::future<void> delete_item(std::string const& item_id, |
1258 | + unity::storage::provider::Context const& ctx) override; |
1259 | + boost::future<unity::storage::provider::Item> move( |
1260 | + std::string const& item_id, std::string const& new_parent_id, |
1261 | + std::string const& new_name, |
1262 | + std::vector<std::string> const& metadata_keys, |
1263 | + unity::storage::provider::Context const& ctx) override; |
1264 | + boost::future<unity::storage::provider::Item> copy( |
1265 | + std::string const& item_id, std::string const& new_parent_id, |
1266 | + std::string const& new_name, |
1267 | + std::vector<std::string> const& metadata_keys, |
1268 | + unity::storage::provider::Context const& ctx) override; |
1269 | + |
1270 | + void throw_if_not_valid(std::string const& method, std::string const& id) const; |
1271 | + unity::storage::provider::Item make_item(std::string const& method, |
1272 | + boost::filesystem::path const& item_path, |
1273 | + boost::filesystem::file_status const& st) const; |
1274 | + |
1275 | +private: |
1276 | + boost::filesystem::path const root_; |
1277 | +}; |
1278 | |
1279 | === added file 'src/local-provider/LocalUploadJob.cpp' |
1280 | --- src/local-provider/LocalUploadJob.cpp 1970-01-01 00:00:00 +0000 |
1281 | +++ src/local-provider/LocalUploadJob.cpp 2017-03-17 05:04:57 +0000 |
1282 | @@ -0,0 +1,338 @@ |
1283 | +/* |
1284 | + * Copyright (C) 2017 Canonical Ltd |
1285 | + * |
1286 | + * This program is free software: you can redistribute it and/or modify |
1287 | + * it under the terms of the GNU Lesser General Public License version 3 as |
1288 | + * published by the Free Software Foundation. |
1289 | + * |
1290 | + * This program is distributed in the hope that it will be useful, |
1291 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1292 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1293 | + * GNU Lesser General Public License for more details. |
1294 | + * |
1295 | + * You should have received a copy of the GNU Lesser General Public License |
1296 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1297 | + * |
1298 | + * Authors: Michi Henning <michi.henning@canonical.com> |
1299 | + */ |
1300 | + |
1301 | +#include "LocalUploadJob.h" |
1302 | + |
1303 | +#include "LocalProvider.h" |
1304 | +#include "utils.h" |
1305 | +#include <unity/storage/internal/safe_strerror.h> |
1306 | +#include <unity/storage/provider/Exceptions.h> |
1307 | +#include <unity/storage/provider/ProviderBase.h> // TODO: Should not be needed |
1308 | + |
1309 | +#include <fcntl.h> |
1310 | + |
1311 | +using namespace unity::storage::provider; |
1312 | +using namespace std; |
1313 | + |
1314 | +static int next_upload_id = 0; |
1315 | + |
1316 | +LocalUploadJob::LocalUploadJob(shared_ptr<LocalProvider> const& provider, int64_t size, const string& method) |
1317 | + : UploadJob(to_string(++next_upload_id)) |
1318 | + , provider_(provider) |
1319 | + , size_(size) |
1320 | + , bytes_to_write_(size) |
1321 | + , method_(method) |
1322 | + , state_(in_progress) |
1323 | + , tmp_fd_([](int fd){ if (fd != -1) ::close(fd); }) |
1324 | +{ |
1325 | +} |
1326 | + |
1327 | +LocalUploadJob::LocalUploadJob(shared_ptr<LocalProvider> const& provider, |
1328 | + string const& parent_id, |
1329 | + string const& name, |
1330 | + int64_t size, |
1331 | + bool allow_overwrite) |
1332 | + : LocalUploadJob(provider, size, "create_file()") |
1333 | +{ |
1334 | + using namespace boost::filesystem; |
1335 | + |
1336 | + parent_id_ = parent_id; |
1337 | + allow_overwrite_ = allow_overwrite; |
1338 | + |
1339 | + provider_->throw_if_not_valid(method_, parent_id); |
1340 | + |
1341 | + auto sanitized_name = sanitize(method_, name); |
1342 | + path p = parent_id; |
1343 | + p /= sanitized_name; |
1344 | + item_id_ = p.native(); |
1345 | + if (!allow_overwrite && exists(item_id_)) |
1346 | + { |
1347 | + string msg = method_ + ": \"" + item_id_ + "\" exists already"; |
1348 | + throw ExistsException(msg, item_id_, sanitized_name.native()); |
1349 | + } |
1350 | + |
1351 | + prepare_channels(); |
1352 | +} |
1353 | + |
1354 | +LocalUploadJob::LocalUploadJob(shared_ptr<LocalProvider> const& provider, |
1355 | + string const& item_id, |
1356 | + int64_t size, |
1357 | + string const& old_etag) |
1358 | + : LocalUploadJob(provider, size, "update()") |
1359 | +{ |
1360 | + using namespace boost::filesystem; |
1361 | + |
1362 | + item_id_ = item_id; |
1363 | + provider_->throw_if_not_valid(method_, item_id); |
1364 | + try |
1365 | + { |
1366 | + auto st = status(item_id); |
1367 | + if (!is_regular_file(st)) |
1368 | + { |
1369 | + throw InvalidArgumentException(method_ + ": \"" + item_id + "\" is not a file"); |
1370 | + } |
1371 | + } |
1372 | + // LCOV_EXCL_START |
1373 | + catch (filesystem_error const& e) |
1374 | + { |
1375 | + // The call to status could throw if the file is unlinked immediately |
1376 | + // after the call to throw_if_not_valid. |
1377 | + throw_storage_exception(method_, e); |
1378 | + } |
1379 | + // LCOV_EXCL_STOP |
1380 | + if (!old_etag.empty()) |
1381 | + { |
1382 | + int64_t mtime = get_mtime_nsecs(method_, item_id); |
1383 | + if (to_string(mtime) != old_etag) |
1384 | + { |
1385 | + throw ConflictException(method_ + ": etag mismatch"); |
1386 | + } |
1387 | + } |
1388 | + old_etag_ = old_etag; |
1389 | + |
1390 | + prepare_channels(); |
1391 | +} |
1392 | + |
1393 | +LocalUploadJob::~LocalUploadJob() = default; |
1394 | + |
1395 | +void LocalUploadJob::prepare_channels() |
1396 | +{ |
1397 | + using namespace boost::filesystem; |
1398 | + |
1399 | + // Open tmp file for writing. |
1400 | + auto parent_path = path(item_id_).parent_path(); |
1401 | + tmp_fd_.reset(open(parent_path.native().c_str(), O_TMPFILE | O_WRONLY, 0600)); |
1402 | + if (tmp_fd_.get() == -1) |
1403 | + { |
1404 | + // Some kernels on the phones don't support O_TMPFILE and return various errno values when this fails. |
1405 | + // So, if anything at all goes wrong, we fall back on conventional temp file creation and |
1406 | + // produce a hard error if that doesn't work either. |
1407 | + // Note that, in this case, the temp file retains its name in the file system. Not nice because, |
1408 | + // if this process dies at the wrong moment, we leave the temp file behind. |
1409 | + use_linkat_ = false; |
1410 | + string tmpfile = parent_path.native() + "/" + TMPFILE_PREFIX + "-%%%%-%%%%-%%%%-%%%%"; |
1411 | + tmp_fd_.reset(mkstemp(const_cast<char*>(tmpfile.data()))); |
1412 | + if (tmp_fd_.get() == -1) |
1413 | + { |
1414 | + string msg = method_ + ": cannot create temp file \"" + tmpfile + "\": " |
1415 | + + unity::storage::internal::safe_strerror(errno); |
1416 | + throw ResourceException(msg, errno); |
1417 | + } |
1418 | + // LCOV_EXCL_START |
1419 | + file_.reset(new QFile(QString::fromStdString(tmpfile))); |
1420 | + file_->open(QIODevice::WriteOnly); |
1421 | + // LCOV_EXCL_STOP |
1422 | + } |
1423 | + else |
1424 | + { |
1425 | + use_linkat_ = true; |
1426 | + file_.reset(new QFile); |
1427 | + file_->open(tmp_fd_.get(), QIODevice::WriteOnly, QFileDevice::DontCloseHandle); |
1428 | + } |
1429 | + |
1430 | + // Make read socket ready. |
1431 | + int dup_fd = dup(read_socket()); |
1432 | + if (dup_fd == -1) |
1433 | + { |
1434 | + // LCOV_EXCL_START |
1435 | + string msg = method_ + ": dup() failed: " + unity::storage::internal::safe_strerror(errno); |
1436 | + throw ResourceException(msg, errno); |
1437 | + // LCOV_EXCL_STOP |
1438 | + } |
1439 | + read_socket_.setSocketDescriptor(dup_fd, QLocalSocket::ConnectedState, QIODevice::ReadOnly); |
1440 | + connect(&read_socket_, &QLocalSocket::readyRead, this, &LocalUploadJob::on_bytes_ready); |
1441 | + connect(&read_socket_, &QIODevice::readChannelFinished, this, &LocalUploadJob::on_read_channel_finished); |
1442 | +} |
1443 | + |
1444 | +boost::future<void> LocalUploadJob::cancel() |
1445 | +{ |
1446 | + if (state_ == in_progress) |
1447 | + { |
1448 | + abort_upload(); |
1449 | + } |
1450 | + return boost::make_ready_future(); |
1451 | +} |
1452 | + |
1453 | +boost::future<Item> LocalUploadJob::finish() |
1454 | +{ |
1455 | + on_bytes_ready(); // Read any remaining unread buffered data. |
1456 | + |
1457 | + if (bytes_to_write_ > 0) |
1458 | + { |
1459 | + string msg = "finish() method called too early, size was given as " |
1460 | + + to_string(size_) + " but only " |
1461 | + + to_string(size_ - bytes_to_write_) + " bytes were received"; |
1462 | + return boost::make_exceptional_future<Item>(LogicException(msg)); |
1463 | + } |
1464 | + |
1465 | + // We are committed to finishing successfully or with an error now. |
1466 | + state_ = finished; |
1467 | + |
1468 | + try |
1469 | + { |
1470 | + // We check again for an etag mismatch or overwrite, in case the file was updated after the upload started. |
1471 | + if (!parent_id_.empty()) |
1472 | + { |
1473 | + // create_file() |
1474 | + if (!allow_overwrite_ && boost::filesystem::exists(item_id_)) |
1475 | + { |
1476 | + string msg = method_ + ": \"" + item_id_ + "\" exists already"; |
1477 | + boost::filesystem::path(item_id_).filename().native(); |
1478 | + BOOST_THROW_EXCEPTION( |
1479 | + ExistsException(msg, item_id_, boost::filesystem::path(item_id_).filename().native())); |
1480 | + } |
1481 | + } |
1482 | + else if (!old_etag_.empty()) |
1483 | + { |
1484 | + // update() |
1485 | + int64_t mtime = get_mtime_nsecs(method_, item_id_); |
1486 | + if (to_string(mtime) != old_etag_) |
1487 | + { |
1488 | + BOOST_THROW_EXCEPTION(ConflictException(method_ + ": etag mismatch")); |
1489 | + } |
1490 | + } |
1491 | + |
1492 | + if (!file_->flush()) // Make sure that all buffered data is written. |
1493 | + { |
1494 | + // LCOV_EXCL_START |
1495 | + string msg = "finish(): cannot flush output file: " + file_->errorString().toStdString(); |
1496 | + throw_storage_exception("finish()", msg, file_->error()); |
1497 | + // LCOV_EXCL_STOP |
1498 | + } |
1499 | + |
1500 | + // Link the anonymous tmp file into the file system. |
1501 | + using namespace unity::storage::internal; |
1502 | + |
1503 | + if (use_linkat_) |
1504 | + { |
1505 | + auto old_path = string("/proc/self/fd/") + std::to_string(tmp_fd_.get()); |
1506 | + ::unlink(item_id_.c_str()); // linkat() will not remove existing file: http://lwn.net/Articles/559969/ |
1507 | + if (linkat(-1, old_path.c_str(), tmp_fd_.get(), item_id_.c_str(), AT_SYMLINK_FOLLOW) == -1) |
1508 | + { |
1509 | + // LCOV_EXCL_START |
1510 | + string msg = "finish(): linkat \"" + old_path + "\" to \"" + item_id_ + "\" failed: " |
1511 | + + safe_strerror(errno); |
1512 | + BOOST_THROW_EXCEPTION(ResourceException(msg, errno)); |
1513 | + // LCOV_EXCL_STOP |
1514 | + } |
1515 | + } |
1516 | + else |
1517 | + { |
1518 | + // LCOV_EXCL_START |
1519 | + auto old_path = file_->fileName().toStdString(); |
1520 | + if (rename(old_path.c_str(), item_id_.c_str()) == -1) |
1521 | + { |
1522 | + string msg = "finish(): rename \"" + old_path + "\" to \"" + item_id_ + "\" failed: " |
1523 | + + safe_strerror(errno); |
1524 | + BOOST_THROW_EXCEPTION(ResourceException(msg, errno)); |
1525 | + } |
1526 | + // LCOV_EXCL_STOP |
1527 | + } |
1528 | + |
1529 | + file_->close(); |
1530 | + read_socket_.close(); |
1531 | + |
1532 | + auto st = boost::filesystem::status(item_id_); |
1533 | + return boost::make_ready_future<Item>(provider_->make_item(method_, item_id_, st)); |
1534 | + } |
1535 | + catch (StorageException const&) |
1536 | + { |
1537 | + return boost::make_exceptional_future<Item>(boost::current_exception()); |
1538 | + } |
1539 | + // LCOV_EXCL_START |
1540 | + catch (boost::filesystem::filesystem_error const& e) |
1541 | + { |
1542 | + try |
1543 | + { |
1544 | + throw_storage_exception("finish()", e); |
1545 | + } |
1546 | + catch (StorageException const&) |
1547 | + { |
1548 | + return boost::make_exceptional_future<Item>(boost::current_exception()); |
1549 | + } |
1550 | + } |
1551 | + catch (std::exception const& e) |
1552 | + { |
1553 | + return boost::make_exceptional_future<Item>(UnknownException(e.what())); |
1554 | + } |
1555 | + // LCOV_EXCL_STOP |
1556 | +} |
1557 | + |
1558 | +void LocalUploadJob::on_bytes_ready() |
1559 | +{ |
1560 | + if (bytes_to_write_ < 0) |
1561 | + { |
1562 | + return; // LCOV_EXCL_LINE // We received too many bytes earlier. |
1563 | + } |
1564 | + |
1565 | + try |
1566 | + { |
1567 | + auto buf = read_socket_.readAll(); |
1568 | + if (buf.size() != 0) |
1569 | + { |
1570 | + bytes_to_write_ -= buf.size(); |
1571 | + if (bytes_to_write_ < 0) |
1572 | + { |
1573 | + string msg = method_ + ": received more than the expected number (" + to_string(size_) + ") of bytes"; |
1574 | + throw LogicException(msg); |
1575 | + } |
1576 | + auto bytes_written = file_->write(buf); |
1577 | + if (bytes_written == -1) |
1578 | + { |
1579 | + // LCOV_EXCL_START |
1580 | + string msg = "write error: " + file_->errorString().toStdString(); |
1581 | + throw_storage_exception(method_, msg, file_->error()); |
1582 | + // LCOV_EXCL_STOP |
1583 | + } |
1584 | + else if (bytes_written != buf.size()) |
1585 | + { |
1586 | + // LCOV_EXCL_START |
1587 | + string msg = "write error, requested " + to_string(buf.size()) + " B, but wrote only " |
1588 | + + to_string(bytes_written) + " B."; |
1589 | + throw_storage_exception(method_, msg, QFileDevice::FatalError); |
1590 | + // LCOV_EXCL_STOP |
1591 | + } |
1592 | + } |
1593 | + } |
1594 | + catch (std::exception const&) |
1595 | + { |
1596 | + abort_upload(); |
1597 | + report_error(current_exception()); |
1598 | + } |
1599 | +} |
1600 | + |
1601 | +void LocalUploadJob::on_read_channel_finished() |
1602 | +{ |
1603 | + on_bytes_ready(); // In case there s still buffered data to be read. |
1604 | +} |
1605 | + |
1606 | +void LocalUploadJob::abort_upload() |
1607 | +{ |
1608 | + state_ = cancelled; |
1609 | + disconnect(&read_socket_, nullptr, this, nullptr); |
1610 | + read_socket_.abort(); |
1611 | + file_->close(); |
1612 | + if (!use_linkat_) |
1613 | + { |
1614 | + // LCOV_EXCL_START |
1615 | + string filename = file_->fileName().toStdString(); |
1616 | + ::unlink(filename.c_str()); // Don't leave any temp file behind. |
1617 | + // LCOV_EXCL_STOP |
1618 | + } |
1619 | + bytes_to_write_ = 0; |
1620 | +} |
1621 | |
1622 | === added file 'src/local-provider/LocalUploadJob.h' |
1623 | --- src/local-provider/LocalUploadJob.h 1970-01-01 00:00:00 +0000 |
1624 | +++ src/local-provider/LocalUploadJob.h 2017-03-17 05:04:57 +0000 |
1625 | @@ -0,0 +1,79 @@ |
1626 | +/* |
1627 | + * Copyright (C) 2017 Canonical Ltd |
1628 | + * |
1629 | + * This program is free software: you can redistribute it and/or modify |
1630 | + * it under the terms of the GNU Lesser General Public License version 3 as |
1631 | + * published by the Free Software Foundation. |
1632 | + * |
1633 | + * This program is distributed in the hope that it will be useful, |
1634 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1635 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1636 | + * GNU Lesser General Public License for more details. |
1637 | + * |
1638 | + * You should have received a copy of the GNU Lesser General Public License |
1639 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1640 | + * |
1641 | + * Authors: Michi Henning <michi.henning@canonical.com> |
1642 | + */ |
1643 | + |
1644 | +#pragma once |
1645 | + |
1646 | +#include <unity/storage/provider/UploadJob.h> |
1647 | + |
1648 | +#include <unity/util/ResourcePtr.h> |
1649 | + |
1650 | +#pragma GCC diagnostic push |
1651 | +#pragma GCC diagnostic ignored "-Wctor-dtor-privacy" |
1652 | +#pragma GCC diagnostic ignored "-Wswitch-default" |
1653 | +#include <QFile> |
1654 | +#include <QLocalSocket> |
1655 | +#pragma GCC diagnostic pop |
1656 | + |
1657 | +class LocalProvider; |
1658 | + |
1659 | +class LocalUploadJob : public QObject, public unity::storage::provider::UploadJob |
1660 | +{ |
1661 | + Q_OBJECT |
1662 | +public: |
1663 | + LocalUploadJob(std::shared_ptr<LocalProvider> const& provider, int64_t size, const std::string& method); |
1664 | + |
1665 | + // create_file() |
1666 | + LocalUploadJob(std::shared_ptr<LocalProvider> const& provider, |
1667 | + std::string const& parent_id, |
1668 | + std::string const& name, |
1669 | + int64_t size, |
1670 | + bool allow_overwrite); |
1671 | + // update() |
1672 | + LocalUploadJob(std::shared_ptr<LocalProvider> const& provider, |
1673 | + std::string const& item_id, |
1674 | + int64_t size, |
1675 | + std::string const& old_etag); |
1676 | + virtual ~LocalUploadJob(); |
1677 | + |
1678 | + virtual boost::future<void> cancel() override; |
1679 | + virtual boost::future<unity::storage::provider::Item> finish() override; |
1680 | + |
1681 | +private Q_SLOTS: |
1682 | + void on_bytes_ready(); |
1683 | + void on_read_channel_finished(); |
1684 | + |
1685 | +private: |
1686 | + enum State { in_progress, finished, cancelled }; |
1687 | + |
1688 | + void prepare_channels(); |
1689 | + void abort_upload(); |
1690 | + |
1691 | + std::shared_ptr<LocalProvider> const provider_; |
1692 | + int64_t const size_; |
1693 | + int64_t bytes_to_write_; |
1694 | + std::unique_ptr<QFile> file_; |
1695 | + QLocalSocket read_socket_; |
1696 | + std::string const method_; |
1697 | + State state_; |
1698 | + std::string item_id_; |
1699 | + std::string old_etag_; // Empty for create_file() |
1700 | + std::string parent_id_; // Empty for update() |
1701 | + bool allow_overwrite_; // Undefined for update() |
1702 | + unity::util::ResourcePtr<int, std::function<void(int)>> tmp_fd_; |
1703 | + bool use_linkat_; |
1704 | +}; |
1705 | |
1706 | === added file 'src/local-provider/com.canonical.StorageFramework.Provider.Local.service.in' |
1707 | --- src/local-provider/com.canonical.StorageFramework.Provider.Local.service.in 1970-01-01 00:00:00 +0000 |
1708 | +++ src/local-provider/com.canonical.StorageFramework.Provider.Local.service.in 2017-03-17 05:04:57 +0000 |
1709 | @@ -0,0 +1,3 @@ |
1710 | +[D-BUS Service] |
1711 | +Name=com.canonical.StorageFramework.Provider.Local |
1712 | +Exec=@CMAKE_INSTALL_FULL_LIBDIR@/@PROJECT_NAME@/storage-provider-local |
1713 | |
1714 | === added file 'src/local-provider/main.cpp' |
1715 | --- src/local-provider/main.cpp 1970-01-01 00:00:00 +0000 |
1716 | +++ src/local-provider/main.cpp 2017-03-17 05:04:57 +0000 |
1717 | @@ -0,0 +1,52 @@ |
1718 | +/* |
1719 | + * Copyright (C) 2017 Canonical Ltd |
1720 | + * |
1721 | + * This program is free software: you can redistribute it and/or modify |
1722 | + * it under the terms of the GNU Lesser General Public License version 3 as |
1723 | + * published by the Free Software Foundation. |
1724 | + * |
1725 | + * This program is distributed in the hope that it will be useful, |
1726 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1727 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1728 | + * GNU Lesser General Public License for more details. |
1729 | + * |
1730 | + * You should have received a copy of the GNU Lesser General Public License |
1731 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1732 | + * |
1733 | + * Authors: Michi Henning <michi.henning@canonical.com> |
1734 | + */ |
1735 | + |
1736 | +#include <unity/storage/provider/Server.h> |
1737 | + |
1738 | +#include "LocalProvider.h" |
1739 | + |
1740 | +#include <boost/filesystem.hpp> |
1741 | + |
1742 | +#include <iostream> |
1743 | + |
1744 | +using namespace std; |
1745 | +using namespace unity::storage::provider; |
1746 | + |
1747 | +int main(int argc, char* argv[]) |
1748 | +{ |
1749 | + using namespace boost::filesystem; |
1750 | + |
1751 | + string const bus_name = "com.canonical.StorageFramework.Provider.Local"; |
1752 | + string const account_service_id = ""; |
1753 | + |
1754 | + string progname = argv[0]; |
1755 | + |
1756 | + try |
1757 | + { |
1758 | + progname = path(progname).filename().native(); |
1759 | + |
1760 | + Server<LocalProvider> server(bus_name, account_service_id); |
1761 | + server.init(argc, argv); |
1762 | + server.run(); |
1763 | + } |
1764 | + catch (std::exception const& e) |
1765 | + { |
1766 | + cerr << progname << ": " << e.what() << endl; |
1767 | + return 1; |
1768 | + } |
1769 | +} |
1770 | |
1771 | === added file 'src/local-provider/utils.cpp' |
1772 | --- src/local-provider/utils.cpp 1970-01-01 00:00:00 +0000 |
1773 | +++ src/local-provider/utils.cpp 2017-03-17 05:04:57 +0000 |
1774 | @@ -0,0 +1,146 @@ |
1775 | +/* |
1776 | + * Copyright (C) 2017 Canonical Ltd |
1777 | + * |
1778 | + * This program is free software: you can redistribute it and/or modify |
1779 | + * it under the terms of the GNU Lesser General Public License version 3 as |
1780 | + * published by the Free Software Foundation. |
1781 | + * |
1782 | + * This program is distributed in the hope that it will be useful, |
1783 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1784 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1785 | + * GNU Lesser General Public License for more details. |
1786 | + * |
1787 | + * You should have received a copy of the GNU Lesser General Public License |
1788 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1789 | + * |
1790 | + * Authors: Michi Henning <michi.henning@canonical.com> |
1791 | + */ |
1792 | + |
1793 | +#include "utils.h" |
1794 | + |
1795 | +#include <unity/storage/internal/safe_strerror.h> |
1796 | +#include <unity/storage/provider/Exceptions.h> |
1797 | + |
1798 | +#include <boost/algorithm/string.hpp> |
1799 | +#include <boost/exception/enable_current_exception.hpp> |
1800 | + |
1801 | +#include <sys/stat.h> |
1802 | + |
1803 | +using namespace unity::storage::provider; |
1804 | +using namespace std; |
1805 | + |
1806 | +// Return modification time in nanoseconds since the epoch. |
1807 | + |
1808 | +int64_t get_mtime_nsecs(string const& method, string const& path) |
1809 | +{ |
1810 | + using namespace unity::storage::internal; |
1811 | + |
1812 | + struct stat st; |
1813 | + if (stat(path.c_str(), &st) == -1) |
1814 | + { |
1815 | + // LCOV_EXCL_START |
1816 | + string msg = method + ": cannot stat \"" + path + "\": " + safe_strerror(errno); |
1817 | + throw boost::enable_current_exception(ResourceException(msg, errno)); |
1818 | + // LCOV_EXCL_STOP |
1819 | + } |
1820 | + return int64_t(st.st_mtim.tv_sec) * 1000000000 + st.st_mtim.tv_nsec; |
1821 | +} |
1822 | + |
1823 | +// Return true if the path uses the temp file prefix. |
1824 | + |
1825 | +bool is_reserved_path(boost::filesystem::path const& path) |
1826 | +{ |
1827 | + string filename = path.filename().native(); |
1828 | + return boost::starts_with(filename, TMPFILE_PREFIX); |
1829 | +} |
1830 | + |
1831 | +// Check that name is a valid file or directory name, that is, has a single component |
1832 | +// and is not "", ".", or "..". Also check that the name does not start with the |
1833 | +// temp file prefix. Throw if the name is invalid. |
1834 | + |
1835 | +boost::filesystem::path sanitize(string const& method, string const& name) |
1836 | +{ |
1837 | + using namespace boost::filesystem; |
1838 | + |
1839 | + path p = name; |
1840 | + if (!p.parent_path().empty()) |
1841 | + { |
1842 | + // name contains more than one component. |
1843 | + string msg = method + ": name \"" + name + "\" cannot contain a slash"; |
1844 | + throw boost::enable_current_exception(InvalidArgumentException(msg)); |
1845 | + } |
1846 | + path filename = p.filename(); |
1847 | + if (filename.empty() || filename == "." || filename == "..") |
1848 | + { |
1849 | + // Not an allowable file name. |
1850 | + string msg = method + ": invalid name: \"" + name + "\""; |
1851 | + throw boost::enable_current_exception(InvalidArgumentException(msg)); |
1852 | + } |
1853 | + if (is_reserved_path(filename)) |
1854 | + { |
1855 | + string msg = string(method + ": names beginning with \"") + TMPFILE_PREFIX + "\" are reserved"; |
1856 | + throw boost::enable_current_exception(InvalidArgumentException(msg)); |
1857 | + } |
1858 | + return p; |
1859 | +} |
1860 | + |
1861 | +// Throw a StorageException that corresponds to a boost::filesystem_error. |
1862 | + |
1863 | +void throw_storage_exception(string const& method, boost::filesystem::filesystem_error const& e) |
1864 | +{ |
1865 | + using namespace boost::system::errc; |
1866 | + |
1867 | + string msg = method + ": "; |
1868 | + string path1 = e.path1().native(); |
1869 | + string path2 = e.path2().native(); |
1870 | + if (!path2.empty()) |
1871 | + { |
1872 | + msg += "src = \"" + path1 + "\", target = \"" + path2 + "\""; // LCOV_EXCL_LINE |
1873 | + } |
1874 | + else |
1875 | + { |
1876 | + msg += "\"" + path1 + "\""; |
1877 | + } |
1878 | + msg += string(": ") + e.what(); |
1879 | + switch (e.code().value()) |
1880 | + { |
1881 | + case permission_denied: |
1882 | + case operation_not_permitted: |
1883 | + throw boost::enable_current_exception(PermissionException(msg)); |
1884 | + case no_such_file_or_directory: |
1885 | + throw boost::enable_current_exception(NotExistsException(msg, e.path1().native())); |
1886 | + // LCOV_EXCL_START |
1887 | + case file_exists: |
1888 | + throw boost::enable_current_exception( |
1889 | + ExistsException(msg, e.path1().native(), e.path1().filename().native())); |
1890 | + case no_space_on_device: |
1891 | + throw boost::enable_current_exception(QuotaException(msg)); |
1892 | + default: |
1893 | + throw boost::enable_current_exception(ResourceException(msg, e.code().value())); |
1894 | + // LCOV_EXCL_STOP |
1895 | + } |
1896 | +} |
1897 | + |
1898 | +// Throw a storage exception that corresponds to a FileError. |
1899 | + |
1900 | +void throw_storage_exception(string const& method, string const& msg, QFileDevice::FileError e) |
1901 | +{ |
1902 | + string const error_msg = method + ": " + msg; |
1903 | + switch (e) |
1904 | + { |
1905 | + case QFileDevice::NoError: |
1906 | + abort(); // LCOV_EXCL_LINE // Precondition violation |
1907 | + break; |
1908 | + default: |
1909 | + throw ResourceException(error_msg + " (QFileDevice::FileError = " + to_string(e) + ")", e); |
1910 | + } |
1911 | +} |
1912 | + |
1913 | +// Throw a storage exception that corresponds to a LocalSocketError. |
1914 | + |
1915 | +// LCOV_EXCL_START |
1916 | +void throw_storage_exception(string const& method, string const& msg, QLocalSocket::LocalSocketError e) |
1917 | +{ |
1918 | + throw ResourceException(method + ": " + msg + " (QLocalSocket::LocalSocketError = " + to_string(e) + ")", e); |
1919 | +} |
1920 | +// LCOV_EXCL_STOP |
1921 | |
1922 | === added file 'src/local-provider/utils.h' |
1923 | --- src/local-provider/utils.h 1970-01-01 00:00:00 +0000 |
1924 | +++ src/local-provider/utils.h 2017-03-17 05:04:57 +0000 |
1925 | @@ -0,0 +1,45 @@ |
1926 | +/* |
1927 | + * Copyright (C) 2017 Canonical Ltd |
1928 | + * |
1929 | + * This program is free software: you can redistribute it and/or modify |
1930 | + * it under the terms of the GNU Lesser General Public License version 3 as |
1931 | + * published by the Free Software Foundation. |
1932 | + * |
1933 | + * This program is distributed in the hope that it will be useful, |
1934 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1935 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1936 | + * GNU Lesser General Public License for more details. |
1937 | + * |
1938 | + * You should have received a copy of the GNU Lesser General Public License |
1939 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1940 | + * |
1941 | + * Authors: Michi Henning <michi.henning@canonical.com> |
1942 | + */ |
1943 | + |
1944 | +#pragma once |
1945 | + |
1946 | +#include <boost/filesystem.hpp> |
1947 | + |
1948 | +#pragma GCC diagnostic push |
1949 | +#pragma GCC diagnostic ignored "-Wctor-dtor-privacy" |
1950 | +#include <QFileDevice> |
1951 | +#include <QLocalSocket> |
1952 | +#include <QString> |
1953 | +#pragma GCC diagnostic pop |
1954 | + |
1955 | +#include <string> |
1956 | + |
1957 | +constexpr char const* TMPFILE_PREFIX = ".storage-framework"; |
1958 | + |
1959 | +int64_t get_mtime_nsecs(std::string const& method, std::string const& path); |
1960 | +bool is_reserved_path(boost::filesystem::path const& path); |
1961 | +boost::filesystem::path sanitize(std::string const& method, std::string const& name); |
1962 | + |
1963 | +[[ noreturn ]] |
1964 | +void throw_storage_exception(std::string const& method, boost::filesystem::filesystem_error const& e); |
1965 | + |
1966 | +[[ noreturn ]] |
1967 | +void throw_storage_exception(std::string const& method, std::string const& msg, QFileDevice::FileError e); |
1968 | + |
1969 | +[[ noreturn ]] |
1970 | +void throw_storage_exception(std::string const& method, std::string const& msg, QLocalSocket::LocalSocketError e); |
1971 | |
1972 | === modified file 'src/qt/internal/RuntimeImpl.cpp' |
1973 | --- src/qt/internal/RuntimeImpl.cpp 2016-11-21 13:29:39 +0000 |
1974 | +++ src/qt/internal/RuntimeImpl.cpp 2017-03-17 05:04:57 +0000 |
1975 | @@ -24,9 +24,11 @@ |
1976 | #include <unity/storage/qt/internal/AccountImpl.h> |
1977 | #include <unity/storage/qt/internal/AccountsJobImpl.h> |
1978 | #include <unity/storage/qt/internal/StorageErrorImpl.h> |
1979 | +#include <unity/storage/qt/Downloader.h> |
1980 | #include <unity/storage/qt/ItemJob.h> |
1981 | #include <unity/storage/qt/ItemListJob.h> |
1982 | #include <unity/storage/qt/Runtime.h> |
1983 | +#include <unity/storage/qt/Uploader.h> |
1984 | #include <unity/storage/qt/VoidJob.h> |
1985 | #include <unity/storage/registry/Registry.h> |
1986 | |
1987 | @@ -51,10 +53,12 @@ |
1988 | qRegisterMetaType<unity::storage::qt::AccountsJob::Status>(); |
1989 | qRegisterMetaType<unity::storage::qt::Account>(); |
1990 | qRegisterMetaType<QList<unity::storage::qt::Account>>(); |
1991 | + qRegisterMetaType<unity::storage::qt::Downloader::Status>(); |
1992 | qRegisterMetaType<unity::storage::qt::Item>(); |
1993 | qRegisterMetaType<QList<unity::storage::qt::Item>>(); |
1994 | qRegisterMetaType<unity::storage::qt::ItemJob::Status>(); |
1995 | qRegisterMetaType<unity::storage::qt::ItemListJob::Status>(); |
1996 | + qRegisterMetaType<unity::storage::qt::Uploader::Status>(); |
1997 | qRegisterMetaType<unity::storage::qt::VoidJob::Status>(); |
1998 | |
1999 | qDBusRegisterMetaType<unity::storage::internal::ItemMetadata>(); |
2000 | |
2001 | === modified file 'src/qt/internal/StorageErrorImpl.cpp' |
2002 | --- src/qt/internal/StorageErrorImpl.cpp 2017-01-24 06:12:09 +0000 |
2003 | +++ src/qt/internal/StorageErrorImpl.cpp 2017-03-17 05:04:57 +0000 |
2004 | @@ -78,7 +78,6 @@ |
2005 | || type == StorageError::Type::Cancelled |
2006 | || type == StorageError::Type::LogicError |
2007 | || type == StorageError::Type::InvalidArgument); |
2008 | - assert(!msg.isEmpty()); |
2009 | |
2010 | message_ = msg; |
2011 | } |
2012 | @@ -87,7 +86,6 @@ |
2013 | : StorageErrorImpl(type) |
2014 | { |
2015 | assert(type == StorageError::Type::NotExists); |
2016 | - assert(!msg.isEmpty()); |
2017 | |
2018 | message_ = msg; |
2019 | item_id_ = key; |
2020 | @@ -104,9 +102,6 @@ |
2021 | : StorageErrorImpl(type) |
2022 | { |
2023 | assert(type == StorageError::Type::Exists); |
2024 | - assert(!msg.isEmpty()); |
2025 | - assert(!item_id.isEmpty()); |
2026 | - assert(!item_name.isEmpty()); |
2027 | |
2028 | message_ = msg; |
2029 | item_id_ = item_id; |
2030 | @@ -117,7 +112,6 @@ |
2031 | : StorageErrorImpl(type) |
2032 | { |
2033 | assert(type == StorageError::Type::ResourceError); |
2034 | - assert(!msg.isEmpty()); |
2035 | |
2036 | message_ = msg; |
2037 | error_code_ = error_code; |
2038 | |
2039 | === modified file 'tests/CMakeLists.txt' |
2040 | --- tests/CMakeLists.txt 2017-03-07 10:23:24 +0000 |
2041 | +++ tests/CMakeLists.txt 2017-03-17 05:04:57 +0000 |
2042 | @@ -8,6 +8,7 @@ |
2043 | set(unit_test_dirs |
2044 | registry |
2045 | local-client |
2046 | + local-provider |
2047 | remote-client |
2048 | remote-client-v1 |
2049 | provider-AccountData |
2050 | |
2051 | === added directory 'tests/local-provider' |
2052 | === added file 'tests/local-provider/CMakeLists.txt' |
2053 | --- tests/local-provider/CMakeLists.txt 1970-01-01 00:00:00 +0000 |
2054 | +++ tests/local-provider/CMakeLists.txt 2017-03-17 05:04:57 +0000 |
2055 | @@ -0,0 +1,18 @@ |
2056 | +add_executable(local-provider_test local-provider_test.cpp) |
2057 | + |
2058 | +add_definitions(-DTEST_DIR="${CMAKE_CURRENT_BINARY_DIR}" -DBOOST_THREAD_VERSION=4) |
2059 | + |
2060 | +target_link_libraries(local-provider_test |
2061 | + local-provider-lib |
2062 | + storage-framework-provider |
2063 | + storage-framework-qt-client-v2 |
2064 | + Qt5::Test |
2065 | + ${Boost_LIBRARIES} |
2066 | + ${GLIB_DEPS_LIBRARIES} |
2067 | + ${GIO_DEPS_LIBRARIES} |
2068 | + testutils |
2069 | + gtest |
2070 | +) |
2071 | +add_test(local-provider local-provider_test) |
2072 | + |
2073 | +set(UNIT_TEST_TARGETS ${UNIT_TEST_TARGETS} PARENT_SCOPE) |
2074 | |
2075 | === added file 'tests/local-provider/local-provider_test.cpp' |
2076 | --- tests/local-provider/local-provider_test.cpp 1970-01-01 00:00:00 +0000 |
2077 | +++ tests/local-provider/local-provider_test.cpp 2017-03-17 05:04:57 +0000 |
2078 | @@ -0,0 +1,1473 @@ |
2079 | +/* |
2080 | + * Copyright (C) 2017 Canonical Ltd |
2081 | + * |
2082 | + * This program is free software: you can redistribute it and/or modify |
2083 | + * it under the terms of the GNU Lesser General Public License version 3 as |
2084 | + * published by the Free Software Foundation. |
2085 | + * |
2086 | + * This program is distributed in the hope that it will be useful, |
2087 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
2088 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2089 | + * GNU Lesser General Public License for more details. |
2090 | + * |
2091 | + * You should have received a copy of the GNU Lesser General Public License |
2092 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
2093 | + * |
2094 | + * Authors: Michi Henning <michi.henning@canonical.com> |
2095 | + */ |
2096 | + |
2097 | +#include "../../src/local-provider/LocalDownloadJob.h" |
2098 | +#include "../../src/local-provider/LocalProvider.h" |
2099 | +#include "../../src/local-provider/LocalUploadJob.h" |
2100 | + |
2101 | +#include <unity/storage/provider/DownloadJob.h> |
2102 | +#include <unity/storage/provider/Exceptions.h> |
2103 | +#include <unity/storage/provider/Server.h> |
2104 | +#include <unity/storage/qt/client-api.h> |
2105 | +#include <utils/env_var_guard.h> |
2106 | +#include <utils/ProviderFixture.h> |
2107 | + |
2108 | +#include <boost/algorithm/string.hpp> |
2109 | +#include <gtest/gtest.h> |
2110 | +#include <QCoreApplication> |
2111 | +#include <QSignalSpy> |
2112 | + |
2113 | +#include <chrono> |
2114 | +#include <regex> |
2115 | + |
2116 | +#include <fcntl.h> |
2117 | + |
2118 | +using namespace unity::storage; |
2119 | +using namespace std; |
2120 | + |
2121 | +namespace |
2122 | +{ |
2123 | + |
2124 | +int64_t nanosecs_now() |
2125 | +{ |
2126 | + return chrono::system_clock::now().time_since_epoch() / chrono::nanoseconds(1); |
2127 | +} |
2128 | + |
2129 | +string const ROOT_DIR = TEST_DIR "/data"; |
2130 | + |
2131 | +class LocalProviderTest : public ProviderFixture |
2132 | +{ |
2133 | +protected: |
2134 | + void SetUp() override |
2135 | + { |
2136 | + boost::filesystem::remove_all(ROOT_DIR); |
2137 | + boost::filesystem::create_directory(ROOT_DIR); |
2138 | + |
2139 | + ProviderFixture::SetUp(); |
2140 | + runtime_.reset(new qt::Runtime(connection())); |
2141 | + acc_ = runtime_->make_test_account(service_connection_->baseService(), object_path()); |
2142 | + } |
2143 | + |
2144 | + void TearDown() override |
2145 | + { |
2146 | + runtime_.reset(); |
2147 | + ProviderFixture::TearDown(); |
2148 | + } |
2149 | + |
2150 | + unique_ptr<qt::Runtime> runtime_; |
2151 | + qt::Account acc_; |
2152 | +}; |
2153 | + |
2154 | +constexpr int SIGNAL_WAIT_TIME = 30000; |
2155 | + |
2156 | +template <typename Job> |
2157 | +void wait(Job* job) |
2158 | +{ |
2159 | + QSignalSpy spy(job, &Job::statusChanged); |
2160 | + while (job->status() == Job::Loading) |
2161 | + { |
2162 | + if (!spy.wait(SIGNAL_WAIT_TIME)) |
2163 | + { |
2164 | + throw runtime_error("Wait for statusChanged signal timed out"); |
2165 | + } |
2166 | + } |
2167 | +} |
2168 | + |
2169 | +qt::Item get_root(qt::Account const& account) |
2170 | +{ |
2171 | + unique_ptr<qt::ItemListJob> j(account.roots()); |
2172 | + assert(j->isValid()); |
2173 | + QSignalSpy ready_spy(j.get(), &qt::ItemListJob::itemsReady); |
2174 | + assert(ready_spy.wait(SIGNAL_WAIT_TIME)); |
2175 | + auto arg = ready_spy.takeFirst(); |
2176 | + auto items = qvariant_cast<QList<qt::Item>>(arg.at(0)); |
2177 | + assert(items.size() == 1); |
2178 | + return items[0]; |
2179 | +} |
2180 | + |
2181 | +QList<qt::Item> get_items(qt::ItemListJob *job) |
2182 | +{ |
2183 | + QList<qt::Item> items; |
2184 | + auto connection = QObject::connect( |
2185 | + job, &qt::ItemListJob::itemsReady, |
2186 | + [&](QList<qt::Item> const& new_items) |
2187 | + { |
2188 | + items.append(new_items); |
2189 | + }); |
2190 | + try |
2191 | + { |
2192 | + wait(job); |
2193 | + } |
2194 | + catch (...) |
2195 | + { |
2196 | + QObject::disconnect(connection); |
2197 | + throw; |
2198 | + } |
2199 | + QObject::disconnect(connection); |
2200 | + return items; |
2201 | +} |
2202 | + |
2203 | +const string file_contents = |
2204 | + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " |
2205 | + "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut " |
2206 | + "enim ad minim veniam, quis nostrud exercitation ullamco laboris " |
2207 | + "nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor " |
2208 | + "in reprehenderit in voluptate velit esse cillum dolore eu fugiat " |
2209 | + "nulla pariatur. Excepteur sint occaecat cupidatat non proident, " |
2210 | + "sunt in culpa qui officia deserunt mollit anim id est laborum.\n"; |
2211 | + |
2212 | +} // namespace |
2213 | + |
2214 | +TEST(Directories, env_vars) |
2215 | +{ |
2216 | + // These tests cause the constructor to throw, so we instantiate the provider directly. |
2217 | + |
2218 | + { |
2219 | + EnvVarGuard env("SF_LOCAL_PROVIDER_ROOT", "/no_such_dir"); |
2220 | + |
2221 | + try |
2222 | + { |
2223 | + LocalProvider(); |
2224 | + FAIL(); |
2225 | + } |
2226 | + catch (provider::InvalidArgumentException const& e) |
2227 | + { |
2228 | + EXPECT_STREQ("InvalidArgumentException: LocalProvider(): Environment variable " |
2229 | + "SF_LOCAL_PROVIDER_ROOT must denote an existing directory", |
2230 | + e.what()); |
2231 | + } |
2232 | + } |
2233 | + |
2234 | + { |
2235 | + EnvVarGuard env("SF_LOCAL_PROVIDER_ROOT", TEST_DIR "/Makefile"); |
2236 | + |
2237 | + try |
2238 | + { |
2239 | + LocalProvider(); |
2240 | + FAIL(); |
2241 | + } |
2242 | + catch (provider::InvalidArgumentException const& e) |
2243 | + { |
2244 | + EXPECT_STREQ("InvalidArgumentException: LocalProvider(): Environment variable " |
2245 | + "SF_LOCAL_PROVIDER_ROOT must denote an existing directory", |
2246 | + e.what()); |
2247 | + } |
2248 | + } |
2249 | + |
2250 | + { |
2251 | + string const dir = TEST_DIR "/noperm"; |
2252 | + |
2253 | + mkdir(dir.c_str(), 0555); |
2254 | + ASSERT_EQ(0, chmod(dir.c_str(), 0555)); // In case dir was there already. |
2255 | + |
2256 | + EnvVarGuard env1("SF_LOCAL_PROVIDER_ROOT", nullptr); |
2257 | + EnvVarGuard env2("XDG_DATA_HOME", dir.c_str()); |
2258 | + |
2259 | + using namespace boost::filesystem; |
2260 | + |
2261 | + try |
2262 | + { |
2263 | + LocalProvider(); |
2264 | + ASSERT_EQ(0, chmod(dir.c_str(), 0775)); |
2265 | + remove_all(dir); |
2266 | + FAIL(); |
2267 | + } |
2268 | + catch (provider::PermissionException const& e) |
2269 | + { |
2270 | + EXPECT_EQ(string("PermissionException: LocalProvider(): \"") + dir + "/storage-framework\": " |
2271 | + "boost::filesystem::create_directories: Permission denied: \"" + dir + "/storage-framework\"", |
2272 | + e.what()); |
2273 | + } |
2274 | + |
2275 | + ASSERT_EQ(0, chmod(dir.c_str(), 0775)); |
2276 | + ASSERT_TRUE(remove_all(dir)); |
2277 | + |
2278 | + // Try again, which must succeed now (for coverage). |
2279 | + LocalProvider(); |
2280 | + ASSERT_TRUE(is_directory(dir + "/storage-framework/local")); |
2281 | + ASSERT_TRUE(remove_all(dir)); |
2282 | + } |
2283 | + |
2284 | + { |
2285 | + string const dir = TEST_DIR "/snap_user_common"; |
2286 | + mkdir(dir.c_str(), 0775); |
2287 | + |
2288 | + EnvVarGuard env1("SF_LOCAL_PROVIDER_ROOT", nullptr); |
2289 | + EnvVarGuard env2("SNAP_USER_COMMON", dir.c_str()); |
2290 | + |
2291 | + using namespace boost::filesystem; |
2292 | + |
2293 | + LocalProvider(); |
2294 | + ASSERT_TRUE(exists(dir + "/storage-framework/local")); |
2295 | + ASSERT_TRUE(remove_all(dir)); |
2296 | + } |
2297 | +} |
2298 | + |
2299 | +TEST_F(LocalProviderTest, basic) |
2300 | +{ |
2301 | + using namespace unity::storage::qt; |
2302 | + |
2303 | + set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider)); |
2304 | + |
2305 | + // Basic sanity check, get the root. |
2306 | + unique_ptr<ItemListJob> j(acc_.roots()); |
2307 | + EXPECT_TRUE(j->isValid()); |
2308 | + QSignalSpy ready_spy(j.get(), &ItemListJob::itemsReady); |
2309 | + ASSERT_TRUE(ready_spy.wait(SIGNAL_WAIT_TIME)); |
2310 | + ASSERT_EQ(1, ready_spy.count()); |
2311 | + auto arg = ready_spy.takeFirst(); |
2312 | + auto items = qvariant_cast<QList<Item>>(arg.at(0)); |
2313 | + ASSERT_EQ(1, items.size()); |
2314 | + |
2315 | + // Check contents of returned item. |
2316 | + auto root = items[0]; |
2317 | + EXPECT_TRUE(root.isValid()); |
2318 | + EXPECT_EQ(Item::Type::Root, root.type()); |
2319 | + EXPECT_EQ(ROOT_DIR, root.itemId().toStdString()); |
2320 | + EXPECT_EQ("/", root.name()); |
2321 | + EXPECT_EQ("", root.etag()); |
2322 | + EXPECT_EQ(QList<QString>(), root.parentIds()); |
2323 | + qDebug() << root.lastModifiedTime(); |
2324 | + EXPECT_TRUE(root.lastModifiedTime().isValid()); |
2325 | + EXPECT_EQ(acc_, root.account()); |
2326 | + |
2327 | + ASSERT_EQ(5, root.metadata().size()); |
2328 | + auto free_space_bytes = root.metadata().value("free_space_bytes").toULongLong(); |
2329 | + cout << "free_space_bytes: " << free_space_bytes << endl; |
2330 | + EXPECT_GT(free_space_bytes, 0); |
2331 | + auto used_space_bytes = root.metadata().value("used_space_bytes").toULongLong(); |
2332 | + cout << "used_space_bytes: " << used_space_bytes << endl; |
2333 | + EXPECT_GT(used_space_bytes, 0); |
2334 | + auto content_type = root.metadata().value("content_type").toString(); |
2335 | + EXPECT_EQ("inode/directory", content_type); |
2336 | + auto writable = root.metadata().value("writable").toBool(); |
2337 | + EXPECT_TRUE(writable); |
2338 | + |
2339 | + // yyyy-mm-ddThh:mm:ssZ |
2340 | + 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$"; |
2341 | + string mtime = root.metadata().value("last_modified_time").toString().toStdString(); |
2342 | + cout << "last_modified_time: " << mtime << endl; |
2343 | + regex re(date_time_fmt); |
2344 | + EXPECT_TRUE(regex_match(mtime, re)); |
2345 | +} |
2346 | + |
2347 | +TEST_F(LocalProviderTest, create_folder) |
2348 | +{ |
2349 | + { |
2350 | + using namespace unity::storage::qt; |
2351 | + |
2352 | + set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider)); |
2353 | + |
2354 | + auto root = get_root(acc_); |
2355 | + unique_ptr<ItemJob> job(root.createFolder("child")); |
2356 | + wait(job.get()); |
2357 | + ASSERT_EQ(ItemJob::Finished, job->status()) << job->error().errorString().toStdString(); |
2358 | + |
2359 | + Item child = job->item(); |
2360 | + EXPECT_EQ(ROOT_DIR + "/child", child.itemId().toStdString()); |
2361 | + EXPECT_EQ("child", child.name().toStdString()); |
2362 | + ASSERT_EQ(1, child.parentIds().size()); |
2363 | + EXPECT_EQ(ROOT_DIR, child.parentIds().at(0).toStdString()); |
2364 | + EXPECT_EQ("", child.etag()); |
2365 | + EXPECT_EQ(Item::Type::Folder, child.type()); |
2366 | + EXPECT_EQ(5, child.metadata().size()); |
2367 | + |
2368 | + struct stat st; |
2369 | + ASSERT_EQ(0, stat(child.itemId().toStdString().c_str(), &st)); |
2370 | + EXPECT_TRUE(S_ISDIR(st.st_mode)); |
2371 | + |
2372 | + // Again, to get coverage for a StorageException caught in invoke_async(). |
2373 | + job.reset(root.createFolder("child")); |
2374 | + wait(job.get()); |
2375 | + ASSERT_EQ(ItemJob::Error, job->status()) << job->error().errorString().toStdString(); |
2376 | + EXPECT_EQ(string("Exists: create_folder(): \"") + ROOT_DIR + "/child\" exists already", |
2377 | + job->error().errorString().toStdString()); |
2378 | + |
2379 | + // Again, without write permission on the root dir, to get coverage for a filesystem_error in invoke_async(). |
2380 | + ASSERT_EQ(0, ::rmdir((ROOT_DIR + "/child").c_str())); |
2381 | + ASSERT_EQ(0, ::chmod(ROOT_DIR.c_str(), 0555)); |
2382 | + job.reset(root.createFolder("child")); |
2383 | + wait(job.get()); |
2384 | + ::chmod(ROOT_DIR.c_str(), 0755); |
2385 | + ASSERT_EQ(ItemJob::Error, job->status()) << job->error().errorString().toStdString(); |
2386 | + EXPECT_EQ(string("PermissionDenied: create_folder(): \"") + ROOT_DIR |
2387 | + + "/child\": boost::filesystem::create_directory: Permission denied: \"" + ROOT_DIR + "/child\"", |
2388 | + job->error().errorString().toStdString()); |
2389 | + } |
2390 | +} |
2391 | + |
2392 | +TEST_F(LocalProviderTest, delete_item) |
2393 | +{ |
2394 | + { |
2395 | + using namespace unity::storage::qt; |
2396 | + |
2397 | + set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider)); |
2398 | + |
2399 | + auto root = get_root(acc_); |
2400 | + unique_ptr<ItemJob> job(root.createFolder("child")); |
2401 | + wait(job.get()); |
2402 | + ASSERT_EQ(ItemJob::Finished, job->status()) << job->error().errorString().toStdString(); |
2403 | + |
2404 | + Item child = job->item(); |
2405 | + unique_ptr<VoidJob> delete_job(child.deleteItem()); |
2406 | + wait(delete_job.get()); |
2407 | + ASSERT_EQ(ItemJob::Finished, delete_job->status()) << delete_job->error().errorString().toStdString(); |
2408 | + |
2409 | + struct stat st; |
2410 | + ASSERT_EQ(-1, stat(child.itemId().toStdString().c_str(), &st)); |
2411 | + EXPECT_EQ(ENOENT, errno); |
2412 | + } |
2413 | +} |
2414 | + |
2415 | +TEST_F(LocalProviderTest, delete_item_noperm) |
2416 | +{ |
2417 | + { |
2418 | + using namespace unity::storage::qt; |
2419 | + |
2420 | + set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider)); |
2421 | + |
2422 | + auto root = get_root(acc_); |
2423 | + unique_ptr<ItemJob> job(root.createFolder("child")); |
2424 | + wait(job.get()); |
2425 | + ASSERT_EQ(ItemJob::Finished, job->status()) << job->error().errorString().toStdString(); |
2426 | + } |
2427 | +} |
2428 | + |
2429 | +TEST_F(LocalProviderTest, delete_root) |
2430 | +{ |
2431 | + // Client-side API does not allow us to try to delete the root, so we talk to the provider directly. |
2432 | + auto p = make_shared<LocalProvider>(); |
2433 | + |
2434 | + auto fut = p->delete_item(ROOT_DIR, provider::Context()); |
2435 | + try |
2436 | + { |
2437 | + fut.get(); |
2438 | + FAIL(); |
2439 | + } |
2440 | + catch (provider::LogicException const& e) |
2441 | + { |
2442 | + EXPECT_STREQ("LogicException: delete_item(): cannot delete root", e.what()); |
2443 | + } |
2444 | +} |
2445 | + |
2446 | +TEST_F(LocalProviderTest, metadata) |
2447 | +{ |
2448 | + // Client-side API does not call the Metadata DBus method (except as part of parents()), |
2449 | + // so we talk to the provider directly. |
2450 | + auto p = make_shared<LocalProvider>(); |
2451 | + |
2452 | + auto fut = p->metadata(ROOT_DIR, {}, provider::Context()); |
2453 | + auto item = fut.get(); |
2454 | + EXPECT_EQ(5, item.metadata.size()); |
2455 | + |
2456 | + // Again, to get coverage for the "not file or folder" case in make_item(). |
2457 | + ASSERT_EQ(0, mknod((ROOT_DIR + "/pipe").c_str(), S_IFIFO | 06666, 0)); |
2458 | + try |
2459 | + { |
2460 | + auto fut = p->metadata(ROOT_DIR + "/pipe", {}, provider::Context()); |
2461 | + fut.get(); |
2462 | + FAIL(); |
2463 | + } |
2464 | + catch (provider::NotExistsException const& e) |
2465 | + { |
2466 | + EXPECT_EQ(string("NotExistsException: metadata(): \"") + ROOT_DIR + "/pipe\" is neither a file nor a folder", |
2467 | + e.what()); |
2468 | + } |
2469 | +} |
2470 | + |
2471 | +TEST_F(LocalProviderTest, lookup) |
2472 | +{ |
2473 | + using namespace unity::storage::qt; |
2474 | + |
2475 | + set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider)); |
2476 | + |
2477 | + auto root = get_root(acc_); |
2478 | + { |
2479 | + unique_ptr<ItemJob> job(root.createFolder("child")); |
2480 | + wait(job.get()); |
2481 | + ASSERT_EQ(ItemJob::Finished, job->status()) << job->error().errorString().toStdString(); |
2482 | + } |
2483 | + |
2484 | + unique_ptr<ItemListJob> job(root.lookup("child")); |
2485 | + auto items = get_items(job.get()); |
2486 | + ASSERT_EQ(1, items.size()); |
2487 | + auto child = items.at(0); |
2488 | + |
2489 | + EXPECT_EQ(ROOT_DIR + "/child", child.itemId().toStdString()); |
2490 | + EXPECT_EQ("child", child.name().toStdString()); |
2491 | + ASSERT_EQ(1, child.parentIds().size()); |
2492 | + EXPECT_EQ(ROOT_DIR, child.parentIds().at(0).toStdString()); |
2493 | + EXPECT_EQ("", child.etag()); |
2494 | + EXPECT_EQ(Item::Type::Folder, child.type()); |
2495 | + EXPECT_EQ(5, child.metadata().size()); |
2496 | + |
2497 | + // Remove the child again and try the lookup once more. |
2498 | + ASSERT_EQ(0, rmdir((ROOT_DIR + "/child").c_str())); |
2499 | + |
2500 | + job.reset(root.lookup("child")); |
2501 | + wait(job.get()); |
2502 | + EXPECT_EQ(ItemJob::Error, job->status()); |
2503 | + EXPECT_EQ(string("NotExists: lookup(): \"") + ROOT_DIR + "/child\": boost::filesystem::canonical: " |
2504 | + + "No such file or directory: \"" + ROOT_DIR + "/child\"", |
2505 | + job->error().errorString().toStdString()); |
2506 | +} |
2507 | + |
2508 | +TEST_F(LocalProviderTest, list) |
2509 | +{ |
2510 | + using namespace unity::storage::qt; |
2511 | + |
2512 | + set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider)); |
2513 | + |
2514 | + auto root = get_root(acc_); |
2515 | + { |
2516 | + unique_ptr<ItemJob> job(root.createFolder("child")); |
2517 | + wait(job.get()); |
2518 | + ASSERT_EQ(ItemJob::Finished, job->status()) << job->error().errorString().toStdString(); |
2519 | + } |
2520 | + |
2521 | + // Make a weird item that will be ignored (for coverage). |
2522 | + ASSERT_EQ(0, mknod((ROOT_DIR + "/pipe").c_str(), S_IFIFO | 06666, 0)); |
2523 | + |
2524 | + // Make a file that starts with the temp file prefix (for coverage). |
2525 | + int fd = creat((ROOT_DIR + "/.storage-framework").c_str(), 0755); |
2526 | + ASSERT_GT(fd, 0); |
2527 | + close(fd); |
2528 | + |
2529 | + unique_ptr<ItemListJob> job(root.list()); |
2530 | + auto items = get_items(job.get()); |
2531 | + ASSERT_EQ(1, items.size()); |
2532 | + auto child = items.at(0); |
2533 | + |
2534 | + EXPECT_EQ(ROOT_DIR + "/child", child.itemId().toStdString()); |
2535 | + EXPECT_EQ("child", child.name().toStdString()); |
2536 | + ASSERT_EQ(1, child.parentIds().size()); |
2537 | + EXPECT_EQ(ROOT_DIR, child.parentIds().at(0).toStdString()); |
2538 | + EXPECT_EQ("", child.etag()); |
2539 | + EXPECT_EQ(Item::Type::Folder, child.type()); |
2540 | + EXPECT_EQ(5, child.metadata().size()); |
2541 | +} |
2542 | + |
2543 | +void make_hierarchy() |
2544 | +{ |
2545 | + // Make a small tree so we have something to test with for move() and copy(). |
2546 | + ASSERT_EQ(0, mkdir((ROOT_DIR + "/a").c_str(), 0755)); |
2547 | + ASSERT_EQ(0, mkdir((ROOT_DIR + "/a/b").c_str(), 0755)); |
2548 | + string cmd = string("echo hello >") + ROOT_DIR + "/hello"; |
2549 | + ASSERT_EQ(0, system(cmd.c_str())); |
2550 | + cmd = string("echo text >") + ROOT_DIR + "/a/foo.txt"; |
2551 | + ASSERT_EQ(0, system(cmd.c_str())); |
2552 | + ASSERT_EQ(0, mknod((ROOT_DIR + "/a/pipe").c_str(), S_IFIFO | 06666, 0)); |
2553 | + ASSERT_EQ(0, mkdir((ROOT_DIR + "/a/.storage-framework-").c_str(), 0755)); |
2554 | + ASSERT_EQ(0, mkdir((ROOT_DIR + "/a/b/.storage-framework-").c_str(), 0755)); |
2555 | + ASSERT_EQ(0, mknod((ROOT_DIR + "/a/b/pipe").c_str(), S_IFIFO | 06666, 0)); |
2556 | +} |
2557 | + |
2558 | +TEST_F(LocalProviderTest, move) |
2559 | +{ |
2560 | + using namespace unity::storage::qt; |
2561 | + |
2562 | + set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider)); |
2563 | + |
2564 | + auto start_time = nanosecs_now(); |
2565 | + |
2566 | + make_hierarchy(); |
2567 | + |
2568 | + auto root = get_root(acc_); |
2569 | + |
2570 | + qt::Item hello; |
2571 | + { |
2572 | + unique_ptr<ItemListJob> job(root.lookup("hello")); |
2573 | + auto items = get_items(job.get()); |
2574 | + ASSERT_EQ(ItemListJob::Finished, job->status()) << job->error().errorString().toStdString(); |
2575 | + ASSERT_EQ(1, items.size()); |
2576 | + hello = items.at(0); |
2577 | + } |
2578 | + |
2579 | + struct stat st; |
2580 | + ASSERT_EQ(0, stat(hello.itemId().toStdString().c_str(), &st)); |
2581 | + auto old_ino = st.st_ino; |
2582 | + |
2583 | + // Check metadata. |
2584 | + EXPECT_EQ("hello", hello.name()); |
2585 | + ASSERT_EQ(1, hello.parentIds().size()); |
2586 | + EXPECT_EQ(ROOT_DIR, hello.parentIds().at(0).toStdString()); |
2587 | + EXPECT_EQ(Item::Type::File, hello.type()); |
2588 | + |
2589 | + ASSERT_EQ(6, hello.metadata().size()); |
2590 | + auto free_space_bytes = hello.metadata().value("free_space_bytes").toLongLong(); |
2591 | + cout << "free_space_bytes: " << free_space_bytes << endl; |
2592 | + EXPECT_GT(free_space_bytes, 0); |
2593 | + auto used_space_bytes = hello.metadata().value("used_space_bytes").toLongLong(); |
2594 | + cout << "used_space_bytes: " << used_space_bytes << endl; |
2595 | + EXPECT_GT(used_space_bytes, 0); |
2596 | + auto content_type = hello.metadata().value("content_type").toString(); |
2597 | + EXPECT_EQ("application/octet-stream", content_type); |
2598 | + auto writable = hello.metadata().value("writable").toBool(); |
2599 | + EXPECT_TRUE(writable); |
2600 | + auto size = hello.metadata().value("size_in_bytes").toLongLong(); |
2601 | + EXPECT_EQ(6, size); |
2602 | + |
2603 | + // yyyy-mm-ddThh:mm:ssZ |
2604 | + 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$"; |
2605 | + string date_time = hello.metadata().value("last_modified_time").toString().toStdString(); |
2606 | + cout << "last_modified_time: " << date_time << endl; |
2607 | + regex re(date_time_fmt); |
2608 | + EXPECT_TRUE(regex_match(date_time, re)); |
2609 | + |
2610 | + // Check that the file was modified in the last two seconds. |
2611 | + // Because the system clock can tick a lot more frequently than the file system time stamp, |
2612 | + // we allow the mtime to be up to one second *earlier* than the time we started the operation. |
2613 | + string mtime_str = hello.etag().toStdString(); |
2614 | + char* end; |
2615 | + int64_t mtime = strtoll(mtime_str.c_str(), &end, 10); |
2616 | + EXPECT_LE(start_time - 1000000000, mtime); |
2617 | + EXPECT_LT(mtime, start_time + 2000000000); |
2618 | + |
2619 | + // Move hello -> world |
2620 | + qt::Item world; |
2621 | + { |
2622 | + unique_ptr<ItemJob> job(hello.move(root, "world")); |
2623 | + wait(job.get()); |
2624 | + ASSERT_EQ(ItemJob::Finished, job->status()) << job->error().errorString().toStdString(); |
2625 | + world = job->item(); |
2626 | + } |
2627 | + EXPECT_FALSE(boost::filesystem::exists(hello.itemId().toStdString())); |
2628 | + EXPECT_EQ(ROOT_DIR + "/world", world.itemId().toStdString()); |
2629 | + |
2630 | + ASSERT_EQ(0, stat(world.itemId().toStdString().c_str(), &st)); |
2631 | + auto new_ino = st.st_ino; |
2632 | + EXPECT_EQ(old_ino, new_ino); |
2633 | + |
2634 | + // For coverage: try moving world -> a (which must fail) |
2635 | + unique_ptr<ItemJob> job(world.move(root, "a")); |
2636 | + wait(job.get()); |
2637 | + ASSERT_EQ(ItemJob::Error, job->status()) << job->error().errorString().toStdString(); |
2638 | + EXPECT_EQ(string("Exists: move(): \"") + ROOT_DIR + "/a\" exists already", |
2639 | + job->error().errorString().toStdString()); |
2640 | +} |
2641 | + |
2642 | +TEST_F(LocalProviderTest, copy_file) |
2643 | +{ |
2644 | + using namespace unity::storage::qt; |
2645 | + |
2646 | + set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider)); |
2647 | + |
2648 | + make_hierarchy(); |
2649 | + |
2650 | + auto root = get_root(acc_); |
2651 | + |
2652 | + // Copy hello -> world |
2653 | + qt::Item hello; |
2654 | + { |
2655 | + unique_ptr<ItemListJob> job(root.lookup("hello")); |
2656 | + auto items = get_items(job.get()); |
2657 | + ASSERT_EQ(ItemListJob::Finished, job->status()) << job->error().errorString().toStdString(); |
2658 | + ASSERT_EQ(1, items.size()); |
2659 | + hello = items.at(0); |
2660 | + } |
2661 | + |
2662 | + struct stat st; |
2663 | + ASSERT_EQ(0, stat(hello.itemId().toStdString().c_str(), &st)); |
2664 | + auto old_ino = st.st_ino; |
2665 | + |
2666 | + qt::Item world; |
2667 | + { |
2668 | + unique_ptr<ItemJob> job(hello.copy(root, "world")); |
2669 | + wait(job.get()); |
2670 | + ASSERT_EQ(ItemJob::Finished, job->status()) << job->error().errorString().toStdString(); |
2671 | + world = job->item(); |
2672 | + } |
2673 | + EXPECT_TRUE(boost::filesystem::exists(hello.itemId().toStdString())); |
2674 | + EXPECT_EQ(ROOT_DIR + "/world", world.itemId().toStdString()); |
2675 | + |
2676 | + ASSERT_EQ(0, stat(world.itemId().toStdString().c_str(), &st)); |
2677 | + auto new_ino = st.st_ino; |
2678 | + EXPECT_NE(old_ino, new_ino); |
2679 | +} |
2680 | + |
2681 | +TEST_F(LocalProviderTest, copy_tree) |
2682 | +{ |
2683 | + using namespace unity::storage::qt; |
2684 | + using namespace boost::filesystem; |
2685 | + |
2686 | + set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider)); |
2687 | + |
2688 | + make_hierarchy(); |
2689 | + |
2690 | + auto root = get_root(acc_); |
2691 | + |
2692 | + // Copy a -> c |
2693 | + qt::Item a; |
2694 | + { |
2695 | + unique_ptr<ItemListJob> job(root.lookup("a")); |
2696 | + auto items = get_items(job.get()); |
2697 | + ASSERT_EQ(ItemListJob::Finished, job->status()) << job->error().errorString().toStdString(); |
2698 | + ASSERT_EQ(1, items.size()); |
2699 | + a = items.at(0); |
2700 | + } |
2701 | + |
2702 | + qt::Item c; |
2703 | + { |
2704 | + unique_ptr<ItemJob> job(a.copy(root, "c")); |
2705 | + wait(job.get()); |
2706 | + ASSERT_EQ(ItemJob::Finished, job->status()) << job->error().errorString().toStdString(); |
2707 | + c = job->item(); |
2708 | + } |
2709 | + EXPECT_TRUE(exists(c.itemId().toStdString())); |
2710 | + |
2711 | + // Check that we only copied regular files and directories, but not a pipe or anything starting with |
2712 | + // the temp file prefix. |
2713 | + EXPECT_TRUE(exists(ROOT_DIR + "/c/b")); |
2714 | + EXPECT_TRUE(exists(ROOT_DIR + "/c/foo.txt")); |
2715 | + EXPECT_FALSE(exists(ROOT_DIR + "/c/pipe")); |
2716 | + EXPECT_FALSE(exists(ROOT_DIR + "/c/storage-framework-")); |
2717 | + EXPECT_FALSE(exists(ROOT_DIR + "/c/b/pipe")); |
2718 | + EXPECT_FALSE(exists(ROOT_DIR + "/c/b/storage-framework-")); |
2719 | + |
2720 | + // Copy c -> a. This must fail because a exists. |
2721 | + { |
2722 | + unique_ptr<ItemJob> job(c.copy(root, "a")); |
2723 | + wait(job.get()); |
2724 | + ASSERT_EQ(ItemJob::Error, job->status()) << job->error().errorString().toStdString(); |
2725 | + EXPECT_EQ(string("Exists: copy(): \"") + ROOT_DIR + "/a\" exists already", |
2726 | + job->error().errorString().toStdString()); |
2727 | + } |
2728 | +} |
2729 | + |
2730 | +TEST_F(LocalProviderTest, download) |
2731 | +{ |
2732 | + using namespace unity::storage::qt; |
2733 | + |
2734 | + set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider)); |
2735 | + |
2736 | + int const segments = 10000; |
2737 | + string large_contents; |
2738 | + large_contents.reserve(file_contents.size() * segments); |
2739 | + for (int i = 0; i < segments; i++) |
2740 | + { |
2741 | + large_contents += file_contents; |
2742 | + } |
2743 | + string const full_path = ROOT_DIR + "/foo.txt"; |
2744 | + { |
2745 | + int fd = open(full_path.c_str(), O_WRONLY | O_CREAT | O_EXCL, 0644); |
2746 | + ASSERT_GT(fd, 0); |
2747 | + ASSERT_EQ(ssize_t(large_contents.size()), write(fd, &large_contents[0], large_contents.size())) << strerror(errno); |
2748 | + ASSERT_EQ(0, close(fd)); |
2749 | + } |
2750 | + |
2751 | + unique_ptr<ItemJob> job(acc_.get(QString::fromStdString(full_path))); |
2752 | + wait(job.get()); |
2753 | + EXPECT_TRUE(job->isValid()); |
2754 | + |
2755 | + auto file = job->item(); |
2756 | + unique_ptr<Downloader> downloader(file.createDownloader(Item::ErrorIfConflict)); |
2757 | + |
2758 | + int64_t n_read = 0; |
2759 | + QObject::connect(downloader.get(), &QIODevice::readyRead, |
2760 | + [&]() { |
2761 | + auto bytes = downloader->readAll(); |
2762 | + string const expected = large_contents.substr(n_read, bytes.size()); |
2763 | + EXPECT_EQ(expected, bytes.toStdString()); |
2764 | + n_read += bytes.size(); |
2765 | + }); |
2766 | + QSignalSpy read_finished_spy(downloader.get(), &QIODevice::readChannelFinished); |
2767 | + ASSERT_TRUE(read_finished_spy.wait(SIGNAL_WAIT_TIME)); |
2768 | + |
2769 | + QSignalSpy status_spy(downloader.get(), &Downloader::statusChanged); |
2770 | + downloader->close(); |
2771 | + while (downloader->status() == Downloader::Ready) |
2772 | + { |
2773 | + ASSERT_TRUE(status_spy.wait(SIGNAL_WAIT_TIME)); |
2774 | + } |
2775 | + ASSERT_EQ(Downloader::Finished, downloader->status()) << downloader->error().errorString().toStdString(); |
2776 | + |
2777 | + EXPECT_EQ(int64_t(large_contents.size()), n_read); |
2778 | +} |
2779 | + |
2780 | +TEST_F(LocalProviderTest, download_short_read) |
2781 | +{ |
2782 | + using namespace unity::storage::qt; |
2783 | + |
2784 | + set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider)); |
2785 | + |
2786 | + int const segments = 10000; |
2787 | + string const full_path = ROOT_DIR + "/foo.txt"; |
2788 | + { |
2789 | + int fd = open(full_path.c_str(), O_WRONLY | O_CREAT | O_EXCL, 0644); |
2790 | + ASSERT_GT(fd, 0); |
2791 | + for (int i = 0; i < segments; i++) |
2792 | + { |
2793 | + ASSERT_EQ(ssize_t(file_contents.size()), write(fd, &file_contents[0], file_contents.size())) << strerror(errno); |
2794 | + } |
2795 | + ASSERT_EQ(0, close(fd)); |
2796 | + } |
2797 | + |
2798 | + unique_ptr<ItemJob> job(acc_.get(QString::fromStdString(full_path))); |
2799 | + wait(job.get()); |
2800 | + ASSERT_EQ(ItemJob::Finished, job->status()) << job->error().errorString().toStdString(); |
2801 | + |
2802 | + auto file = job->item(); |
2803 | + unique_ptr<Downloader> downloader(file.createDownloader(Item::ErrorIfConflict)); |
2804 | + |
2805 | + QSignalSpy spy(downloader.get(), &Downloader::statusChanged); |
2806 | + while (downloader->status() == Downloader::Loading) |
2807 | + { |
2808 | + ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); |
2809 | + } |
2810 | + |
2811 | + downloader->close(); |
2812 | + while (downloader->status() == Downloader::Ready) |
2813 | + { |
2814 | + ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); |
2815 | + } |
2816 | + ASSERT_EQ(Downloader::Error, downloader->status()) << downloader->error().errorString().toStdString(); |
2817 | + |
2818 | + auto error = downloader->error(); |
2819 | + EXPECT_EQ(qt::StorageError::LogicError, error.type()); |
2820 | + cout << error.message().toStdString() << endl; |
2821 | + EXPECT_TRUE(boost::starts_with(error.message().toStdString(), |
2822 | + "finish() method called too early, file \"" |
2823 | + + full_path + "\" has size 4460000 but only")); |
2824 | +} |
2825 | + |
2826 | +TEST_F(LocalProviderTest, download_etag_mismatch) |
2827 | +{ |
2828 | + using namespace unity::storage::qt; |
2829 | + |
2830 | + set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider)); |
2831 | + |
2832 | + string const full_path = ROOT_DIR + "/foo.txt"; |
2833 | + string cmd = string("echo hello >") + full_path; |
2834 | + ASSERT_EQ(0, system(cmd.c_str())); |
2835 | + |
2836 | + unique_ptr<ItemJob> job(acc_.get(QString::fromStdString(full_path))); |
2837 | + wait(job.get()); |
2838 | + ASSERT_EQ(ItemJob::Finished, job->status()) << job->error().errorString().toStdString(); |
2839 | + |
2840 | + auto file = job->item(); |
2841 | + |
2842 | + sleep(1); |
2843 | + cmd = string("touch ") + full_path; |
2844 | + ASSERT_EQ(0, system(cmd.c_str())); |
2845 | + |
2846 | + unique_ptr<Downloader> downloader(file.createDownloader(Item::ErrorIfConflict)); |
2847 | + |
2848 | + QSignalSpy spy(downloader.get(), &Downloader::statusChanged); |
2849 | + while (downloader->status() != Downloader::Error) |
2850 | + { |
2851 | + ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); |
2852 | + } |
2853 | + |
2854 | + auto error = downloader->error(); |
2855 | + EXPECT_EQ(qt::StorageError::Conflict, error.type()); |
2856 | + EXPECT_EQ("download(): etag mismatch", error.message().toStdString()); |
2857 | +} |
2858 | + |
2859 | +TEST_F(LocalProviderTest, download_wrong_file_type) |
2860 | +{ |
2861 | + // We can't try a download for a directory via the client API, so we use the LocalDownloadJob directly. |
2862 | + |
2863 | + auto p = make_shared<LocalProvider>(); |
2864 | + |
2865 | + string const dir = ROOT_DIR + "/dir"; |
2866 | + ASSERT_EQ(0, mkdir(dir.c_str(), 0755)); |
2867 | + |
2868 | + try |
2869 | + { |
2870 | + LocalDownloadJob(p, dir, "some_etag"); |
2871 | + FAIL(); |
2872 | + } |
2873 | + catch (provider::InvalidArgumentException const& e) |
2874 | + { |
2875 | + EXPECT_EQ(string("InvalidArgumentException: download(): \"" + dir + "\" is not a file"), e.what()); |
2876 | + } |
2877 | +} |
2878 | + |
2879 | +TEST_F(LocalProviderTest, download_no_permission) |
2880 | +{ |
2881 | + using namespace unity::storage::qt; |
2882 | + |
2883 | + set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider)); |
2884 | + |
2885 | + string const full_path = ROOT_DIR + "/foo.txt"; |
2886 | + string cmd = string("echo hello >") + full_path; |
2887 | + ASSERT_EQ(0, system(cmd.c_str())); |
2888 | + |
2889 | + unique_ptr<ItemJob> job(acc_.get(QString::fromStdString(full_path))); |
2890 | + wait(job.get()); |
2891 | + ASSERT_EQ(ItemJob::Finished, job->status()) << job->error().errorString().toStdString(); |
2892 | + |
2893 | + auto file = job->item(); |
2894 | + |
2895 | + ASSERT_EQ(0, chmod(full_path.c_str(), 0244)); |
2896 | + |
2897 | + unique_ptr<Downloader> downloader(file.createDownloader(Item::ErrorIfConflict)); |
2898 | + |
2899 | + QSignalSpy spy(downloader.get(), &Downloader::statusChanged); |
2900 | + while (downloader->status() != Downloader::Error) |
2901 | + { |
2902 | + ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); |
2903 | + } |
2904 | + |
2905 | + auto error = downloader->error(); |
2906 | + EXPECT_EQ(qt::StorageError::ResourceError, error.type()); |
2907 | + EXPECT_EQ(string("download(): : cannot open \"") + full_path + "\": Permission denied (QFileDevice::FileError = 5)", |
2908 | + error.message().toStdString()); |
2909 | +} |
2910 | + |
2911 | +TEST_F(LocalProviderTest, download_no_such_file) |
2912 | +{ |
2913 | + // We can't try a download for a non-existent file via the client API, so we use the LocalDownloadJob directly. |
2914 | + |
2915 | + auto p = make_shared<LocalProvider>(); |
2916 | + |
2917 | + try |
2918 | + { |
2919 | + LocalDownloadJob(p, ROOT_DIR + "/no_such_file", "some_etag"); |
2920 | + FAIL(); |
2921 | + } |
2922 | + catch (provider::NotExistsException const&) |
2923 | + { |
2924 | + } |
2925 | +} |
2926 | + |
2927 | +TEST_F(LocalProviderTest, update) |
2928 | +{ |
2929 | + using namespace unity::storage::qt; |
2930 | + |
2931 | + set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider)); |
2932 | + |
2933 | + auto full_path = ROOT_DIR + "/foo.txt"; |
2934 | + auto cmd = string("echo hello >") + full_path; |
2935 | + ASSERT_EQ(0, system(cmd.c_str())); |
2936 | + |
2937 | + unique_ptr<ItemJob> job(acc_.get(QString::fromStdString(full_path))); |
2938 | + wait(job.get()); |
2939 | + EXPECT_TRUE(job->isValid()); |
2940 | + |
2941 | + auto file = job->item(); |
2942 | + auto old_etag = file.etag(); |
2943 | + |
2944 | + int const segments = 50; |
2945 | + unique_ptr<Uploader> uploader(file.createUploader(Item::ErrorIfConflict, file_contents.size() * segments)); |
2946 | + |
2947 | + int count = 0; |
2948 | + QTimer timer; |
2949 | + timer.setSingleShot(false); |
2950 | + timer.setInterval(10); |
2951 | + QObject::connect(&timer, &QTimer::timeout, [&] { |
2952 | + uploader->write(&file_contents[0], file_contents.size()); |
2953 | + count++; |
2954 | + if (count == segments) |
2955 | + { |
2956 | + uploader->close(); |
2957 | + } |
2958 | + }); |
2959 | + |
2960 | + QSignalSpy spy(uploader.get(), &Uploader::statusChanged); |
2961 | + timer.start(); |
2962 | + while (uploader->status() == Uploader::Loading || |
2963 | + uploader->status() == Uploader::Ready) |
2964 | + { |
2965 | + ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); |
2966 | + } |
2967 | + ASSERT_EQ(Uploader::Finished, uploader->status()) << uploader->error().errorString().toStdString(); |
2968 | + |
2969 | + file = uploader->item(); |
2970 | + EXPECT_NE(old_etag, file.etag()); |
2971 | + EXPECT_EQ(int64_t(file_contents.size() * segments), file.sizeInBytes()); |
2972 | +} |
2973 | + |
2974 | +TEST_F(LocalProviderTest, update_empty) |
2975 | +{ |
2976 | + using namespace unity::storage::qt; |
2977 | + |
2978 | + set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider)); |
2979 | + |
2980 | + auto full_path = ROOT_DIR + "/foo.txt"; |
2981 | + auto cmd = string("echo hello >") + full_path; |
2982 | + ASSERT_EQ(0, system(cmd.c_str())); |
2983 | + |
2984 | + unique_ptr<ItemJob> job(acc_.get(QString::fromStdString(full_path))); |
2985 | + wait(job.get()); |
2986 | + EXPECT_TRUE(job->isValid()); |
2987 | + |
2988 | + auto file = job->item(); |
2989 | + auto old_etag = file.etag(); |
2990 | + |
2991 | + sleep(1); // Make sure mtime changes. |
2992 | + unique_ptr<Uploader> uploader(file.createUploader(Item::ErrorIfConflict, 0)); |
2993 | + { |
2994 | + QSignalSpy spy(uploader.get(), &Uploader::statusChanged); |
2995 | + while (uploader->status() != Uploader::Ready) |
2996 | + { |
2997 | + ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); |
2998 | + } |
2999 | + } |
3000 | + |
3001 | + QSignalSpy spy(uploader.get(), &Uploader::statusChanged); |
3002 | + uploader->close(); |
3003 | + while (uploader->status() != Uploader::Finished) |
3004 | + { |
3005 | + ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); |
3006 | + } |
3007 | + |
3008 | + file = uploader->item(); |
3009 | + EXPECT_NE(old_etag, file.etag()); |
3010 | + EXPECT_EQ(int64_t(0), file.sizeInBytes()); |
3011 | +} |
3012 | + |
3013 | +TEST_F(LocalProviderTest, update_cancel) |
3014 | +{ |
3015 | + using namespace unity::storage::qt; |
3016 | + |
3017 | + set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider)); |
3018 | + |
3019 | + auto full_path = ROOT_DIR + "/foo.txt"; |
3020 | + auto cmd = string("echo hello >") + full_path; |
3021 | + ASSERT_EQ(0, system(cmd.c_str())); |
3022 | + |
3023 | + unique_ptr<ItemJob> job(acc_.get(QString::fromStdString(full_path))); |
3024 | + wait(job.get()); |
3025 | + EXPECT_TRUE(job->isValid()); |
3026 | + |
3027 | + auto file = job->item(); |
3028 | + auto old_etag = file.etag(); |
3029 | + |
3030 | + int const segments = 50; |
3031 | + unique_ptr<Uploader> uploader(file.createUploader(Item::ErrorIfConflict, file_contents.size() * segments)); |
3032 | + |
3033 | + int count = 0; |
3034 | + QTimer timer; |
3035 | + timer.setSingleShot(false); |
3036 | + timer.setInterval(10); |
3037 | + QObject::connect(&timer, &QTimer::timeout, [&] { |
3038 | + uploader->write(&file_contents[0], file_contents.size()); |
3039 | + count++; |
3040 | + if (count == segments / 2) |
3041 | + { |
3042 | + uploader->cancel(); |
3043 | + } |
3044 | + else if (count == segments) |
3045 | + { |
3046 | + uploader->close(); |
3047 | + } |
3048 | + }); |
3049 | + |
3050 | + QSignalSpy spy(uploader.get(), &Uploader::statusChanged); |
3051 | + timer.start(); |
3052 | + while (uploader->status() != Uploader::Cancelled) |
3053 | + { |
3054 | + ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); |
3055 | + } |
3056 | +} |
3057 | + |
3058 | +TEST_F(LocalProviderTest, update_file_touched_before_uploading) |
3059 | +{ |
3060 | + using namespace unity::storage::qt; |
3061 | + |
3062 | + set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider)); |
3063 | + |
3064 | + auto full_path = ROOT_DIR + "/foo.txt"; |
3065 | + auto cmd = string("echo hello >") + full_path; |
3066 | + ASSERT_EQ(0, system(cmd.c_str())); |
3067 | + |
3068 | + unique_ptr<ItemJob> job(acc_.get(QString::fromStdString(full_path))); |
3069 | + wait(job.get()); |
3070 | + EXPECT_TRUE(job->isValid()); |
3071 | + |
3072 | + auto file = job->item(); |
3073 | + auto old_etag = file.etag(); |
3074 | + |
3075 | + sleep(1); // Make sure mtime changes. |
3076 | + cmd = string("touch ") + full_path; |
3077 | + ASSERT_EQ(0, system(cmd.c_str())); |
3078 | + unique_ptr<Uploader> uploader(file.createUploader(Item::ErrorIfConflict, 0)); |
3079 | + QSignalSpy spy(uploader.get(), &Uploader::statusChanged); |
3080 | + while (uploader->status() != Uploader::Error) |
3081 | + { |
3082 | + ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); |
3083 | + } |
3084 | + EXPECT_EQ("update(): etag mismatch", uploader->error().message().toStdString()); |
3085 | +} |
3086 | + |
3087 | +TEST_F(LocalProviderTest, update_file_touched_while_uploading) |
3088 | +{ |
3089 | + using namespace unity::storage::qt; |
3090 | + |
3091 | + set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider)); |
3092 | + |
3093 | + auto full_path = ROOT_DIR + "/foo.txt"; |
3094 | + auto cmd = string("echo hello >") + full_path; |
3095 | + ASSERT_EQ(0, system(cmd.c_str())); |
3096 | + |
3097 | + unique_ptr<ItemJob> job(acc_.get(QString::fromStdString(full_path))); |
3098 | + wait(job.get()); |
3099 | + EXPECT_TRUE(job->isValid()); |
3100 | + |
3101 | + auto file = job->item(); |
3102 | + auto old_etag = file.etag(); |
3103 | + |
3104 | + int const segments = 50; |
3105 | + unique_ptr<Uploader> uploader(file.createUploader(Item::ErrorIfConflict, file_contents.size() * segments)); |
3106 | + |
3107 | + int count = 0; |
3108 | + QTimer timer; |
3109 | + timer.setSingleShot(false); |
3110 | + timer.setInterval(10); |
3111 | + QObject::connect(&timer, &QTimer::timeout, [&] { |
3112 | + uploader->write(&file_contents[0], file_contents.size()); |
3113 | + count++; |
3114 | + if (count == segments / 2) |
3115 | + { |
3116 | + sleep(1); |
3117 | + cmd = string("touch ") + full_path; |
3118 | + ASSERT_EQ(0, system(cmd.c_str())); |
3119 | + } |
3120 | + else if (count == segments) |
3121 | + { |
3122 | + uploader->close(); |
3123 | + } |
3124 | + }); |
3125 | + |
3126 | + QSignalSpy spy(uploader.get(), &Uploader::statusChanged); |
3127 | + timer.start(); |
3128 | + while (uploader->status() != Uploader::Error) |
3129 | + { |
3130 | + ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); |
3131 | + } |
3132 | + ASSERT_EQ(Uploader::Error, uploader->status()); |
3133 | + EXPECT_EQ("update(): etag mismatch", uploader->error().message().toStdString()); |
3134 | +} |
3135 | + |
3136 | +TEST_F(LocalProviderTest, update_ignore_etag_mismatch) |
3137 | +{ |
3138 | + using namespace unity::storage::qt; |
3139 | + |
3140 | + set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider)); |
3141 | + |
3142 | + auto full_path = ROOT_DIR + "/foo.txt"; |
3143 | + auto cmd = string("echo hello >") + full_path; |
3144 | + ASSERT_EQ(0, system(cmd.c_str())); |
3145 | + |
3146 | + unique_ptr<ItemJob> job(acc_.get(QString::fromStdString(full_path))); |
3147 | + wait(job.get()); |
3148 | + EXPECT_TRUE(job->isValid()); |
3149 | + |
3150 | + auto file = job->item(); |
3151 | + auto old_etag = file.etag(); |
3152 | + |
3153 | + sleep(1); // Make sure mtime changes. |
3154 | + cmd = string("touch ") + full_path; |
3155 | + unique_ptr<Uploader> uploader(file.createUploader(Item::IgnoreConflict, 0)); |
3156 | + { |
3157 | + QSignalSpy spy(uploader.get(), &Uploader::statusChanged); |
3158 | + while (uploader->status() != Uploader::Ready) |
3159 | + { |
3160 | + ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); |
3161 | + } |
3162 | + } |
3163 | + |
3164 | + QSignalSpy spy(uploader.get(), &Uploader::statusChanged); |
3165 | + uploader->close(); |
3166 | + while (uploader->status() != Uploader::Finished) |
3167 | + { |
3168 | + ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); |
3169 | + } |
3170 | + |
3171 | + file = uploader->item(); |
3172 | + EXPECT_NE(old_etag, file.etag()); |
3173 | + EXPECT_EQ(int64_t(0), file.sizeInBytes()); |
3174 | +} |
3175 | + |
3176 | +TEST_F(LocalProviderTest, update_close_too_soon) |
3177 | +{ |
3178 | + using namespace unity::storage::qt; |
3179 | + |
3180 | + set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider)); |
3181 | + |
3182 | + auto full_path = ROOT_DIR + "/foo.txt"; |
3183 | + auto cmd = string("echo hello >") + full_path; |
3184 | + ASSERT_EQ(0, system(cmd.c_str())); |
3185 | + |
3186 | + unique_ptr<ItemJob> job(acc_.get(QString::fromStdString(full_path))); |
3187 | + wait(job.get()); |
3188 | + EXPECT_TRUE(job->isValid()); |
3189 | + |
3190 | + auto file = job->item(); |
3191 | + auto old_etag = file.etag(); |
3192 | + |
3193 | + int const segments = 50; |
3194 | + unique_ptr<Uploader> uploader(file.createUploader(Item::ErrorIfConflict, file_contents.size() * segments)); |
3195 | + |
3196 | + int count = 0; |
3197 | + QTimer timer; |
3198 | + timer.setSingleShot(false); |
3199 | + timer.setInterval(10); |
3200 | + QObject::connect(&timer, &QTimer::timeout, [&] { |
3201 | + uploader->write(&file_contents[0], file_contents.size()); |
3202 | + count++; |
3203 | + if (count == segments - 1) |
3204 | + { |
3205 | + uploader->close(); |
3206 | + } |
3207 | + }); |
3208 | + |
3209 | + QSignalSpy spy(uploader.get(), &Uploader::statusChanged); |
3210 | + timer.start(); |
3211 | + while (uploader->status() != Uploader::Error) |
3212 | + { |
3213 | + ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); |
3214 | + } |
3215 | + ASSERT_EQ(Uploader::Error, uploader->status()) << uploader->error().errorString().toStdString(); |
3216 | + EXPECT_EQ("LogicError: finish() method called too early, size was given as 22300 but only 21854 bytes were received", |
3217 | + uploader->error().errorString().toStdString()); |
3218 | +} |
3219 | + |
3220 | +TEST_F(LocalProviderTest, update_write_too_much) |
3221 | +{ |
3222 | + using namespace unity::storage::qt; |
3223 | + |
3224 | + set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider)); |
3225 | + |
3226 | + auto full_path = ROOT_DIR + "/foo.txt"; |
3227 | + auto cmd = string("echo hello >") + full_path; |
3228 | + ASSERT_EQ(0, system(cmd.c_str())); |
3229 | + |
3230 | + unique_ptr<ItemJob> job(acc_.get(QString::fromStdString(full_path))); |
3231 | + wait(job.get()); |
3232 | + EXPECT_TRUE(job->isValid()); |
3233 | + |
3234 | + auto file = job->item(); |
3235 | + auto old_etag = file.etag(); |
3236 | + |
3237 | + int const segments = 50; |
3238 | + // We write more than this many bytes below. |
3239 | + unique_ptr<Uploader> uploader(file.createUploader(Item::ErrorIfConflict, file_contents.size() * segments - 1)); |
3240 | + |
3241 | + int count = 0; |
3242 | + QTimer timer; |
3243 | + timer.setSingleShot(false); |
3244 | + timer.setInterval(10); |
3245 | + QObject::connect(&timer, &QTimer::timeout, [&] { |
3246 | + uploader->write(&file_contents[0], file_contents.size()); |
3247 | + count++; |
3248 | + if (count == segments) |
3249 | + { |
3250 | + uploader->close(); |
3251 | + } |
3252 | + }); |
3253 | + |
3254 | + QSignalSpy spy(uploader.get(), &Uploader::statusChanged); |
3255 | + timer.start(); |
3256 | + while (uploader->status() != Uploader::Error) |
3257 | + { |
3258 | + ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); |
3259 | + } |
3260 | + ASSERT_EQ(Uploader::Error, uploader->status()); |
3261 | + EXPECT_EQ("update(): received more than the expected number (22299) of bytes", |
3262 | + uploader->error().message().toStdString()); |
3263 | +} |
3264 | + |
3265 | +TEST_F(LocalProviderTest, upload_wrong_file_type) |
3266 | +{ |
3267 | + // We can't try an upload for a directory via the client API, so we use the LocalUploadJob directly. |
3268 | + |
3269 | + auto p = make_shared<LocalProvider>(); |
3270 | + |
3271 | + string const dir = ROOT_DIR + "/dir"; |
3272 | + ASSERT_EQ(0, mkdir(dir.c_str(), 0755)); |
3273 | + |
3274 | + try |
3275 | + { |
3276 | + LocalUploadJob(p, dir, 0, ""); |
3277 | + FAIL(); |
3278 | + } |
3279 | + catch (provider::InvalidArgumentException const& e) |
3280 | + { |
3281 | + EXPECT_EQ(string("InvalidArgumentException: update(): \"" + dir + "\" is not a file"), e.what()); |
3282 | + } |
3283 | +} |
3284 | + |
3285 | +TEST_F(LocalProviderTest, upload_root_noperm) |
3286 | +{ |
3287 | + // Force an error in prepare_channels when creating temp file. |
3288 | + |
3289 | + auto p = make_shared<LocalProvider>(); |
3290 | + |
3291 | + ASSERT_EQ(0, chmod(ROOT_DIR.c_str(), 0644)); |
3292 | + |
3293 | + try |
3294 | + { |
3295 | + LocalUploadJob(p, ROOT_DIR, "name", 0, true); |
3296 | + chmod(ROOT_DIR.c_str(), 0755); |
3297 | + FAIL(); |
3298 | + } |
3299 | + catch (provider::ResourceException const& e) |
3300 | + { |
3301 | + EXPECT_EQ(string("ResourceException: create_file(): cannot create temp file \"") + ROOT_DIR |
3302 | + + "/.storage-framework-%%%%-%%%%-%%%%-%%%%\": Invalid argument", |
3303 | + e.what()); |
3304 | + } |
3305 | + chmod(ROOT_DIR.c_str(), 0755); |
3306 | +} |
3307 | + |
3308 | +TEST_F(LocalProviderTest, sanitize) |
3309 | +{ |
3310 | + // Force various errors in sanitize() for coverage. |
3311 | + |
3312 | + auto p = make_shared<LocalProvider>(); |
3313 | + |
3314 | + try |
3315 | + { |
3316 | + LocalUploadJob(p, ROOT_DIR, "a/b", 0, true); |
3317 | + FAIL(); |
3318 | + } |
3319 | + catch (provider::InvalidArgumentException const& e) |
3320 | + { |
3321 | + EXPECT_STREQ("InvalidArgumentException: create_file(): name \"a/b\" cannot contain a slash", e.what()); |
3322 | + } |
3323 | + |
3324 | + try |
3325 | + { |
3326 | + LocalUploadJob(p, ROOT_DIR, "..", 0, true); |
3327 | + FAIL(); |
3328 | + } |
3329 | + catch (provider::InvalidArgumentException const& e) |
3330 | + { |
3331 | + EXPECT_STREQ("InvalidArgumentException: create_file(): invalid name: \"..\"", e.what()); |
3332 | + } |
3333 | + |
3334 | + try |
3335 | + { |
3336 | + LocalUploadJob(p, ROOT_DIR, ".storage-framework", 0, true); |
3337 | + FAIL(); |
3338 | + } |
3339 | + catch (provider::InvalidArgumentException const& e) |
3340 | + { |
3341 | + EXPECT_STREQ("InvalidArgumentException: create_file(): names beginning with \".storage-framework\" are reserved", |
3342 | + e.what()); |
3343 | + } |
3344 | +} |
3345 | + |
3346 | +TEST_F(LocalProviderTest, throw_if_not_valid) |
3347 | +{ |
3348 | + // Make sure that we can't escape the root. |
3349 | + |
3350 | + auto p = make_shared<LocalProvider>(); |
3351 | + |
3352 | + try |
3353 | + { |
3354 | + LocalUploadJob(p, ROOT_DIR + "/..", "a", 0, true); |
3355 | + FAIL(); |
3356 | + } |
3357 | + catch (provider::InvalidArgumentException const& e) |
3358 | + { |
3359 | + EXPECT_EQ(string("InvalidArgumentException: create_file(): invalid id: \"") + ROOT_DIR + "/..\"", e.what()); |
3360 | + } |
3361 | + |
3362 | + try |
3363 | + { |
3364 | + LocalUploadJob(p, "/bin" , "a", 0, true); |
3365 | + FAIL(); |
3366 | + } |
3367 | + catch (provider::InvalidArgumentException const& e) |
3368 | + { |
3369 | + EXPECT_STREQ("InvalidArgumentException: create_file(): invalid id: \"/bin\"", e.what()); |
3370 | + } |
3371 | +} |
3372 | + |
3373 | +TEST_F(LocalProviderTest, create_file) |
3374 | +{ |
3375 | + using namespace unity::storage::qt; |
3376 | + |
3377 | + set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider)); |
3378 | + |
3379 | + auto root = get_root(acc_); |
3380 | + int const segments = 50; |
3381 | + unique_ptr<Uploader> uploader(root.createFile("foo.txt", Item::ErrorIfConflict, |
3382 | + file_contents.size() * segments, "text/plain")); |
3383 | + |
3384 | + int count = 0; |
3385 | + QTimer timer; |
3386 | + timer.setSingleShot(false); |
3387 | + timer.setInterval(10); |
3388 | + QObject::connect(&timer, &QTimer::timeout, [&] { |
3389 | + uploader->write(&file_contents[0], file_contents.size()); |
3390 | + count++; |
3391 | + if (count == segments) |
3392 | + { |
3393 | + uploader->close(); |
3394 | + } |
3395 | + }); |
3396 | + |
3397 | + QSignalSpy spy(uploader.get(), &Uploader::statusChanged); |
3398 | + while (uploader->status() != Uploader::Ready) |
3399 | + { |
3400 | + ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); |
3401 | + } |
3402 | + timer.start(); |
3403 | + while (uploader->status() != Uploader::Finished) |
3404 | + { |
3405 | + ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); |
3406 | + } |
3407 | + |
3408 | + auto file = uploader->item(); |
3409 | + EXPECT_EQ(int64_t(file_contents.size() * segments), file.sizeInBytes()); |
3410 | +} |
3411 | + |
3412 | +TEST_F(LocalProviderTest, create_file_ignore_conflict) |
3413 | +{ |
3414 | + using namespace unity::storage::qt; |
3415 | + |
3416 | + set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider)); |
3417 | + |
3418 | + auto full_path = ROOT_DIR + "/foo.txt"; |
3419 | + auto cmd = string("echo hello >") + full_path; |
3420 | + ASSERT_EQ(0, system(cmd.c_str())); |
3421 | + |
3422 | + auto root = get_root(acc_); |
3423 | + int const segments = 50; |
3424 | + unique_ptr<Uploader> uploader(root.createFile("foo.txt", Item::IgnoreConflict, |
3425 | + file_contents.size() * segments, "text/plain")); |
3426 | + |
3427 | + int count = 0; |
3428 | + QTimer timer; |
3429 | + timer.setSingleShot(false); |
3430 | + timer.setInterval(10); |
3431 | + QObject::connect(&timer, &QTimer::timeout, [&] { |
3432 | + uploader->write(&file_contents[0], file_contents.size()); |
3433 | + count++; |
3434 | + if (count == segments) |
3435 | + { |
3436 | + uploader->close(); |
3437 | + } |
3438 | + }); |
3439 | + |
3440 | + QSignalSpy spy(uploader.get(), &Uploader::statusChanged); |
3441 | + while (uploader->status() != Uploader::Ready) |
3442 | + { |
3443 | + ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); |
3444 | + } |
3445 | + timer.start(); |
3446 | + while (uploader->status() != Uploader::Finished) |
3447 | + { |
3448 | + ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); |
3449 | + } |
3450 | + |
3451 | + auto file = uploader->item(); |
3452 | + EXPECT_EQ(int64_t(file_contents.size() * segments), file.sizeInBytes()); |
3453 | +} |
3454 | + |
3455 | +TEST_F(LocalProviderTest, create_file_error_if_conflict) |
3456 | +{ |
3457 | + using namespace unity::storage::qt; |
3458 | + |
3459 | + set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider)); |
3460 | + |
3461 | + auto full_path = ROOT_DIR + "/foo.txt"; |
3462 | + auto cmd = string("echo hello >") + full_path; |
3463 | + ASSERT_EQ(0, system(cmd.c_str())); |
3464 | + |
3465 | + auto root = get_root(acc_); |
3466 | + int const segments = 50; |
3467 | + unique_ptr<Uploader> uploader(root.createFile("foo.txt", Item::ErrorIfConflict, |
3468 | + file_contents.size() * segments, "text/plain")); |
3469 | + |
3470 | + QSignalSpy spy(uploader.get(), &Uploader::statusChanged); |
3471 | + while (uploader->status() != Uploader::Error) |
3472 | + { |
3473 | + ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); |
3474 | + } |
3475 | + EXPECT_EQ(string("create_file(): \"") + full_path + "\" exists already", |
3476 | + uploader->error().message().toStdString()); |
3477 | +} |
3478 | + |
3479 | +TEST_F(LocalProviderTest, create_file_created_during_upload) |
3480 | +{ |
3481 | + using namespace unity::storage::qt; |
3482 | + |
3483 | + set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider)); |
3484 | + |
3485 | + auto root = get_root(acc_); |
3486 | + int const segments = 50; |
3487 | + string full_path = ROOT_DIR + "/foo.txt"; |
3488 | + unique_ptr<Uploader> uploader(root.createFile("foo.txt", Item::ErrorIfConflict, |
3489 | + file_contents.size() * segments, "text/plain")); |
3490 | + |
3491 | + int count = 0; |
3492 | + QTimer timer; |
3493 | + timer.setSingleShot(false); |
3494 | + timer.setInterval(10); |
3495 | + QObject::connect(&timer, &QTimer::timeout, [&] { |
3496 | + uploader->write(&file_contents[0], file_contents.size()); |
3497 | + count++; |
3498 | + if (count == segments / 2) |
3499 | + { |
3500 | + string cmd = "touch " + full_path; |
3501 | + ASSERT_EQ(0, system(cmd.c_str())); |
3502 | + } |
3503 | + else if (count == segments) |
3504 | + { |
3505 | + uploader->close(); |
3506 | + } |
3507 | + }); |
3508 | + |
3509 | + QSignalSpy spy(uploader.get(), &Uploader::statusChanged); |
3510 | + while (uploader->status() != Uploader::Ready) |
3511 | + { |
3512 | + ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); |
3513 | + } |
3514 | + timer.start(); |
3515 | + while (uploader->status() != Uploader::Error) |
3516 | + { |
3517 | + ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); |
3518 | + } |
3519 | + EXPECT_EQ(string("create_file(): \"") + full_path + "\" exists already", |
3520 | + uploader->error().message().toStdString()); |
3521 | +} |
3522 | + |
3523 | +int main(int argc, char** argv) |
3524 | +{ |
3525 | + setenv("LANG", "C", true); |
3526 | + setenv("SF_LOCAL_PROVIDER_ROOT", ROOT_DIR.c_str(), true); |
3527 | + |
3528 | + // Test test fixture repeatedly creates and tears down the dbus connection. |
3529 | + // The provider calls g_file_new_for_path() which talks to the GVfs backend |
3530 | + // via dbus. If the dbus connection disappears, that causes GIO to send a |
3531 | + // a SIGTERM, killing the test. |
3532 | + // Setting GIO_USE_VFS variable to "local" disables sending the signal. |
3533 | + setenv("GIO_USE_VFS", "local", true); |
3534 | + |
3535 | + QCoreApplication app(argc, argv); |
3536 | + |
3537 | + ::testing::InitGoogleTest(&argc, argv); |
3538 | + int rc = RUN_ALL_TESTS(); |
3539 | + |
3540 | + // Process any pending events to avoid bogus leak reports from valgrind. |
3541 | + QCoreApplication::sendPostedEvents(); |
3542 | + QCoreApplication::processEvents(); |
3543 | + |
3544 | + if (rc == 0) |
3545 | + { |
3546 | + boost::system::error_code ec; |
3547 | + boost::filesystem::remove_all(ROOT_DIR, ec); |
3548 | + } |
3549 | + |
3550 | + return rc; |
3551 | +} |
3552 | |
3553 | === added file 'tests/utils/env_var_guard.h' |
3554 | --- tests/utils/env_var_guard.h 1970-01-01 00:00:00 +0000 |
3555 | +++ tests/utils/env_var_guard.h 2017-03-17 05:04:57 +0000 |
3556 | @@ -0,0 +1,70 @@ |
3557 | +/* |
3558 | + * Copyright (C) 2015 Canonical Ltd. |
3559 | + * |
3560 | + * This program is free software: you can redistribute it and/or modify |
3561 | + * it under the terms of the GNU General Public License version 3 as |
3562 | + * published by the Free Software Foundation. |
3563 | + * |
3564 | + * This program is distributed in the hope that it will be useful, |
3565 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
3566 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3567 | + * GNU General Public License for more details. |
3568 | + * |
3569 | + * You should have received a copy of the GNU General Public License |
3570 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
3571 | + * |
3572 | + * Authored by: Michi Henning <michi.henning@canonical.com> |
3573 | + */ |
3574 | + |
3575 | +#pragma once |
3576 | + |
3577 | +#include <cassert> |
3578 | +#include <cstdlib> |
3579 | +#include <string> |
3580 | + |
3581 | +class EnvVarGuard final |
3582 | +{ |
3583 | +public: |
3584 | + // Set environment variable 'name' to 'val'. |
3585 | + // To clear the variable, pass nullptr for 'val'. |
3586 | + // The destructor restores the original setting. |
3587 | + EnvVarGuard(char const* name, char const* val) |
3588 | + : name_(name) |
3589 | + { |
3590 | + assert(name && *name != '\0'); |
3591 | + auto const old_val = getenv(name); |
3592 | + if ((was_set_ = old_val != nullptr)) |
3593 | + { |
3594 | + old_value_ = old_val; |
3595 | + } |
3596 | + if (val) |
3597 | + { |
3598 | + setenv(name, val, true); |
3599 | + } |
3600 | + else |
3601 | + { |
3602 | + unsetenv(name); |
3603 | + } |
3604 | + } |
3605 | + |
3606 | + // Restore the original setting. |
3607 | + ~EnvVarGuard() |
3608 | + { |
3609 | + if (was_set_) |
3610 | + { |
3611 | + setenv(name_.c_str(), old_value_.c_str(), true); |
3612 | + } |
3613 | + else |
3614 | + { |
3615 | + unsetenv(name_.c_str()); |
3616 | + } |
3617 | + } |
3618 | + |
3619 | + EnvVarGuard(const EnvVarGuard&) = delete; |
3620 | + EnvVarGuard& operator=(const EnvVarGuard&) = delete; |
3621 | + |
3622 | +private: |
3623 | + std::string name_; |
3624 | + std::string old_value_; |
3625 | + bool was_set_; |
3626 | +}; |
FAILED: Continuous integration, rev:108 /jenkins. canonical. com/unity- api-1/job/ lp-storage- framework- ci/251/ /jenkins. canonical. com/unity- api-1/job/ build/1777/ console /jenkins. canonical. com/unity- api-1/job/ build-0- fetch/1784 /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=amd64, release= xenial+ overlay/ 1560/console /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=amd64, release= zesty/1560/ console /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=armhf, release= xenial+ overlay/ 1560/console /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=armhf, release= zesty/1560/ console /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=i386, release= xenial+ overlay/ 1560/console /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=i386, release= zesty/1560/ console
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
Click here to trigger a rebuild: /jenkins. canonical. com/unity- api-1/job/ lp-storage- framework- ci/251/ rebuild
https:/