Merge lp:~xavi-garcia-mena/keeper/task-state-manager into lp:keeper/devel

Proposed by Xavi Garcia
Status: Needs review
Proposed branch: lp:~xavi-garcia-mena/keeper/task-state-manager
Merge into: lp:keeper/devel
Diff against target: 1634 lines (+1003/-359)
15 files modified
include/helper/backup-helper.h (+4/-0)
include/helper/helper.h (+4/-2)
src/cli/main.cpp (+0/-1)
src/helper/backup-helper.cpp (+41/-14)
src/helper/helper.cpp (+23/-27)
src/service/CMakeLists.txt (+3/-0)
src/service/keeper-task-backup.cpp (+175/-0)
src/service/keeper-task-backup.h (+46/-0)
src/service/keeper-task.cpp (+176/-0)
src/service/keeper-task.h (+73/-0)
src/service/keeper.cpp (+16/-315)
src/service/private/keeper-task_p.h (+54/-0)
src/service/task-manager.cpp (+327/-0)
src/service/task-manager.h (+60/-0)
tests/unit/helper/fake-helper.h (+1/-0)
To merge this branch: bzr merge lp:~xavi-garcia-mena/keeper/task-state-manager
Reviewer Review Type Date Requested Status
unity-api-1-bot continuous-integration Needs Fixing
Charles Kerr (community) Approve
Review via email: mp+303802@code.launchpad.net

Commit message

Initial implementation of a task manager that holds the state and cleans keeper.cpp from having references to helpers.

Description of the change

Initial implementation of a task manager that holds the state and cleans keeper.cpp from having references to helpers.

Main class is TaskManager, that runs KeeperTask objects sequentially and stores the state of all of them.

A KeeperTask object basically holds the state of that task and the common members between different kinds of tasks. For example, the Helper is a protected member of KeeperTask, and the concrete tasks will be the ones to define if the helper is a backup or restore helper.

KeeperTaskBackup inherits from KeeperTask, represents a backup task and holds all the storage framework socket functionality.

Note: I've created a private/keeper-task_p.h to follow Qt's implementation and to avoid allocations and memory to be repeated along concrete helpers. For example, the goal was to have only one allocation of the helper and make it accessible to all the concrete tasks.

To post a comment you must log in.
Revision history for this message
Xavi Garcia (xavi-garcia-mena) wrote :

Also, the toString method in the helper has been changed to to_string and it's virtual.

The reason why is because, for example, the backup helper may say that the started state means "saving" while the restore helper would say "restoring".

There is a default implementation with the default strings.

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

FAILED: Continuous integration, rev:96
https://jenkins.canonical.com/unity-api-1/job/lp-keeper-ci/29/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build/468/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/474
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=vivid+overlay/379
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=xenial+overlay/379
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=yakkety/379
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/309
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/309/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/309
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/309/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/309
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/309/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/309
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/309/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/309
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/309/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/309
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/309/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/309/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/309
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/309/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/309
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/309/artifact/output/*zip*/output.zip

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

review: Needs Fixing (continuous-integration)
Revision history for this message
Charles Kerr (charlesk) wrote :

Mostly LGTM, a few comments and suggestions inline.

Main issue is I suspect I know what the problem is that's requiring the DATA_COMPLETE, you may want to talk to james about the shared_ptr<QLocalSocket>.

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

I added a response to one of Charles's comments.

Revision history for this message
Charles Kerr (charlesk) wrote :

Replied with more detail inline

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

Thanks for the comments Charles!

I'm a Qt newbie, so I might be getting this wrong. But I was under the impression that, if the QLocalSocket is destroyed, the signal connection is torn down too. In other words, you either get the signal and, if you do, the socket is still guaranteed to be there, or the socket was destroyed, in which case you don't get the signal.

Have I got this wrong?

At any rate, as long as you keep a copy of the socket shared_ptr around, you know that it can't magically disappear from underneath you. Isn't that sufficient to make sure that things are OK with respect to life cycle?

Revision history for this message
Xavi Garcia (xavi-garcia-mena) wrote :

Thanks for the review, Charles, I left some comments inline.

I also found a bug in the state management while creating the new integration tests and I will fix them in this branch.

Regarding the QLocalSocket issue... I will change the code to reset the shared_ptr and when I get more information I will let you all know.

Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Xavi Garcia (xavi-garcia-mena) wrote :

Michi, Charles... I removed the "hack" and when we don't need the QLocalSocket I just drop the shared_ptr.... it works fine now. I updated the version of storage-framework this week, so that possibly fixed the issue I had.

Revision history for this message
Charles Kerr (charlesk) :
review: Approve
Revision history for this message
Charles Kerr (charlesk) wrote :

Michi,

Yes, I think you're misunderstanding. What I'm saying is because we're using `delete o' instead of `o.deleteLater()' in the QLocalSocket's shared_ptr, we're setting up a case where it can be destroyed in the middle of its bytesWritten signal.

1. keeper calls create_file(), gets an uploader
2. uploader's ctor creates a QLocalSocket smart ptr (socket refcount init to 1)
3. keeper holds onto the uploader (uploader refcount init to 1)
4. keeper calls uploader::socket(), keeps a ref (socket refcount inc to 2), subscribes to bytesWritten
(work happens)
5. keeper gets a socket::bytesWritten signal and calls its update-task-status method

two paths here:

6a. if keeper's got an error flag set on this task, eg error between the backup helper and keeper, we set the task as in error and reset state, including our socket smart_ptr (socket refcount dec to 1) and our uploader smart_ptr (uploader refcount dec to 0, socket refcount dec to 0) so we've called "delete QLocalSocket" in the middle of its bytesWritten callback

(less sure about this, branch, steps to trigger same bug here depend on how Qt signals work)
6b. if no error flag but we've written all we planned to, keeper calls finish_upload()
7b. UploaderImpl::finish_upload() calls disconnectFromServer() in caller thread (ie, we're still in the QLocalSocket::bytesWritten slot wrt stack unwinding)
8b. disconnectFromServer() -> readChannelFinished signal -> uploader's on_read_channel_finished() slot -> do_finish() -> finalize() -> make_ready_future(qf_, file) -> keeper's QFutureListener -> keeper's ``file ready'' handler
9b. keeper says "we're done, clear the state", including our socket reference (socket refcount dec to 1) and our uploader (uploader refcount dec to 0, socket refcount dec to 0), so again we've called 'delete QLocalSocket" in the middle of its bytesWritten callback

On top of that, keeper has its own bug in branch b: it's letting the uploader refcount dec to 0 while still having a QFutureWatcher has a dangling reference to the now-destroyed uploader's QFuture.

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

FAILED: Autolanding.
More details in the following jenkins job:
https://jenkins.canonical.com/unity-api-1/job/lp-keeper-autoland/4/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build/530/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/536
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=vivid+overlay/438
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=xenial+overlay/438
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=yakkety/438
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/368
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/368/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/368/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/368/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/368/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/368
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/368/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/368/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/368
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/368/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/368
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/368/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/368/console

review: Needs Fixing (continuous-integration)
Revision history for this message
Michi Henning (michihenning) wrote :

> Michi,
>
> Yes, I think you're misunderstanding. What I'm saying is because we're using
> `delete o' instead of `o.deleteLater()' in the QLocalSocket's shared_ptr,
> we're setting up a case where it can be destroyed in the middle of its
> bytesWritten signal.

Ah, OK, thanks for the explanation, I get it now :-)

I've pushed a fix and added it to silo 60. The QLocalSocket shared pointers that are handed to the client now have a custom deleter that calls socket->deleteLater().

Revision history for this message
Charles Kerr (charlesk) wrote :

Michi, thanks!

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'include/helper/backup-helper.h'
2--- include/helper/backup-helper.h 2016-08-21 23:43:33 +0000
3+++ include/helper/backup-helper.h 2016-08-30 14:16:37 +0000
4@@ -52,6 +52,10 @@
5 void stop() override;
6 void on_helper_process_stopped() override;
7 int get_helper_socket() const;
8+ QString to_string(Helper::State state) const override;
9+
10+public Q_SLOTS:
11+ void on_storage_framework_finished();
12
13 private:
14 QScopedPointer<BackupHelperPrivate> const d_ptr;
15
16=== modified file 'include/helper/helper.h'
17--- include/helper/helper.h 2016-08-21 23:43:33 +0000
18+++ include/helper/helper.h 2016-08-30 14:16:37 +0000
19@@ -37,11 +37,13 @@
20 Q_DISABLE_COPY(Helper)
21
22 Q_ENUMS(State)
23- enum class State {NOT_STARTED, STARTED, CANCELLED, FAILED, COMPLETE};
24+ enum class State {NOT_STARTED, STARTED, CANCELLED, FAILED, DATA_COMPLETE, COMPLETE};
25
26 Q_PROPERTY(Helper::State state READ state NOTIFY state_changed)
27 State state() const __pure;
28
29+ virtual QString to_string(Helper::State state) const;
30+
31 // NB: range is [0.0 .. 1.0]
32 Q_PROPERTY(float percent_done READ percent_done NOTIFY percent_done_changed)
33 float percent_done() const __pure;
34@@ -70,7 +72,7 @@
35 Helper(QString const & appid, const clock_func& clock=default_clock, QObject *parent=nullptr);
36 void set_state(State);
37 void record_data_transferred(qint64 n_bytes);
38- virtual void on_helper_process_stopped();
39+ virtual void on_helper_process_stopped() = 0;
40
41 private:
42
43
44=== modified file 'src/cli/main.cpp'
45--- src/cli/main.cpp 2016-08-26 09:30:11 +0000
46+++ src/cli/main.cpp 2016-08-30 14:16:37 +0000
47@@ -75,7 +75,6 @@
48 {
49 if (iter_values.value().toString() == "folder")
50 {
51- // got it
52 qDebug() << "Adding uuid" << iter.key() << "with type:" << "folder";
53 uuids << iter.key();
54 }
55
56=== modified file 'src/helper/backup-helper.cpp'
57--- src/helper/backup-helper.cpp 2016-08-24 07:04:33 +0000
58+++ src/helper/backup-helper.cpp 2016-08-30 14:16:37 +0000
59@@ -51,11 +51,6 @@
60 , helper_socket_(new QLocalSocket())
61 , read_socket_(new QLocalSocket())
62 , upload_buffer_{}
63- , n_read_{}
64- , n_uploaded_{}
65- , read_error_{}
66- , write_error_{}
67- , cancelled_{}
68 {
69 // listen for inactivity
70 QObject::connect(timer_.data(), &QTimer::timeout,
71@@ -139,6 +134,20 @@
72 return int(helper_socket_->socketDescriptor());
73 }
74
75+ void on_storage_framework_finished()
76+ {
77+ qDebug() << "storage framework has finished for the current helper...";
78+ storage_framework_socket_.reset();
79+ check_for_done();
80+ }
81+
82+ QString to_string(Helper::State state) const
83+ {
84+ return state == Helper::State::STARTED
85+ ? QStringLiteral("saving")
86+ : q_ptr->Helper::to_string(state);
87+ }
88+
89 private:
90
91 void on_inactivity_detected()
92@@ -158,8 +167,8 @@
93 n_uploaded_ += n;
94 q_ptr->record_data_transferred(n);
95 qDebug("n_read %zu n_uploaded %zu (newly uploaded %zu)", size_t(n_read_), size_t(n_uploaded_), size_t(n));
96+ process_more();
97 check_for_done();
98- process_more();
99 }
100
101 void process_more()
102@@ -218,8 +227,10 @@
103 {
104 if (n_uploaded_ == q_ptr->expected_size())
105 {
106- qDebug() << "n_uploaded = " << n_uploaded_ << " expected = " << q_ptr->expected_size() << " --- COMPLETE";
107- q_ptr->set_state(Helper::State::COMPLETE);
108+ if (storage_framework_socket_)
109+ q_ptr->set_state(Helper::State::DATA_COMPLETE);
110+ else
111+ q_ptr->set_state(Helper::State::COMPLETE);
112 }
113 else if (read_error_ || write_error_ || (n_uploaded_ > q_ptr->expected_size()))
114 {
115@@ -241,15 +252,15 @@
116 BackupHelper * const q_ptr;
117 QScopedPointer<QTimer> timer_;
118 std::shared_ptr<QLocalSocket> storage_framework_socket_;
119- std::shared_ptr<QMetaObject::Connection> storage_framework_socket_connection_;
120 QScopedPointer<QLocalSocket> helper_socket_;
121 QScopedPointer<QLocalSocket> read_socket_;
122 QByteArray upload_buffer_;
123- qint64 n_read_;
124- qint64 n_uploaded_;
125- bool read_error_;
126- bool write_error_;
127- bool cancelled_;
128+ qint64 n_read_ = 0;
129+ qint64 n_uploaded_ = 0;
130+ bool read_error_ = false;
131+ bool write_error_ = false;
132+ bool cancelled_ = false;
133+ std::shared_ptr<QMetaObject::Connection> storage_framework_socket_connection_;
134 };
135
136 /***
137@@ -307,3 +318,19 @@
138
139 return d->get_helper_socket();
140 }
141+
142+QString
143+BackupHelper::to_string(Helper::State state) const
144+{
145+ Q_D(const BackupHelper);
146+
147+ return d->to_string(state);
148+}
149+
150+void
151+BackupHelper::on_storage_framework_finished()
152+{
153+ Q_D(BackupHelper);
154+
155+ return d->on_storage_framework_finished();
156+}
157
158=== modified file 'src/helper/helper.cpp'
159--- src/helper/helper.cpp 2016-08-21 23:43:33 +0000
160+++ src/helper/helper.cpp 2016-08-30 14:16:37 +0000
161@@ -166,7 +166,7 @@
162 {
163 if (state_ != state)
164 {
165- qDebug() << "changing state of helper" << static_cast<void*>(this) << "from" << toString(state_) << "to" << toString(state);
166+ qDebug() << "changing state of helper" << static_cast<void*>(this) << "from" << q_ptr->to_string(state_) << "to" << q_ptr->to_string(state);
167 state_ = state;
168 q_ptr->state_changed(state);
169 }
170@@ -221,9 +221,21 @@
171 ual_stop();
172 }
173
174- void on_helper_process_stopped()
175+ QString to_string(Helper::State state) const
176 {
177- q_ptr->set_state(Helper::State::COMPLETE);
178+ auto ret = QStringLiteral("bug");
179+
180+ switch (state)
181+ {
182+ case State::NOT_STARTED: ret = QStringLiteral("not-started"); break;
183+ case State::STARTED: ret = QStringLiteral("started"); break;
184+ case State::CANCELLED: ret = QStringLiteral("cancelled"); break;
185+ case State::FAILED: ret = QStringLiteral("failed"); break;
186+ case State::DATA_COMPLETE: ret = QStringLiteral("finishing"); break;
187+ case State::COMPLETE: ret = QStringLiteral("complete"); break;
188+ }
189+
190+ return ret;
191 }
192
193 private:
194@@ -311,22 +323,6 @@
195 }
196 }
197
198- QString toString(Helper::State state)
199- {
200- auto ret = QStringLiteral("bug");
201-
202- switch (state)
203- {
204- case State::NOT_STARTED: ret = QStringLiteral("not-started"); break;
205- case State::STARTED: ret = QStringLiteral("started"); break;
206- case State::CANCELLED: ret = QStringLiteral("cancelled"); break;
207- case State::FAILED: ret = QStringLiteral("failed"); break;
208- case State::COMPLETE: ret = QStringLiteral("complete"); break;
209- }
210-
211- return ret;
212- }
213-
214 Helper * const q_ptr;
215 QString appid_;
216 clock_func clock_;
217@@ -360,6 +356,14 @@
218 return d->state();
219 }
220
221+QString
222+Helper::to_string(Helper::State state) const
223+{
224+ Q_D(const Helper);
225+
226+ return d->to_string(state);
227+}
228+
229 int
230 Helper::speed() const
231 {
232@@ -438,11 +442,3 @@
233
234 d->stop();
235 }
236-
237-void
238-Helper::on_helper_process_stopped()
239-{
240- Q_D(Helper);
241-
242- d->on_helper_process_stopped();
243-}
244
245=== modified file 'src/service/CMakeLists.txt'
246--- src/service/CMakeLists.txt 2016-08-12 06:08:22 +0000
247+++ src/service/CMakeLists.txt 2016-08-30 14:16:37 +0000
248@@ -12,6 +12,9 @@
249 keeper-user.cpp
250 keeper-helper.cpp
251 restore-choices.cpp
252+ task-manager.cpp
253+ keeper-task.cpp
254+ keeper-task-backup.cpp
255 )
256 add_library(
257 ${SERVICE_LIB}
258
259=== added file 'src/service/keeper-task-backup.cpp'
260--- src/service/keeper-task-backup.cpp 1970-01-01 00:00:00 +0000
261+++ src/service/keeper-task-backup.cpp 2016-08-30 14:16:37 +0000
262@@ -0,0 +1,175 @@
263+/*
264+ * Copyright (C) 2016 Canonical, Ltd.
265+ *
266+ * This program is free software: you can redistribute it and/or modify it
267+ * under the terms of the GNU General Public License version 3, as published
268+ * by the Free Software Foundation.
269+ *
270+ * This program is distributed in the hope that it will be useful, but
271+ * WITHOUT ANY WARRANTY; without even the implied warranties of
272+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
273+ * PURPOSE. See the GNU General Public License for more details.
274+ *
275+ * You should have received a copy of the GNU General Public License along
276+ * with this program. If not, see <http://www.gnu.org/licenses/>.
277+ *
278+ * Authors:
279+ * Xavi Garcia <xavi.garcia.mena@canoincal.com>
280+ * Charles Kerr <charles.kerr@canoincal.com>
281+ */
282+#include "app-const.h" // DEKKO_APP_ID
283+#include "helper/backup-helper.h"
284+#include "keeper-task-backup.h"
285+#include "keeper-task.h"
286+#include "private/keeper-task_p.h"
287+#include "storage-framework/storage_framework_client.h"
288+
289+class KeeperTaskBackupPrivate : public KeeperTaskPrivate
290+{
291+ Q_DECLARE_PUBLIC(KeeperTaskBackup)
292+public:
293+ KeeperTaskBackupPrivate(KeeperTask * keeper_task,
294+ KeeperTask::TaskData const & task_data,
295+ QSharedPointer<HelperRegistry> const & helper_registry,
296+ QSharedPointer<StorageFrameworkClient> const & storage)
297+ : KeeperTaskPrivate(keeper_task, task_data, helper_registry, storage)
298+ {
299+ storage_framework_socket_connection_ready_.reset(
300+ new QMetaObject::Connection(
301+ QObject::connect(
302+ storage_.data(), &StorageFrameworkClient::socketReady,
303+ std::bind(&KeeperTaskBackupPrivate::on_backup_socket_ready, this, std::placeholders::_1)
304+ )
305+ ),
306+ [](QMetaObject::Connection* c){
307+ QObject::disconnect(*c);
308+ }
309+ );
310+ }
311+
312+ ~KeeperTaskBackupPrivate() = default;
313+
314+ QStringList get_helper_urls() const
315+ {
316+ return helper_registry_->get_backup_helper_urls(task_data_.metadata);
317+ }
318+
319+ void init_helper()
320+ {
321+ qDebug() << "Initializing a backup helper";
322+ helper_.reset(new BackupHelper(DEKKO_APP_ID));
323+ qDebug() << "Helper " << static_cast<void*>(helper_.data()) << " was created";
324+
325+ auto backup_helper = qSharedPointerDynamicCast<BackupHelper>(helper_);
326+ if (backup_helper)
327+ {
328+ // listen for the storage framework to finish
329+ storage_framework_socket_connection_finished_.reset(
330+ new QMetaObject::Connection(
331+ QObject::connect(
332+ storage_.data(), &StorageFrameworkClient::finished,
333+ std::bind(&BackupHelper::on_storage_framework_finished, backup_helper.data())
334+ )
335+ ),
336+ [](QMetaObject::Connection* c){
337+ QObject::disconnect(*c);
338+ }
339+ );
340+ }
341+ }
342+
343+ void on_backup_socket_ready(std::shared_ptr<QLocalSocket> const & sf_socket)
344+ {
345+ qDebug("calling helper.set_storage_framework_socket(socket=%d)", int(sf_socket->socketDescriptor()));
346+ qDebug() << "Helper is " << static_cast<void*>(helper_.data());
347+ auto backup_helper = qSharedPointerDynamicCast<BackupHelper>(helper_);
348+ if (!backup_helper)
349+ {
350+ qWarning() << "Only backup tasks are allowed to ask for storage framework sockets";
351+ helper_->stop();
352+ return;
353+ }
354+ backup_helper->set_storage_framework_socket(sf_socket);
355+ Q_EMIT(q_ptr->task_socket_ready(backup_helper->get_helper_socket()));
356+ }
357+
358+ void ask_for_storage_framework_socket(quint64 n_bytes)
359+ {
360+ qDebug() << "asking storage framework for a socket";
361+ storage_->getNewFileForBackup(n_bytes);
362+ helper_->set_expected_size(n_bytes);
363+ }
364+
365+ void on_helper_state_changed(Helper::State state) override
366+ {
367+ auto new_state = state;
368+ switch (state)
369+ {
370+ case Helper::State::NOT_STARTED:
371+ break;
372+
373+ case Helper::State::STARTED:
374+ qDebug() << "Backup helper started";
375+ break;
376+
377+ case Helper::State::CANCELLED:
378+ qDebug() << "Backup helper cancelled... closing the socket.";
379+ storage_->finish(false);
380+ break;
381+
382+ case Helper::State::FAILED:
383+ qDebug() << "Backup helper failed... closing the socket.";
384+ storage_->finish(false);
385+ break;
386+
387+ case Helper::State::DATA_COMPLETE:
388+ task_data_.percent_done = 1;
389+ qDebug() << "Backup helper finished... closing the socket.";
390+ try
391+ {
392+ storage_->finish(true);
393+ }
394+ catch (std::exception & e)
395+ {
396+ qDebug() << "Failed finishing sf... setting the state to failed";
397+ new_state = Helper::State::FAILED;
398+ }
399+ break;
400+ case Helper::State::COMPLETE:
401+ break;
402+ }
403+ KeeperTaskPrivate::on_helper_state_changed(new_state);
404+ }
405+
406+private:
407+ std::shared_ptr<QMetaObject::Connection> storage_framework_socket_connection_ready_;
408+ std::shared_ptr<QMetaObject::Connection> storage_framework_socket_connection_finished_;
409+};
410+
411+KeeperTaskBackup::KeeperTaskBackup(TaskData const & task_data,
412+ QSharedPointer<HelperRegistry> const & helper_registry,
413+ QSharedPointer<StorageFrameworkClient> const & storage,
414+ QObject *parent)
415+ : KeeperTask(*new KeeperTaskBackupPrivate(this, task_data, helper_registry, storage), parent)
416+{
417+}
418+
419+KeeperTaskBackup::~KeeperTaskBackup() = default;
420+
421+QStringList KeeperTaskBackup::get_helper_urls() const
422+{
423+ Q_D(const KeeperTaskBackup);
424+ return d->get_helper_urls();
425+}
426+
427+void KeeperTaskBackup::init_helper()
428+{
429+ Q_D(KeeperTaskBackup);
430+ d->init_helper();
431+}
432+
433+void KeeperTaskBackup::ask_for_storage_framework_socket(quint64 n_bytes)
434+{
435+ Q_D(KeeperTaskBackup);
436+ d->ask_for_storage_framework_socket(n_bytes);
437+}
438
439=== added file 'src/service/keeper-task-backup.h'
440--- src/service/keeper-task-backup.h 1970-01-01 00:00:00 +0000
441+++ src/service/keeper-task-backup.h 2016-08-30 14:16:37 +0000
442@@ -0,0 +1,46 @@
443+/*
444+ * Copyright (C) 2016 Canonical, Ltd.
445+ *
446+ * This program is free software: you can redistribute it and/or modify it
447+ * under the terms of the GNU General Public License version 3, as published
448+ * by the Free Software Foundation.
449+ *
450+ * This program is distributed in the hope that it will be useful, but
451+ * WITHOUT ANY WARRANTY; without even the implied warranties of
452+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
453+ * PURPOSE. See the GNU General Public License for more details.
454+ *
455+ * You should have received a copy of the GNU General Public License along
456+ * with this program. If not, see <http://www.gnu.org/licenses/>.
457+ *
458+ * Authors:
459+ * Xavi Garcia <xavi.garcia.mena@canoincal.com>
460+ * Charles Kerr <charles.kerr@canoincal.com>
461+ */
462+#pragma once
463+
464+#include "keeper-task.h"
465+
466+class KeeperTaskBackupPrivate;
467+
468+class KeeperTaskBackup : public KeeperTask
469+{
470+ Q_OBJECT
471+ Q_DECLARE_PRIVATE(KeeperTaskBackup)
472+public:
473+
474+ KeeperTaskBackup(TaskData const & task_data,
475+ QSharedPointer<HelperRegistry> const & helper_registry,
476+ QSharedPointer<StorageFrameworkClient> const & storage,
477+ QObject *parent = nullptr);
478+ virtual ~KeeperTaskBackup();
479+
480+ Q_DISABLE_COPY(KeeperTaskBackup)
481+
482+ void ask_for_storage_framework_socket(quint64 n_bytes);
483+
484+protected:
485+ QStringList get_helper_urls() const override;
486+ void init_helper() override;
487+
488+};
489
490=== added file 'src/service/keeper-task.cpp'
491--- src/service/keeper-task.cpp 1970-01-01 00:00:00 +0000
492+++ src/service/keeper-task.cpp 2016-08-30 14:16:37 +0000
493@@ -0,0 +1,176 @@
494+/*
495+ * Copyright (C) 2016 Canonical, Ltd.
496+ *
497+ * This program is free software: you can redistribute it and/or modify it
498+ * under the terms of the GNU General Public License version 3, as published
499+ * by the Free Software Foundation.
500+ *
501+ * This program is distributed in the hope that it will be useful, but
502+ * WITHOUT ANY WARRANTY; without even the implied warranties of
503+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
504+ * PURPOSE. See the GNU General Public License for more details.
505+ *
506+ * You should have received a copy of the GNU General Public License along
507+ * with this program. If not, see <http://www.gnu.org/licenses/>.
508+ *
509+ * Authors:
510+ * Xavi Garcia <xavi.garcia.mena@canoincal.com>
511+ * Charles Kerr <charles.kerr@canoincal.com>
512+ */
513+#include "app-const.h" // DEKKO_APP_ID
514+#include "helper/metadata.h"
515+#include "keeper-task.h"
516+
517+#include "private/keeper-task_p.h"
518+
519+KeeperTaskPrivate::KeeperTaskPrivate(KeeperTask * keeper_task,
520+ KeeperTask::TaskData const & task_data,
521+ QSharedPointer<HelperRegistry> const & helper_registry,
522+ QSharedPointer<StorageFrameworkClient> const & storage)
523+ : q_ptr(keeper_task)
524+ , task_data_(task_data)
525+ , helper_registry_(helper_registry)
526+ , storage_(storage)
527+{
528+}
529+
530+KeeperTaskPrivate::~KeeperTaskPrivate() = default;
531+
532+bool KeeperTaskPrivate::start()
533+{
534+ // initialize the helper
535+ q_ptr->init_helper();
536+
537+ const auto urls = q_ptr->get_helper_urls();
538+ if (urls.isEmpty())
539+ {
540+ task_data_.action = helper_->to_string(Helper::State::FAILED);
541+ task_data_.error = "no helper information in registry";
542+ qWarning() << "ERROR: uuid: " << task_data_.metadata.uuid() << " has no url";
543+ calculate_and_notify_state(Helper::State::FAILED);
544+ return false;
545+ }
546+
547+ // listen for helper state changes
548+ QObject::connect(helper_.data(), &Helper::state_changed,
549+ std::bind(&KeeperTaskPrivate::on_helper_state_changed, this, std::placeholders::_1)
550+ );
551+
552+ // listen for helper process changes
553+ QObject::connect(helper_.data(), &Helper::percent_done_changed,
554+ std::bind(&KeeperTaskPrivate::on_helper_percent_done_changed, this, std::placeholders::_1)
555+ );
556+
557+ helper_->start(urls);
558+ return true;
559+}
560+
561+QVariantMap KeeperTaskPrivate::state() const
562+{
563+ return state_;
564+}
565+
566+void KeeperTaskPrivate::set_current_task_action(QString const& action)
567+{
568+ task_data_.action = action;
569+ calculate_task_state();
570+}
571+
572+void KeeperTaskPrivate::on_helper_state_changed(Helper::State state)
573+{
574+ switch (state)
575+ {
576+ case Helper::State::NOT_STARTED:
577+ break;
578+
579+ case Helper::State::STARTED:
580+ qDebug() << "Helper started";
581+ break;
582+
583+ case Helper::State::CANCELLED:
584+ qDebug() << "Helper cancelled... closing the socket.";
585+ break;
586+
587+ case Helper::State::FAILED:
588+ qDebug() << "Helper failed... closing the socket.";
589+ break;
590+
591+ case Helper::State::DATA_COMPLETE:
592+ task_data_.percent_done = 1;
593+ qDebug() << "Helper data complete.. closing the socket.";
594+ break;
595+
596+ case Helper::State::COMPLETE:
597+ qDebug() << "Helper complete.";
598+ break;
599+ }
600+ set_current_task_action(helper_->to_string(state));
601+ calculate_and_notify_state(state);
602+}
603+
604+void KeeperTaskPrivate::on_helper_percent_done_changed(float /*percent_done*/)
605+{
606+ calculate_task_state();
607+}
608+
609+QVariantMap KeeperTaskPrivate::calculate_task_state()
610+{
611+ QVariantMap ret;
612+
613+ auto const uuid = task_data_.metadata.uuid();
614+
615+ ret.insert(QStringLiteral("action"), task_data_.action);
616+
617+ ret.insert(QStringLiteral("display-name"), task_data_.metadata.display_name());
618+
619+ // FIXME: assuming backup_helper_ for now...
620+ int32_t speed {};
621+ speed = helper_->speed();
622+ ret.insert(QStringLiteral("speed"), speed);
623+
624+ task_data_.percent_done = helper_->percent_done();
625+ ret.insert(QStringLiteral("percent-done"), double(task_data_.percent_done));
626+
627+ if (task_data_.action == "failed")
628+ ret.insert(QStringLiteral("error"), task_data_.error);
629+
630+ ret.insert(QStringLiteral("uuid"), uuid);
631+
632+ return ret;
633+}
634+
635+void KeeperTaskPrivate::calculate_and_notify_state(Helper::State state)
636+{
637+ state_ = calculate_task_state();
638+ Q_EMIT(q_ptr->task_state_changed(state));
639+}
640+
641+KeeperTask::KeeperTask(TaskData const & task_data,
642+ QSharedPointer<HelperRegistry> const & helper_registry,
643+ QSharedPointer<StorageFrameworkClient> const & storage,
644+ QObject *parent)
645+ : QObject(parent)
646+ , d_ptr( new KeeperTaskPrivate(this, task_data, helper_registry, storage))
647+{
648+}
649+
650+KeeperTask::KeeperTask(KeeperTaskPrivate & d, QObject *parent)
651+ : QObject(parent)
652+ , d_ptr(&d)
653+{
654+}
655+
656+
657+KeeperTask::~KeeperTask() = default;
658+
659+bool KeeperTask::start()
660+{
661+ Q_D(KeeperTask);
662+ return d->start();
663+}
664+
665+QVariantMap KeeperTask::state() const
666+{
667+ Q_D(const KeeperTask);
668+ return d->state();
669+}
670
671=== added file 'src/service/keeper-task.h'
672--- src/service/keeper-task.h 1970-01-01 00:00:00 +0000
673+++ src/service/keeper-task.h 2016-08-30 14:16:37 +0000
674@@ -0,0 +1,73 @@
675+/*
676+ * Copyright (C) 2016 Canonical, Ltd.
677+ *
678+ * This program is free software: you can redistribute it and/or modify it
679+ * under the terms of the GNU General Public License version 3, as published
680+ * by the Free Software Foundation.
681+ *
682+ * This program is distributed in the hope that it will be useful, but
683+ * WITHOUT ANY WARRANTY; without even the implied warranties of
684+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
685+ * PURPOSE. See the GNU General Public License for more details.
686+ *
687+ * You should have received a copy of the GNU General Public License along
688+ * with this program. If not, see <http://www.gnu.org/licenses/>.
689+ *
690+ * Authors:
691+ * Xavi Garcia <xavi.garcia.mena@canoincal.com>
692+ * Charles Kerr <charles.kerr@canoincal.com>
693+ */
694+
695+#pragma once
696+
697+#include "helper/metadata.h"
698+#include "helper/backup-helper.h"
699+#include "helper/helper.h"
700+
701+#include <QObject>
702+#include <QSharedPointer>
703+
704+class HelperRegistry;
705+class KeeperTaskPrivate;
706+class StorageFrameworkClient;
707+
708+class KeeperTask : public QObject
709+{
710+ Q_OBJECT
711+ Q_DECLARE_PRIVATE(KeeperTask)
712+public:
713+
714+ enum class TaskType { BACKUP, RESTORE };
715+
716+ struct TaskData
717+ {
718+ QString action;
719+ QString error;
720+ KeeperTask::TaskType type;
721+ Metadata metadata;
722+ float percent_done;
723+ };
724+
725+ KeeperTask(TaskData const & task_data,
726+ QSharedPointer<HelperRegistry> const & helper_registry,
727+ QSharedPointer<StorageFrameworkClient> const & storage,
728+ QObject *parent = nullptr);
729+ virtual ~KeeperTask();
730+
731+ Q_DISABLE_COPY(KeeperTask)
732+
733+ bool start();
734+ QVariantMap state() const;
735+
736+ Q_SIGNALS:
737+ void task_state_changed(Helper::State state);
738+ void task_socket_ready(int socket_descriptor);
739+
740+protected:
741+ KeeperTask(KeeperTaskPrivate & d, QObject *parent = nullptr);
742+ virtual QStringList get_helper_urls() const = 0;
743+ virtual void init_helper() = 0;
744+
745+protected:
746+ QScopedPointer<KeeperTaskPrivate> const d_ptr;
747+};
748
749=== modified file 'src/service/keeper.cpp'
750--- src/service/keeper.cpp 2016-08-19 16:12:41 +0000
751+++ src/service/keeper.cpp 2016-08-30 14:16:37 +0000
752@@ -18,68 +18,34 @@
753 * Charles Kerr <charles.kerr@canonical.com>
754 */
755
756-#include "app-const.h" // DEKKO_APP_ID
757-#include "helper/backup-helper.h"
758 #include "helper/metadata.h"
759 #include "service/metadata-provider.h"
760 #include "service/keeper.h"
761 #include "storage-framework/storage_framework_client.h"
762-#include "util/dbus-utils.h"
763+#include "task-manager.h"
764
765 #include <QDebug>
766 #include <QDBusMessage>
767 #include <QDBusConnection>
768 #include <QSharedPointer>
769-#include <QScopedPointer>
770-#include <QVariant>
771 #include <QVector>
772
773-#include <uuid/uuid.h>
774-
775-
776 class KeeperPrivate
777 {
778- enum class TaskType { BACKUP, RESTORE };
779-
780- struct TaskData
781- {
782- QString action;
783- QString error;
784- TaskType type;
785- Metadata metadata;
786- float percent_done;
787- };
788-
789 public:
790-
791- QScopedPointer<BackupHelper> backup_helper_;
792- QScopedPointer<StorageFrameworkClient> storage_;
793+ QScopedPointer<TaskManager> task_manager_;
794
795 KeeperPrivate(Keeper* keeper,
796 const QSharedPointer<HelperRegistry>& helper_registry,
797 const QSharedPointer<MetadataProvider>& backup_choices,
798 const QSharedPointer<MetadataProvider>& restore_choices)
799- : backup_helper_(new BackupHelper(DEKKO_APP_ID))
800+ : q_ptr(keeper)
801 , storage_(new StorageFrameworkClient())
802- , q_ptr(keeper)
803 , helper_registry_(helper_registry)
804 , backup_choices_(backup_choices)
805 , restore_choices_(restore_choices)
806 {
807- // listen for backup helper state changes
808- QObject::connect(backup_helper_.data(), &Helper::state_changed,
809- std::bind(&KeeperPrivate::on_helper_state_changed, this, std::placeholders::_1)
810- );
811-
812- // listen for backup helper process changes
813- QObject::connect(backup_helper_.data(), &Helper::percent_done_changed,
814- std::bind(&KeeperPrivate::on_helper_percent_done_changed, this, std::placeholders::_1)
815- );
816-
817- // listen for the storage framework to finish
818- QObject::connect(storage_.data(), &StorageFrameworkClient::finished,
819- std::bind(&KeeperPrivate::on_storage_framework_finished, this)
820- );
821+ task_manager_.reset(new TaskManager(helper_registry, storage_, get_backup_choices(), get_restore_choices()));
822 }
823
824 ~KeeperPrivate() =default;
825@@ -88,42 +54,7 @@
826
827 void start_tasks(QStringList const& uuids)
828 {
829- if (!remaining_tasks_.isEmpty())
830- {
831- // FIXME: return a dbus error here
832- qWarning() << "keeper is already active";
833- return;
834- }
835-
836- // rebuild the state variables
837- state_.clear();
838- task_data_.clear();
839- all_tasks_.clear();
840- current_task_.clear();
841- remaining_tasks_.clear();
842- for(auto const& uuid : uuids)
843- {
844- Metadata m;
845- TaskType type;
846- if (!find_task_metadata(uuid, m, type))
847- {
848- // TODO Report error to user
849- qCritical() << "uuid" << uuid << "not found; skipping";
850- continue;
851- }
852-
853- all_tasks_ << uuid;
854- remaining_tasks_ << uuid;
855-
856- auto& td = task_data_[uuid];
857- td.metadata = m;
858- td.type = type;
859- td.action = QStringLiteral("queued");
860-
861- update_task_state(td);
862- }
863-
864- start_next_task();
865+ task_manager_->start_tasks(uuids);
866 }
867
868 QVector<Metadata> get_backup_choices() const
869@@ -144,27 +75,21 @@
870
871 QVariantDictMap get_state() const
872 {
873- return state_;
874+ return task_manager_->get_state();
875 }
876
877 QDBusUnixFileDescriptor start_backup(QDBusConnection bus, const QDBusMessage& msg, quint64 n_bytes)
878 {
879- qDebug("Helper::StartBackup(n_bytes=%zu)", size_t(n_bytes));
880+
881+ qDebug("Keeper::StartBackup(n_bytes=%zu)", size_t(n_bytes));
882
883 // the next time we get a socket from storage-framework, return it to our caller
884 auto tag = new int {};
885- auto on_socket_ready = [bus,msg,n_bytes,tag,this](std::shared_ptr<QLocalSocket> const& sf_socket)
886+ auto on_socket_ready = [bus,msg,n_bytes,tag,this](int backup_reply)
887 {
888- if (sf_socket)
889- {
890- qDebug("calling helper.set_storage_framework_socket(n_bytes=%zu socket=%d)",
891- size_t(n_bytes),
892- int(sf_socket->socketDescriptor()));
893- backup_helper_->set_expected_size(n_bytes);
894- backup_helper_->set_storage_framework_socket(sf_socket);
895- }
896+ qDebug("BackupManager returned socket %d", backup_reply);
897 auto reply = msg.createReply();
898- reply << QVariant::fromValue(QDBusUnixFileDescriptor(backup_helper_->get_helper_socket()));
899+ reply << QVariant::fromValue(QDBusUnixFileDescriptor(backup_reply));
900 bus.send(reply);
901
902 // one-shot client, so disconnect now
903@@ -174,14 +99,14 @@
904 // cppcheck-suppress deallocuse
905 *tag = remember_connection(
906 QObject::connect(
907- storage_.data(),
908- &StorageFrameworkClient::socketReady,
909+ task_manager_.data(),
910+ &TaskManager::socket_ready,
911 on_socket_ready
912 )
913 );
914
915- // ask storage framework for a new socket
916- storage_->getNewFileForBackup(n_bytes);
917+ qDebug() << "Asking for an storage framework socket to the task manager";
918+ task_manager_->ask_for_storage_framework_socket(n_bytes);
919
920 // tell the caller that we'll be responding async
921 msg.setDelayedReply(true);
922@@ -190,225 +115,6 @@
923
924 private:
925
926- void on_helper_state_changed(Helper::State state)
927- {
928- switch (state)
929- {
930- case Helper::State::NOT_STARTED:
931- break;
932-
933- case Helper::State::STARTED:
934- qDebug() << "Backup helper started";
935- break;
936-
937- case Helper::State::CANCELLED:
938- set_current_task_action(QStringLiteral("cancelled"));
939- qDebug() << "Backup helper cancelled... closing the socket.";
940- storage_->finish(false);
941- break;
942-
943- case Helper::State::FAILED:
944- set_current_task_action(QStringLiteral("failed"));
945- qDebug() << "Backup helper failed... closing the socket.";
946- storage_->finish(false);
947- break;
948-
949- case Helper::State::COMPLETE:
950- task_data_[current_task_].percent_done = 1;
951- set_current_task_action(QStringLiteral("complete"));
952- qDebug() << "Backup helper finished... closing the socket.";
953- try
954- {
955- storage_->finish(true);
956- }
957- catch (std::exception & e)
958- {
959- qDebug() << "Failed finishing sf... seting the state to failed";
960- set_current_task_action(QStringLiteral("failed"));
961- }
962- break;
963- }
964- }
965-
966- void on_helper_percent_done_changed(float /*percent_done*/)
967- {
968- update_task_state(current_task_);
969- }
970-
971- void on_storage_framework_finished()
972- {
973- qDebug() << "storage framework has finished for the current task";
974- start_next_task();
975- }
976-
977- /***
978- **** Task Queueing
979- ***/
980-
981- void start_next_task()
982- {
983- bool started {false};
984-
985- while (!started && !remaining_tasks_.isEmpty())
986- started = start_task(remaining_tasks_.takeFirst());
987-
988- if (!started)
989- clear_current_task();
990- }
991-
992- bool start_task(QString const& uuid)
993- {
994- if (!task_data_.contains(uuid))
995- {
996- qCritical() << "no task data found for" << uuid;
997- return false;
998- }
999-
1000- auto& td = task_data_[uuid];
1001- if (td.type == TaskType::BACKUP)
1002- {
1003- qDebug() << "Task is a backup task";
1004-
1005- const auto urls = helper_registry_->get_backup_helper_urls(td.metadata);
1006- if (urls.isEmpty())
1007- {
1008- td.action = "failed";
1009- td.error = "no helper information in registry";
1010- qWarning() << "ERROR: uuid: " << uuid << " has no url";
1011- return false;
1012- }
1013-
1014- set_current_task(uuid);
1015- set_current_task_action(QStringLiteral("saving"));
1016- backup_helper_->start(urls);
1017- return true;
1018- }
1019- else // RESTORE
1020- {
1021- set_current_task(uuid);
1022- set_current_task_action(QStringLiteral("restoring"));
1023- qWarning() << "restore not implemented yet";
1024- return false;
1025- }
1026- }
1027-
1028- /***
1029- **** State
1030- ***/
1031-
1032- void update_task_state(QString const& uuid)
1033- {
1034- auto it = task_data_.find(uuid);
1035- if (it == task_data_.end())
1036- {
1037- qCritical() << "no task data for" << uuid;
1038- return;
1039- }
1040-
1041- update_task_state(it.value());
1042- }
1043-
1044- void update_task_state(TaskData& td)
1045- {
1046- state_[td.metadata.uuid()] = calculate_task_state(td);
1047-
1048-#if 0
1049- // FIXME: we don't need this to work correctly for the sprint because Marcus is polling in a loop
1050- // but we will need this in order for him to stop doing that
1051-
1052- // TODO: compare old and new and decide if it's worth emitting a PropertyChanged signal;
1053- // eg don't contribute to dbus noise for minor speed fluctuations
1054-
1055- // TODO: this function is called inside a loop when initializing the state
1056- // after start_tasks(), so also ensure we don't have a notify flood here
1057-
1058- DBusUtils::notifyPropertyChanged(
1059- QDBusConnection::sessionBus(),
1060- *q_ptr,
1061- DBusTypes::KEEPER_USER_PATH,
1062- DBusTypes::KEEPER_USER_INTERFACE,
1063- QStringList(QStringLiteral("State"))
1064- );
1065-#endif
1066- }
1067-
1068- QVariantMap calculate_task_state(TaskData& td) const
1069- {
1070- QVariantMap ret;
1071-
1072- auto const uuid = td.metadata.uuid();
1073- bool const current = uuid == current_task_;
1074-
1075- ret.insert(QStringLiteral("action"), td.action);
1076-
1077- ret.insert(QStringLiteral("display-name"), td.metadata.display_name());
1078-
1079- // FIXME: assuming backup_helper_ for now...
1080- int32_t speed {};
1081- if (current)
1082- speed = backup_helper_->speed();
1083- ret.insert(QStringLiteral("speed"), speed);
1084-
1085- if (current)
1086- td.percent_done = backup_helper_->percent_done();
1087- ret.insert(QStringLiteral("percent-done"), double(td.percent_done));
1088-
1089- if (td.action == "failed")
1090- ret.insert(QStringLiteral("error"), td.error);
1091-
1092- ret.insert(QStringLiteral("uuid"), uuid);
1093-
1094- return ret;
1095- }
1096-
1097- void set_current_task(QString const& uuid)
1098- {
1099- auto const prev = current_task_;
1100-
1101- current_task_ = uuid;
1102-
1103- if (!prev.isEmpty())
1104- update_task_state(prev);
1105-
1106- if (!uuid.isEmpty())
1107- update_task_state(uuid);
1108- }
1109-
1110- void clear_current_task()
1111- {
1112- set_current_task(QString());
1113- }
1114-
1115- void set_current_task_action(QString const& action)
1116- {
1117- auto& td = task_data_[current_task_];
1118- td.action = action;
1119- update_task_state(td);
1120- }
1121-
1122- /***
1123- **** Misc
1124- ***/
1125-
1126- bool find_task_metadata(QString const& uuid, Metadata& setme_task, TaskType& setme_type) const
1127- {
1128- for (const auto& c : get_backup_choices()) {
1129- if (c.uuid() == uuid) {
1130- setme_task = c;
1131- setme_type = TaskType::BACKUP;
1132- return true;
1133- }
1134- }
1135- for (const auto& c : get_restore_choices()) {
1136- if (c.uuid() == uuid) {
1137- setme_task = c;
1138- setme_type = TaskType::RESTORE;
1139- return true;
1140- }
1141- }
1142- return false;
1143- }
1144-
1145 /***
1146 ****
1147 ***/
1148@@ -437,18 +143,13 @@
1149 ***/
1150
1151 Keeper * const q_ptr;
1152+ QSharedPointer<StorageFrameworkClient> storage_;
1153 QSharedPointer<HelperRegistry> helper_registry_;
1154 QSharedPointer<MetadataProvider> backup_choices_;
1155 QSharedPointer<MetadataProvider> restore_choices_;
1156 mutable QVector<Metadata> cached_backup_choices_;
1157 mutable QVector<Metadata> cached_restore_choices_;
1158- QStringList all_tasks_;
1159- QStringList remaining_tasks_;
1160- QString current_task_;
1161- QVariantDictMap state_;
1162 QMap<int,std::shared_ptr<QMetaObject::Connection>> connections_;
1163-
1164- mutable QMap<QString,TaskData> task_data_;
1165 };
1166
1167
1168
1169=== added directory 'src/service/private'
1170=== added file 'src/service/private/keeper-task_p.h'
1171--- src/service/private/keeper-task_p.h 1970-01-01 00:00:00 +0000
1172+++ src/service/private/keeper-task_p.h 2016-08-30 14:16:37 +0000
1173@@ -0,0 +1,54 @@
1174+/*
1175+ * Copyright (C) 2016 Canonical, Ltd.
1176+ *
1177+ * This program is free software: you can redistribute it and/or modify it
1178+ * under the terms of the GNU General Public License version 3, as published
1179+ * by the Free Software Foundation.
1180+ *
1181+ * This program is distributed in the hope that it will be useful, but
1182+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1183+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1184+ * PURPOSE. See the GNU General Public License for more details.
1185+ *
1186+ * You should have received a copy of the GNU General Public License along
1187+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1188+ *
1189+ * Authors:
1190+ * Xavi Garcia <xavi.garcia.mena@canoincal.com>
1191+ * Charles Kerr <charles.kerr@canoincal.com>
1192+ */
1193+
1194+#pragma once
1195+#include "../keeper-task.h"
1196+
1197+class KeeperTaskPrivate
1198+{
1199+ Q_DECLARE_PUBLIC(KeeperTask)
1200+public:
1201+ KeeperTaskPrivate(KeeperTask * keeper_task,
1202+ KeeperTask::TaskData const & task_data,
1203+ QSharedPointer<HelperRegistry> const & helper_registry,
1204+ QSharedPointer<StorageFrameworkClient> const & storage);
1205+
1206+ virtual ~KeeperTaskPrivate();
1207+
1208+ bool start();
1209+ QVariantMap state() const;
1210+ void ask_for_storage_framework_socket(quint64 n_bytes);
1211+
1212+protected:
1213+ void set_current_task_action(QString const& action);
1214+ void on_helper_percent_done_changed(float percent_done);
1215+ QVariantMap calculate_task_state();
1216+ void calculate_and_notify_state(Helper::State state);
1217+ void on_backup_socket_ready(std::shared_ptr<QLocalSocket> const & sf_socket);
1218+
1219+ virtual void on_helper_state_changed(Helper::State state);
1220+
1221+ KeeperTask * const q_ptr;
1222+ KeeperTask::TaskData task_data_;
1223+ QSharedPointer<HelperRegistry> helper_registry_;
1224+ QSharedPointer<StorageFrameworkClient> storage_;
1225+ QSharedPointer<Helper> helper_;
1226+ QVariantMap state_;
1227+};
1228
1229=== added file 'src/service/task-manager.cpp'
1230--- src/service/task-manager.cpp 1970-01-01 00:00:00 +0000
1231+++ src/service/task-manager.cpp 2016-08-30 14:16:37 +0000
1232@@ -0,0 +1,327 @@
1233+/*
1234+ * Copyright (C) 2016 Canonical, Ltd.
1235+ *
1236+ * This program is free software: you can redistribute it and/or modify it
1237+ * under the terms of the GNU General Public License version 3, as published
1238+ * by the Free Software Foundation.
1239+ *
1240+ * This program is distributed in the hope that it will be useful, but
1241+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1242+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1243+ * PURPOSE. See the GNU General Public License for more details.
1244+ *
1245+ * You should have received a copy of the GNU General Public License along
1246+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1247+ *
1248+ * Authors:
1249+ * Xavi Garcia <xavi.garcia.mena@canoincal.com>
1250+ * Charles Kerr <charles.kerr@canoincal.com>
1251+ */
1252+
1253+#include "helper/metadata.h"
1254+#include "keeper-task-backup.h"
1255+#include "storage-framework/storage_framework_client.h"
1256+#include "task-manager.h"
1257+
1258+class TaskManagerPrivate
1259+{
1260+public:
1261+ TaskManagerPrivate(TaskManager * manager,
1262+ QSharedPointer<HelperRegistry> const & helper_registry,
1263+ QSharedPointer<StorageFrameworkClient> const & storage,
1264+ QVector<Metadata> const & backup_metadata,
1265+ QVector<Metadata> const & restore_metadata)
1266+ : q_ptr(manager)
1267+ , helper_registry_(helper_registry)
1268+ , backup_metadata_(backup_metadata)
1269+ , restore_metadata_(restore_metadata)
1270+ , storage_(storage)
1271+ {
1272+ }
1273+
1274+ ~TaskManagerPrivate() = default;
1275+
1276+ /***
1277+ **** Task Queueing public
1278+ ***/
1279+
1280+ void start_tasks(QStringList const & task_uuids)
1281+ {
1282+ if (!remaining_tasks_.isEmpty())
1283+ {
1284+ // FIXME: return a dbus error here
1285+ qWarning() << "keeper is already active";
1286+ return;
1287+ }
1288+
1289+ // rebuild the state variables
1290+ state_.clear();
1291+ task_data_.clear();
1292+ current_task_.clear();
1293+ remaining_tasks_.clear();
1294+
1295+ for(auto const& uuid : task_uuids)
1296+ {
1297+ Metadata m;
1298+ KeeperTask::TaskType type;
1299+ if (!find_task_metadata(uuid, m, type))
1300+ {
1301+ // TODO Report error to user
1302+ qCritical() << "uuid" << uuid << "not found; skipping";
1303+ continue;
1304+ }
1305+
1306+ remaining_tasks_ << uuid;
1307+
1308+ auto& td = task_data_[uuid];
1309+ td.metadata = m;
1310+ td.type = type;
1311+ td.action = QStringLiteral("queued"); // TODO i18n
1312+
1313+// update_task_state(td);
1314+ }
1315+
1316+ start_next_task();
1317+ }
1318+
1319+ /***
1320+ *** State public
1321+ ***/
1322+
1323+ QVariantDictMap get_state() const
1324+ {
1325+ return state_;
1326+ }
1327+
1328+ void on_helper_state_changed(Helper::State state)
1329+ {
1330+ qDebug() << "Task State changed";
1331+ auto& td = task_data_[current_task_];
1332+ update_task_state(td);
1333+
1334+ switch (state)
1335+ {
1336+ case Helper::State::NOT_STARTED:
1337+ case Helper::State::STARTED:
1338+ case Helper::State::CANCELLED:
1339+ case Helper::State::FAILED:
1340+ break;
1341+
1342+ case Helper::State::DATA_COMPLETE:
1343+ task_data_[current_task_].percent_done = 1;
1344+ break;
1345+ case Helper::State::COMPLETE:
1346+ qDebug() << "STARTING NEXT TASK ---------------------------------------";
1347+ start_next_task();
1348+ break;
1349+ }
1350+ }
1351+
1352+ void ask_for_storage_framework_socket(quint64 n_bytes)
1353+ {
1354+ qDebug() << "Starting backup";
1355+ if (task_)
1356+ {
1357+ auto backup_task_ = qSharedPointerDynamicCast<KeeperTaskBackup>(task_);
1358+ if (!backup_task_)
1359+ {
1360+ qWarning() << "Only backup tasks are allowed to ask for storage framework sockets";
1361+ // TODO Mark this as an error at the current task and move to the next task
1362+ return;
1363+ }
1364+ backup_task_->ask_for_storage_framework_socket(n_bytes);
1365+ }
1366+ }
1367+private:
1368+ /***
1369+ **** Task Queueing
1370+ ***/
1371+
1372+ bool start_task(QString const& uuid)
1373+ {
1374+ if (!task_data_.contains(uuid))
1375+ {
1376+ qCritical() << "no task data found for" << uuid;
1377+ return false;
1378+ }
1379+
1380+ auto& td = task_data_[uuid];
1381+
1382+ qDebug() << "Creating task for uuid = " << uuid;
1383+ // initialize a new task
1384+
1385+ task_.data()->disconnect();
1386+ if (td.type == KeeperTask::TaskType::BACKUP)
1387+ {
1388+ task_.reset(new KeeperTaskBackup(td, helper_registry_, storage_));
1389+ }
1390+ else
1391+ {
1392+ // TODO initialize a Restore task
1393+ }
1394+
1395+ qDebug() << "task created: " << state_;
1396+
1397+ set_current_task(uuid);
1398+
1399+ QObject::connect(task_.data(), &KeeperTask::task_state_changed,
1400+ std::bind(&TaskManagerPrivate::on_helper_state_changed, this, std::placeholders::_1)
1401+ );
1402+
1403+ QObject::connect(task_.data(), &KeeperTask::task_socket_ready,
1404+ std::bind(&TaskManager::socket_ready, q_ptr, std::placeholders::_1)
1405+ );
1406+
1407+
1408+ return task_->start();
1409+ }
1410+
1411+ void set_current_task(QString const& uuid)
1412+ {
1413+ auto const prev = current_task_;
1414+
1415+ current_task_ = uuid;
1416+
1417+ if (!uuid.isEmpty())
1418+ update_task_state(uuid);
1419+ }
1420+
1421+ void clear_current_task()
1422+ {
1423+ set_current_task(QString());
1424+ }
1425+
1426+ void start_next_task()
1427+ {
1428+ bool started {false};
1429+
1430+ while (!started && !remaining_tasks_.isEmpty())
1431+ started = start_task(remaining_tasks_.takeFirst());
1432+
1433+ if (!started)
1434+ clear_current_task();
1435+ }
1436+
1437+ /***
1438+ **** State
1439+ ***/
1440+
1441+ void update_task_state(QString const& uuid)
1442+ {
1443+ auto it = task_data_.find(uuid);
1444+ if (it == task_data_.end())
1445+ {
1446+ qCritical() << "no task data for" << uuid;
1447+ return;
1448+ }
1449+
1450+ update_task_state(it.value());
1451+ }
1452+
1453+ void update_task_state(KeeperTask::KeeperTask::TaskData& td)
1454+ {
1455+ state_[td.metadata.uuid()] = task_->state();
1456+
1457+#if 0
1458+ // FIXME: we don't need this to work correctly for the sprint because Marcus is polling in a loop
1459+ // but we will need this in order for him to stop doing that
1460+
1461+ // TODO: compare old and new and decide if it's worth emitting a PropertyChanged signal;
1462+ // eg don't contribute to dbus noise for minor speed fluctuations
1463+
1464+ // TODO: this function is called inside a loop when initializing the state
1465+ // after start_tasks(), so also ensure we don't have a notify flood here
1466+
1467+ DBusUtils::notifyPropertyChanged(
1468+ QDBusConnection::sessionBus(),
1469+ *q_ptr,
1470+ DBusTypes::KEEPER_USER_PATH,
1471+ DBusTypes::KEEPER_USER_INTERFACE,
1472+ QStringList(QStringLiteral("State"))
1473+ );
1474+#endif
1475+ }
1476+
1477+ void set_current_task_action(QString const& action)
1478+ {
1479+ auto& td = task_data_[current_task_];
1480+ td.action = action;
1481+ update_task_state(td);
1482+ }
1483+
1484+ /***
1485+ **** Misc
1486+ ***/
1487+
1488+ bool find_task_metadata(QString const& uuid, Metadata& setme_task, KeeperTask::TaskType & type) const
1489+ {
1490+ for (const auto& c : backup_metadata_)
1491+ {
1492+ if (c.uuid() == uuid) {
1493+ setme_task = c;
1494+ type = KeeperTask::TaskType::BACKUP;
1495+ return true;
1496+ }
1497+ }
1498+ for (const auto& c : restore_metadata_)
1499+ {
1500+ if (c.uuid() == uuid) {
1501+ setme_task = c;
1502+ type = KeeperTask::TaskType::RESTORE;
1503+ return true;
1504+ }
1505+ }
1506+ return false;
1507+ }
1508+
1509+ TaskManager * const q_ptr;
1510+ QSharedPointer<HelperRegistry> helper_registry_;
1511+ QVector<Metadata> backup_metadata_;
1512+ QVector<Metadata> restore_metadata_;
1513+ QSharedPointer<StorageFrameworkClient> storage_;
1514+
1515+ QStringList remaining_tasks_;
1516+ QString current_task_;
1517+
1518+ QVariantDictMap state_;
1519+ QSharedPointer<KeeperTask> task_;
1520+
1521+ mutable QMap<QString,KeeperTask::TaskData> task_data_;
1522+};
1523+
1524+/***
1525+****
1526+***/
1527+
1528+TaskManager::TaskManager(QSharedPointer<HelperRegistry> const & helper_registry,
1529+ QSharedPointer<StorageFrameworkClient> const & storage,
1530+ QVector<Metadata> const & backup_metadata,
1531+ QVector<Metadata> const & restore_metadata,
1532+ QObject *parent)
1533+ : QObject(parent)
1534+ , d_ptr(new TaskManagerPrivate(this, helper_registry, storage, backup_metadata, restore_metadata))
1535+{
1536+}
1537+
1538+TaskManager::~TaskManager() = default;
1539+
1540+void TaskManager::start_tasks(QStringList const & task_uuids)
1541+{
1542+ Q_D(TaskManager);
1543+
1544+ d->start_tasks(task_uuids);
1545+}
1546+
1547+QVariantDictMap TaskManager::get_state() const
1548+{
1549+ Q_D(const TaskManager);
1550+
1551+ return d->get_state();
1552+}
1553+
1554+void TaskManager::ask_for_storage_framework_socket(quint64 n_bytes)
1555+{
1556+ Q_D(TaskManager);
1557+
1558+ d->ask_for_storage_framework_socket(n_bytes);
1559+}
1560
1561=== added file 'src/service/task-manager.h'
1562--- src/service/task-manager.h 1970-01-01 00:00:00 +0000
1563+++ src/service/task-manager.h 2016-08-30 14:16:37 +0000
1564@@ -0,0 +1,60 @@
1565+/*
1566+ * Copyright (C) 2016 Canonical, Ltd.
1567+ *
1568+ * This program is free software: you can redistribute it and/or modify it
1569+ * under the terms of the GNU General Public License version 3, as published
1570+ * by the Free Software Foundation.
1571+ *
1572+ * This program is distributed in the hope that it will be useful, but
1573+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1574+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1575+ * PURPOSE. See the GNU General Public License for more details.
1576+ *
1577+ * You should have received a copy of the GNU General Public License along
1578+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1579+ *
1580+ * Authors:
1581+ * Xavi Garcia <xavi.garcia.mena@canoincal.com>
1582+ * Charles Kerr <charles.kerr@canoincal.com>
1583+ */
1584+
1585+#pragma once
1586+
1587+#include "helper/metadata.h"
1588+#include "keeper-task.h"
1589+#include "qdbus-stubs/dbus-types.h"
1590+
1591+#include <QObject>
1592+
1593+class HelperRegistry;
1594+class TaskManagerPrivate;
1595+class StorageFrameworkClient;
1596+
1597+class TaskManager : public QObject
1598+{
1599+ Q_OBJECT
1600+ Q_DECLARE_PRIVATE(TaskManager)
1601+public:
1602+
1603+ TaskManager(QSharedPointer<HelperRegistry> const & helper_registry,
1604+ QSharedPointer<StorageFrameworkClient> const & storage,
1605+ QVector<Metadata> const & backup_metadata,
1606+ QVector<Metadata> const & restore_metadata,
1607+ QObject *parent = nullptr);
1608+
1609+ virtual ~TaskManager();
1610+
1611+ Q_DISABLE_COPY(TaskManager)
1612+
1613+ void start_tasks(QStringList const & task_uuids);
1614+
1615+ QVariantDictMap get_state() const;
1616+
1617+ void ask_for_storage_framework_socket(quint64 n_bytes);
1618+
1619+Q_SIGNALS:
1620+ void socket_ready(int reply);
1621+
1622+private:
1623+ QScopedPointer<TaskManagerPrivate> const d_ptr;
1624+};
1625
1626=== modified file 'tests/unit/helper/fake-helper.h'
1627--- tests/unit/helper/fake-helper.h 2016-08-17 13:14:32 +0000
1628+++ tests/unit/helper/fake-helper.h 2016-08-30 14:16:37 +0000
1629@@ -33,4 +33,5 @@
1630
1631 void record_data_transferred(qint64 n) {Helper::record_data_transferred(n);}
1632 void set_expected_size(qint64 n) {Helper::set_expected_size(n);}
1633+ void on_helper_process_stopped() override {set_state(Helper::State::COMPLETE);};
1634 };

Subscribers

People subscribed via source and target branches

to all changes: