Merge lp:~ted/ubuntu-app-launch/system-app-watch into lp:ubuntu-app-launch

Proposed by Ted Gould
Status: Merged
Approved by: Marcus Tomlinson
Approved revision: 340
Merged at revision: 311
Proposed branch: lp:~ted/ubuntu-app-launch/system-app-watch
Merge into: lp:ubuntu-app-launch
Prerequisite: lp:~ted/ubuntu-app-launch/registry-cleanup
Diff against target: 1009 lines (+694/-39)
16 files modified
libubuntu-app-launch/app-store-legacy.cpp (+172/-0)
libubuntu-app-launch/app-store-legacy.h (+14/-0)
libubuntu-app-launch/info-watcher.h (+14/-0)
libubuntu-app-launch/registry-impl.cpp (+40/-14)
libubuntu-app-launch/registry-impl.h (+17/-2)
libubuntu-app-launch/registry.cpp (+10/-0)
libubuntu-app-launch/registry.h (+19/-0)
tests/CMakeLists.txt (+10/-0)
tests/app-store-legacy.cpp (+155/-0)
tests/eventually-fixture.h (+76/-0)
tests/jobs-systemd.cpp (+2/-2)
tests/libual-cpp-test.cc (+43/-8)
tests/libual-test.cc (+2/-8)
tests/registry-mock.h (+13/-0)
tests/test-directory.h (+94/-0)
tools/ubuntu-app-watch.cpp (+13/-5)
To merge this branch: bzr merge lp:~ted/ubuntu-app-launch/system-app-watch
Reviewer Review Type Date Requested Status
Pete Woods (community) Approve
Marcus Tomlinson (community) Approve
unity-api-1-bot continuous-integration Needs Fixing
Review via email: mp+321241@code.launchpad.net

Commit message

Watch system folders for apps added and removed

Description of the change

This branch adds to signals, appAdded and appRemoved, and populates the backend for them with the legacy backend. It sets up file watches on the appropriate directories and then signals when things are changed.

Also it adds a bit to the testing framework to not wait forever on futures. Hopefully this will help some with tests timing out.

To post a comment you must log in.
Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Marcus Tomlinson (marcustomlinson) wrote :

When trying to CMake with "-DCMAKE_BUILD_TYPE=coverage" I get the following errors (Have these tests since been removed?):

CMake Error at /usr/share/cmake/CoverageReport/EnableCoverageReport.cmake:91 (SET_PROPERTY):
  set_property could not find TARGET exec-util-test. Perhaps it has not yet
  been created.
Call Stack (most recent call first):
  CMakeLists.txt:131 (ENABLE_COVERAGE_REPORT)

CMake Error at /usr/share/cmake/CoverageReport/EnableCoverageReport.cmake:91 (SET_PROPERTY):
  set_property could not find TARGET failure-test. Perhaps it has not yet
  been created.
Call Stack (most recent call first):
  CMakeLists.txt:131 (ENABLE_COVERAGE_REPORT)

CMake Error at /usr/share/cmake/CoverageReport/EnableCoverageReport.cmake:91 (SET_PROPERTY):
  set_property could not find TARGET zg-test. Perhaps it has not yet been
  created.
Call Stack (most recent call first):
  CMakeLists.txt:131 (ENABLE_COVERAGE_REPORT)

Revision history for this message
Marcus Tomlinson (marcustomlinson) wrote :

The jobs-systems tests seem quite flaky. At least half the time I run them I get this mid-way through a test (no particular test, it varies from run to run):

systemd: Shutting down
DBus daemon: Shutdown

At this point the test process freezes. Looks like your test daemons are shutting down before the test is complete. Seen this at all?

Revision history for this message
Marcus Tomlinson (marcustomlinson) wrote :

Following from my last comment, I saw this same issue with libual-test (and I assume I'd probably see it with any other test using those test daemons). I restarted my machine and the tests began running smoother again. Zombie services hanging around perhaps? Sorry for the rambling, I don't just saying it as I see it.

Revision history for this message
Marcus Tomlinson (marcustomlinson) wrote :

While a brief scan of the code looks alright, I'd really like to run valgrind over the new code you've added to check the sanity of the memory handling.

I see that the bits you've added/changed to app-store-legacy.cpp, registry-impl.cpp, and registry.cpp are not covered in any tests. Would you be able to add some tests for these? Would be super handy! Also would be a shame to exclude this considering how good the coverage has been in this project thus far.

Revision history for this message
Marcus Tomlinson (marcustomlinson) wrote :

Changing to needs fixing.

review: Needs Fixing
Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :
review: Needs Fixing (continuous-integration)
334. By Ted Gould

Add handlers for app added and removed

335. By Ted Gould

C++ style

336. By Ted Gould

Split out the test directory helper

337. By Ted Gould

Add tests for the glue code in registry impl

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

FAILED: Continuous integration, rev:335
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/294/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build/1905/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/1912
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1694/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=zesty/1694/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1694
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1694/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=zesty/1694
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=zesty/1694/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/1694
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/1694/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=zesty/1694/console

Click here to trigger a rebuild:
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/294/rebuild

review: Needs Fixing (continuous-integration)
338. By Ted Gould

Forgot newlines

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

FAILED: Continuous integration, rev:337
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/295/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build/1906/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/1913
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1695
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1695/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=zesty/1695/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1695
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1695/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=zesty/1695
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=zesty/1695/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/1695/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=zesty/1695/console

Click here to trigger a rebuild:
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/295/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :
review: Needs Fixing (continuous-integration)
339. By Ted Gould

Registry changes

Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :
review: Needs Fixing (continuous-integration)
340. By Ted Gould

Fix the tests too

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

FAILED: Continuous integration, rev:340
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/300/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build/1911/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/1918
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1700
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1700/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=zesty/1700/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1700
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1700/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=zesty/1700/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/1700
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/1700/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=zesty/1700
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=zesty/1700/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/300/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Marcus Tomlinson (marcustomlinson) wrote :

+1

review: Approve
Revision history for this message
Pete Woods (pete-woods) wrote :

WOrks for me on the zesty u8 session.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'libubuntu-app-launch/app-store-legacy.cpp'
2--- libubuntu-app-launch/app-store-legacy.cpp 2017-04-04 06:03:50 +0000
3+++ libubuntu-app-launch/app-store-legacy.cpp 2017-04-04 06:03:50 +0000
4@@ -19,6 +19,7 @@
5
6 #include "app-store-legacy.h"
7 #include "application-impl-legacy.h"
8+#include "registry-impl.h"
9 #include "string-util.h"
10
11 #include <regex>
12@@ -192,6 +193,177 @@
13 return std::make_shared<app_impls::Legacy>(appid.appname, getReg());
14 }
15
16+/** Turns a directory changed event from a file monitor into an
17+ * internal signal. Makes sure we can deal with it first, and
18+ * then propegates up the stack. */
19+void Legacy::directoryChanged(GFile* file, GFileMonitorEvent type)
20+{
21+ g_debug("Getting event for '%s'", unique_gchar(g_file_get_path(file)).get());
22+
23+ auto filetype = g_file_query_file_type(file, G_FILE_QUERY_INFO_NONE, nullptr);
24+ if (filetype != G_FILE_TYPE_REGULAR && filetype != G_FILE_TYPE_UNKNOWN)
25+ {
26+ g_debug("\tNot a regular file");
27+ return;
28+ }
29+
30+ auto cdesktopname = unique_gchar(g_file_get_basename(file));
31+ if (!cdesktopname)
32+ {
33+ g_debug("\tNo basename");
34+ return;
35+ }
36+ std::string desktopname{cdesktopname.get()};
37+
38+ std::string appname;
39+ std::smatch match;
40+ if (std::regex_match(desktopname, match, desktop_remover))
41+ {
42+ appname = match[1].str();
43+ }
44+ else
45+ {
46+ return;
47+ }
48+
49+ auto reg = getReg();
50+
51+ switch (type)
52+ {
53+ case G_FILE_MONITOR_EVENT_CREATED:
54+ {
55+ auto app = std::make_shared<app_impls::Legacy>(AppID::AppName::from_raw(appname), reg);
56+
57+ appAdded_(app);
58+ break;
59+ }
60+ case G_FILE_MONITOR_EVENT_CHANGED:
61+ {
62+ auto app = std::make_shared<app_impls::Legacy>(AppID::AppName::from_raw(appname), reg);
63+
64+ infoChanged_(app);
65+ break;
66+ }
67+ case G_FILE_MONITOR_EVENT_DELETED:
68+ {
69+ AppID appid{AppID::Package::from_raw({}), AppID::AppName::from_raw(appname), AppID::Version::from_raw({})};
70+ if (verifyAppname(appid.package, appid.appname))
71+ {
72+ /* Check to see if we've got a shadow situation and we
73+ * can still build this app */
74+ auto app = std::make_shared<app_impls::Legacy>(AppID::AppName::from_raw(appname), reg);
75+ infoChanged_(app);
76+ }
77+ else
78+ {
79+ appRemoved_(appid);
80+ }
81+ break;
82+ }
83+ default:
84+ break;
85+ };
86+}
87+
88+/** Function that setups file monitors on all of the system application
89+ * directories and the user application directory. Any time an application
90+ * is added or removed or changed we send the appropriate signal up the
91+ * stack. */
92+void Legacy::setupMonitors()
93+{
94+ std::call_once(monitorsSetup_, [this]() {
95+ auto reg = getReg();
96+ monitors_ =
97+ reg->thread.executeOnThread<std::set<std::unique_ptr<GFileMonitor, unity::util::GObjectDeleter>>>([this]() {
98+ std::set<std::unique_ptr<GFileMonitor, unity::util::GObjectDeleter>> monitors;
99+
100+ auto monitorDir = [this](const gchar* dirname) {
101+ auto appdir = unique_gchar(g_build_filename(dirname, "applications", nullptr));
102+ auto gfile = unity::util::unique_gobject(g_file_new_for_path(appdir.get()));
103+
104+ if (!g_file_query_exists(gfile.get(), nullptr))
105+ {
106+ throw std::runtime_error{std::string{"Directory '"} + appdir.get() + "' doesn't exist"};
107+ }
108+
109+ if (g_file_query_file_type(gfile.get(), G_FILE_QUERY_INFO_NONE, nullptr) != G_FILE_TYPE_DIRECTORY)
110+ {
111+ throw std::runtime_error{std::string{"'"} + appdir.get() + "' is not a directory"};
112+ }
113+
114+ GError* error = nullptr;
115+ auto monitor = unity::util::unique_gobject(
116+ g_file_monitor_directory(gfile.get(), G_FILE_MONITOR_NONE, nullptr, &error));
117+
118+ if (error != nullptr)
119+ {
120+ std::string message = std::string{"Unable to create file monitor: "} + error->message;
121+ g_error_free(error);
122+ throw std::runtime_error{message};
123+ }
124+
125+ g_signal_connect(
126+ monitor.get(), "changed",
127+ G_CALLBACK(+[](GFileMonitor*, GFile* file, GFile*, GFileMonitorEvent type, gpointer user_data) {
128+ auto pthis = static_cast<Legacy*>(user_data);
129+ pthis->directoryChanged(file, type);
130+ }),
131+ this);
132+
133+ return monitor;
134+ };
135+
136+ auto dirs = g_get_system_data_dirs();
137+ for (int i = 0; dirs != nullptr && dirs[i] != nullptr; i++)
138+ {
139+ try
140+ {
141+ monitors.insert(monitorDir(dirs[i]));
142+ }
143+ catch (std::runtime_error& e)
144+ {
145+ g_debug("Unable to create directory monitor for system dir '%s': %s", dirs[i], e.what());
146+ }
147+ }
148+
149+ try
150+ {
151+ monitors.insert(monitorDir(g_get_user_data_dir()));
152+ }
153+ catch (std::runtime_error& e)
154+ {
155+ g_debug("Unable to create directory monitor for user data dir: %s", e.what());
156+ }
157+
158+ return monitors;
159+ });
160+ });
161+}
162+
163+/** Return the signal object, but make sure we have the
164+ * monitors setup first */
165+core::Signal<const std::shared_ptr<Application>&>& Legacy::infoChanged()
166+{
167+ setupMonitors();
168+ return infoChanged_;
169+}
170+
171+/** Return the signal object, but make sure we have the
172+ * monitors setup first */
173+core::Signal<const std::shared_ptr<Application>&>& Legacy::appAdded()
174+{
175+ setupMonitors();
176+ return appAdded_;
177+}
178+
179+/** Return the signal object, but make sure we have the
180+ * monitors setup first */
181+core::Signal<const AppID&>& Legacy::appRemoved()
182+{
183+ setupMonitors();
184+ return appRemoved_;
185+}
186+
187 } // namespace app_store
188 } // namespace app_launch
189 } // namespace ubuntu
190
191=== modified file 'libubuntu-app-launch/app-store-legacy.h'
192--- libubuntu-app-launch/app-store-legacy.h 2017-04-04 06:03:50 +0000
193+++ libubuntu-app-launch/app-store-legacy.h 2017-04-04 06:03:50 +0000
194@@ -21,6 +21,8 @@
195
196 #include "app-store-base.h"
197
198+#include <unity/util/GObjectMemory.h>
199+
200 namespace ubuntu
201 {
202 namespace app_launch
203@@ -46,6 +48,18 @@
204
205 /* Application Creation */
206 virtual std::shared_ptr<app_impls::Base> create(const AppID& appid) override;
207+
208+ /* Info watching */
209+ virtual core::Signal<const std::shared_ptr<Application>&>& infoChanged() override;
210+ virtual core::Signal<const std::shared_ptr<Application>&>& appAdded() override;
211+ virtual core::Signal<const AppID&>& appRemoved() override;
212+
213+private:
214+ std::set<std::unique_ptr<GFileMonitor, unity::util::GObjectDeleter>> monitors_;
215+ std::once_flag monitorsSetup_;
216+
217+ void directoryChanged(GFile* file, GFileMonitorEvent type);
218+ void setupMonitors();
219 };
220
221 } // namespace app_store
222
223=== modified file 'libubuntu-app-launch/info-watcher.h'
224--- libubuntu-app-launch/info-watcher.h 2017-04-04 06:03:50 +0000
225+++ libubuntu-app-launch/info-watcher.h 2017-04-04 06:03:50 +0000
226@@ -43,9 +43,23 @@
227 return infoChanged_;
228 }
229
230+ virtual core::Signal<const std::shared_ptr<Application>&>& appAdded()
231+ {
232+ return appAdded_;
233+ }
234+
235+ virtual core::Signal<const AppID&>& appRemoved()
236+ {
237+ return appRemoved_;
238+ }
239+
240 protected:
241 /** Signal for info changed on an application */
242 core::Signal<const std::shared_ptr<Application>&> infoChanged_;
243+ /** Signal for applications added */
244+ core::Signal<const std::shared_ptr<Application>&> appAdded_;
245+ /** Signal for applications removed */
246+ core::Signal<const AppID&> appRemoved_;
247
248 /** Accessor function to the registry that ensures we can still
249 get it, which we always should be able to, but in case. */
250
251=== modified file 'libubuntu-app-launch/registry-impl.cpp'
252--- libubuntu-app-launch/registry-impl.cpp 2017-04-04 06:03:50 +0000
253+++ libubuntu-app-launch/registry-impl.cpp 2017-04-04 06:03:50 +0000
254@@ -171,25 +171,51 @@
255 return watchingAppStarting_;
256 }
257
258+/** Sets up the signals down to the info watchers and we aggregate
259+ them up to users of UAL. We connect to all their signals and
260+ pass them up. */
261+void Registry::Impl::infoWatchersSetup()
262+{
263+ std::call_once(flag_infoWatchersSetup, [this] {
264+ g_debug("Info watchers signals setup");
265+
266+ /* Grab all the app stores and the ZG info watcher */
267+ std::list<std::shared_ptr<info_watcher::Base>> watchers{_appStores.begin(), _appStores.end()};
268+ watchers.push_back(getZgWatcher());
269+
270+ /* Connect each of their signals to us, and track that connection */
271+ for (const auto& watcher : watchers)
272+ {
273+ infoWatchers_.emplace_back(std::make_pair(
274+ watcher,
275+ infoWatcherConnections{
276+ watcher->infoChanged().connect(
277+ [this](const std::shared_ptr<Application>& app) { sig_appInfoUpdated(app); }),
278+ watcher->appAdded().connect([this](const std::shared_ptr<Application>& app) { sig_appAdded(app); }),
279+ watcher->appRemoved().connect([this](const AppID& appid) { sig_appRemoved(appid); }),
280+ }));
281+ }
282+ });
283+}
284+
285 core::Signal<const std::shared_ptr<Application>&>& Registry::Impl::appInfoUpdated()
286 {
287- std::call_once(flag_appInfoUpdated, [this] {
288- g_debug("App Info Updated Signal Initialized");
289-
290- std::list<std::shared_ptr<info_watcher::Base>> apps{_appStores.begin(), _appStores.end()};
291- apps.push_back(getZgWatcher());
292-
293- for (const auto& app : apps)
294- {
295- infoWatchers_.emplace_back(
296- std::make_pair(app, app->infoChanged().connect([this](const std::shared_ptr<Application>& app) {
297- sig_appInfoUpdated(app);
298- })));
299- }
300- });
301+ infoWatchersSetup();
302 return sig_appInfoUpdated;
303 }
304
305+core::Signal<const std::shared_ptr<Application>&>& Registry::Impl::appAdded()
306+{
307+ infoWatchersSetup();
308+ return sig_appAdded;
309+}
310+
311+core::Signal<const AppID&>& Registry::Impl::appRemoved()
312+{
313+ infoWatchersSetup();
314+ return sig_appRemoved;
315+}
316+
317 std::shared_ptr<Application> Registry::Impl::createApp(const AppID& appid)
318 {
319 for (const auto& appStore : appStores())
320
321=== modified file 'libubuntu-app-launch/registry-impl.h'
322--- libubuntu-app-launch/registry-impl.h 2017-04-04 06:03:50 +0000
323+++ libubuntu-app-launch/registry-impl.h 2017-04-04 06:03:50 +0000
324@@ -98,6 +98,8 @@
325 }
326
327 core::Signal<const std::shared_ptr<Application>&>& appInfoUpdated();
328+ core::Signal<const std::shared_ptr<Application>&>& appAdded();
329+ core::Signal<const AppID&>& appRemoved();
330
331 const std::list<std::shared_ptr<app_store::Base>>& appStores()
332 {
333@@ -156,10 +158,23 @@
334
335 /** Signal for application info changing */
336 core::Signal<const std::shared_ptr<Application>&> sig_appInfoUpdated;
337+ /** Signal for applications added */
338+ core::Signal<const std::shared_ptr<Application>&> sig_appAdded;
339+ /** Signal for applications removed */
340+ core::Signal<const AppID&> sig_appRemoved;
341+
342+ void infoWatchersSetup(const std::shared_ptr<Registry>& reg);
343 /** Flag to see if we've initialized the info watcher list */
344- std::once_flag flag_appInfoUpdated;
345+ std::once_flag flag_infoWatchersSetup;
346+ struct infoWatcherConnections
347+ {
348+ core::ScopedConnection infoUpdated;
349+ core::ScopedConnection appAdded;
350+ core::ScopedConnection appRemoved;
351+ };
352 /** List of info watchers along with a signal handle to our connection to their update signal */
353- std::list<std::pair<std::shared_ptr<info_watcher::Base>, core::ScopedConnection>> infoWatchers_;
354+ std::list<std::pair<std::shared_ptr<info_watcher::Base>, infoWatcherConnections>> infoWatchers_;
355+ void infoWatchersSetup();
356
357 /** ZG Info Watcher */
358 std::shared_ptr<info_watcher::Zeitgeist> zgWatcher_;
359
360=== modified file 'libubuntu-app-launch/registry.cpp'
361--- libubuntu-app-launch/registry.cpp 2017-04-04 06:03:50 +0000
362+++ libubuntu-app-launch/registry.cpp 2017-04-04 06:03:50 +0000
363@@ -154,5 +154,15 @@
364 return reg->impl->appInfoUpdated();
365 }
366
367+core::Signal<const std::shared_ptr<Application>&>& Registry::appAdded(const std::shared_ptr<Registry>& reg)
368+{
369+ return reg->impl->appAdded();
370+}
371+
372+core::Signal<const AppID&>& Registry::appRemoved(const std::shared_ptr<Registry>& reg)
373+{
374+ return reg->impl->appRemoved();
375+}
376+
377 } // namespace app_launch
378 } // namespace ubuntu
379
380=== modified file 'libubuntu-app-launch/registry.h'
381--- libubuntu-app-launch/registry.h 2017-04-04 06:03:50 +0000
382+++ libubuntu-app-launch/registry.h 2017-04-04 06:03:50 +0000
383@@ -137,6 +137,25 @@
384 static core::Signal<const std::shared_ptr<Application>&>& appInfoUpdated(
385 const std::shared_ptr<Registry>& reg = getDefault());
386
387+ /** Get the signal object that is signaled when there is a new application
388+ that has been added to the system.
389+
390+ \note This signal handler is activated on the UAL thread
391+
392+ \param reg Registry to get the handler from
393+ */
394+ static core::Signal<const std::shared_ptr<Application>&>& appAdded(
395+ const std::shared_ptr<Registry>& reg = getDefault());
396+
397+ /** Get the signal object that is signaled when an application is
398+ removed from the system.
399+
400+ \note This signal handler is activated on the UAL thread
401+
402+ \param reg Registry to get the handler from
403+ */
404+ static core::Signal<const AppID&>& appRemoved(const std::shared_ptr<Registry>& reg = getDefault());
405+
406 /** The Application Manager, almost always if you're not Unity8, don't
407 use this API. Testing is a special case. Subclass this interface and
408 implement these functions.
409
410=== modified file 'tests/CMakeLists.txt'
411--- tests/CMakeLists.txt 2017-03-14 16:46:00 +0000
412+++ tests/CMakeLists.txt 2017-04-04 06:03:50 +0000
413@@ -50,6 +50,15 @@
414 add_test (NAME libual-test COMMAND libual-test)
415 add_test (NAME libual-cpp-test COMMAND libual-cpp-test)
416
417+# App Store Legacy
418+
419+add_executable (app-store-legacy
420+ app-store-legacy.cpp)
421+target_link_libraries (app-store-legacy ${GMOCK_LIBRARIES} ${GTEST_MAIN_LIBRARIES} launcher-static ${DBUSTEST_LIBRARIES})
422+
423+add_test(NAME app-store-legacy COMMAND "${CMAKE_CURRENT_BINARY_DIR}/app-store-legacy" --gtest_list_tests "|" grep "\"^ \"" "|" xargs -n 1 printf "\"--gtest_filter=*.%s\\n\"" "|" xargs -n 1 "${CMAKE_CURRENT_BINARY_DIR}/app-store-legacy")
424+
425+
426 # Jobs Base Test
427
428 add_executable (jobs-base-test
429@@ -129,6 +138,7 @@
430 add_custom_target(format-tests
431 COMMAND clang-format -i -style=file
432 application-info-desktop.cpp
433+ app-store-legacy.cpp
434 libual-cpp-test.cc
435 libual-test.cc
436 list-apps.cpp
437
438=== added file 'tests/app-store-legacy.cpp'
439--- tests/app-store-legacy.cpp 1970-01-01 00:00:00 +0000
440+++ tests/app-store-legacy.cpp 2017-04-04 06:03:50 +0000
441@@ -0,0 +1,155 @@
442+/*
443+ * Copyright © 2017 Canonical Ltd.
444+ *
445+ * This program is free software: you can redistribute it and/or modify it
446+ * under the terms of the GNU General Public License version 3, as published
447+ * by the Free Software Foundation.
448+ *
449+ * This program is distributed in the hope that it will be useful, but
450+ * WITHOUT ANY WARRANTY; without even the implied warranties of
451+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
452+ * PURPOSE. See the GNU General Public License for more details.
453+ *
454+ * You should have received a copy of the GNU General Public License along
455+ * with this program. If not, see <http://www.gnu.org/licenses/>.
456+ *
457+ * Authors:
458+ * Ted Gould <ted.gould@canonical.com>
459+ */
460+
461+#include "app-store-legacy.h"
462+
463+#include "eventually-fixture.h"
464+#include "registry-mock.h"
465+#include "test-directory.h"
466+#include <gmock/gmock.h>
467+#include <gtest/gtest.h>
468+#include <libdbustest/dbus-test.h>
469+#include <unity/util/GObjectMemory.h>
470+#include <unity/util/GlibMemory.h>
471+
472+class AppStoreLegacy : public EventuallyFixture
473+{
474+protected:
475+ std::unique_ptr<DbusTestService, unity::util::GObjectDeleter> service;
476+ std::shared_ptr<RegistryMock> registry;
477+
478+ virtual void SetUp()
479+ {
480+ setenv("XDG_DATA_DIRS", CMAKE_SOURCE_DIR, 1);
481+
482+ service = unity::util::unique_gobject(dbus_test_service_new(nullptr));
483+ dbus_test_service_start_tasks(service.get());
484+ registry = std::make_shared<RegistryMock>(std::list<std::shared_ptr<ubuntu::app_launch::app_store::Base>>{},
485+ std::shared_ptr<ubuntu::app_launch::jobs::manager::Base>{});
486+ }
487+};
488+
489+TEST_F(AppStoreLegacy, Init)
490+{
491+ auto store = std::make_shared<ubuntu::app_launch::app_store::Legacy>(registry->impl);
492+ store.reset();
493+}
494+
495+TEST_F(AppStoreLegacy, FindApp)
496+{
497+ TestDirectory testdir;
498+ testdir.addApp("testapp",
499+ {{G_KEY_FILE_DESKTOP_GROUP,
500+ {
501+ {G_KEY_FILE_DESKTOP_KEY_NAME, "Test App"},
502+ {G_KEY_FILE_DESKTOP_KEY_TYPE, "Application"},
503+ {G_KEY_FILE_DESKTOP_KEY_ICON, "foo.png"},
504+ {G_KEY_FILE_DESKTOP_KEY_EXEC, "foo"},
505+ }}});
506+
507+ auto store = std::make_shared<ubuntu::app_launch::app_store::Legacy>(registry->impl);
508+
509+ EXPECT_TRUE(store->verifyAppname(ubuntu::app_launch::AppID::Package::from_raw({}),
510+ ubuntu::app_launch::AppID::AppName::from_raw("testapp")));
511+}
512+
513+TEST_F(AppStoreLegacy, RemoveApp)
514+{
515+ TestDirectory testdir;
516+ testdir.addApp("testapp",
517+ {{G_KEY_FILE_DESKTOP_GROUP,
518+ {
519+ {G_KEY_FILE_DESKTOP_KEY_NAME, "Test App"},
520+ {G_KEY_FILE_DESKTOP_KEY_TYPE, "Application"},
521+ {G_KEY_FILE_DESKTOP_KEY_ICON, "foo.png"},
522+ {G_KEY_FILE_DESKTOP_KEY_EXEC, "foo"},
523+ }}});
524+
525+ auto store = std::make_shared<ubuntu::app_launch::app_store::Legacy>(registry->impl);
526+
527+ std::promise<std::string> removedAppId;
528+ store->appRemoved().connect([&](const ubuntu::app_launch::AppID &appid) { removedAppId.set_value(appid); });
529+
530+ testdir.removeApp("testapp");
531+
532+ EXPECT_EVENTUALLY_FUTURE_EQ(std::string{"testapp"}, removedAppId.get_future());
533+}
534+
535+TEST_F(AppStoreLegacy, AddedApp)
536+{
537+ TestDirectory testdir;
538+ auto store = std::make_shared<ubuntu::app_launch::app_store::Legacy>(registry->impl);
539+
540+ std::promise<std::string> addedAppId;
541+ store->appAdded().connect(
542+ [&](const std::shared_ptr<ubuntu::app_launch::Application> &app) { addedAppId.set_value(app->appId()); });
543+
544+ testdir.addApp("testapp",
545+ {{G_KEY_FILE_DESKTOP_GROUP,
546+ {
547+ {G_KEY_FILE_DESKTOP_KEY_NAME, "Test App"},
548+ {G_KEY_FILE_DESKTOP_KEY_TYPE, "Application"},
549+ {G_KEY_FILE_DESKTOP_KEY_ICON, "foo.png"},
550+ {G_KEY_FILE_DESKTOP_KEY_EXEC, "foo"},
551+ }}});
552+
553+ EXPECT_EVENTUALLY_FUTURE_EQ(std::string{"testapp"}, addedAppId.get_future());
554+}
555+
556+TEST_F(AppStoreLegacy, ShadowDelete)
557+{
558+ TestDirectory testdir;
559+ testdir.addApp("testapp",
560+ {{G_KEY_FILE_DESKTOP_GROUP,
561+ {
562+ {G_KEY_FILE_DESKTOP_KEY_NAME, "Test App"},
563+ {G_KEY_FILE_DESKTOP_KEY_TYPE, "Application"},
564+ {G_KEY_FILE_DESKTOP_KEY_ICON, "foo.png"},
565+ {G_KEY_FILE_DESKTOP_KEY_EXEC, "foo"},
566+ }}});
567+
568+ TestDirectory testdir2;
569+ testdir2.addApp("testapp",
570+ {{G_KEY_FILE_DESKTOP_GROUP,
571+ {
572+ {G_KEY_FILE_DESKTOP_KEY_NAME, "Test App"},
573+ {G_KEY_FILE_DESKTOP_KEY_TYPE, "Application"},
574+ {G_KEY_FILE_DESKTOP_KEY_ICON, "foo.png"},
575+ {G_KEY_FILE_DESKTOP_KEY_EXEC, "foo"},
576+ }}});
577+
578+ auto store = std::make_shared<ubuntu::app_launch::app_store::Legacy>(registry->impl);
579+
580+ std::promise<std::string> updatedAppId;
581+ store->infoChanged().connect(
582+ [&](const std::shared_ptr<ubuntu::app_launch::Application> &app) { updatedAppId.set_value(app->appId()); });
583+
584+ std::promise<std::string> deleteAppId;
585+ store->appRemoved().connect([&](const ubuntu::app_launch::AppID &appid) { deleteAppId.set_value(appid); });
586+ std::shared_future<std::string> deleteFuture = deleteAppId.get_future();
587+
588+ testdir.removeApp("testapp");
589+
590+ EXPECT_EVENTUALLY_FUTURE_EQ(std::string{"testapp"}, updatedAppId.get_future());
591+ EXPECT_NE(std::future_status::ready, deleteFuture.wait_for(std::chrono::seconds{0}));
592+
593+ testdir2.removeApp("testapp");
594+
595+ EXPECT_EVENTUALLY_FUTURE_EQ(std::string{"testapp"}, deleteFuture);
596+}
597
598=== modified file 'tests/eventually-fixture.h'
599--- tests/eventually-fixture.h 2017-01-18 20:43:25 +0000
600+++ tests/eventually-fixture.h 2017-04-04 06:03:50 +0000
601@@ -118,6 +118,37 @@
602 return eventuallyLoop(func); \
603 }
604
605+#define _EVENTUALLY_FUTURE_HELPER(oper) \
606+ template <typename comptype> \
607+ testing::AssertionResult eventuallyFutureHelper##oper(const char *desca, const char *descb, comptype expected, \
608+ std::future<comptype> future) \
609+ { \
610+ std::function<testing::AssertionResult(void)> func = [&]() { \
611+ auto status = future.wait_for(std::chrono::seconds{0}); \
612+ if (status != std::future_status::ready) \
613+ { \
614+ return testing::AssertionFailure(); \
615+ } \
616+ return testing::internal::CmpHelper##oper(desca, descb, expected, future.get()); \
617+ }; \
618+ return eventuallyLoop(func); \
619+ } \
620+ \
621+ template <typename comptype> \
622+ testing::AssertionResult eventuallyFutureHelper##oper(const char *desca, const char *descb, comptype expected, \
623+ std::shared_future<comptype> future) \
624+ { \
625+ std::function<testing::AssertionResult(void)> func = [&]() { \
626+ auto status = future.wait_for(std::chrono::seconds{0}); \
627+ if (status != std::future_status::ready) \
628+ { \
629+ return testing::AssertionFailure(); \
630+ } \
631+ return testing::internal::CmpHelper##oper(desca, descb, expected, future.get()); \
632+ }; \
633+ return eventuallyLoop(func); \
634+ }
635+
636 _EVENTUALLY_HELPER(EQ);
637 _EVENTUALLY_HELPER(NE);
638 _EVENTUALLY_HELPER(LT);
639@@ -132,8 +163,16 @@
640 _EVENTUALLY_FUNC_HELPER(STREQ);
641 _EVENTUALLY_FUNC_HELPER(STRNE);
642
643+ _EVENTUALLY_FUTURE_HELPER(EQ);
644+ _EVENTUALLY_FUTURE_HELPER(NE);
645+ _EVENTUALLY_FUTURE_HELPER(LT);
646+ _EVENTUALLY_FUTURE_HELPER(GT);
647+ _EVENTUALLY_FUTURE_HELPER(STREQ);
648+ _EVENTUALLY_FUTURE_HELPER(STRNE);
649+
650 #undef _EVENTUALLY_HELPER
651 #undef _EVENTUALLY_FUNC_HELPER
652+#undef _EVENTUALLY_FUTURE_HELPER
653 };
654
655 /* Helpers */
656@@ -209,3 +248,40 @@
657
658 #define ASSERT_EVENTUALLY_FUNC_STRNE(expected, actual) \
659 ASSERT_PRED_FORMAT2(EventuallyFixture::eventuallyFuncHelperSTRNE, expected, actual)
660+
661+/* Future Helpers */
662+#define EXPECT_EVENTUALLY_FUTURE_EQ(expected, actual) \
663+ EXPECT_PRED_FORMAT2(EventuallyFixture::eventuallyFutureHelperEQ, expected, actual)
664+
665+#define EXPECT_EVENTUALLY_FUTURE_NE(expected, actual) \
666+ EXPECT_PRED_FORMAT2(EventuallyFixture::eventuallyFutureHelperNE, expected, actual)
667+
668+#define EXPECT_EVENTUALLY_FUTURE_LT(expected, actual) \
669+ EXPECT_PRED_FORMAT2(EventuallyFixture::eventuallyFutureHelperLT, expected, actual)
670+
671+#define EXPECT_EVENTUALLY_FUTURE_GT(expected, actual) \
672+ EXPECT_PRED_FORMAT2(EventuallyFixture::eventuallyFutureHelperGT, expected, actual)
673+
674+#define EXPECT_EVENTUALLY_FUTURE_STREQ(expected, actual) \
675+ EXPECT_PRED_FORMAT2(EventuallyFixture::eventuallyFutureHelperSTREQ, expected, actual)
676+
677+#define EXPECT_EVENTUALLY_FUTURE_STRNE(expected, actual) \
678+ EXPECT_PRED_FORMAT2(EventuallyFixture::eventuallyFutureHelperSTRNE, expected, actual)
679+
680+#define ASSERT_EVENTUALLY_FUTURE_EQ(expected, actual) \
681+ ASSERT_PRED_FORMAT2(EventuallyFixture::eventuallyFutureHelperEQ, expected, actual)
682+
683+#define ASSERT_EVENTUALLY_FUTURE_NE(expected, actual) \
684+ ASSERT_PRED_FORMAT2(EventuallyFixture::eventuallyFutureHelperNE, expected, actual)
685+
686+#define ASSERT_EVENTUALLY_FUTURE_LT(expected, actual) \
687+ ASSERT_PRED_FORMAT2(EventuallyFixture::eventuallyFutureHelperLT, expected, actual)
688+
689+#define ASSERT_EVENTUALLY_FUTURE_GT(expected, actual) \
690+ ASSERT_PRED_FORMAT2(EventuallyFixture::eventuallyFutureHelperGT, expected, actual)
691+
692+#define ASSERT_EVENTUALLY_FUTURE_STREQ(expected, actual) \
693+ ASSERT_PRED_FORMAT2(EventuallyFixture::eventuallyFutureHelperSTREQ, expected, actual)
694+
695+#define ASSERT_EVENTUALLY_FUTURE_STRNE(expected, actual) \
696+ ASSERT_PRED_FORMAT2(EventuallyFixture::eventuallyFutureHelperSTRNE, expected, actual)
697
698=== modified file 'tests/jobs-systemd.cpp'
699--- tests/jobs-systemd.cpp 2017-04-04 06:03:50 +0000
700+++ tests/jobs-systemd.cpp 2017-04-04 06:03:50 +0000
701@@ -326,7 +326,7 @@
702 {defaultJobName(), std::string{multipleAppID()}, "1234", 1, {}}),
703 "/foo");
704
705- EXPECT_EQ(multipleAppID(), newunit.get_future().get());
706+ EXPECT_EVENTUALLY_FUTURE_EQ(multipleAppID(), newunit.get_future());
707 }
708
709 TEST_F(JobsSystemd, SignalRemove)
710@@ -362,7 +362,7 @@
711 {defaultJobName(), std::string{multipleAppID()}, "1234567890", 1, {}}),
712 "/foo");
713
714- EXPECT_EQ(multipleAppID(), removeunit.get_future().get());
715+ EXPECT_EVENTUALLY_FUTURE_EQ(multipleAppID(), removeunit.get_future());
716 }
717
718 TEST_F(JobsSystemd, UnitFailure)
719
720=== modified file 'tests/libual-cpp-test.cc'
721--- tests/libual-cpp-test.cc 2017-04-04 06:03:50 +0000
722+++ tests/libual-cpp-test.cc 2017-04-04 06:03:50 +0000
723@@ -44,6 +44,7 @@
724 #include "snapd-mock.h"
725 #include "spew-master.h"
726 #include "systemd-mock.h"
727+#include "test-directory.h"
728 #include "zg-mock.h"
729
730 #define LOCAL_SNAPD_TEST_SOCKET (SNAPD_TEST_SOCKET "-libual-cpp-test")
731@@ -1154,6 +1155,46 @@
732 g_spawn_command_line_sync("rm -rf " CMAKE_BINARY_DIR "/libual-proc", NULL, NULL, NULL, NULL);
733 }
734
735+TEST_F(LibUAL, AppInfoSignals)
736+{
737+ /* Setup the stores mock */
738+ auto mockstore = std::make_shared<MockStore>(registry->impl);
739+ registry =
740+ std::make_shared<RegistryMock>(std::list<std::shared_ptr<ubuntu::app_launch::app_store::Base>>{mockstore},
741+ std::shared_ptr<ubuntu::app_launch::jobs::manager::Base>{});
742+
743+ /* Build an app */
744+ auto singleappid = ubuntu::app_launch::AppID::find(registry, "single");
745+ auto myapp = std::make_shared<MockApp>(singleappid, registry->impl);
746+
747+ /* Setup an app added signal handler */
748+ std::promise<ubuntu::app_launch::AppID> addedAppId;
749+ ubuntu::app_launch::Registry::appAdded(registry).connect(
750+ [&](const std::shared_ptr<ubuntu::app_launch::Application>& app) { addedAppId.set_value(app->appId()); });
751+
752+ mockstore->mock_signalAppAdded(myapp);
753+
754+ EXPECT_EVENTUALLY_FUTURE_EQ(singleappid, addedAppId.get_future());
755+
756+ /* Setup an info changed signal handler */
757+ std::promise<ubuntu::app_launch::AppID> changedAppId;
758+ ubuntu::app_launch::Registry::appInfoUpdated(registry).connect(
759+ [&](const std::shared_ptr<ubuntu::app_launch::Application>& app) { changedAppId.set_value(app->appId()); });
760+
761+ mockstore->mock_signalAppInfoChanged(myapp);
762+
763+ EXPECT_EVENTUALLY_FUTURE_EQ(singleappid, changedAppId.get_future());
764+
765+ /* Setup an app removed signal handler */
766+ std::promise<ubuntu::app_launch::AppID> removedAppId;
767+ ubuntu::app_launch::Registry::appRemoved(registry).connect(
768+ [&](const ubuntu::app_launch::AppID& appid) { removedAppId.set_value(appid); });
769+
770+ mockstore->mock_signalAppRemoved(singleappid);
771+
772+ EXPECT_EVENTUALLY_FUTURE_EQ(singleappid, removedAppId.get_future());
773+}
774+
775 TEST_F(LibUAL, OOMSet)
776 {
777 g_setenv("UBUNTU_APP_LAUNCH_OOM_PROC_PATH", CMAKE_BINARY_DIR "/libual-proc", 1);
778@@ -1277,13 +1318,7 @@
779 });
780 t.detach();
781
782- auto outputfuture = outputpromise.get_future();
783- while (outputfuture.wait_for(std::chrono::milliseconds{1}) != std::future_status::ready)
784- {
785- pause();
786- }
787-
788- ASSERT_STREQ(filedata, outputfuture.get().c_str());
789+ EXPECT_EVENTUALLY_FUTURE_EQ(std::string{filedata}, outputpromise.get_future());
790
791 return;
792 }
793@@ -1376,7 +1411,7 @@
794 std::vector<std::string> execList{"Foo", "Bar", "Really really really long value", "Another value"};
795 ubuntu::app_launch::Helper::setExec(execList);
796
797- EXPECT_EQ(execList, socketpromise.get_future().get());
798+ EXPECT_EVENTUALLY_FUTURE_EQ(execList, socketpromise.get_future());
799 }
800
801 TEST_F(LibUAL, AppInfo)
802
803=== modified file 'tests/libual-test.cc'
804--- tests/libual-test.cc 2017-03-20 15:21:13 +0000
805+++ tests/libual-test.cc 2017-04-04 06:03:50 +0000
806@@ -892,13 +892,7 @@
807 });
808 t.detach();
809
810- auto outputfuture = outputpromise.get_future();
811- while (outputfuture.wait_for(std::chrono::milliseconds{1}) != std::future_status::ready)
812- {
813- pause();
814- }
815-
816- ASSERT_STREQ(filedata, outputfuture.get().c_str());
817+ EXPECT_EVENTUALLY_FUTURE_EQ(std::string{filedata}, outputpromise.get_future());
818
819 return;
820 }
821@@ -1002,7 +996,7 @@
822 .c_str(),
823 nullptr);
824
825- EXPECT_EQ(execList, socketpromise.get_future().get());
826+ EXPECT_EVENTUALLY_FUTURE_EQ(execList, socketpromise.get_future());
827 }
828
829 TEST_F(LibUAL, AppInfo)
830
831=== modified file 'tests/registry-mock.h'
832--- tests/registry-mock.h 2017-04-04 06:03:50 +0000
833+++ tests/registry-mock.h 2017-04-04 06:03:50 +0000
834@@ -54,6 +54,19 @@
835
836 /* Application Creation */
837 MOCK_METHOD1(create, std::shared_ptr<ubuntu::app_launch::app_impls::Base>(const ubuntu::app_launch::AppID&));
838+
839+ void mock_signalAppAdded(const std::shared_ptr<ubuntu::app_launch::Application>& app)
840+ {
841+ appAdded_(app);
842+ }
843+ void mock_signalAppRemoved(const ubuntu::app_launch::AppID& appid)
844+ {
845+ appRemoved_(appid);
846+ }
847+ void mock_signalAppInfoChanged(const std::shared_ptr<ubuntu::app_launch::Application>& app)
848+ {
849+ infoChanged_(app);
850+ }
851 };
852
853 class MockApp : public ubuntu::app_launch::app_impls::Base
854
855=== added file 'tests/test-directory.h'
856--- tests/test-directory.h 1970-01-01 00:00:00 +0000
857+++ tests/test-directory.h 2017-04-04 06:03:50 +0000
858@@ -0,0 +1,94 @@
859+/*
860+ * Copyright © 2017 Canonical Ltd.
861+ *
862+ * This program is free software: you can redistribute it and/or modify it
863+ * under the terms of the GNU General Public License version 3, as published
864+ * by the Free Software Foundation.
865+ *
866+ * This program is distributed in the hope that it will be useful, but
867+ * WITHOUT ANY WARRANTY; without even the implied warranties of
868+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
869+ * PURPOSE. See the GNU General Public License for more details.
870+ *
871+ * You should have received a copy of the GNU General Public License along
872+ * with this program. If not, see <http://www.gnu.org/licenses/>.
873+ *
874+ * Authors:
875+ * Ted Gould <ted.gould@canonical.com>
876+ */
877+
878+#include <unity/util/GlibMemory.h>
879+#include <string>
880+
881+class TestDirectory
882+{
883+ std::string dirname_;
884+ std::string appdir_;
885+
886+public:
887+ TestDirectory()
888+ {
889+ GError *error{nullptr};
890+
891+ auto dirname = ubuntu::app_launch::unique_gchar(g_dir_make_tmp("xdg-data-tmp-XXXXXX", &error));
892+ if (error != nullptr)
893+ {
894+ auto message = std::string{"Unable to create temporary directory: "} + error->message;
895+ g_error_free(error);
896+ throw std::runtime_error{message};
897+ }
898+ dirname_ = dirname.get();
899+ g_debug("Setting temp XDG_DATA directory: %s", dirname.get());
900+
901+ appdir_ = ubuntu::app_launch::unique_gchar(g_build_filename(dirname.get(), "applications", nullptr)).get();
902+ g_mkdir_with_parents(appdir_.c_str(), 0700);
903+
904+ auto datadirs =
905+ ubuntu::app_launch::unique_gchar(g_strdup_printf("%s:%s", dirname.get(), g_getenv("XDG_DATA_DIRS")));
906+ setenv("XDG_DATA_DIRS", datadirs.get(), 1);
907+ }
908+
909+ ~TestDirectory()
910+ {
911+ auto command = ubuntu::app_launch::unique_gchar(g_strdup_printf("rm -rf %s", dirname_.c_str()));
912+ g_spawn_command_line_sync(command.get(), nullptr, nullptr, nullptr, nullptr);
913+ g_debug("Removing test directory: %s", dirname_.c_str());
914+ }
915+
916+ void addApp(const std::string &appname,
917+ const std::list<std::pair<std::string, std::list<std::pair<std::string, std::string>>>> &keydata)
918+ {
919+ auto keyfile = unity::util::unique_glib(g_key_file_new());
920+
921+ for (const auto &groupset : keydata)
922+ {
923+ auto groupname = groupset.first;
924+ for (const auto &dataset : groupset.second)
925+ {
926+ auto key = dataset.first;
927+ auto value = dataset.second;
928+
929+ g_key_file_set_string(keyfile.get(), groupname.c_str(), key.c_str(), value.c_str());
930+ }
931+ }
932+
933+ auto path = ubuntu::app_launch::unique_gchar(
934+ g_build_filename(appdir_.c_str(), (appname + ".desktop").c_str(), nullptr));
935+ GError *error{nullptr};
936+
937+ g_key_file_save_to_file(keyfile.get(), path.get(), &error);
938+ if (error != nullptr)
939+ {
940+ auto message = std::string{"Unable to write desktop file for '"} + appname + "': " + error->message;
941+ g_error_free(error);
942+ throw std::runtime_error{message};
943+ }
944+ }
945+
946+ void removeApp(const std::string &appname)
947+ {
948+ auto path = ubuntu::app_launch::unique_gchar(
949+ g_build_filename(appdir_.c_str(), (appname + ".desktop").c_str(), nullptr));
950+ unlink(path.get());
951+ }
952+};
953
954=== modified file 'tools/ubuntu-app-watch.cpp'
955--- tools/ubuntu-app-watch.cpp 2017-02-08 17:34:52 +0000
956+++ tools/ubuntu-app-watch.cpp 2017-04-04 06:03:50 +0000
957@@ -29,16 +29,16 @@
958
959 registry.appStarted().connect([](const std::shared_ptr<ubuntu::app_launch::Application>& app,
960 const std::shared_ptr<ubuntu::app_launch::Application::Instance>& instance) {
961- std::cout << "Started: " << (std::string)app->appId() << std::endl;
962+ std::cout << "Started: " << std::string{app->appId()} << std::endl;
963 });
964 registry.appStopped().connect([](const std::shared_ptr<ubuntu::app_launch::Application>& app,
965 const std::shared_ptr<ubuntu::app_launch::Application::Instance>& instance) {
966- std::cout << "Stopped: " << (std::string)app->appId() << std::endl;
967+ std::cout << "Stopped: " << std::string{app->appId()} << std::endl;
968 });
969 registry.appPaused().connect([](const std::shared_ptr<ubuntu::app_launch::Application>& app,
970 const std::shared_ptr<ubuntu::app_launch::Application::Instance>& instance,
971 const std::vector<pid_t>& pids) {
972- std::cout << "Paused: " << (std::string)app->appId() << " (";
973+ std::cout << "Paused: " << std::string{app->appId()} << " (";
974
975 for (auto pid : pids)
976 {
977@@ -50,7 +50,7 @@
978 registry.appResumed().connect([](const std::shared_ptr<ubuntu::app_launch::Application>& app,
979 const std::shared_ptr<ubuntu::app_launch::Application::Instance>& instance,
980 const std::vector<pid_t>& pids) {
981- std::cout << "Resumed: " << (std::string)app->appId() << " (";
982+ std::cout << "Resumed: " << std::string{app->appId()} << " (";
983
984 for (auto pid : pids)
985 {
986@@ -62,7 +62,7 @@
987 registry.appFailed().connect([](const std::shared_ptr<ubuntu::app_launch::Application>& app,
988 const std::shared_ptr<ubuntu::app_launch::Application::Instance>& instance,
989 ubuntu::app_launch::Registry::FailureType type) {
990- std::cout << "Failed: " << (std::string)app->appId();
991+ std::cout << "Failed: " << std::string{app->appId()};
992 switch (type)
993 {
994 case ubuntu::app_launch::Registry::FailureType::CRASH:
995@@ -74,6 +74,14 @@
996 }
997 std::cout << std::endl;
998 });
999+ registry.appAdded().connect([](const std::shared_ptr<ubuntu::app_launch::Application>& app) {
1000+ std::cout << "Added: " << std::string{app->appId()} << std::endl;
1001+ });
1002+ registry.appRemoved().connect(
1003+ [](const ubuntu::app_launch::AppID& appid) { std::cout << "Removed: " << std::string{appid} << std::endl; });
1004+ registry.appInfoUpdated().connect([](const std::shared_ptr<ubuntu::app_launch::Application>& app) {
1005+ std::cout << "Updated: " << std::string{app->appId()} << std::endl;
1006+ });
1007
1008 std::signal(SIGTERM, [](int signal) -> void { retval.set_value(EXIT_SUCCESS); });
1009 return retval.get_future().get();

Subscribers

People subscribed via source and target branches