Merge lp:~michihenning/storage-framework/parents into lp:storage-framework/devel

Proposed by Michi Henning
Status: Merged
Approved by: Michi Henning
Approved revision: 92
Merged at revision: 74
Proposed branch: lp:~michihenning/storage-framework/parents
Merge into: lp:storage-framework/devel
Prerequisite: lp:~michihenning/storage-framework/more-tests
Diff against target: 1841 lines (+1086/-153)
26 files modified
include/unity/storage/internal/ItemMetadata.h (+1/-3)
include/unity/storage/qt/Item.h (+2/-2)
include/unity/storage/qt/ItemListJob.h (+8/-4)
include/unity/storage/qt/internal/Handler.h (+3/-2)
include/unity/storage/qt/internal/ItemImpl.h (+1/-2)
include/unity/storage/qt/internal/ItemJobImpl.h (+5/-5)
include/unity/storage/qt/internal/ItemListJobImpl.h (+8/-20)
include/unity/storage/qt/internal/ListJobImplBase.h (+76/-0)
include/unity/storage/qt/internal/MultiItemJobImpl.h (+68/-0)
include/unity/storage/qt/internal/StorageErrorImpl.h (+2/-0)
include/unity/storage/qt/internal/VoidJobImpl.h (+4/-4)
src/qt/CMakeLists.txt (+4/-0)
src/qt/Item.cpp (+2/-9)
src/qt/ItemListJob.cpp (+2/-1)
src/qt/client/internal/remote_client/ItemImpl.cpp (+8/-1)
src/qt/internal/AccountImpl.cpp (+14/-4)
src/qt/internal/ItemImpl.cpp (+55/-8)
src/qt/internal/ItemJobImpl.cpp (+5/-5)
src/qt/internal/ItemListJobImpl.cpp (+9/-45)
src/qt/internal/ListJobImplBase.cpp (+111/-0)
src/qt/internal/MultiItemJobImpl.cpp (+135/-0)
src/qt/internal/StorageErrorImpl.cpp (+37/-24)
src/qt/internal/VoidJobImpl.cpp (+4/-4)
tests/provider-ProviderInterface/ProviderInterface_test.cpp (+7/-7)
tests/remote-client/MockProvider.cpp (+43/-2)
tests/remote-client/remote-client_test.cpp (+472/-1)
To merge this branch: bzr merge lp:~michihenning/storage-framework/parents
Reviewer Review Type Date Requested Status
unity-api-1-bot continuous-integration Approve
James Henstridge Approve
Review via email: mp+306986@code.launchpad.net

Commit message

Changed ItemMetadata for parent_ids to QList (from QVector).
Added parents() implementation.
Refactored ItemListJobImpl and MultiItemJobImpl to use a base class that does most of the work, so we can create an ItemListJob from both a single invocation that returns a list, and from multiple invocations that return a single item each.
Minor renaming of the make_* factory methods for brevity.
A few minor bug fixes here and there.
Lots more tests.

Description of the change

Changed ItemMetadata for parent_ids to QList (from QVector).
Added parents() implementation.
Refactored ItemListJobImpl and MultiItemJobImpl to use a base class that does most of the work, so we can create an ItemListJob from both a single invocation that returns a list, and from multiple invocations that return a single item each.
Minor renaming of the make_* factory methods for brevity.
A few minor bug fixes here and there.
Lots more tests.

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

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

review: Approve (continuous-integration)
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/136/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build/806
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/812
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/618
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/618/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/618
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/618/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/618
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/618/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/618
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/618/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/618
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/618/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/618
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/618/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/618
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/618/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/618
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/618/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/618
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/618/artifact/output/*zip*/output.zip

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

review: Approve (continuous-integration)
Revision history for this message
James Henstridge (jamesh) wrote :

This looks good. I've left a few minor comments that are mostly stylistic in nature rather than issues with the branch. So I'm approving the branch but will leave it up to you to top-approve when you're ready.

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

Thanks for the review! I've commented in-line.

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

Thanks for the review! I've commented in-line.

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/142/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build/847
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/854
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/661
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/661/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/661
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/661/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/661
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/661/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/661
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/661/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/661
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/661/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/661
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/661/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/661
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/661/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/661
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/661/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/661
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/661/artifact/output/*zip*/output.zip

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

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

FAILED: Autolanding.
Unapproved changes made after approval.
https://jenkins.canonical.com/unity-api-1/job/lp-storage-framework-autoland/49/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build/848
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/855
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/662
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/662/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/662
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/662/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/662
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/662/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/662
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/662/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/662
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/662/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/662
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/662/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/662
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/662/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/662
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/662/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/662
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/662/artifact/output/*zip*/output.zip

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'include/unity/storage/internal/ItemMetadata.h'
2--- include/unity/storage/internal/ItemMetadata.h 2016-09-16 06:25:08 +0000
3+++ include/unity/storage/internal/ItemMetadata.h 2016-10-10 01:18:55 +0000
4@@ -24,9 +24,7 @@
5 #pragma GCC diagnostic ignored "-Wcast-align"
6 #pragma GCC diagnostic ignored "-Wctor-dtor-privacy"
7 #pragma GCC diagnostic ignored "-Wswitch-default"
8-#include <QMap>
9 #include <QVariant>
10-#include <QVector>
11 #pragma GCC diagnostic pop
12
13 namespace unity
14@@ -39,7 +37,7 @@
15 struct ItemMetadata
16 {
17 QString item_id;
18- QVector<QString> parent_ids;
19+ QList<QString> parent_ids;
20 QString name;
21 QString etag;
22 ItemType type;
23
24=== modified file 'include/unity/storage/qt/Item.h'
25--- include/unity/storage/qt/Item.h 2016-09-26 08:57:05 +0000
26+++ include/unity/storage/qt/Item.h 2016-10-10 01:18:55 +0000
27@@ -60,7 +60,7 @@
28 Q_PROPERTY(unity::storage::qt::Item::Type type READ type FINAL)
29 Q_PROPERTY(QVariantMap metadata READ metadata FINAL)
30 Q_PROPERTY(QDateTime lastModifiedTime READ lastModifiedTime FINAL)
31- Q_PROPERTY(QVector<QString> parentIds READ parentIds FINAL)
32+ Q_PROPERTY(QList<QString> parentIds READ parentIds FINAL)
33
34 public:
35 Item();
36@@ -93,7 +93,7 @@
37 Type type() const;
38 QVariantMap metadata() const;
39 QDateTime lastModifiedTime() const;
40- QVector<QString> parentIds() const; // TODO: should be QList
41+ QList<QString> parentIds() const;
42
43 Q_INVOKABLE ItemListJob* parents() const;
44 Q_INVOKABLE ItemJob* copy(Item const& newParent, QString const& newName) const;
45
46=== modified file 'include/unity/storage/qt/ItemListJob.h'
47--- include/unity/storage/qt/ItemListJob.h 2016-09-26 02:12:30 +0000
48+++ include/unity/storage/qt/ItemListJob.h 2016-10-10 01:18:55 +0000
49@@ -34,7 +34,9 @@
50 namespace internal
51 {
52
53+class ListJobImplBase;
54 class ItemListJobImpl;
55+class MultiItemJobImpl;
56
57 } // namespace internal
58
59@@ -63,11 +65,13 @@
60 void itemsReady(QList<unity::storage::qt::Item> const& items) const;
61
62 private:
63- ItemListJob(std::unique_ptr<internal::ItemListJobImpl> p);
64-
65- std::unique_ptr<internal::ItemListJobImpl> const p_;
66-
67+ ItemListJob(std::unique_ptr<internal::ListJobImplBase> p);
68+
69+ std::unique_ptr<internal::ListJobImplBase> const p_;
70+
71+ friend class internal::ListJobImplBase;
72 friend class internal::ItemListJobImpl;
73+ friend class internal::MultiItemJobImpl;
74 };
75
76 } // namespace qt
77
78=== modified file 'include/unity/storage/qt/internal/Handler.h'
79--- include/unity/storage/qt/internal/Handler.h 2016-09-16 06:25:08 +0000
80+++ include/unity/storage/qt/internal/Handler.h 2016-10-10 01:18:55 +0000
81@@ -58,7 +58,7 @@
82 {
83 // LCOV_EXCL_START
84 QString msg = "impossible provider exception: " + e.errorString();
85- qCritical() << msg;
86+ qCritical().noquote() << msg;
87 e = StorageErrorImpl::local_comms_error(msg);
88 break;
89 // LCOV_EXCL_STOP
90@@ -68,7 +68,8 @@
91 case StorageError::ResourceError:
92 {
93 // Log these errors because they are unexpected.
94- qCritical() << "provider exception:" << e.errorString();
95+ QString msg = "provider exception: " + e.errorString();
96+ qCritical().noquote() << msg;
97 break;
98 }
99 default:
100
101=== modified file 'include/unity/storage/qt/internal/ItemImpl.h'
102--- include/unity/storage/qt/internal/ItemImpl.h 2016-09-26 08:57:05 +0000
103+++ include/unity/storage/qt/internal/ItemImpl.h 2016-10-10 01:18:55 +0000
104@@ -53,7 +53,7 @@
105 Item::Type type() const;
106 QVariantMap metadata() const;
107 QDateTime lastModifiedTime() const;
108- QVector<QString> parentIds() const;
109+ QList<QString> parentIds() const;
110
111 ItemListJob* parents() const;
112 ItemJob* copy(Item const& newParent, QString const& newName) const;
113@@ -89,7 +89,6 @@
114 bool is_valid_;
115 storage::internal::ItemMetadata md_;
116 std::shared_ptr<AccountImpl> account_;
117- //std::shared_ptr<RootImpl> root_;
118
119 friend class unity::storage::qt::Item;
120 };
121
122=== modified file 'include/unity/storage/qt/internal/ItemJobImpl.h'
123--- include/unity/storage/qt/internal/ItemJobImpl.h 2016-09-26 08:57:05 +0000
124+++ include/unity/storage/qt/internal/ItemJobImpl.h 2016-10-10 01:18:55 +0000
125@@ -53,11 +53,11 @@
126 StorageError error() const;
127 Item item() const;
128
129- static ItemJob* make_item_job(std::shared_ptr<AccountImpl> const& account,
130- QString const& method,
131- QDBusPendingReply<storage::internal::ItemMetadata> const& reply,
132- std::function<void(storage::internal::ItemMetadata const&)> const& validate);
133- static ItemJob* make_item_job(StorageError const& e);
134+ static ItemJob* make_job(std::shared_ptr<AccountImpl> const& account,
135+ QString const& method,
136+ QDBusPendingReply<storage::internal::ItemMetadata> const& reply,
137+ std::function<void(storage::internal::ItemMetadata const&)> const& validate);
138+ static ItemJob* make_job(StorageError const& e);
139
140 private:
141 ItemJobImpl(std::shared_ptr<AccountImpl> const& account,
142
143=== modified file 'include/unity/storage/qt/internal/ItemListJobImpl.h'
144--- include/unity/storage/qt/internal/ItemListJobImpl.h 2016-09-26 08:57:05 +0000
145+++ include/unity/storage/qt/internal/ItemListJobImpl.h 2016-10-10 01:18:55 +0000
146@@ -18,10 +18,9 @@
147
148 #pragma once
149
150+#include <unity/storage/qt/internal/ListJobImplBase.h>
151 #include <unity/storage/qt/ItemListJob.h>
152
153-#include <unity/storage/qt/StorageError.h>
154-
155 #include <QDBusPendingReply>
156
157 namespace unity
158@@ -42,35 +41,24 @@
159
160 class AccountImpl;
161
162-class ItemListJobImpl : public QObject
163+class ItemListJobImpl : public ListJobImplBase
164 {
165 Q_OBJECT
166 public:
167 virtual ~ItemListJobImpl() = default;
168
169- bool isValid() const;
170- ItemListJob::Status status() const;
171- StorageError error() const;
172-
173- static ItemListJob* make_item_list_job(std::shared_ptr<AccountImpl> const& account,
174- QString const& method,
175- QDBusPendingReply<QList<storage::internal::ItemMetadata>> const& reply,
176- std::function<void(storage::internal::ItemMetadata const&)> const& validate);
177- static ItemListJob* make_item_list_job(StorageError const& error);
178+ static ItemListJob* make_job(std::shared_ptr<AccountImpl> const& account,
179+ QString const& method,
180+ QDBusPendingReply<QList<storage::internal::ItemMetadata>> const& reply,
181+ std::function<void(storage::internal::ItemMetadata const&)> const& validate);
182+ static ItemListJob* make_job(StorageError const& error);
183
184 private:
185+ ItemListJobImpl() = default;
186 ItemListJobImpl(std::shared_ptr<AccountImpl> const& account,
187 QString const& method,
188 QDBusPendingReply<QList<storage::internal::ItemMetadata>> const& reply,
189 std::function<void(storage::internal::ItemMetadata const&)> const& validate);
190- ItemListJobImpl(StorageError const& error);
191-
192- ItemListJob* public_instance_;
193- ItemListJob::Status status_;
194- StorageError error_;
195- QString method_;
196- std::shared_ptr<AccountImpl> account_;
197- std::function<void(storage::internal::ItemMetadata const&)> validate_;
198 };
199
200 } // namespace internal
201
202=== added file 'include/unity/storage/qt/internal/ListJobImplBase.h'
203--- include/unity/storage/qt/internal/ListJobImplBase.h 1970-01-01 00:00:00 +0000
204+++ include/unity/storage/qt/internal/ListJobImplBase.h 2016-10-10 01:18:55 +0000
205@@ -0,0 +1,76 @@
206+/*
207+ * Copyright (C) 2016 Canonical Ltd
208+ *
209+ * This program is free software: you can redistribute it and/or modify
210+ * it under the terms of the GNU Lesser General Public License version 3 as
211+ * published by the Free Software Foundation.
212+ *
213+ * This program is distributed in the hope that it will be useful,
214+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
215+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
216+ * GNU Lesser General Public License for more details.
217+ *
218+ * You should have received a copy of the GNU Lesser General Public License
219+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
220+ *
221+ * Authors: Michi Henning <michi.henning@canonical.com>
222+ */
223+
224+#pragma once
225+
226+#include <unity/storage/qt/ItemListJob.h>
227+#include <unity/storage/qt/StorageError.h>
228+
229+#include <QDBusPendingReply>
230+
231+namespace unity
232+{
233+namespace storage
234+{
235+namespace internal
236+{
237+
238+class ItemMetadata;
239+
240+} // namespace internal
241+
242+namespace qt
243+{
244+namespace internal
245+{
246+
247+class AccountImpl;
248+class MultiItemJobImpl;
249+
250+class ListJobImplBase : public QObject
251+{
252+public:
253+ ListJobImplBase(); // Makes job in Finished state.
254+ ListJobImplBase(std::shared_ptr<AccountImpl> const& account,
255+ QString const& method,
256+ std::function<void(storage::internal::ItemMetadata const&)> const& validate);
257+ ListJobImplBase(StorageError const& error);
258+ virtual ~ListJobImplBase() = default;
259+
260+ bool isValid() const;
261+ ItemListJob::Status status() const;
262+ StorageError error() const;
263+
264+ void set_public_instance(ItemListJob* p);
265+
266+ static ItemListJob* make_job(StorageError const& error);
267+ static ItemListJob* make_empty_job();
268+
269+protected:
270+ ItemListJob* public_instance_;
271+ ItemListJob::Status status_;
272+ StorageError error_;
273+ QString method_;
274+ std::shared_ptr<AccountImpl> account_;
275+ std::function<void(storage::internal::ItemMetadata const&)> validate_;
276+};
277+
278+} // namespace internal
279+} // namespace qt
280+} // namespace storage
281+} // namespace unity
282
283=== added file 'include/unity/storage/qt/internal/MultiItemJobImpl.h'
284--- include/unity/storage/qt/internal/MultiItemJobImpl.h 1970-01-01 00:00:00 +0000
285+++ include/unity/storage/qt/internal/MultiItemJobImpl.h 2016-10-10 01:18:55 +0000
286@@ -0,0 +1,68 @@
287+/*
288+ * Copyright (C) 2016 Canonical Ltd
289+ *
290+ * This program is free software: you can redistribute it and/or modify
291+ * it under the terms of the GNU Lesser General Public License version 3 as
292+ * published by the Free Software Foundation.
293+ *
294+ * This program is distributed in the hope that it will be useful,
295+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
296+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
297+ * GNU Lesser General Public License for more details.
298+ *
299+ * You should have received a copy of the GNU Lesser General Public License
300+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
301+ *
302+ * Authors: Michi Henning <michi.henning@canonical.com>
303+ */
304+
305+#pragma once
306+
307+#include <unity/storage/qt/internal/ListJobImplBase.h>
308+#include <unity/storage/qt/ItemListJob.h>
309+
310+#include <QDBusPendingReply>
311+
312+namespace unity
313+{
314+namespace storage
315+{
316+namespace internal
317+{
318+
319+class ItemMetadata;
320+
321+} // namespace internal
322+
323+namespace qt
324+{
325+namespace internal
326+{
327+
328+class AccountImpl;
329+
330+class MultiItemJobImpl : public ListJobImplBase
331+{
332+ Q_OBJECT
333+public:
334+ virtual ~MultiItemJobImpl() = default;
335+
336+ static ItemListJob* make_job(std::shared_ptr<AccountImpl> const& account,
337+ QString const& method,
338+ QList<QDBusPendingReply<storage::internal::ItemMetadata>> const& replies,
339+ std::function<void(storage::internal::ItemMetadata const&)> const& validate);
340+
341+private:
342+ MultiItemJobImpl() = default;
343+ MultiItemJobImpl(std::shared_ptr<AccountImpl> const& account,
344+ QString const& method,
345+ QList<QDBusPendingReply<storage::internal::ItemMetadata>> const& replies,
346+ std::function<void(storage::internal::ItemMetadata const&)> const& validate);
347+
348+ int replies_remaining_;
349+};
350+
351+} // namespace internal
352+} // namespace qt
353+} // namespace storage
354+} // namespace unity
355
356=== modified file 'include/unity/storage/qt/internal/StorageErrorImpl.h'
357--- include/unity/storage/qt/internal/StorageErrorImpl.h 2016-09-16 06:25:08 +0000
358+++ include/unity/storage/qt/internal/StorageErrorImpl.h 2016-10-10 01:18:55 +0000
359@@ -70,6 +70,8 @@
360 static StorageError resource_error(QString const& msg, int error_code);
361
362 private:
363+ StorageErrorImpl(StorageError::Type type);
364+
365 StorageError::Type type_;
366 QString name_;
367 QString message_;
368
369=== modified file 'include/unity/storage/qt/internal/VoidJobImpl.h'
370--- include/unity/storage/qt/internal/VoidJobImpl.h 2016-09-26 22:22:09 +0000
371+++ include/unity/storage/qt/internal/VoidJobImpl.h 2016-10-10 01:18:55 +0000
372@@ -45,10 +45,10 @@
373 VoidJob::Status status() const;
374 StorageError error() const;
375
376- static VoidJob* make_void_job(std::shared_ptr<ItemImpl> const& item,
377- QString const& method,
378- QDBusPendingReply<void> const& reply);
379- static VoidJob* make_void_job(StorageError const& e);
380+ static VoidJob* make_job(std::shared_ptr<ItemImpl> const& item,
381+ QString const& method,
382+ QDBusPendingReply<void> const& reply);
383+ static VoidJob* make_job(StorageError const& e);
384
385 private:
386 VoidJobImpl(std::shared_ptr<ItemImpl> const& item,
387
388=== modified file 'src/qt/CMakeLists.txt'
389--- src/qt/CMakeLists.txt 2016-09-26 08:57:05 +0000
390+++ src/qt/CMakeLists.txt 2016-10-10 01:18:55 +0000
391@@ -30,6 +30,8 @@
392 internal/ItemImpl.cpp
393 internal/ItemJobImpl.cpp
394 internal/ItemListJobImpl.cpp
395+ internal/ListJobImplBase.cpp
396+ internal/MultiItemJobImpl.cpp
397 internal/RuntimeImpl.cpp
398 internal/StorageErrorImpl.cpp
399 internal/unmarshal_error.cpp
400@@ -46,6 +48,8 @@
401 ${CMAKE_SOURCE_DIR}/include/unity/storage/qt/internal/HandlerBase.h
402 ${CMAKE_SOURCE_DIR}/include/unity/storage/qt/internal/ItemJobImpl.h
403 ${CMAKE_SOURCE_DIR}/include/unity/storage/qt/internal/ItemListJobImpl.h
404+ ${CMAKE_SOURCE_DIR}/include/unity/storage/qt/internal/ListJobImplBase.h
405+ ${CMAKE_SOURCE_DIR}/include/unity/storage/qt/internal/MultiItemJobImpl.h
406 ${CMAKE_SOURCE_DIR}/include/unity/storage/qt/internal/VoidJobImpl.h
407 )
408
409
410=== modified file 'src/qt/Item.cpp'
411--- src/qt/Item.cpp 2016-09-22 01:52:20 +0000
412+++ src/qt/Item.cpp 2016-10-10 01:18:55 +0000
413@@ -48,6 +48,7 @@
414 }
415
416 Item::Item(Item&& other)
417+ : p_(make_shared<internal::ItemImpl>())
418 {
419 p_->is_valid_ = false;
420 swap(p_, other.p_);
421@@ -92,14 +93,6 @@
422 return p_->account();
423 }
424
425-#if 0
426-Item Item::root() const
427-{
428-
429- return p_->root();
430-}
431-#endif
432-
433 QString Item::etag() const
434 {
435 return p_->etag();
436@@ -120,7 +113,7 @@
437 return p_->lastModifiedTime();
438 }
439
440-QVector<QString> Item::parentIds() const
441+QList<QString> Item::parentIds() const
442 {
443 return p_->parentIds();
444 }
445
446=== modified file 'src/qt/ItemListJob.cpp'
447--- src/qt/ItemListJob.cpp 2016-09-16 06:25:08 +0000
448+++ src/qt/ItemListJob.cpp 2016-10-10 01:18:55 +0000
449@@ -19,6 +19,7 @@
450 #include <unity/storage/qt/ItemListJob.h>
451
452 #include <unity/storage/qt/internal/ItemListJobImpl.h>
453+#include <unity/storage/qt/internal/MultiItemJobImpl.h>
454
455 using namespace unity::storage::qt;
456 using namespace std;
457@@ -30,7 +31,7 @@
458 namespace qt
459 {
460
461-ItemListJob::ItemListJob(unique_ptr<internal::ItemListJobImpl> p)
462+ItemListJob::ItemListJob(unique_ptr<internal::ListJobImplBase> p)
463 : p_(move(p))
464 {
465 }
466
467=== modified file 'src/qt/client/internal/remote_client/ItemImpl.cpp'
468--- src/qt/client/internal/remote_client/ItemImpl.cpp 2016-08-26 04:45:56 +0000
469+++ src/qt/client/internal/remote_client/ItemImpl.cpp 2016-10-10 01:18:55 +0000
470@@ -217,7 +217,14 @@
471 {
472 throw_if_destroyed("Item::parent_ids()");
473 // TODO, need different metadata representation, affects xml
474- return md_.parent_ids;
475+ // We changed ItemMetadata to contain a QList for the v2 API,
476+ // so we copy here.
477+ QVector<QString> ids;
478+ for (auto const& id : md_.parent_ids)
479+ {
480+ ids.append(id);
481+ }
482+ return ids;
483 }
484
485 QFuture<void> ItemImpl::delete_item()
486
487=== modified file 'src/qt/internal/AccountImpl.cpp'
488--- src/qt/internal/AccountImpl.cpp 2016-09-26 08:57:05 +0000
489+++ src/qt/internal/AccountImpl.cpp 2016-10-10 01:18:55 +0000
490@@ -86,10 +86,15 @@
491 QString const method = "Account::roots()";
492
493 auto runtime = runtime_.lock();
494+ if (!is_valid_)
495+ {
496+ auto e = StorageErrorImpl::logic_error(method + ": cannot create job from invalid account");
497+ return ItemListJobImpl::make_job(e);
498+ }
499 if (!runtime || !runtime->isValid())
500 {
501 auto e = StorageErrorImpl::runtime_destroyed_error(method + ": Runtime was destroyed previously");
502- return ItemListJobImpl::make_item_list_job(e);
503+ return ItemListJobImpl::make_job(e);
504 }
505
506 auto validate = [method](storage::internal::ItemMetadata const& md)
507@@ -104,18 +109,23 @@
508
509 auto reply = provider_->Roots();
510 auto This = const_pointer_cast<AccountImpl>(shared_from_this());
511- return ItemListJobImpl::make_item_list_job(This, method, reply, validate);
512+ return ItemListJobImpl::make_job(This, method, reply, validate);
513 }
514
515 ItemJob* AccountImpl::get(QString const& itemId) const
516 {
517 QString const method = "Account::get()";
518
519+ if (!is_valid_)
520+ {
521+ auto e = StorageErrorImpl::logic_error(method + ": cannot create job from invalid account");
522+ return ItemJobImpl::make_job(e);
523+ }
524 auto runtime = runtime_.lock();
525 if (!runtime || !runtime->isValid())
526 {
527 auto e = StorageErrorImpl::runtime_destroyed_error(method + ": Runtime was destroyed previously");
528- return ItemJobImpl::make_item_job(e);
529+ return ItemJobImpl::make_job(e);
530 }
531
532 // LCOV_EXCL_START
533@@ -126,7 +136,7 @@
534
535 auto reply = provider_->Metadata(itemId);
536 auto This = const_pointer_cast<AccountImpl>(shared_from_this());
537- return ItemJobImpl::make_item_job(This, method, reply, validate);
538+ return ItemJobImpl::make_job(This, method, reply, validate);
539 }
540
541 bool AccountImpl::operator==(AccountImpl const& other) const
542
543=== modified file 'src/qt/internal/ItemImpl.cpp'
544--- src/qt/internal/ItemImpl.cpp 2016-09-26 08:57:05 +0000
545+++ src/qt/internal/ItemImpl.cpp 2016-10-10 01:18:55 +0000
546@@ -22,6 +22,8 @@
547 #include <unity/storage/provider/metadata_keys.h>
548 #include <unity/storage/qt/internal/AccountImpl.h>
549 #include <unity/storage/qt/internal/ItemJobImpl.h>
550+#include <unity/storage/qt/internal/ItemListJobImpl.h>
551+#include <unity/storage/qt/internal/MultiItemJobImpl.h>
552 #include <unity/storage/qt/internal/RuntimeImpl.h>
553 #include <unity/storage/qt/internal/StorageErrorImpl.h>
554 #include <unity/storage/qt/internal/VoidJobImpl.h>
555@@ -104,14 +106,56 @@
556 : QDateTime();
557 }
558
559-QVector<QString> ItemImpl::parentIds() const
560+QList<QString> ItemImpl::parentIds() const
561 {
562- return is_valid_ ? md_.parent_ids : QVector<QString>();
563+ if (!is_valid_ || md_.type == storage::ItemType::root)
564+ {
565+ return QList<QString>();
566+ }
567+ return md_.parent_ids;
568 }
569
570 ItemListJob* ItemImpl::parents() const
571 {
572- return nullptr; // TODO
573+ QString const method = "Item::parents()";
574+
575+ if (!is_valid_)
576+ {
577+ auto e = StorageErrorImpl::logic_error(method + ": cannot create job from invalid item");
578+ return ListJobImplBase::make_job(e);
579+ }
580+ auto runtime = account_->runtime();
581+ if (!runtime || !runtime->isValid())
582+ {
583+ auto e = StorageErrorImpl::runtime_destroyed_error(method + ": Runtime was destroyed previously");
584+ return ListJobImplBase::make_job(e);
585+ }
586+
587+ if (md_.type == storage::ItemType::root)
588+ {
589+ return ListJobImplBase::make_empty_job(); // Root has no parents.
590+ }
591+
592+ assert(!md_.parent_ids.isEmpty());
593+
594+ QList<QDBusPendingReply<storage::internal::ItemMetadata>> replies;
595+ for (auto const& id : md_.parent_ids)
596+ {
597+ auto reply = account_->provider()->Metadata(id);
598+ replies.append(reply);
599+ }
600+
601+ auto validate = [method](storage::internal::ItemMetadata const& md)
602+ {
603+ if (md.type == ItemType::file)
604+ {
605+ QString msg = method + ": provider returned a file as a parent";
606+ qCritical() << msg;
607+ throw StorageErrorImpl::local_comms_error(msg);
608+ }
609+ };
610+
611+ return MultiItemJobImpl::make_job(account_, method, replies, validate);
612 }
613
614 ItemJob* ItemImpl::copy(Item const& newParent, QString const& newName) const
615@@ -128,23 +172,26 @@
616 {
617 QString const method = "Item::deleteItem()";
618
619- assert(account_);
620+ if (!is_valid_)
621+ {
622+ auto e = StorageErrorImpl::logic_error(method + ": cannot create job from invalid item");
623+ return VoidJobImpl::make_job(e);
624+ }
625 auto runtime = account_->runtime();
626 if (!runtime || !runtime->isValid())
627 {
628 auto e = StorageErrorImpl::runtime_destroyed_error(method + ": Runtime was destroyed previously");
629- return VoidJobImpl::make_void_job(e);
630+ return VoidJobImpl::make_job(e);
631 }
632-
633 if (md_.type == storage::ItemType::root)
634 {
635 auto e = StorageErrorImpl::logic_error(method + ": cannot delete root");
636- return VoidJobImpl::make_void_job(e);
637+ return VoidJobImpl::make_job(e);
638 }
639
640 auto reply = account_->provider()->Delete(md_.item_id);
641 auto This = const_pointer_cast<ItemImpl>(shared_from_this());
642- return VoidJobImpl::make_void_job(This, method, reply);
643+ return VoidJobImpl::make_job(This, method, reply);
644 }
645
646 Uploader* ItemImpl::createUploader(Item::ConflictPolicy policy, qint64 sizeInBytes) const
647
648=== modified file 'src/qt/internal/ItemJobImpl.cpp'
649--- src/qt/internal/ItemJobImpl.cpp 2016-09-26 08:57:05 +0000
650+++ src/qt/internal/ItemJobImpl.cpp 2016-10-10 01:18:55 +0000
651@@ -112,10 +112,10 @@
652 return item_;
653 }
654
655-ItemJob* ItemJobImpl::make_item_job(shared_ptr<AccountImpl> const& account,
656- QString const& method,
657- QDBusPendingReply<storage::internal::ItemMetadata> const& reply,
658- std::function<void(storage::internal::ItemMetadata const&)> const& validate)
659+ItemJob* ItemJobImpl::make_job(shared_ptr<AccountImpl> const& account,
660+ QString const& method,
661+ QDBusPendingReply<storage::internal::ItemMetadata> const& reply,
662+ std::function<void(storage::internal::ItemMetadata const&)> const& validate)
663 {
664 unique_ptr<ItemJobImpl> impl(new ItemJobImpl(account, method, reply, validate));
665 auto job = new ItemJob(move(impl));
666@@ -123,7 +123,7 @@
667 return job;
668 }
669
670-ItemJob* ItemJobImpl::make_item_job(StorageError const& error)
671+ItemJob* ItemJobImpl::make_job(StorageError const& error)
672 {
673 unique_ptr<ItemJobImpl> impl(new ItemJobImpl(error));
674 auto job = new ItemJob(move(impl));
675
676=== modified file 'src/qt/internal/ItemListJobImpl.cpp'
677--- src/qt/internal/ItemListJobImpl.cpp 2016-09-26 08:57:05 +0000
678+++ src/qt/internal/ItemListJobImpl.cpp 2016-10-10 01:18:55 +0000
679@@ -19,7 +19,6 @@
680 #include <unity/storage/qt/internal/ItemListJobImpl.h>
681
682 #include <unity/storage/internal/dbusmarshal.h>
683-#include <unity/storage/internal/ItemMetadata.h>
684 #include <unity/storage/qt/internal/AccountImpl.h>
685 #include <unity/storage/qt/internal/Handler.h>
686 #include <unity/storage/qt/internal/ItemImpl.h>
687@@ -40,15 +39,8 @@
688 QString const& method,
689 QDBusPendingReply<QList<storage::internal::ItemMetadata>> const& reply,
690 std::function<void(storage::internal::ItemMetadata const&)> const& validate)
691- : status_(ItemListJob::Loading)
692- , method_(method)
693- , account_(account)
694- , validate_(validate)
695+ : ListJobImplBase(account, method, validate)
696 {
697- assert(!method.isEmpty());
698- assert(account);
699- assert(validate);
700-
701 auto process_reply = [this](decltype(reply)& r)
702 {
703 auto runtime = account_->runtime();
704@@ -82,6 +74,7 @@
705
706 auto process_error = [this](StorageError const& error)
707 {
708+ // TODO: method name is not being set this way.
709 error_ = error;
710 status_ = ItemListJob::Error;
711 Q_EMIT public_instance_->statusChanged(status_);
712@@ -90,49 +83,20 @@
713 new Handler<QList<storage::internal::ItemMetadata>>(this, reply, process_reply, process_error);
714 }
715
716-ItemListJobImpl::ItemListJobImpl(StorageError const& error)
717- : status_(ItemListJob::Error)
718- , error_(error)
719-{
720-}
721-
722-bool ItemListJobImpl::isValid() const
723-{
724- return status_ != ItemListJob::Status::Error;
725-}
726-
727-ItemListJob::Status ItemListJobImpl::status() const
728-{
729- return status_;
730-}
731-
732-StorageError ItemListJobImpl::error() const
733-{
734- return error_;
735-}
736-
737-ItemListJob* ItemListJobImpl::make_item_list_job(
738- shared_ptr<AccountImpl> const& account,
739- QString const& method,
740- QDBusPendingReply<QList<storage::internal::ItemMetadata>> const& reply,
741- std::function<void(storage::internal::ItemMetadata const&)> const& validate)
742+ItemListJob* ItemListJobImpl::make_job(shared_ptr<AccountImpl> const& account,
743+ QString const& method,
744+ QDBusPendingReply<QList<storage::internal::ItemMetadata>> const& reply,
745+ std::function<void(storage::internal::ItemMetadata const&)> const& validate)
746 {
747 unique_ptr<ItemListJobImpl> impl(new ItemListJobImpl(account, method, reply, validate));
748 auto job = new ItemListJob(move(impl));
749- job->p_->public_instance_ = job;
750+ job->p_->set_public_instance(job);
751 return job;
752 }
753
754-ItemListJob* ItemListJobImpl::make_item_list_job(StorageError const& error)
755+ItemListJob* ItemListJobImpl::make_job(StorageError const& error)
756 {
757- unique_ptr<ItemListJobImpl> impl(new ItemListJobImpl(error));
758- auto job = new ItemListJob(move(impl));
759- job->p_->public_instance_ = job;
760- QMetaObject::invokeMethod(job,
761- "statusChanged",
762- Qt::QueuedConnection,
763- Q_ARG(unity::storage::qt::ItemListJob::Status, job->p_->status_));
764- return job;
765+ return ListJobImplBase::make_job(error);
766 }
767
768 } // namespace internal
769
770=== added file 'src/qt/internal/ListJobImplBase.cpp'
771--- src/qt/internal/ListJobImplBase.cpp 1970-01-01 00:00:00 +0000
772+++ src/qt/internal/ListJobImplBase.cpp 2016-10-10 01:18:55 +0000
773@@ -0,0 +1,111 @@
774+/*
775+ * Copyright (C) 2016 Canonical Ltd
776+ *
777+ * This program is free software: you can redistribute it and/or modify
778+ * it under the terms of the GNU Lesser General Public License version 3 as
779+ * published by the Free Software Foundation.
780+ *
781+ * This program is distributed in the hope that it will be useful,
782+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
783+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
784+ * GNU Lesser General Public License for more details.
785+ *
786+ * You should have received a copy of the GNU Lesser General Public License
787+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
788+ *
789+ * Authors: Michi Henning <michi.henning@canonical.com>
790+ */
791+
792+#include <unity/storage/qt/internal/ListJobImplBase.h>
793+
794+#include <unity/storage/internal/dbusmarshal.h>
795+#include <unity/storage/internal/ItemMetadata.h>
796+#include <unity/storage/qt/internal/AccountImpl.h>
797+#include <unity/storage/qt/internal/Handler.h>
798+#include <unity/storage/qt/internal/ItemImpl.h>
799+#include <unity/storage/qt/internal/RuntimeImpl.h>
800+
801+using namespace std;
802+
803+namespace unity
804+{
805+namespace storage
806+{
807+namespace qt
808+{
809+namespace internal
810+{
811+
812+ListJobImplBase::ListJobImplBase()
813+ : status_(ItemListJob::Finished)
814+{
815+}
816+
817+ListJobImplBase::ListJobImplBase(shared_ptr<AccountImpl> const& account,
818+ QString const& method,
819+ std::function<void(storage::internal::ItemMetadata const&)> const& validate)
820+ : status_(ItemListJob::Loading)
821+ , method_(method)
822+ , account_(account)
823+ , validate_(validate)
824+{
825+ assert(!method.isEmpty());
826+ assert(account);
827+ assert(validate);
828+}
829+
830+ListJobImplBase::ListJobImplBase(StorageError const& error)
831+ : status_(ItemListJob::Error)
832+ , error_(error)
833+{
834+}
835+
836+bool ListJobImplBase::isValid() const
837+{
838+ return status_ != ItemListJob::Status::Error;
839+}
840+
841+ItemListJob::Status ListJobImplBase::status() const
842+{
843+ return status_;
844+}
845+
846+StorageError ListJobImplBase::error() const
847+{
848+ return error_;
849+}
850+
851+void ListJobImplBase::set_public_instance(ItemListJob* p)
852+{
853+ assert(p);
854+ public_instance_ = p;
855+}
856+
857+ItemListJob* ListJobImplBase::make_job(StorageError const& error)
858+{
859+ unique_ptr<ListJobImplBase> impl(new ListJobImplBase(error));
860+ auto job = new ItemListJob(move(impl));
861+ job->p_->public_instance_ = job;
862+ QMetaObject::invokeMethod(job,
863+ "statusChanged",
864+ Qt::QueuedConnection,
865+ Q_ARG(unity::storage::qt::ItemListJob::Status, job->status()));
866+ return job;
867+}
868+
869+ItemListJob* ListJobImplBase::make_empty_job()
870+{
871+ unique_ptr<ListJobImplBase> impl(new ListJobImplBase());
872+ auto job = new ItemListJob(move(impl));
873+ job->p_->public_instance_ = job;
874+ QMetaObject::invokeMethod(job,
875+ "statusChanged",
876+ Qt::QueuedConnection,
877+ Q_ARG(unity::storage::qt::ItemListJob::Status, job->status()));
878+ return job;
879+}
880+
881+} // namespace internal
882+} // namespace qt
883+} // namespace storage
884+} // namespace unity
885
886=== added file 'src/qt/internal/MultiItemJobImpl.cpp'
887--- src/qt/internal/MultiItemJobImpl.cpp 1970-01-01 00:00:00 +0000
888+++ src/qt/internal/MultiItemJobImpl.cpp 2016-10-10 01:18:55 +0000
889@@ -0,0 +1,135 @@
890+/*
891+ * Copyright (C) 2016 Canonical Ltd
892+ *
893+ * This program is free software: you can redistribute it and/or modify
894+ * it under the terms of the GNU Lesser General Public License version 3 as
895+ * published by the Free Software Foundation.
896+ *
897+ * This program is distributed in the hope that it will be useful,
898+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
899+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
900+ * GNU Lesser General Public License for more details.
901+ *
902+ * You should have received a copy of the GNU Lesser General Public License
903+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
904+ *
905+ * Authors: Michi Henning <michi.henning@canonical.com>
906+ */
907+
908+#include <unity/storage/qt/internal/MultiItemJobImpl.h>
909+
910+#include <unity/storage/internal/dbusmarshal.h>
911+#include <unity/storage/qt/internal/AccountImpl.h>
912+#include <unity/storage/qt/internal/Handler.h>
913+#include <unity/storage/qt/internal/ItemImpl.h>
914+#include <unity/storage/qt/internal/RuntimeImpl.h>
915+
916+using namespace std;
917+
918+namespace unity
919+{
920+namespace storage
921+{
922+namespace qt
923+{
924+namespace internal
925+{
926+
927+MultiItemJobImpl::MultiItemJobImpl(shared_ptr<AccountImpl> const& account,
928+ QString const& method,
929+ QList<QDBusPendingReply<storage::internal::ItemMetadata>> const& replies,
930+ std::function<void(storage::internal::ItemMetadata const&)> const& validate)
931+ : ListJobImplBase(account, method, validate)
932+ , replies_remaining_(replies.size())
933+{
934+ assert(!method.isEmpty());
935+ assert(account);
936+ assert(validate);
937+
938+ // We ask the provider for the metadata for each of this item's parents.
939+ // As the replies trickle in, we track when the last reply has arrived and
940+ // signal that the job is complete.
941+ // If anything goes wrong at all, we report the first error and then ignore all
942+ // other replies.
943+
944+ auto process_reply = [this](QDBusPendingReply<storage::internal::ItemMetadata> const& r)
945+ {
946+ assert(status_ != ItemListJob::Finished);
947+
948+ --replies_remaining_;
949+
950+ if (status_ == ItemListJob::Error)
951+ {
952+ return;
953+ }
954+
955+ auto runtime = account_->runtime();
956+ if (!runtime || !runtime->isValid())
957+ {
958+ error_ = StorageErrorImpl::runtime_destroyed_error(method_ + ": Runtime was destroyed previously");
959+ status_ = ItemListJob::Error;
960+ Q_EMIT public_instance_->statusChanged(status_);
961+ return;
962+ }
963+
964+ auto metadata = r.value();
965+ Item item;
966+ try
967+ {
968+ validate_(metadata);
969+ item = ItemImpl::make_item(method_, metadata, account_);
970+ }
971+ catch (StorageError const& e)
972+ {
973+ // Bad metadata received from provider, validate_() or make_item() have logged it.
974+ status_ = ItemListJob::Error;
975+ error_ = e;
976+ Q_EMIT public_instance_->statusChanged(status_);
977+ return;
978+ }
979+ QList<Item> items;
980+ items.append(item);
981+ Q_EMIT public_instance_->itemsReady(items);
982+
983+ if (replies_remaining_ == 0)
984+ {
985+ status_ = ItemListJob::Finished;
986+ Q_EMIT public_instance_->statusChanged(status_);
987+ }
988+ };
989+
990+ auto process_error = [this](StorageError const& error)
991+ {
992+ assert(status_ != ItemListJob::Finished);
993+
994+ if (status_ == ItemListJob::Error)
995+ {
996+ return;
997+ }
998+ // TODO: method name is not being set this way.
999+ error_ = error;
1000+ status_ = ItemListJob::Error;
1001+ Q_EMIT public_instance_->statusChanged(status_);
1002+ };
1003+
1004+ for (auto const& reply : replies)
1005+ {
1006+ new Handler<storage::internal::ItemMetadata>(this, reply, process_reply, process_error);
1007+ }
1008+}
1009+
1010+ItemListJob* MultiItemJobImpl::make_job(shared_ptr<AccountImpl> const& account,
1011+ QString const& method,
1012+ QList<QDBusPendingReply<storage::internal::ItemMetadata>> const& replies,
1013+ std::function<void(storage::internal::ItemMetadata const&)> const& validate)
1014+{
1015+ unique_ptr<MultiItemJobImpl> impl(new MultiItemJobImpl(account, method, replies, validate));
1016+ auto job = new ItemListJob(move(impl));
1017+ job->p_->set_public_instance(job);
1018+ return job;
1019+}
1020+
1021+} // namespace internal
1022+} // namespace qt
1023+} // namespace storage
1024+} // namespace unity
1025
1026=== modified file 'src/qt/internal/StorageErrorImpl.cpp'
1027--- src/qt/internal/StorageErrorImpl.cpp 2016-09-16 06:25:08 +0000
1028+++ src/qt/internal/StorageErrorImpl.cpp 2016-10-10 01:18:55 +0000
1029@@ -35,27 +35,40 @@
1030 namespace
1031 {
1032
1033-static char const * const ERROR_NAMES[StorageError::__LAST_STORAGE_ERROR] =
1034+static const QString ERROR_NAMES[StorageError::__LAST_STORAGE_ERROR] =
1035 {
1036- "NoError", "LocalCommsError", "RemoteCommsError", "Deleted", "RuntimeDestroyed", "NotExists",
1037- "Exists", "Conflict", "PermissionDenied", "Cancelled", "LogicError", "InvalidArgument", "ResourceError"
1038+ QStringLiteral("NoError"),
1039+ QStringLiteral("LocalCommsError"),
1040+ QStringLiteral("RemoteCommsError"),
1041+ QStringLiteral("Deleted"),
1042+ QStringLiteral("RuntimeDestroyed"),
1043+ QStringLiteral("NotExists"),
1044+ QStringLiteral("Exists"),
1045+ QStringLiteral("Conflict"),
1046+ QStringLiteral("PermissionDenied"),
1047+ QStringLiteral("Cancelled"),
1048+ QStringLiteral("LogicError"),
1049+ QStringLiteral("InvalidArgument"),
1050+ QStringLiteral("ResourceError")
1051 };
1052
1053 } // namespace
1054
1055+StorageErrorImpl::StorageErrorImpl(StorageError::Type type)
1056+ : type_(type)
1057+ , name_(ERROR_NAMES[type_])
1058+ , error_code_(0)
1059+{
1060+}
1061+
1062 StorageErrorImpl::StorageErrorImpl()
1063- : type_(StorageError::Type::NoError)
1064- , name_(ERROR_NAMES[type_])
1065- , message_("No error")
1066- , error_code_(0)
1067+ : StorageErrorImpl(StorageError::Type::NoError)
1068 {
1069+ message_ = "No error";
1070 }
1071
1072 StorageErrorImpl::StorageErrorImpl(StorageError::Type type, QString const& msg)
1073- : type_(type)
1074- , name_(ERROR_NAMES[type_])
1075- , message_(msg)
1076- , error_code_(0)
1077+ : StorageErrorImpl(type)
1078 {
1079 assert( type == StorageError::Type::LocalCommsError
1080 || type == StorageError::Type::RemoteCommsError
1081@@ -66,18 +79,18 @@
1082 || type == StorageError::Type::LogicError
1083 || type == StorageError::Type::InvalidArgument);
1084 assert(!msg.isEmpty());
1085+
1086+ message_ = msg;
1087 }
1088
1089 StorageErrorImpl::StorageErrorImpl(StorageError::Type type, QString const& msg, QString const& key)
1090- : type_(type)
1091- , name_(ERROR_NAMES[type_])
1092- , message_(msg)
1093- , error_code_(0)
1094+ : StorageErrorImpl(type)
1095 {
1096 assert( type == StorageError::Type::Deleted
1097 || type == StorageError::Type::NotExists);
1098 assert(!msg.isEmpty());
1099
1100+ message_ = msg;
1101 item_id_ = key;
1102 if (type == StorageError::Type::NotExists)
1103 {
1104@@ -89,26 +102,26 @@
1105 QString const& msg,
1106 QString const& item_id,
1107 QString const& item_name)
1108- : type_(type)
1109- , name_(ERROR_NAMES[type_])
1110- , message_(msg)
1111- , item_id_(item_id)
1112- , item_name_(item_name)
1113- , error_code_(0)
1114+ : StorageErrorImpl(type)
1115 {
1116 assert(type == StorageError::Type::Exists);
1117 assert(!msg.isEmpty());
1118 assert(!item_id.isEmpty());
1119 assert(!item_name.isEmpty());
1120+
1121+ message_ = msg;
1122+ item_id_ = item_id;
1123+ item_name_ = item_name;
1124 }
1125
1126 StorageErrorImpl::StorageErrorImpl(StorageError::Type type, QString const& msg, int error_code)
1127- : type_(type)
1128- , message_(msg)
1129- , error_code_(error_code)
1130+ : StorageErrorImpl(type)
1131 {
1132 assert(type == StorageError::Type::ResourceError);
1133 assert(!msg.isEmpty());
1134+
1135+ message_ = msg;
1136+ error_code_ = error_code;
1137 }
1138
1139 StorageError::Type StorageErrorImpl::type() const
1140
1141=== modified file 'src/qt/internal/VoidJobImpl.cpp'
1142--- src/qt/internal/VoidJobImpl.cpp 2016-09-26 08:57:05 +0000
1143+++ src/qt/internal/VoidJobImpl.cpp 2016-10-10 01:18:55 +0000
1144@@ -89,9 +89,9 @@
1145 return error_;
1146 }
1147
1148-VoidJob* VoidJobImpl::make_void_job(shared_ptr<ItemImpl> const& item,
1149- QString const& method,
1150- QDBusPendingReply<void> const& reply)
1151+VoidJob* VoidJobImpl::make_job(shared_ptr<ItemImpl> const& item,
1152+ QString const& method,
1153+ QDBusPendingReply<void> const& reply)
1154 {
1155 unique_ptr<VoidJobImpl> impl(new VoidJobImpl(item, method, reply));
1156 auto job = new VoidJob(move(impl));
1157@@ -99,7 +99,7 @@
1158 return job;
1159 }
1160
1161-VoidJob* VoidJobImpl::make_void_job(StorageError const& error)
1162+VoidJob* VoidJobImpl::make_job(StorageError const& error)
1163 {
1164 unique_ptr<VoidJobImpl> impl(new VoidJobImpl(error));
1165 auto job = new VoidJob(move(impl));
1166
1167=== modified file 'tests/provider-ProviderInterface/ProviderInterface_test.cpp'
1168--- tests/provider-ProviderInterface/ProviderInterface_test.cpp 2016-09-26 02:37:03 +0000
1169+++ tests/provider-ProviderInterface/ProviderInterface_test.cpp 2016-10-10 01:18:55 +0000
1170@@ -91,7 +91,7 @@
1171 EXPECT_EQ(1, reply.value().size());
1172 auto root = reply.value()[0];
1173 EXPECT_EQ("root_id", root.item_id);
1174- EXPECT_EQ(QVector<QString>(), root.parent_ids);
1175+ EXPECT_EQ(QList<QString>(), root.parent_ids);
1176 EXPECT_EQ("Root", root.name);
1177 EXPECT_EQ("etag", root.etag);
1178 EXPECT_EQ(ItemType::root, root.type);
1179@@ -146,7 +146,7 @@
1180 ASSERT_EQ(1, items.size());
1181 auto item = items[0];
1182 EXPECT_EQ("child_id", item.item_id);
1183- EXPECT_EQ(QVector<QString>{ "root_id"}, item.parent_ids);
1184+ EXPECT_EQ(QList<QString>{ "root_id"}, item.parent_ids);
1185 EXPECT_EQ("Filename", item.name);
1186 EXPECT_EQ(ItemType::file, item.type);
1187 }
1188@@ -160,7 +160,7 @@
1189 ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString();
1190 auto item = reply.value();
1191 EXPECT_EQ("root_id", item.item_id);
1192- EXPECT_EQ(QVector<QString>(), item.parent_ids);
1193+ EXPECT_EQ(QList<QString>(), item.parent_ids);
1194 EXPECT_EQ("Root", item.name);
1195 EXPECT_EQ(ItemType::root, item.type);
1196 }
1197@@ -174,7 +174,7 @@
1198 ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString();
1199 auto item = reply.value();
1200 EXPECT_EQ("new_folder_id", item.item_id);
1201- EXPECT_EQ(QVector<QString>{ "root_id" }, item.parent_ids);
1202+ EXPECT_EQ(QList<QString>{ "root_id" }, item.parent_ids);
1203 EXPECT_EQ("New Folder", item.name);
1204 EXPECT_EQ(ItemType::folder, item.type);
1205 }
1206@@ -224,7 +224,7 @@
1207 ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString();
1208 auto item = reply.value();
1209 EXPECT_EQ("new_file_id", item.item_id);
1210- EXPECT_EQ(QVector<QString>{ "parent_id" }, item.parent_ids);
1211+ EXPECT_EQ(QList<QString>{ "parent_id" }, item.parent_ids);
1212 EXPECT_EQ("file name", item.name);
1213 }
1214
1215@@ -761,7 +761,7 @@
1216 ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString();
1217 auto item = reply.value();
1218 EXPECT_EQ("child_id", item.item_id);
1219- EXPECT_EQ(QVector<QString>{ "new_parent_id" }, item.parent_ids);
1220+ EXPECT_EQ(QList<QString>{ "new_parent_id" }, item.parent_ids);
1221 EXPECT_EQ("New name", item.name);
1222 EXPECT_EQ(ItemType::file, item.type);
1223 }
1224@@ -775,7 +775,7 @@
1225 ASSERT_TRUE(reply.isValid()) << reply.error().message().toStdString();
1226 auto item = reply.value();
1227 EXPECT_EQ("new_id", item.item_id);
1228- EXPECT_EQ(QVector<QString>{ "new_parent_id" }, item.parent_ids);
1229+ EXPECT_EQ(QList<QString>{ "new_parent_id" }, item.parent_ids);
1230 EXPECT_EQ("New name", item.name);
1231 EXPECT_EQ(ItemType::file, item.type);
1232 }
1233
1234=== modified file 'tests/remote-client/MockProvider.cpp'
1235--- tests/remote-client/MockProvider.cpp 2016-09-26 08:57:05 +0000
1236+++ tests/remote-client/MockProvider.cpp 2016-10-10 01:18:55 +0000
1237@@ -58,6 +58,11 @@
1238 };
1239 return make_ready_future<ItemList>(roots);
1240 }
1241+ if (cmd_ == "roots_throw")
1242+ {
1243+ string msg = "roots(): I'm sorry Dave, I'm afraid I can't do that.";
1244+ return make_exceptional_future<ItemList>(PermissionException(msg));
1245+ }
1246
1247 ItemList roots =
1248 {
1249@@ -115,6 +120,8 @@
1250
1251 boost::future<Item> MockProvider::metadata(string const& item_id, Context const&)
1252 {
1253+ static int num_calls = 0;
1254+
1255 if (cmd_ == "slow_metadata")
1256 {
1257 this_thread::sleep_for(chrono::seconds(1));
1258@@ -124,13 +131,47 @@
1259 Item metadata{"", {}, "Root", "etag", ItemType::root, {}};
1260 return make_ready_future<Item>(metadata);
1261 }
1262+ if (cmd_== "two_parents_throw")
1263+ {
1264+ ++num_calls;
1265+ switch (num_calls)
1266+ {
1267+ case 3:
1268+ return make_exceptional_future<Item>(ResourceException("metadata(): weird error", 42));
1269+ case 4:
1270+ num_calls = 0;
1271+ return make_exceptional_future<Item>(RemoteCommsException("metadata(): HTTP broken"));
1272+ default:
1273+ break;
1274+ }
1275+ }
1276 if (item_id == "root_id")
1277 {
1278+ if (cmd_ == "bad_parent_metadata_from_child")
1279+ {
1280+ ++num_calls;
1281+ if (num_calls == 2)
1282+ {
1283+ num_calls = 0;
1284+ // On second call, we return type file for the root.
1285+ Item metadata{"root_id", {}, "Root", "etag", ItemType::file, {}};
1286+ return make_ready_future<Item>(metadata);
1287+ }
1288+ }
1289 Item metadata{"root_id", {}, "Root", "etag", ItemType::root, {}};
1290 return make_ready_future<Item>(metadata);
1291 }
1292- else if (item_id == "child_id")
1293+ if (item_id == "child_id")
1294 {
1295+ if (cmd_ == "two_parents" || cmd_ == "two_parents_throw")
1296+ {
1297+ Item metadata
1298+ {
1299+ "child_id", { "root_id", "child_folder_id" }, "Child", "etag", ItemType::file,
1300+ { { SIZE_IN_BYTES, 0 }, { LAST_MODIFIED_TIME, "2007-04-05T14:30Z" } }
1301+ };
1302+ return make_ready_future<Item>(metadata);
1303+ }
1304 Item metadata
1305 {
1306 "child_id", { "root_id" }, "Child", "etag", ItemType::file,
1307@@ -138,7 +179,7 @@
1308 };
1309 return make_ready_future<Item>(metadata);
1310 }
1311- else if (item_id == "child_folder_id")
1312+ if (item_id == "child_folder_id")
1313 {
1314 Item metadata{"child_folder_id", { "root_id" }, "Child_Folder", "etag", ItemType::folder, {}};
1315 return make_ready_future<Item>(metadata);
1316
1317=== modified file 'tests/remote-client/remote-client_test.cpp'
1318--- tests/remote-client/remote-client_test.cpp 2016-09-29 13:09:00 +0000
1319+++ tests/remote-client/remote-client_test.cpp 2016-10-10 01:18:55 +0000
1320@@ -58,6 +58,7 @@
1321 class DeleteTest : public RemoteClientTest {};
1322 class GetTest : public RemoteClientTest {};
1323 class ItemTest : public RemoteClientTest {};
1324+class ParentsTest : public RemoteClientTest {};
1325 class RootsTest : public RemoteClientTest {};
1326 class RuntimeTest : public ProviderFixture {};
1327
1328@@ -397,7 +398,7 @@
1329 EXPECT_EQ("root_id", root.itemId());
1330 EXPECT_EQ("Root", root.name());
1331 EXPECT_EQ("etag", root.etag());
1332- EXPECT_EQ(QVector<QString>(), root.parentIds());
1333+ EXPECT_EQ(QList<QString>(), root.parentIds());
1334 EXPECT_FALSE(root.lastModifiedTime().isValid());
1335 EXPECT_EQ(acc_, root.account());
1336 }
1337@@ -441,6 +442,46 @@
1338 EXPECT_EQ("Account::roots(): Runtime was destroyed previously", j->error().message());
1339 }
1340
1341+TEST_F(RootsTest, invalid_account)
1342+{
1343+ Account a;
1344+ unique_ptr<ItemListJob> j(a.roots());
1345+ EXPECT_FALSE(j->isValid());
1346+ EXPECT_EQ(ItemListJob::Error, j->status());
1347+ EXPECT_EQ(StorageError::LogicError, j->error().type());
1348+ EXPECT_EQ("Account::roots(): cannot create job from invalid account", j->error().message());
1349+
1350+ // Signal must be received.
1351+ QSignalSpy spy(j.get(), &ItemListJob::statusChanged);
1352+ spy.wait(SIGNAL_WAIT_TIME);
1353+ ASSERT_EQ(1, spy.count());
1354+ auto arg = spy.takeFirst();
1355+ EXPECT_EQ(ItemListJob::Error, qvariant_cast<ItemListJob::Status>(arg.at(0)));
1356+
1357+ EXPECT_EQ("Account::roots(): cannot create job from invalid account", j->error().message());
1358+}
1359+
1360+TEST_F(RootsTest, exception)
1361+{
1362+ set_provider(unique_ptr<provider::ProviderBase>(new MockProvider("roots_throw")));
1363+
1364+ unique_ptr<ItemListJob> j(acc_.roots());
1365+ EXPECT_TRUE(j->isValid());
1366+ EXPECT_EQ(ItemListJob::Loading, j->status());
1367+ EXPECT_EQ(StorageError::NoError, j->error().type());
1368+ EXPECT_EQ("No error", j->error().message());
1369+
1370+ QSignalSpy spy(j.get(), &ItemListJob::statusChanged);
1371+ spy.wait(SIGNAL_WAIT_TIME);
1372+ ASSERT_EQ(1, spy.count());
1373+ auto arg = spy.takeFirst();
1374+ EXPECT_EQ(ItemListJob::Error, qvariant_cast<ItemListJob::Status>(arg.at(0)));
1375+
1376+ EXPECT_EQ(ItemListJob::Error, j->status());
1377+ EXPECT_EQ(StorageError::PermissionDenied, j->error().type());
1378+ EXPECT_EQ("PermissionDenied: roots(): I'm sorry Dave, I'm afraid I can't do that.", j->error().errorString());
1379+}
1380+
1381 TEST_F(RootsTest, not_a_root)
1382 {
1383 set_provider(unique_ptr<provider::ProviderBase>(new MockProvider("not_a_root")));
1384@@ -534,6 +575,25 @@
1385 EXPECT_EQ("Account::get(): Runtime was destroyed previously", j->error().message());
1386 }
1387
1388+TEST_F(GetTest, invalid_account)
1389+{
1390+ Account a;
1391+ unique_ptr<ItemJob> j(a.get("child_Id"));
1392+ EXPECT_FALSE(j->isValid());
1393+ EXPECT_EQ(ItemJob::Error, j->status());
1394+ EXPECT_EQ(StorageError::LogicError, j->error().type());
1395+ EXPECT_EQ("Account::get(): cannot create job from invalid account", j->error().message());
1396+
1397+ // Signal must be received.
1398+ QSignalSpy spy(j.get(), &ItemJob::statusChanged);
1399+ spy.wait(SIGNAL_WAIT_TIME);
1400+ ASSERT_EQ(1, spy.count());
1401+ auto arg = spy.takeFirst();
1402+ EXPECT_EQ(ItemJob::Error, qvariant_cast<ItemJob::Status>(arg.at(0)));
1403+
1404+ EXPECT_EQ("Account::get(): cannot create job from invalid account", j->error().message());
1405+}
1406+
1407 TEST_F(GetTest, empty_id_from_provider)
1408 {
1409 set_provider(unique_ptr<provider::ProviderBase>(new MockProvider("empty_id")));
1410@@ -704,6 +764,25 @@
1411 EXPECT_EQ("Item::deleteItem(): Runtime was destroyed previously", j->error().message()) << j->error().message().toStdString();
1412 }
1413
1414+TEST_F(DeleteTest, invalid_item)
1415+{
1416+ Item i;
1417+ unique_ptr<VoidJob> j(i.deleteItem());
1418+ EXPECT_FALSE(j->isValid());
1419+ EXPECT_EQ(VoidJob::Error, j->status());
1420+ EXPECT_EQ(StorageError::LogicError, j->error().type());
1421+ EXPECT_EQ("Item::deleteItem(): cannot create job from invalid item", j->error().message());
1422+
1423+ // Signal must be received.
1424+ QSignalSpy spy(j.get(), &unity::storage::qt::VoidJob::statusChanged);
1425+ spy.wait(SIGNAL_WAIT_TIME);
1426+ ASSERT_EQ(1, spy.count());
1427+ auto arg = spy.takeFirst();
1428+ EXPECT_EQ(VoidJob::Error, qvariant_cast<VoidJob::Status>(arg.at(0)));
1429+
1430+ EXPECT_EQ("Item::deleteItem(): cannot create job from invalid item", j->error().message());
1431+}
1432+
1433 #if 0
1434 // TODO: need to make internal symbols available for testing.
1435 TEST_F(ValidateTest, basic)
1436@@ -716,6 +795,112 @@
1437 }
1438 #endif
1439
1440+TEST_F(ItemTest, basic)
1441+{
1442+ set_provider(unique_ptr<provider::ProviderBase>(new MockProvider()));
1443+
1444+ {
1445+ // Default constructor.
1446+ Item i;
1447+ EXPECT_FALSE(i.isValid());
1448+ EXPECT_EQ("", i.itemId());
1449+ EXPECT_EQ("", i.name());
1450+ EXPECT_EQ("", i.etag());
1451+ EXPECT_EQ(Item::File, i.type());
1452+ auto mtime = i.lastModifiedTime();
1453+ EXPECT_FALSE(mtime.isValid());
1454+ auto pids = i.parentIds();
1455+ EXPECT_EQ(0, pids.size());
1456+ }
1457+
1458+ {
1459+ unique_ptr<ItemJob> j(acc_.get("child_id"));
1460+
1461+ {
1462+ QSignalSpy spy(j.get(), &ItemJob::statusChanged);
1463+ spy.wait(SIGNAL_WAIT_TIME);
1464+ }
1465+ Item i = j->item();
1466+ EXPECT_TRUE(i.isValid());
1467+ EXPECT_EQ("child_id", i.itemId());
1468+ EXPECT_EQ("Child", i.name());
1469+ EXPECT_TRUE(i.account().isValid());
1470+ EXPECT_EQ("etag", i.etag());
1471+ EXPECT_EQ(Item::File, i.type());
1472+
1473+ // Copy constructor
1474+ Item i2(i);
1475+ EXPECT_EQ(i, i2);
1476+
1477+ // Move constructor
1478+ Item i3(move(i2));
1479+ EXPECT_TRUE(i3.isValid());
1480+ EXPECT_EQ(i, i3);
1481+
1482+ // Moved-from object must be invalid
1483+ EXPECT_FALSE(i2.isValid());
1484+
1485+ // Moved-from object must be assignable
1486+ j.reset(acc_.get("child_id"));
1487+ {
1488+ QSignalSpy spy(j.get(), &ItemJob::statusChanged);
1489+ spy.wait(SIGNAL_WAIT_TIME);
1490+ }
1491+ auto i4 = j->item();
1492+ i2 = i4;
1493+ EXPECT_EQ(i4, i2);
1494+ }
1495+
1496+ {
1497+ unique_ptr<ItemJob> j1(acc_.get("child_id"));
1498+ unique_ptr<ItemJob> j2(acc_.get("root_id"));
1499+
1500+ QSignalSpy spy1(j1.get(), &ItemJob::statusChanged);
1501+ QSignalSpy spy2(j2.get(), &ItemJob::statusChanged);
1502+ spy2.wait(SIGNAL_WAIT_TIME);
1503+ ASSERT_EQ(1, spy1.count());
1504+
1505+ auto i1 = j1->item();
1506+ auto i2 = j2->item();
1507+
1508+ // Copy assignment
1509+ i1 = i2;
1510+ EXPECT_TRUE(i2.isValid());
1511+ EXPECT_EQ(i2, i1);
1512+
1513+ // Self-assignment
1514+ i2 = i2;
1515+ EXPECT_TRUE(i2.isValid());
1516+ EXPECT_EQ("root_id", i2.itemId());
1517+ EXPECT_EQ("Root", i2.name());
1518+ EXPECT_TRUE(i2.account().isValid());
1519+ EXPECT_EQ("etag", i2.etag());
1520+ EXPECT_EQ(Item::Root, i2.type());
1521+
1522+ // Move assignment
1523+ unique_ptr<ItemJob> j3(acc_.get("child_folder_id"));
1524+ QSignalSpy spy(j3.get(), &ItemJob::statusChanged);
1525+ spy.wait(SIGNAL_WAIT_TIME);
1526+ ASSERT_EQ(1, spy1.count());
1527+
1528+ auto i3 = j3->item();
1529+
1530+ i1 = move(i3);
1531+ EXPECT_TRUE(i1.isValid());
1532+ EXPECT_EQ("child_folder_id", i1.itemId());
1533+ EXPECT_EQ("Child_Folder", i1.name());
1534+ EXPECT_EQ(i1.account(), i1.account());
1535+ EXPECT_EQ("etag", i1.etag());
1536+ EXPECT_EQ(Item::Folder, i1.type());
1537+
1538+ // Moved-from object must be invalid
1539+ EXPECT_FALSE(i3.isValid());
1540+
1541+ // Moved-from object must be assignable
1542+ i3 = i2;
1543+ EXPECT_EQ(i2, i3);
1544+ }
1545+}
1546
1547 TEST_F(ItemTest, comparison_and_hash)
1548 {
1549@@ -851,6 +1036,292 @@
1550 }
1551 }
1552
1553+TEST_F(ParentsTest, basic)
1554+{
1555+ set_provider(unique_ptr<provider::ProviderBase>(new MockProvider()));
1556+
1557+ Item root;
1558+ {
1559+ unique_ptr<ItemJob> j(acc_.get("root_id"));
1560+ QSignalSpy spy(j.get(), &ItemJob::statusChanged);
1561+ spy.wait(SIGNAL_WAIT_TIME);
1562+ root = j->item();
1563+ }
1564+
1565+ {
1566+ // Getting parents from root does not call the provider and returns
1567+ // no parents immediately.
1568+ unique_ptr<ItemListJob> j(root.parents());
1569+ EXPECT_TRUE(j->isValid());
1570+ EXPECT_EQ(ItemListJob::Finished, j->status());
1571+ EXPECT_EQ(StorageError::NoError, j->error().type());
1572+
1573+ // Signal must be received.
1574+ QSignalSpy spy(j.get(), &ItemListJob::statusChanged);
1575+ spy.wait(SIGNAL_WAIT_TIME);
1576+ ASSERT_EQ(1, spy.count());
1577+ auto arg = spy.takeFirst();
1578+ EXPECT_EQ(ItemListJob::Finished, qvariant_cast<ItemListJob::Status>(arg.at(0)));
1579+ }
1580+
1581+ Item child;
1582+ {
1583+ unique_ptr<ItemJob> j(acc_.get("child_id"));
1584+ QSignalSpy spy(j.get(), &ItemJob::statusChanged);
1585+ spy.wait(SIGNAL_WAIT_TIME);
1586+ child = j->item();
1587+ }
1588+
1589+ QList<Item> parents;
1590+ {
1591+ unique_ptr<ItemListJob> j(child.parents());
1592+ EXPECT_TRUE(j->isValid());
1593+ EXPECT_EQ(ItemListJob::Loading, j->status());
1594+ EXPECT_EQ(StorageError::NoError, j->error().type());
1595+
1596+ QSignalSpy ready_spy(j.get(), &ItemListJob::itemsReady);
1597+ QSignalSpy status_spy(j.get(), &ItemListJob::statusChanged);
1598+ ready_spy.wait(SIGNAL_WAIT_TIME);
1599+ ASSERT_EQ(1, ready_spy.count());
1600+ auto list_arg = ready_spy.takeFirst();
1601+ parents = qvariant_cast<QList<Item>>(list_arg.at(0));
1602+
1603+ // When the signal for the final item arrives, status must be Finished.
1604+ EXPECT_EQ(ItemListJob::Finished, j->status());
1605+
1606+ // Finished signal must be received.
1607+ if (status_spy.count() == 0)
1608+ {
1609+ status_spy.wait(SIGNAL_WAIT_TIME);
1610+ }
1611+ ASSERT_EQ(1, status_spy.count());
1612+ auto status_arg = status_spy.takeFirst();
1613+ EXPECT_EQ(ItemListJob::Finished, qvariant_cast<ItemListJob::Status>(status_arg.at(0)));
1614+
1615+ // Child must have one parent, namely the root.
1616+ ASSERT_EQ(1, parents.size());
1617+ EXPECT_EQ(root, parents[0]);
1618+ }
1619+}
1620+
1621+TEST_F(ParentsTest, two_parents)
1622+{
1623+ set_provider(unique_ptr<provider::ProviderBase>(new MockProvider("two_parents")));
1624+
1625+ Item root;
1626+ {
1627+ unique_ptr<ItemJob> j(acc_.get("root_id"));
1628+ QSignalSpy spy(j.get(), &ItemJob::statusChanged);
1629+ spy.wait(SIGNAL_WAIT_TIME);
1630+ root = j->item();
1631+ }
1632+
1633+ Item child;
1634+ {
1635+ unique_ptr<ItemJob> j(acc_.get("child_id"));
1636+ QSignalSpy spy(j.get(), &ItemJob::statusChanged);
1637+ spy.wait(SIGNAL_WAIT_TIME);
1638+ child = j->item();
1639+ }
1640+
1641+ QList<Item> parents;
1642+ {
1643+ unique_ptr<ItemListJob> j(child.parents());
1644+ EXPECT_TRUE(j->isValid());
1645+
1646+ QSignalSpy ready_spy(j.get(), &ItemListJob::itemsReady);
1647+ QSignalSpy status_spy(j.get(), &ItemListJob::statusChanged);
1648+
1649+ ready_spy.wait(SIGNAL_WAIT_TIME);
1650+ auto list_arg = ready_spy.takeFirst();
1651+ auto this_parent = qvariant_cast<QList<Item>>(list_arg.at(0));
1652+ parents.append(this_parent);
1653+ while (ready_spy.count() < 1)
1654+ {
1655+ ready_spy.wait(SIGNAL_WAIT_TIME);
1656+ }
1657+ list_arg = ready_spy.takeFirst();
1658+ this_parent = qvariant_cast<QList<Item>>(list_arg.at(0));
1659+ parents.append(this_parent);
1660+
1661+ // Finished signal must be received.
1662+ if (status_spy.count() == 0)
1663+ {
1664+ status_spy.wait(SIGNAL_WAIT_TIME);
1665+ }
1666+ ASSERT_EQ(1, status_spy.count());
1667+ auto status_arg = status_spy.takeFirst();
1668+ EXPECT_EQ(ItemListJob::Finished, qvariant_cast<ItemListJob::Status>(status_arg.at(0)));
1669+
1670+ // Child must have two parents.
1671+ ASSERT_EQ(2, parents.size());
1672+ EXPECT_EQ("root_id", parents[0].itemId());
1673+ EXPECT_EQ("child_folder_id", parents[1].itemId());
1674+ }
1675+}
1676+
1677+TEST_F(ParentsTest, two_parents_throw)
1678+{
1679+ set_provider(unique_ptr<provider::ProviderBase>(new MockProvider("two_parents_throw")));
1680+
1681+ Item root;
1682+ {
1683+ unique_ptr<ItemJob> j(acc_.get("root_id"));
1684+ QSignalSpy spy(j.get(), &ItemJob::statusChanged);
1685+ spy.wait(SIGNAL_WAIT_TIME);
1686+ root = j->item();
1687+ }
1688+
1689+ Item child;
1690+ {
1691+ unique_ptr<ItemJob> j(acc_.get("child_id"));
1692+ QSignalSpy spy(j.get(), &ItemJob::statusChanged);
1693+ spy.wait(SIGNAL_WAIT_TIME);
1694+ child = j->item();
1695+ }
1696+
1697+ QList<Item> parents;
1698+ {
1699+ unique_ptr<ItemListJob> j(child.parents());
1700+ EXPECT_TRUE(j->isValid());
1701+
1702+ QSignalSpy status_spy(j.get(), &ItemListJob::statusChanged);
1703+ QSignalSpy ready_spy(j.get(), &ItemListJob::itemsReady);
1704+
1705+ status_spy.wait(SIGNAL_WAIT_TIME);
1706+ ASSERT_EQ(1, status_spy.count());
1707+ auto status_arg = status_spy.takeFirst();
1708+ EXPECT_EQ(ItemListJob::Error, qvariant_cast<ItemListJob::Status>(status_arg.at(0)));
1709+ EXPECT_EQ(StorageError::ResourceError, j->error().type());
1710+ EXPECT_EQ("ResourceError: metadata(): weird error", j->error().errorString());
1711+ EXPECT_EQ(42, j->error().errorCode());
1712+
1713+ // We wait here to allow the error return for the second parent to arrive in MultiItemJobImpl.
1714+ // This gives us coverage on the early return in the process_error lambda, when the job is
1715+ // already in the error state.
1716+ EXPECT_FALSE(ready_spy.wait(1000));
1717+ }
1718+}
1719+
1720+TEST_F(ParentsTest, invalid_item)
1721+{
1722+ set_provider(unique_ptr<provider::ProviderBase>(new MockProvider()));
1723+
1724+ Item invalid;
1725+ unique_ptr<ItemListJob> j(invalid.parents());
1726+ EXPECT_FALSE(j->isValid());
1727+ EXPECT_EQ(ItemListJob::Error, j->status());
1728+ EXPECT_EQ(StorageError::LogicError, j->error().type());
1729+ EXPECT_EQ("Item::parents(): cannot create job from invalid item", j->error().message());
1730+
1731+ // Signal must be received.
1732+ QSignalSpy spy(j.get(), &ItemListJob::statusChanged);
1733+ spy.wait(SIGNAL_WAIT_TIME);
1734+ ASSERT_EQ(1, spy.count());
1735+ auto arg = spy.takeFirst();
1736+ EXPECT_EQ(ItemListJob::Error, qvariant_cast<ItemListJob::Status>(arg.at(0)));
1737+}
1738+
1739+TEST_F(ParentsTest, runtime_destroyed)
1740+{
1741+ set_provider(unique_ptr<provider::ProviderBase>(new MockProvider()));
1742+
1743+ Item root;
1744+ {
1745+ unique_ptr<ItemJob> j(acc_.get("root_id"));
1746+ QSignalSpy spy(j.get(), &ItemJob::statusChanged);
1747+ spy.wait(SIGNAL_WAIT_TIME);
1748+ root = j->item();
1749+ }
1750+
1751+ EXPECT_EQ(StorageError::NoError, runtime_->shutdown().type()); // Destroy runtime.
1752+
1753+ unique_ptr<ItemListJob> j(root.parents());
1754+ EXPECT_FALSE(j->isValid());
1755+ EXPECT_EQ(ItemJob::Error, j->status());
1756+ EXPECT_EQ(StorageError::RuntimeDestroyed, j->error().type());
1757+ EXPECT_EQ("Item::parents(): Runtime was destroyed previously", j->error().message());
1758+
1759+ // Signal must be received.
1760+ QSignalSpy spy(j.get(), &ItemListJob::statusChanged);
1761+ spy.wait(SIGNAL_WAIT_TIME);
1762+ auto arg = spy.takeFirst();
1763+ EXPECT_EQ(ItemListJob::Error, qvariant_cast<ItemListJob::Status>(arg.at(0)));
1764+
1765+ EXPECT_EQ("Item::parents(): Runtime was destroyed previously", j->error().message());
1766+}
1767+
1768+TEST_F(ParentsTest, runtime_destroyed_while_item_list_job_running)
1769+{
1770+ set_provider(unique_ptr<provider::ProviderBase>(new MockProvider("slow_metadata")));
1771+
1772+ Item root;
1773+ {
1774+ unique_ptr<ItemJob> j(acc_.get("root_id"));
1775+ QSignalSpy spy(j.get(), &ItemJob::statusChanged);
1776+ spy.wait(SIGNAL_WAIT_TIME);
1777+ root = j->item();
1778+ }
1779+
1780+ Item child;
1781+ {
1782+ unique_ptr<ItemJob> j(acc_.get("child_id"));
1783+ QSignalSpy spy(j.get(), &ItemJob::statusChanged);
1784+ spy.wait(SIGNAL_WAIT_TIME);
1785+ child = j->item();
1786+ }
1787+
1788+ unique_ptr<ItemListJob> j(child.parents());
1789+
1790+ EXPECT_EQ(StorageError::NoError, runtime_->shutdown().type()); // Destroy runtime, provider still sleeping
1791+
1792+ // Signal must be received.
1793+ QSignalSpy spy(j.get(), &ItemListJob::statusChanged);
1794+ spy.wait(SIGNAL_WAIT_TIME);
1795+ ASSERT_EQ(1, spy.count());
1796+ auto arg = spy.takeFirst();
1797+ EXPECT_EQ(ItemListJob::Error, qvariant_cast<ItemListJob::Status>(arg.at(0)));
1798+
1799+ EXPECT_EQ("Item::parents(): Runtime was destroyed previously", j->error().message());
1800+}
1801+
1802+TEST_F(ParentsTest, bad_metadata)
1803+{
1804+ set_provider(unique_ptr<provider::ProviderBase>(new MockProvider("bad_parent_metadata_from_child")));
1805+
1806+ Item root;
1807+ {
1808+ unique_ptr<ItemJob> j(acc_.get("root_id"));
1809+ QSignalSpy spy(j.get(), &ItemJob::statusChanged);
1810+ spy.wait(SIGNAL_WAIT_TIME);
1811+ root = j->item();
1812+ }
1813+
1814+ Item child;
1815+ {
1816+ unique_ptr<ItemJob> j(acc_.get("child_id"));
1817+ QSignalSpy spy(j.get(), &ItemJob::statusChanged);
1818+ spy.wait(SIGNAL_WAIT_TIME);
1819+ child = j->item();
1820+ }
1821+
1822+ QList<Item> parents;
1823+ {
1824+ unique_ptr<ItemListJob> j(child.parents());
1825+ EXPECT_TRUE(j->isValid());
1826+ EXPECT_EQ(ItemListJob::Loading, j->status());
1827+ EXPECT_EQ(StorageError::NoError, j->error().type());
1828+
1829+ QSignalSpy spy(j.get(), &ItemListJob::statusChanged);
1830+ spy.wait(SIGNAL_WAIT_TIME);
1831+ ASSERT_EQ(1, spy.count());
1832+ auto arg = spy.takeFirst();
1833+ EXPECT_EQ(ItemListJob::Error, qvariant_cast<ItemListJob::Status>(arg.at(0)));
1834+
1835+ EXPECT_EQ("Item::parents(): provider returned a file as a parent", j->error().message());
1836+ }
1837+}
1838+
1839 #if 0
1840 TEST_F(RootTest, basic)
1841 {

Subscribers

People subscribed via source and target branches

to all changes: