Merge lp:~marcustomlinson/keeper/qml-plugin into lp:keeper/devel

Proposed by Marcus Tomlinson
Status: Merged
Approved by: Charles Kerr
Approved revision: 108
Merge reported by: Charles Kerr
Merged at revision: not available
Proposed branch: lp:~marcustomlinson/keeper/qml-plugin
Merge into: lp:keeper/devel
Prerequisite: lp:~marcustomlinson/keeper/link-against-real-storage-framework
Diff against target: 579 lines (+356/-54)
12 files modified
CMakeLists.txt (+8/-2)
debian/changelog (+1/-1)
debian/control (+10/-0)
debian/qml-keeper.install (+1/-0)
include/client/client.h (+36/-8)
src/cli/main.cpp (+1/-1)
src/client/CMakeLists.txt (+5/-14)
src/client/client.cpp (+180/-28)
src/client/qml-plugin/CMakeLists.txt (+42/-0)
src/client/qml-plugin/plugin.cpp (+37/-0)
src/client/qml-plugin/plugin.h (+33/-0)
src/client/qml-plugin/qmldir (+2/-0)
To merge this branch: bzr merge lp:~marcustomlinson/keeper/qml-plugin
Reviewer Review Type Date Requested Status
Charles Kerr (community) Approve
unity-api-1-bot continuous-integration Needs Fixing
Review via email: mp+304050@code.launchpad.net

Commit message

Add client QML API

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

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

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

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2016-08-26 09:36:01 +0000
3+++ CMakeLists.txt 2016-08-26 09:36:01 +0000
4@@ -1,7 +1,12 @@
5 # Version
6 set(KEEPER_MAJOR "0")
7-set(KEEPER_MINOR "0")
8-set(KEEPER_MICRO "1")
9+set(KEEPER_MINOR "1")
10+set(KEEPER_MICRO "0")
11+
12+set(
13+ KEEPER_CLIENT_LIB
14+ keeper-client-${KEEPER_MAJOR}.${KEEPER_MINOR}
15+)
16
17 # Default install location. Must be set here, before setting the project.
18 if(NOT DEFINED CMAKE_INSTALL_PREFIX)
19@@ -52,6 +57,7 @@
20 find_package(Threads REQUIRED)
21
22 set(CMAKE_AUTOMOC ON)
23+set(CMAKE_INCLUDE_CURRENT_DIR ON)
24
25 add_definitions(
26 -DQT_NO_KEYWORDS=1
27
28=== modified file 'debian/changelog'
29--- debian/changelog 2016-06-21 08:04:21 +0000
30+++ debian/changelog 2016-08-26 09:36:01 +0000
31@@ -1,4 +1,4 @@
32-keeper (0.0.1) UNRELEASED; urgency=medium
33+keeper (0.1.0) UNRELEASED; urgency=medium
34
35 * Initial release.
36
37
38=== modified file 'debian/control'
39--- debian/control 2016-08-12 01:40:55 +0000
40+++ debian/control 2016-08-26 09:36:01 +0000
41@@ -66,6 +66,16 @@
42 Description: Client library for Keeper
43 Runtime support for Keeper clients.
44
45+Package: qml-keeper
46+Architecture: any
47+Pre-Depends: ${misc:Pre-Depends}
48+Multi-Arch: same
49+Depends: ${misc:Depends},
50+ ${shlibs:Depends},
51+ libkeeper-client (= ${binary:Version}),
52+Description: QML bindings for Keeper
53+ This package contains the qtdeclarative bindings for Ubuntu Keeper.
54+
55 Package: keeper-client-dev
56 Section: libdevel
57 Architecture: any
58
59=== added file 'debian/qml-keeper.install'
60--- debian/qml-keeper.install 1970-01-01 00:00:00 +0000
61+++ debian/qml-keeper.install 2016-08-26 09:36:01 +0000
62@@ -0,0 +1,1 @@
63+usr/lib/*/qt5/qml
64
65=== modified file 'include/client/client.h'
66--- include/client/client.h 2016-08-11 00:25:59 +0000
67+++ include/client/client.h 2016-08-26 09:36:01 +0000
68@@ -19,30 +19,58 @@
69
70 #pragma once
71
72-#define KEEPER_EXPORT __attribute__((visibility("default")))
73-
74 #include <QObject>
75 #include <QScopedPointer>
76+#include <QStringList>
77 #include <QVariant>
78
79 class KeeperClientPrivate;
80
81-class KEEPER_EXPORT KeeperClient final : public QObject
82+class Q_DECL_EXPORT KeeperClient : public QObject
83 {
84 Q_OBJECT
85 Q_DISABLE_COPY(KeeperClient)
86
87+// QML
88 public:
89 explicit KeeperClient(QObject* parent = nullptr);
90 ~KeeperClient();
91
92- QMap<QString, QVariantMap> GetBackupChoices() const;
93- void StartBackup(QStringList const& uuids) const;
94-
95- QMap<QString, QVariantMap> GetState() const;
96+ Q_PROPERTY(QStringList backupUuids READ backupUuids CONSTANT)
97+ QStringList backupUuids();
98+
99+ Q_PROPERTY(QString status READ status NOTIFY statusChanged)
100+ QString status();
101+
102+ Q_PROPERTY(double progress READ progress NOTIFY progressChanged)
103+ double progress();
104+
105+ Q_PROPERTY(bool readyToBackup READ readyToBackup NOTIFY readyToBackupChanged)
106+ bool readyToBackup();
107+
108+ Q_PROPERTY(bool backupBusy READ backupBusy NOTIFY backupBusyChanged)
109+ bool backupBusy();
110+
111+ Q_INVOKABLE QString getBackupName(QString uuid);
112+ Q_INVOKABLE void enableBackup(QString uuid, bool enabled);
113+ Q_INVOKABLE void startBackup();
114+
115+// C++
116+public:
117+ QMap<QString, QVariantMap> getBackupChoices() const;
118+ void startBackup(QStringList const& uuids) const;
119+
120+ QMap<QString, QVariantMap> getState() const;
121
122 Q_SIGNALS:
123+ void statusChanged();
124+ void progressChanged();
125+ void readyToBackupChanged();
126+ void backupBusyChanged();
127+
128+private Q_SLOTS:
129+ void stateUpdated();
130
131 private:
132- QScopedPointer<KeeperClientPrivate> const d_ptr;
133+ QScopedPointer<KeeperClientPrivate> const d;
134 };
135
136=== modified file 'src/cli/main.cpp'
137--- src/cli/main.cpp 2016-08-10 07:46:28 +0000
138+++ src/cli/main.cpp 2016-08-26 09:36:01 +0000
139@@ -21,7 +21,7 @@
140 #include <dbus-types.h>
141 #include <util/logging.h>
142
143-#include "keeper_user_interface.h"
144+#include <keeper_user_interface.h>
145
146 #include <QCoreApplication>
147 #include <QDBusConnection>
148
149=== modified file 'src/client/CMakeLists.txt'
150--- src/client/CMakeLists.txt 2016-08-10 07:43:36 +0000
151+++ src/client/CMakeLists.txt 2016-08-26 09:36:01 +0000
152@@ -1,7 +1,4 @@
153-set(
154- CLIENT_LIB
155- keeper-client-${KEEPER_MAJOR}.${KEEPER_MINOR}
156-)
157+add_subdirectory(qml-plugin)
158
159 set(
160 CLIENT_HEADERS
161@@ -9,19 +6,13 @@
162 )
163
164 add_library(
165- ${CLIENT_LIB} SHARED
166+ ${KEEPER_CLIENT_LIB} SHARED
167 client.cpp
168 ${CLIENT_HEADERS}
169 )
170
171-set_target_properties(
172- ${CLIENT_LIB}
173- PROPERTIES
174- AUTOMOC TRUE
175-)
176-
177 target_link_libraries(
178- ${CLIENT_LIB}
179+ ${KEEPER_CLIENT_LIB}
180 qdbus-stubs
181 Qt5::Core
182 Qt5::DBus
183@@ -30,7 +21,7 @@
184 set(
185 COVERAGE_REPORT_TARGETS
186 ${COVERAGE_REPORT_TARGETS}
187- ${CLIENT_LIB}
188+ ${KEEPER_CLIENT_LIB}
189 PARENT_SCOPE
190 )
191
192@@ -40,7 +31,7 @@
193 )
194
195 install(
196- TARGETS ${CLIENT_LIB}
197+ TARGETS ${KEEPER_CLIENT_LIB}
198 LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
199 )
200
201
202=== modified file 'src/client/client.cpp'
203--- src/client/client.cpp 2016-08-10 02:06:08 +0000
204+++ src/client/client.cpp 2016-08-26 09:36:01 +0000
205@@ -17,41 +17,149 @@
206 * Marcus Tomlinson <marcus.tomlinson@canonical.com>
207 */
208
209+#include <QTimer>
210+
211 #include <client/client.h>
212
213 #include <qdbus-stubs/keeper_user_interface.h>
214
215-class KeeperClientPrivate final
216+struct KeeperClientPrivate final
217 {
218 Q_DISABLE_COPY(KeeperClientPrivate)
219
220-public:
221- KeeperClientPrivate()
222- : user_iface(new DBusInterfaceKeeperUser(
223- DBusTypes::KEEPER_SERVICE,
224- DBusTypes::KEEPER_USER_PATH,
225- QDBusConnection::sessionBus()
226- ))
227+ KeeperClientPrivate(QObject* parent)
228+ : userIface(new DBusInterfaceKeeperUser(
229+ DBusTypes::KEEPER_SERVICE,
230+ DBusTypes::KEEPER_USER_PATH,
231+ QDBusConnection::sessionBus()
232+ )),
233+ status(""),
234+ progress(0),
235+ readyToBackup(false),
236+ backupBusy(false),
237+ stateTimer(parent)
238 {
239 }
240
241 ~KeeperClientPrivate() = default;
242
243- QScopedPointer<DBusInterfaceKeeperUser> user_iface;
244+ QScopedPointer<DBusInterfaceKeeperUser> userIface;
245+ QString status;
246+ QMap<QString, QVariantMap> backups;
247+ double progress;
248+ bool readyToBackup;
249+ bool backupBusy;
250+ QTimer stateTimer;
251 };
252
253-KeeperClient::KeeperClient(QObject* parent)
254- : QObject{parent}
255- , d_ptr(new KeeperClientPrivate())
256+KeeperClient::KeeperClient(QObject* parent) :
257+ QObject(parent),
258+ d(new KeeperClientPrivate(this))
259 {
260 DBusTypes::registerMetaTypes();
261+
262+ // Store backups list locally with an additional "enabled" pair to keep track enabled states
263+ // TODO: We should be listening to a backupChoicesChanged signal to keep this list updated
264+ d->backups = getBackupChoices();
265+ for(auto iter = d->backups.begin(); iter != d->backups.end(); ++iter)
266+ {
267+ iter.value()["enabled"] = false;
268+ }
269+
270+ connect(&d->stateTimer, &QTimer::timeout, this, &KeeperClient::stateUpdated);
271 }
272
273 KeeperClient::~KeeperClient() = default;
274
275-QMap<QString, QVariantMap> KeeperClient::GetBackupChoices() const
276-{
277- QDBusReply<QMap<QString, QVariantMap>> choices = d_ptr->user_iface->call("GetBackupChoices");
278+QStringList KeeperClient::backupUuids()
279+{
280+ QStringList returnList;
281+ for(auto iter = d->backups.begin(); iter != d->backups.end(); ++iter)
282+ {
283+ // TODO: We currently only support "folder" type backups
284+ if (iter.value().value("type").toString() == "folder")
285+ {
286+ returnList.append(iter.key());
287+ }
288+ }
289+ return returnList;
290+}
291+
292+QString KeeperClient::status()
293+{
294+ return d->status;
295+}
296+
297+double KeeperClient::progress()
298+{
299+ return d->progress;
300+}
301+
302+bool KeeperClient::readyToBackup()
303+{
304+ return d->readyToBackup;
305+}
306+
307+bool KeeperClient::backupBusy()
308+{
309+ return d->backupBusy;
310+}
311+
312+void KeeperClient::enableBackup(QString uuid, bool enabled)
313+{
314+ d->backups[uuid]["enabled"] = enabled;
315+
316+ for (auto const& backup : d->backups)
317+ {
318+ d->readyToBackup = false;
319+
320+ if (backup.value("enabled") == true)
321+ {
322+ d->readyToBackup = true;
323+ break;
324+ }
325+ }
326+
327+ Q_EMIT readyToBackupChanged();
328+}
329+
330+void KeeperClient::startBackup()
331+{
332+ // TODO: Instead of polling for state, we should be listening to a stateChanged signal
333+ if (!d->stateTimer.isActive())
334+ {
335+ d->stateTimer.start(200);
336+ }
337+
338+ // Determine which backups are enabled, and start only those
339+ QStringList backupList;
340+ for(auto iter = d->backups.begin(); iter != d->backups.end(); ++iter)
341+ {
342+ if (iter.value().value("enabled").toBool())
343+ {
344+ backupList.append(iter.key());
345+ }
346+ }
347+
348+ if (!backupList.empty())
349+ {
350+ startBackup(backupList);
351+
352+ d->status = "Preparing Backup...";
353+ Q_EMIT statusChanged();
354+ d->backupBusy = true;
355+ Q_EMIT backupBusyChanged();
356+ }
357+}
358+
359+QString KeeperClient::getBackupName(QString uuid)
360+{
361+ return d->backups.value(uuid).value("display-name").toString();
362+}
363+
364+QMap<QString, QVariantMap> KeeperClient::getBackupChoices() const
365+{
366+ QDBusReply<QMap<QString, QVariantMap>> choices = d->userIface->call("GetBackupChoices");
367
368 if (!choices.isValid())
369 {
370@@ -62,17 +170,61 @@
371 return choices.value();
372 }
373
374-void KeeperClient::StartBackup(const QStringList& uuids) const
375-{
376- QDBusReply<void> backup_reply = d_ptr->user_iface->call("StartBackup", uuids);
377-
378- if (!backup_reply.isValid())
379- {
380- qWarning() << "Error starting backup:" << backup_reply.error().message();
381- }
382-}
383-
384-QMap<QString, QVariantMap> KeeperClient::GetState() const
385-{
386- return d_ptr->user_iface->state();
387+void KeeperClient::startBackup(const QStringList& uuids) const
388+{
389+ QDBusReply<void> backupReply = d->userIface->call("StartBackup", uuids);
390+
391+ if (!backupReply.isValid())
392+ {
393+ qWarning() << "Error starting backup:" << backupReply.error().message();
394+ }
395+}
396+
397+QMap<QString, QVariantMap> KeeperClient::getState() const
398+{
399+ return d->userIface->state();
400+}
401+
402+void KeeperClient::stateUpdated()
403+{
404+ auto states = getState();
405+
406+ if (!states.empty())
407+ {
408+ // Calculate current total progress
409+ // TODO: May be better to monitor each backup's progress separately instead of total
410+ // to avoid irregular jumps in progress between larger and smaller backups
411+ double totalProgress = 0;
412+ for (auto const& state : states)
413+ {
414+ totalProgress += state.value("percent-done").toDouble();
415+ }
416+
417+ d->progress = totalProgress / states.count();
418+ Q_EMIT progressChanged();
419+
420+ // Update backup status
421+ if (d->progress > 0 && d->progress < 1)
422+ {
423+ d->status = "Backup In Progress...";
424+ Q_EMIT statusChanged();
425+
426+ d->backupBusy = true;
427+ Q_EMIT backupBusyChanged();
428+ }
429+ else if (d->progress >= 1)
430+ {
431+ d->status = "Backup Complete";
432+ Q_EMIT statusChanged();
433+
434+ d->backupBusy = false;
435+ Q_EMIT backupBusyChanged();
436+
437+ // Stop state timer
438+ if (d->stateTimer.isActive())
439+ {
440+ d->stateTimer.stop();
441+ }
442+ }
443+ }
444 }
445
446=== added directory 'src/client/qml-plugin'
447=== added file 'src/client/qml-plugin/CMakeLists.txt'
448--- src/client/qml-plugin/CMakeLists.txt 1970-01-01 00:00:00 +0000
449+++ src/client/qml-plugin/CMakeLists.txt 2016-08-26 09:36:01 +0000
450@@ -0,0 +1,42 @@
451+set(
452+ QML_KEEPER_CLIENT_LIB
453+ keeper-qml
454+)
455+
456+add_definitions(-DKEEPER_MAJOR=${KEEPER_MAJOR})
457+add_definitions(-DKEEPER_MINOR=${KEEPER_MINOR})
458+
459+include(QmlPlugins)
460+
461+find_package(Qt5Quick REQUIRED)
462+
463+include_directories(
464+ "${CMAKE_SOURCE_DIR}/include/client"
465+)
466+
467+set(KEEPER_QML_SRC
468+ plugin.cpp
469+)
470+
471+add_library(
472+ ${QML_KEEPER_CLIENT_LIB} SHARED
473+ ${KEEPER_QML_SRC}
474+)
475+
476+target_link_libraries(
477+ ${QML_KEEPER_CLIENT_LIB}
478+ ${KEEPER_CLIENT_LIB}
479+)
480+
481+qt5_use_modules(
482+ ${QML_KEEPER_CLIENT_LIB}
483+ Qml
484+)
485+
486+add_qmlplugin(
487+ Ubuntu.Keeper
488+ ${KEEPER_MAJOR}.${KEEPER_MINOR}
489+ Ubuntu/Keeper
490+ TARGET_PREFIX Keeper
491+ TARGETS ${QML_KEEPER_CLIENT_LIB}
492+)
493
494=== added file 'src/client/qml-plugin/plugin.cpp'
495--- src/client/qml-plugin/plugin.cpp 1970-01-01 00:00:00 +0000
496+++ src/client/qml-plugin/plugin.cpp 2016-08-26 09:36:01 +0000
497@@ -0,0 +1,37 @@
498+/*
499+ * Copyright © 2016 Canonical Ltd.
500+ *
501+ * This program is free software: you can redistribute it and/or modify it
502+ * under the terms of the GNU Lesser General Public License version 3,
503+ * as published by the Free Software Foundation.
504+ *
505+ * This program is distributed in the hope that it will be useful,
506+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
507+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
508+ * GNU Lesser General Public License for more details.
509+ *
510+ * You should have received a copy of the GNU Lesser General Public License
511+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
512+ *
513+ * Authors:
514+ * Marcus Tomlinson <marcus.tomlinson@canonical.com>
515+ */
516+
517+#include <QtQml>
518+#include <QtQml/QQmlContext>
519+
520+#include <plugin.h>
521+
522+#include <client.h>
523+
524+void QmlKeeperPlugin::registerTypes(const char *uri)
525+{
526+ Q_ASSERT(uri == QLatin1String("Ubuntu.Keeper"));
527+
528+ qmlRegisterType<KeeperClient>(uri, KEEPER_MAJOR, KEEPER_MINOR, "Keeper");
529+}
530+
531+void QmlKeeperPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
532+{
533+ QQmlExtensionPlugin::initializeEngine(engine, uri);
534+}
535
536=== added file 'src/client/qml-plugin/plugin.h'
537--- src/client/qml-plugin/plugin.h 1970-01-01 00:00:00 +0000
538+++ src/client/qml-plugin/plugin.h 2016-08-26 09:36:01 +0000
539@@ -0,0 +1,33 @@
540+/*
541+ * Copyright © 2016 Canonical Ltd.
542+ *
543+ * This program is free software: you can redistribute it and/or modify it
544+ * under the terms of the GNU Lesser General Public License version 3,
545+ * as published by the Free Software Foundation.
546+ *
547+ * This program is distributed in the hope that it will be useful,
548+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
549+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
550+ * GNU Lesser General Public License for more details.
551+ *
552+ * You should have received a copy of the GNU Lesser General Public License
553+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
554+ *
555+ * Authors:
556+ * Marcus Tomlinson <marcus.tomlinson@canonical.com>
557+ */
558+
559+#pragma once
560+
561+#include <QtQml/QQmlEngine>
562+#include <QtQml/QQmlExtensionPlugin>
563+
564+class QmlKeeperPlugin : public QQmlExtensionPlugin
565+{
566+ Q_OBJECT
567+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
568+
569+public:
570+ void registerTypes(const char *uri) override;
571+ void initializeEngine(QQmlEngine *engine, const char *uri) override;
572+};
573
574=== added file 'src/client/qml-plugin/qmldir'
575--- src/client/qml-plugin/qmldir 1970-01-01 00:00:00 +0000
576+++ src/client/qml-plugin/qmldir 2016-08-26 09:36:01 +0000
577@@ -0,0 +1,2 @@
578+module Ubuntu.Keeper
579+plugin keeper-qml

Subscribers

People subscribed via source and target branches