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
=== modified file 'CMakeLists.txt'
--- CMakeLists.txt 2017-03-30 00:57:11 +0000
+++ CMakeLists.txt 2017-04-06 07:42:38 +0000
@@ -134,6 +134,7 @@
134add_subdirectory(tests)134add_subdirectory(tests)
135add_subdirectory(demo)135add_subdirectory(demo)
136add_subdirectory(tools)136add_subdirectory(tools)
137add_subdirectory(doc)
137138
138enable_coverage_report(139enable_coverage_report(
139 TARGETS140 TARGETS
140141
=== modified file 'debian/changelog'
--- debian/changelog 2017-04-06 04:57:35 +0000
+++ debian/changelog 2017-04-06 07:42:38 +0000
@@ -2,6 +2,7 @@
22
3 [ Michi Henning ]3 [ Michi Henning ]
4 * Removed dependency on libgio.4 * Removed dependency on libgio.
5 * Added tutorial and provider reference documentation.
56
6 [ James Henstridge ]7 [ James Henstridge ]
7 * Add systemd units for registry and local provider D-Bus services.8 * Add systemd units for registry and local provider D-Bus services.
89
=== modified file 'debian/control'
--- debian/control 2017-03-20 04:51:09 +0000
+++ debian/control 2017-04-06 07:42:38 +0000
@@ -110,3 +110,11 @@
110 ${misc:Depends},110 ${misc:Depends},
111Description: Header files for the Storage Framework provider library111Description: Header files for the Storage Framework provider library
112 Development C++ headers for the provider API.112 Development C++ headers for the provider API.
113
114Package: storage-framework-doc
115Section: doc
116Architecture: all
117Multi-Arch: foreign
118Depends: ${misc:Depends},
119Description: Documentation for storage-framework-dev
120 Tutorial and API reference.
113121
=== modified file 'debian/control.in'
--- debian/control.in 2017-03-17 05:04:09 +0000
+++ debian/control.in 2017-04-06 07:42:38 +0000
@@ -105,3 +105,11 @@
105 ${misc:Depends},105 ${misc:Depends},
106Description: Header files for the Storage Framework provider library106Description: Header files for the Storage Framework provider library
107 Development C++ headers for the provider API.107 Development C++ headers for the provider API.
108
109Package: storage-framework-doc
110Section: doc
111Architecture: all
112Multi-Arch: foreign
113Depends: ${misc:Depends},
114Description: Documentation for storage-framework-dev
115 Tutorial and API reference.
108116
=== added file 'debian/storage-framework-doc.install'
--- debian/storage-framework-doc.install 1970-01-01 00:00:00 +0000
+++ debian/storage-framework-doc.install 2017-04-06 07:42:38 +0000
@@ -0,0 +1,1 @@
1usr/share/doc/storage-framework/*
02
=== added directory 'doc'
=== added file 'doc/CMakeLists.txt'
--- doc/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ doc/CMakeLists.txt 2017-04-06 07:42:38 +0000
@@ -0,0 +1,20 @@
1find_package(DoxygenBuilder)
2
3add_doxygen(
4 storage-framework-doc
5 INPUT
6 ${CMAKE_CURRENT_SOURCE_DIR}
7 ${CMAKE_SOURCE_DIR}/include/unity/storage
8 STRIP_FROM_PATH
9 ${CMAKE_SOURCE_DIR}
10 STRIP_FROM_INC_PATH
11 ${CMAKE_SOURCE_DIR}/include
12 EXCLUDE_PATTERNS
13 */internal/*
14 */qt/*
15 ALL
16)
17
18install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html
19 DESTINATION share/doc/storage-framework
20)
021
=== added file 'doc/provider_tut.dox'
--- doc/provider_tut.dox 1970-01-01 00:00:00 +0000
+++ doc/provider_tut.dox 2017-04-06 07:42:38 +0000
@@ -0,0 +1,588 @@
1/**
2\mainpage Tutorial
3
4\section overview Overview
5
6The storage framework provides a general-purpose file system abstraction that is independent of any physical storage
7mechanism and the APIs that are needed to manipulate different kinds of storage. This allows client applications to
8access and modify files that exist in remote storage mechanisms (such as
9<a href="https://www.google.com/drive/">Google Drive</a> or <a href="https://owncloud.org/">Owncloud</a>) as well
10as files in the local file system through a common API.
11
12The framework includes a provider for local files; to allow access to remote files, the framework
13requires provider implementations that adapt the common API to each cloud service.
14
15Each provider implementation runs as a separate DBus service on the session bus; the storage framework
16provides run-time support that takes care of DBus communications, error handling, sanitizing input parameters,
17authorization, and so on. This allows providers to be created without having to handle these details, and enables
18the addition of new providers over time without disturbing existing provider implementations.
19
20Ubuntu includes a number of provider implementations, among them an
21<a href="https://code.launchpad.net/storage-provider-webdav">Owncloud provider</a> and an
22<a href="https://launchpad.net/mcloud">mCloud provider</a>.
23
24\section semantics File System Semantics
25
26The storage framework establishes common semantics that apply to all storage backends.
27Clients must not expect anything not guaranteed by these semantics, and providers must implement the semantics
28as faithfully as possible.
29
30The operations that are available are necessarily limited to those that can be supported by the
31majority of backends:
32
33- List the root folder (or root folders; some providers support multiple roots)
34
35- Create and destroy folders or files
36
37- Move or copy a file or folder
38
39- List the contents of a folder
40
41- Look up a file or folder by name
42
43- Look up a file or folder by identity
44
45- Upload or download a file
46
47- Get additional metadata (such as modification time or mime type) for a file or folder
48
49Note that all data transfers can be performed only by uploading or downloading an entire file. The framework
50does not support random access to files.
51
52Move and copy operations can be performed only within the same account. If an application wants to copy or move files
53from one account to another, it must download the files from the source account and upload them to the target account
54(and, for a move, delete the source files after uploading them).
55
56\subsection accounts Accounts
57
58The storage framework is integrated with
59<a href="https://help.ubuntu.com/stable/ubuntu-help/accounts.html">Online Accounts</a>. When a client application
60uses the framework, it first retrieves a list of available accounts from the framework. The application only receives
61those accounts for which the user has authorized access. The application then retrieves the root (or roots) for that
62account, and uses the root to access and modify files and folders underneath that root.
63On the server side, the runtime passes any
64authorization token that is needed to access the cloud backend to the provider implementation; the runtime
65also handles re-authorization in case a token has expired.
66
67\subsection identity File and Folder Identity
68
69Each file and folder has an identity that is unique within the account that contains the file.
70Identities are opaque strings that are assigned by the provider. (Clients cannot control the identity
71of files and folders).
72
73The identity of files and folders is immutable during their life time, and identities
74can be compared for equality and less-than.
75This means that clients can store identities in an external file or a database and expect them to
76continue to refer to the same file or folder even after a process re-start.
77
78Identities are not globally unique; different files in different accounts may have the same identity.
79
80Identities may not be unique across object life times. For example, if a client creates a file and deletes that
81file again, another file that is created later may get the identity that was used by the earlier file.
82Similarly, moving a file or folder may (or may not) change its identity.
83
84\subsection etags ETags
85
86Files have an associated <a href="https://en.wikipedia.org/wiki/HTTP_ETag">ETag</a>. The ETag for a file
87changes every time a file is updated. When uploading or downloading a file, a client can specify whether to check
88the ETag of a file. If the client requests the ETag to be checked, the provider performs the
89upload or download only if the ETag sent by the client matches the ETag of the file. This allows clients to ensure
90that a file has not been modified since the client last retrieved the file's metadata.
91
92ETags have no particular format; they simply are opaque strings that change each time a file changes.
93
94\subsection names File and Folder Names
95
96Clients choose the name of files and folders they create. However, providers may modify a name that is provided
97by a client, for example, a provider could map upper-case to lower-case letters.
98The actual name of a new file or folder is returned by the provider as part of the metadata.
99
100Different providers place different restrictions on the characters that are allowed as part of a name.
101The following meta-characters are likely to cause problems and should not be used in file or folder names:
102
103<tt>/</tt>, <tt>\\</tt>, <tt>*</tt>, <tt>"</tt>, <tt>:</tt>, <tt>\<</tt>, <tt>\></tt>, <tt>|</tt>, <tt>?</tt>, <tt>#</tt>, and <tt>%</tt>.
104
105The name of a file or folder is not necessarily unique within its parent folder. (Some cloud services
106allow different files with the same name in the same folder.) Similarly, a single file or folder may
107have more than one name within the same parent folder. (The name of a file or folder does not indicate
108anything about its identity. If you want to establish whether a file is the same as another file, you must
109compare the files' identities, not their names.)
110
111The name of a root folder can be anything. (It might be <tt>/</tt>, but could be something else.)
112
113\subsection metadata Metadata
114Files and folders have metadata associated with them.
115Metadata items are key&ndash;value pairs. The key (a string) identifies the item and determines the type of
116the corresponding value (such as string or integer).
117
118Some metadata is guaranteed to always be present (such as file
119size and modification time); other metadata may or may not be available, depending on the provider. (For example,
120not all providers maintain a modification time for directories.)
121
122The framework defines a number of well-known metadata keys that are used by the majority of providers (such as
123<tt>size_in_bytes</tt>). In addition, providers can also add provider-specific metadata. Clients can specify
124which metadata items should be returned by the provider. The requested metadata items are hints only: a provider
125may return fewer or more metadata items than were requested by the client.
126
127A special metadata key (<tt>__ALL__</tt>)
128indicates that the provider should return all available metadata. If a client does not request any specific
129metadata, the provider returns a default set of items. (Clients can specify which metadata should be returned
130in order to minimize the amount of data that is copied over the network; some providers support extensive metadata
131that consumes considerable bandwidth.)
132
133If a provider supplies non-standard metadata items, the item keys have a provider-specific prefix, such as
134<tt>mcloud:</tt> or <tt>gdrive:</tt>.
135
136\subsection uploads-downloads Uploads and Downloads
137
138Uploads and downloads take place over a UNIX domain socket. When a client requests an upload, the runtime creates
139a socket pair and passes a socket that is open for writing to the client, and a socket that is open for reading
140to the provider. (For downloads, the read and write ends are reversed.) The client and provider write and read
141the data for the file to/from their respective sockets. Once the client has written all the data, it makes one
142final API call to check whether the provider has correctly received all the data. This call returns the new metadata
143for the file (or an error, if the upload failed).
144
145Downloads work the same way as uploads, but with the read and write roles reversed.
146
147\section provider Implementing a Provider
148
149This section provides an overview of the provider API and explains the semantics you are expected to adhere
150to in your provider implementation. We strongly recommend that you also read the source code
151for the <a href="https://code.launchpad.net/storage-provider-webdav">Owncloud provider</a>. It is useful
152to learn how to implement a provider that uses HTTP to access a cloud service. (While the details
153of the interactions with your provider will differ, the principles will be
154very similar.)
155
156\subsection provider-overview Overview
157
158To implement a provider, you must create an implementation of the abstract
159@ref unity::storage::provider::ProviderBase "ProviderBase" class:
160
161\code{.cpp}
162class ProviderBase : public std::enable_shared_from_this<ProviderBase>
163{
164public:
165 ProviderBase();
166 virtual ~ProviderBase();
167
168 virtual boost::future<ItemList> roots(vector<string> const& keys,
169 Context const& context) = 0;
170 virtual boost::future<ItemList> lookup(string const& parent_id,
171 string const& name,
172 vector<string> const& keys,
173 Context const& context) = 0;
174 // More methods here...
175};
176\endcode
177
178The base class defines a pure virtual method for each operation (such as
179@ref unity::storage::provider::ProviderBase::list "list()",
180@ref unity::storage::provider::ProviderBase::lookup "lookup()",
181@ref unity::storage::provider::ProviderBase::update "update()", etc.)
182
183Note that each operation returns a <code>boost::future</code> to the storage framework runtime;
184your operation implementation must make the future ready once the result of the operation is available.
185
186For this example, we assume that you want to create a provider for a (hypothetical) <code>MyCloud</code>
187storage service.
188
189To connect your implementation class to the runtime, you instantiate a <code>Server</code> instance and call its
190@ref unity::storage::provider::Server::init "init()" and
191@ref unity::storage::provider::Server::run "run()" methods:
192
193\code{.cpp}
194class MyCloudProvider : public ProviderBase
195{
196public:
197 MyCloudProvider();
198 virtual ~MyCloudProvider();
199
200 boost::future<ItemList> roots(vector<string> const& keys,
201 Context const& context) override;
202 boost::future<ItemList> lookup(string const& parent_id,
203 string const& name,
204 vector<string> const& keys,
205 Context const& context) override;
206 // More methods here...
207};
208
209int main(int argc, char* argv[])
210{
211 string const bus_name = "com.acme.StorageFramework.Provider.MyCloud";
212 string const account_service_id = "storage-provider-mycloud";
213
214 try
215 {
216 Server<MyCloudProvider> server(bus_name, account_service_id);
217 server.init(argc, argv);
218 return server.run();
219 }
220 catch (std::exception const& e)
221 {
222 cerr << argv[0] << ": " << e.what() << endl;
223 return 1;
224 }
225}
226\endcode
227
228The <code>Server</code> class is a template that instantiates your <code>MyCloudProvider</code> class.
229You must provide a DBus name for your service, as well as the service ID with which your provider is
230known to Online Accounts.
231
232The call to @ref unity::storage::provider::Server::init "init()" initializes the storage framework runtime,
233connects the service to DBus and, once
234you call @ref unity::storage::provider::Server::run "run()",
235starts an event loop that forwards incoming requests to the methods of your
236<code>MyCloudProvider</code> class.
237
238The @ref unity::storage::provider::Server::run "run()"
239method returns after some period of inactivity (30 seconds, by default), so the provider
240process shuts down when it is not needed, and is started automatically if it is not running at the time a client
241request arrives.
242
243If you need to pass additional arguments to the <code>MyCloudProvider</code> constructor, you can do so
244by creating a server class of your own that derives from @ref unity::storage::provider::ServerBase "ServerBase".
245That class has an abstract @ref unity::storage::provider::ServerBase::make_provider() "make_provider()"
246factory method that you can override. The runtime calls the factory method to instantiate your provider.
247
248\subsection threading Threading Considerations
249
250The runtime invokes all methods of your provider implementation on the main thread. Your methods
251<i>must not</i> block for any length of time. Unless you know that a method will return
252immediately (within a few milliseconds), you <i>must</i> implement the method such that it can
253complete in the background (either using asynchronous I/O or a separate thread).
254
255The runtime uses <a href="http://doc.qt.io/qt-5/qcoreapplication.html"><code>QCoreApplication</code></a>,
256so your implementation can use anything that uses the Qt or glib event loop. (Alternatively, you can also
257run a separate event loop if you prefer.)
258
259When starting an async operation, create a
260<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,
261call <code>set_value()</code> or <code>set_exception()</code> to make the future ready.
262
263For a synchronous implementation, you can use
264<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
265<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>
266or <a href="http://www.boost.org/doc/libs/master/libs/exception/doc/throw_exception.html"><code>boost::throw_exception</code></a>.
267
268The storage framework runtime is thread-safe, that is, you can up-call into the runtime (for example, to indicate
269an error during an upload) from any thread. The runtime takes care of any necessary locking and moving data
270to the correct thread for marshaling.
271
272\subsection provider-error-handling Error Handling
273
274All error handling uses exceptions. If you call into the runtime and it detects a problem, the runtime throws
275an exception that derives from <code>std::exception</code> or, for specific well-known problems, from
276@ref unity::storage::provider::StorageException "StorageException". (The code in <code>main()</code> above
277relies on this behavior to report any errors and return non-zero exit status.)
278
279If your implementation uses a library that does not transparently handle exceptions (such Qt), you
280must catch exceptions and deal with them as appropriate when you call into the runtime.
281
282Conversely, the runtime is exception-safe. If you encounter an error during an operation that
283throws an exception and you let the exception escape, the runtime will intercept the exception, abort the
284operation, and marshal an appropriate error back to the client. (If you throw something unexpected, such as
285<tt>99</tt> or a <code>std::exception</code>, the client receives a generic error.)
286This means that, to indicate an error to the runtime, you can always throw an exception or, alternatively,
287for methods that return a future, return a future that contains the exception.
288
289When throwing an exception, be as detailed in the error message as possible and include any details you receive
290from the cloud provider, as well as the identity of the file. Without this, it may be impossible to
291diagnose a problem from log files.
292
293The storage framework defines a small number of exceptions that indicate specific error conditions to the client.
294You <i>must</i> use these exception to indicate their respective error conditions, otherwise
295the client only knows that something did not work, but does not know the real cause of the error. <i>All</i> providers
296<i>must</i> adhere to this common set of error semantics, otherwise clients cannot implement meaningful
297error messages and error recovery.
298
299All provider exceptions derive from a common abstract base exception class:
300
301\code{.cpp}
302class StorageException : public std::exception
303{
304public:
305 // ...
306 virtual char const* what() const noexcept override;
307
308 std::string type() const;
309 std::string error_message() const;
310};
311
312class RemoteCommsException : public StorageException { ... };
313class NotExistsException : public StorageException { ... };
314// etc...
315\endcode
316
317The @ref unity::storage::provider::StorageException::type "type()"
318method returns the name of the exception (such as "NotExistsException"), and
319@ref unity::storage::provider::StorageException::error_message "error_message()"
320returns a message you provide when you throw the exception.
321(@ref unity::storage::provider::StorageException::what "what()"
322returns a string that contains both the exception name and the error message.)
323
324The concrete derived exceptions indicate error semantics as follows:
325
326- @ref unity::storage::provider::RemoteCommsException "RemoteCommsException"
327
328 This exception indicates an (unexpected) error in the communication between your provider and the remote
329 cloud service, such as the remote server being unreachable or returning garbled data.
330
331- @ref unity::storage::provider::NotExistsException "NotExistsException"
332
333 This exception indicates that a file or folder the client wants to access does not exist. You must
334 use this exception only if the remote cloud service has <i>authoritatively</i> indicated non-existence
335 (such as with an HTTP 404 response). Do not use this exception for non-authoritative errors, such as
336 timeouts or similar.
337
338- @ref unity::storage::provider::ExistsException "ExistsException"
339
340 This exception indicates that a file or folder already exists, preventing an operation (such as a create or copy)
341 from completing. Use this exception only if the remote cloud service has <i>authoritatively</i>
342 indicated this error. Do not use this exception for non-authoritative errors, such as timeouts
343 or similar.
344
345- @ref unity::storage::provider::ConflictException "ConflictException"
346
347 This exception indicates an ETag mismatch: the client asked for a file to be updated or downloaded, but
348 the file's ETag has changed since.
349
350- @ref unity::storage::provider::UnauthorizedException "UnauthorizedException"
351
352 This exception indicates that the remote cloud service has indicated an authorization failure. Note that
353 you must throw this exception only if the cloud service indicates invalid credentials. Do <i>not</i> throw
354 this exception for permission errors (such as an attempt to write to a read-only file). In particular,
355 do not throw this exception for an HTTP 403 error, which indicates that access to a particular resource
356 was denied even though the credentials were valid.
357
358 Some cloud providers use authorization tokens that expire after some time. If you detect an
359 authorization failure in your code and throw this exception, the runtime re-retrieves the token from Online
360 Accounts and transparently calls your operation again if the new token differs. If the runtime receives an
361 @ref unity::storage::provider::UnauthorizedException "UnauthorizedException" on this second attempt too,
362 it returns the error to the client.
363
364- @ref unity::storage::provider::PermissionException "PermissionException"
365
366 This exception indicates that a file or folder cannot be accessed due to insufficient permissions (such as an
367 attempt to write to a read-only file). Do <i>not</i> throw this exception for authorization errors.
368
369- @ref unity::storage::provider::QuotaException "QuotaException"
370
371 This exception indicates that the provider has run out of space or that the maximum number of files and/or
372 folders was exceeded.
373
374- @ref unity::storage::provider::CancelledException "CancelledException"
375
376 This exception indicates that the client attempted to interact with an upload or download after it was cancelled.
377 Due to the way the API is structured, you will not ever need to throw this exception. It is provided for
378 completeness and to allow for provider implementations that do not use the storage framework API.
379
380- @ref unity::storage::provider::LogicException "LogicException"
381
382 This exception indicates that the client has used the API incorrectly, such as indicating that an upload has
383 completed before it has written all the data. In general, use
384 @ref unity::storage::provider::LogicException "LogicException"
385 for any errors that relate to operations being called in the wrong order, and for semantic errors
386 (such as an attempt to download a folder or to list a file).
387
388- @ref unity::storage::provider::InvalidArgumentException "InvalidArgumentException"
389
390 This exception indicates that the value of an argument supplied by the client is unacceptable, such as
391 an empty string where a non-empty string was excepted, or a negative integer where a positive integer was expected.
392
393- @ref unity::storage::provider::ResourceException "ResourceException"
394
395 This is a generic "catch-all" exception that you can throw to indicate unexpected errors that do not fit into
396 any of the other categories. For example, <code>ResourceException</code> is appropriate to indicate out of
397 file descriptors, failure to locate a configuration file, or any other unexpected system error. You should
398 provide as much contextual information about such errors as possible. In particular, unexpected errors
399 typically need to be diagnosed from log files. This means that you should provide, at least, the full error
400 message you received from the cloud service (or the operating system), together with all other relevant
401 details, such as the name and identity of the file, the URL of a failed request,
402 any HTTP error information, and so on.
403
404- @ref unity::storage::provider::UnknownException "UnknownException"
405
406 If your code throws an exception that does not derive from <code>StorageException</code>, it returns
407 <code>UnknownException</code> to the client. Do not throw this exception explicitly; is is a "catch-all"
408 exception that the runtime uses to indicate that something completely unexpected happened. (If a client
409 receives this exception from your provider, chances are that you have forgotten to handle an error condition
410 somewhere.)
411
412\subsection metadata-keys Metadata Keys
413Each method of your provider has a <code>keys</code> parameter of type <code>vector<string></code>. The parameter
414is provided by the client and indicates which metadata items you should include in the metadata you return
415from the operation.
416
417If the vector is empty, this indicates that you should return a set of default metadata items. In practice, this
418means that you should return whatever metadata you can cheaply receive from the cloud provider (in terms
419of bandwidth and number of requests).
420
421If the vector contains a single entry <code>__ALL__</code>, you should return complete metadata. In particular,
422you should return as many of the well-known metadata items that are defined in
423common.h as possible, plus
424any provider-specific metadata items you may support (prefixed with a provider name, such as <tt>Mycloud:</tt>).
425
426If the client provides a specific list of keys, you should restrict the metadata to those items requested by the
427client. This is useful to reduce traffic between your provider and the cloud service, if the cloud service
428requires additional requests for particular metadata items: you need to provide that metadata only if the client asks
429for it.
430
431The keys provided by the client are a hint only, that is, it is fine to provide more items than the client asked for
432(if you happen to have them available anyway at no extra cost), and it is legal to not provide all of the items
433the client asked for.
434
435If you receive a key that is not one of the keys defined in <code>common.h</code> and is not one of the keys
436you recognize as provider-specific, do <i>not</i> return an error from the operation. Instead, write a log message
437with the details (your provider name, the identity and name of the file and folder, and the invalid key) and
438ignore the invalid key.
439
440Note that <code>common.h</code> defines symbolic constants for metadata keys. Use these symbolic constants
441instead of hard-coding the corresponding string literals.
442
443\note For files, the <code>SIZE_IN_BYTES</code> and <code>LAST_MODIFIED_TIME</code> metadata items are mandatory;
444you must always supply these, whether the client asks for them or not.
445
446\subsection authorization Authorization
447
448Each method of your provider has a trailing parameter of type
449@ref unity::storage::provider::Context "Context":
450
451\code{.cpp}
452struct Context
453{
454 uid_t uid;
455 pid_t pid;
456 string security_label;
457
458 Credentials credentials;
459};
460\endcode
461
462The structure contains the user ID, process ID, and the
463<a href="https://help.ubuntu.com/lts/serverguide/apparmor.html">Apparmor</a> security label of the calling client,
464plus credentials that you can use for authorization with the cloud service. In turn, the credentials are a variant
465type that enables authorization with <a href="https://oauth.net/1/">OAuth1</a>,
466<a href="https://oauth.net/2/">OAuth2</a>, as well as with user name and password:
467
468\code{.cpp}
469struct NoCredentials
470{
471};
472
473struct OAuth1Credentials
474{
475 string consumer_key;
476 string consumer_secret;
477 string token;
478 string token_secret;
479};
480
481struct OAuth2Credentials
482{
483 string access_token;
484};
485
486struct PasswordCredentials
487{
488 string username;
489 string password;
490 string host;
491};
492
493typedef boost::variant<boost::blank,OAuth1Credentials,OAuth2Credentials,PasswordCredentials> Credentials;
494\endcode
495
496If you receive an authorization error from the cloud provider with the credentials that are passed to you
497by the runtime, you must throw an @ref unity::storage::provider::UnauthorizedException "UnauthorizedException".
498This prompts the runtime to refresh the token if it may have expired and call your your method
499a second time with the new token. If that second attempt also throws
500@ref unity::storage::provider::UnauthorizedException "UnauthorizedException", the runtime propagates the
501error to the client.
502
503\subsection download-upload Downloads and Uploads
504
505To implement download and upload, you must create implementations of the abstract
506@ref unity::storage::provider::DownloadJob "DownloadJob" and @ref unity::storage::provider::UploadJob "UploadJob"
507classes and return them from your @ref unity::storage::provider::ProviderBase::download "download()" and
508@ref unity::storage::provider::ProviderBase::update "update()" (or
509@ref unity::storage::provider::ProviderBase::create_file "create_file()") methods, respectively.
510The two base classes are very similar, so we focus on @ref unity::storage::provider::DownloadJob "DownloadJob" here.
511
512\code{.cpp}
513class MyDownloadJob : public unity::storage::provider::DownloadJob
514{
515public:
516 MyDownloadJob(shared_ptr<MyCloudProvider> const& provider,
517 string const& item_id,
518 string const& match_etag);
519 virtual ~LocalDownloadJob();
520
521 boost::future<void> cancel() override;
522 boost::future<void> finish() override;
523
524private:
525 // ...
526};
527\endcode
528
529In the implementation of your constructor, you must initiate the download. The download must be able to proceed
530without blocking. How you do this is up to you. You can run the download in a separate thread, use the
531runtime's Qt event loop, or run a separate event loop. (The storage framework runtime does not care.)
532
533Your <code>MyDownloadJob</code> receives a socket that is open for writing and connected to the client by calling
534@ref unity::storage::provider::DownloadJob::write_socket "write_socket()" on its base class. You must write the
535data for the file to the socket (from which the client can read it).
536
537At some point, the runtime either calls your @ref unity::storage::provider::DownloadJob::cancel "cancel()" method,
538in which case you simply abort the download, or it calls @ref unity::storage::provider::DownloadJob::finish "finish()".
539The purpose of @ref unity::storage::provider::DownloadJob::finish "finish()" is to confirm whether the download
540completed successfully. At this point, you must check whether you have received <a>all</a> of the data for the file
541from the cloud provider and have successfully written it to the socket. If so, you return a ready future; if not,
542you return a future that holds the appropriate @ref unity::storage::provider::StorageException "StorageException"
543to indicate the nature of the error.
544
545@ref unity::storage::provider::DownloadJob "DownloadJob" and @ref unity::storage::provider::UploadJob "UploadJob"
546classes also provide a @ref unity::storage::provider::DownloadJob::report_error "report_error()" method that
547you can use to report any errors that arise while your downloader runs in the background. (If you call
548@ref unity::storage::provider::DownloadJob::report_error "report_error()", neither
549@ref unity::storage::provider::DownloadJob::finish "finish()" nor
550@ref unity::storage::provider::DownloadJob::cancel "cancel()" are called by the runtime.)
551
552The @ref unity::storage::provider::DownloadJob "DownloadJob" base class also provides a
553@ref unity::storage::provider::DownloadJob::report_complete "report_complete()" method that you call
554to indicate successful completion without having to wait for the call to
555@ref unity::storage::provider::DownloadJob::finish "finish()".
556
557\subsection buffering Download and Upload Buffering
558
559When implementing your provider, you need to be aware of when (and when not) to buffer data.
560
561For downloads, you should write the data to the client socket as soon as you receive it from the cloud provider.
562This is necessary to enable streaming. (A media player should be able to start playing a song or video as soon
563as possible.)
564
565For uploads, the situation is the opposite, and you should buffer any uploads that are larger than than a few
566kilobytes in the file system before starting the upload with the provider.
567This is particularly important for mobile devices, where applications may be suspended for extended periods.
568If you write data to the cloud provider while the client is still writing it, and the client application is suspended,
569the upload gets stuck until the client application resumes (by which time your connection with the cloud provider
570may have timed out, or the user may have walked out of reach of the network).
571
572Note that, eventually, you will either receive the @ref unity::storage::provider::DownloadJob::finish "finish()"
573call to confirm whether or not there was an error (or a @ref unity::storage::provider::DownloadJob::cancel "cancel()"
574call if the client application dies). This means that, if your cloud provider is able to resume a previously
575interrupted upload, you can re-start an upload that failed due to loss of the network. (Be judicious in how
576aggressively you try to resume though; on a battery-powered device, it is expensive to turn on the radio, so you
577should use a back-off algorithm and give up if the upload cannot be resumed within a reasonable period of time.)
578
579\section local-provider The Local Provider
580
581The storage framework includes a local provider that stores files in the local file system.
582In a classic environment, the root of the local files is <code>$XDG_DATA_DIR/storage-framework/local</code>;
583in a Snap environment, the root is <code>$SNAP_USER_COMMON/storage-framework/local</code>.
584You can set <code>$SF_LOCAL_PROVIDER_ROOT</code> to change the root directory. (This is intended mainly for testing.)
585
586The local provider uses <code>com.canonical.StorageFramework.Provider.Local</code> as the DBus name. The object path
587is <code>/provider/0</code>.
588*/
0589
=== modified file 'include/unity/storage/common.h'
--- include/unity/storage/common.h 2016-11-03 03:41:24 +0000
+++ include/unity/storage/common.h 2017-04-06 07:42:38 +0000
@@ -23,36 +23,57 @@
23namespace storage23namespace storage
24{24{
2525
26/**
27\brief Indicates the type of an item.
28*/
29
26enum class ItemType30enum class ItemType
27{31{
28 file,32 file, /*!< The item represents a file. */
29 folder,33 folder, /*!< The item represents a (non-root) folder. */
30 root,34 root, /*!< The item represents a root folder. */
31 LAST_ENTRY__35 LAST_ENTRY__ /*!< End of enumeration marker. */
32};36};
3337
38/**
39\brief Determines the behavior in case of an ETag mismatch.
40*/
41
34enum class ConflictPolicy42enum class ConflictPolicy
35{43{
36 error_if_conflict,44 error_if_conflict, /*!< Return an error if the ETag has changed. */
37 ignore_conflict,45 ignore_conflict, /*!< Ignore ETag mismatch and overwrite or replace the file. */
38 overwrite = ignore_conflict, // TODO: remove this, it's here only for compatibility with v1 API46 overwrite = ignore_conflict, // TODO: remove this, it's here only for compatibility with v1 API
39};47};
4048
49/**
50\brief This namespace defines well-known metadata keys.
51
52See \ref common.h for details.
53*/
54
41namespace metadata55namespace metadata
42{56{
4357
44static char constexpr SIZE_IN_BYTES[] = "size_in_bytes"; // int64_t, >= 058// Symbolic constants for well-known metadata keys.
45static char constexpr CREATION_TIME[] = "creation_time"; // String, ISO 8601 format59
46static char constexpr LAST_MODIFIED_TIME[] = "last_modified_time"; // String, ISO 8601 format60// Doxygen bug makes it impossible to document each constant.
47static char constexpr CHILD_COUNT[] = "child_count"; // int64_t, >= 061
48static char constexpr DESCRIPTION[] = "description"; // String62static char constexpr SIZE_IN_BYTES[] = "size_in_bytes"; // int64_t >= 0
49static char constexpr DISPLAY_NAME[] = "display_name"; // String63static char constexpr CREATION_TIME[] = "creation_time"; // string, ISO 8601 format
50static char constexpr FREE_SPACE_BYTES[] = "free_space_bytes"; // int64_t, >= 064static char constexpr LAST_MODIFIED_TIME[] = "last_modified_time"; // string, ISO 8601 format
51static char constexpr USED_SPACE_BYTES[] = "used_space_bytes"; // int64_t, >= 065static char constexpr CHILD_COUNT[] = "child_count"; // int64_t >= 0
52static char constexpr CONTENT_TYPE[] = "content_type"; // String66static char constexpr DESCRIPTION[] = "description"; // string
53static char constexpr WRITABLE[] = "writable"; // Bool67static char constexpr DISPLAY_NAME[] = "display_name"; // string
54static char constexpr MD5[] = "md5"; // String68static char constexpr FREE_SPACE_BYTES[] = "free_space_bytes"; // int64_t >= 0
55static char constexpr DOWNLOAD_URL[] = "download_url"; // String69static char constexpr USED_SPACE_BYTES[] = "used_space_bytes"; // int64_t >= 0
70static char constexpr CONTENT_TYPE[] = "content_type"; // string
71static char constexpr WRITABLE[] = "writable"; // int64_t, 0 or 1
72static char constexpr MD5[] = "md5"; // string
73static char constexpr DOWNLOAD_URL[] = "download_url"; // string
74
75// A single-element vector containing the key ALL indicates that the client would like all available
76// metadata to be returned by the provider.
5677
57static char constexpr ALL[] = "__ALL__";78static char constexpr ALL[] = "__ALL__";
5879
5980
=== modified file 'include/unity/storage/provider/DownloadJob.h'
--- include/unity/storage/provider/DownloadJob.h 2016-07-12 02:22:05 +0000
+++ include/unity/storage/provider/DownloadJob.h 2017-04-06 07:42:38 +0000
@@ -37,25 +37,129 @@
37class ProviderInterface;37class ProviderInterface;
38}38}
3939
40/**
41\brief Abstract base class for download implementations.
42
43When the runtime calls ProviderBase::download(), you must return
44a new downloader instance that derives from DownloadJob. Your implementation is responsible
45for retrieving the file's data from the cloud provider and writing it to
46the socket provided by the runtime.
47
48You can implement your downloader
49any way you wish, such as by running the download in a separate thread, or
50by using async I/O driven by the runtime's (or any other) event loop.
51
52The runtime invokes all methods on the downloader from the main thread.
53*/
54
40class UNITY_STORAGE_EXPORT DownloadJob55class UNITY_STORAGE_EXPORT DownloadJob
41{56{
42public:57public:
58 /**
59 \brief Construct a downloader.
60 \param download_id An identifier for this particular download. You can use any non-empty string,
61 as long as it is unique among all downloads that are in progress within the corresponding account.
62 (A simple incrementing counter will work fine, or you can use the download identifier you receive
63 from the cloud service.)
64 The runtime uses the <code>download_id</code> to distinguish different downloads that may be
65 in progress concurrently, and it ensures that each ID can be used only by its corresponding client.
66 */
43 DownloadJob(std::string const& download_id);67 DownloadJob(std::string const& download_id);
44 virtual ~DownloadJob();68 virtual ~DownloadJob();
4569
70 /**
71 \brief Returns the download ID.
72 \return The value of the <code>download_id</code> parameter that was passed to the constructor.
73 */
46 std::string const& download_id() const;74 std::string const& download_id() const;
75
76 /**
77 \brief Returns the socket to write the file contents to.
78 \return A socket that is open for writing. You must write the file contents to this socket (which is
79 connected to the client by the runtime).
80 */
47 int write_socket() const;81 int write_socket() const;
4882
49 // If the result of the download is reported with either of the83 /**
50 // following two functions, then neither cancel() or finish() will84 \brief Informs the runtime that a download completed successfully.
51 // be called.85
86 This method makes it unnecessary to wait for the runtime to call finish() in order to confirm
87 whether a download completed successfully. You can call report_complete() as soon as you have
88 written all of a file's data to the download socket and closed it successfully. This can be convenient
89 if a download runs in a separate thread or event loop because there is no need to store success/error state
90 for use in finish().
91
92 You can call report_complete() from an arbitrary thread.
93
94 If you call report_complete(), the runtime guarantees that neither finish() nor cancel() will be called, so
95 you must reclaim any resources associated with the download before calling report_complete().
96 \see report_error(), finish()
97 */
52 void report_complete();98 void report_complete();
53 void report_error(std::exception_ptr p);99
54100 /**
101 \brief Informs the runtime that a download encountered an error.
102
103 As for report_complete(), you can call report_error() as soon as you encounter an error during a download
104 (such as losing the connection to the cloud provider, or getting an error when writing to the download
105 socket).
106
107 You can call report_error() from an arbitrary thread.
108
109 If you call report_error(), the runtime guarantees that neither finish() nor cancel() will be called, so
110 you must reclaim any resources associated with the download before calling report_error().
111
112 \param storage_exception You <i>must</i> pass a StorageException to indicate the reason for the failure.
113 \see report_complete(), finish()
114 */
115 void report_error(std::exception_ptr storage_exception);
116
117 /**
118 \brief Cancel this download.
119
120 The runtime calls this method when a client explicitly cancels a download or crashes.
121 Your implementation should reclaim all resources that are used by the download. In particular,
122 you should stop writing any more data and close the download socket. In addition,
123 you should reclaim any resources (such as open connections) that are associated
124 with the download from the cloud provider (possibly after informing the cloud provider of
125 the cancellation).
126
127 The runtime guarantees that cancel() will be called only once, and that finish() will not be called
128 if cancel() was called.
129
130 If any errors are encountered, you <i>must</i> report them by returning a future that stores
131 a StorageException. Do <i>not</i> call report_error() from inside cancel().
132
133 \return A future that becomes ready (or contains a StorageException) once cancellation is complete.
134 */
55 virtual boost::future<void> cancel() = 0;135 virtual boost::future<void> cancel() = 0;
136
137 /**
138 \brief Finalize this download.
139
140 The runtime calls this method when a client finishes a download. Your implementation <i>must</i> verify
141 that it has successfully written <i>all</i> of the file's data at this point.
142 If not, it <i>must</i> store a LogicException
143 in the returned future. (This is essential to make sure that the client can distinguish orderly socket
144 closure from disorderly closure and has not received partial data for the file.)
145
146 You should close the download socket and reclaim any resources (such as open
147 connections) that are associated with the download from the cloud provider.
148
149 The runtime guarantees that finish() will be called only once, and that cancel() will not be called
150 if finish() was called.
151
152 If any errors are encountered, you <i>must</i> report them by returning a future that stores
153 a StorageException. Do <i>not</i> call report_error() from inside finish().
154
155 Do <i>not</i> call report_complete() from inside finish().
156
157 \return A future that becomes ready (or contains a StorageException) once the download is complete.
158 \see report_complete(), report_error()
159 */
56 virtual boost::future<void> finish() = 0;160 virtual boost::future<void> finish() = 0;
57161
58protected:162private:
59 DownloadJob(internal::DownloadJobImpl *p) UNITY_STORAGE_HIDDEN;163 DownloadJob(internal::DownloadJobImpl *p) UNITY_STORAGE_HIDDEN;
60 internal::DownloadJobImpl *p_ = nullptr;164 internal::DownloadJobImpl *p_ = nullptr;
61165
62166
=== modified file 'include/unity/storage/provider/Exceptions.h'
--- include/unity/storage/provider/Exceptions.h 2017-03-30 02:52:53 +0000
+++ include/unity/storage/provider/Exceptions.h 2017-04-06 07:42:38 +0000
@@ -37,20 +37,39 @@
37// - src/qt/internal/unmarshal_error.cpp37// - src/qt/internal/unmarshal_error.cpp
3838
39/**39/**
40\brief Base exception class for all server-side exceptions.40\brief Abstract base exception class for all server-side exceptions.
41*/41*/
42
42class UNITY_STORAGE_EXPORT StorageException : public std::exception43class UNITY_STORAGE_EXPORT StorageException : public std::exception
43{44{
44public:45public:
46 /**
47 \brief Constructs a StorageException.
48 \param exception_type The concrete type name of the exception, such as "ConflictException".
49 \param error_message The error message text for the exception.
50 */
45 StorageException(std::string const& exception_type, std::string const& error_message);51 StorageException(std::string const& exception_type, std::string const& error_message);
4652
47protected:53protected:
48 ~StorageException();54 ~StorageException();
4955
50public:56public:
57 /**
58 \brief Returns an error string.
59 \return The error message prefixed by the exception type, such as "ConflictException: ETag mismatch".
60 */
51 virtual char const* what() const noexcept override;61 virtual char const* what() const noexcept override;
5262
63 /**
64 \brief Returns the exception type name.
65 \return The value of the <code>exception_type</code> parameter that was passed to the constructor.
66 */
53 std::string type() const;67 std::string type() const;
68
69 /**
70 \brief Returns the except error message.
71 \return The value of the <code>error_message</code> parameter that was passed to the constructor.
72 */
54 std::string error_message() const;73 std::string error_message() const;
5574
56private:75private:
@@ -61,23 +80,45 @@
6180
62/**81/**
63\brief Indicates errors in the communication between the storage provider and the cloud service.82\brief Indicates errors in the communication between the storage provider and the cloud service.
83
84Use this exception for unexpected communication errors, such as the remote server being unreachable
85or returning garbled data.
64*/86*/
87
65class UNITY_STORAGE_EXPORT RemoteCommsException : public StorageException88class UNITY_STORAGE_EXPORT RemoteCommsException : public StorageException
66{89{
67public:90public:
91 /**
92 \brief Construct a RemoteCommsException.
93 \param error_message The error message for the exception.
94 */
68 RemoteCommsException(std::string const& error_message);95 RemoteCommsException(std::string const& error_message);
69 ~RemoteCommsException();96 ~RemoteCommsException();
70};97};
7198
72/**99/**
73\brief Indicates that an item does not exist or could not be found.100\brief Indicates that an item does not exist.
101
102Use this exception only if the remote cloud service has <i>authoritatively</i> indicated non-existence
103(such as with an HTTP 404 response). Do not use this exception for non-authoritative errors, such as
104timeouts or similar.
74*/105*/
106
75class UNITY_STORAGE_EXPORT NotExistsException : public StorageException107class UNITY_STORAGE_EXPORT NotExistsException : public StorageException
76{108{
77public:109public:
110 /**
111 \brief Construct a RemoteCommsException.
112 \param error_message The error message for the exception.
113 \param key The name or identity of the non-existent item.
114 */
78 NotExistsException(std::string const& error_message, std::string const& key);115 NotExistsException(std::string const& error_message, std::string const& key);
79 ~NotExistsException();116 ~NotExistsException();
80117
118 /**
119 \brief Return the key.
120 \return The value of the <code>key</code> parameter that was passed to the constructor.
121 */
81 std::string key() const;122 std::string key() const;
82123
83private:124private:
@@ -85,15 +126,35 @@
85};126};
86127
87/**128/**
88\brief Indicates that an item cannot be created because it exists already.129\brief Indicates that an item already exists.
130
131Use this exception only if the remote cloud service has <i>authoritatively</i> indicated that an operation
132cannot be carried out because an item exists already. Do not use this exception for non-authoritative errors,
133such as timeouts or similar.
89*/134*/
135
90class UNITY_STORAGE_EXPORT ExistsException : public StorageException136class UNITY_STORAGE_EXPORT ExistsException : public StorageException
91{137{
92public:138public:
139 /**
140 \brief Construct an ExistsException.
141 \param error_message The error message for the exception.
142 \param identity The identity of the item.
143 \param name The name of the item.
144 */
93 ExistsException(std::string const& error_message, std::string const& identity, std::string const& name);145 ExistsException(std::string const& error_message, std::string const& identity, std::string const& name);
94 ~ExistsException();146 ~ExistsException();
95147
148 /**
149 \brief Return the identity.
150 \return The value of the <code>identity</code> parameter that was passed to the constructor.
151 */
96 std::string native_identity() const;152 std::string native_identity() const;
153
154 /**
155 \brief Return the name of the item.
156 \return The value of the <code>name</code> parameter that was passed to the constructor.
157 */
97 std::string name() const;158 std::string name() const;
98159
99private:160private:
@@ -102,33 +163,42 @@
102};163};
103164
104/**165/**
105\brief Indicates that an upload or download detected a version mismatch.166\brief Indicates that an upload or download detected a ETag mismatch.
106*/167*/
168
107class UNITY_STORAGE_EXPORT ConflictException : public StorageException169class UNITY_STORAGE_EXPORT ConflictException : public StorageException
108{170{
109public:171public:
172 /**
173 \brief Construct a ConflictException.
174 \param error_message The error message for the exception.
175 */
110 ConflictException(std::string const& error_message);176 ConflictException(std::string const& error_message);
111 ~ConflictException();177 ~ConflictException();
112};178};
113179
114/**180/**
115\brief Indicates that an operation failed because the authentication credentials are invalid or expired.181\brief Indicates that an operation failed because the credentials are invalid or have expired.
116182
117A provider implementation must throw this exception if it cannot reach183A provider implementation must throw this exception if it cannot reach
118its provider because the credentials are invalid. Do not throw this184its provider because the credentials are invalid. Do <i>not</i> throw this
119exception if the credentials are valid, but an operation failed due to185exception if the credentials are valid, but an operation failed due to
120insufficient permission for an item (such as an attempt to write to a186insufficient permission for an item (such as an attempt to write to a
121read-only file).187read-only file).
122188
123Typically, this will cause the request to be retried after refreshing189Typically, this exception will cause the runtime to retry the operation after refreshing
124the authentication credentials, but may be returned to the client on190the credentials; the exception may be returned to the client on repeated failures.
125repeated failures.
126191
127\see PermissionException192\see PermissionException
128*/193*/
194
129class UNITY_STORAGE_EXPORT UnauthorizedException : public StorageException195class UNITY_STORAGE_EXPORT UnauthorizedException : public StorageException
130{196{
131public:197public:
198 /**
199 \brief Construct an UnauthorizedException.
200 \param error_message The error message for the exception.
201 */
132 UnauthorizedException(std::string const& error_message);202 UnauthorizedException(std::string const& error_message);
133 ~UnauthorizedException();203 ~UnauthorizedException();
134};204};
@@ -139,69 +209,126 @@
139A provider implementation must throw this exception if it can209A provider implementation must throw this exception if it can
140authenticate with its provider, but the provider denied the operation210authenticate with its provider, but the provider denied the operation
141due to insufficient permission for an item (such as an attempt to211due to insufficient permission for an item (such as an attempt to
142write to a read-only file). Do not throw this exception for failure to212write to a read-only file). Do <i>not</i> throw this exception for failure to
143authenticate with the provider.213authenticate with the provider.
144214
145\see UnauthorizedException215\see UnauthorizedException
146*/216*/
217
147class UNITY_STORAGE_EXPORT PermissionException : public StorageException218class UNITY_STORAGE_EXPORT PermissionException : public StorageException
148{219{
149public:220public:
221 /**
222 \brief Construct a PermissionException.
223 \param error_message The error message for the exception.
224 */
150 PermissionException(std::string const& error_message);225 PermissionException(std::string const& error_message);
151 ~PermissionException();226 ~PermissionException();
152};227};
153228
154/**229/**
155\brief Indicates that an update failed because the provider ran out of space.230\brief Indicates that an update failed because the provider ran out of space or exceeded
231the maximum number of files or folders.
156*/232*/
233
157class UNITY_STORAGE_EXPORT QuotaException : public StorageException234class UNITY_STORAGE_EXPORT QuotaException : public StorageException
158{235{
159public:236public:
237 /**
238 \brief Construct a QuotaException.
239 \param error_message The error message for the exception.
240 */
160 QuotaException(std::string const& error_message);241 QuotaException(std::string const& error_message);
161 ~QuotaException();242 ~QuotaException();
162};243};
163244
164/**245/**
165\brief Indicates that an upload or download was cancelled before it could complete.246\brief Indicates that an upload or download was cancelled before it could complete.
247\note Due to the way the provider API is structured, you will not ever need to throw this exception. It is provided for
248completeness and to allow for provider implementations that do not use the storage framework API.
166*/249*/
250
167class UNITY_STORAGE_EXPORT CancelledException : public StorageException251class UNITY_STORAGE_EXPORT CancelledException : public StorageException
168{252{
169public:253public:
254 /**
255 \brief Construct a CancelledException.
256 \param error_message The error message for the exception.
257 */
170 CancelledException(std::string const& error_message);258 CancelledException(std::string const& error_message);
171 ~CancelledException();259 ~CancelledException();
172};260};
173261
174/**262/**
175\brief Indicates incorrect use of the API, such as calling methods in the wrong order.263\brief Indicates incorrect use of the API.
264
265Use this exception for errors that the client could avoid, such as calling methods in the wrong order or attempting
266to list the contents of file (as opposed to a folder).
267\note Do <i>not</i> throw this exception to indicate arguments that are malformed or out of range.
268\see InvalidArgumentException
176*/269*/
177class UNITY_STORAGE_EXPORT LogicException : public StorageException270class UNITY_STORAGE_EXPORT LogicException : public StorageException
178{271{
179public:272public:
273 /**
274 \brief Construct a LogicException.
275 \param error_message The error message for the exception.
276 */
180 LogicException(std::string const& error_message);277 LogicException(std::string const& error_message);
181 ~LogicException();278 ~LogicException();
182};279};
183280
184/**281/**
185\brief Indicates an invalid parameter, such as a negative value when a positive one was282\brief Indicates an invalid parameter.
283
284Use this exception for errors such as a negative parameter value when a positive one was
186expected, or a string that does not parse correctly or is empty when it should be non-empty.285expected, or a string that does not parse correctly or is empty when it should be non-empty.
286\note Do <i>not</i> throw this exception to indicate incorrect use of the API.
287\see LogicException
187*/288*/
289
188class UNITY_STORAGE_EXPORT InvalidArgumentException : public StorageException290class UNITY_STORAGE_EXPORT InvalidArgumentException : public StorageException
189{291{
190public:292public:
293 /**
294 \brief Construct an InvalidArgumentException.
295 \param error_message The error message for the exception.
296 */
191 InvalidArgumentException(std::string const& error_message);297 InvalidArgumentException(std::string const& error_message);
192 ~InvalidArgumentException();298 ~InvalidArgumentException();
193};299};
194300
195/**301/**
196\brief Indicates a system error, such as failure to create a file or folder,302\brief Indicates a system error.
197or any other (usually non-recoverable) kind of error that should not arise during normal operation.303
304This is a generic "catch-all" exception that you can throw to indicate unexpected errors that do not fit into
305any of the other categories. For example, ResourceException is appropriate to indicate out of
306file descriptors, failure to locate a configuration file, or any other unexpected system error. You should
307provide as much contextual information about such errors as possible. In particular, unexpected errors
308typically need to be diagnosed from log files. This means that you should provide, at least, the full error
309message you received from the cloud service (or the operating system), together with all other relevant
310details, such as the name of the file, the URL of a failed request, any HTTP error information, and so on.
198*/311*/
312
199class UNITY_STORAGE_EXPORT ResourceException : public StorageException313class UNITY_STORAGE_EXPORT ResourceException : public StorageException
200{314{
201public:315public:
316 /**
317 \brief Construct an InvalidArgumentException.
318 \param error_message The error message for the exception.
319 \param error_code The system (or library) error code for the exception.
320 In case of system call failures, <code>error_code</code> should be the value of <code>errno</code>.
321 If the error code represents something else, such as a <code>QFileDevice::FileError</code>,
322 also indicate in the error message what that error code is; otherwise, it can be
323 impossible to diagnose a problem, especially when looking at log files after the fact.
324 */
202 ResourceException(std::string const& error_message, int error_code);325 ResourceException(std::string const& error_message, int error_code);
203 ~ResourceException();326 ~ResourceException();
204327
328 /**
329 \brief Return the error code.
330 \return The value of the <code>error_code</code> parameter that was passed to the constructor.
331 */
205 int error_code() const noexcept;332 int error_code() const noexcept;
206333
207private:334private:
@@ -209,12 +336,25 @@
209};336};
210337
211/**338/**
212\brief Indicates that the server side caught an exception that does not derive from339\brief Indicates that a provider threw an exception not derived from StorageException.
213StorageException, such as a std::exception, or caught some other unknown type (such as `int`).340
341The runtime translates all exception that are thrown by a provider and that do not derive from StorageException
342to UnknownException.
343
344Do not throw this exception explicitly; is is a "catch-all" exception that the runtime uses to
345indicate that something completely unexpected happened. (If a client receives this exception from your
346provider, chances are that you have forgotten to handle an error condition somewhere.)
214*/347*/
348
215class UNITY_STORAGE_EXPORT UnknownException : public StorageException349class UNITY_STORAGE_EXPORT UnknownException : public StorageException
216{350{
217public:351public:
352 /**
353 \brief Construct an UnknownArgumentException.
354 \param error_message The error message for the exception. This is the string returned by <code>what()</code>
355 for <code>std::exception</code> or "Unknown exception" for exceptions that do not derive
356 from <code>std::exception</code>.
357 */
218 UnknownException(std::string const& error_message);358 UnknownException(std::string const& error_message);
219 ~UnknownException();359 ~UnknownException();
220};360};
221361
=== modified file 'include/unity/storage/provider/Item.h'
--- include/unity/storage/provider/Item.h 2017-03-17 02:51:05 +0000
+++ include/unity/storage/provider/Item.h 2017-04-06 07:42:38 +0000
@@ -33,17 +33,63 @@
33namespace provider33namespace provider
34{34{
3535
36/**
37\brief The type of a metadata item (key&ndash;value pair).
38
39For boolean values, use <code>int64_t</code> with a zero or non-zero value to
40indicate <code>false</code> or <code>true</code>.
41
42Well-known metadata keys are defined in \ref common.h.
43*/
36// Note: When growing the set of supported variant types, add new types44// Note: When growing the set of supported variant types, add new types
37// to the *end* of the list, and update the marshaling code in dbusmarshal.cpp.45// to the *end* of the list, and update the marshaling code in dbusmarshal.cpp.
38typedef boost::variant<std::string, int64_t> MetadataValue;46typedef boost::variant<std::string, int64_t> MetadataValue;
3947
48/**
49\brief Details about a storage item.
50*/
51
40struct UNITY_STORAGE_EXPORT Item52struct UNITY_STORAGE_EXPORT Item
41{53{
54 /**
55 \brief The unique identity of the item.
56
57 See <a href="index.html#identity">File and Folder Identity</a> for detailed semantics.
58 */
42 std::string item_id;59 std::string item_id;
60
61 /**
62 \brief The list of parent folder identities.
63
64 \note Depending on the provider, it is possible for a file or folder to have more than one
65 parent folder.
66 */
43 std::vector<std::string> parent_ids;67 std::vector<std::string> parent_ids;
68
69 /**
70 \brief The name of a file or folder.
71
72 \note A provider may create a file or folder with a name other than the name that was requested by the
73 client (such as folding upper case to lower case). This field contains the name of the item as created
74 by the provider, not the name requested by the client.
75 */
44 std::string name;76 std::string name;
77
78 /**
79 \brief The ETag of a file.
80
81 \note For folders, the ETag is may be empty because not all providers support ETags for folders.
82 */
45 std::string etag;83 std::string etag;
84
85 /**
86 \brief The item type (file, folder, or root,).
87 */
46 unity::storage::ItemType type;88 unity::storage::ItemType type;
89
90 /**
91 \brief Additional metadata for a file or folder.
92 */
47 std::map<std::string, MetadataValue> metadata;93 std::map<std::string, MetadataValue> metadata;
48};94};
4995
5096
=== modified file 'include/unity/storage/provider/ProviderBase.h'
--- include/unity/storage/provider/ProviderBase.h 2017-03-16 02:50:03 +0000
+++ include/unity/storage/provider/ProviderBase.h 2017-04-06 07:42:38 +0000
@@ -42,15 +42,41 @@
42class DownloadJob;42class DownloadJob;
43class UploadJob;43class UploadJob;
4444
45/**
46\brief Security related information for an operation invocation.
47
48Each provider method has a trailing parameter of type Context that provides
49access to
50<a href="https://help.ubuntu.com/lts/serverguide/apparmor.html">Apparmor</a> details as
51well as credentials for the cloud provider.
52*/
53
45struct UNITY_STORAGE_EXPORT Context54struct UNITY_STORAGE_EXPORT Context
46{55{
47 uid_t uid;56 uid_t uid; /*!< The user ID of the client process. */
48 pid_t pid;57 pid_t pid; /*!< The process ID of the client process. */
49 std::string security_label;58 std::string security_label; /*!< The Apparmor security label of the client process. */
5059
51 Credentials credentials;60 Credentials credentials; /*!< Credentials to authenticate with the cloud provider. */
52};61};
5362
63/**
64\brief Abstract base class for provider implementations.
65
66The runtime calls methods on this class in response to incoming requests from clients. Each method
67implements a specific operation on the storage backend.
68
69All methods are called from the main thread and must not block. Methods indicate error conditions to the runtime
70by throwing exceptions (which are caught and handled by the runtime). Alternatively, methods can return a future
71that stores an exception to indicate an error.
72
73\note Besides the explicitly listed exceptions for specific error conditions, all methods
74must indicate other errors by throwing an exception derived from StorageException, such
75as PermissionException or ResourceException (see <a href="index.html#provider-error-handling">Error Handling</a>).
76If an exception that does not derive from StorageException is thrown a provider method, the runtime returns a
77generic UnknownException to the client.
78*/
79
54class UNITY_STORAGE_EXPORT ProviderBase : public std::enable_shared_from_this<ProviderBase>80class UNITY_STORAGE_EXPORT ProviderBase : public std::enable_shared_from_this<ProviderBase>
55{81{
56public:82public:
@@ -60,41 +86,201 @@
60 ProviderBase(ProviderBase const& other) = delete;86 ProviderBase(ProviderBase const& other) = delete;
61 ProviderBase& operator=(ProviderBase const& other) = delete;87 ProviderBase& operator=(ProviderBase const& other) = delete;
6288
63 virtual boost::future<ItemList> roots(std::vector<std::string> const& keys, Context const& context) = 0;89 /**
64 virtual boost::future<std::tuple<ItemList,std::string>> list(90 \brief Return the root folder (or folders) of the provider.
65 std::string const& item_id, std::string const& page_token,91 \param keys The keys of metadata items that the client wants to receive.
66 std::vector<std::string> const& keys,92 \param context The security context of the operation.
67 Context const& context) = 0;93 \return A (non-empty) list of roots.
68 virtual boost::future<ItemList> lookup(94 */
69 std::string const& parent_id, std::string const& name, std::vector<std::string> const& keys,95 virtual boost::future<ItemList> roots(std::vector<std::string> const& keys,
70 Context const& context) = 0;96 Context const& context) = 0;
71 virtual boost::future<Item> metadata(std::string const& item_id, std::vector<std::string> const& keys,97
72 Context const& context) = 0;98 /**
7399 \brief Return a (non-recursive) list of the contents of a folder.
74 virtual boost::future<Item> create_folder(100 \param item_id The identity of the folder.
75 std::string const& parent_id, std::string const& name, std::vector<std::string> const& keys,101 \param page_token A token identifying the next page of results (empty for the initial request).
76 Context const& context) = 0;102 \param keys The keys of metadata items that the client wants to receive.
77103 \param context The security context of the operation.
78 virtual boost::future<std::unique_ptr<UploadJob>> create_file(104 \return A tuple containing a number of child items, plus a new page token. The page token
79 std::string const& parent_id, std::string const& name,105 allows the method to return the results in multiple "pages". For the initial request, the runtime passes
80 int64_t size, std::string const& content_type, bool allow_overwrite, std::vector<std::string> const& keys,106 an empty <code>page_token</code>. The method returns new page token to indicate whether there are more
81 Context const& context) = 0;107 results to be retrieved. If the returned token is non-empty, the runtime calls the method again,
82 virtual boost::future<std::unique_ptr<UploadJob>> update(108 passing the token that was returned by the previous call. To indicate that all results were retrieved, the method
83 std::string const& item_id, int64_t size, std::string const& old_etag, std::vector<std::string> const& keys,109 must return an empty page token to the runtime.
84 Context const& context) = 0;110 \throws InvalidArgumentException <code>item_id</code> or <code>page_token</code> are invalid.
85111 \throws NotExistsException <code>item_id</code> does not exist.
86 virtual boost::future<std::unique_ptr<DownloadJob>> download(112 \throws LogicException If at all possible, the implementation should throw a LogicException
87 std::string const& item_id, std::string const& match_etag,113 if <code>item_id</code> denotes a file. If a provider cannot distinguish between
88 Context const& context) = 0;114 attempts to list a file and other errors, it <i>must</i> return an empty list instead.
89115 */
90 virtual boost::future<void> delete_item(116 virtual boost::future<std::tuple<ItemList,std::string>> list(std::string const& item_id,
91 std::string const& item_id, Context const& context) = 0;117 std::string const& page_token,
92 virtual boost::future<Item> move(118 std::vector<std::string> const& keys,
93 std::string const& item_id, std::string const& new_parent_id,119 Context const& context) = 0;
94 std::string const& new_name, std::vector<std::string> const& keys, Context const& context) = 0;120
95 virtual boost::future<Item> copy(121 /**
96 std::string const& item_id, std::string const& new_parent_id,122 \brief Retrieve a file or folder within a parent folder by name.
97 std::string const& new_name, std::vector<std::string> const& keys, Context const& context) = 0;123 \param parent_id The identity of the parent folder.
124 \param name The name of the file or folder.
125 \param keys The keys of metadata items that the client wants to receive.
126 \param context The security context of the operation.
127 \return A non-empty list of items with the given name. The list may contain more than one entry
128 if the provider allows non-unique names for items within a folder.
129 The list may also contain more than one entry with the same identity if the provider allows
130 the same item to have more than one name.
131 \throws InvalidArgumentException <code>parent_id</code> or <code>name</code> are invalid.
132 \throws NotExistsException <code>parent_id</code> does not exist or does not contain a file or folder with
133 the given <code>name</code>. (Do <i>not</i> return an empty list in this case.)
134 */
135 virtual boost::future<ItemList> lookup(std::string const& parent_id,
136 std::string const& name,
137 std::vector<std::string> const& keys,
138 Context const& context) = 0;
139
140 /**
141 \brief Retrieve a file or folder by its identity.
142 \param item_id The identity of the item.
143 \param keys The keys of metadata items that the client wants to receive.
144 \param context The security context of the operation.
145 \return The item.
146 \throws InvalidArgumentException <code>item_id</code> is invalid.
147 \throws NotExistsException No file or folder with the given identity exists.
148 */
149 virtual boost::future<Item> metadata(std::string const& item_id,
150 std::vector<std::string> const& keys,
151 Context const& context) = 0;
152
153 /**
154 \brief Create a new folder.
155 \param parent_id The identity of the parent folder.
156 \param name The name of the new folder.
157 \param keys The keys of metadata items that the client wants to receive.
158 \param context The security context of the operation.
159 \return The item representing the folder.
160 \throws InvalidArgumentException <code>parent_id</code> or <code>name</code> are invalid.
161 \throws ExistsException An item with the given <code>name</code> exists already.
162 */
163 virtual boost::future<Item> create_folder(std::string const& parent_id,
164 std::string const& name,
165 std::vector<std::string> const& keys,
166 Context const& context) = 0;
167
168 /**
169 \brief Create a new file.
170 \param parent_id The identity of the parent folder.
171 \param name The name of the new file.
172 \param size The size of the file contents in bytes.
173 \param content_type The mime type of the file. If empty, the provider may (or may not) determine the mime type automatically.
174 \param allow_overwrite If true, the file will be created even if a file with the same name exists already.
175 \param keys The keys of metadata items that the client wants to receive.
176 \param context The security context of the operation.
177 \return An UploadJob that will read any data provided by the client and write it to the new file.
178 \throws InvalidArgumentException <code>parent_id</code> or <code>name</code> are invalid.
179 \throws ExistsException A file with the given <code>name</code> exists already and
180 <code>allow_overwrite</code> is <code>false</code>, or a folder with the given <code>name</code> exists already.
181 */
182 // TODO: The runtime should check that size is non-negative, so the provider implementation can rely on this.
183 virtual boost::future<std::unique_ptr<UploadJob>> create_file(std::string const& parent_id,
184 std::string const& name,
185 int64_t size,
186 std::string const& content_type,
187 bool allow_overwrite,
188 std::vector<std::string> const& keys,
189 Context const& context) = 0;
190
191 /**
192 \brief Update the contents of a file.
193 \param item_id The identity of the file.
194 \param size The size of the file contents in bytes.
195 \param old_etag The ETag of the existing file (empty if the file should be overwritten).
196 \param keys The keys of metadata items that the client wants to receive.
197 \param context The security context of the operation.
198 \return An UploadJob that will read any data provided by the client and write it to the file.
199 \throws InvalidArgumentException <code>item_id</code> or <code>size</code> are invalid.
200 \throws ConflictException The file's ETag does not match the given (non-empty) <code>old_etag</code>.
201 \throws LogicException The <code>item_id</code> denotes a folder.
202 */
203 // TODO: The runtime should check that size is non-negative, so the provider implementation can rely on this.
204 virtual boost::future<std::unique_ptr<UploadJob>> update(std::string const& item_id,
205 int64_t size,
206 std::string const& old_etag,
207 std::vector<std::string> const& keys,
208 Context const& context) = 0;
209
210 /**
211 \brief Download the contents of a file.
212 \param item_id The identity of the file.
213 \param match_etag The ETag of the existing file (empty if the file should be downloaded unconditionally).
214 \param context The security context of the operation.
215 \return A DownloadJob that will write the file data to the client socket.
216 \throws InvalidArgumentException <code>item_id</code> or <code>name</code> are invalid.
217 \throws NotExistsException <code>item_id</code> does not exist.
218 \throws LogicException The <code>item_id</code> denotes a folder.
219 \throws ConflictException The ETag for <code>item_id</code> does not match the given
220 (non-empty) <code>match_etag</code>.
221 */
222 virtual boost::future<std::unique_ptr<DownloadJob>> download(std::string const& item_id,
223 std::string const& match_etag,
224 Context const& context) = 0;
225
226 /**
227 \brief Delete an item.
228
229 Deletion is recursive. If <code>item_id</code> denotes a folder, the folder and all its contents are deleted.
230 \param item_id The identity of the file or folder.
231 \param context The security context of the operation.
232 \throws InvalidArgumentException <code>item_id</code> is invalid.
233 \throws PermissionException <code>item_id</code> denotes a root (or permission was denied for another reason).
234 */
235 virtual boost::future<void> delete_item(std::string const& item_id,
236 Context const& context) = 0;
237
238 /**
239 \brief Move and/or rename an item.
240
241 \param item_id The identity of the file or folder to be moved.
242 \param new_parent_id The identity of the folder to move the file or folder to. If <code>new_parent_id</code>
243 is the same as the existing parent of <code>item_id</code>, the file or folder is to be renamed within its
244 parent folder.
245 \param new_name The new name of the file (which can be the same as the old name if <code>new_parent_id</code>
246 differs).
247 \param keys The keys of metadata items that the client wants to receive.
248 \param context The security context of the operation.
249 \return The moved or renamed item.
250 \throws InvalidArgumentException <code>item_id</code>, <code>new_parent_id</code>, or <code>new_name</code>
251 are invalid.
252 \throws ExistsException The folder <code>new_parent_id</code> already contains an item with
253 name <code>new_name</code>.
254 \throws PermissionException <code>item_id</code> denotes a root (or permission was denied for another reason).
255 */
256 virtual boost::future<Item> move(std::string const& item_id,
257 std::string const& new_parent_id,
258 std::string const& new_name,
259 std::vector<std::string> const& keys,
260 Context const& context) = 0;
261
262 /**
263 \brief Copy an item.
264
265 Copy is recursive, so copying a folder copies the folder's contents.
266 \param item_id The identity of the file or folder to be copied.
267 \param new_parent_id The identity of the folder to copy the file or folder to. If <code>new_parent_id</code>
268 is the same as the existing parent of <code>item_id</code>, the file or folder is to be copied within its
269 parent folder.
270 \param new_name The new name of the file or folder.
271 \param keys The keys of metadata items that the client wants to receive.
272 \param context The security context of the operation.
273 \return The copied item.
274 \throws InvalidArgumentException <code>item_id</code>, <code>new_parent_id</code>, or <code>new_name</code>
275 are invalid.
276 \throws ExistsException The folder <code>new_parent_id</code> already contains an item with
277 name <code>new_name</code>.
278 */
279 virtual boost::future<Item> copy(std::string const& item_id,
280 std::string const& new_parent_id,
281 std::string const& new_name,
282 std::vector<std::string> const& keys,
283 Context const& context) = 0;
98};284};
99285
100}286}
101287
=== modified file 'include/unity/storage/provider/Server.h'
--- include/unity/storage/provider/Server.h 2017-03-16 03:30:18 +0000
+++ include/unity/storage/provider/Server.h 2017-04-06 07:42:38 +0000
@@ -37,29 +37,141 @@
3737
38class ProviderBase;38class ProviderBase;
3939
40/**
41\brief Base class to register a storage provider with the runtime.
42
43\anchor extra-args This class allows you to register a provider that requires additional
44parameters with the storage framework runtime, by overriding the
45make_provider() method. (If your provider does not require constructor
46arguments, use the predefined Server template instead.)
47
48For example, here is how you can instantiate a provider that requires
49a <code>some_value</code> constructor argument:
50
51\code{.cpp}
52class MyCloudProvider : public ProviderBase
53{
54public:
55 MyCloudProvider(int some_value,
56 string const& bus_name,
57 string const& service_id)
58 : ProviderBase(bus_name, service_id)
59 {
60 }
61 // ...
62};
63
64class MyCloudServer : public ServerBase
65{
66public:
67 MyCloudServer(int some_value,
68 string const& bus_name,
69 string const& account_service_id)
70 : some_value_(some_value)
71 , bus_name_(bus_name)
72 , account_service_id_(account_service_id)
73 {
74 }
75
76protected:
77 shared_ptr<ProviderBase> make_provider() override
78 {
79 return make_shared<MyCloudProvider>(some_value, bus_name, account_service_id);
80 }
81
82private:
83 int some_value_;
84 string bus_name_;
85 string account_service_id_;
86};
87
88int main(int argc, char* argv[])
89{
90 string const bus_name = "com.acme.StorageFramework.Provider.MyCloud";
91 string const account_service_id = "storage-provider-mycloud";
92
93 try
94 {
95 MyCloudServer server(99, bus_name, account_service_id);
96 server.init(argc, argv);
97 return server.run();
98 }
99 catch (std::exception const& e)
100 {
101 cerr << argv[0] << ": " << e.what() << endl;
102 return 1;
103 }
104}
105\endcode
106*/
107
40class UNITY_STORAGE_EXPORT ServerBase108class UNITY_STORAGE_EXPORT ServerBase
41{109{
42public:110public:
111 /**
112 \brief Constructs a server instance.
113 \param bus_name The DBus name of the provider service on the session bus.
114 \param account_service_id The service ID with which the provider is known to
115 <a href="https://help.ubuntu.com/stable/ubuntu-help/accounts.html">Online Accounts</a>.
116 */
43 ServerBase(std::string const& bus_name, std::string const& account_service_id);117 ServerBase(std::string const& bus_name, std::string const& account_service_id);
44 virtual ~ServerBase();118 virtual ~ServerBase();
45119
120 /**
121 \brief Initializes the storage framework runtime.
122 \param argc, argv The runtime passes these parameters through to
123 <a href="http://doc.qt.io/qt-5/qcoreapplication.html">QCoreApplication</a>.
124 \throws StorageException
125 */
46 void init(int& argc, char** argv);126 void init(int& argc, char** argv);
127
128 /**
129 \brief Starts an event loop for the service.
130
131 You <i>must</i> call init() before calling this method.
132 \return In case of an error, run() returns non-zero status.
133 */
47 int run();134 int run();
48135
49protected:136protected:
137 /**
138 \brief Factory method to instantiate a provider.
139 The runtime calls this method to instantiate a provider as part
140 of the init() method. You can override this method if you need
141 to pass additional arguments to the constructor of your provider class.
142 (see \ref extra-args "example code").
143 \return A <code>shared_ptr</code> to the provider instance.
144 */
50 virtual std::shared_ptr<ProviderBase> make_provider() = 0;145 virtual std::shared_ptr<ProviderBase> make_provider() = 0;
146
51private:147private:
52 std::unique_ptr<internal::ServerImpl> p_;148 std::unique_ptr<internal::ServerImpl> p_;
53149
54 friend class internal::ServerImpl;150 friend class internal::ServerImpl;
55};151};
56152
153/**
154\brief Default ServerBase implementation for providers with a default constructor.
155
156You can use this class to connect your provider class to the runtime, provided
157the class has a default constructor. If you need to pass additional arguments
158to the constructor, you must derive a class from ServerBase and override the
159ServerBase::make_provider() factory method (see \ref extra-args "example code").
160*/
161
57template <typename T>162template <typename T>
58class Server : public ServerBase163class Server : public ServerBase
59{164{
60public:165public:
61 using ServerBase::ServerBase;166 using ServerBase::ServerBase;
167
62protected:168protected:
169 /**
170 \brief Factory method to instantiate a provider with a default constructor.
171
172 The runtime calls this method to create your provider instance of type T.
173 \return A <code>shared_ptr</code> to the provider instance.
174 */
63 std::shared_ptr<ProviderBase> make_provider() override {175 std::shared_ptr<ProviderBase> make_provider() override {
64 return std::make_shared<T>();176 return std::make_shared<T>();
65 }177 }
66178
=== modified file 'include/unity/storage/provider/TempfileUploadJob.h'
--- include/unity/storage/provider/TempfileUploadJob.h 2016-08-24 10:52:17 +0000
+++ include/unity/storage/provider/TempfileUploadJob.h 2017-04-06 07:42:38 +0000
@@ -36,21 +36,59 @@
36class TempfileUploadJobImpl;36class TempfileUploadJobImpl;
37}37}
3838
39/**
40\brief Helper class to store the contents of an upload in a temporary file.
41
42For uploads, a provider implementation must decide whether to upload data to the cloud provider
43immediately (as soon as the data arrives from the client) or whether to buffer the data locally first
44and upload it to the cloud provider once the client successfully finalizes the upload. Uploading
45immediately has the down side that it is more likely to fail. This is an issue particularly
46for mobile devices, where applications may be suspended for extended periods, and where connectivity
47is often lost without warning. Uploads of large files (such as media files) are unlikely
48to ever complete in this case: if the user swipes away from the client application, no more data arrives
49at the provider until the application resumes, by which time the connection to the cloud provider has
50most likely timed out.
51
52Especially for files that are more than a few kilobytes in size, it is usually necessary to write the data
53to a local file first and to write it to the cloud provider only once all of data has been sent by the client.
54(The provider service does not get suspended and will not exit while an upload is in progress.)
55
56TempfileUploadJob is a helper class that implements an uploader that writes the data to a temporary file.
57The file is unlinked once the TempfileUploadJob is destroyed. You implementation must provide finish() and
58cancel().
59
60*/
61
39class UNITY_STORAGE_EXPORT TempfileUploadJob : public UploadJob62class UNITY_STORAGE_EXPORT TempfileUploadJob : public UploadJob
40{63{
41public:64public:
65 /**
66 \brief Construct a TempfileUploadJob.
67 \param upload_id An identifier for this particular upload. You can use any non-empty string,
68 as long as it is unique among all uploads that are in progress within this provider.
69 The runtime uses the <code>upload_id</code> to distinguish different uploads that may be
70 in progress concurrently.
71 */
42 TempfileUploadJob(std::string const& upload_id);72 TempfileUploadJob(std::string const& upload_id);
43 virtual ~TempfileUploadJob();73 virtual ~TempfileUploadJob();
4474
75 /**
76 \brief Returns the name of the file.
77 \return The full path name of the temporary file.
78 */
45 std::string file_name() const;79 std::string file_name() const;
4680
47 // This function should be called from your finish()81 /**
48 // implementation to read the remaining data from the socket. If82 \brief Reads any unread data from the upload socket and writes it to the temporary file.
49 // the client has not closed the socket as expected, LogicError83
50 // will be thrown.84 You must call drain() from your UploadJob::finish() method. Its implementation reads any remaining
85 data from the upload socket and writes it to the temporary file. If an error occurs,
86 drain() throws an exception.
87 \throws LogicException The client did not close its end of the socket.
88 */
51 void drain();89 void drain();
5290
53protected:91private:
54 TempfileUploadJob(internal::TempfileUploadJobImpl *p) UNITY_STORAGE_HIDDEN;92 TempfileUploadJob(internal::TempfileUploadJobImpl *p) UNITY_STORAGE_HIDDEN;
55};93};
5694
5795
=== modified file 'include/unity/storage/provider/UploadJob.h'
--- include/unity/storage/provider/UploadJob.h 2016-08-24 10:52:17 +0000
+++ include/unity/storage/provider/UploadJob.h 2017-04-06 07:42:38 +0000
@@ -39,28 +39,126 @@
39class UploadJobImpl;39class UploadJobImpl;
40}40}
4141
42class TempfileUploadJob;
43
44/**
45\brief Abstract base class for upload implementations.
46
47When the runtime calls ProviderBase::update() or ProviderBase::create_file(), you must return
48a new uploader instance that derives from UploadJob. Your implementation is responsible
49for retrieving the file's data from the upload socket and writing it to
50the corresponding file in the cloud provider.
51
52You can implement your uploader
53any way you wish, such as by running the upload in a separate thread, or
54by using async I/O driven by the runtime's (or any other) event loop.
55
56The runtime invokes all methods on the uploader from the main thread.
57*/
58
42class UNITY_STORAGE_EXPORT UploadJob59class UNITY_STORAGE_EXPORT UploadJob
43{60{
44public:61public:
62 /**
63 \brief Construct an uploader.
64 \param upload_id An identifier for this particular upload. You can use any non-empty string,
65 as long as it is unique among all uploads that are in progress within the corresponding account.
66 (A simple incrementing counter will work fine, or you can use the upload identifier you receive
67 from the cloud service.)
68 The runtime uses the <code>upload_id</code> to distinguish different uploads that may be
69 in progress concurrently, and it ensures that each ID can be used only by its corresponding client.
70 */
45 UploadJob(std::string const& upload_id);71 UploadJob(std::string const& upload_id);
46 virtual ~UploadJob();72 virtual ~UploadJob();
4773
74 /**
75 \brief Returns the upload ID.
76 \return The value of the <code>upload_id</code> parameter that was passed to the constructor.
77 */
48 std::string const& upload_id() const;78 std::string const& upload_id() const;
79
80 /**
81 \brief Returns the socket to read the file contents from.
82 \return A socket that is open for reading. You must read the file contents from this socket (which is
83 connected to the client by the runtime).
84 */
49 int read_socket() const;85 int read_socket() const;
5086
51 // If an error is reported early, cancel() or finish() will not be87 /**
52 // invoked.88 \brief Informs the runtime that an upload encountered an error.
53 void report_error(std::exception_ptr p);89
5490 This method makes it unnecessary to wait for the runtime to call finish() in order to confirm
91 whether an upload completed successfully. You can call report_error() as soon as you encounter an error
92 during an upload (such as losing the connection to the cloud provider, or getting an error when reading
93 from the upload socket). This can be convenient if an upload runs in a separate thread or event loop
94 because there is no need to store success/error state for use in finish().
95
96 You can call report_error() from an arbitrary thread.
97
98 If you call report_error(), the runtime guarantees that neither finish() nor cancel() will be called, so
99 you must reclaim any resources associated with the upload before calling report_error().
100
101 \param storage_exception You <i>must</i> pass a StorageException to indicate the reason for the failure.
102 \see finish()
103 */
104 void report_error(std::exception_ptr storage_exception);
105
106 /**
107 \brief Cancel this upload.
108
109 The runtime calls this method when a client explicitly cancels an upload or crashes.
110 Your implementation should reclaim all resources that are used by the upload. In particular,
111 you should stop reading any more data and close the upload socket. In addition,
112 you should reclaim any resources (such as open connections) that are associated
113 with the upload to the cloud provider (possibly after informing the cloud provider of
114 the cancellation).
115
116 The runtime guarantees that cancel() will be called only once, and that finish() will not be called
117 if cancel() was called.
118
119 If any errors are encountered, you <i>must</i> report them by returning a future that stores
120 a StorageException. Do <i>not</i> call report_error() from inside cancel().
121
122 \return A future that becomes ready (or contains a StorageException) once cancellation is complete.
123 */
55 virtual boost::future<void> cancel() = 0;124 virtual boost::future<void> cancel() = 0;
125
126 /**
127 \brief Finalize this upload.
128
129 The runtime calls this method when a client finishes an upload. Your implementation <i>must</i> verify
130 that it has successfully read <i>and</i> written the <i>exact</i> number of bytes that were passed
131 to ProviderBase::update() or ProviderBase::create_file() before making the future ready.
132 If too few or too many bytes were sent by the client, it <i>must</i> store a LogicException in the future.
133 It must also verify that the cloud provider has received <i>all</i> of the file's data; otherwise,
134 the file may end up being partially written in the cloud provider.
135
136 Note that finish() may be called while there is still buffered data that remains to be read from the
137 upload socket, so you must take care to drain the socket of any remaining data before checking that the
138 actual number bytes uploaded by the client matches the expected number of bytes.
139
140 You should close the upload socket and reclaim any resources (such as open
141 connections) that are associated with the upload from the cloud provider.
142
143 The runtime guarantees that finish() will be called only once, and that cancel() will not be called
144 if finish() was called.
145
146 If any errors are encountered, you <i>must</i> report them by returning a future that stores
147 a StorageException. Do <i>not</i> call report_error() from inside finish().
148
149 \return A future that becomes ready and contains the metadata for the file (or contains a StorageException)
150 once the upload is complete.
151 \see report_error(), TempfileUploadJob
152 */
56 virtual boost::future<Item> finish() = 0;153 virtual boost::future<Item> finish() = 0;
57154
58protected:155private:
59 UploadJob(internal::UploadJobImpl *p) UNITY_STORAGE_HIDDEN;156 UploadJob(internal::UploadJobImpl *p) UNITY_STORAGE_HIDDEN;
60 internal::UploadJobImpl *p_ = nullptr;157 internal::UploadJobImpl *p_ = nullptr;
61158
62 friend class internal::PendingJobs;159 friend class internal::PendingJobs;
63 friend class internal::ProviderInterface;160 friend class internal::ProviderInterface;
161 friend class TempfileUploadJob;
64};162};
65163
66}164}
67165
=== modified file 'include/unity/storage/provider/testing/TestServer.h'
--- include/unity/storage/provider/testing/TestServer.h 2016-09-28 07:04:41 +0000
+++ include/unity/storage/provider/testing/TestServer.h 2017-04-06 07:42:38 +0000
@@ -23,11 +23,13 @@
23#include <memory>23#include <memory>
24#include <string>24#include <string>
2525
26//@cond
26namespace OnlineAccounts27namespace OnlineAccounts
27{28{
28class Account;29class Account;
29}30}
30class QDBusConnection;31class QDBusConnection;
32//@endcond
3133
32namespace unity34namespace unity
33{35{
@@ -46,16 +48,44 @@
46namespace testing48namespace testing
47{49{
4850
51/**
52\brief Helper class to enable testing of provider implementations.
53
54
55TestServer is a simple helper class that allows you to test a provider implementation
56on a separate DBus connection. The class requires access to
57an <a href="https://help.ubuntu.com/stable/ubuntu-help/accounts.html">Online Accounts</a>
58service. If you do not want to test with the live Online Accounts service, you can pass
59<code>nullptr</code>, in which case your provider receives blank
60\link unity::storage::provider::Credentials Credentials\endlink.
61*/
62
49class UNITY_STORAGE_EXPORT TestServer63class UNITY_STORAGE_EXPORT TestServer
50{64{
51public:65public:
66 /**
67 \brief Constructs a TestServer instance.
68 \param provider The provider implementation to be tested.
69 \param account The account for the provider (or <code>nullptr</code>).
70 \param connection The DBus connection to connect the provider to.
71 \param object_path The DBus object path for the provider interface.
72 */
52 TestServer(std::shared_ptr<ProviderBase> const& provider,73 TestServer(std::shared_ptr<ProviderBase> const& provider,
53 OnlineAccounts::Account* account,74 OnlineAccounts::Account* account,
54 QDBusConnection const& connection,75 QDBusConnection const& connection,
55 std::string const& object_path);76 std::string const& object_path);
56 ~TestServer();77 ~TestServer();
5778
79 /**
80 \brief Returns the DBus connection.
81 \return The value of the <code>connection</code> parameter that was passed to the constructor.
82 */
58 QDBusConnection const& connection() const;83 QDBusConnection const& connection() const;
84
85 /**
86 \brief Returns the object path.
87 \return The value of the <code>object_path</code> parameter that was passed to the constructor.
88 */
59 std::string const& object_path() const;89 std::string const& object_path() const;
6090
61private:91private:
6292
=== modified file 'include/unity/storage/qt/internal/StorageErrorImpl.h'
--- include/unity/storage/qt/internal/StorageErrorImpl.h 2016-10-13 06:48:11 +0000
+++ include/unity/storage/qt/internal/StorageErrorImpl.h 2017-04-06 07:42:38 +0000
@@ -63,6 +63,7 @@
63 static StorageError not_exists_error(QString const& msg, QString const& key);63 static StorageError not_exists_error(QString const& msg, QString const& key);
64 static StorageError exists_error(QString const& msg, QString const& item_id, QString const& item_name);64 static StorageError exists_error(QString const& msg, QString const& item_id, QString const& item_name);
65 static StorageError cancelled_error(QString const& msg);65 static StorageError cancelled_error(QString const& msg);
66 static StorageError permission_error(QString const& msg);
66 static StorageError logic_error(QString const& msg);67 static StorageError logic_error(QString const& msg);
67 static StorageError invalid_argument_error(QString const& msg);68 static StorageError invalid_argument_error(QString const& msg);
68 static StorageError resource_error(QString const& msg, int error_code);69 static StorageError resource_error(QString const& msg, int error_code);
6970
=== modified file 'src/local-provider/LocalDownloadJob.cpp'
--- src/local-provider/LocalDownloadJob.cpp 2017-03-14 07:11:40 +0000
+++ src/local-provider/LocalDownloadJob.cpp 2017-04-06 07:42:38 +0000
@@ -46,7 +46,7 @@
46 auto st = status(item_id_);46 auto st = status(item_id_);
47 if (!is_regular_file(st))47 if (!is_regular_file(st))
48 {48 {
49 throw InvalidArgumentException(method + ": \"" + item_id_ + "\" is not a file");49 throw LogicException(method + ": \"" + item_id_ + "\" is not a file");
50 }50 }
51 }51 }
52 // LCOV_EXCL_START // Too small a window to hit with a test.52 // LCOV_EXCL_START // Too small a window to hit with a test.
@@ -60,7 +60,7 @@
60 int64_t mtime = get_mtime_nsecs(method, item_id_);60 int64_t mtime = get_mtime_nsecs(method, item_id_);
61 if (to_string(mtime) != match_etag)61 if (to_string(mtime) != match_etag)
62 {62 {
63 throw ConflictException(method + ": etag mismatch");63 throw ConflictException(method + ": ETag mismatch");
64 }64 }
65 }65 }
6666
6767
=== modified file 'src/local-provider/LocalProvider.cpp'
--- src/local-provider/LocalProvider.cpp 2017-03-23 06:55:00 +0000
+++ src/local-provider/LocalProvider.cpp 2017-04-06 07:42:38 +0000
@@ -195,6 +195,11 @@
195 using namespace boost::filesystem;195 using namespace boost::filesystem;
196196
197 This->throw_if_not_valid(method, item_id);197 This->throw_if_not_valid(method, item_id);
198 if (!is_directory(item_id))
199 {
200 string msg = method + ": \"" + item_id + "\" is not a folder";
201 throw boost::enable_current_exception(LogicException(msg));
202 }
198 vector<Item> items;203 vector<Item> items;
199 for (directory_iterator it(item_id); it != directory_iterator(); ++it)204 for (directory_iterator it(item_id); it != directory_iterator(); ++it)
200 {205 {
@@ -345,7 +350,7 @@
345 if (canonical(item_id).native() == This->root_)350 if (canonical(item_id).native() == This->root_)
346 {351 {
347 string msg = method + ": cannot delete root";352 string msg = method + ": cannot delete root";
348 throw boost::enable_current_exception(LogicException(msg));353 throw boost::enable_current_exception(PermissionException(msg));
349 }354 }
350 remove_all(item_id);355 remove_all(item_id);
351 };356 };
@@ -378,6 +383,11 @@
378 string msg = method + ": \"" + target_path.native() + "\" exists already";383 string msg = method + ": \"" + target_path.native() + "\" exists already";
379 throw boost::enable_current_exception(ExistsException(msg, target_path.native(), new_name));384 throw boost::enable_current_exception(ExistsException(msg, target_path.native(), new_name));
380 }385 }
386 if (canonical(item_id).native() == This->root_)
387 {
388 string msg = method + ": cannot move root";
389 throw boost::enable_current_exception(PermissionException(msg));
390 }
381391
382 // Small race condition here: if exists() just said that the target does not exist, it is392 // Small race condition here: if exists() just said that the target does not exist, it is
383 // possible for it to have been created since. If so, if the target is a file or an empty393 // possible for it to have been created since. If so, if the target is a file or an empty
384394
=== modified file 'src/local-provider/LocalUploadJob.cpp'
--- src/local-provider/LocalUploadJob.cpp 2017-03-16 02:50:03 +0000
+++ src/local-provider/LocalUploadJob.cpp 2017-04-06 07:42:38 +0000
@@ -83,7 +83,7 @@
83 auto st = status(item_id);83 auto st = status(item_id);
84 if (!is_regular_file(st))84 if (!is_regular_file(st))
85 {85 {
86 throw InvalidArgumentException(method_ + ": \"" + item_id + "\" is not a file");86 throw LogicException(method_ + ": \"" + item_id + "\" is not a file");
87 }87 }
88 }88 }
89 // LCOV_EXCL_START89 // LCOV_EXCL_START
@@ -99,7 +99,7 @@
99 int64_t mtime = get_mtime_nsecs(method_, item_id);99 int64_t mtime = get_mtime_nsecs(method_, item_id);
100 if (to_string(mtime) != old_etag)100 if (to_string(mtime) != old_etag)
101 {101 {
102 throw ConflictException(method_ + ": etag mismatch");102 throw ConflictException(method_ + ": ETag mismatch");
103 }103 }
104 }104 }
105 old_etag_ = old_etag;105 old_etag_ = old_etag;
@@ -184,14 +184,13 @@
184184
185 try185 try
186 {186 {
187 // We check again for an etag mismatch or overwrite, in case the file was updated after the upload started.187 // We check again for an ETag mismatch or overwrite, in case the file was updated after the upload started.
188 if (!parent_id_.empty())188 if (!parent_id_.empty())
189 {189 {
190 // create_file()190 // create_file()
191 if (!allow_overwrite_ && boost::filesystem::exists(item_id_))191 if (!allow_overwrite_ && boost::filesystem::exists(item_id_))
192 {192 {
193 string msg = method_ + ": \"" + item_id_ + "\" exists already";193 string msg = method_ + ": \"" + item_id_ + "\" exists already";
194 boost::filesystem::path(item_id_).filename().native();
195 BOOST_THROW_EXCEPTION(194 BOOST_THROW_EXCEPTION(
196 ExistsException(msg, item_id_, boost::filesystem::path(item_id_).filename().native()));195 ExistsException(msg, item_id_, boost::filesystem::path(item_id_).filename().native()));
197 }196 }
@@ -199,10 +198,22 @@
199 else if (!old_etag_.empty())198 else if (!old_etag_.empty())
200 {199 {
201 // update()200 // update()
201 using namespace boost::filesystem;
202 if (exists(item_id_) && !is_regular_file(item_id_))
203 {
204 // Yes, ExistsException, not LogicException. A folder is in the way
205 // and was created after the update started, meaning that the client correctly
206 // started the operation with an existing file, but then the file was removed
207 // and replaced with a folder with the same name.
208 string msg = method_ + ": \"" + item_id_ + "\" exists already";
209 BOOST_THROW_EXCEPTION(
210 ExistsException(msg, item_id_, boost::filesystem::path(item_id_).filename().native()));
211 }
202 int64_t mtime = get_mtime_nsecs(method_, item_id_);212 int64_t mtime = get_mtime_nsecs(method_, item_id_);
203 if (to_string(mtime) != old_etag_)213 if (to_string(mtime) != old_etag_)
204 {214 {
205 BOOST_THROW_EXCEPTION(ConflictException(method_ + ": etag mismatch"));215 // File was touched after the upload started.
216 BOOST_THROW_EXCEPTION(ConflictException(method_ + ": ETag mismatch"));
206 }217 }
207 }218 }
208219
209220
=== modified file 'src/qt/internal/ItemImpl.cpp'
--- src/qt/internal/ItemImpl.cpp 2016-11-25 03:23:55 +0000
+++ src/qt/internal/ItemImpl.cpp 2017-04-06 07:42:38 +0000
@@ -235,7 +235,7 @@
235 }235 }
236 if (md_.type == storage::ItemType::root)236 if (md_.type == storage::ItemType::root)
237 {237 {
238 auto e = StorageErrorImpl::logic_error(method + ": cannot delete root");238 auto e = StorageErrorImpl::permission_error(method + ": cannot delete root");
239 return VoidJobImpl::make_job(e);239 return VoidJobImpl::make_job(e);
240 }240 }
241241
242242
=== modified file 'src/qt/internal/StorageErrorImpl.cpp'
--- src/qt/internal/StorageErrorImpl.cpp 2017-03-14 07:11:40 +0000
+++ src/qt/internal/StorageErrorImpl.cpp 2017-04-06 07:42:38 +0000
@@ -188,6 +188,12 @@
188 return StorageError(move(p));188 return StorageError(move(p));
189}189}
190190
191StorageError StorageErrorImpl::permission_error(QString const& msg)
192{
193 unique_ptr<StorageErrorImpl> p(new StorageErrorImpl(StorageError::Type::PermissionDenied, msg));
194 return StorageError(move(p));
195}
196
191StorageError StorageErrorImpl::logic_error(QString const& msg)197StorageError StorageErrorImpl::logic_error(QString const& msg)
192{198{
193 unique_ptr<StorageErrorImpl> p(new StorageErrorImpl(StorageError::Type::LogicError, msg));199 unique_ptr<StorageErrorImpl> p(new StorageErrorImpl(StorageError::Type::LogicError, msg));
194200
=== modified file 'src/qt/internal/unmarshal_error.cpp'
--- src/qt/internal/unmarshal_error.cpp 2017-01-24 06:12:09 +0000
+++ src/qt/internal/unmarshal_error.cpp 2017-04-06 07:42:38 +0000
@@ -84,11 +84,11 @@
84 { "ConflictException", make_error<StorageError::Type::Conflict> },84 { "ConflictException", make_error<StorageError::Type::Conflict> },
85 { "UnauthorizedException", make_error<StorageError::Type::Unauthorized> },85 { "UnauthorizedException", make_error<StorageError::Type::Unauthorized> },
86 { "PermissionException", make_error<StorageError::Type::PermissionDenied> },86 { "PermissionException", make_error<StorageError::Type::PermissionDenied> },
87 { "QuotaException", make_error<StorageError::Type::QuotaExceeded> },
87 { "CancelledException", make_error<StorageError::Type::Cancelled> },88 { "CancelledException", make_error<StorageError::Type::Cancelled> },
88 { "LogicException", make_error<StorageError::Type::LogicError> },89 { "LogicException", make_error<StorageError::Type::LogicError> },
89 { "InvalidArgumentException", make_error<StorageError::Type::InvalidArgument> },90 { "InvalidArgumentException", make_error<StorageError::Type::InvalidArgument> },
90 { "ResourceException", make_error<StorageError::Type::ResourceError> },91 { "ResourceException", make_error<StorageError::Type::ResourceError> },
91 { "QuotaException", make_error<StorageError::Type::QuotaExceeded> },
92 { "UnknownException", make_error<StorageError::Type::LocalCommsError> } // Yes, LocalCommsError is intentional92 { "UnknownException", make_error<StorageError::Type::LocalCommsError> } // Yes, LocalCommsError is intentional
93};93};
9494
9595
=== modified file 'tests/local-provider/local-provider_test.cpp'
--- tests/local-provider/local-provider_test.cpp 2017-03-23 06:55:00 +0000
+++ tests/local-provider/local-provider_test.cpp 2017-04-06 07:42:38 +0000
@@ -141,6 +141,22 @@
141 "nulla pariatur. Excepteur sint occaecat cupidatat non proident, "141 "nulla pariatur. Excepteur sint occaecat cupidatat non proident, "
142 "sunt in culpa qui officia deserunt mollit anim id est laborum.\n";142 "sunt in culpa qui officia deserunt mollit anim id est laborum.\n";
143143
144void make_hierarchy(string const& root_dir)
145{
146 // Make a small tree so we have something to test against.
147 ASSERT_EQ(0, mkdir((root_dir + "/a").c_str(), 0755));
148 ASSERT_EQ(0, mkdir((root_dir + "/a/b").c_str(), 0755));
149 string cmd = string("echo hello >") + root_dir + "/hello";
150 ASSERT_EQ(0, system(cmd.c_str()));
151 cmd = string("echo text >") + root_dir + "/a/foo.txt";
152 ASSERT_EQ(0, system(cmd.c_str()));
153 ASSERT_EQ(0, mknod((root_dir + "/a/pipe").c_str(), S_IFIFO | 06666, 0));
154 ASSERT_EQ(0, mkdir((root_dir + "/a/.storage-framework-").c_str(), 0755));
155 ASSERT_EQ(0, mkdir((root_dir + "/a/b/.storage-framework-").c_str(), 0755));
156 ASSERT_EQ(0, mknod((root_dir + "/a/b/pipe").c_str(), S_IFIFO | 06666, 0));
157 ASSERT_EQ(0, mkdir((root_dir + "/empty_dir").c_str(), 0755));
158}
159
144} // namespace160} // namespace
145161
146TEST(Directories, env_vars)162TEST(Directories, env_vars)
@@ -307,6 +323,9 @@
307 ASSERT_EQ(ItemJob::Error, job->status()) << job->error().errorString().toStdString();323 ASSERT_EQ(ItemJob::Error, job->status()) << job->error().errorString().toStdString();
308 EXPECT_EQ(string("Exists: create_folder(): \"") + ROOT_DIR() + "/child\" exists already",324 EXPECT_EQ(string("Exists: create_folder(): \"") + ROOT_DIR() + "/child\" exists already",
309 job->error().errorString().toStdString());325 job->error().errorString().toStdString());
326 EXPECT_EQ(ROOT_DIR() + "/child", job->error().itemId().toStdString());
327 EXPECT_EQ(ROOT_DIR() + "/child", job->error().itemId().toStdString());
328 EXPECT_EQ("child", job->error().itemName().toStdString());
310329
311 // Again, without write permission on the root dir, to get coverage for a filesystem_error in invoke_async().330 // Again, without write permission on the root dir, to get coverage for a filesystem_error in invoke_async().
312 ASSERT_EQ(0, ::rmdir((ROOT_DIR() + "/child").c_str()));331 ASSERT_EQ(0, ::rmdir((ROOT_DIR() + "/child").c_str()));
@@ -363,15 +382,17 @@
363 // Client-side API does not allow us to try to delete the root, so we talk to the provider directly.382 // Client-side API does not allow us to try to delete the root, so we talk to the provider directly.
364 auto p = make_shared<LocalProvider>();383 auto p = make_shared<LocalProvider>();
365384
385 make_hierarchy(ROOT_DIR());
386
366 auto fut = p->delete_item(ROOT_DIR(), provider::Context());387 auto fut = p->delete_item(ROOT_DIR(), provider::Context());
367 try388 try
368 {389 {
369 fut.get();390 fut.get();
370 FAIL();391 FAIL();
371 }392 }
372 catch (provider::LogicException const& e)393 catch (provider::PermissionException const& e)
373 {394 {
374 EXPECT_STREQ("LogicException: delete_item(): cannot delete root", e.what());395 EXPECT_STREQ("PermissionException: delete_item(): cannot delete root", e.what());
375 }396 }
376}397}
377398
@@ -435,6 +456,8 @@
435 EXPECT_EQ(string("NotExists: lookup(): \"") + ROOT_DIR() + "/child\": boost::filesystem::canonical: "456 EXPECT_EQ(string("NotExists: lookup(): \"") + ROOT_DIR() + "/child\": boost::filesystem::canonical: "
436 + "No such file or directory: \"" + ROOT_DIR() + "/child\"",457 + "No such file or directory: \"" + ROOT_DIR() + "/child\"",
437 job->error().errorString().toStdString());458 job->error().errorString().toStdString());
459 EXPECT_EQ(qt::StorageError::NotExists, job->error().type());
460 EXPECT_EQ(ROOT_DIR() + "/child", job->error().itemId().toStdString());
438}461}
439462
440TEST_F(LocalProviderTest, list)463TEST_F(LocalProviderTest, list)
@@ -472,19 +495,24 @@
472 EXPECT_EQ(5, child.metadata().size());495 EXPECT_EQ(5, child.metadata().size());
473}496}
474497
475void make_hierarchy(string const& root_dir)498TEST_F(LocalProviderTest, list_file)
476{499{
477 // Make a small tree so we have something to test with for move() and copy().500 // We can't try a list on a file via the client API, so we use the provider directly.
478 ASSERT_EQ(0, mkdir((root_dir + "/a").c_str(), 0755));501
479 ASSERT_EQ(0, mkdir((root_dir + "/a/b").c_str(), 0755));502 auto p = make_shared<LocalProvider>();
480 string cmd = string("echo hello >") + root_dir + "/hello";503
481 ASSERT_EQ(0, system(cmd.c_str()));504 make_hierarchy(ROOT_DIR());
482 cmd = string("echo text >") + root_dir + "/a/foo.txt";505
483 ASSERT_EQ(0, system(cmd.c_str()));506 try
484 ASSERT_EQ(0, mknod((root_dir + "/a/pipe").c_str(), S_IFIFO | 06666, 0));507 {
485 ASSERT_EQ(0, mkdir((root_dir + "/a/.storage-framework-").c_str(), 0755));508 auto fut = p->list(ROOT_DIR() + "/hello", "", {}, provider::Context());
486 ASSERT_EQ(0, mkdir((root_dir + "/a/b/.storage-framework-").c_str(), 0755));509 fut.get();
487 ASSERT_EQ(0, mknod((root_dir + "/a/b/pipe").c_str(), S_IFIFO | 06666, 0));510 FAIL();
511 }
512 catch (provider::LogicException const& e)
513 {
514 EXPECT_EQ(string("LogicException: list(): \"") + ROOT_DIR() + "/hello\" is not a folder", e.what());
515 }
488}516}
489517
490TEST_F(LocalProviderTest, move)518TEST_F(LocalProviderTest, move)
@@ -571,6 +599,26 @@
571 job->error().errorString().toStdString());599 job->error().errorString().toStdString());
572}600}
573601
602TEST_F(LocalProviderTest, move_root)
603{
604 // We can't try to move a root via the client API, so we use the LocalDownloadJob directly.
605
606 auto p = make_shared<LocalProvider>();
607
608 make_hierarchy(ROOT_DIR());
609
610 auto fut = p->move(ROOT_DIR(), ROOT_DIR() + "/empty_dir", "moved_root", {}, provider::Context());
611 try
612 {
613 fut.get();
614 FAIL();
615 }
616 catch (provider::PermissionException const& e)
617 {
618 EXPECT_STREQ("PermissionException: move(): cannot move root", e.what());
619 }
620}
621
574TEST_F(LocalProviderTest, copy_file)622TEST_F(LocalProviderTest, copy_file)
575{623{
576 using namespace unity::storage::qt;624 using namespace unity::storage::qt;
@@ -659,6 +707,44 @@
659 }707 }
660}708}
661709
710TEST_F(LocalProviderTest, copy_root)
711{
712 using namespace unity::storage::qt;
713 using namespace boost::filesystem;
714
715 set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider));
716
717 make_hierarchy(ROOT_DIR());
718
719 auto root = get_root(acc_);
720
721 qt::Item empty_dir;
722 {
723 unique_ptr<ItemListJob> job(root.lookup("empty_dir"));
724 auto items = get_items(job.get());
725 ASSERT_EQ(ItemListJob::Finished, job->status()) << job->error().errorString().toStdString();
726 ASSERT_EQ(1, items.size());
727 empty_dir = items.at(0);
728 }
729
730 {
731 unique_ptr<ItemJob> job(root.copy(empty_dir, "copied_root"));
732 wait(job.get());
733 ASSERT_EQ(ItemJob::Finished, job->status()) << job->error().errorString().toStdString();
734 }
735
736 // Check that we only copied regular files and directories, but not a pipe or anything starting with
737 // the temp file prefix.
738 EXPECT_TRUE(exists(ROOT_DIR() + "/empty_dir/copied_root/a/b"));
739 EXPECT_TRUE(exists(ROOT_DIR() + "/empty_dir/copied_root/hello"));
740 EXPECT_TRUE(exists(ROOT_DIR() + "/empty_dir/copied_root/a/foo.txt"));
741 EXPECT_TRUE(exists(ROOT_DIR() + "/empty_dir/copied_root/empty_dir"));
742 EXPECT_FALSE(exists(ROOT_DIR() + "/empty_dir/copied_root/a/pipe"));
743 EXPECT_FALSE(exists(ROOT_DIR() + "/empty_dir/copied_root/a/storage-framework-"));
744 EXPECT_FALSE(exists(ROOT_DIR() + "/empty_dir/copied_root/a/b/pipe"));
745 EXPECT_FALSE(exists(ROOT_DIR() + "/empty_dir/copied_root/a/b/storage-framework-"));
746}
747
662TEST_F(LocalProviderTest, download)748TEST_F(LocalProviderTest, download)
663{749{
664 using namespace unity::storage::qt;750 using namespace unity::storage::qt;
@@ -785,7 +871,7 @@
785871
786 auto error = downloader->error();872 auto error = downloader->error();
787 EXPECT_EQ(qt::StorageError::Conflict, error.type());873 EXPECT_EQ(qt::StorageError::Conflict, error.type());
788 EXPECT_EQ("download(): etag mismatch", error.message().toStdString());874 EXPECT_EQ("download(): ETag mismatch", error.message().toStdString());
789}875}
790876
791TEST_F(LocalProviderTest, download_wrong_file_type)877TEST_F(LocalProviderTest, download_wrong_file_type)
@@ -802,9 +888,9 @@
802 LocalDownloadJob(p, dir, "some_etag");888 LocalDownloadJob(p, dir, "some_etag");
803 FAIL();889 FAIL();
804 }890 }
805 catch (provider::InvalidArgumentException const& e)891 catch (provider::LogicException const& e)
806 {892 {
807 EXPECT_EQ(string("InvalidArgumentException: download(): \"" + dir + "\" is not a file"), e.what());893 EXPECT_EQ(string("LogicException: download(): \"" + dir + "\" is not a file"), e.what());
808 }894 }
809}895}
810896
@@ -1013,7 +1099,7 @@
1013 {1099 {
1014 ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME));1100 ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME));
1015 }1101 }
1016 EXPECT_EQ("update(): etag mismatch", uploader->error().message().toStdString());1102 EXPECT_EQ("update(): ETag mismatch", uploader->error().message().toStdString());
1017}1103}
10181104
1019TEST_F(LocalProviderTest, update_file_touched_while_uploading)1105TEST_F(LocalProviderTest, update_file_touched_while_uploading)
@@ -1062,7 +1148,56 @@
1062 ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME));1148 ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME));
1063 }1149 }
1064 ASSERT_EQ(Uploader::Error, uploader->status());1150 ASSERT_EQ(Uploader::Error, uploader->status());
1065 EXPECT_EQ("update(): etag mismatch", uploader->error().message().toStdString());1151 EXPECT_EQ("update(): ETag mismatch", uploader->error().message().toStdString());
1152}
1153
1154TEST_F(LocalProviderTest, update_folder_created_while_uploading)
1155{
1156 using namespace unity::storage::qt;
1157
1158 set_provider(unique_ptr<provider::ProviderBase>(new LocalProvider));
1159
1160 auto full_path = ROOT_DIR() + "/foo.txt";
1161 auto cmd = string("echo hello >") + full_path;
1162 ASSERT_EQ(0, system(cmd.c_str()));
1163
1164 unique_ptr<ItemJob> job(acc_.get(QString::fromStdString(full_path)));
1165 wait(job.get());
1166 EXPECT_TRUE(job->isValid());
1167
1168 auto file = job->item();
1169 auto old_etag = file.etag();
1170
1171 int const segments = 50;
1172 unique_ptr<Uploader> uploader(file.createUploader(Item::ErrorIfConflict, file_contents.size() * segments));
1173
1174 int count = 0;
1175 QTimer timer;
1176 timer.setSingleShot(false);
1177 timer.setInterval(10);
1178 QObject::connect(&timer, &QTimer::timeout, [&] {
1179 uploader->write(&file_contents[0], file_contents.size());
1180 count++;
1181 if (count == segments / 2)
1182 {
1183 sleep(1);
1184 cmd = string("rm ") + full_path + "; mkdir " + full_path;
1185 ASSERT_EQ(0, system(cmd.c_str()));
1186 }
1187 else if (count == segments)
1188 {
1189 uploader->close();
1190 }
1191 });
1192
1193 QSignalSpy spy(uploader.get(), &Uploader::statusChanged);
1194 timer.start();
1195 while (uploader->status() != Uploader::Error)
1196 {
1197 ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME));
1198 }
1199 ASSERT_EQ(Uploader::Error, uploader->status());
1200 EXPECT_EQ(string("update(): \"") + full_path + "\" exists already", uploader->error().message().toStdString());
1066}1201}
10671202
1068TEST_F(LocalProviderTest, update_ignore_etag_mismatch)1203TEST_F(LocalProviderTest, update_ignore_etag_mismatch)
@@ -1208,9 +1343,9 @@
1208 LocalUploadJob(p, dir, 0, "");1343 LocalUploadJob(p, dir, 0, "");
1209 FAIL();1344 FAIL();
1210 }1345 }
1211 catch (provider::InvalidArgumentException const& e)1346 catch (provider::LogicException const& e)
1212 {1347 {
1213 EXPECT_EQ(string("InvalidArgumentException: update(): \"" + dir + "\" is not a file"), e.what());1348 EXPECT_EQ(string("LogicException: update(): \"" + dir + "\" is not a file"), e.what());
1214 }1349 }
1215}1350}
12161351
12171352
=== modified file 'tests/remote-client/remote-client_test.cpp'
--- tests/remote-client/remote-client_test.cpp 2017-01-12 06:02:30 +0000
+++ tests/remote-client/remote-client_test.cpp 2017-04-06 07:42:38 +0000
@@ -946,7 +946,7 @@
946 unique_ptr<VoidJob> j(item.deleteItem());946 unique_ptr<VoidJob> j(item.deleteItem());
947 EXPECT_FALSE(j->isValid());947 EXPECT_FALSE(j->isValid());
948 EXPECT_EQ(VoidJob::Status::Error, j->status());948 EXPECT_EQ(VoidJob::Status::Error, j->status());
949 EXPECT_EQ(StorageError::Type::LogicError, j->error().type());949 EXPECT_EQ(StorageError::Type::PermissionDenied, j->error().type());
950950
951 // Signal must be received.951 // Signal must be received.
952 QSignalSpy spy(j.get(), &VoidJob::statusChanged);952 QSignalSpy spy(j.get(), &VoidJob::statusChanged);

Subscribers

People subscribed via source and target branches

to all changes: