Merge lp:~michihenning/storage-framework/add-doc into lp:storage-framework/devel

Proposed by Michi Henning
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
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 InvalidArgumentException.

To post a comment you must log in.
Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :

PASSED: Continuous integration, rev:127
https://jenkins.canonical.com/unity-api-1/job/lp-storage-framework-ci/275/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build/1886
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/1893
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1675
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1675/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=zesty/1675
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=zesty/1675/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1675
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1675/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=zesty/1675
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=zesty/1675/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/1675
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/1675/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=zesty/1675
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=zesty/1675/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/unity-api-1/job/lp-storage-framework-ci/275/rebuild

review: Approve (continuous-integration)
Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :

PASSED: Continuous integration, rev:128
https://jenkins.canonical.com/unity-api-1/job/lp-storage-framework-ci/276/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build/1887
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/1894
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1676
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1676/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=zesty/1676
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=zesty/1676/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1676
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1676/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=zesty/1676
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=zesty/1676/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/1676
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/1676/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=zesty/1676
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=zesty/1676/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/unity-api-1/job/lp-storage-framework-ci/276/rebuild

review: Approve (continuous-integration)
Revision history for this message
Marcus Tomlinson (marcustomlinson) wrote :

First pass spell checking (inline)

Revision history for this message
James Henstridge (jamesh) wrote :

Some comments left inline.

review: Needs Fixing
Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :

PASSED: Continuous integration, rev:131
https://jenkins.canonical.com/unity-api-1/job/lp-storage-framework-ci/278/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build/1916
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/1923
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1704
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1704/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=zesty/1704
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=zesty/1704/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1704
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1704/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=zesty/1704
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=zesty/1704/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/1704
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/1704/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=zesty/1704
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=zesty/1704/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/unity-api-1/job/lp-storage-framework-ci/278/rebuild

review: Approve (continuous-integration)
Revision history for this message
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!

Revision history for this message
James Henstridge (jamesh) wrote :

Looks good.

review: Approve
Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :
review: Needs Fixing (continuous-integration)
132. By Michi Henning

Merged devel.

Revision history for this message
Michi Henning (michihenning) wrote :

Re-approving for Jenkins after fixing merge conflict.

review: Approve
Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :

PASSED: Continuous integration, rev:132
https://jenkins.canonical.com/unity-api-1/job/lp-storage-framework-ci/282/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build/1926
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/1933
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1713
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1713/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=zesty/1713
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=zesty/1713/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1713
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1713/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=zesty/1713
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=zesty/1713/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/1713
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/1713/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=zesty/1713
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=zesty/1713/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/unity-api-1/job/lp-storage-framework-ci/282/rebuild

review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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&ndash;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&ndash;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);

Subscribers

People subscribed via source and target branches

to all changes: