Merge lp:~michihenning/storage-framework/metadata-keys into lp:storage-framework/devel

Proposed by Michi Henning
Status: Merged
Approved by: James Henstridge
Approved revision: 92
Merged at revision: 84
Proposed branch: lp:~michihenning/storage-framework/metadata-keys
Merge into: lp:storage-framework/devel
Prerequisite: lp:~michihenning/storage-framework/specify-metadata
Diff against target: 1147 lines (+500/-115)
18 files modified
CMakeLists.txt (+1/-1)
debian/changelog (+1/-0)
demo/provider_test/provider-test.cpp (+5/-5)
include/unity/storage/common.h (+19/-0)
include/unity/storage/internal/metadata_keys.h (+22/-15)
include/unity/storage/qt/Downloader.h (+1/-1)
include/unity/storage/qt/Item.h (+1/-3)
include/unity/storage/qt/MetadataKeys.h.THIS (+37/-0)
include/unity/storage/qt/internal/ItemImpl.h (+1/-2)
src/qt/Item.cpp (+5/-10)
src/qt/client/internal/remote_client/FileImpl.cpp (+2/-2)
src/qt/client/internal/remote_client/ItemImpl.cpp (+3/-3)
src/qt/client/internal/remote_client/validate.cpp (+14/-9)
src/qt/internal/ItemImpl.cpp (+14/-14)
src/qt/internal/validate.cpp (+43/-31)
tests/remote-client-v1/MockProvider.cpp (+5/-5)
tests/remote-client/MockProvider.cpp (+123/-14)
tests/remote-client/remote-client_test.cpp (+203/-0)
To merge this branch: bzr merge lp:~michihenning/storage-framework/metadata-keys
Reviewer Review Type Date Requested Status
unity-api-1-bot continuous-integration Approve
James Henstridge Approve
Review via email: mp+309653@code.launchpad.net

Commit message

Moved metadata key definitions into common.h and move metadata_keys.h to include/unity/storage/internal so both client and server side can see them.
Added commonly supported metadata keys.
Removed free and used space methods (they are metadata now).
No metadata validation for roots because Dropbox can't support that.

Description of the change

Moved metadata key definitions into common.h and move metadata_keys.h to include/unity/storage/internal so both client and server side can see them.
Added commonly supported metadata keys.
Removed free and used space methods (they are metadata now).
No metadata validation for roots because Dropbox can't support that.

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:85
https://jenkins.canonical.com/unity-api-1/job/lp-storage-framework-ci/159/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build/919
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/926
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/729
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/729/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/729
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/729/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/729
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/729/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/729
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/729/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/729
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/729/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/729
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/729/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/729
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/729/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/729
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/729/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/729
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/729/artifact/output/*zip*/output.zip

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

review: Approve (continuous-integration)
88. By Michi Henning

Updated changelog. Bumped API version.

89. By Michi Henning

Server-side API version back to 1. Updated changelog accordingly.

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

PASSED: Continuous integration, rev:87
https://jenkins.canonical.com/unity-api-1/job/lp-storage-framework-ci/160/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build/920
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/927
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/730
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/730/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/730
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/730/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/730
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/730/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/730
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/730/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/730
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/730/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/730
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/730/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/730
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/730/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/730
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/730/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/730
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/730/artifact/output/*zip*/output.zip

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

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

PASSED: Continuous integration, rev:89
https://jenkins.canonical.com/unity-api-1/job/lp-storage-framework-ci/161/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build/921
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/928
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/731
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/731/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/731
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/731/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/731
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/731/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/731
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/731/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/731
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/731/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/731
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/731/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/731
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/731/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/731
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/731/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/731
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/731/artifact/output/*zip*/output.zip

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

review: Approve (continuous-integration)
90. By Michi Henning

Merged dependent branch.

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

FAILED: Continuous integration, rev:90
https://jenkins.canonical.com/unity-api-1/job/lp-storage-framework-ci/168/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build/946/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/953
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/756/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/756
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/756/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/756
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/756/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/756/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/756
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/756/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/756
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/756/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/756/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/756
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/756/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/756
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/756/artifact/output/*zip*/output.zip

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

review: Needs Fixing (continuous-integration)
91. By Michi Henning

Merged dependent branch.

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

PASSED: Continuous integration, rev:91
https://jenkins.canonical.com/unity-api-1/job/lp-storage-framework-ci/171/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build/949
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/956
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/759
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/759/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/759
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/759/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/759
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/759/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/759
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/759/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/759
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/759/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/759
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/759/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/759
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/759/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/759
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/759/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/759
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/759/artifact/output/*zip*/output.zip

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

review: Approve (continuous-integration)
92. By Michi Henning

Relaxed validation of etag so it applies only to files.

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 :

PASSED: Continuous integration, rev:92
https://jenkins.canonical.com/unity-api-1/job/lp-storage-framework-ci/173/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build/952
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/959
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/762
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/762/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/762
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/762/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/762
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/762/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/762
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/762/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/762
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/762/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/762
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/762/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/762
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/762/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/762
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/762/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/762
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/762/artifact/output/*zip*/output.zip

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

review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2016-09-29 11:37:05 +0000
3+++ CMakeLists.txt 2016-11-02 06:07:10 +0000
4@@ -8,7 +8,7 @@
5 project(storage-framework VERSION "0.2" LANGUAGES C CXX)
6
7 # These variables should be incremented when we wish to create a new
8-# source incompatible version of the the library where users of the
9+# source incompatible version of the library where users of the
10 # old API will not compile against the new one. It is not
11 # necessary to increment this for ABI breaks that are source compatible.
12 set(SF_CLIENT_API_VERSION "2")
13
14=== modified file 'debian/changelog'
15--- debian/changelog 2016-09-21 04:41:06 +0000
16+++ debian/changelog 2016-11-02 06:07:10 +0000
17@@ -2,6 +2,7 @@
18
19 [ Michi Henning ]
20 * Added v2 of the client-side API.
21+ * Updated server-side API to tell the provider which metadata to return.
22
23 -- Michi Henning <michi.henning@canonical.com> Wed, 21 Sep 2016 14:36:15 +1000
24
25
26=== modified file 'demo/provider_test/provider-test.cpp'
27--- demo/provider_test/provider-test.cpp 2016-11-02 06:07:10 +0000
28+++ demo/provider_test/provider-test.cpp 2016-11-02 06:07:10 +0000
29@@ -16,9 +16,9 @@
30 * Authors: James Henstridge <james.henstridge@canonical.com>
31 */
32
33+#include <unity/storage/common.h>
34 #include <unity/storage/provider/DownloadJob.h>
35 #include <unity/storage/provider/Exceptions.h>
36-#include <unity/storage/provider/metadata_keys.h>
37 #include <unity/storage/provider/ProviderBase.h>
38 #include <unity/storage/provider/Server.h>
39 #include <unity/storage/provider/TempfileUploadJob.h>
40@@ -128,7 +128,7 @@
41 {
42 {
43 "child_id", { "root_id" }, "Child", "etag", ItemType::file,
44- { { SIZE_IN_BYTES, 0 }, { LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
45+ { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
46 }
47 };
48 boost::promise<tuple<ItemList,string>> p;
49@@ -154,7 +154,7 @@
50 ItemList children =
51 {
52 { "child_id", { "root_id" }, "Child", "etag", ItemType::file,
53- { { SIZE_IN_BYTES, 0 }, { LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } } }
54+ { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } } }
55 };
56 return make_ready_future<ItemList>(children);
57 }
58@@ -175,7 +175,7 @@
59 Item metadata
60 {
61 "child_id", { "root_id" }, "Child", "etag", ItemType::file,
62- { { SIZE_IN_BYTES, 0 }, { LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
63+ { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
64 };
65 return make_ready_future<Item>(metadata);
66 }
67@@ -289,7 +289,7 @@
68 Item metadata
69 {
70 "some_id", { "root_id" }, "some_upload", "etag", ItemType::file,
71- { { SIZE_IN_BYTES, 10 }, { LAST_MODIFIED_TIME, "2011-04-05T14:30:10.005Z" } }
72+ { { metadata::SIZE_IN_BYTES, 10 }, { metadata::LAST_MODIFIED_TIME, "2011-04-05T14:30:10.005Z" } }
73 };
74 return make_ready_future(metadata);
75 }
76
77=== modified file 'include/unity/storage/common.h'
78--- include/unity/storage/common.h 2016-07-12 02:22:05 +0000
79+++ include/unity/storage/common.h 2016-11-02 06:07:10 +0000
80@@ -37,5 +37,24 @@
81 overwrite,
82 };
83
84+namespace metadata
85+{
86+
87+static char constexpr SIZE_IN_BYTES[] = "size_in_bytes"; // int64_t, >= 0
88+static char constexpr CREATION_TIME[] = "creation_time"; // String, ISO 8601 format
89+static char constexpr LAST_MODIFIED_TIME[] = "last_modified_time"; // String, ISO 8601 format
90+static char constexpr CHILD_COUNT[] = "child_count"; // int64_t, >= 0
91+static char constexpr DESCRIPTION[] = "description"; // String
92+static char constexpr DISPLAY_NAME[] = "display_name"; // String
93+static char constexpr FREE_SPACE_BYTES[] = "free_space_bytes"; // int64_t, >= 0
94+static char constexpr USED_SPACE_BYTES[] = "used_space_bytes"; // int64_t, >= 0
95+static char constexpr CONTENT_TYPE[] = "content_type"; // String
96+static char constexpr WRITABLE[] = "writable"; // Bool
97+static char constexpr MD5[] = "md5"; // String
98+static char constexpr DOWNLOAD_URL[] = "download_url"; // String
99+
100+static char constexpr ALL[] = "__ALL__";
101+
102+} // namespace metadata
103 } // namespace storage
104 } // namespace unity
105
106=== renamed file 'include/unity/storage/provider/metadata_keys.h' => 'include/unity/storage/internal/metadata_keys.h'
107--- include/unity/storage/provider/metadata_keys.h 2016-08-09 02:25:13 +0000
108+++ include/unity/storage/internal/metadata_keys.h 2016-11-02 06:07:10 +0000
109@@ -18,28 +18,35 @@
110
111 #pragma once
112
113+#include <unity/storage/common.h>
114+
115 #include <unordered_map>
116
117 namespace unity
118 {
119 namespace storage
120 {
121-namespace provider
122-{
123-
124-static char constexpr SIZE_IN_BYTES[] = "size_in_bytes"; // int64_t, >= 0
125-static char constexpr CREATION_TIME[] = "creation_time"; // String, ISO 8601 format
126-static char constexpr LAST_MODIFIED_TIME[] = "last_modified_time"; // String, ISO 8601 format
127-
128-enum class MetadataType { int64, iso_8601_date_time };
129-
130-static std::unordered_map<std::string, MetadataType> known_metadata =
131-{
132- { SIZE_IN_BYTES, MetadataType::int64 },
133- { CREATION_TIME, MetadataType::iso_8601_date_time },
134- { LAST_MODIFIED_TIME, MetadataType::iso_8601_date_time }
135+namespace metadata
136+{
137+
138+enum class MetadataType { non_zero_pos_int64, iso_8601_date_time, string, boolean };
139+
140+static std::unordered_map<std::string, MetadataType> const known_metadata =
141+{
142+ { metadata::SIZE_IN_BYTES, MetadataType::non_zero_pos_int64 },
143+ { metadata::CREATION_TIME, MetadataType::iso_8601_date_time },
144+ { metadata::LAST_MODIFIED_TIME, MetadataType::iso_8601_date_time },
145+ { metadata::CHILD_COUNT, MetadataType::non_zero_pos_int64 },
146+ { metadata::DESCRIPTION, MetadataType::string },
147+ { metadata::DISPLAY_NAME, MetadataType::string },
148+ { metadata::FREE_SPACE_BYTES, MetadataType::non_zero_pos_int64 },
149+ { metadata::USED_SPACE_BYTES, MetadataType::non_zero_pos_int64 },
150+ { metadata::CONTENT_TYPE, MetadataType::string },
151+ { metadata::WRITABLE, MetadataType::boolean },
152+ { metadata::MD5, MetadataType::string },
153+ { metadata::DOWNLOAD_URL, MetadataType::string }
154 };
155
156-} // namespace provider
157+} // namespace metadata
158 } // namespace storage
159 } // namespace unity
160
161=== modified file 'include/unity/storage/qt/Downloader.h'
162--- include/unity/storage/qt/Downloader.h 2016-10-12 05:25:20 +0000
163+++ include/unity/storage/qt/Downloader.h 2016-11-02 06:07:10 +0000
164@@ -54,7 +54,7 @@
165 bool isValid() const; // Not nice, hides QLocalSocket::isValid()
166 Status status() const;
167 StorageError error() const; // Not nice, hides QLocalSocket::error()
168- Item item() const; // TODO: Should we keep this?
169+ Item item() const;
170
171 Q_INVOKABLE void finishDownload();
172 Q_INVOKABLE void cancel();
173
174=== modified file 'include/unity/storage/qt/Item.h'
175--- include/unity/storage/qt/Item.h 2016-11-02 06:07:10 +0000
176+++ include/unity/storage/qt/Item.h 2016-11-02 06:07:10 +0000
177@@ -94,6 +94,7 @@
178 QString etag() const;
179 Type type() const;
180 QVariantMap metadata() const;
181+ qint64 sizeInBytes() const;
182 QDateTime lastModifiedTime() const;
183 QStringList parentIds() const;
184
185@@ -120,9 +121,6 @@
186 QString const& contentType,
187 QStringList const& keys = QStringList()) const;
188
189- Q_INVOKABLE IntJob* freeSpaceBytes() const;
190- Q_INVOKABLE IntJob* usedSpaceBytes() const;
191-
192 bool operator==(Item const&) const;
193 bool operator!=(Item const&) const;
194 bool operator<(Item const&) const;
195
196=== added file 'include/unity/storage/qt/MetadataKeys.h.THIS'
197--- include/unity/storage/qt/MetadataKeys.h.THIS 1970-01-01 00:00:00 +0000
198+++ include/unity/storage/qt/MetadataKeys.h.THIS 2016-11-02 06:07:10 +0000
199@@ -0,0 +1,37 @@
200+/*
201+ * Copyright (C) 2016 Canonical Ltd
202+ *
203+ * This program is free software: you can redistribute it and/or modify
204+ * it under the terms of the GNU Lesser General Public License version 3 as
205+ * published by the Free Software Foundation.
206+ *
207+ * This program is distributed in the hope that it will be useful,
208+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
209+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
210+ * GNU Lesser General Public License for more details.
211+ *
212+ * You should have received a copy of the GNU Lesser General Public License
213+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
214+ *
215+ * Authors: Michi Henning <michi.henning@canonical.com>
216+ */
217+
218+#pragma once
219+
220+#include <unity/storage/common.h>
221+
222+#include <QList>
223+#include <QString>
224+
225+namespace unity
226+{
227+namespace storage
228+{
229+namespace qt
230+{
231+
232+static QStringList const ALL_METADATA = { metadata::ALL };
233+
234+} // namespace qt
235+} // namespace storage
236+} // namespace unity
237
238=== modified file 'include/unity/storage/qt/internal/ItemImpl.h'
239--- include/unity/storage/qt/internal/ItemImpl.h 2016-11-02 06:07:10 +0000
240+++ include/unity/storage/qt/internal/ItemImpl.h 2016-11-02 06:07:10 +0000
241@@ -55,6 +55,7 @@
242 QString etag() const;
243 Item::Type type() const;
244 QVariantMap metadata() const;
245+ qint64 sizeInBytes() const;
246 QDateTime lastModifiedTime() const;
247 QList<QString> parentIds() const;
248
249@@ -73,8 +74,6 @@
250 qint64 sizeInBytes,
251 QString const& contentType,
252 QStringList const& keys) const;
253- IntJob* freeSpaceBytes() const;
254- IntJob* usedSpaceBytes() const;
255
256 bool operator==(ItemImpl const&) const;
257 bool operator!=(ItemImpl const&) const;
258
259=== modified file 'src/qt/Item.cpp'
260--- src/qt/Item.cpp 2016-11-02 06:07:10 +0000
261+++ src/qt/Item.cpp 2016-11-02 06:07:10 +0000
262@@ -107,6 +107,11 @@
263 return p_->metadata();
264 }
265
266+qint64 Item::sizeInBytes() const
267+{
268+ return p_->sizeInBytes();
269+}
270+
271 QDateTime Item::lastModifiedTime() const
272 {
273 return p_->lastModifiedTime();
274@@ -171,16 +176,6 @@
275 return p_->createFile(name, policy, sizeInBytes, contentType, keys);
276 }
277
278-IntJob* Item::freeSpaceBytes() const
279-{
280- return p_->freeSpaceBytes();
281-}
282-
283-IntJob* Item::usedSpaceBytes() const
284-{
285- return p_->usedSpaceBytes();
286-}
287-
288 bool Item::operator==(Item const& other) const
289 {
290 return p_->operator==(*other.p_);
291
292=== modified file 'src/qt/client/internal/remote_client/FileImpl.cpp'
293--- src/qt/client/internal/remote_client/FileImpl.cpp 2016-11-02 06:07:10 +0000
294+++ src/qt/client/internal/remote_client/FileImpl.cpp 2016-11-02 06:07:10 +0000
295@@ -19,7 +19,7 @@
296 #include <unity/storage/qt/client/internal/remote_client/FileImpl.h>
297
298 #include "ProviderInterface.h"
299-#include <unity/storage/provider/metadata_keys.h>
300+#include <unity/storage/common.h>
301 #include <unity/storage/qt/client/File.h>
302 #include <unity/storage/qt/client/internal/remote_client/Handler.h>
303 #include <unity/storage/qt/client/internal/remote_client/DownloaderImpl.h>
304@@ -51,7 +51,7 @@
305 int64_t FileImpl::size() const
306 {
307 throw_if_destroyed("File::size()");
308- return md_.metadata.value(provider::SIZE_IN_BYTES).toLongLong();
309+ return md_.metadata.value(metadata::SIZE_IN_BYTES).toLongLong();
310 }
311
312 QFuture<shared_ptr<Uploader>> FileImpl::create_uploader(ConflictPolicy policy, int64_t size)
313
314=== modified file 'src/qt/client/internal/remote_client/ItemImpl.cpp'
315--- src/qt/client/internal/remote_client/ItemImpl.cpp 2016-11-02 06:07:10 +0000
316+++ src/qt/client/internal/remote_client/ItemImpl.cpp 2016-11-02 06:07:10 +0000
317@@ -19,7 +19,7 @@
318 #include <unity/storage/qt/client/internal/remote_client/ItemImpl.h>
319
320 #include "ProviderInterface.h"
321-#include <unity/storage/provider/metadata_keys.h>
322+#include <unity/storage/common.h>
323 #include <unity/storage/qt/client/Account.h>
324 #include <unity/storage/qt/client/internal/remote_client/AccountImpl.h>
325 #include <unity/storage/qt/client/internal/remote_client/FileImpl.h>
326@@ -72,7 +72,7 @@
327 QDateTime ItemImpl::last_modified_time() const
328 {
329 throw_if_destroyed("Item::last_modified_time()");
330- return QDateTime::fromString(md_.metadata.value(provider::LAST_MODIFIED_TIME).toString(), Qt::ISODate);
331+ return QDateTime::fromString(md_.metadata.value(metadata::LAST_MODIFIED_TIME).toString(), Qt::ISODate);
332 }
333
334 QFuture<shared_ptr<Item>> ItemImpl::copy(shared_ptr<Folder> const& new_parent, QString const& new_name)
335@@ -254,7 +254,7 @@
336 QDateTime ItemImpl::creation_time() const
337 {
338 throw_if_destroyed("Item::creation_time()");
339- return QDateTime::fromString(md_.metadata.value(provider::CREATION_TIME).toString(), Qt::ISODate);
340+ return QDateTime::fromString(md_.metadata.value(metadata::CREATION_TIME).toString(), Qt::ISODate);
341 }
342
343 MetadataMap ItemImpl::native_metadata() const
344
345=== modified file 'src/qt/client/internal/remote_client/validate.cpp'
346--- src/qt/client/internal/remote_client/validate.cpp 2016-09-28 10:08:40 +0000
347+++ src/qt/client/internal/remote_client/validate.cpp 2016-11-02 06:07:10 +0000
348@@ -18,7 +18,7 @@
349
350 #include <unity/storage/internal/ItemMetadata.h>
351
352-#include <unity/storage/provider/metadata_keys.h>
353+#include <unity/storage/internal/metadata_keys.h>
354 #include <unity/storage/qt/client/Exceptions.h>
355
356 #include <QDateTime>
357@@ -48,9 +48,9 @@
358
359 void validate_type_and_value(QString const& prefix,
360 QMapIterator<QString, QVariant> actual,
361- unordered_map<string, provider::MetadataType>::const_iterator known)
362+ unordered_map<string, metadata::MetadataType>::const_iterator known)
363 {
364- using namespace unity::storage::provider;
365+ using namespace unity::storage::metadata;
366
367 switch (known->second)
368 {
369@@ -75,7 +75,7 @@
370 }
371 break;
372 }
373- case MetadataType::int64:
374+ case MetadataType::non_zero_pos_int64:
375 {
376 if (actual.value().type() != QVariant::LongLong)
377 {
378@@ -84,6 +84,11 @@
379 }
380 break;
381 }
382+ case MetadataType::string:
383+ case MetadataType::boolean:
384+ {
385+ break;
386+ }
387 default:
388 {
389 abort(); // Impossible. // LCOV_EXCL_LINE
390@@ -95,7 +100,7 @@
391
392 void validate(QString const& method, ItemMetadata const& md)
393 {
394- using namespace unity::storage::provider;
395+ using namespace unity::storage::metadata;
396
397 QString prefix = method + ": received invalid metadata from server: ";
398
399@@ -150,13 +155,13 @@
400 // Sanity check metadata to make sure that mandatory fields are present.
401 if (md.type == ItemType::file)
402 {
403- if (!md.metadata.contains(SIZE_IN_BYTES))
404+ if (!md.metadata.contains(metadata::SIZE_IN_BYTES))
405 {
406- throw LocalCommsException(prefix + "missing key " + SIZE_IN_BYTES + " in metadata for " + md.item_id);
407+ throw LocalCommsException(prefix + "missing key " + metadata::SIZE_IN_BYTES + " in metadata for " + md.item_id);
408 }
409- if (!md.metadata.contains(LAST_MODIFIED_TIME))
410+ if (!md.metadata.contains(metadata::LAST_MODIFIED_TIME))
411 {
412- throw LocalCommsException(prefix + "missing key " + LAST_MODIFIED_TIME + " in metadata for " + md.item_id);
413+ throw LocalCommsException(prefix + "missing key " + metadata::LAST_MODIFIED_TIME + " in metadata for " + md.item_id);
414 }
415 }
416 }
417
418=== modified file 'src/qt/internal/ItemImpl.cpp'
419--- src/qt/internal/ItemImpl.cpp 2016-11-02 06:07:10 +0000
420+++ src/qt/internal/ItemImpl.cpp 2016-11-02 06:07:10 +0000
421@@ -19,7 +19,7 @@
422 #include <unity/storage/qt/internal/ItemImpl.h>
423
424 #include "ProviderInterface.h"
425-#include <unity/storage/provider/metadata_keys.h>
426+#include <unity/storage/common.h>
427 #include <unity/storage/qt/internal/DownloaderImpl.h>
428 #include <unity/storage/qt/internal/ItemJobImpl.h>
429 #include <unity/storage/qt/internal/ItemListJobImpl.h>
430@@ -96,13 +96,23 @@
431
432 QVariantMap ItemImpl::metadata() const
433 {
434- // TODO: Need to agree on metadata representation.
435- return is_valid_ ? QVariantMap() : QVariantMap();
436+ return is_valid_ ? md_.metadata : QVariantMap();
437+}
438+
439+qint64 ItemImpl::sizeInBytes() const
440+{
441+ if (!is_valid_ || md_.type != ItemType::file)
442+ {
443+ return 0;
444+ }
445+ auto variant = md_.metadata.value(metadata::SIZE_IN_BYTES);
446+ assert(variant.isValid());
447+ return variant.toLongLong();
448 }
449
450 QDateTime ItemImpl::lastModifiedTime() const
451 {
452- return is_valid_ ? QDateTime::fromString(md_.metadata.value(provider::LAST_MODIFIED_TIME).toString(), Qt::ISODate)
453+ return is_valid_ ? QDateTime::fromString(md_.metadata.value(metadata::LAST_MODIFIED_TIME).toString(), Qt::ISODate)
454 : QDateTime();
455 }
456
457@@ -425,16 +435,6 @@
458 return UploaderImpl::make_job(This, method, reply, validate, policy, sizeInBytes);
459 }
460
461-IntJob* ItemImpl::freeSpaceBytes() const
462-{
463- return nullptr; // TODO
464-}
465-
466-IntJob* ItemImpl::usedSpaceBytes() const
467-{
468- return nullptr; // TODO
469-}
470-
471 bool ItemImpl::operator==(ItemImpl const& other) const
472 {
473 if (is_valid_)
474
475=== modified file 'src/qt/internal/validate.cpp'
476--- src/qt/internal/validate.cpp 2016-09-16 06:25:08 +0000
477+++ src/qt/internal/validate.cpp 2016-11-02 06:07:10 +0000
478@@ -19,7 +19,7 @@
479 #include <unity/storage/qt/internal/validate.h>
480
481 #include <unity/storage/internal/ItemMetadata.h>
482-#include <unity/storage/provider/metadata_keys.h>
483+#include <unity/storage/internal/metadata_keys.h>
484 #include <unity/storage/qt/internal/StorageErrorImpl.h>
485
486 #include <QDateTime>
487@@ -45,9 +45,9 @@
488
489 void validate_type_and_value(QString const& prefix,
490 QMapIterator<QString, QVariant> actual,
491- unordered_map<string, provider::MetadataType>::const_iterator known)
492+ unordered_map<string, metadata::MetadataType>::const_iterator known)
493 {
494- using namespace unity::storage::provider;
495+ using namespace unity::storage::metadata;
496
497 switch (known->second)
498 {
499@@ -55,7 +55,7 @@
500 {
501 if (actual.value().type() != QVariant::String)
502 {
503- QString msg = prefix + actual.key() + ": expected value of type String, but received value of type "
504+ QString msg = prefix + actual.key() + ": expected value of type QString, but received value of type "
505 + actual.value().typeName();
506 throw StorageErrorImpl::local_comms_error(msg);
507 }
508@@ -75,14 +75,26 @@
509 }
510 break;
511 }
512- case MetadataType::int64:
513- {
514- if (actual.value().type() != QVariant::LongLong)
515- {
516- QString msg = prefix + actual.key() + ": expected value of type LongLong, but received value of type "
517- + actual.value().typeName();
518- throw StorageErrorImpl::local_comms_error(msg);
519- }
520+ case MetadataType::non_zero_pos_int64:
521+ {
522+ auto variant = actual.value();
523+ if (variant.type() != QVariant::LongLong)
524+ {
525+ QString msg = prefix + actual.key() + ": expected value of type qlonglong, but received value of type "
526+ + variant.typeName();
527+ throw StorageErrorImpl::local_comms_error(msg);
528+ }
529+ qint64 val = variant.toLongLong();
530+ if (val < 0)
531+ {
532+ QString msg = prefix + actual.key() + ": expected value >= 0, but received " + QString::number(val);
533+ throw StorageErrorImpl::local_comms_error(msg);
534+ }
535+ break;
536+ }
537+ case MetadataType::string:
538+ case MetadataType::boolean:
539+ {
540 break;
541 }
542 default:
543@@ -96,7 +108,7 @@
544
545 void validate(QString const& method, ItemMetadata const& md)
546 {
547- using namespace unity::storage::provider;
548+ using namespace unity::storage::metadata;
549
550 QString prefix = method + ": received invalid metadata from provider: ";
551
552@@ -121,15 +133,18 @@
553 }
554 if (md.type == ItemType::root && !md.parent_ids.isEmpty())
555 {
556- throw StorageErrorImpl::local_comms_error(prefix + "metadata: parent_ids of root must be empty");
557- }
558- if (md.name.isEmpty())
559- {
560- throw StorageErrorImpl::local_comms_error(prefix + "name cannot be empty");
561- }
562- if (md.etag.isEmpty())
563- {
564- throw StorageErrorImpl::local_comms_error(prefix + "etag cannot be empty");
565+ throw StorageErrorImpl::local_comms_error(prefix + "parent_ids of root must be empty");
566+ }
567+ if (md.type != ItemType::root) // Dropbox does not support metadata for roots.
568+ {
569+ if (md.name.isEmpty())
570+ {
571+ throw StorageErrorImpl::local_comms_error(prefix + "name cannot be empty");
572+ }
573+ }
574+ if (md.type == ItemType::file && md.etag.isEmpty()) // WebDav doesn't do etag for folders.
575+ {
576+ throw StorageErrorImpl::local_comms_error(prefix + "etag of a file cannot be empty");
577 }
578
579 // Sanity check metadata to make sure only known metadata keys appear.
580@@ -140,7 +155,7 @@
581 auto known = known_metadata.find(actual.key().toStdString());
582 if (known == known_metadata.end())
583 {
584- qWarning() << prefix << "unknown metadata key:" << actual.key();
585+ qWarning().noquote().nospace() << prefix << "unknown metadata key: \"" << actual.key() << "\"";
586 }
587 else
588 {
589@@ -151,14 +166,11 @@
590 // Sanity check metadata to make sure that mandatory fields are present.
591 if (md.type == ItemType::file)
592 {
593- if (!md.metadata.contains(SIZE_IN_BYTES))
594- {
595- QString msg = prefix + "missing key " + SIZE_IN_BYTES + " in metadata for " + md.item_id;
596- throw StorageErrorImpl::local_comms_error(msg);
597- }
598- if (!md.metadata.contains(LAST_MODIFIED_TIME))
599- {
600- QString msg = prefix + "missing key " + LAST_MODIFIED_TIME + " in metadata for " + md.item_id;
601+ if (!md.metadata.contains(metadata::SIZE_IN_BYTES) ||
602+ !md.metadata.contains(metadata::LAST_MODIFIED_TIME))
603+ {
604+ QString msg = prefix + "missing key \"" + metadata::SIZE_IN_BYTES + "\" "
605+ "in metadata for \"" + md.item_id + "\"";
606 throw StorageErrorImpl::local_comms_error(msg);
607 }
608 }
609
610=== modified file 'tests/remote-client-v1/MockProvider.cpp'
611--- tests/remote-client-v1/MockProvider.cpp 2016-11-02 06:07:10 +0000
612+++ tests/remote-client-v1/MockProvider.cpp 2016-11-02 06:07:10 +0000
613@@ -18,8 +18,8 @@
614
615 #include "MockProvider.h"
616
617+#include <unity/storage/internal/metadata_keys.h>
618 #include <unity/storage/provider/Exceptions.h>
619-#include <unity/storage/provider/metadata_keys.h>
620
621 #include <boost/thread.hpp>
622 #include <boost/thread/future.hpp>
623@@ -71,7 +71,7 @@
624 {
625 {
626 "child_id", { "root_id" }, "Child", "etag", ItemType::file,
627- { { SIZE_IN_BYTES, 0 }, { LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
628+ { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
629 }
630 };
631 boost::promise<tuple<ItemList,string>> p;
632@@ -95,7 +95,7 @@
633 ItemList children =
634 {
635 { "child_id", { "root_id" }, "Child", "etag", ItemType::file,
636- { { SIZE_IN_BYTES, 0 }, { LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } } }
637+ { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } } }
638 };
639 return make_ready_future<ItemList>(children);
640 }
641@@ -112,7 +112,7 @@
642 Item metadata
643 {
644 "child_id", { "root_id" }, "Child", "etag", ItemType::file,
645- { { SIZE_IN_BYTES, 0 }, { LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
646+ { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
647 };
648 return make_ready_future<Item>(metadata);
649 }
650@@ -209,7 +209,7 @@
651 Item metadata
652 {
653 "some_id", { "root_id" }, "some_upload", "etag", ItemType::file,
654- { { SIZE_IN_BYTES, 10 }, { LAST_MODIFIED_TIME, "2011-04-05T14:30:10.005Z" } }
655+ { { metadata::SIZE_IN_BYTES, 10 }, { metadata::LAST_MODIFIED_TIME, "2011-04-05T14:30:10.005Z" } }
656 };
657 return make_ready_future(metadata);
658 }
659
660=== modified file 'tests/remote-client/MockProvider.cpp'
661--- tests/remote-client/MockProvider.cpp 2016-11-02 06:07:10 +0000
662+++ tests/remote-client/MockProvider.cpp 2016-11-02 06:07:10 +0000
663@@ -18,8 +18,8 @@
664
665 #include "MockProvider.h"
666
667+#include <unity/storage/internal/metadata_keys.h>
668 #include <unity/storage/provider/Exceptions.h>
669-#include <unity/storage/provider/metadata_keys.h>
670
671 #include <boost/thread.hpp>
672 #include <boost/thread/future.hpp>
673@@ -96,7 +96,7 @@
674 {
675 {
676 "child_id", { "root_id" }, "Child", "etag", ItemType::root,
677- { { SIZE_IN_BYTES, 0 }, { LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
678+ { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
679 }
680 };
681 boost::promise<tuple<ItemList,string>> p;
682@@ -114,7 +114,7 @@
683 {
684 {
685 "child_id", { "root_id" }, "Child", "etag", ItemType::file,
686- { { SIZE_IN_BYTES, 0 }, { LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
687+ { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
688 }
689 };
690 }
691@@ -125,7 +125,7 @@
692 {
693 {
694 "child2_id", { "root_id" }, "Child2", "etag", ItemType::file,
695- { { SIZE_IN_BYTES, 0 }, { LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
696+ { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
697 }
698 };
699 }
700@@ -147,7 +147,7 @@
701 {
702 {
703 "child_id", { "root_id" }, "Child", "etag", ItemType::file,
704- { { SIZE_IN_BYTES, 0 }, { LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
705+ { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
706 }
707 };
708 boost::promise<tuple<ItemList,string>> p;
709@@ -171,7 +171,7 @@
710 ItemList children =
711 {
712 { "child_id", { "root_id" }, "Child", "etag", ItemType::file,
713- { { SIZE_IN_BYTES, 0 }, { LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } } }
714+ { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } } }
715 };
716 return make_ready_future<ItemList>(children);
717 }
718@@ -216,24 +216,133 @@
719 return make_ready_future<Item>(metadata);
720 }
721 }
722+ if (cmd_ == "root_with_parent")
723+ {
724+ Item metadata{"root_id", { "this shouldn't be here" }, "Root", "etag", ItemType::root, {}};
725+ return make_ready_future<Item>(metadata);
726+ }
727 Item metadata{"root_id", {}, "Root", "etag", ItemType::root, {}};
728 return make_ready_future<Item>(metadata);
729 }
730 if (item_id == "child_id")
731 {
732+ if (cmd_ == "no_parents")
733+ {
734+ Item metadata
735+ {
736+ "child_id", {}, "Child", "etag", ItemType::file,
737+ { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
738+ };
739+ return make_ready_future<Item>(metadata);
740+ }
741+ if (cmd_ == "empty_name")
742+ {
743+ Item metadata
744+ {
745+ "child_id", { "root_id" }, "", "etag", ItemType::file,
746+ { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
747+ };
748+ return make_ready_future<Item>(metadata);
749+ }
750+ if (cmd_ == "empty_etag")
751+ {
752+ Item metadata
753+ {
754+ "child_id", { "root_id" }, "Child", "", ItemType::file,
755+ { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
756+ };
757+ return make_ready_future<Item>(metadata);
758+ }
759+ if (cmd_ == "unknown_key")
760+ {
761+ Item metadata
762+ {
763+ "child_id", { "root_id" }, "Child", "etag", ItemType::file,
764+ { { metadata::SIZE_IN_BYTES, 0 },
765+ { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" },
766+ { metadata::DESCRIPTION, "child test file" }, // For coverage
767+ { metadata::WRITABLE, true }, // For coverage
768+ { "unknown_key", "" }
769+ }
770+ };
771+ return make_ready_future<Item>(metadata);
772+ }
773+ if (cmd_ == "missing_key")
774+ {
775+ Item metadata
776+ {
777+ "child_id", { "root_id" }, "Child", "etag", ItemType::file,
778+ { { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
779+ };
780+ return make_ready_future<Item>(metadata);
781+ }
782+ if (cmd_ == "wrong_type_for_time")
783+ {
784+ Item metadata
785+ {
786+ "child_id", { "root_id" }, "Child", "etag", ItemType::file,
787+ { { metadata::SIZE_IN_BYTES, 10 }, { metadata::LAST_MODIFIED_TIME, true } }
788+ };
789+ return make_ready_future<Item>(metadata);
790+ }
791+ if (cmd_ == "bad_parse_for_time")
792+ {
793+ Item metadata
794+ {
795+ "child_id", { "root_id" }, "Child", "etag", ItemType::file,
796+ { { metadata::SIZE_IN_BYTES, 10 }, { metadata::LAST_MODIFIED_TIME, "xyz" } }
797+ };
798+ return make_ready_future<Item>(metadata);
799+ }
800+ if (cmd_ == "missing_timezone")
801+ {
802+ Item metadata
803+ {
804+ "child_id", { "root_id" }, "Child", "etag", ItemType::file,
805+ { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30" } }
806+ };
807+ return make_ready_future<Item>(metadata);
808+ }
809+ if (cmd_ == "wrong_type_for_size")
810+ {
811+ Item metadata
812+ {
813+ "child_id", { "root_id" }, "Child", "etag", ItemType::file,
814+ { { metadata::SIZE_IN_BYTES, "10" }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
815+ };
816+ return make_ready_future<Item>(metadata);
817+ }
818+ if (cmd_ == "negative_size")
819+ {
820+ Item metadata
821+ {
822+ "child_id", { "root_id" }, "Child", "etag", ItemType::file,
823+ { { metadata::SIZE_IN_BYTES, -1 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
824+ };
825+ return make_ready_future<Item>(metadata);
826+ }
827+ if (cmd_ == "empty_parent")
828+ {
829+ Item metadata
830+ {
831+ "child_id", { "" }, "Child", "etag", ItemType::file,
832+ { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
833+ };
834+ return make_ready_future<Item>(metadata);
835+ }
836 if (cmd_ == "two_parents" || cmd_ == "two_parents_throw")
837 {
838 Item metadata
839 {
840 "child_id", { "root_id", "child_folder_id" }, "Child", "etag", ItemType::file,
841- { { SIZE_IN_BYTES, 0 }, { LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
842+ { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
843 };
844 return make_ready_future<Item>(metadata);
845 }
846 Item metadata
847 {
848 "child_id", { "root_id" }, "Child", "etag", ItemType::file,
849- { { SIZE_IN_BYTES, 0 }, { LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
850+ { { metadata::SIZE_IN_BYTES, 10 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
851 };
852 return make_ready_future<Item>(metadata);
853 }
854@@ -335,7 +444,7 @@
855 Item metadata
856 {
857 "root_id", { new_parent_id }, new_name, "etag", ItemType::root,
858- { { LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
859+ { { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
860 };
861 return make_ready_future(metadata);
862 }
863@@ -344,14 +453,14 @@
864 Item metadata
865 {
866 item_id, { new_parent_id }, new_name, "etag", ItemType::folder,
867- { { SIZE_IN_BYTES, 0 }, { LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
868+ { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
869 };
870 return make_ready_future(metadata);
871 }
872 Item metadata
873 {
874 item_id, { new_parent_id }, new_name, "etag", ItemType::file,
875- { { SIZE_IN_BYTES, 0 }, { LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
876+ { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
877 };
878 return make_ready_future(metadata);
879 }
880@@ -365,14 +474,14 @@
881 Item metadata
882 {
883 "new_item_id", { new_parent_id }, new_name, "etag", ItemType::folder,
884- { { SIZE_IN_BYTES, 0 }, { LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
885+ { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
886 };
887 return make_ready_future(metadata);
888 }
889 Item metadata
890 {
891 "new_item_id", { new_parent_id }, new_name, "etag", ItemType::file,
892- { { SIZE_IN_BYTES, 0 }, { LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
893+ { { metadata::SIZE_IN_BYTES, 0 }, { metadata::LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
894 };
895 return make_ready_future(metadata);
896 }
897@@ -416,7 +525,7 @@
898 Item metadata
899 {
900 "child_id", { "root_id" }, "some_upload", "etag", ItemType::file,
901- { { SIZE_IN_BYTES, 10 }, { LAST_MODIFIED_TIME, "2011-04-05T14:30:10.005Z" } }
902+ { { metadata::SIZE_IN_BYTES, 10 }, { metadata::LAST_MODIFIED_TIME, "2011-04-05T14:30:10.005Z" } }
903 };
904 return make_ready_future(metadata);
905 }
906
907=== modified file 'tests/remote-client/remote-client_test.cpp'
908--- tests/remote-client/remote-client_test.cpp 2016-10-17 13:01:16 +0000
909+++ tests/remote-client/remote-client_test.cpp 2016-11-02 06:07:10 +0000
910@@ -66,6 +66,7 @@
911 class ItemTest : public RemoteClientTest {};
912 class ListTest : public RemoteClientTest {};
913 class LookupTest : public RemoteClientTest {};
914+class MetadataTest : public RemoteClientTest {};
915 class MoveTest : public RemoteClientTest {};
916 class ParentsTest : public RemoteClientTest {};
917 class RootsTest : public RemoteClientTest {};
918@@ -345,6 +346,8 @@
919 EXPECT_EQ(AccountsJob::Status::Finished, j->status());
920 EXPECT_EQ(StorageError::Type::NoError, j->error().type());
921
922+ EXPECT_TRUE(runtime_->connection().isConnected()); // Just for coverage.
923+
924 auto accounts = j->accounts();
925
926 // We don't check the contents of accounts here because we are using the real online accounts manager
927@@ -635,6 +638,203 @@
928 EXPECT_EQ("no_such_id", j->error().itemId());
929 }
930
931+TEST_F(MetadataTest, basic)
932+{
933+ set_provider(unique_ptr<provider::ProviderBase>(new MockProvider()));
934+
935+ {
936+ Item i;
937+ EXPECT_EQ(0, i.metadata().size());
938+ }
939+
940+ {
941+ unique_ptr<ItemJob> j(acc_.get("root_id"));
942+
943+ QSignalSpy spy(j.get(), &ItemJob::statusChanged);
944+ spy.wait(SIGNAL_WAIT_TIME);
945+
946+ EXPECT_EQ(0, j->item().sizeInBytes());
947+ EXPECT_EQ(0, j->item().metadata().size());
948+ }
949+
950+ {
951+ unique_ptr<ItemJob> j(acc_.get("child_id"));
952+
953+ QSignalSpy spy(j.get(), &ItemJob::statusChanged);
954+ spy.wait(SIGNAL_WAIT_TIME);
955+
956+ EXPECT_EQ(10, j->item().sizeInBytes());
957+ EXPECT_EQ(2, j->item().metadata().size());
958+ }
959+}
960+
961+TEST_F(MetadataTest, no_parents)
962+{
963+ set_provider(unique_ptr<provider::ProviderBase>(new MockProvider("no_parents")));
964+
965+ unique_ptr<ItemJob> j(acc_.get("child_id"));
966+
967+ QSignalSpy spy(j.get(), &ItemJob::statusChanged);
968+ spy.wait(SIGNAL_WAIT_TIME);
969+
970+ EXPECT_EQ(ItemJob::Status::Error, j->status());
971+ EXPECT_EQ("LocalCommsError: Account::get(): received invalid metadata from provider: "
972+ "file or folder must have at least one parent ID", j->error().errorString());
973+}
974+
975+TEST_F(MetadataTest, empty_parent)
976+{
977+ set_provider(unique_ptr<provider::ProviderBase>(new MockProvider("empty_parent")));
978+
979+ unique_ptr<ItemJob> j(acc_.get("child_id"));
980+
981+ QSignalSpy spy(j.get(), &ItemJob::statusChanged);
982+ spy.wait(SIGNAL_WAIT_TIME);
983+
984+ EXPECT_EQ(ItemJob::Status::Error, j->status());
985+ EXPECT_EQ("LocalCommsError: Account::get(): received invalid metadata from provider: "
986+ "parent_id of file or folder cannot be empty", j->error().errorString());
987+}
988+
989+TEST_F(MetadataTest, root_with_parent)
990+{
991+ set_provider(unique_ptr<provider::ProviderBase>(new MockProvider("root_with_parent")));
992+
993+ unique_ptr<ItemJob> j(acc_.get("root_id"));
994+
995+ QSignalSpy spy(j.get(), &ItemJob::statusChanged);
996+ spy.wait(SIGNAL_WAIT_TIME);
997+
998+ EXPECT_EQ(ItemJob::Status::Error, j->status());
999+ EXPECT_EQ("LocalCommsError: Account::get(): received invalid metadata from provider: "
1000+ "parent_ids of root must be empty", j->error().errorString());
1001+}
1002+
1003+TEST_F(MetadataTest, empty_name)
1004+{
1005+ set_provider(unique_ptr<provider::ProviderBase>(new MockProvider("empty_name")));
1006+
1007+ unique_ptr<ItemJob> j(acc_.get("child_id"));
1008+
1009+ QSignalSpy spy(j.get(), &ItemJob::statusChanged);
1010+ spy.wait(SIGNAL_WAIT_TIME);
1011+
1012+ EXPECT_EQ(ItemJob::Status::Error, j->status());
1013+ EXPECT_EQ("LocalCommsError: Account::get(): received invalid metadata from provider: "
1014+ "name cannot be empty", j->error().errorString());
1015+}
1016+
1017+TEST_F(MetadataTest, empty_etag)
1018+{
1019+ set_provider(unique_ptr<provider::ProviderBase>(new MockProvider("empty_etag")));
1020+
1021+ unique_ptr<ItemJob> j(acc_.get("child_id"));
1022+
1023+ QSignalSpy spy(j.get(), &ItemJob::statusChanged);
1024+ spy.wait(SIGNAL_WAIT_TIME);
1025+
1026+ EXPECT_EQ(ItemJob::Status::Error, j->status());
1027+ EXPECT_EQ("LocalCommsError: Account::get(): received invalid metadata from provider: "
1028+ "etag of a file cannot be empty", j->error().errorString());
1029+}
1030+
1031+TEST_F(MetadataTest, unknown_key)
1032+{
1033+ set_provider(unique_ptr<provider::ProviderBase>(new MockProvider("unknown_key")));
1034+
1035+ unique_ptr<ItemJob> j(acc_.get("child_id"));
1036+
1037+ QSignalSpy spy(j.get(), &ItemJob::statusChanged);
1038+ spy.wait(SIGNAL_WAIT_TIME);
1039+
1040+ // We only emit a warning for unknown keys.
1041+ EXPECT_EQ(ItemJob::Status::Finished, j->status());
1042+}
1043+
1044+TEST_F(MetadataTest, missing_size)
1045+{
1046+ set_provider(unique_ptr<provider::ProviderBase>(new MockProvider("missing_key")));
1047+
1048+ unique_ptr<ItemJob> j(acc_.get("child_id"));
1049+
1050+ QSignalSpy spy(j.get(), &ItemJob::statusChanged);
1051+ spy.wait(SIGNAL_WAIT_TIME);
1052+
1053+ EXPECT_EQ(ItemJob::Status::Error, j->status());
1054+ EXPECT_EQ("LocalCommsError: Account::get(): received invalid metadata from provider: "
1055+ "missing key \"size_in_bytes\" in metadata for \"child_id\"", j->error().errorString());
1056+}
1057+
1058+TEST_F(MetadataTest, wrong_type_for_time)
1059+{
1060+ set_provider(unique_ptr<provider::ProviderBase>(new MockProvider("wrong_type_for_time")));
1061+
1062+ unique_ptr<ItemJob> j(acc_.get("child_id"));
1063+
1064+ QSignalSpy spy(j.get(), &ItemJob::statusChanged);
1065+ spy.wait(SIGNAL_WAIT_TIME);
1066+
1067+ EXPECT_EQ(ItemJob::Status::Error, j->status());
1068+ EXPECT_EQ("LocalCommsError: Account::get(): received invalid metadata from provider: last_modified_time: "
1069+ "expected value of type QString, but received value of type qlonglong", j->error().errorString());
1070+}
1071+
1072+TEST_F(MetadataTest, bad_parse_for_time)
1073+{
1074+ set_provider(unique_ptr<provider::ProviderBase>(new MockProvider("bad_parse_for_time")));
1075+
1076+ unique_ptr<ItemJob> j(acc_.get("child_id"));
1077+
1078+ QSignalSpy spy(j.get(), &ItemJob::statusChanged);
1079+ spy.wait(SIGNAL_WAIT_TIME);
1080+
1081+ EXPECT_EQ(ItemJob::Status::Error, j->status());
1082+ EXPECT_EQ("LocalCommsError: Account::get(): received invalid metadata from provider: last_modified_time: "
1083+ "value \"xyz\" does not parse as ISO-8601 date", j->error().errorString());
1084+}
1085+
1086+TEST_F(MetadataTest, missing_timezone)
1087+{
1088+ set_provider(unique_ptr<provider::ProviderBase>(new MockProvider("missing_timezone")));
1089+
1090+ unique_ptr<ItemJob> j(acc_.get("child_id"));
1091+
1092+ QSignalSpy spy(j.get(), &ItemJob::statusChanged);
1093+ spy.wait(SIGNAL_WAIT_TIME);
1094+
1095+ EXPECT_EQ(ItemJob::Status::Error, j->status());
1096+ EXPECT_EQ("LocalCommsError: Account::get(): received invalid metadata from provider: last_modified_time: "
1097+ "value \"2007-04-05T14:30\" lacks a time zone specification", j->error().errorString());
1098+}
1099+
1100+TEST_F(MetadataTest, wrong_type_for_size)
1101+{
1102+ set_provider(unique_ptr<provider::ProviderBase>(new MockProvider("wrong_type_for_size")));
1103+
1104+ unique_ptr<ItemJob> j(acc_.get("child_id"));
1105+
1106+ QSignalSpy spy(j.get(), &ItemJob::statusChanged);
1107+ spy.wait(SIGNAL_WAIT_TIME);
1108+
1109+ EXPECT_EQ(ItemJob::Status::Error, j->status());
1110+ EXPECT_EQ("LocalCommsError: Account::get(): received invalid metadata from provider: size_in_bytes: "
1111+ "expected value of type qlonglong, but received value of type QString", j->error().errorString());
1112+}
1113+
1114+TEST_F(MetadataTest, negative_size)
1115+{
1116+ set_provider(unique_ptr<provider::ProviderBase>(new MockProvider("negative_size")));
1117+
1118+ unique_ptr<ItemJob> j(acc_.get("child_id"));
1119+
1120+ QSignalSpy spy(j.get(), &ItemJob::statusChanged);
1121+ spy.wait(SIGNAL_WAIT_TIME);
1122+
1123+ EXPECT_EQ(ItemJob::Status::Error, j->status());
1124+ EXPECT_EQ("LocalCommsError: Account::get(): received invalid metadata from provider: size_in_bytes: "
1125+ "expected value >= 0, but received -1", j->error().errorString());
1126+}
1127+
1128 TEST_F(DeleteTest, basic)
1129 {
1130 set_provider(unique_ptr<provider::ProviderBase>(new MockProvider));
1131@@ -817,6 +1017,8 @@
1132 EXPECT_EQ("", i.name());
1133 EXPECT_EQ("", i.etag());
1134 EXPECT_EQ(Item::Type::File, i.type());
1135+ EXPECT_EQ(0, i.metadata().size());
1136+ EXPECT_EQ(0, i.sizeInBytes());
1137 auto mtime = i.lastModifiedTime();
1138 EXPECT_FALSE(mtime.isValid());
1139 auto pids = i.parentIds();
1140@@ -834,6 +1036,7 @@
1141 EXPECT_TRUE(i.isValid());
1142 EXPECT_EQ("child_id", i.itemId());
1143 EXPECT_EQ("Child", i.name());
1144+ EXPECT_EQ(10, i.sizeInBytes());
1145 EXPECT_TRUE(i.account().isValid());
1146 EXPECT_EQ("etag", i.etag());
1147 EXPECT_EQ(Item::Type::File, i.type());

Subscribers

People subscribed via source and target branches

to all changes: