Merge lp:~michihenning/storage-framework/add-doc into lp:storage-framework/devel
- add-doc
- Merge into devel
Status: | Merged |
---|---|
Approved by: | Michi Henning |
Approved revision: | 132 |
Merged at revision: | 126 |
Proposed branch: | lp:~michihenning/storage-framework/add-doc |
Merge into: | lp:storage-framework/devel |
Diff against target: |
2476 lines (+1687/-122) 25 files modified
CMakeLists.txt (+1/-0) debian/changelog (+1/-0) debian/control (+8/-0) debian/control.in (+8/-0) debian/storage-framework-doc.install (+1/-0) doc/CMakeLists.txt (+20/-0) doc/provider_tut.dox (+588/-0) include/unity/storage/common.h (+39/-18) include/unity/storage/provider/DownloadJob.h (+110/-6) include/unity/storage/provider/Exceptions.h (+157/-17) include/unity/storage/provider/Item.h (+46/-0) include/unity/storage/provider/ProviderBase.h (+225/-39) include/unity/storage/provider/Server.h (+112/-0) include/unity/storage/provider/TempfileUploadJob.h (+43/-5) include/unity/storage/provider/UploadJob.h (+103/-5) include/unity/storage/provider/testing/TestServer.h (+30/-0) include/unity/storage/qt/internal/StorageErrorImpl.h (+1/-0) src/local-provider/LocalDownloadJob.cpp (+2/-2) src/local-provider/LocalProvider.cpp (+11/-1) src/local-provider/LocalUploadJob.cpp (+16/-5) src/qt/internal/ItemImpl.cpp (+1/-1) src/qt/internal/StorageErrorImpl.cpp (+6/-0) src/qt/internal/unmarshal_error.cpp (+1/-1) tests/local-provider/local-provider_test.cpp (+156/-21) tests/remote-client/remote-client_test.cpp (+1/-1) |
To merge this branch: | bzr merge lp:~michihenning/storage-framework/add-doc |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
unity-api-1-bot | continuous-integration | Approve | |
Michi Henning (community) | Approve | ||
James Henstridge | Approve | ||
Review via email: mp+321408@code.launchpad.net |
Commit message
Added tutorial and provider reference documentation.
Description of the change
Added tutorial and provider reference documentation.
I'd appreciate any and all comments. Typos, style/grammar issues, omissions, inaccuracies, etc. In particular, what did I not say that should have been said?
Besides adding doc, this MR also fixes a few other things I stumbled into:
- A few extra tests for corner cases
- Previously protected members in UploadJob, TempfileUploadJOb, and DownloadJob are now private.
- Attempts to delete or move a root now throw PermissionException instead of LogicException.
- Attempts to list a file or download/upload a folder now throw LogicException instead of InvalidArgument
unity-api-1-bot (unity-api-1-bot) wrote : | # |
unity-api-1-bot (unity-api-1-bot) wrote : | # |
PASSED: Continuous integration, rev:128
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:/
Marcus Tomlinson (marcustomlinson) wrote : | # |
First pass spell checking (inline)
James Henstridge (jamesh) wrote : | # |
Some comments left inline.
unity-api-1-bot (unity-api-1-bot) wrote : | # |
FAILED: Continuous integration, rev:130
https:/
Executed test runs:
FAILURE: https:/
FAILURE: https:/
Click here to trigger a rebuild:
https:/
unity-api-1-bot (unity-api-1-bot) wrote : | # |
PASSED: Continuous integration, rev:131
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:/
Michi Henning (michihenning) wrote : | # |
Thanks heaps for the reviews, guys!
I've fixed the spelling mistakes (plus a few others I stumbled across).
I've responded to a few comments inline. Where I didn't respond, I believe I've addressed the concern. Let me know if something still doesn't look right please!
unity-api-1-bot (unity-api-1-bot) wrote : | # |
FAILED: Autolanding.
More details in the following jenkins job:
https:/
Executed test runs:
FAILURE: https:/
FAILURE: https:/
- 132. By Michi Henning
-
Merged devel.
Michi Henning (michihenning) wrote : | # |
Re-approving for Jenkins after fixing merge conflict.
unity-api-1-bot (unity-api-1-bot) wrote : | # |
PASSED: Continuous integration, rev:132
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:/
Preview Diff
1 | === modified file 'CMakeLists.txt' |
2 | --- CMakeLists.txt 2017-03-30 00:57:11 +0000 |
3 | +++ CMakeLists.txt 2017-04-06 07:42:38 +0000 |
4 | @@ -134,6 +134,7 @@ |
5 | add_subdirectory(tests) |
6 | add_subdirectory(demo) |
7 | add_subdirectory(tools) |
8 | +add_subdirectory(doc) |
9 | |
10 | enable_coverage_report( |
11 | TARGETS |
12 | |
13 | === modified file 'debian/changelog' |
14 | --- debian/changelog 2017-04-06 04:57:35 +0000 |
15 | +++ debian/changelog 2017-04-06 07:42:38 +0000 |
16 | @@ -2,6 +2,7 @@ |
17 | |
18 | [ Michi Henning ] |
19 | * Removed dependency on libgio. |
20 | + * Added tutorial and provider reference documentation. |
21 | |
22 | [ James Henstridge ] |
23 | * Add systemd units for registry and local provider D-Bus services. |
24 | |
25 | === modified file 'debian/control' |
26 | --- debian/control 2017-03-20 04:51:09 +0000 |
27 | +++ debian/control 2017-04-06 07:42:38 +0000 |
28 | @@ -110,3 +110,11 @@ |
29 | ${misc:Depends}, |
30 | Description: Header files for the Storage Framework provider library |
31 | Development C++ headers for the provider API. |
32 | + |
33 | +Package: storage-framework-doc |
34 | +Section: doc |
35 | +Architecture: all |
36 | +Multi-Arch: foreign |
37 | +Depends: ${misc:Depends}, |
38 | +Description: Documentation for storage-framework-dev |
39 | + Tutorial and API reference. |
40 | |
41 | === modified file 'debian/control.in' |
42 | --- debian/control.in 2017-03-17 05:04:09 +0000 |
43 | +++ debian/control.in 2017-04-06 07:42:38 +0000 |
44 | @@ -105,3 +105,11 @@ |
45 | ${misc:Depends}, |
46 | Description: Header files for the Storage Framework provider library |
47 | Development C++ headers for the provider API. |
48 | + |
49 | +Package: storage-framework-doc |
50 | +Section: doc |
51 | +Architecture: all |
52 | +Multi-Arch: foreign |
53 | +Depends: ${misc:Depends}, |
54 | +Description: Documentation for storage-framework-dev |
55 | + Tutorial and API reference. |
56 | |
57 | === added file 'debian/storage-framework-doc.install' |
58 | --- debian/storage-framework-doc.install 1970-01-01 00:00:00 +0000 |
59 | +++ debian/storage-framework-doc.install 2017-04-06 07:42:38 +0000 |
60 | @@ -0,0 +1,1 @@ |
61 | +usr/share/doc/storage-framework/* |
62 | |
63 | === added directory 'doc' |
64 | === added file 'doc/CMakeLists.txt' |
65 | --- doc/CMakeLists.txt 1970-01-01 00:00:00 +0000 |
66 | +++ doc/CMakeLists.txt 2017-04-06 07:42:38 +0000 |
67 | @@ -0,0 +1,20 @@ |
68 | +find_package(DoxygenBuilder) |
69 | + |
70 | +add_doxygen( |
71 | + storage-framework-doc |
72 | + INPUT |
73 | + ${CMAKE_CURRENT_SOURCE_DIR} |
74 | + ${CMAKE_SOURCE_DIR}/include/unity/storage |
75 | + STRIP_FROM_PATH |
76 | + ${CMAKE_SOURCE_DIR} |
77 | + STRIP_FROM_INC_PATH |
78 | + ${CMAKE_SOURCE_DIR}/include |
79 | + EXCLUDE_PATTERNS |
80 | + */internal/* |
81 | + */qt/* |
82 | + ALL |
83 | +) |
84 | + |
85 | +install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html |
86 | + DESTINATION share/doc/storage-framework |
87 | +) |
88 | |
89 | === added file 'doc/provider_tut.dox' |
90 | --- doc/provider_tut.dox 1970-01-01 00:00:00 +0000 |
91 | +++ doc/provider_tut.dox 2017-04-06 07:42:38 +0000 |
92 | @@ -0,0 +1,588 @@ |
93 | +/** |
94 | +\mainpage Tutorial |
95 | + |
96 | +\section overview Overview |
97 | + |
98 | +The storage framework provides a general-purpose file system abstraction that is independent of any physical storage |
99 | +mechanism and the APIs that are needed to manipulate different kinds of storage. This allows client applications to |
100 | +access and modify files that exist in remote storage mechanisms (such as |
101 | +<a href="https://www.google.com/drive/">Google Drive</a> or <a href="https://owncloud.org/">Owncloud</a>) as well |
102 | +as files in the local file system through a common API. |
103 | + |
104 | +The framework includes a provider for local files; to allow access to remote files, the framework |
105 | +requires provider implementations that adapt the common API to each cloud service. |
106 | + |
107 | +Each provider implementation runs as a separate DBus service on the session bus; the storage framework |
108 | +provides run-time support that takes care of DBus communications, error handling, sanitizing input parameters, |
109 | +authorization, and so on. This allows providers to be created without having to handle these details, and enables |
110 | +the addition of new providers over time without disturbing existing provider implementations. |
111 | + |
112 | +Ubuntu includes a number of provider implementations, among them an |
113 | +<a href="https://code.launchpad.net/storage-provider-webdav">Owncloud provider</a> and an |
114 | +<a href="https://launchpad.net/mcloud">mCloud provider</a>. |
115 | + |
116 | +\section semantics File System Semantics |
117 | + |
118 | +The storage framework establishes common semantics that apply to all storage backends. |
119 | +Clients must not expect anything not guaranteed by these semantics, and providers must implement the semantics |
120 | +as faithfully as possible. |
121 | + |
122 | +The operations that are available are necessarily limited to those that can be supported by the |
123 | +majority of backends: |
124 | + |
125 | +- List the root folder (or root folders; some providers support multiple roots) |
126 | + |
127 | +- Create and destroy folders or files |
128 | + |
129 | +- Move or copy a file or folder |
130 | + |
131 | +- List the contents of a folder |
132 | + |
133 | +- Look up a file or folder by name |
134 | + |
135 | +- Look up a file or folder by identity |
136 | + |
137 | +- Upload or download a file |
138 | + |
139 | +- Get additional metadata (such as modification time or mime type) for a file or folder |
140 | + |
141 | +Note that all data transfers can be performed only by uploading or downloading an entire file. The framework |
142 | +does not support random access to files. |
143 | + |
144 | +Move and copy operations can be performed only within the same account. If an application wants to copy or move files |
145 | +from one account to another, it must download the files from the source account and upload them to the target account |
146 | +(and, for a move, delete the source files after uploading them). |
147 | + |
148 | +\subsection accounts Accounts |
149 | + |
150 | +The storage framework is integrated with |
151 | +<a href="https://help.ubuntu.com/stable/ubuntu-help/accounts.html">Online Accounts</a>. When a client application |
152 | +uses the framework, it first retrieves a list of available accounts from the framework. The application only receives |
153 | +those accounts for which the user has authorized access. The application then retrieves the root (or roots) for that |
154 | +account, and uses the root to access and modify files and folders underneath that root. |
155 | +On the server side, the runtime passes any |
156 | +authorization token that is needed to access the cloud backend to the provider implementation; the runtime |
157 | +also handles re-authorization in case a token has expired. |
158 | + |
159 | +\subsection identity File and Folder Identity |
160 | + |
161 | +Each file and folder has an identity that is unique within the account that contains the file. |
162 | +Identities are opaque strings that are assigned by the provider. (Clients cannot control the identity |
163 | +of files and folders). |
164 | + |
165 | +The identity of files and folders is immutable during their life time, and identities |
166 | +can be compared for equality and less-than. |
167 | +This means that clients can store identities in an external file or a database and expect them to |
168 | +continue to refer to the same file or folder even after a process re-start. |
169 | + |
170 | +Identities are not globally unique; different files in different accounts may have the same identity. |
171 | + |
172 | +Identities may not be unique across object life times. For example, if a client creates a file and deletes that |
173 | +file again, another file that is created later may get the identity that was used by the earlier file. |
174 | +Similarly, moving a file or folder may (or may not) change its identity. |
175 | + |
176 | +\subsection etags ETags |
177 | + |
178 | +Files have an associated <a href="https://en.wikipedia.org/wiki/HTTP_ETag">ETag</a>. The ETag for a file |
179 | +changes every time a file is updated. When uploading or downloading a file, a client can specify whether to check |
180 | +the ETag of a file. If the client requests the ETag to be checked, the provider performs the |
181 | +upload or download only if the ETag sent by the client matches the ETag of the file. This allows clients to ensure |
182 | +that a file has not been modified since the client last retrieved the file's metadata. |
183 | + |
184 | +ETags have no particular format; they simply are opaque strings that change each time a file changes. |
185 | + |
186 | +\subsection names File and Folder Names |
187 | + |
188 | +Clients choose the name of files and folders they create. However, providers may modify a name that is provided |
189 | +by a client, for example, a provider could map upper-case to lower-case letters. |
190 | +The actual name of a new file or folder is returned by the provider as part of the metadata. |
191 | + |
192 | +Different providers place different restrictions on the characters that are allowed as part of a name. |
193 | +The following meta-characters are likely to cause problems and should not be used in file or folder names: |
194 | + |
195 | +<tt>/</tt>, <tt>\\</tt>, <tt>*</tt>, <tt>"</tt>, <tt>:</tt>, <tt>\<</tt>, <tt>\></tt>, <tt>|</tt>, <tt>?</tt>, <tt>#</tt>, and <tt>%</tt>. |
196 | + |
197 | +The name of a file or folder is not necessarily unique within its parent folder. (Some cloud services |
198 | +allow different files with the same name in the same folder.) Similarly, a single file or folder may |
199 | +have more than one name within the same parent folder. (The name of a file or folder does not indicate |
200 | +anything about its identity. If you want to establish whether a file is the same as another file, you must |
201 | +compare the files' identities, not their names.) |
202 | + |
203 | +The name of a root folder can be anything. (It might be <tt>/</tt>, but could be something else.) |
204 | + |
205 | +\subsection metadata Metadata |
206 | +Files and folders have metadata associated with them. |
207 | +Metadata items are key–value pairs. The key (a string) identifies the item and determines the type of |
208 | +the corresponding value (such as string or integer). |
209 | + |
210 | +Some metadata is guaranteed to always be present (such as file |
211 | +size and modification time); other metadata may or may not be available, depending on the provider. (For example, |
212 | +not all providers maintain a modification time for directories.) |
213 | + |
214 | +The framework defines a number of well-known metadata keys that are used by the majority of providers (such as |
215 | +<tt>size_in_bytes</tt>). In addition, providers can also add provider-specific metadata. Clients can specify |
216 | +which metadata items should be returned by the provider. The requested metadata items are hints only: a provider |
217 | +may return fewer or more metadata items than were requested by the client. |
218 | + |
219 | +A special metadata key (<tt>__ALL__</tt>) |
220 | +indicates that the provider should return all available metadata. If a client does not request any specific |
221 | +metadata, the provider returns a default set of items. (Clients can specify which metadata should be returned |
222 | +in order to minimize the amount of data that is copied over the network; some providers support extensive metadata |
223 | +that consumes considerable bandwidth.) |
224 | + |
225 | +If a provider supplies non-standard metadata items, the item keys have a provider-specific prefix, such as |
226 | +<tt>mcloud:</tt> or <tt>gdrive:</tt>. |
227 | + |
228 | +\subsection uploads-downloads Uploads and Downloads |
229 | + |
230 | +Uploads and downloads take place over a UNIX domain socket. When a client requests an upload, the runtime creates |
231 | +a socket pair and passes a socket that is open for writing to the client, and a socket that is open for reading |
232 | +to the provider. (For downloads, the read and write ends are reversed.) The client and provider write and read |
233 | +the data for the file to/from their respective sockets. Once the client has written all the data, it makes one |
234 | +final API call to check whether the provider has correctly received all the data. This call returns the new metadata |
235 | +for the file (or an error, if the upload failed). |
236 | + |
237 | +Downloads work the same way as uploads, but with the read and write roles reversed. |
238 | + |
239 | +\section provider Implementing a Provider |
240 | + |
241 | +This section provides an overview of the provider API and explains the semantics you are expected to adhere |
242 | +to in your provider implementation. We strongly recommend that you also read the source code |
243 | +for the <a href="https://code.launchpad.net/storage-provider-webdav">Owncloud provider</a>. It is useful |
244 | +to learn how to implement a provider that uses HTTP to access a cloud service. (While the details |
245 | +of the interactions with your provider will differ, the principles will be |
246 | +very similar.) |
247 | + |
248 | +\subsection provider-overview Overview |
249 | + |
250 | +To implement a provider, you must create an implementation of the abstract |
251 | +@ref unity::storage::provider::ProviderBase "ProviderBase" class: |
252 | + |
253 | +\code{.cpp} |
254 | +class ProviderBase : public std::enable_shared_from_this<ProviderBase> |
255 | +{ |
256 | +public: |
257 | + ProviderBase(); |
258 | + virtual ~ProviderBase(); |
259 | + |
260 | + virtual boost::future<ItemList> roots(vector<string> const& keys, |
261 | + Context const& context) = 0; |
262 | + virtual boost::future<ItemList> lookup(string const& parent_id, |
263 | + string const& name, |
264 | + vector<string> const& keys, |
265 | + Context const& context) = 0; |
266 | + // More methods here... |
267 | +}; |
268 | +\endcode |
269 | + |
270 | +The base class defines a pure virtual method for each operation (such as |
271 | +@ref unity::storage::provider::ProviderBase::list "list()", |
272 | +@ref unity::storage::provider::ProviderBase::lookup "lookup()", |
273 | +@ref unity::storage::provider::ProviderBase::update "update()", etc.) |
274 | + |
275 | +Note that each operation returns a <code>boost::future</code> to the storage framework runtime; |
276 | +your operation implementation must make the future ready once the result of the operation is available. |
277 | + |
278 | +For this example, we assume that you want to create a provider for a (hypothetical) <code>MyCloud</code> |
279 | +storage service. |
280 | + |
281 | +To connect your implementation class to the runtime, you instantiate a <code>Server</code> instance and call its |
282 | +@ref unity::storage::provider::Server::init "init()" and |
283 | +@ref unity::storage::provider::Server::run "run()" methods: |
284 | + |
285 | +\code{.cpp} |
286 | +class MyCloudProvider : public ProviderBase |
287 | +{ |
288 | +public: |
289 | + MyCloudProvider(); |
290 | + virtual ~MyCloudProvider(); |
291 | + |
292 | + boost::future<ItemList> roots(vector<string> const& keys, |
293 | + Context const& context) override; |
294 | + boost::future<ItemList> lookup(string const& parent_id, |
295 | + string const& name, |
296 | + vector<string> const& keys, |
297 | + Context const& context) override; |
298 | + // More methods here... |
299 | +}; |
300 | + |
301 | +int main(int argc, char* argv[]) |
302 | +{ |
303 | + string const bus_name = "com.acme.StorageFramework.Provider.MyCloud"; |
304 | + string const account_service_id = "storage-provider-mycloud"; |
305 | + |
306 | + try |
307 | + { |
308 | + Server<MyCloudProvider> server(bus_name, account_service_id); |
309 | + server.init(argc, argv); |
310 | + return server.run(); |
311 | + } |
312 | + catch (std::exception const& e) |
313 | + { |
314 | + cerr << argv[0] << ": " << e.what() << endl; |
315 | + return 1; |
316 | + } |
317 | +} |
318 | +\endcode |
319 | + |
320 | +The <code>Server</code> class is a template that instantiates your <code>MyCloudProvider</code> class. |
321 | +You must provide a DBus name for your service, as well as the service ID with which your provider is |
322 | +known to Online Accounts. |
323 | + |
324 | +The call to @ref unity::storage::provider::Server::init "init()" initializes the storage framework runtime, |
325 | +connects the service to DBus and, once |
326 | +you call @ref unity::storage::provider::Server::run "run()", |
327 | +starts an event loop that forwards incoming requests to the methods of your |
328 | +<code>MyCloudProvider</code> class. |
329 | + |
330 | +The @ref unity::storage::provider::Server::run "run()" |
331 | +method returns after some period of inactivity (30 seconds, by default), so the provider |
332 | +process shuts down when it is not needed, and is started automatically if it is not running at the time a client |
333 | +request arrives. |
334 | + |
335 | +If you need to pass additional arguments to the <code>MyCloudProvider</code> constructor, you can do so |
336 | +by creating a server class of your own that derives from @ref unity::storage::provider::ServerBase "ServerBase". |
337 | +That class has an abstract @ref unity::storage::provider::ServerBase::make_provider() "make_provider()" |
338 | +factory method that you can override. The runtime calls the factory method to instantiate your provider. |
339 | + |
340 | +\subsection threading Threading Considerations |
341 | + |
342 | +The runtime invokes all methods of your provider implementation on the main thread. Your methods |
343 | +<i>must not</i> block for any length of time. Unless you know that a method will return |
344 | +immediately (within a few milliseconds), you <i>must</i> implement the method such that it can |
345 | +complete in the background (either using asynchronous I/O or a separate thread). |
346 | + |
347 | +The runtime uses <a href="http://doc.qt.io/qt-5/qcoreapplication.html"><code>QCoreApplication</code></a>, |
348 | +so your implementation can use anything that uses the Qt or glib event loop. (Alternatively, you can also |
349 | +run a separate event loop if you prefer.) |
350 | + |
351 | +When starting an async operation, create a |
352 | +<a href="http://www.boost.org/doc/libs/1_63_0/libs/fiber/doc/html/fiber/synchronization/futures/promise.html"><code>boost::promise</code></a> and return <code>promise::get_future()</code>; on completion, |
353 | +call <code>set_value()</code> or <code>set_exception()</code> to make the future ready. |
354 | + |
355 | +For a synchronous implementation, you can use |
356 | +<a href="http://www.boost.org/doc/libs/1_63_0/doc/html/thread/synchronization.html#thread.synchronization.futures.async"><code>boost::async()</code></a> to spawn a new thread. To indicate errors, call |
357 | +<a href="http://www.boost.org/doc/libs/1_63_0/libs/exception/doc/enable_current_exception.html"><code>boost::enable_current_exception()</code></a> |
358 | +or <a href="http://www.boost.org/doc/libs/master/libs/exception/doc/throw_exception.html"><code>boost::throw_exception</code></a>. |
359 | + |
360 | +The storage framework runtime is thread-safe, that is, you can up-call into the runtime (for example, to indicate |
361 | +an error during an upload) from any thread. The runtime takes care of any necessary locking and moving data |
362 | +to the correct thread for marshaling. |
363 | + |
364 | +\subsection provider-error-handling Error Handling |
365 | + |
366 | +All error handling uses exceptions. If you call into the runtime and it detects a problem, the runtime throws |
367 | +an exception that derives from <code>std::exception</code> or, for specific well-known problems, from |
368 | +@ref unity::storage::provider::StorageException "StorageException". (The code in <code>main()</code> above |
369 | +relies on this behavior to report any errors and return non-zero exit status.) |
370 | + |
371 | +If your implementation uses a library that does not transparently handle exceptions (such Qt), you |
372 | +must catch exceptions and deal with them as appropriate when you call into the runtime. |
373 | + |
374 | +Conversely, the runtime is exception-safe. If you encounter an error during an operation that |
375 | +throws an exception and you let the exception escape, the runtime will intercept the exception, abort the |
376 | +operation, and marshal an appropriate error back to the client. (If you throw something unexpected, such as |
377 | +<tt>99</tt> or a <code>std::exception</code>, the client receives a generic error.) |
378 | +This means that, to indicate an error to the runtime, you can always throw an exception or, alternatively, |
379 | +for methods that return a future, return a future that contains the exception. |
380 | + |
381 | +When throwing an exception, be as detailed in the error message as possible and include any details you receive |
382 | +from the cloud provider, as well as the identity of the file. Without this, it may be impossible to |
383 | +diagnose a problem from log files. |
384 | + |
385 | +The storage framework defines a small number of exceptions that indicate specific error conditions to the client. |
386 | +You <i>must</i> use these exception to indicate their respective error conditions, otherwise |
387 | +the client only knows that something did not work, but does not know the real cause of the error. <i>All</i> providers |
388 | +<i>must</i> adhere to this common set of error semantics, otherwise clients cannot implement meaningful |
389 | +error messages and error recovery. |
390 | + |
391 | +All provider exceptions derive from a common abstract base exception class: |
392 | + |
393 | +\code{.cpp} |
394 | +class StorageException : public std::exception |
395 | +{ |
396 | +public: |
397 | + // ... |
398 | + virtual char const* what() const noexcept override; |
399 | + |
400 | + std::string type() const; |
401 | + std::string error_message() const; |
402 | +}; |
403 | + |
404 | +class RemoteCommsException : public StorageException { ... }; |
405 | +class NotExistsException : public StorageException { ... }; |
406 | +// etc... |
407 | +\endcode |
408 | + |
409 | +The @ref unity::storage::provider::StorageException::type "type()" |
410 | +method returns the name of the exception (such as "NotExistsException"), and |
411 | +@ref unity::storage::provider::StorageException::error_message "error_message()" |
412 | +returns a message you provide when you throw the exception. |
413 | +(@ref unity::storage::provider::StorageException::what "what()" |
414 | +returns a string that contains both the exception name and the error message.) |
415 | + |
416 | +The concrete derived exceptions indicate error semantics as follows: |
417 | + |
418 | +- @ref unity::storage::provider::RemoteCommsException "RemoteCommsException" |
419 | + |
420 | + This exception indicates an (unexpected) error in the communication between your provider and the remote |
421 | + cloud service, such as the remote server being unreachable or returning garbled data. |
422 | + |
423 | +- @ref unity::storage::provider::NotExistsException "NotExistsException" |
424 | + |
425 | + This exception indicates that a file or folder the client wants to access does not exist. You must |
426 | + use this exception only if the remote cloud service has <i>authoritatively</i> indicated non-existence |
427 | + (such as with an HTTP 404 response). Do not use this exception for non-authoritative errors, such as |
428 | + timeouts or similar. |
429 | + |
430 | +- @ref unity::storage::provider::ExistsException "ExistsException" |
431 | + |
432 | + This exception indicates that a file or folder already exists, preventing an operation (such as a create or copy) |
433 | + from completing. Use this exception only if the remote cloud service has <i>authoritatively</i> |
434 | + indicated this error. Do not use this exception for non-authoritative errors, such as timeouts |
435 | + or similar. |
436 | + |
437 | +- @ref unity::storage::provider::ConflictException "ConflictException" |
438 | + |
439 | + This exception indicates an ETag mismatch: the client asked for a file to be updated or downloaded, but |
440 | + the file's ETag has changed since. |
441 | + |
442 | +- @ref unity::storage::provider::UnauthorizedException "UnauthorizedException" |
443 | + |
444 | + This exception indicates that the remote cloud service has indicated an authorization failure. Note that |
445 | + you must throw this exception only if the cloud service indicates invalid credentials. Do <i>not</i> throw |
446 | + this exception for permission errors (such as an attempt to write to a read-only file). In particular, |
447 | + do not throw this exception for an HTTP 403 error, which indicates that access to a particular resource |
448 | + was denied even though the credentials were valid. |
449 | + |
450 | + Some cloud providers use authorization tokens that expire after some time. If you detect an |
451 | + authorization failure in your code and throw this exception, the runtime re-retrieves the token from Online |
452 | + Accounts and transparently calls your operation again if the new token differs. If the runtime receives an |
453 | + @ref unity::storage::provider::UnauthorizedException "UnauthorizedException" on this second attempt too, |
454 | + it returns the error to the client. |
455 | + |
456 | +- @ref unity::storage::provider::PermissionException "PermissionException" |
457 | + |
458 | + This exception indicates that a file or folder cannot be accessed due to insufficient permissions (such as an |
459 | + attempt to write to a read-only file). Do <i>not</i> throw this exception for authorization errors. |
460 | + |
461 | +- @ref unity::storage::provider::QuotaException "QuotaException" |
462 | + |
463 | + This exception indicates that the provider has run out of space or that the maximum number of files and/or |
464 | + folders was exceeded. |
465 | + |
466 | +- @ref unity::storage::provider::CancelledException "CancelledException" |
467 | + |
468 | + This exception indicates that the client attempted to interact with an upload or download after it was cancelled. |
469 | + Due to the way the API is structured, you will not ever need to throw this exception. It is provided for |
470 | + completeness and to allow for provider implementations that do not use the storage framework API. |
471 | + |
472 | +- @ref unity::storage::provider::LogicException "LogicException" |
473 | + |
474 | + This exception indicates that the client has used the API incorrectly, such as indicating that an upload has |
475 | + completed before it has written all the data. In general, use |
476 | + @ref unity::storage::provider::LogicException "LogicException" |
477 | + for any errors that relate to operations being called in the wrong order, and for semantic errors |
478 | + (such as an attempt to download a folder or to list a file). |
479 | + |
480 | +- @ref unity::storage::provider::InvalidArgumentException "InvalidArgumentException" |
481 | + |
482 | + This exception indicates that the value of an argument supplied by the client is unacceptable, such as |
483 | + an empty string where a non-empty string was excepted, or a negative integer where a positive integer was expected. |
484 | + |
485 | +- @ref unity::storage::provider::ResourceException "ResourceException" |
486 | + |
487 | + This is a generic "catch-all" exception that you can throw to indicate unexpected errors that do not fit into |
488 | + any of the other categories. For example, <code>ResourceException</code> is appropriate to indicate out of |
489 | + file descriptors, failure to locate a configuration file, or any other unexpected system error. You should |
490 | + provide as much contextual information about such errors as possible. In particular, unexpected errors |
491 | + typically need to be diagnosed from log files. This means that you should provide, at least, the full error |
492 | + message you received from the cloud service (or the operating system), together with all other relevant |
493 | + details, such as the name and identity of the file, the URL of a failed request, |
494 | + any HTTP error information, and so on. |
495 | + |
496 | +- @ref unity::storage::provider::UnknownException "UnknownException" |
497 | + |
498 | + If your code throws an exception that does not derive from <code>StorageException</code>, it returns |
499 | + <code>UnknownException</code> to the client. Do not throw this exception explicitly; is is a "catch-all" |
500 | + exception that the runtime uses to indicate that something completely unexpected happened. (If a client |
501 | + receives this exception from your provider, chances are that you have forgotten to handle an error condition |
502 | + somewhere.) |
503 | + |
504 | +\subsection metadata-keys Metadata Keys |
505 | +Each method of your provider has a <code>keys</code> parameter of type <code>vector<string></code>. The parameter |
506 | +is provided by the client and indicates which metadata items you should include in the metadata you return |
507 | +from the operation. |
508 | + |
509 | +If the vector is empty, this indicates that you should return a set of default metadata items. In practice, this |
510 | +means that you should return whatever metadata you can cheaply receive from the cloud provider (in terms |
511 | +of bandwidth and number of requests). |
512 | + |
513 | +If the vector contains a single entry <code>__ALL__</code>, you should return complete metadata. In particular, |
514 | +you should return as many of the well-known metadata items that are defined in |
515 | +common.h as possible, plus |
516 | +any provider-specific metadata items you may support (prefixed with a provider name, such as <tt>Mycloud:</tt>). |
517 | + |
518 | +If the client provides a specific list of keys, you should restrict the metadata to those items requested by the |
519 | +client. This is useful to reduce traffic between your provider and the cloud service, if the cloud service |
520 | +requires additional requests for particular metadata items: you need to provide that metadata only if the client asks |
521 | +for it. |
522 | + |
523 | +The keys provided by the client are a hint only, that is, it is fine to provide more items than the client asked for |
524 | +(if you happen to have them available anyway at no extra cost), and it is legal to not provide all of the items |
525 | +the client asked for. |
526 | + |
527 | +If you receive a key that is not one of the keys defined in <code>common.h</code> and is not one of the keys |
528 | +you recognize as provider-specific, do <i>not</i> return an error from the operation. Instead, write a log message |
529 | +with the details (your provider name, the identity and name of the file and folder, and the invalid key) and |
530 | +ignore the invalid key. |
531 | + |
532 | +Note that <code>common.h</code> defines symbolic constants for metadata keys. Use these symbolic constants |
533 | +instead of hard-coding the corresponding string literals. |
534 | + |
535 | +\note For files, the <code>SIZE_IN_BYTES</code> and <code>LAST_MODIFIED_TIME</code> metadata items are mandatory; |
536 | +you must always supply these, whether the client asks for them or not. |
537 | + |
538 | +\subsection authorization Authorization |
539 | + |
540 | +Each method of your provider has a trailing parameter of type |
541 | +@ref unity::storage::provider::Context "Context": |
542 | + |
543 | +\code{.cpp} |
544 | +struct Context |
545 | +{ |
546 | + uid_t uid; |
547 | + pid_t pid; |
548 | + string security_label; |
549 | + |
550 | + Credentials credentials; |
551 | +}; |
552 | +\endcode |
553 | + |
554 | +The structure contains the user ID, process ID, and the |
555 | +<a href="https://help.ubuntu.com/lts/serverguide/apparmor.html">Apparmor</a> security label of the calling client, |
556 | +plus credentials that you can use for authorization with the cloud service. In turn, the credentials are a variant |
557 | +type that enables authorization with <a href="https://oauth.net/1/">OAuth1</a>, |
558 | +<a href="https://oauth.net/2/">OAuth2</a>, as well as with user name and password: |
559 | + |
560 | +\code{.cpp} |
561 | +struct NoCredentials |
562 | +{ |
563 | +}; |
564 | + |
565 | +struct OAuth1Credentials |
566 | +{ |
567 | + string consumer_key; |
568 | + string consumer_secret; |
569 | + string token; |
570 | + string token_secret; |
571 | +}; |
572 | + |
573 | +struct OAuth2Credentials |
574 | +{ |
575 | + string access_token; |
576 | +}; |
577 | + |
578 | +struct PasswordCredentials |
579 | +{ |
580 | + string username; |
581 | + string password; |
582 | + string host; |
583 | +}; |
584 | + |
585 | +typedef boost::variant<boost::blank,OAuth1Credentials,OAuth2Credentials,PasswordCredentials> Credentials; |
586 | +\endcode |
587 | + |
588 | +If you receive an authorization error from the cloud provider with the credentials that are passed to you |
589 | +by the runtime, you must throw an @ref unity::storage::provider::UnauthorizedException "UnauthorizedException". |
590 | +This prompts the runtime to refresh the token if it may have expired and call your your method |
591 | +a second time with the new token. If that second attempt also throws |
592 | +@ref unity::storage::provider::UnauthorizedException "UnauthorizedException", the runtime propagates the |
593 | +error to the client. |
594 | + |
595 | +\subsection download-upload Downloads and Uploads |
596 | + |
597 | +To implement download and upload, you must create implementations of the abstract |
598 | +@ref unity::storage::provider::DownloadJob "DownloadJob" and @ref unity::storage::provider::UploadJob "UploadJob" |
599 | +classes and return them from your @ref unity::storage::provider::ProviderBase::download "download()" and |
600 | +@ref unity::storage::provider::ProviderBase::update "update()" (or |
601 | +@ref unity::storage::provider::ProviderBase::create_file "create_file()") methods, respectively. |
602 | +The two base classes are very similar, so we focus on @ref unity::storage::provider::DownloadJob "DownloadJob" here. |
603 | + |
604 | +\code{.cpp} |
605 | +class MyDownloadJob : public unity::storage::provider::DownloadJob |
606 | +{ |
607 | +public: |
608 | + MyDownloadJob(shared_ptr<MyCloudProvider> const& provider, |
609 | + string const& item_id, |
610 | + string const& match_etag); |
611 | + virtual ~LocalDownloadJob(); |
612 | + |
613 | + boost::future<void> cancel() override; |
614 | + boost::future<void> finish() override; |
615 | + |
616 | +private: |
617 | + // ... |
618 | +}; |
619 | +\endcode |
620 | + |
621 | +In the implementation of your constructor, you must initiate the download. The download must be able to proceed |
622 | +without blocking. How you do this is up to you. You can run the download in a separate thread, use the |
623 | +runtime's Qt event loop, or run a separate event loop. (The storage framework runtime does not care.) |
624 | + |
625 | +Your <code>MyDownloadJob</code> receives a socket that is open for writing and connected to the client by calling |
626 | +@ref unity::storage::provider::DownloadJob::write_socket "write_socket()" on its base class. You must write the |
627 | +data for the file to the socket (from which the client can read it). |
628 | + |
629 | +At some point, the runtime either calls your @ref unity::storage::provider::DownloadJob::cancel "cancel()" method, |
630 | +in which case you simply abort the download, or it calls @ref unity::storage::provider::DownloadJob::finish "finish()". |
631 | +The purpose of @ref unity::storage::provider::DownloadJob::finish "finish()" is to confirm whether the download |
632 | +completed successfully. At this point, you must check whether you have received <a>all</a> of the data for the file |
633 | +from the cloud provider and have successfully written it to the socket. If so, you return a ready future; if not, |
634 | +you return a future that holds the appropriate @ref unity::storage::provider::StorageException "StorageException" |
635 | +to indicate the nature of the error. |
636 | + |
637 | +@ref unity::storage::provider::DownloadJob "DownloadJob" and @ref unity::storage::provider::UploadJob "UploadJob" |
638 | +classes also provide a @ref unity::storage::provider::DownloadJob::report_error "report_error()" method that |
639 | +you can use to report any errors that arise while your downloader runs in the background. (If you call |
640 | +@ref unity::storage::provider::DownloadJob::report_error "report_error()", neither |
641 | +@ref unity::storage::provider::DownloadJob::finish "finish()" nor |
642 | +@ref unity::storage::provider::DownloadJob::cancel "cancel()" are called by the runtime.) |
643 | + |
644 | +The @ref unity::storage::provider::DownloadJob "DownloadJob" base class also provides a |
645 | +@ref unity::storage::provider::DownloadJob::report_complete "report_complete()" method that you call |
646 | +to indicate successful completion without having to wait for the call to |
647 | +@ref unity::storage::provider::DownloadJob::finish "finish()". |
648 | + |
649 | +\subsection buffering Download and Upload Buffering |
650 | + |
651 | +When implementing your provider, you need to be aware of when (and when not) to buffer data. |
652 | + |
653 | +For downloads, you should write the data to the client socket as soon as you receive it from the cloud provider. |
654 | +This is necessary to enable streaming. (A media player should be able to start playing a song or video as soon |
655 | +as possible.) |
656 | + |
657 | +For uploads, the situation is the opposite, and you should buffer any uploads that are larger than than a few |
658 | +kilobytes in the file system before starting the upload with the provider. |
659 | +This is particularly important for mobile devices, where applications may be suspended for extended periods. |
660 | +If you write data to the cloud provider while the client is still writing it, and the client application is suspended, |
661 | +the upload gets stuck until the client application resumes (by which time your connection with the cloud provider |
662 | +may have timed out, or the user may have walked out of reach of the network). |
663 | + |
664 | +Note that, eventually, you will either receive the @ref unity::storage::provider::DownloadJob::finish "finish()" |
665 | +call to confirm whether or not there was an error (or a @ref unity::storage::provider::DownloadJob::cancel "cancel()" |
666 | +call if the client application dies). This means that, if your cloud provider is able to resume a previously |
667 | +interrupted upload, you can re-start an upload that failed due to loss of the network. (Be judicious in how |
668 | +aggressively you try to resume though; on a battery-powered device, it is expensive to turn on the radio, so you |
669 | +should use a back-off algorithm and give up if the upload cannot be resumed within a reasonable period of time.) |
670 | + |
671 | +\section local-provider The Local Provider |
672 | + |
673 | +The storage framework includes a local provider that stores files in the local file system. |
674 | +In a classic environment, the root of the local files is <code>$XDG_DATA_DIR/storage-framework/local</code>; |
675 | +in a Snap environment, the root is <code>$SNAP_USER_COMMON/storage-framework/local</code>. |
676 | +You can set <code>$SF_LOCAL_PROVIDER_ROOT</code> to change the root directory. (This is intended mainly for testing.) |
677 | + |
678 | +The local provider uses <code>com.canonical.StorageFramework.Provider.Local</code> as the DBus name. The object path |
679 | +is <code>/provider/0</code>. |
680 | +*/ |
681 | |
682 | === modified file 'include/unity/storage/common.h' |
683 | --- include/unity/storage/common.h 2016-11-03 03:41:24 +0000 |
684 | +++ include/unity/storage/common.h 2017-04-06 07:42:38 +0000 |
685 | @@ -23,36 +23,57 @@ |
686 | namespace storage |
687 | { |
688 | |
689 | +/** |
690 | +\brief Indicates the type of an item. |
691 | +*/ |
692 | + |
693 | enum class ItemType |
694 | { |
695 | - file, |
696 | - folder, |
697 | - root, |
698 | - LAST_ENTRY__ |
699 | + file, /*!< The item represents a file. */ |
700 | + folder, /*!< The item represents a (non-root) folder. */ |
701 | + root, /*!< The item represents a root folder. */ |
702 | + LAST_ENTRY__ /*!< End of enumeration marker. */ |
703 | }; |
704 | |
705 | +/** |
706 | +\brief Determines the behavior in case of an ETag mismatch. |
707 | +*/ |
708 | + |
709 | enum class ConflictPolicy |
710 | { |
711 | - error_if_conflict, |
712 | - ignore_conflict, |
713 | + error_if_conflict, /*!< Return an error if the ETag has changed. */ |
714 | + ignore_conflict, /*!< Ignore ETag mismatch and overwrite or replace the file. */ |
715 | overwrite = ignore_conflict, // TODO: remove this, it's here only for compatibility with v1 API |
716 | }; |
717 | |
718 | +/** |
719 | +\brief This namespace defines well-known metadata keys. |
720 | + |
721 | +See \ref common.h for details. |
722 | +*/ |
723 | + |
724 | namespace metadata |
725 | { |
726 | |
727 | -static char constexpr SIZE_IN_BYTES[] = "size_in_bytes"; // int64_t, >= 0 |
728 | -static char constexpr CREATION_TIME[] = "creation_time"; // String, ISO 8601 format |
729 | -static char constexpr LAST_MODIFIED_TIME[] = "last_modified_time"; // String, ISO 8601 format |
730 | -static char constexpr CHILD_COUNT[] = "child_count"; // int64_t, >= 0 |
731 | -static char constexpr DESCRIPTION[] = "description"; // String |
732 | -static char constexpr DISPLAY_NAME[] = "display_name"; // String |
733 | -static char constexpr FREE_SPACE_BYTES[] = "free_space_bytes"; // int64_t, >= 0 |
734 | -static char constexpr USED_SPACE_BYTES[] = "used_space_bytes"; // int64_t, >= 0 |
735 | -static char constexpr CONTENT_TYPE[] = "content_type"; // String |
736 | -static char constexpr WRITABLE[] = "writable"; // Bool |
737 | -static char constexpr MD5[] = "md5"; // String |
738 | -static char constexpr DOWNLOAD_URL[] = "download_url"; // String |
739 | +// Symbolic constants for well-known metadata keys. |
740 | + |
741 | +// Doxygen bug makes it impossible to document each constant. |
742 | + |
743 | +static char constexpr SIZE_IN_BYTES[] = "size_in_bytes"; // int64_t >= 0 |
744 | +static char constexpr CREATION_TIME[] = "creation_time"; // string, ISO 8601 format |
745 | +static char constexpr LAST_MODIFIED_TIME[] = "last_modified_time"; // string, ISO 8601 format |
746 | +static char constexpr CHILD_COUNT[] = "child_count"; // int64_t >= 0 |
747 | +static char constexpr DESCRIPTION[] = "description"; // string |
748 | +static char constexpr DISPLAY_NAME[] = "display_name"; // string |
749 | +static char constexpr FREE_SPACE_BYTES[] = "free_space_bytes"; // int64_t >= 0 |
750 | +static char constexpr USED_SPACE_BYTES[] = "used_space_bytes"; // int64_t >= 0 |
751 | +static char constexpr CONTENT_TYPE[] = "content_type"; // string |
752 | +static char constexpr WRITABLE[] = "writable"; // int64_t, 0 or 1 |
753 | +static char constexpr MD5[] = "md5"; // string |
754 | +static char constexpr DOWNLOAD_URL[] = "download_url"; // string |
755 | + |
756 | +// A single-element vector containing the key ALL indicates that the client would like all available |
757 | +// metadata to be returned by the provider. |
758 | |
759 | static char constexpr ALL[] = "__ALL__"; |
760 | |
761 | |
762 | === modified file 'include/unity/storage/provider/DownloadJob.h' |
763 | --- include/unity/storage/provider/DownloadJob.h 2016-07-12 02:22:05 +0000 |
764 | +++ include/unity/storage/provider/DownloadJob.h 2017-04-06 07:42:38 +0000 |
765 | @@ -37,25 +37,129 @@ |
766 | class ProviderInterface; |
767 | } |
768 | |
769 | +/** |
770 | +\brief Abstract base class for download implementations. |
771 | + |
772 | +When the runtime calls ProviderBase::download(), you must return |
773 | +a new downloader instance that derives from DownloadJob. Your implementation is responsible |
774 | +for retrieving the file's data from the cloud provider and writing it to |
775 | +the socket provided by the runtime. |
776 | + |
777 | +You can implement your downloader |
778 | +any way you wish, such as by running the download in a separate thread, or |
779 | +by using async I/O driven by the runtime's (or any other) event loop. |
780 | + |
781 | +The runtime invokes all methods on the downloader from the main thread. |
782 | +*/ |
783 | + |
784 | class UNITY_STORAGE_EXPORT DownloadJob |
785 | { |
786 | public: |
787 | + /** |
788 | + \brief Construct a downloader. |
789 | + \param download_id An identifier for this particular download. You can use any non-empty string, |
790 | + as long as it is unique among all downloads that are in progress within the corresponding account. |
791 | + (A simple incrementing counter will work fine, or you can use the download identifier you receive |
792 | + from the cloud service.) |
793 | + The runtime uses the <code>download_id</code> to distinguish different downloads that may be |
794 | + in progress concurrently, and it ensures that each ID can be used only by its corresponding client. |
795 | + */ |
796 | DownloadJob(std::string const& download_id); |
797 | virtual ~DownloadJob(); |
798 | |
799 | + /** |
800 | + \brief Returns the download ID. |
801 | + \return The value of the <code>download_id</code> parameter that was passed to the constructor. |
802 | + */ |
803 | std::string const& download_id() const; |
804 | + |
805 | + /** |
806 | + \brief Returns the socket to write the file contents to. |
807 | + \return A socket that is open for writing. You must write the file contents to this socket (which is |
808 | + connected to the client by the runtime). |
809 | + */ |
810 | int write_socket() const; |
811 | |
812 | - // If the result of the download is reported with either of the |
813 | - // following two functions, then neither cancel() or finish() will |
814 | - // be called. |
815 | + /** |
816 | + \brief Informs the runtime that a download completed successfully. |
817 | + |
818 | + This method makes it unnecessary to wait for the runtime to call finish() in order to confirm |
819 | + whether a download completed successfully. You can call report_complete() as soon as you have |
820 | + written all of a file's data to the download socket and closed it successfully. This can be convenient |
821 | + if a download runs in a separate thread or event loop because there is no need to store success/error state |
822 | + for use in finish(). |
823 | + |
824 | + You can call report_complete() from an arbitrary thread. |
825 | + |
826 | + If you call report_complete(), the runtime guarantees that neither finish() nor cancel() will be called, so |
827 | + you must reclaim any resources associated with the download before calling report_complete(). |
828 | + \see report_error(), finish() |
829 | + */ |
830 | void report_complete(); |
831 | - void report_error(std::exception_ptr p); |
832 | - |
833 | + |
834 | + /** |
835 | + \brief Informs the runtime that a download encountered an error. |
836 | + |
837 | + As for report_complete(), you can call report_error() as soon as you encounter an error during a download |
838 | + (such as losing the connection to the cloud provider, or getting an error when writing to the download |
839 | + socket). |
840 | + |
841 | + You can call report_error() from an arbitrary thread. |
842 | + |
843 | + If you call report_error(), the runtime guarantees that neither finish() nor cancel() will be called, so |
844 | + you must reclaim any resources associated with the download before calling report_error(). |
845 | + |
846 | + \param storage_exception You <i>must</i> pass a StorageException to indicate the reason for the failure. |
847 | + \see report_complete(), finish() |
848 | + */ |
849 | + void report_error(std::exception_ptr storage_exception); |
850 | + |
851 | + /** |
852 | + \brief Cancel this download. |
853 | + |
854 | + The runtime calls this method when a client explicitly cancels a download or crashes. |
855 | + Your implementation should reclaim all resources that are used by the download. In particular, |
856 | + you should stop writing any more data and close the download socket. In addition, |
857 | + you should reclaim any resources (such as open connections) that are associated |
858 | + with the download from the cloud provider (possibly after informing the cloud provider of |
859 | + the cancellation). |
860 | + |
861 | + The runtime guarantees that cancel() will be called only once, and that finish() will not be called |
862 | + if cancel() was called. |
863 | + |
864 | + If any errors are encountered, you <i>must</i> report them by returning a future that stores |
865 | + a StorageException. Do <i>not</i> call report_error() from inside cancel(). |
866 | + |
867 | + \return A future that becomes ready (or contains a StorageException) once cancellation is complete. |
868 | + */ |
869 | virtual boost::future<void> cancel() = 0; |
870 | + |
871 | + /** |
872 | + \brief Finalize this download. |
873 | + |
874 | + The runtime calls this method when a client finishes a download. Your implementation <i>must</i> verify |
875 | + that it has successfully written <i>all</i> of the file's data at this point. |
876 | + If not, it <i>must</i> store a LogicException |
877 | + in the returned future. (This is essential to make sure that the client can distinguish orderly socket |
878 | + closure from disorderly closure and has not received partial data for the file.) |
879 | + |
880 | + You should close the download socket and reclaim any resources (such as open |
881 | + connections) that are associated with the download from the cloud provider. |
882 | + |
883 | + The runtime guarantees that finish() will be called only once, and that cancel() will not be called |
884 | + if finish() was called. |
885 | + |
886 | + If any errors are encountered, you <i>must</i> report them by returning a future that stores |
887 | + a StorageException. Do <i>not</i> call report_error() from inside finish(). |
888 | + |
889 | + Do <i>not</i> call report_complete() from inside finish(). |
890 | + |
891 | + \return A future that becomes ready (or contains a StorageException) once the download is complete. |
892 | + \see report_complete(), report_error() |
893 | + */ |
894 | virtual boost::future<void> finish() = 0; |
895 | |
896 | -protected: |
897 | +private: |
898 | DownloadJob(internal::DownloadJobImpl *p) UNITY_STORAGE_HIDDEN; |
899 | internal::DownloadJobImpl *p_ = nullptr; |
900 | |
901 | |
902 | === modified file 'include/unity/storage/provider/Exceptions.h' |
903 | --- include/unity/storage/provider/Exceptions.h 2017-03-30 02:52:53 +0000 |
904 | +++ include/unity/storage/provider/Exceptions.h 2017-04-06 07:42:38 +0000 |
905 | @@ -37,20 +37,39 @@ |
906 | // - src/qt/internal/unmarshal_error.cpp |
907 | |
908 | /** |
909 | -\brief Base exception class for all server-side exceptions. |
910 | +\brief Abstract base exception class for all server-side exceptions. |
911 | */ |
912 | + |
913 | class UNITY_STORAGE_EXPORT StorageException : public std::exception |
914 | { |
915 | public: |
916 | + /** |
917 | + \brief Constructs a StorageException. |
918 | + \param exception_type The concrete type name of the exception, such as "ConflictException". |
919 | + \param error_message The error message text for the exception. |
920 | + */ |
921 | StorageException(std::string const& exception_type, std::string const& error_message); |
922 | |
923 | protected: |
924 | ~StorageException(); |
925 | |
926 | public: |
927 | + /** |
928 | + \brief Returns an error string. |
929 | + \return The error message prefixed by the exception type, such as "ConflictException: ETag mismatch". |
930 | + */ |
931 | virtual char const* what() const noexcept override; |
932 | |
933 | + /** |
934 | + \brief Returns the exception type name. |
935 | + \return The value of the <code>exception_type</code> parameter that was passed to the constructor. |
936 | + */ |
937 | std::string type() const; |
938 | + |
939 | + /** |
940 | + \brief Returns the except error message. |
941 | + \return The value of the <code>error_message</code> parameter that was passed to the constructor. |
942 | + */ |
943 | std::string error_message() const; |
944 | |
945 | private: |
946 | @@ -61,23 +80,45 @@ |
947 | |
948 | /** |
949 | \brief Indicates errors in the communication between the storage provider and the cloud service. |
950 | + |
951 | +Use this exception for unexpected communication errors, such as the remote server being unreachable |
952 | +or returning garbled data. |
953 | */ |
954 | + |
955 | class UNITY_STORAGE_EXPORT RemoteCommsException : public StorageException |
956 | { |
957 | public: |
958 | + /** |
959 | + \brief Construct a RemoteCommsException. |
960 | + \param error_message The error message for the exception. |
961 | + */ |
962 | RemoteCommsException(std::string const& error_message); |
963 | ~RemoteCommsException(); |
964 | }; |
965 | |
966 | /** |
967 | -\brief Indicates that an item does not exist or could not be found. |
968 | +\brief Indicates that an item does not exist. |
969 | + |
970 | +Use this exception only if the remote cloud service has <i>authoritatively</i> indicated non-existence |
971 | +(such as with an HTTP 404 response). Do not use this exception for non-authoritative errors, such as |
972 | +timeouts or similar. |
973 | */ |
974 | + |
975 | class UNITY_STORAGE_EXPORT NotExistsException : public StorageException |
976 | { |
977 | public: |
978 | + /** |
979 | + \brief Construct a RemoteCommsException. |
980 | + \param error_message The error message for the exception. |
981 | + \param key The name or identity of the non-existent item. |
982 | + */ |
983 | NotExistsException(std::string const& error_message, std::string const& key); |
984 | ~NotExistsException(); |
985 | |
986 | + /** |
987 | + \brief Return the key. |
988 | + \return The value of the <code>key</code> parameter that was passed to the constructor. |
989 | + */ |
990 | std::string key() const; |
991 | |
992 | private: |
993 | @@ -85,15 +126,35 @@ |
994 | }; |
995 | |
996 | /** |
997 | -\brief Indicates that an item cannot be created because it exists already. |
998 | +\brief Indicates that an item already exists. |
999 | + |
1000 | +Use this exception only if the remote cloud service has <i>authoritatively</i> indicated that an operation |
1001 | +cannot be carried out because an item exists already. Do not use this exception for non-authoritative errors, |
1002 | +such as timeouts or similar. |
1003 | */ |
1004 | + |
1005 | class UNITY_STORAGE_EXPORT ExistsException : public StorageException |
1006 | { |
1007 | public: |
1008 | + /** |
1009 | + \brief Construct an ExistsException. |
1010 | + \param error_message The error message for the exception. |
1011 | + \param identity The identity of the item. |
1012 | + \param name The name of the item. |
1013 | + */ |
1014 | ExistsException(std::string const& error_message, std::string const& identity, std::string const& name); |
1015 | ~ExistsException(); |
1016 | |
1017 | + /** |
1018 | + \brief Return the identity. |
1019 | + \return The value of the <code>identity</code> parameter that was passed to the constructor. |
1020 | + */ |
1021 | std::string native_identity() const; |
1022 | + |
1023 | + /** |
1024 | + \brief Return the name of the item. |
1025 | + \return The value of the <code>name</code> parameter that was passed to the constructor. |
1026 | + */ |
1027 | std::string name() const; |
1028 | |
1029 | private: |
1030 | @@ -102,33 +163,42 @@ |
1031 | }; |
1032 | |
1033 | /** |
1034 | -\brief Indicates that an upload or download detected a version mismatch. |
1035 | +\brief Indicates that an upload or download detected a ETag mismatch. |
1036 | */ |
1037 | + |
1038 | class UNITY_STORAGE_EXPORT ConflictException : public StorageException |
1039 | { |
1040 | public: |
1041 | + /** |
1042 | + \brief Construct a ConflictException. |
1043 | + \param error_message The error message for the exception. |
1044 | + */ |
1045 | ConflictException(std::string const& error_message); |
1046 | ~ConflictException(); |
1047 | }; |
1048 | |
1049 | /** |
1050 | -\brief Indicates that an operation failed because the authentication credentials are invalid or expired. |
1051 | +\brief Indicates that an operation failed because the credentials are invalid or have expired. |
1052 | |
1053 | A provider implementation must throw this exception if it cannot reach |
1054 | -its provider because the credentials are invalid. Do not throw this |
1055 | +its provider because the credentials are invalid. Do <i>not</i> throw this |
1056 | exception if the credentials are valid, but an operation failed due to |
1057 | insufficient permission for an item (such as an attempt to write to a |
1058 | read-only file). |
1059 | |
1060 | -Typically, this will cause the request to be retried after refreshing |
1061 | -the authentication credentials, but may be returned to the client on |
1062 | -repeated failures. |
1063 | +Typically, this exception will cause the runtime to retry the operation after refreshing |
1064 | +the credentials; the exception may be returned to the client on repeated failures. |
1065 | |
1066 | \see PermissionException |
1067 | */ |
1068 | + |
1069 | class UNITY_STORAGE_EXPORT UnauthorizedException : public StorageException |
1070 | { |
1071 | public: |
1072 | + /** |
1073 | + \brief Construct an UnauthorizedException. |
1074 | + \param error_message The error message for the exception. |
1075 | + */ |
1076 | UnauthorizedException(std::string const& error_message); |
1077 | ~UnauthorizedException(); |
1078 | }; |
1079 | @@ -139,69 +209,126 @@ |
1080 | A provider implementation must throw this exception if it can |
1081 | authenticate with its provider, but the provider denied the operation |
1082 | due to insufficient permission for an item (such as an attempt to |
1083 | -write to a read-only file). Do not throw this exception for failure to |
1084 | +write to a read-only file). Do <i>not</i> throw this exception for failure to |
1085 | authenticate with the provider. |
1086 | |
1087 | \see UnauthorizedException |
1088 | */ |
1089 | + |
1090 | class UNITY_STORAGE_EXPORT PermissionException : public StorageException |
1091 | { |
1092 | public: |
1093 | + /** |
1094 | + \brief Construct a PermissionException. |
1095 | + \param error_message The error message for the exception. |
1096 | + */ |
1097 | PermissionException(std::string const& error_message); |
1098 | ~PermissionException(); |
1099 | }; |
1100 | |
1101 | /** |
1102 | -\brief Indicates that an update failed because the provider ran out of space. |
1103 | +\brief Indicates that an update failed because the provider ran out of space or exceeded |
1104 | +the maximum number of files or folders. |
1105 | */ |
1106 | + |
1107 | class UNITY_STORAGE_EXPORT QuotaException : public StorageException |
1108 | { |
1109 | public: |
1110 | + /** |
1111 | + \brief Construct a QuotaException. |
1112 | + \param error_message The error message for the exception. |
1113 | + */ |
1114 | QuotaException(std::string const& error_message); |
1115 | ~QuotaException(); |
1116 | }; |
1117 | |
1118 | /** |
1119 | \brief Indicates that an upload or download was cancelled before it could complete. |
1120 | +\note Due to the way the provider API is structured, you will not ever need to throw this exception. It is provided for |
1121 | +completeness and to allow for provider implementations that do not use the storage framework API. |
1122 | */ |
1123 | + |
1124 | class UNITY_STORAGE_EXPORT CancelledException : public StorageException |
1125 | { |
1126 | public: |
1127 | + /** |
1128 | + \brief Construct a CancelledException. |
1129 | + \param error_message The error message for the exception. |
1130 | + */ |
1131 | CancelledException(std::string const& error_message); |
1132 | ~CancelledException(); |
1133 | }; |
1134 | |
1135 | /** |
1136 | -\brief Indicates incorrect use of the API, such as calling methods in the wrong order. |
1137 | +\brief Indicates incorrect use of the API. |
1138 | + |
1139 | +Use this exception for errors that the client could avoid, such as calling methods in the wrong order or attempting |
1140 | +to list the contents of file (as opposed to a folder). |
1141 | +\note Do <i>not</i> throw this exception to indicate arguments that are malformed or out of range. |
1142 | +\see InvalidArgumentException |
1143 | */ |
1144 | class UNITY_STORAGE_EXPORT LogicException : public StorageException |
1145 | { |
1146 | public: |
1147 | + /** |
1148 | + \brief Construct a LogicException. |
1149 | + \param error_message The error message for the exception. |
1150 | + */ |
1151 | LogicException(std::string const& error_message); |
1152 | ~LogicException(); |
1153 | }; |
1154 | |
1155 | /** |
1156 | -\brief Indicates an invalid parameter, such as a negative value when a positive one was |
1157 | +\brief Indicates an invalid parameter. |
1158 | + |
1159 | +Use this exception for errors such as a negative parameter value when a positive one was |
1160 | expected, or a string that does not parse correctly or is empty when it should be non-empty. |
1161 | +\note Do <i>not</i> throw this exception to indicate incorrect use of the API. |
1162 | +\see LogicException |
1163 | */ |
1164 | + |
1165 | class UNITY_STORAGE_EXPORT InvalidArgumentException : public StorageException |
1166 | { |
1167 | public: |
1168 | + /** |
1169 | + \brief Construct an InvalidArgumentException. |
1170 | + \param error_message The error message for the exception. |
1171 | + */ |
1172 | InvalidArgumentException(std::string const& error_message); |
1173 | ~InvalidArgumentException(); |
1174 | }; |
1175 | |
1176 | /** |
1177 | -\brief Indicates a system error, such as failure to create a file or folder, |
1178 | -or any other (usually non-recoverable) kind of error that should not arise during normal operation. |
1179 | +\brief Indicates a system error. |
1180 | + |
1181 | +This is a generic "catch-all" exception that you can throw to indicate unexpected errors that do not fit into |
1182 | +any of the other categories. For example, ResourceException is appropriate to indicate out of |
1183 | +file descriptors, failure to locate a configuration file, or any other unexpected system error. You should |
1184 | +provide as much contextual information about such errors as possible. In particular, unexpected errors |
1185 | +typically need to be diagnosed from log files. This means that you should provide, at least, the full error |
1186 | +message you received from the cloud service (or the operating system), together with all other relevant |
1187 | +details, such as the name of the file, the URL of a failed request, any HTTP error information, and so on. |
1188 | */ |
1189 | + |
1190 | class UNITY_STORAGE_EXPORT ResourceException : public StorageException |
1191 | { |
1192 | public: |
1193 | + /** |
1194 | + \brief Construct an InvalidArgumentException. |
1195 | + \param error_message The error message for the exception. |
1196 | + \param error_code The system (or library) error code for the exception. |
1197 | + In case of system call failures, <code>error_code</code> should be the value of <code>errno</code>. |
1198 | + If the error code represents something else, such as a <code>QFileDevice::FileError</code>, |
1199 | + also indicate in the error message what that error code is; otherwise, it can be |
1200 | + impossible to diagnose a problem, especially when looking at log files after the fact. |
1201 | + */ |
1202 | ResourceException(std::string const& error_message, int error_code); |
1203 | ~ResourceException(); |
1204 | |
1205 | + /** |
1206 | + \brief Return the error code. |
1207 | + \return The value of the <code>error_code</code> parameter that was passed to the constructor. |
1208 | + */ |
1209 | int error_code() const noexcept; |
1210 | |
1211 | private: |
1212 | @@ -209,12 +336,25 @@ |
1213 | }; |
1214 | |
1215 | /** |
1216 | -\brief Indicates that the server side caught an exception that does not derive from |
1217 | -StorageException, such as a std::exception, or caught some other unknown type (such as `int`). |
1218 | +\brief Indicates that a provider threw an exception not derived from StorageException. |
1219 | + |
1220 | +The runtime translates all exception that are thrown by a provider and that do not derive from StorageException |
1221 | +to UnknownException. |
1222 | + |
1223 | +Do not throw this exception explicitly; is is a "catch-all" exception that the runtime uses to |
1224 | +indicate that something completely unexpected happened. (If a client receives this exception from your |
1225 | +provider, chances are that you have forgotten to handle an error condition somewhere.) |
1226 | */ |
1227 | + |
1228 | class UNITY_STORAGE_EXPORT UnknownException : public StorageException |
1229 | { |
1230 | public: |
1231 | + /** |
1232 | + \brief Construct an UnknownArgumentException. |
1233 | + \param error_message The error message for the exception. This is the string returned by <code>what()</code> |
1234 | + for <code>std::exception</code> or "Unknown exception" for exceptions that do not derive |
1235 | + from <code>std::exception</code>. |
1236 | + */ |
1237 | UnknownException(std::string const& error_message); |
1238 | ~UnknownException(); |
1239 | }; |
1240 | |
1241 | === modified file 'include/unity/storage/provider/Item.h' |
1242 | --- include/unity/storage/provider/Item.h 2017-03-17 02:51:05 +0000 |
1243 | +++ include/unity/storage/provider/Item.h 2017-04-06 07:42:38 +0000 |
1244 | @@ -33,17 +33,63 @@ |
1245 | namespace provider |
1246 | { |
1247 | |
1248 | +/** |
1249 | +\brief The type of a metadata item (key–value pair). |
1250 | + |
1251 | +For boolean values, use <code>int64_t</code> with a zero or non-zero value to |
1252 | +indicate <code>false</code> or <code>true</code>. |
1253 | + |
1254 | +Well-known metadata keys are defined in \ref common.h. |
1255 | +*/ |
1256 | // Note: When growing the set of supported variant types, add new types |
1257 | // to the *end* of the list, and update the marshaling code in dbusmarshal.cpp. |
1258 | typedef boost::variant<std::string, int64_t> MetadataValue; |
1259 | |
1260 | +/** |
1261 | +\brief Details about a storage item. |
1262 | +*/ |
1263 | + |
1264 | struct UNITY_STORAGE_EXPORT Item |
1265 | { |
1266 | + /** |
1267 | + \brief The unique identity of the item. |
1268 | + |
1269 | + See <a href="index.html#identity">File and Folder Identity</a> for detailed semantics. |
1270 | + */ |
1271 | std::string item_id; |
1272 | + |
1273 | + /** |
1274 | + \brief The list of parent folder identities. |
1275 | + |
1276 | + \note Depending on the provider, it is possible for a file or folder to have more than one |
1277 | + parent folder. |
1278 | + */ |
1279 | std::vector<std::string> parent_ids; |
1280 | + |
1281 | + /** |
1282 | + \brief The name of a file or folder. |
1283 | + |
1284 | + \note A provider may create a file or folder with a name other than the name that was requested by the |
1285 | + client (such as folding upper case to lower case). This field contains the name of the item as created |
1286 | + by the provider, not the name requested by the client. |
1287 | + */ |
1288 | std::string name; |
1289 | + |
1290 | + /** |
1291 | + \brief The ETag of a file. |
1292 | + |
1293 | + \note For folders, the ETag is may be empty because not all providers support ETags for folders. |
1294 | + */ |
1295 | std::string etag; |
1296 | + |
1297 | + /** |
1298 | + \brief The item type (file, folder, or root,). |
1299 | + */ |
1300 | unity::storage::ItemType type; |
1301 | + |
1302 | + /** |
1303 | + \brief Additional metadata for a file or folder. |
1304 | + */ |
1305 | std::map<std::string, MetadataValue> metadata; |
1306 | }; |
1307 | |
1308 | |
1309 | === modified file 'include/unity/storage/provider/ProviderBase.h' |
1310 | --- include/unity/storage/provider/ProviderBase.h 2017-03-16 02:50:03 +0000 |
1311 | +++ include/unity/storage/provider/ProviderBase.h 2017-04-06 07:42:38 +0000 |
1312 | @@ -42,15 +42,41 @@ |
1313 | class DownloadJob; |
1314 | class UploadJob; |
1315 | |
1316 | +/** |
1317 | +\brief Security related information for an operation invocation. |
1318 | + |
1319 | +Each provider method has a trailing parameter of type Context that provides |
1320 | +access to |
1321 | +<a href="https://help.ubuntu.com/lts/serverguide/apparmor.html">Apparmor</a> details as |
1322 | +well as credentials for the cloud provider. |
1323 | +*/ |
1324 | + |
1325 | struct UNITY_STORAGE_EXPORT Context |
1326 | { |
1327 | - uid_t uid; |
1328 | - pid_t pid; |
1329 | - std::string security_label; |
1330 | + uid_t uid; /*!< The user ID of the client process. */ |
1331 | + pid_t pid; /*!< The process ID of the client process. */ |
1332 | + std::string security_label; /*!< The Apparmor security label of the client process. */ |
1333 | |
1334 | - Credentials credentials; |
1335 | + Credentials credentials; /*!< Credentials to authenticate with the cloud provider. */ |
1336 | }; |
1337 | |
1338 | +/** |
1339 | +\brief Abstract base class for provider implementations. |
1340 | + |
1341 | +The runtime calls methods on this class in response to incoming requests from clients. Each method |
1342 | +implements a specific operation on the storage backend. |
1343 | + |
1344 | +All methods are called from the main thread and must not block. Methods indicate error conditions to the runtime |
1345 | +by throwing exceptions (which are caught and handled by the runtime). Alternatively, methods can return a future |
1346 | +that stores an exception to indicate an error. |
1347 | + |
1348 | +\note Besides the explicitly listed exceptions for specific error conditions, all methods |
1349 | +must indicate other errors by throwing an exception derived from StorageException, such |
1350 | +as PermissionException or ResourceException (see <a href="index.html#provider-error-handling">Error Handling</a>). |
1351 | +If an exception that does not derive from StorageException is thrown a provider method, the runtime returns a |
1352 | +generic UnknownException to the client. |
1353 | +*/ |
1354 | + |
1355 | class UNITY_STORAGE_EXPORT ProviderBase : public std::enable_shared_from_this<ProviderBase> |
1356 | { |
1357 | public: |
1358 | @@ -60,41 +86,201 @@ |
1359 | ProviderBase(ProviderBase const& other) = delete; |
1360 | ProviderBase& operator=(ProviderBase const& other) = delete; |
1361 | |
1362 | - virtual boost::future<ItemList> roots(std::vector<std::string> const& keys, Context const& context) = 0; |
1363 | - virtual boost::future<std::tuple<ItemList,std::string>> list( |
1364 | - std::string const& item_id, std::string const& page_token, |
1365 | - std::vector<std::string> const& keys, |
1366 | - Context const& context) = 0; |
1367 | - virtual boost::future<ItemList> lookup( |
1368 | - std::string const& parent_id, std::string const& name, std::vector<std::string> const& keys, |
1369 | - Context const& context) = 0; |
1370 | - virtual boost::future<Item> metadata(std::string const& item_id, std::vector<std::string> const& keys, |
1371 | - Context const& context) = 0; |
1372 | - |
1373 | - virtual boost::future<Item> create_folder( |
1374 | - std::string const& parent_id, std::string const& name, std::vector<std::string> const& keys, |
1375 | - Context const& context) = 0; |
1376 | - |
1377 | - virtual boost::future<std::unique_ptr<UploadJob>> create_file( |
1378 | - std::string const& parent_id, std::string const& name, |
1379 | - int64_t size, std::string const& content_type, bool allow_overwrite, std::vector<std::string> const& keys, |
1380 | - Context const& context) = 0; |
1381 | - virtual boost::future<std::unique_ptr<UploadJob>> update( |
1382 | - std::string const& item_id, int64_t size, std::string const& old_etag, std::vector<std::string> const& keys, |
1383 | - Context const& context) = 0; |
1384 | - |
1385 | - virtual boost::future<std::unique_ptr<DownloadJob>> download( |
1386 | - std::string const& item_id, std::string const& match_etag, |
1387 | - Context const& context) = 0; |
1388 | - |
1389 | - virtual boost::future<void> delete_item( |
1390 | - std::string const& item_id, Context const& context) = 0; |
1391 | - virtual boost::future<Item> move( |
1392 | - std::string const& item_id, std::string const& new_parent_id, |
1393 | - std::string const& new_name, std::vector<std::string> const& keys, Context const& context) = 0; |
1394 | - virtual boost::future<Item> copy( |
1395 | - std::string const& item_id, std::string const& new_parent_id, |
1396 | - std::string const& new_name, std::vector<std::string> const& keys, Context const& context) = 0; |
1397 | + /** |
1398 | + \brief Return the root folder (or folders) of the provider. |
1399 | + \param keys The keys of metadata items that the client wants to receive. |
1400 | + \param context The security context of the operation. |
1401 | + \return A (non-empty) list of roots. |
1402 | + */ |
1403 | + virtual boost::future<ItemList> roots(std::vector<std::string> const& keys, |
1404 | + Context const& context) = 0; |
1405 | + |
1406 | + /** |
1407 | + \brief Return a (non-recursive) list of the contents of a folder. |
1408 | + \param item_id The identity of the folder. |
1409 | + \param page_token A token identifying the next page of results (empty for the initial request). |
1410 | + \param keys The keys of metadata items that the client wants to receive. |
1411 | + \param context The security context of the operation. |
1412 | + \return A tuple containing a number of child items, plus a new page token. The page token |
1413 | + allows the method to return the results in multiple "pages". For the initial request, the runtime passes |
1414 | + an empty <code>page_token</code>. The method returns new page token to indicate whether there are more |
1415 | + results to be retrieved. If the returned token is non-empty, the runtime calls the method again, |
1416 | + passing the token that was returned by the previous call. To indicate that all results were retrieved, the method |
1417 | + must return an empty page token to the runtime. |
1418 | + \throws InvalidArgumentException <code>item_id</code> or <code>page_token</code> are invalid. |
1419 | + \throws NotExistsException <code>item_id</code> does not exist. |
1420 | + \throws LogicException If at all possible, the implementation should throw a LogicException |
1421 | + if <code>item_id</code> denotes a file. If a provider cannot distinguish between |
1422 | + attempts to list a file and other errors, it <i>must</i> return an empty list instead. |
1423 | + */ |
1424 | + virtual boost::future<std::tuple<ItemList,std::string>> list(std::string const& item_id, |
1425 | + std::string const& page_token, |
1426 | + std::vector<std::string> const& keys, |
1427 | + Context const& context) = 0; |
1428 | + |
1429 | + /** |
1430 | + \brief Retrieve a file or folder within a parent folder by name. |
1431 | + \param parent_id The identity of the parent folder. |
1432 | + \param name The name of the file or folder. |
1433 | + \param keys The keys of metadata items that the client wants to receive. |
1434 | + \param context The security context of the operation. |
1435 | + \return A non-empty list of items with the given name. The list may contain more than one entry |
1436 | + if the provider allows non-unique names for items within a folder. |
1437 | + The list may also contain more than one entry with the same identity if the provider allows |
1438 | + the same item to have more than one name. |
1439 | + \throws InvalidArgumentException <code>parent_id</code> or <code>name</code> are invalid. |
1440 | + \throws NotExistsException <code>parent_id</code> does not exist or does not contain a file or folder with |
1441 | + the given <code>name</code>. (Do <i>not</i> return an empty list in this case.) |
1442 | + */ |
1443 | + virtual boost::future<ItemList> lookup(std::string const& parent_id, |
1444 | + std::string const& name, |
1445 | + std::vector<std::string> const& keys, |
1446 | + Context const& context) = 0; |
1447 | + |
1448 | + /** |
1449 | + \brief Retrieve a file or folder by its identity. |
1450 | + \param item_id The identity of the item. |
1451 | + \param keys The keys of metadata items that the client wants to receive. |
1452 | + \param context The security context of the operation. |
1453 | + \return The item. |
1454 | + \throws InvalidArgumentException <code>item_id</code> is invalid. |
1455 | + \throws NotExistsException No file or folder with the given identity exists. |
1456 | + */ |
1457 | + virtual boost::future<Item> metadata(std::string const& item_id, |
1458 | + std::vector<std::string> const& keys, |
1459 | + Context const& context) = 0; |
1460 | + |
1461 | + /** |
1462 | + \brief Create a new folder. |
1463 | + \param parent_id The identity of the parent folder. |
1464 | + \param name The name of the new folder. |
1465 | + \param keys The keys of metadata items that the client wants to receive. |
1466 | + \param context The security context of the operation. |
1467 | + \return The item representing the folder. |
1468 | + \throws InvalidArgumentException <code>parent_id</code> or <code>name</code> are invalid. |
1469 | + \throws ExistsException An item with the given <code>name</code> exists already. |
1470 | + */ |
1471 | + virtual boost::future<Item> create_folder(std::string const& parent_id, |
1472 | + std::string const& name, |
1473 | + std::vector<std::string> const& keys, |
1474 | + Context const& context) = 0; |
1475 | + |
1476 | + /** |
1477 | + \brief Create a new file. |
1478 | + \param parent_id The identity of the parent folder. |
1479 | + \param name The name of the new file. |
1480 | + \param size The size of the file contents in bytes. |
1481 | + \param content_type The mime type of the file. If empty, the provider may (or may not) determine the mime type automatically. |
1482 | + \param allow_overwrite If true, the file will be created even if a file with the same name exists already. |
1483 | + \param keys The keys of metadata items that the client wants to receive. |
1484 | + \param context The security context of the operation. |
1485 | + \return An UploadJob that will read any data provided by the client and write it to the new file. |
1486 | + \throws InvalidArgumentException <code>parent_id</code> or <code>name</code> are invalid. |
1487 | + \throws ExistsException A file with the given <code>name</code> exists already and |
1488 | + <code>allow_overwrite</code> is <code>false</code>, or a folder with the given <code>name</code> exists already. |
1489 | + */ |
1490 | + // TODO: The runtime should check that size is non-negative, so the provider implementation can rely on this. |
1491 | + virtual boost::future<std::unique_ptr<UploadJob>> create_file(std::string const& parent_id, |
1492 | + std::string const& name, |
1493 | + int64_t size, |
1494 | + std::string const& content_type, |
1495 | + bool allow_overwrite, |
1496 | + std::vector<std::string> const& keys, |
1497 | + Context const& context) = 0; |
1498 | + |
1499 | + /** |
1500 | + \brief Update the contents of a file. |
1501 | + \param item_id The identity of the file. |
1502 | + \param size The size of the file contents in bytes. |
1503 | + \param old_etag The ETag of the existing file (empty if the file should be overwritten). |
1504 | + \param keys The keys of metadata items that the client wants to receive. |
1505 | + \param context The security context of the operation. |
1506 | + \return An UploadJob that will read any data provided by the client and write it to the file. |
1507 | + \throws InvalidArgumentException <code>item_id</code> or <code>size</code> are invalid. |
1508 | + \throws ConflictException The file's ETag does not match the given (non-empty) <code>old_etag</code>. |
1509 | + \throws LogicException The <code>item_id</code> denotes a folder. |
1510 | + */ |
1511 | + // TODO: The runtime should check that size is non-negative, so the provider implementation can rely on this. |
1512 | + virtual boost::future<std::unique_ptr<UploadJob>> update(std::string const& item_id, |
1513 | + int64_t size, |
1514 | + std::string const& old_etag, |
1515 | + std::vector<std::string> const& keys, |
1516 | + Context const& context) = 0; |
1517 | + |
1518 | + /** |
1519 | + \brief Download the contents of a file. |
1520 | + \param item_id The identity of the file. |
1521 | + \param match_etag The ETag of the existing file (empty if the file should be downloaded unconditionally). |
1522 | + \param context The security context of the operation. |
1523 | + \return A DownloadJob that will write the file data to the client socket. |
1524 | + \throws InvalidArgumentException <code>item_id</code> or <code>name</code> are invalid. |
1525 | + \throws NotExistsException <code>item_id</code> does not exist. |
1526 | + \throws LogicException The <code>item_id</code> denotes a folder. |
1527 | + \throws ConflictException The ETag for <code>item_id</code> does not match the given |
1528 | + (non-empty) <code>match_etag</code>. |
1529 | + */ |
1530 | + virtual boost::future<std::unique_ptr<DownloadJob>> download(std::string const& item_id, |
1531 | + std::string const& match_etag, |
1532 | + Context const& context) = 0; |
1533 | + |
1534 | + /** |
1535 | + \brief Delete an item. |
1536 | + |
1537 | + Deletion is recursive. If <code>item_id</code> denotes a folder, the folder and all its contents are deleted. |
1538 | + \param item_id The identity of the file or folder. |
1539 | + \param context The security context of the operation. |
1540 | + \throws InvalidArgumentException <code>item_id</code> is invalid. |
1541 | + \throws PermissionException <code>item_id</code> denotes a root (or permission was denied for another reason). |
1542 | + */ |
1543 | + virtual boost::future<void> delete_item(std::string const& item_id, |
1544 | + Context const& context) = 0; |
1545 | + |
1546 | + /** |
1547 | + \brief Move and/or rename an item. |
1548 | + |
1549 | + \param item_id The identity of the file or folder to be moved. |
1550 | + \param new_parent_id The identity of the folder to move the file or folder to. If <code>new_parent_id</code> |
1551 | + is the same as the existing parent of <code>item_id</code>, the file or folder is to be renamed within its |
1552 | + parent folder. |
1553 | + \param new_name The new name of the file (which can be the same as the old name if <code>new_parent_id</code> |
1554 | + differs). |
1555 | + \param keys The keys of metadata items that the client wants to receive. |
1556 | + \param context The security context of the operation. |
1557 | + \return The moved or renamed item. |
1558 | + \throws InvalidArgumentException <code>item_id</code>, <code>new_parent_id</code>, or <code>new_name</code> |
1559 | + are invalid. |
1560 | + \throws ExistsException The folder <code>new_parent_id</code> already contains an item with |
1561 | + name <code>new_name</code>. |
1562 | + \throws PermissionException <code>item_id</code> denotes a root (or permission was denied for another reason). |
1563 | + */ |
1564 | + virtual boost::future<Item> move(std::string const& item_id, |
1565 | + std::string const& new_parent_id, |
1566 | + std::string const& new_name, |
1567 | + std::vector<std::string> const& keys, |
1568 | + Context const& context) = 0; |
1569 | + |
1570 | + /** |
1571 | + \brief Copy an item. |
1572 | + |
1573 | + Copy is recursive, so copying a folder copies the folder's contents. |
1574 | + \param item_id The identity of the file or folder to be copied. |
1575 | + \param new_parent_id The identity of the folder to copy the file or folder to. If <code>new_parent_id</code> |
1576 | + is the same as the existing parent of <code>item_id</code>, the file or folder is to be copied within its |
1577 | + parent folder. |
1578 | + \param new_name The new name of the file or folder. |
1579 | + \param keys The keys of metadata items that the client wants to receive. |
1580 | + \param context The security context of the operation. |
1581 | + \return The copied item. |
1582 | + \throws InvalidArgumentException <code>item_id</code>, <code>new_parent_id</code>, or <code>new_name</code> |
1583 | + are invalid. |
1584 | + \throws ExistsException The folder <code>new_parent_id</code> already contains an item with |
1585 | + name <code>new_name</code>. |
1586 | + */ |
1587 | + virtual boost::future<Item> copy(std::string const& item_id, |
1588 | + std::string const& new_parent_id, |
1589 | + std::string const& new_name, |
1590 | + std::vector<std::string> const& keys, |
1591 | + Context const& context) = 0; |
1592 | }; |
1593 | |
1594 | } |
1595 | |
1596 | === modified file 'include/unity/storage/provider/Server.h' |
1597 | --- include/unity/storage/provider/Server.h 2017-03-16 03:30:18 +0000 |
1598 | +++ include/unity/storage/provider/Server.h 2017-04-06 07:42:38 +0000 |
1599 | @@ -37,29 +37,141 @@ |
1600 | |
1601 | class ProviderBase; |
1602 | |
1603 | +/** |
1604 | +\brief Base class to register a storage provider with the runtime. |
1605 | + |
1606 | +\anchor extra-args This class allows you to register a provider that requires additional |
1607 | +parameters with the storage framework runtime, by overriding the |
1608 | +make_provider() method. (If your provider does not require constructor |
1609 | +arguments, use the predefined Server template instead.) |
1610 | + |
1611 | +For example, here is how you can instantiate a provider that requires |
1612 | +a <code>some_value</code> constructor argument: |
1613 | + |
1614 | +\code{.cpp} |
1615 | +class MyCloudProvider : public ProviderBase |
1616 | +{ |
1617 | +public: |
1618 | + MyCloudProvider(int some_value, |
1619 | + string const& bus_name, |
1620 | + string const& service_id) |
1621 | + : ProviderBase(bus_name, service_id) |
1622 | + { |
1623 | + } |
1624 | + // ... |
1625 | +}; |
1626 | + |
1627 | +class MyCloudServer : public ServerBase |
1628 | +{ |
1629 | +public: |
1630 | + MyCloudServer(int some_value, |
1631 | + string const& bus_name, |
1632 | + string const& account_service_id) |
1633 | + : some_value_(some_value) |
1634 | + , bus_name_(bus_name) |
1635 | + , account_service_id_(account_service_id) |
1636 | + { |
1637 | + } |
1638 | + |
1639 | +protected: |
1640 | + shared_ptr<ProviderBase> make_provider() override |
1641 | + { |
1642 | + return make_shared<MyCloudProvider>(some_value, bus_name, account_service_id); |
1643 | + } |
1644 | + |
1645 | +private: |
1646 | + int some_value_; |
1647 | + string bus_name_; |
1648 | + string account_service_id_; |
1649 | +}; |
1650 | + |
1651 | +int main(int argc, char* argv[]) |
1652 | +{ |
1653 | + string const bus_name = "com.acme.StorageFramework.Provider.MyCloud"; |
1654 | + string const account_service_id = "storage-provider-mycloud"; |
1655 | + |
1656 | + try |
1657 | + { |
1658 | + MyCloudServer server(99, bus_name, account_service_id); |
1659 | + server.init(argc, argv); |
1660 | + return server.run(); |
1661 | + } |
1662 | + catch (std::exception const& e) |
1663 | + { |
1664 | + cerr << argv[0] << ": " << e.what() << endl; |
1665 | + return 1; |
1666 | + } |
1667 | +} |
1668 | +\endcode |
1669 | +*/ |
1670 | + |
1671 | class UNITY_STORAGE_EXPORT ServerBase |
1672 | { |
1673 | public: |
1674 | + /** |
1675 | + \brief Constructs a server instance. |
1676 | + \param bus_name The DBus name of the provider service on the session bus. |
1677 | + \param account_service_id The service ID with which the provider is known to |
1678 | + <a href="https://help.ubuntu.com/stable/ubuntu-help/accounts.html">Online Accounts</a>. |
1679 | + */ |
1680 | ServerBase(std::string const& bus_name, std::string const& account_service_id); |
1681 | virtual ~ServerBase(); |
1682 | |
1683 | + /** |
1684 | + \brief Initializes the storage framework runtime. |
1685 | + \param argc, argv The runtime passes these parameters through to |
1686 | + <a href="http://doc.qt.io/qt-5/qcoreapplication.html">QCoreApplication</a>. |
1687 | + \throws StorageException |
1688 | + */ |
1689 | void init(int& argc, char** argv); |
1690 | + |
1691 | + /** |
1692 | + \brief Starts an event loop for the service. |
1693 | + |
1694 | + You <i>must</i> call init() before calling this method. |
1695 | + \return In case of an error, run() returns non-zero status. |
1696 | + */ |
1697 | int run(); |
1698 | |
1699 | protected: |
1700 | + /** |
1701 | + \brief Factory method to instantiate a provider. |
1702 | + The runtime calls this method to instantiate a provider as part |
1703 | + of the init() method. You can override this method if you need |
1704 | + to pass additional arguments to the constructor of your provider class. |
1705 | + (see \ref extra-args "example code"). |
1706 | + \return A <code>shared_ptr</code> to the provider instance. |
1707 | + */ |
1708 | virtual std::shared_ptr<ProviderBase> make_provider() = 0; |
1709 | + |
1710 | private: |
1711 | std::unique_ptr<internal::ServerImpl> p_; |
1712 | |
1713 | friend class internal::ServerImpl; |
1714 | }; |
1715 | |
1716 | +/** |
1717 | +\brief Default ServerBase implementation for providers with a default constructor. |
1718 | + |
1719 | +You can use this class to connect your provider class to the runtime, provided |
1720 | +the class has a default constructor. If you need to pass additional arguments |
1721 | +to the constructor, you must derive a class from ServerBase and override the |
1722 | +ServerBase::make_provider() factory method (see \ref extra-args "example code"). |
1723 | +*/ |
1724 | + |
1725 | template <typename T> |
1726 | class Server : public ServerBase |
1727 | { |
1728 | public: |
1729 | using ServerBase::ServerBase; |
1730 | + |
1731 | protected: |
1732 | + /** |
1733 | + \brief Factory method to instantiate a provider with a default constructor. |
1734 | + |
1735 | + The runtime calls this method to create your provider instance of type T. |
1736 | + \return A <code>shared_ptr</code> to the provider instance. |
1737 | + */ |
1738 | std::shared_ptr<ProviderBase> make_provider() override { |
1739 | return std::make_shared<T>(); |
1740 | } |
1741 | |
1742 | === modified file 'include/unity/storage/provider/TempfileUploadJob.h' |
1743 | --- include/unity/storage/provider/TempfileUploadJob.h 2016-08-24 10:52:17 +0000 |
1744 | +++ include/unity/storage/provider/TempfileUploadJob.h 2017-04-06 07:42:38 +0000 |
1745 | @@ -36,21 +36,59 @@ |
1746 | class TempfileUploadJobImpl; |
1747 | } |
1748 | |
1749 | +/** |
1750 | +\brief Helper class to store the contents of an upload in a temporary file. |
1751 | + |
1752 | +For uploads, a provider implementation must decide whether to upload data to the cloud provider |
1753 | +immediately (as soon as the data arrives from the client) or whether to buffer the data locally first |
1754 | +and upload it to the cloud provider once the client successfully finalizes the upload. Uploading |
1755 | +immediately has the down side that it is more likely to fail. This is an issue particularly |
1756 | +for mobile devices, where applications may be suspended for extended periods, and where connectivity |
1757 | +is often lost without warning. Uploads of large files (such as media files) are unlikely |
1758 | +to ever complete in this case: if the user swipes away from the client application, no more data arrives |
1759 | +at the provider until the application resumes, by which time the connection to the cloud provider has |
1760 | +most likely timed out. |
1761 | + |
1762 | +Especially for files that are more than a few kilobytes in size, it is usually necessary to write the data |
1763 | +to a local file first and to write it to the cloud provider only once all of data has been sent by the client. |
1764 | +(The provider service does not get suspended and will not exit while an upload is in progress.) |
1765 | + |
1766 | +TempfileUploadJob is a helper class that implements an uploader that writes the data to a temporary file. |
1767 | +The file is unlinked once the TempfileUploadJob is destroyed. You implementation must provide finish() and |
1768 | +cancel(). |
1769 | + |
1770 | +*/ |
1771 | + |
1772 | class UNITY_STORAGE_EXPORT TempfileUploadJob : public UploadJob |
1773 | { |
1774 | public: |
1775 | + /** |
1776 | + \brief Construct a TempfileUploadJob. |
1777 | + \param upload_id An identifier for this particular upload. You can use any non-empty string, |
1778 | + as long as it is unique among all uploads that are in progress within this provider. |
1779 | + The runtime uses the <code>upload_id</code> to distinguish different uploads that may be |
1780 | + in progress concurrently. |
1781 | + */ |
1782 | TempfileUploadJob(std::string const& upload_id); |
1783 | virtual ~TempfileUploadJob(); |
1784 | |
1785 | + /** |
1786 | + \brief Returns the name of the file. |
1787 | + \return The full path name of the temporary file. |
1788 | + */ |
1789 | std::string file_name() const; |
1790 | |
1791 | - // This function should be called from your finish() |
1792 | - // implementation to read the remaining data from the socket. If |
1793 | - // the client has not closed the socket as expected, LogicError |
1794 | - // will be thrown. |
1795 | + /** |
1796 | + \brief Reads any unread data from the upload socket and writes it to the temporary file. |
1797 | + |
1798 | + You must call drain() from your UploadJob::finish() method. Its implementation reads any remaining |
1799 | + data from the upload socket and writes it to the temporary file. If an error occurs, |
1800 | + drain() throws an exception. |
1801 | + \throws LogicException The client did not close its end of the socket. |
1802 | + */ |
1803 | void drain(); |
1804 | |
1805 | -protected: |
1806 | +private: |
1807 | TempfileUploadJob(internal::TempfileUploadJobImpl *p) UNITY_STORAGE_HIDDEN; |
1808 | }; |
1809 | |
1810 | |
1811 | === modified file 'include/unity/storage/provider/UploadJob.h' |
1812 | --- include/unity/storage/provider/UploadJob.h 2016-08-24 10:52:17 +0000 |
1813 | +++ include/unity/storage/provider/UploadJob.h 2017-04-06 07:42:38 +0000 |
1814 | @@ -39,28 +39,126 @@ |
1815 | class UploadJobImpl; |
1816 | } |
1817 | |
1818 | +class TempfileUploadJob; |
1819 | + |
1820 | +/** |
1821 | +\brief Abstract base class for upload implementations. |
1822 | + |
1823 | +When the runtime calls ProviderBase::update() or ProviderBase::create_file(), you must return |
1824 | +a new uploader instance that derives from UploadJob. Your implementation is responsible |
1825 | +for retrieving the file's data from the upload socket and writing it to |
1826 | +the corresponding file in the cloud provider. |
1827 | + |
1828 | +You can implement your uploader |
1829 | +any way you wish, such as by running the upload in a separate thread, or |
1830 | +by using async I/O driven by the runtime's (or any other) event loop. |
1831 | + |
1832 | +The runtime invokes all methods on the uploader from the main thread. |
1833 | +*/ |
1834 | + |
1835 | class UNITY_STORAGE_EXPORT UploadJob |
1836 | { |
1837 | public: |
1838 | + /** |
1839 | + \brief Construct an uploader. |
1840 | + \param upload_id An identifier for this particular upload. You can use any non-empty string, |
1841 | + as long as it is unique among all uploads that are in progress within the corresponding account. |
1842 | + (A simple incrementing counter will work fine, or you can use the upload identifier you receive |
1843 | + from the cloud service.) |
1844 | + The runtime uses the <code>upload_id</code> to distinguish different uploads that may be |
1845 | + in progress concurrently, and it ensures that each ID can be used only by its corresponding client. |
1846 | + */ |
1847 | UploadJob(std::string const& upload_id); |
1848 | virtual ~UploadJob(); |
1849 | |
1850 | + /** |
1851 | + \brief Returns the upload ID. |
1852 | + \return The value of the <code>upload_id</code> parameter that was passed to the constructor. |
1853 | + */ |
1854 | std::string const& upload_id() const; |
1855 | + |
1856 | + /** |
1857 | + \brief Returns the socket to read the file contents from. |
1858 | + \return A socket that is open for reading. You must read the file contents from this socket (which is |
1859 | + connected to the client by the runtime). |
1860 | + */ |
1861 | int read_socket() const; |
1862 | |
1863 | - // If an error is reported early, cancel() or finish() will not be |
1864 | - // invoked. |
1865 | - void report_error(std::exception_ptr p); |
1866 | - |
1867 | + /** |
1868 | + \brief Informs the runtime that an upload encountered an error. |
1869 | + |
1870 | + This method makes it unnecessary to wait for the runtime to call finish() in order to confirm |
1871 | + whether an upload completed successfully. You can call report_error() as soon as you encounter an error |
1872 | + during an upload (such as losing the connection to the cloud provider, or getting an error when reading |
1873 | + from the upload socket). This can be convenient if an upload runs in a separate thread or event loop |
1874 | + because there is no need to store success/error state for use in finish(). |
1875 | + |
1876 | + You can call report_error() from an arbitrary thread. |
1877 | + |
1878 | + If you call report_error(), the runtime guarantees that neither finish() nor cancel() will be called, so |
1879 | + you must reclaim any resources associated with the upload before calling report_error(). |
1880 | + |
1881 | + \param storage_exception You <i>must</i> pass a StorageException to indicate the reason for the failure. |
1882 | + \see finish() |
1883 | + */ |
1884 | + void report_error(std::exception_ptr storage_exception); |
1885 | + |
1886 | + /** |
1887 | + \brief Cancel this upload. |
1888 | + |
1889 | + The runtime calls this method when a client explicitly cancels an upload or crashes. |
1890 | + Your implementation should reclaim all resources that are used by the upload. In particular, |
1891 | + you should stop reading any more data and close the upload socket. In addition, |
1892 | + you should reclaim any resources (such as open connections) that are associated |
1893 | + with the upload to the cloud provider (possibly after informing the cloud provider of |
1894 | + the cancellation). |
1895 | + |
1896 | + The runtime guarantees that cancel() will be called only once, and that finish() will not be called |
1897 | + if cancel() was called. |
1898 | + |
1899 | + If any errors are encountered, you <i>must</i> report them by returning a future that stores |
1900 | + a StorageException. Do <i>not</i> call report_error() from inside cancel(). |
1901 | + |
1902 | + \return A future that becomes ready (or contains a StorageException) once cancellation is complete. |
1903 | + */ |
1904 | virtual boost::future<void> cancel() = 0; |
1905 | + |
1906 | + /** |
1907 | + \brief Finalize this upload. |
1908 | + |
1909 | + The runtime calls this method when a client finishes an upload. Your implementation <i>must</i> verify |
1910 | + that it has successfully read <i>and</i> written the <i>exact</i> number of bytes that were passed |
1911 | + to ProviderBase::update() or ProviderBase::create_file() before making the future ready. |
1912 | + If too few or too many bytes were sent by the client, it <i>must</i> store a LogicException in the future. |
1913 | + It must also verify that the cloud provider has received <i>all</i> of the file's data; otherwise, |
1914 | + the file may end up being partially written in the cloud provider. |
1915 | + |
1916 | + Note that finish() may be called while there is still buffered data that remains to be read from the |
1917 | + upload socket, so you must take care to drain the socket of any remaining data before checking that the |
1918 | + actual number bytes uploaded by the client matches the expected number of bytes. |
1919 | + |
1920 | + You should close the upload socket and reclaim any resources (such as open |
1921 | + connections) that are associated with the upload from the cloud provider. |
1922 | + |
1923 | + The runtime guarantees that finish() will be called only once, and that cancel() will not be called |
1924 | + if finish() was called. |
1925 | + |
1926 | + If any errors are encountered, you <i>must</i> report them by returning a future that stores |
1927 | + a StorageException. Do <i>not</i> call report_error() from inside finish(). |
1928 | + |
1929 | + \return A future that becomes ready and contains the metadata for the file (or contains a StorageException) |
1930 | + once the upload is complete. |
1931 | + \see report_error(), TempfileUploadJob |
1932 | + */ |
1933 | virtual boost::future<Item> finish() = 0; |
1934 | |
1935 | -protected: |
1936 | +private: |
1937 | UploadJob(internal::UploadJobImpl *p) UNITY_STORAGE_HIDDEN; |
1938 | internal::UploadJobImpl *p_ = nullptr; |
1939 | |
1940 | friend class internal::PendingJobs; |
1941 | friend class internal::ProviderInterface; |
1942 | + friend class TempfileUploadJob; |
1943 | }; |
1944 | |
1945 | } |
1946 | |
1947 | === modified file 'include/unity/storage/provider/testing/TestServer.h' |
1948 | --- include/unity/storage/provider/testing/TestServer.h 2016-09-28 07:04:41 +0000 |
1949 | +++ include/unity/storage/provider/testing/TestServer.h 2017-04-06 07:42:38 +0000 |
1950 | @@ -23,11 +23,13 @@ |
1951 | #include <memory> |
1952 | #include <string> |
1953 | |
1954 | +//@cond |
1955 | namespace OnlineAccounts |
1956 | { |
1957 | class Account; |
1958 | } |
1959 | class QDBusConnection; |
1960 | +//@endcond |
1961 | |
1962 | namespace unity |
1963 | { |
1964 | @@ -46,16 +48,44 @@ |
1965 | namespace testing |
1966 | { |
1967 | |
1968 | +/** |
1969 | +\brief Helper class to enable testing of provider implementations. |
1970 | + |
1971 | + |
1972 | +TestServer is a simple helper class that allows you to test a provider implementation |
1973 | +on a separate DBus connection. The class requires access to |
1974 | +an <a href="https://help.ubuntu.com/stable/ubuntu-help/accounts.html">Online Accounts</a> |
1975 | +service. If you do not want to test with the live Online Accounts service, you can pass |
1976 | +<code>nullptr</code>, in which case your provider receives blank |
1977 | +\link unity::storage::provider::Credentials Credentials\endlink. |
1978 | +*/ |
1979 | + |
1980 | class UNITY_STORAGE_EXPORT TestServer |
1981 | { |
1982 | public: |
1983 | + /** |
1984 | + \brief Constructs a TestServer instance. |
1985 | + \param provider The provider implementation to be tested. |
1986 | + \param account The account for the provider (or <code>nullptr</code>). |
1987 | + \param connection The DBus connection to connect the provider to. |
1988 | + \param object_path The DBus object path for the provider interface. |
1989 | + */ |
1990 | TestServer(std::shared_ptr<ProviderBase> const& provider, |
1991 | OnlineAccounts::Account* account, |
1992 | QDBusConnection const& connection, |
1993 | std::string const& object_path); |
1994 | ~TestServer(); |
1995 | |
1996 | + /** |
1997 | + \brief Returns the DBus connection. |
1998 | + \return The value of the <code>connection</code> parameter that was passed to the constructor. |
1999 | + */ |
2000 | QDBusConnection const& connection() const; |
2001 | + |
2002 | + /** |
2003 | + \brief Returns the object path. |
2004 | + \return The value of the <code>object_path</code> parameter that was passed to the constructor. |
2005 | + */ |
2006 | std::string const& object_path() const; |
2007 | |
2008 | private: |
2009 | |
2010 | === modified file 'include/unity/storage/qt/internal/StorageErrorImpl.h' |
2011 | --- include/unity/storage/qt/internal/StorageErrorImpl.h 2016-10-13 06:48:11 +0000 |
2012 | +++ include/unity/storage/qt/internal/StorageErrorImpl.h 2017-04-06 07:42:38 +0000 |
2013 | @@ -63,6 +63,7 @@ |
2014 | static StorageError not_exists_error(QString const& msg, QString const& key); |
2015 | static StorageError exists_error(QString const& msg, QString const& item_id, QString const& item_name); |
2016 | static StorageError cancelled_error(QString const& msg); |
2017 | + static StorageError permission_error(QString const& msg); |
2018 | static StorageError logic_error(QString const& msg); |
2019 | static StorageError invalid_argument_error(QString const& msg); |
2020 | static StorageError resource_error(QString const& msg, int error_code); |
2021 | |
2022 | === modified file 'src/local-provider/LocalDownloadJob.cpp' |
2023 | --- src/local-provider/LocalDownloadJob.cpp 2017-03-14 07:11:40 +0000 |
2024 | +++ src/local-provider/LocalDownloadJob.cpp 2017-04-06 07:42:38 +0000 |
2025 | @@ -46,7 +46,7 @@ |
2026 | auto st = status(item_id_); |
2027 | if (!is_regular_file(st)) |
2028 | { |
2029 | - throw InvalidArgumentException(method + ": \"" + item_id_ + "\" is not a file"); |
2030 | + throw LogicException(method + ": \"" + item_id_ + "\" is not a file"); |
2031 | } |
2032 | } |
2033 | // LCOV_EXCL_START // Too small a window to hit with a test. |
2034 | @@ -60,7 +60,7 @@ |
2035 | int64_t mtime = get_mtime_nsecs(method, item_id_); |
2036 | if (to_string(mtime) != match_etag) |
2037 | { |
2038 | - throw ConflictException(method + ": etag mismatch"); |
2039 | + throw ConflictException(method + ": ETag mismatch"); |
2040 | } |
2041 | } |
2042 | |
2043 | |
2044 | === modified file 'src/local-provider/LocalProvider.cpp' |
2045 | --- src/local-provider/LocalProvider.cpp 2017-03-23 06:55:00 +0000 |
2046 | +++ src/local-provider/LocalProvider.cpp 2017-04-06 07:42:38 +0000 |
2047 | @@ -195,6 +195,11 @@ |
2048 | using namespace boost::filesystem; |
2049 | |
2050 | This->throw_if_not_valid(method, item_id); |
2051 | + if (!is_directory(item_id)) |
2052 | + { |
2053 | + string msg = method + ": \"" + item_id + "\" is not a folder"; |
2054 | + throw boost::enable_current_exception(LogicException(msg)); |
2055 | + } |
2056 | vector<Item> items; |
2057 | for (directory_iterator it(item_id); it != directory_iterator(); ++it) |
2058 | { |
2059 | @@ -345,7 +350,7 @@ |
2060 | if (canonical(item_id).native() == This->root_) |
2061 | { |
2062 | string msg = method + ": cannot delete root"; |
2063 | - throw boost::enable_current_exception(LogicException(msg)); |
2064 | + throw boost::enable_current_exception(PermissionException(msg)); |
2065 | } |
2066 | remove_all(item_id); |
2067 | }; |
2068 | @@ -378,6 +383,11 @@ |
2069 | string msg = method + ": \"" + target_path.native() + "\" exists already"; |
2070 | throw boost::enable_current_exception(ExistsException(msg, target_path.native(), new_name)); |
2071 | } |
2072 | + if (canonical(item_id).native() == This->root_) |
2073 | + { |
2074 | + string msg = method + ": cannot move root"; |
2075 | + throw boost::enable_current_exception(PermissionException(msg)); |
2076 | + } |
2077 | |
2078 | // Small race condition here: if exists() just said that the target does not exist, it is |
2079 | // possible for it to have been created since. If so, if the target is a file or an empty |
2080 | |
2081 | === modified file 'src/local-provider/LocalUploadJob.cpp' |
2082 | --- src/local-provider/LocalUploadJob.cpp 2017-03-16 02:50:03 +0000 |
2083 | +++ src/local-provider/LocalUploadJob.cpp 2017-04-06 07:42:38 +0000 |
2084 | @@ -83,7 +83,7 @@ |
2085 | auto st = status(item_id); |
2086 | if (!is_regular_file(st)) |
2087 | { |
2088 | - throw InvalidArgumentException(method_ + ": \"" + item_id + "\" is not a file"); |
2089 | + throw LogicException(method_ + ": \"" + item_id + "\" is not a file"); |
2090 | } |
2091 | } |
2092 | // LCOV_EXCL_START |
2093 | @@ -99,7 +99,7 @@ |
2094 | int64_t mtime = get_mtime_nsecs(method_, item_id); |
2095 | if (to_string(mtime) != old_etag) |
2096 | { |
2097 | - throw ConflictException(method_ + ": etag mismatch"); |
2098 | + throw ConflictException(method_ + ": ETag mismatch"); |
2099 | } |
2100 | } |
2101 | old_etag_ = old_etag; |
2102 | @@ -184,14 +184,13 @@ |
2103 | |
2104 | try |
2105 | { |
2106 | - // We check again for an etag mismatch or overwrite, in case the file was updated after the upload started. |
2107 | + // We check again for an ETag mismatch or overwrite, in case the file was updated after the upload started. |
2108 | if (!parent_id_.empty()) |
2109 | { |
2110 | // create_file() |
2111 | if (!allow_overwrite_ && boost::filesystem::exists(item_id_)) |
2112 | { |
2113 | string msg = method_ + ": \"" + item_id_ + "\" exists already"; |
2114 | - boost::filesystem::path(item_id_).filename().native(); |
2115 | BOOST_THROW_EXCEPTION( |
2116 | ExistsException(msg, item_id_, boost::filesystem::path(item_id_).filename().native())); |
2117 | } |
2118 | @@ -199,10 +198,22 @@ |
2119 | else if (!old_etag_.empty()) |
2120 | { |
2121 | // update() |
2122 | + using namespace boost::filesystem; |
2123 | + if (exists(item_id_) && !is_regular_file(item_id_)) |
2124 | + { |
2125 | + // Yes, ExistsException, not LogicException. A folder is in the way |
2126 | + // and was created after the update started, meaning that the client correctly |
2127 | + // started the operation with an existing file, but then the file was removed |
2128 | + // and replaced with a folder with the same name. |
2129 | + string msg = method_ + ": \"" + item_id_ + "\" exists already"; |
2130 | + BOOST_THROW_EXCEPTION( |
2131 | + ExistsException(msg, item_id_, boost::filesystem::path(item_id_).filename().native())); |
2132 | + } |
2133 | int64_t mtime = get_mtime_nsecs(method_, item_id_); |
2134 | if (to_string(mtime) != old_etag_) |
2135 | { |
2136 | - BOOST_THROW_EXCEPTION(ConflictException(method_ + ": etag mismatch")); |
2137 | + // File was touched after the upload started. |
2138 | + BOOST_THROW_EXCEPTION(ConflictException(method_ + ": ETag mismatch")); |
2139 | } |
2140 | } |
2141 | |
2142 | |
2143 | === modified file 'src/qt/internal/ItemImpl.cpp' |
2144 | --- src/qt/internal/ItemImpl.cpp 2016-11-25 03:23:55 +0000 |
2145 | +++ src/qt/internal/ItemImpl.cpp 2017-04-06 07:42:38 +0000 |
2146 | @@ -235,7 +235,7 @@ |
2147 | } |
2148 | if (md_.type == storage::ItemType::root) |
2149 | { |
2150 | - auto e = StorageErrorImpl::logic_error(method + ": cannot delete root"); |
2151 | + auto e = StorageErrorImpl::permission_error(method + ": cannot delete root"); |
2152 | return VoidJobImpl::make_job(e); |
2153 | } |
2154 | |
2155 | |
2156 | === modified file 'src/qt/internal/StorageErrorImpl.cpp' |
2157 | --- src/qt/internal/StorageErrorImpl.cpp 2017-03-14 07:11:40 +0000 |
2158 | +++ src/qt/internal/StorageErrorImpl.cpp 2017-04-06 07:42:38 +0000 |
2159 | @@ -188,6 +188,12 @@ |
2160 | return StorageError(move(p)); |
2161 | } |
2162 | |
2163 | +StorageError StorageErrorImpl::permission_error(QString const& msg) |
2164 | +{ |
2165 | + unique_ptr<StorageErrorImpl> p(new StorageErrorImpl(StorageError::Type::PermissionDenied, msg)); |
2166 | + return StorageError(move(p)); |
2167 | +} |
2168 | + |
2169 | StorageError StorageErrorImpl::logic_error(QString const& msg) |
2170 | { |
2171 | unique_ptr<StorageErrorImpl> p(new StorageErrorImpl(StorageError::Type::LogicError, msg)); |
2172 | |
2173 | === modified file 'src/qt/internal/unmarshal_error.cpp' |
2174 | --- src/qt/internal/unmarshal_error.cpp 2017-01-24 06:12:09 +0000 |
2175 | +++ src/qt/internal/unmarshal_error.cpp 2017-04-06 07:42:38 +0000 |
2176 | @@ -84,11 +84,11 @@ |
2177 | { "ConflictException", make_error<StorageError::Type::Conflict> }, |
2178 | { "UnauthorizedException", make_error<StorageError::Type::Unauthorized> }, |
2179 | { "PermissionException", make_error<StorageError::Type::PermissionDenied> }, |
2180 | + { "QuotaException", make_error<StorageError::Type::QuotaExceeded> }, |
2181 | { "CancelledException", make_error<StorageError::Type::Cancelled> }, |
2182 | { "LogicException", make_error<StorageError::Type::LogicError> }, |
2183 | { "InvalidArgumentException", make_error<StorageError::Type::InvalidArgument> }, |
2184 | { "ResourceException", make_error<StorageError::Type::ResourceError> }, |
2185 | - { "QuotaException", make_error<StorageError::Type::QuotaExceeded> }, |
2186 | { "UnknownException", make_error<StorageError::Type::LocalCommsError> } // Yes, LocalCommsError is intentional |
2187 | }; |
2188 | |
2189 | |
2190 | === modified file 'tests/local-provider/local-provider_test.cpp' |
2191 | --- tests/local-provider/local-provider_test.cpp 2017-03-23 06:55:00 +0000 |
2192 | +++ tests/local-provider/local-provider_test.cpp 2017-04-06 07:42:38 +0000 |
2193 | @@ -141,6 +141,22 @@ |
2194 | "nulla pariatur. Excepteur sint occaecat cupidatat non proident, " |
2195 | "sunt in culpa qui officia deserunt mollit anim id est laborum.\n"; |
2196 | |
2197 | +void make_hierarchy(string const& root_dir) |
2198 | +{ |
2199 | + // Make a small tree so we have something to test against. |
2200 | + ASSERT_EQ(0, mkdir((root_dir + "/a").c_str(), 0755)); |
2201 | + ASSERT_EQ(0, mkdir((root_dir + "/a/b").c_str(), 0755)); |
2202 | + string cmd = string("echo hello >") + root_dir + "/hello"; |
2203 | + ASSERT_EQ(0, system(cmd.c_str())); |
2204 | + cmd = string("echo text >") + root_dir + "/a/foo.txt"; |
2205 | + ASSERT_EQ(0, system(cmd.c_str())); |
2206 | + ASSERT_EQ(0, mknod((root_dir + "/a/pipe").c_str(), S_IFIFO | 06666, 0)); |
2207 | + ASSERT_EQ(0, mkdir((root_dir + "/a/.storage-framework-").c_str(), 0755)); |
2208 | + ASSERT_EQ(0, mkdir((root_dir + "/a/b/.storage-framework-").c_str(), 0755)); |
2209 | + ASSERT_EQ(0, mknod((root_dir + "/a/b/pipe").c_str(), S_IFIFO | 06666, 0)); |
2210 | + ASSERT_EQ(0, mkdir((root_dir + "/empty_dir").c_str(), 0755)); |
2211 | +} |
2212 | + |
2213 | } // namespace |
2214 | |
2215 | TEST(Directories, env_vars) |
2216 | @@ -307,6 +323,9 @@ |
2217 | ASSERT_EQ(ItemJob::Error, job->status()) << job->error().errorString().toStdString(); |
2218 | EXPECT_EQ(string("Exists: create_folder(): \"") + ROOT_DIR() + "/child\" exists already", |
2219 | job->error().errorString().toStdString()); |
2220 | + EXPECT_EQ(ROOT_DIR() + "/child", job->error().itemId().toStdString()); |
2221 | + EXPECT_EQ(ROOT_DIR() + "/child", job->error().itemId().toStdString()); |
2222 | + EXPECT_EQ("child", job->error().itemName().toStdString()); |
2223 | |
2224 | // Again, without write permission on the root dir, to get coverage for a filesystem_error in invoke_async(). |
2225 | ASSERT_EQ(0, ::rmdir((ROOT_DIR() + "/child").c_str())); |
2226 | @@ -363,15 +382,17 @@ |
2227 | // Client-side API does not allow us to try to delete the root, so we talk to the provider directly. |
2228 | auto p = make_shared<LocalProvider>(); |
2229 | |
2230 | + make_hierarchy(ROOT_DIR()); |
2231 | + |
2232 | auto fut = p->delete_item(ROOT_DIR(), provider::Context()); |
2233 | try |
2234 | { |
2235 | fut.get(); |
2236 | FAIL(); |
2237 | } |
2238 | - catch (provider::LogicException const& e) |
2239 | + catch (provider::PermissionException const& e) |
2240 | { |
2241 | - EXPECT_STREQ("LogicException: delete_item(): cannot delete root", e.what()); |
2242 | + EXPECT_STREQ("PermissionException: delete_item(): cannot delete root", e.what()); |
2243 | } |
2244 | } |
2245 | |
2246 | @@ -435,6 +456,8 @@ |
2247 | EXPECT_EQ(string("NotExists: lookup(): \"") + ROOT_DIR() + "/child\": boost::filesystem::canonical: " |
2248 | + "No such file or directory: \"" + ROOT_DIR() + "/child\"", |
2249 | job->error().errorString().toStdString()); |
2250 | + EXPECT_EQ(qt::StorageError::NotExists, job->error().type()); |
2251 | + EXPECT_EQ(ROOT_DIR() + "/child", job->error().itemId().toStdString()); |
2252 | } |
2253 | |
2254 | TEST_F(LocalProviderTest, list) |
2255 | @@ -472,19 +495,24 @@ |
2256 | EXPECT_EQ(5, child.metadata().size()); |
2257 | } |
2258 | |
2259 | -void make_hierarchy(string const& root_dir) |
2260 | +TEST_F(LocalProviderTest, list_file) |
2261 | { |
2262 | - // Make a small tree so we have something to test with for move() and copy(). |
2263 | - ASSERT_EQ(0, mkdir((root_dir + "/a").c_str(), 0755)); |
2264 | - ASSERT_EQ(0, mkdir((root_dir + "/a/b").c_str(), 0755)); |
2265 | - string cmd = string("echo hello >") + root_dir + "/hello"; |
2266 | - ASSERT_EQ(0, system(cmd.c_str())); |
2267 | - cmd = string("echo text >") + root_dir + "/a/foo.txt"; |
2268 | - ASSERT_EQ(0, system(cmd.c_str())); |
2269 | - ASSERT_EQ(0, mknod((root_dir + "/a/pipe").c_str(), S_IFIFO | 06666, 0)); |
2270 | - ASSERT_EQ(0, mkdir((root_dir + "/a/.storage-framework-").c_str(), 0755)); |
2271 | - ASSERT_EQ(0, mkdir((root_dir + "/a/b/.storage-framework-").c_str(), 0755)); |
2272 | - ASSERT_EQ(0, mknod((root_dir + "/a/b/pipe").c_str(), S_IFIFO | 06666, 0)); |
2273 | + // We can't try a list on a file via the client API, so we use the provider directly. |
2274 | + |
2275 | + auto p = make_shared<LocalProvider>(); |
2276 | + |
2277 | + make_hierarchy(ROOT_DIR()); |
2278 | + |
2279 | + try |
2280 | + { |
2281 | + auto fut = p->list(ROOT_DIR() + "/hello", "", {}, provider::Context()); |
2282 | + fut.get(); |
2283 | + FAIL(); |
2284 | + } |
2285 | + catch (provider::LogicException const& e) |
2286 | + { |
2287 | + EXPECT_EQ(string("LogicException: list(): \"") + ROOT_DIR() + "/hello\" is not a folder", e.what()); |
2288 | + } |
2289 | } |
2290 | |
2291 | TEST_F(LocalProviderTest, move) |
2292 | @@ -571,6 +599,26 @@ |
2293 | job->error().errorString().toStdString()); |
2294 | } |
2295 | |
2296 | +TEST_F(LocalProviderTest, move_root) |
2297 | +{ |
2298 | + // We can't try to move a root via the client API, so we use the LocalDownloadJob directly. |
2299 | + |
2300 | + auto p = make_shared<LocalProvider>(); |
2301 | + |
2302 | + make_hierarchy(ROOT_DIR()); |
2303 | + |
2304 | + auto fut = p->move(ROOT_DIR(), ROOT_DIR() + "/empty_dir", "moved_root", {}, provider::Context()); |
2305 | + try |
2306 | + { |
2307 | + fut.get(); |
2308 | + FAIL(); |
2309 | + } |
2310 | + catch (provider::PermissionException const& e) |
2311 | + { |
2312 | + EXPECT_STREQ("PermissionException: move(): cannot move root", e.what()); |
2313 | + } |
2314 | +} |
2315 | + |
2316 | TEST_F(LocalProviderTest, copy_file) |
2317 | { |
2318 | using namespace unity::storage::qt; |
2319 | @@ -659,6 +707,44 @@ |
2320 | } |
2321 | } |
2322 | |
2323 | +TEST_F(LocalProviderTest, copy_root) |
2324 | +{ |
2325 | + using namespace unity::storage::qt; |
2326 | + using namespace boost::filesystem; |
2327 | + |
2328 | + set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider)); |
2329 | + |
2330 | + make_hierarchy(ROOT_DIR()); |
2331 | + |
2332 | + auto root = get_root(acc_); |
2333 | + |
2334 | + qt::Item empty_dir; |
2335 | + { |
2336 | + unique_ptr<ItemListJob> job(root.lookup("empty_dir")); |
2337 | + auto items = get_items(job.get()); |
2338 | + ASSERT_EQ(ItemListJob::Finished, job->status()) << job->error().errorString().toStdString(); |
2339 | + ASSERT_EQ(1, items.size()); |
2340 | + empty_dir = items.at(0); |
2341 | + } |
2342 | + |
2343 | + { |
2344 | + unique_ptr<ItemJob> job(root.copy(empty_dir, "copied_root")); |
2345 | + wait(job.get()); |
2346 | + ASSERT_EQ(ItemJob::Finished, job->status()) << job->error().errorString().toStdString(); |
2347 | + } |
2348 | + |
2349 | + // Check that we only copied regular files and directories, but not a pipe or anything starting with |
2350 | + // the temp file prefix. |
2351 | + EXPECT_TRUE(exists(ROOT_DIR() + "/empty_dir/copied_root/a/b")); |
2352 | + EXPECT_TRUE(exists(ROOT_DIR() + "/empty_dir/copied_root/hello")); |
2353 | + EXPECT_TRUE(exists(ROOT_DIR() + "/empty_dir/copied_root/a/foo.txt")); |
2354 | + EXPECT_TRUE(exists(ROOT_DIR() + "/empty_dir/copied_root/empty_dir")); |
2355 | + EXPECT_FALSE(exists(ROOT_DIR() + "/empty_dir/copied_root/a/pipe")); |
2356 | + EXPECT_FALSE(exists(ROOT_DIR() + "/empty_dir/copied_root/a/storage-framework-")); |
2357 | + EXPECT_FALSE(exists(ROOT_DIR() + "/empty_dir/copied_root/a/b/pipe")); |
2358 | + EXPECT_FALSE(exists(ROOT_DIR() + "/empty_dir/copied_root/a/b/storage-framework-")); |
2359 | +} |
2360 | + |
2361 | TEST_F(LocalProviderTest, download) |
2362 | { |
2363 | using namespace unity::storage::qt; |
2364 | @@ -785,7 +871,7 @@ |
2365 | |
2366 | auto error = downloader->error(); |
2367 | EXPECT_EQ(qt::StorageError::Conflict, error.type()); |
2368 | - EXPECT_EQ("download(): etag mismatch", error.message().toStdString()); |
2369 | + EXPECT_EQ("download(): ETag mismatch", error.message().toStdString()); |
2370 | } |
2371 | |
2372 | TEST_F(LocalProviderTest, download_wrong_file_type) |
2373 | @@ -802,9 +888,9 @@ |
2374 | LocalDownloadJob(p, dir, "some_etag"); |
2375 | FAIL(); |
2376 | } |
2377 | - catch (provider::InvalidArgumentException const& e) |
2378 | + catch (provider::LogicException const& e) |
2379 | { |
2380 | - EXPECT_EQ(string("InvalidArgumentException: download(): \"" + dir + "\" is not a file"), e.what()); |
2381 | + EXPECT_EQ(string("LogicException: download(): \"" + dir + "\" is not a file"), e.what()); |
2382 | } |
2383 | } |
2384 | |
2385 | @@ -1013,7 +1099,7 @@ |
2386 | { |
2387 | ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); |
2388 | } |
2389 | - EXPECT_EQ("update(): etag mismatch", uploader->error().message().toStdString()); |
2390 | + EXPECT_EQ("update(): ETag mismatch", uploader->error().message().toStdString()); |
2391 | } |
2392 | |
2393 | TEST_F(LocalProviderTest, update_file_touched_while_uploading) |
2394 | @@ -1062,7 +1148,56 @@ |
2395 | ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); |
2396 | } |
2397 | ASSERT_EQ(Uploader::Error, uploader->status()); |
2398 | - EXPECT_EQ("update(): etag mismatch", uploader->error().message().toStdString()); |
2399 | + EXPECT_EQ("update(): ETag mismatch", uploader->error().message().toStdString()); |
2400 | +} |
2401 | + |
2402 | +TEST_F(LocalProviderTest, update_folder_created_while_uploading) |
2403 | +{ |
2404 | + using namespace unity::storage::qt; |
2405 | + |
2406 | + set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider)); |
2407 | + |
2408 | + auto full_path = ROOT_DIR() + "/foo.txt"; |
2409 | + auto cmd = string("echo hello >") + full_path; |
2410 | + ASSERT_EQ(0, system(cmd.c_str())); |
2411 | + |
2412 | + unique_ptr<ItemJob> job(acc_.get(QString::fromStdString(full_path))); |
2413 | + wait(job.get()); |
2414 | + EXPECT_TRUE(job->isValid()); |
2415 | + |
2416 | + auto file = job->item(); |
2417 | + auto old_etag = file.etag(); |
2418 | + |
2419 | + int const segments = 50; |
2420 | + unique_ptr<Uploader> uploader(file.createUploader(Item::ErrorIfConflict, file_contents.size() * segments)); |
2421 | + |
2422 | + int count = 0; |
2423 | + QTimer timer; |
2424 | + timer.setSingleShot(false); |
2425 | + timer.setInterval(10); |
2426 | + QObject::connect(&timer, &QTimer::timeout, [&] { |
2427 | + uploader->write(&file_contents[0], file_contents.size()); |
2428 | + count++; |
2429 | + if (count == segments / 2) |
2430 | + { |
2431 | + sleep(1); |
2432 | + cmd = string("rm ") + full_path + "; mkdir " + full_path; |
2433 | + ASSERT_EQ(0, system(cmd.c_str())); |
2434 | + } |
2435 | + else if (count == segments) |
2436 | + { |
2437 | + uploader->close(); |
2438 | + } |
2439 | + }); |
2440 | + |
2441 | + QSignalSpy spy(uploader.get(), &Uploader::statusChanged); |
2442 | + timer.start(); |
2443 | + while (uploader->status() != Uploader::Error) |
2444 | + { |
2445 | + ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME)); |
2446 | + } |
2447 | + ASSERT_EQ(Uploader::Error, uploader->status()); |
2448 | + EXPECT_EQ(string("update(): \"") + full_path + "\" exists already", uploader->error().message().toStdString()); |
2449 | } |
2450 | |
2451 | TEST_F(LocalProviderTest, update_ignore_etag_mismatch) |
2452 | @@ -1208,9 +1343,9 @@ |
2453 | LocalUploadJob(p, dir, 0, ""); |
2454 | FAIL(); |
2455 | } |
2456 | - catch (provider::InvalidArgumentException const& e) |
2457 | + catch (provider::LogicException const& e) |
2458 | { |
2459 | - EXPECT_EQ(string("InvalidArgumentException: update(): \"" + dir + "\" is not a file"), e.what()); |
2460 | + EXPECT_EQ(string("LogicException: update(): \"" + dir + "\" is not a file"), e.what()); |
2461 | } |
2462 | } |
2463 | |
2464 | |
2465 | === modified file 'tests/remote-client/remote-client_test.cpp' |
2466 | --- tests/remote-client/remote-client_test.cpp 2017-01-12 06:02:30 +0000 |
2467 | +++ tests/remote-client/remote-client_test.cpp 2017-04-06 07:42:38 +0000 |
2468 | @@ -946,7 +946,7 @@ |
2469 | unique_ptr<VoidJob> j(item.deleteItem()); |
2470 | EXPECT_FALSE(j->isValid()); |
2471 | EXPECT_EQ(VoidJob::Status::Error, j->status()); |
2472 | - EXPECT_EQ(StorageError::Type::LogicError, j->error().type()); |
2473 | + EXPECT_EQ(StorageError::Type::PermissionDenied, j->error().type()); |
2474 | |
2475 | // Signal must be received. |
2476 | QSignalSpy spy(j.get(), &VoidJob::statusChanged); |
PASSED: Continuous integration, rev:127 /jenkins. canonical. com/unity- api-1/job/ lp-storage- framework- ci/275/ /jenkins. canonical. com/unity- api-1/job/ build/1886 /jenkins. canonical. com/unity- api-1/job/ build-0- fetch/1893 /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=amd64, release= xenial+ overlay/ 1675 /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=amd64, release= xenial+ overlay/ 1675/artifact/ output/ *zip*/output. zip /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=amd64, release= zesty/1675 /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=amd64, release= zesty/1675/ artifact/ output/ *zip*/output. zip /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=armhf, release= xenial+ overlay/ 1675 /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=armhf, release= xenial+ overlay/ 1675/artifact/ output/ *zip*/output. zip /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=armhf, release= zesty/1675 /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=armhf, release= zesty/1675/ artifact/ output/ *zip*/output. zip /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=i386, release= xenial+ overlay/ 1675 /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=i386, release= xenial+ overlay/ 1675/artifact/ output/ *zip*/output. zip /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=i386, release= zesty/1675 /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=i386, release= zesty/1675/ artifact/ output/ *zip*/output. zip
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: /jenkins. canonical. com/unity- api-1/job/ lp-storage- framework- ci/275/ rebuild
https:/