Merge lp:~ted/ubuntu-app-launch/abstract-jobs into lp:ubuntu-app-launch/16.10

Proposed by Ted Gould
Status: Superseded
Proposed branch: lp:~ted/ubuntu-app-launch/abstract-jobs
Merge into: lp:ubuntu-app-launch/16.10
Diff against target: 6212 lines (+3149/-2127)
34 files modified
docs/index.rst (+40/-10)
libubuntu-app-launch/CMakeLists.txt (+4/-0)
libubuntu-app-launch/application-impl-base.cpp (+0/-766)
libubuntu-app-launch/application-impl-base.h (+0/-69)
libubuntu-app-launch/application-impl-click.cpp (+6/-19)
libubuntu-app-launch/application-impl-legacy.cpp (+6/-32)
libubuntu-app-launch/application-impl-libertine.cpp (+7/-17)
libubuntu-app-launch/application-impl-snap.cpp (+6/-17)
libubuntu-app-launch/application.cpp (+7/-0)
libubuntu-app-launch/jobs-base.cpp (+392/-0)
libubuntu-app-launch/jobs-base.h (+117/-0)
libubuntu-app-launch/jobs-upstart.cpp (+899/-0)
libubuntu-app-launch/jobs-upstart.h (+75/-0)
libubuntu-app-launch/libubuntu-app-launch.map (+1/-0)
libubuntu-app-launch/registry-impl.cpp (+494/-264)
libubuntu-app-launch/registry-impl.h (+78/-25)
libubuntu-app-launch/registry.cpp (+47/-54)
libubuntu-app-launch/registry.h (+120/-14)
libubuntu-app-launch/ubuntu-app-launch.cpp (+339/-413)
tests/CMakeLists.txt (+1/-0)
tests/failure-test.cc (+123/-96)
tests/libual-cpp-test.cc (+186/-141)
tools/CMakeLists.txt (+21/-2)
tools/ubuntu-app-info.cpp (+18/-9)
tools/ubuntu-app-launch.cpp (+56/-68)
tools/ubuntu-app-list-pids.cpp (+7/-3)
tools/ubuntu-app-list.cpp (+1/-1)
tools/ubuntu-app-pid.cpp (+8/-4)
tools/ubuntu-app-stop.cpp (+7/-3)
tools/ubuntu-app-triplet.cpp (+1/-1)
tools/ubuntu-app-watch.cpp (+61/-88)
tools/ubuntu-helper-list.cpp (+1/-1)
tools/ubuntu-helper-start.cpp (+10/-5)
tools/ubuntu-helper-stop.cpp (+10/-5)
To merge this branch: bzr merge lp:~ted/ubuntu-app-launch/abstract-jobs
Reviewer Review Type Date Requested Status
Unity API Team Pending
Review via email: mp+309408@code.launchpad.net

This proposal has been superseded by a proposal from 2016-11-10.

Commit message

Abstract out jobs backends and make Upstart one

To post a comment you must log in.
272. By Ted Gould

Make sure to grab numeric header as well

273. By Ted Gould

Merge in the app object signals branch

274. By Ted Gould

Merging in app object changes

275. By Ted Gould

Merge fail

276. By Ted Gould

Updated tests

277. By Ted Gould

Merging through trunk

278. By Ted Gould

Migrate tests to use pause/resume c++ signals

279. By Ted Gould

Merging app signals branch

280. By Ted Gould

Update to trunk

Unmerged revisions

280. By Ted Gould

Update to trunk

279. By Ted Gould

Merging app signals branch

278. By Ted Gould

Migrate tests to use pause/resume c++ signals

277. By Ted Gould

Merging through trunk

276. By Ted Gould

Updated tests

275. By Ted Gould

Merge fail

274. By Ted Gould

Merging in app object changes

273. By Ted Gould

Merge in the app object signals branch

272. By Ted Gould

Make sure to grab numeric header as well

271. By Ted Gould

Merge trunk

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'docs/index.rst'
2--- docs/index.rst 2016-08-04 14:04:40 +0000
3+++ docs/index.rst 2016-11-10 21:58:30 +0000
4@@ -146,6 +146,46 @@
5 :private-members:
6 :undoc-members:
7
8+Jobs Manager Base
9+-----------------
10+
11+.. doxygenclass:: ubuntu::app_launch::jobs::manager::Base
12+ :project: libubuntu-app-launch
13+ :members:
14+ :protected-members:
15+ :private-members:
16+ :undoc-members:
17+
18+Jobs Instance Base
19+------------------
20+
21+.. doxygenclass:: ubuntu::app_launch::jobs::instance::Base
22+ :project: libubuntu-app-launch
23+ :members:
24+ :protected-members:
25+ :private-members:
26+ :undoc-members:
27+
28+Jobs Manager Upstart
29+--------------------
30+
31+.. doxygenclass:: ubuntu::app_launch::jobs::manager::Upstart
32+ :project: libubuntu-app-launch
33+ :members:
34+ :protected-members:
35+ :private-members:
36+ :undoc-members:
37+
38+Jobs Instance Upstart
39+---------------------
40+
41+.. doxygenclass:: ubuntu::app_launch::jobs::instance::Upstart
42+ :project: libubuntu-app-launch
43+ :members:
44+ :protected-members:
45+ :private-members:
46+ :undoc-members:
47+
48 Registry Implementation
49 -----------------------
50
51@@ -176,16 +216,6 @@
52 :private-members:
53 :undoc-members:
54
55-Upstart Instance
56-----------------
57-
58-.. doxygenclass:: ubuntu::app_launch::app_impls::UpstartInstance
59- :project: libubuntu-app-launch
60- :members:
61- :protected-members:
62- :private-members:
63- :undoc-members:
64-
65 Quality
66 =======
67
68
69=== modified file 'libubuntu-app-launch/CMakeLists.txt'
70--- libubuntu-app-launch/CMakeLists.txt 2016-08-26 17:33:34 +0000
71+++ libubuntu-app-launch/CMakeLists.txt 2016-11-10 21:58:30 +0000
72@@ -52,6 +52,10 @@
73 helper-impl-click.cpp
74 glib-thread.h
75 glib-thread.cpp
76+jobs-base.h
77+jobs-base.cpp
78+jobs-upstart.h
79+jobs-upstart.cpp
80 )
81
82 set(LAUNCHER_SOURCES
83
84=== modified file 'libubuntu-app-launch/application-impl-base.cpp'
85--- libubuntu-app-launch/application-impl-base.cpp 2016-10-03 23:54:20 +0000
86+++ libubuntu-app-launch/application-impl-base.cpp 2016-11-10 21:58:30 +0000
87@@ -23,8 +23,6 @@
88 #include <map>
89 #include <numeric>
90
91-#include <upstart.h>
92-
93 #include "application-impl-base.h"
94 #include "helpers.h"
95 #include "registry-impl.h"
96@@ -97,770 +95,6 @@
97 return retval;
98 }
99
100-/** Checks to see if we have a primary PID for the instance */
101-bool UpstartInstance::isRunning()
102-{
103- return primaryPid() != 0;
104-}
105-
106-/** Uses Upstart to get the primary PID of the instance using Upstart's
107- DBus interface */
108-pid_t UpstartInstance::primaryPid()
109-{
110- auto jobpath = registry_->impl->upstartJobPath(job_);
111- if (jobpath.empty())
112- {
113- g_debug("Unable to get a valid job path");
114- return 0;
115- }
116-
117- return registry_->impl->thread.executeOnThread<pid_t>([this, &jobpath]() -> pid_t {
118- GError* error = nullptr;
119-
120- std::string instancename = std::string(appId_);
121- if (job_ != "application-click")
122- {
123- instancename += "-" + instance_;
124- }
125-
126- g_debug("Getting instance by name: %s", instance_.c_str());
127- GVariant* vinstance_path =
128- g_dbus_connection_call_sync(registry_->impl->_dbus.get(), /* connection */
129- DBUS_SERVICE_UPSTART, /* service */
130- jobpath.c_str(), /* object path */
131- DBUS_INTERFACE_UPSTART_JOB, /* iface */
132- "GetInstanceByName", /* method */
133- g_variant_new("(s)", instancename.c_str()), /* params */
134- G_VARIANT_TYPE("(o)"), /* return type */
135- G_DBUS_CALL_FLAGS_NONE, /* flags */
136- -1, /* timeout: default */
137- registry_->impl->thread.getCancellable().get(), /* cancellable */
138- &error);
139-
140- if (error != nullptr)
141- {
142- g_warning("Unable to get instance '%s' of job '%s': %s", instance_.c_str(), job_.c_str(), error->message);
143- g_error_free(error);
144- return 0;
145- }
146-
147- /* Jump rope to make this into a C++ type */
148- std::string instance_path;
149- gchar* cinstance_path = nullptr;
150- g_variant_get(vinstance_path, "(o)", &cinstance_path);
151- g_variant_unref(vinstance_path);
152- if (cinstance_path != nullptr)
153- {
154- instance_path = cinstance_path;
155- g_free(cinstance_path);
156- }
157-
158- if (instance_path.empty())
159- {
160- g_debug("No instance object for instance name: %s", instance_.c_str());
161- return 0;
162- }
163-
164- GVariant* props_tuple =
165- g_dbus_connection_call_sync(registry_->impl->_dbus.get(), /* connection */
166- DBUS_SERVICE_UPSTART, /* service */
167- instance_path.c_str(), /* object path */
168- "org.freedesktop.DBus.Properties", /* interface */
169- "GetAll", /* method */
170- g_variant_new("(s)", DBUS_INTERFACE_UPSTART_INSTANCE), /* params */
171- G_VARIANT_TYPE("(a{sv})"), /* return type */
172- G_DBUS_CALL_FLAGS_NONE, /* flags */
173- -1, /* timeout: default */
174- registry_->impl->thread.getCancellable().get(), /* cancellable */
175- &error);
176-
177- if (error != nullptr)
178- {
179- g_warning("Unable to name of properties '%s': %s", instance_path.c_str(), error->message);
180- g_error_free(error);
181- error = nullptr;
182- return 0;
183- }
184-
185- GVariant* props_dict = g_variant_get_child_value(props_tuple, 0);
186-
187- pid_t retval = 0;
188- GVariant* processes = g_variant_lookup_value(props_dict, "processes", G_VARIANT_TYPE("a(si)"));
189- if (processes != nullptr && g_variant_n_children(processes) > 0)
190- {
191-
192- GVariant* first_entry = g_variant_get_child_value(processes, 0);
193- GVariant* pidv = g_variant_get_child_value(first_entry, 1);
194-
195- retval = g_variant_get_int32(pidv);
196-
197- g_variant_unref(pidv);
198- g_variant_unref(first_entry);
199- }
200- else
201- {
202- g_debug("Unable to get 'processes' from properties of instance at path: %s", instance_path.c_str());
203- }
204-
205- g_variant_unref(props_dict);
206-
207- return retval;
208- });
209-}
210-
211-/** Generate the full name of the Upstart job for the job, the
212- instance and how all those fit together.
213-
214- Handles the special case of application-click which isn't designed
215- to have multi-instance apps.
216-*/
217-std::string UpstartInstance::upstartJobPath()
218-{
219- std::string path = job_ + "-" + std::string(appId_);
220- if (job_ != "application-click")
221- {
222- path += "-";
223- }
224- if (!instance_.empty())
225- {
226- path += instance_;
227- }
228-
229- return path;
230-}
231-
232-/** Looks at the PIDs in the instance cgroup and checks to see if @pid
233- is in the set.
234-
235- @param pid PID to look for
236-*/
237-bool UpstartInstance::hasPid(pid_t pid)
238-{
239- for (auto testpid : registry_->impl->pidsFromCgroup(upstartJobPath()))
240- if (pid == testpid)
241- return true;
242- return false;
243-}
244-
245-/** Gets the path to the log file for this instance */
246-std::string UpstartInstance::logPath()
247-{
248- std::string logfile = upstartJobPath() + ".log";
249-
250- gchar* cpath = g_build_filename(g_get_user_cache_dir(), "upstart", logfile.c_str(), nullptr);
251- std::string path(cpath);
252- g_free(cpath);
253-
254- return path;
255-}
256-
257-/** Returns all the PIDs that are in the cgroup for this application */
258-std::vector<pid_t> UpstartInstance::pids()
259-{
260- auto pids = registry_->impl->pidsFromCgroup(upstartJobPath());
261- g_debug("Got %d PIDs for AppID '%s'", int(pids.size()), std::string(appId_).c_str());
262- return pids;
263-}
264-
265-/** Pauses this application by sending SIGSTOP to all the PIDs in the
266- cgroup and tells Zeitgeist that we've left the application. */
267-void UpstartInstance::pause()
268-{
269- g_debug("Pausing application: %s", std::string(appId_).c_str());
270- registry_->impl->zgSendEvent(appId_, ZEITGEIST_ZG_LEAVE_EVENT);
271-
272- auto pids = forAllPids([this](pid_t pid) {
273- auto oomval = oom::paused();
274- g_debug("Pausing PID: %d (%d)", pid, int(oomval));
275- signalToPid(pid, SIGSTOP);
276- oomValueToPid(pid, oomval);
277- });
278-
279- pidListToDbus(pids, "ApplicationPaused");
280-}
281-
282-/** Resumes this application by sending SIGCONT to all the PIDs in the
283- cgroup and tells Zeitgeist that we're accessing the application. */
284-void UpstartInstance::resume()
285-{
286- g_debug("Resuming application: %s", std::string(appId_).c_str());
287- registry_->impl->zgSendEvent(appId_, ZEITGEIST_ZG_ACCESS_EVENT);
288-
289- auto pids = forAllPids([this](pid_t pid) {
290- auto oomval = oom::focused();
291- g_debug("Resuming PID: %d (%d)", pid, int(oomval));
292- signalToPid(pid, SIGCONT);
293- oomValueToPid(pid, oomval);
294- });
295-
296- pidListToDbus(pids, "ApplicationResumed");
297-}
298-
299-/** Stops this instance by asking Upstart to stop it. Upstart will then
300- send a SIGTERM and five seconds later start killing things. */
301-void UpstartInstance::stop()
302-{
303- if (!registry_->impl->thread.executeOnThread<bool>([this]() {
304-
305- g_debug("Stopping job %s app_id %s instance_id %s", job_.c_str(), std::string(appId_).c_str(),
306- instance_.c_str());
307-
308- auto jobpath = registry_->impl->upstartJobPath(job_);
309- if (jobpath.empty())
310- {
311- throw new std::runtime_error("Unable to get job path for Upstart job '" + job_ + "'");
312- }
313-
314- GVariantBuilder builder;
315- g_variant_builder_init(&builder, G_VARIANT_TYPE_TUPLE);
316- g_variant_builder_open(&builder, G_VARIANT_TYPE_ARRAY);
317-
318- g_variant_builder_add_value(
319- &builder, g_variant_new_take_string(g_strdup_printf("APP_ID=%s", std::string(appId_).c_str())));
320-
321- if (!instance_.empty())
322- {
323- g_variant_builder_add_value(
324- &builder, g_variant_new_take_string(g_strdup_printf("INSTANCE_ID=%s", instance_.c_str())));
325- }
326-
327- g_variant_builder_close(&builder);
328- g_variant_builder_add_value(&builder, g_variant_new_boolean(FALSE)); /* wait */
329-
330- GError* error = nullptr;
331- GVariant* stop_variant =
332- g_dbus_connection_call_sync(registry_->impl->_dbus.get(), /* Dbus */
333- DBUS_SERVICE_UPSTART, /* Upstart name */
334- jobpath.c_str(), /* path */
335- DBUS_INTERFACE_UPSTART_JOB, /* interface */
336- "Stop", /* method */
337- g_variant_builder_end(&builder), /* params */
338- nullptr, /* return */
339- G_DBUS_CALL_FLAGS_NONE, /* flags */
340- -1, /* timeout: default */
341- registry_->impl->thread.getCancellable().get(), /* cancellable */
342- &error); /* error (hopefully not) */
343-
344- g_clear_pointer(&stop_variant, g_variant_unref);
345-
346- if (error != nullptr)
347- {
348- g_warning("Unable to stop job %s app_id %s instance_id %s: %s", job_.c_str(),
349- std::string(appId_).c_str(), instance_.c_str(), error->message);
350- g_error_free(error);
351- return false;
352- }
353-
354- return true;
355- }))
356- {
357- g_warning("Unable to stop Upstart instance");
358- }
359-}
360-
361-/** Sets the OOM adjustment by getting the list of PIDs and writing
362- the value to each of their files in proc
363-
364- \param score OOM Score to set
365-*/
366-void UpstartInstance::setOomAdjustment(const oom::Score score)
367-{
368- forAllPids([this, &score](pid_t pid) { oomValueToPid(pid, score); });
369-}
370-
371-/** Figures out the path to the primary PID of the application and
372- then reads its OOM adjustment file. */
373-const oom::Score UpstartInstance::getOomAdjustment()
374-{
375- auto pid = primaryPid();
376- if (pid == 0)
377- {
378- throw std::runtime_error("No PID for application: " + std::string(appId_));
379- }
380-
381- auto path = pidToOomPath(pid);
382- GError* error = nullptr;
383- gchar* content = nullptr;
384-
385- g_file_get_contents(path.c_str(), /* path */
386- &content, /* data */
387- nullptr, /* size */
388- &error); /* error */
389-
390- if (error != nullptr)
391- {
392- auto serror = std::shared_ptr<GError>(error, g_error_free);
393- throw std::runtime_error("Unable to access OOM value for '" + std::string(appId_) + "' primary PID '" +
394- std::to_string(pid) + "' because: " + serror->message);
395- }
396-
397- auto score = static_cast<oom::Score>(std::atoi(content));
398- g_free(content);
399- return score;
400-}
401-
402-/** Go through the list of PIDs calling a function and handling
403- the issue with getting PIDs being a racey condition.
404-
405- \param eachPid Function to run on each PID
406-*/
407-std::vector<pid_t> UpstartInstance::forAllPids(std::function<void(pid_t)> eachPid)
408-{
409- std::set<pid_t> seenPids;
410- bool added = true;
411-
412- while (added)
413- {
414- added = false;
415- auto pidlist = pids();
416- for (auto pid : pidlist)
417- {
418- if (seenPids.insert(pid).second)
419- {
420- eachPid(pid);
421- added = true;
422- }
423- }
424- }
425-
426- return std::vector<pid_t>(seenPids.begin(), seenPids.end());
427-}
428-
429-/** Sends a signal to a PID with a warning if we can't send it.
430- We could throw an exception, but we can't handle it usefully anyway
431-
432- \param pid PID to send the signal to
433- \param signal signal to send
434-*/
435-void UpstartInstance::signalToPid(pid_t pid, int signal)
436-{
437- if (-1 == kill(pid, signal))
438- {
439- /* While that didn't work, we still want to try as many as we can */
440- g_warning("Unable to send signal %d to pid %d", signal, pid);
441- }
442-}
443-
444-/** Get the path to the PID's OOM adjust path, with allowing for an
445- override for testing using the environment variable
446- UBUNTU_APP_LAUNCH_OOM_PROC_PATH
447-
448- \param pid PID to build path for
449-*/
450-std::string UpstartInstance::pidToOomPath(pid_t pid)
451-{
452- static std::string procpath;
453- if (G_UNLIKELY(procpath.empty()))
454- {
455- /* Set by the test suite, probably not anyone else */
456- auto envvar = g_getenv("UBUNTU_APP_LAUNCH_OOM_PROC_PATH");
457- if (G_LIKELY(envvar == nullptr))
458- procpath = "/proc";
459- else
460- procpath = envvar;
461- }
462-
463- gchar* gpath = g_build_filename(procpath.c_str(), std::to_string(pid).c_str(), "oom_score_adj", nullptr);
464- std::string path = gpath;
465- g_free(gpath);
466- return path;
467-}
468-
469-/** Writes an OOM value to proc, assuming we have a string
470- in the outer loop
471-
472- \param pid PID to change the OOM value of
473- \param oomvalue OOM value to set
474-*/
475-void UpstartInstance::oomValueToPid(pid_t pid, const oom::Score oomvalue)
476-{
477- auto oomstr = std::to_string(static_cast<std::int32_t>(oomvalue));
478- auto path = pidToOomPath(pid);
479- FILE* adj = fopen(path.c_str(), "w");
480- int openerr = errno;
481-
482- if (adj == nullptr)
483- {
484- switch (openerr)
485- {
486- case ENOENT:
487- /* ENOENT happens a fair amount because of races, so it's not
488- worth printing a warning about */
489- return;
490- case EACCES:
491- {
492- /* We can get this error when trying to set the OOM value on
493- Oxide renderers because they're started by the sandbox and
494- don't have their adjustment value available for us to write.
495- We have a helper to deal with this, but it's kinda expensive
496- so we only use it when we have to. */
497- oomValueToPidHelper(pid, oomvalue);
498- return;
499- }
500- default:
501- g_warning("Unable to set OOM value for '%d' to '%s': %s", int(pid), oomstr.c_str(),
502- std::strerror(openerr));
503- return;
504- }
505- }
506-
507- size_t writesize = fwrite(oomstr.c_str(), 1, oomstr.size(), adj);
508- int writeerr = errno;
509- fclose(adj);
510-
511- if (writesize == oomstr.size())
512- return;
513-
514- if (writeerr != 0)
515- g_warning("Unable to set OOM value for '%d' to '%s': %s", int(pid), oomstr.c_str(), strerror(writeerr));
516- else
517- /* No error, but yet, wrong size. Not sure, what could cause this. */
518- g_debug("Unable to set OOM value for '%d' to '%s': Wrote %d bytes", int(pid), oomstr.c_str(), int(writesize));
519-}
520-
521-/** Use a setuid root helper for setting the oom value of
522- Chromium instances
523-
524- \param pid PID to change the OOM value of
525- \param oomvalue OOM value to set
526-*/
527-void UpstartInstance::oomValueToPidHelper(pid_t pid, const oom::Score oomvalue)
528-{
529- GError* error = nullptr;
530- std::string oomstr = std::to_string(static_cast<std::int32_t>(oomvalue));
531- std::string pidstr = std::to_string(pid);
532- std::array<const char*, 4> args = {OOM_HELPER, pidstr.c_str(), oomstr.c_str(), nullptr};
533-
534- g_debug("Excuting OOM Helper (pid: %d, score: %d): %s", int(pid), int(oomvalue),
535- std::accumulate(args.begin(), args.end(), std::string{},
536- [](const std::string& instr, const char* output) -> std::string {
537- if (instr.empty())
538- {
539- return output;
540- }
541- else if (output != nullptr)
542- {
543- return instr + " " + std::string(output);
544- }
545- else
546- {
547- return instr;
548- }
549- })
550- .c_str());
551-
552- g_spawn_async(nullptr, /* working dir */
553- (char**)(args.data()), /* args */
554- nullptr, /* env */
555- G_SPAWN_DEFAULT, /* flags */
556- nullptr, /* child setup */
557- nullptr, /* child setup userdata*/
558- nullptr, /* pid */
559- &error); /* error */
560-
561- if (error != nullptr)
562- {
563- g_warning("Unable to launch OOM helper '" OOM_HELPER "' on PID '%d': %s", pid, error->message);
564- g_error_free(error);
565- return;
566- }
567-}
568-
569-/** Send a signal that we've change the application. Do this on the
570- registry thread in an idle so that we don't block anyone.
571-
572- \param pids List of PIDs to turn into variants to send
573- \param signal Name of the DBus signal to send
574-*/
575-void UpstartInstance::pidListToDbus(const std::vector<pid_t>& pids, const std::string& signal)
576-{
577- auto registry = registry_;
578- auto lappid = appId_;
579-
580- registry_->impl->thread.executeOnThread([registry, lappid, pids, signal] {
581- auto vpids = std::shared_ptr<GVariant>(
582- [pids]() {
583- GVariant* pidarray = nullptr;
584-
585- if (pids.empty())
586- {
587- pidarray = g_variant_new_array(G_VARIANT_TYPE_UINT64, nullptr, 0);
588- g_variant_ref_sink(pidarray);
589- return pidarray;
590- }
591-
592- GVariantBuilder builder;
593- g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
594- for (auto pid : pids)
595- {
596- g_variant_builder_add_value(&builder, g_variant_new_uint64(pid));
597- }
598-
599- pidarray = g_variant_builder_end(&builder);
600- g_variant_ref_sink(pidarray);
601- return pidarray;
602- }(),
603- [](GVariant* var) { g_variant_unref(var); });
604-
605- GVariantBuilder params;
606- g_variant_builder_init(&params, G_VARIANT_TYPE_TUPLE);
607- g_variant_builder_add_value(&params, g_variant_new_string(std::string(lappid).c_str()));
608- g_variant_builder_add_value(&params, vpids.get());
609-
610- GError* error = nullptr;
611- g_dbus_connection_emit_signal(registry->impl->_dbus.get(), /* bus */
612- nullptr, /* destination */
613- "/", /* path */
614- "com.canonical.UbuntuAppLaunch", /* interface */
615- signal.c_str(), /* signal */
616- g_variant_builder_end(&params), /* params, the same */
617- &error); /* error */
618-
619- if (error != nullptr)
620- {
621- g_warning("Unable to emit signal '%s' for appid '%s': %s", signal.c_str(), std::string(lappid).c_str(),
622- error->message);
623- g_error_free(error);
624- }
625- else
626- {
627- g_debug("Emmitted '%s' to DBus", signal.c_str());
628- }
629- });
630-}
631-
632-/** Create a new Upstart Instance object that can track the job and
633- get information about it.
634-
635- \param appId Application ID
636- \param job Upstart job name
637- \param instance Upstart instance name
638- \param urls URLs sent to the application (only on launch today)
639- \param registry Registry of persistent connections to use
640-*/
641-UpstartInstance::UpstartInstance(const AppID& appId,
642- const std::string& job,
643- const std::string& instance,
644- const std::vector<Application::URL>& urls,
645- const std::shared_ptr<Registry>& registry)
646- : appId_(appId)
647- , job_(job)
648- , instance_(instance)
649- , urls_(urls)
650- , registry_(registry)
651-{
652- g_debug("Creating a new UpstartInstance for '%s' instance '%s'", std::string(appId_).c_str(), instance.c_str());
653-}
654-
655-/** Reformat a C++ vector of URLs into a C GStrv of strings
656-
657- \param urls Vector of URLs to make into C strings
658-*/
659-std::shared_ptr<gchar*> UpstartInstance::urlsToStrv(const std::vector<Application::URL>& urls)
660-{
661- if (urls.empty())
662- {
663- return {};
664- }
665-
666- auto array = g_array_new(TRUE, FALSE, sizeof(gchar*));
667-
668- for (auto url : urls)
669- {
670- auto str = g_strdup(url.value().c_str());
671- g_debug("Converting URL: %s", str);
672- g_array_append_val(array, str);
673- }
674-
675- return std::shared_ptr<gchar*>((gchar**)g_array_free(array, FALSE), g_strfreev);
676-}
677-
678-/** Small helper that we can new/delete to work better with C stuff */
679-struct StartCHelper
680-{
681- std::shared_ptr<UpstartInstance> ptr;
682-};
683-
684-/** Callback from starting an application. It checks to see whether the
685- app is already running. If it is already running then we need to send
686- the URLs to it via DBus.
687-
688- \param obj The GDBusConnection object
689- \param res Async result object
690- \param user_data A pointer to a StartCHelper structure
691-*/
692-void UpstartInstance::application_start_cb(GObject* obj, GAsyncResult* res, gpointer user_data)
693-{
694- auto data = static_cast<StartCHelper*>(user_data);
695- GError* error{nullptr};
696- GVariant* result{nullptr};
697-
698- tracepoint(ubuntu_app_launch, libual_start_message_callback, std::string(data->ptr->appId_).c_str());
699-
700- g_debug("Started Message Callback: %s", std::string(data->ptr->appId_).c_str());
701-
702- result = g_dbus_connection_call_finish(G_DBUS_CONNECTION(obj), res, &error);
703-
704- g_clear_pointer(&result, g_variant_unref);
705-
706- if (error != nullptr)
707- {
708- if (g_dbus_error_is_remote_error(error))
709- {
710- gchar* remote_error = g_dbus_error_get_remote_error(error);
711- g_debug("Remote error: %s", remote_error);
712- if (g_strcmp0(remote_error, "com.ubuntu.Upstart0_6.Error.AlreadyStarted") == 0)
713- {
714- auto urls = urlsToStrv(data->ptr->urls_);
715- second_exec(data->ptr->registry_->impl->_dbus.get(), /* DBus */
716- data->ptr->registry_->impl->thread.getCancellable().get(), /* cancellable */
717- data->ptr->primaryPid(), /* primary pid */
718- std::string(data->ptr->appId_).c_str(), /* appid */
719- urls.get()); /* urls */
720- }
721-
722- g_free(remote_error);
723- }
724- else
725- {
726- g_warning("Unable to emit event to start application: %s", error->message);
727- }
728- g_error_free(error);
729- }
730-
731- delete data;
732-}
733-
734-/** Launch an application and create a new UpstartInstance object to track
735- its progress.
736-
737- \param appId Application ID
738- \param job Upstart job name
739- \param instance Upstart instance name
740- \param urls URLs sent to the application (only on launch today)
741- \param registry Registry of persistent connections to use
742- \param mode Whether or not to setup the environment for testing
743- \param getenv A function to get additional environment variable when appropriate
744-*/
745-std::shared_ptr<UpstartInstance> UpstartInstance::launch(
746- const AppID& appId,
747- const std::string& job,
748- const std::string& instance,
749- const std::vector<Application::URL>& urls,
750- const std::shared_ptr<Registry>& registry,
751- launchMode mode,
752- std::function<std::list<std::pair<std::string, std::string>>(void)>& getenv)
753-{
754- if (appId.empty())
755- return {};
756-
757- return registry->impl->thread.executeOnThread<std::shared_ptr<UpstartInstance>>(
758- [&]() -> std::shared_ptr<UpstartInstance> {
759- std::string appIdStr{appId};
760- g_debug("Initializing params for an new UpstartInstance for: %s", appIdStr.c_str());
761-
762- tracepoint(ubuntu_app_launch, libual_start, appIdStr.c_str());
763-
764- int timeout = 1;
765- if (ubuntu::app_launch::Registry::Impl::isWatchingAppStarting())
766- {
767- timeout = 0;
768- }
769-
770- auto handshake = starting_handshake_start(appIdStr.c_str(), timeout);
771- if (handshake == nullptr)
772- {
773- g_warning("Unable to setup starting handshake");
774- }
775-
776- /* Figure out the DBus path for the job */
777- auto jobpath = registry->impl->upstartJobPath(job);
778-
779- /* Build up our environment */
780- auto env = getenv();
781-
782- env.emplace_back(std::make_pair("APP_ID", appIdStr)); /* Application ID */
783- env.emplace_back(std::make_pair("APP_LAUNCHER_PID", std::to_string(getpid()))); /* Who we are, for bugs */
784-
785- if (!urls.empty())
786- {
787- auto accumfunc = [](const std::string& prev, Application::URL thisurl) -> std::string {
788- gchar* gescaped = g_shell_quote(thisurl.value().c_str());
789- std::string escaped;
790- if (gescaped != nullptr)
791- {
792- escaped = gescaped;
793- g_free(gescaped);
794- }
795- else
796- {
797- g_warning("Unable to escape URL: %s", thisurl.value().c_str());
798- return prev;
799- }
800-
801- if (prev.empty())
802- {
803- return escaped;
804- }
805- else
806- {
807- return prev + " " + escaped;
808- }
809- };
810- auto urlstring = std::accumulate(urls.begin(), urls.end(), std::string{}, accumfunc);
811- env.emplace_back(std::make_pair("APP_URIS", urlstring));
812- }
813-
814- if (mode == launchMode::TEST)
815- {
816- env.emplace_back(std::make_pair("QT_LOAD_TESTABILITY", "1"));
817- }
818-
819- /* Convert to GVariant */
820- GVariantBuilder builder;
821- g_variant_builder_init(&builder, G_VARIANT_TYPE_TUPLE);
822-
823- g_variant_builder_open(&builder, G_VARIANT_TYPE_ARRAY);
824-
825- for (const auto& envvar : env)
826- {
827- g_variant_builder_add_value(&builder, g_variant_new_take_string(g_strdup_printf(
828- "%s=%s", envvar.first.c_str(), envvar.second.c_str())));
829- }
830-
831- g_variant_builder_close(&builder);
832- g_variant_builder_add_value(&builder, g_variant_new_boolean(TRUE));
833-
834- auto retval = std::make_shared<UpstartInstance>(appId, job, instance, urls, registry);
835- auto chelper = new StartCHelper{};
836- chelper->ptr = retval;
837-
838- tracepoint(ubuntu_app_launch, handshake_wait, appIdStr.c_str());
839- starting_handshake_wait(handshake);
840- tracepoint(ubuntu_app_launch, handshake_complete, appIdStr.c_str());
841-
842- /* Call the job start function */
843- g_debug("Asking Upstart to start task for: %s", appIdStr.c_str());
844- g_dbus_connection_call(registry->impl->_dbus.get(), /* bus */
845- DBUS_SERVICE_UPSTART, /* service name */
846- jobpath.c_str(), /* Path */
847- DBUS_INTERFACE_UPSTART_JOB, /* interface */
848- "Start", /* method */
849- g_variant_builder_end(&builder), /* params */
850- nullptr, /* return */
851- G_DBUS_CALL_FLAGS_NONE, /* flags */
852- -1, /* default timeout */
853- registry->impl->thread.getCancellable().get(), /* cancellable */
854- application_start_cb, /* callback */
855- chelper /* object */
856- );
857-
858- tracepoint(ubuntu_app_launch, libual_start_message_sent, appIdStr.c_str());
859-
860- return retval;
861- });
862-}
863-
864 } // namespace app_impls
865 } // namespace app_launch
866 } // namespace ubuntu
867
868=== modified file 'libubuntu-app-launch/application-impl-base.h'
869--- libubuntu-app-launch/application-impl-base.h 2016-09-23 22:30:51 +0000
870+++ libubuntu-app-launch/application-impl-base.h 2016-11-10 21:58:30 +0000
871@@ -51,75 +51,6 @@
872 const std::string& pkgdir);
873 };
874
875-/** An object that represents an instance of a job on Upstart. This
876- then implements everything needed by the instance interface. Most
877- applications tie into this today and use it as the backend for
878- their instances. */
879-class UpstartInstance : public Application::Instance
880-{
881-public:
882- explicit UpstartInstance(const AppID& appId,
883- const std::string& job,
884- const std::string& instance,
885- const std::vector<Application::URL>& urls,
886- const std::shared_ptr<Registry>& registry);
887-
888- /* Query lifecycle */
889- bool isRunning() override;
890- pid_t primaryPid() override;
891- bool hasPid(pid_t pid) override;
892- std::string logPath() override;
893- std::vector<pid_t> pids() override;
894-
895- /* Manage lifecycle */
896- void pause() override;
897- void resume() override;
898- void stop() override;
899-
900- /* OOM Functions */
901- void setOomAdjustment(const oom::Score score) override;
902- const oom::Score getOomAdjustment() override;
903-
904- /** Flag for whether we should include the testing environment variables */
905- enum class launchMode
906- {
907- STANDARD, /**< Standard variable set */
908- TEST /**< Include testing environment vars */
909- };
910- static std::shared_ptr<UpstartInstance> launch(
911- const AppID& appId,
912- const std::string& job,
913- const std::string& instance,
914- const std::vector<Application::URL>& urls,
915- const std::shared_ptr<Registry>& registry,
916- launchMode mode,
917- std::function<std::list<std::pair<std::string, std::string>>(void)>& getenv);
918-
919-private:
920- /** Application ID */
921- const AppID appId_;
922- /** Upstart job name */
923- const std::string job_;
924- /** Instance ID environment value, empty if none */
925- const std::string instance_;
926- /** The URLs that this was launched for. Only valid on launched jobs, we
927- should look at perhaps changing that. */
928- std::vector<Application::URL> urls_;
929- /** A link to the registry we're using for connections */
930- std::shared_ptr<Registry> registry_;
931-
932- std::vector<pid_t> forAllPids(std::function<void(pid_t)> eachPid);
933- void signalToPid(pid_t pid, int signal);
934- std::string pidToOomPath(pid_t pid);
935- void oomValueToPid(pid_t pid, const oom::Score oomvalue);
936- void oomValueToPidHelper(pid_t pid, const oom::Score oomvalue);
937- void pidListToDbus(const std::vector<pid_t>& pids, const std::string& signal);
938- std::string upstartJobPath();
939-
940- static std::shared_ptr<gchar*> urlsToStrv(const std::vector<Application::URL>& urls);
941- static void application_start_cb(GObject* obj, GAsyncResult* res, gpointer user_data);
942-};
943-
944 } // namespace app_impls
945 } // namespace app_launch
946 } // namespace ubuntu
947
948=== modified file 'libubuntu-app-launch/application-impl-click.cpp'
949--- libubuntu-app-launch/application-impl-click.cpp 2016-10-03 23:54:20 +0000
950+++ libubuntu-app-launch/application-impl-click.cpp 2016-11-10 21:58:30 +0000
951@@ -323,21 +323,8 @@
952
953 std::vector<std::shared_ptr<Application::Instance>> Click::instances()
954 {
955- std::vector<std::shared_ptr<Instance>> vect;
956- std::string sappid = appId();
957-
958- for (auto instancename : _registry->impl->upstartInstancesForJob("application-click"))
959- {
960- /* There an be only one, but we want to make sure it is
961- there or return an empty vector */
962- if (sappid == instancename)
963- {
964- vect.emplace_back(std::make_shared<UpstartInstance>(appId(), "application-click", std::string{},
965- std::vector<Application::URL>{}, _registry));
966- break;
967- }
968- }
969- return vect;
970+ auto vbase = _registry->impl->jobs->instances(appId(), "application-click");
971+ return std::vector<std::shared_ptr<Application::Instance>>(vbase.begin(), vbase.end());
972 }
973
974 /** Grabs all the environment variables for the application to
975@@ -361,15 +348,15 @@
976 std::shared_ptr<Application::Instance> Click::launch(const std::vector<Application::URL>& urls)
977 {
978 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this]() { return launchEnv(); };
979- return UpstartInstance::launch(appId(), "application-click", {}, urls, _registry,
980- UpstartInstance::launchMode::STANDARD, envfunc);
981+ return _registry->impl->jobs->launch(appId(), "application-click", {}, urls, jobs::manager::launchMode::STANDARD,
982+ envfunc);
983 }
984
985 std::shared_ptr<Application::Instance> Click::launchTest(const std::vector<Application::URL>& urls)
986 {
987 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this]() { return launchEnv(); };
988- return UpstartInstance::launch(appId(), "application-click", {}, urls, _registry, UpstartInstance::launchMode::TEST,
989- envfunc);
990+ return _registry->impl->jobs->launch(appId(), "application-click", {}, urls, jobs::manager::launchMode::TEST,
991+ envfunc);
992 }
993
994 } // namespace app_impls
995
996=== modified file 'libubuntu-app-launch/application-impl-legacy.cpp'
997--- libubuntu-app-launch/application-impl-legacy.cpp 2016-10-03 23:54:20 +0000
998+++ libubuntu-app-launch/application-impl-legacy.cpp 2016-11-10 21:58:30 +0000
999@@ -33,9 +33,6 @@
1000 /** Path that snapd puts desktop files, we don't want to read those directly
1001 in the Legacy backend. We want to use the snap backend. */
1002 const std::string snappyDesktopPath{"/var/lib/snapd"};
1003-/** Special characters that could be an application name that
1004- would activate in a regex */
1005-const static std::regex regexCharacters("([\\.\\-])");
1006
1007 /***********************************
1008 Prototypes
1009@@ -77,14 +74,6 @@
1010 {
1011 throw std::runtime_error{"Looking like a legacy app, but should be a Snap: " + appname.value()};
1012 }
1013-
1014- /* Build a regex that'll match instances of the applications which
1015- roughly looks like: $(appid)-2345345
1016-
1017- It is important to filter out the special characters that are in
1018- the appid.
1019- */
1020- instanceRegex_ = std::regex("^(?:" + std::regex_replace(_appname.value(), regexCharacters, "\\$&") + ")\\-(\\d*)$");
1021 }
1022
1023 std::tuple<std::string, std::shared_ptr<GKeyFile>, std::string> keyfileForApp(const AppID::AppName& name)
1024@@ -291,23 +280,8 @@
1025
1026 std::vector<std::shared_ptr<Application::Instance>> Legacy::instances()
1027 {
1028- std::vector<std::shared_ptr<Instance>> vect;
1029- auto startsWith = std::string(appId()) + "-";
1030-
1031- for (auto instance : _registry->impl->upstartInstancesForJob("application-legacy"))
1032- {
1033- std::smatch instanceMatch;
1034- g_debug("Looking at legacy instance: %s", instance.c_str());
1035- if (std::regex_match(instance, instanceMatch, instanceRegex_))
1036- {
1037- vect.emplace_back(std::make_shared<UpstartInstance>(appId(), "application-legacy", instanceMatch[1].str(),
1038- std::vector<Application::URL>{}, _registry));
1039- }
1040- }
1041-
1042- g_debug("Legacy app '%s' has %d instances", std::string(appId()).c_str(), int(vect.size()));
1043-
1044- return vect;
1045+ auto vbase = _registry->impl->jobs->instances(appId(), "application-legacy");
1046+ return std::vector<std::shared_ptr<Application::Instance>>(vbase.begin(), vbase.end());
1047 }
1048
1049 /** Grabs all the environment for a legacy app. Mostly this consists of
1050@@ -394,8 +368,8 @@
1051 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this, instance]() {
1052 return launchEnv(instance);
1053 };
1054- return UpstartInstance::launch(appId(), "application-legacy", instance, urls, _registry,
1055- UpstartInstance::launchMode::STANDARD, envfunc);
1056+ return _registry->impl->jobs->launch(appId(), "application-legacy", instance, urls,
1057+ jobs::manager::launchMode::STANDARD, envfunc);
1058 }
1059
1060 /** Create an UpstartInstance for this AppID using the UpstartInstance launch
1061@@ -409,8 +383,8 @@
1062 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this, instance]() {
1063 return launchEnv(instance);
1064 };
1065- return UpstartInstance::launch(appId(), "application-legacy", instance, urls, _registry,
1066- UpstartInstance::launchMode::TEST, envfunc);
1067+ return _registry->impl->jobs->launch(appId(), "application-legacy", instance, urls, jobs::manager::launchMode::TEST,
1068+ envfunc);
1069 }
1070
1071 } // namespace app_impls
1072
1073=== modified file 'libubuntu-app-launch/application-impl-libertine.cpp'
1074--- libubuntu-app-launch/application-impl-libertine.cpp 2016-10-03 23:54:20 +0000
1075+++ libubuntu-app-launch/application-impl-libertine.cpp 2016-11-10 21:58:30 +0000
1076@@ -255,8 +255,7 @@
1077 }
1078 catch (std::runtime_error& e)
1079 {
1080- g_debug("Unable to create application for libertine appname '%s': %s",
1081- apps.get()[j], e.what());
1082+ g_debug("Unable to create application for libertine appname '%s': %s", apps.get()[j], e.what());
1083 }
1084 }
1085 }
1086@@ -276,17 +275,8 @@
1087
1088 std::vector<std::shared_ptr<Application::Instance>> Libertine::instances()
1089 {
1090- std::vector<std::shared_ptr<Instance>> vect;
1091- std::string sappid = appId();
1092-
1093- for (auto instancename : _registry->impl->upstartInstancesForJob("application-legacy"))
1094- {
1095- if (std::equal(sappid.begin(), sappid.end(), instancename.begin()))
1096- vect.emplace_back(std::make_shared<UpstartInstance>(appId(), "application-legacy", std::string{},
1097- std::vector<Application::URL>{}, _registry));
1098- }
1099-
1100- return vect;
1101+ auto vbase = _registry->impl->jobs->instances(appId(), "application-legacy");
1102+ return std::vector<std::shared_ptr<Application::Instance>>(vbase.begin(), vbase.end());
1103 }
1104
1105 /** Grabs all the environment variables for the application to
1106@@ -328,15 +318,15 @@
1107 std::shared_ptr<Application::Instance> Libertine::launch(const std::vector<Application::URL>& urls)
1108 {
1109 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this]() { return launchEnv(); };
1110- return UpstartInstance::launch(appId(), "application-legacy", {}, urls, _registry,
1111- UpstartInstance::launchMode::STANDARD, envfunc);
1112+ return _registry->impl->jobs->launch(appId(), "application-legacy", {}, urls, jobs::manager::launchMode::STANDARD,
1113+ envfunc);
1114 }
1115
1116 std::shared_ptr<Application::Instance> Libertine::launchTest(const std::vector<Application::URL>& urls)
1117 {
1118 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this]() { return launchEnv(); };
1119- return UpstartInstance::launch(appId(), "application-legacy", {}, urls, _registry,
1120- UpstartInstance::launchMode::TEST, envfunc);
1121+ return _registry->impl->jobs->launch(appId(), "application-legacy", {}, urls, jobs::manager::launchMode::TEST,
1122+ envfunc);
1123 }
1124
1125 } // namespace app_impls
1126
1127=== modified file 'libubuntu-app-launch/application-impl-snap.cpp'
1128--- libubuntu-app-launch/application-impl-snap.cpp 2016-10-03 23:54:20 +0000
1129+++ libubuntu-app-launch/application-impl-snap.cpp 2016-11-10 21:58:30 +0000
1130@@ -396,19 +396,8 @@
1131 /** Get all of the instances of this snap package that are running */
1132 std::vector<std::shared_ptr<Application::Instance>> Snap::instances()
1133 {
1134- std::vector<std::shared_ptr<Instance>> vect;
1135- auto startsWith = std::string(appid_) + "-";
1136-
1137- for (const auto& instance : _registry->impl->upstartInstancesForJob("application-snap"))
1138- {
1139- if (std::equal(startsWith.begin(), startsWith.end(), instance.begin()))
1140- {
1141- vect.emplace_back(std::make_shared<UpstartInstance>(appid_, "application-snap", std::string{},
1142- std::vector<Application::URL>{}, _registry));
1143- }
1144- }
1145-
1146- return vect;
1147+ auto vbase = _registry->impl->jobs->instances(appId(), "application-snap");
1148+ return std::vector<std::shared_ptr<Application::Instance>>(vbase.begin(), vbase.end());
1149 }
1150
1151 /** Return the launch environment for this snap. That includes whether
1152@@ -448,8 +437,8 @@
1153 std::shared_ptr<Application::Instance> Snap::launch(const std::vector<Application::URL>& urls)
1154 {
1155 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this]() { return launchEnv(); };
1156- return UpstartInstance::launch(appid_, "application-snap", {}, urls, _registry,
1157- UpstartInstance::launchMode::STANDARD, envfunc);
1158+ return _registry->impl->jobs->launch(appid_, "application-snap", {}, urls, jobs::manager::launchMode::STANDARD,
1159+ envfunc);
1160 }
1161
1162 /** Create a new instance of this Snap with a testing environment
1163@@ -460,8 +449,8 @@
1164 std::shared_ptr<Application::Instance> Snap::launchTest(const std::vector<Application::URL>& urls)
1165 {
1166 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this]() { return launchEnv(); };
1167- return UpstartInstance::launch(appid_, "application-snap", {}, urls, _registry, UpstartInstance::launchMode::TEST,
1168- envfunc);
1169+ return _registry->impl->jobs->launch(appid_, "application-snap", {}, urls, jobs::manager::launchMode::TEST,
1170+ envfunc);
1171 }
1172
1173 } // namespace app_impls
1174
1175=== modified file 'libubuntu-app-launch/application.cpp'
1176--- libubuntu-app-launch/application.cpp 2016-08-26 17:33:34 +0000
1177+++ libubuntu-app-launch/application.cpp 2016-11-10 21:58:30 +0000
1178@@ -28,6 +28,8 @@
1179 #include "application-impl-snap.h"
1180 #endif
1181 #include "application.h"
1182+#include "jobs-base.h"
1183+#include "registry-impl.h"
1184 #include "registry.h"
1185
1186 #include <functional>
1187@@ -46,6 +48,11 @@
1188 throw std::runtime_error("AppID is empty");
1189 }
1190
1191+ if (!registry->impl->jobs)
1192+ {
1193+ registry->impl->jobs = jobs::manager::Base::determineFactory(registry);
1194+ }
1195+
1196 if (app_impls::Click::hasAppId(appid, registry))
1197 {
1198 return std::make_shared<app_impls::Click>(appid, registry);
1199
1200=== added file 'libubuntu-app-launch/jobs-base.cpp'
1201--- libubuntu-app-launch/jobs-base.cpp 1970-01-01 00:00:00 +0000
1202+++ libubuntu-app-launch/jobs-base.cpp 2016-11-10 21:58:30 +0000
1203@@ -0,0 +1,392 @@
1204+/*
1205+ * Copyright © 2016 Canonical Ltd.
1206+ *
1207+ * This program is free software: you can redistribute it and/or modify it
1208+ * under the terms of the GNU General Public License version 3, as published
1209+ * by the Free Software Foundation.
1210+ *
1211+ * This program is distributed in the hope that it will be useful, but
1212+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1213+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1214+ * PURPOSE. See the GNU General Public License for more details.
1215+ *
1216+ * You should have received a copy of the GNU General Public License along
1217+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1218+ *
1219+ * Authors:
1220+ * Ted Gould <ted.gould@canonical.com>
1221+ */
1222+
1223+#include <algorithm>
1224+#include <cerrno>
1225+#include <cstring>
1226+#include <numeric>
1227+
1228+#include "jobs-base.h"
1229+#include "jobs-upstart.h"
1230+#include "registry-impl.h"
1231+
1232+namespace ubuntu
1233+{
1234+namespace app_launch
1235+{
1236+namespace jobs
1237+{
1238+namespace manager
1239+{
1240+
1241+Base::Base(const std::shared_ptr<Registry>& registry)
1242+ : registry_(registry)
1243+{
1244+}
1245+
1246+std::shared_ptr<Base> Base::determineFactory(std::shared_ptr<Registry> registry)
1247+{
1248+ return std::make_shared<jobs::manager::Upstart>(registry);
1249+}
1250+
1251+} // namespace manager
1252+
1253+namespace instance
1254+{
1255+
1256+Base::Base(const AppID& appId,
1257+ const std::string& job,
1258+ const std::string& instance,
1259+ const std::vector<Application::URL>& urls,
1260+ const std::shared_ptr<Registry>& registry)
1261+ : appId_(appId)
1262+ , job_(job)
1263+ , instance_(instance)
1264+ , urls_(urls)
1265+ , registry_(registry)
1266+{
1267+}
1268+
1269+/** Checks to see if we have a primary PID for the instance */
1270+bool Base::isRunning()
1271+{
1272+ return primaryPid() != 0;
1273+}
1274+
1275+/** Looks at the PIDs in the instance cgroup and checks to see if @pid
1276+ is in the set.
1277+
1278+ @param pid PID to look for
1279+*/
1280+bool Base::hasPid(pid_t pid)
1281+{
1282+ auto vpids = pids();
1283+ return std::find(vpids.begin(), vpids.end(), pid) != vpids.end();
1284+}
1285+
1286+/** Pauses this application by sending SIGSTOP to all the PIDs in the
1287+ cgroup and tells Zeitgeist that we've left the application. */
1288+void Base::pause()
1289+{
1290+ g_debug("Pausing application: %s", std::string(appId_).c_str());
1291+ registry_->impl->zgSendEvent(appId_, ZEITGEIST_ZG_LEAVE_EVENT);
1292+
1293+ auto pids = forAllPids([this](pid_t pid) {
1294+ auto oomval = oom::paused();
1295+ g_debug("Pausing PID: %d (%d)", pid, int(oomval));
1296+ signalToPid(pid, SIGSTOP);
1297+ oomValueToPid(pid, oomval);
1298+ });
1299+
1300+ pidListToDbus(pids, "ApplicationPaused");
1301+}
1302+
1303+/** Resumes this application by sending SIGCONT to all the PIDs in the
1304+ cgroup and tells Zeitgeist that we're accessing the application. */
1305+void Base::resume()
1306+{
1307+ g_debug("Resuming application: %s", std::string(appId_).c_str());
1308+ registry_->impl->zgSendEvent(appId_, ZEITGEIST_ZG_ACCESS_EVENT);
1309+
1310+ auto pids = forAllPids([this](pid_t pid) {
1311+ auto oomval = oom::focused();
1312+ g_debug("Resuming PID: %d (%d)", pid, int(oomval));
1313+ signalToPid(pid, SIGCONT);
1314+ oomValueToPid(pid, oomval);
1315+ });
1316+
1317+ pidListToDbus(pids, "ApplicationResumed");
1318+}
1319+
1320+/** Go through the list of PIDs calling a function and handling
1321+ the issue with getting PIDs being a racey condition.
1322+
1323+ \param eachPid Function to run on each PID
1324+*/
1325+std::vector<pid_t> Base::forAllPids(std::function<void(pid_t)> eachPid)
1326+{
1327+ std::set<pid_t> seenPids;
1328+ bool added = true;
1329+
1330+ while (added)
1331+ {
1332+ added = false;
1333+ auto pidlist = pids();
1334+ for (auto pid : pidlist)
1335+ {
1336+ if (seenPids.insert(pid).second)
1337+ {
1338+ eachPid(pid);
1339+ added = true;
1340+ }
1341+ }
1342+ }
1343+
1344+ return std::vector<pid_t>(seenPids.begin(), seenPids.end());
1345+}
1346+
1347+/** Send a signal that we've change the application. Do this on the
1348+ registry thread in an idle so that we don't block anyone.
1349+
1350+ \param pids List of PIDs to turn into variants to send
1351+ \param signal Name of the DBus signal to send
1352+*/
1353+void Base::pidListToDbus(const std::vector<pid_t>& pids, const std::string& signal)
1354+{
1355+ auto registry = registry_;
1356+ auto lappid = appId_;
1357+
1358+ registry_->impl->thread.executeOnThread([registry, lappid, pids, signal] {
1359+ auto vpids = std::shared_ptr<GVariant>(
1360+ [pids]() {
1361+ GVariant* pidarray = nullptr;
1362+
1363+ if (pids.empty())
1364+ {
1365+ pidarray = g_variant_new_array(G_VARIANT_TYPE_UINT64, nullptr, 0);
1366+ g_variant_ref_sink(pidarray);
1367+ return pidarray;
1368+ }
1369+
1370+ GVariantBuilder builder;
1371+ g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
1372+ for (auto pid : pids)
1373+ {
1374+ g_variant_builder_add_value(&builder, g_variant_new_uint64(pid));
1375+ }
1376+
1377+ pidarray = g_variant_builder_end(&builder);
1378+ g_variant_ref_sink(pidarray);
1379+ return pidarray;
1380+ }(),
1381+ [](GVariant* var) { g_variant_unref(var); });
1382+
1383+ GVariantBuilder params;
1384+ g_variant_builder_init(&params, G_VARIANT_TYPE_TUPLE);
1385+ g_variant_builder_add_value(&params, g_variant_new_string(std::string(lappid).c_str()));
1386+ g_variant_builder_add_value(&params, vpids.get());
1387+
1388+ GError* error = nullptr;
1389+ g_dbus_connection_emit_signal(registry->impl->_dbus.get(), /* bus */
1390+ nullptr, /* destination */
1391+ "/", /* path */
1392+ "com.canonical.UbuntuAppLaunch", /* interface */
1393+ signal.c_str(), /* signal */
1394+ g_variant_builder_end(&params), /* params, the same */
1395+ &error); /* error */
1396+
1397+ if (error != nullptr)
1398+ {
1399+ g_warning("Unable to emit signal '%s' for appid '%s': %s", signal.c_str(), std::string(lappid).c_str(),
1400+ error->message);
1401+ g_error_free(error);
1402+ }
1403+ else
1404+ {
1405+ g_debug("Emmitted '%s' to DBus", signal.c_str());
1406+ }
1407+ });
1408+}
1409+
1410+/** Sets the OOM adjustment by getting the list of PIDs and writing
1411+ the value to each of their files in proc
1412+
1413+ \param score OOM Score to set
1414+*/
1415+void Base::setOomAdjustment(const oom::Score score)
1416+{
1417+ forAllPids([this, &score](pid_t pid) { oomValueToPid(pid, score); });
1418+}
1419+
1420+/** Figures out the path to the primary PID of the application and
1421+ then reads its OOM adjustment file. */
1422+const oom::Score Base::getOomAdjustment()
1423+{
1424+ auto pid = primaryPid();
1425+ if (pid == 0)
1426+ {
1427+ throw std::runtime_error("No PID for application: " + std::string(appId_));
1428+ }
1429+
1430+ auto path = pidToOomPath(pid);
1431+ GError* error = nullptr;
1432+ gchar* content = nullptr;
1433+
1434+ g_file_get_contents(path.c_str(), /* path */
1435+ &content, /* data */
1436+ nullptr, /* size */
1437+ &error); /* error */
1438+
1439+ if (error != nullptr)
1440+ {
1441+ auto serror = std::shared_ptr<GError>(error, g_error_free);
1442+ throw std::runtime_error("Unable to access OOM value for '" + std::string(appId_) + "' primary PID '" +
1443+ std::to_string(pid) + "' because: " + serror->message);
1444+ }
1445+
1446+ auto score = static_cast<oom::Score>(std::atoi(content));
1447+ g_free(content);
1448+ return score;
1449+}
1450+
1451+/** Sends a signal to a PID with a warning if we can't send it.
1452+ We could throw an exception, but we can't handle it usefully anyway
1453+
1454+ \param pid PID to send the signal to
1455+ \param signal signal to send
1456+*/
1457+void Base::signalToPid(pid_t pid, int signal)
1458+{
1459+ if (-1 == kill(pid, signal))
1460+ {
1461+ /* While that didn't work, we still want to try as many as we can */
1462+ g_warning("Unable to send signal %d to pid %d", signal, pid);
1463+ }
1464+}
1465+
1466+/** Get the path to the PID's OOM adjust path, with allowing for an
1467+ override for testing using the environment variable
1468+ UBUNTU_APP_LAUNCH_OOM_PROC_PATH
1469+
1470+ \param pid PID to build path for
1471+*/
1472+std::string Base::pidToOomPath(pid_t pid)
1473+{
1474+ static std::string procpath;
1475+ if (G_UNLIKELY(procpath.empty()))
1476+ {
1477+ /* Set by the test suite, probably not anyone else */
1478+ auto envvar = g_getenv("UBUNTU_APP_LAUNCH_OOM_PROC_PATH");
1479+ if (G_LIKELY(envvar == nullptr))
1480+ procpath = "/proc";
1481+ else
1482+ procpath = envvar;
1483+ }
1484+
1485+ gchar* gpath = g_build_filename(procpath.c_str(), std::to_string(pid).c_str(), "oom_score_adj", nullptr);
1486+ std::string path = gpath;
1487+ g_free(gpath);
1488+ return path;
1489+}
1490+
1491+/** Writes an OOM value to proc, assuming we have a string
1492+ in the outer loop
1493+
1494+ \param pid PID to change the OOM value of
1495+ \param oomvalue OOM value to set
1496+*/
1497+void Base::oomValueToPid(pid_t pid, const oom::Score oomvalue)
1498+{
1499+ auto oomstr = std::to_string(static_cast<std::int32_t>(oomvalue));
1500+ auto path = pidToOomPath(pid);
1501+ FILE* adj = fopen(path.c_str(), "w");
1502+ int openerr = errno;
1503+
1504+ if (adj == nullptr)
1505+ {
1506+ switch (openerr)
1507+ {
1508+ case ENOENT:
1509+ /* ENOENT happens a fair amount because of races, so it's not
1510+ worth printing a warning about */
1511+ return;
1512+ case EACCES:
1513+ {
1514+ /* We can get this error when trying to set the OOM value on
1515+ Oxide renderers because they're started by the sandbox and
1516+ don't have their adjustment value available for us to write.
1517+ We have a helper to deal with this, but it's kinda expensive
1518+ so we only use it when we have to. */
1519+ oomValueToPidHelper(pid, oomvalue);
1520+ return;
1521+ }
1522+ default:
1523+ g_warning("Unable to set OOM value for '%d' to '%s': %s", int(pid), oomstr.c_str(),
1524+ std::strerror(openerr));
1525+ return;
1526+ }
1527+ }
1528+
1529+ size_t writesize = fwrite(oomstr.c_str(), 1, oomstr.size(), adj);
1530+ int writeerr = errno;
1531+ fclose(adj);
1532+
1533+ if (writesize == oomstr.size())
1534+ return;
1535+
1536+ if (writeerr != 0)
1537+ g_warning("Unable to set OOM value for '%d' to '%s': %s", int(pid), oomstr.c_str(), strerror(writeerr));
1538+ else
1539+ /* No error, but yet, wrong size. Not sure, what could cause this. */
1540+ g_debug("Unable to set OOM value for '%d' to '%s': Wrote %d bytes", int(pid), oomstr.c_str(), int(writesize));
1541+}
1542+
1543+/** Use a setuid root helper for setting the oom value of
1544+ Chromium instances
1545+
1546+ \param pid PID to change the OOM value of
1547+ \param oomvalue OOM value to set
1548+*/
1549+void Base::oomValueToPidHelper(pid_t pid, const oom::Score oomvalue)
1550+{
1551+ GError* error = nullptr;
1552+ std::string oomstr = std::to_string(static_cast<std::int32_t>(oomvalue));
1553+ std::string pidstr = std::to_string(pid);
1554+ std::array<const char*, 4> args = {OOM_HELPER, pidstr.c_str(), oomstr.c_str(), nullptr};
1555+
1556+ g_debug("Excuting OOM Helper (pid: %d, score: %d): %s", int(pid), int(oomvalue),
1557+ std::accumulate(args.begin(), args.end(), std::string{},
1558+ [](const std::string& instr, const char* output) -> std::string {
1559+ if (instr.empty())
1560+ {
1561+ return output;
1562+ }
1563+ else if (output != nullptr)
1564+ {
1565+ return instr + " " + std::string(output);
1566+ }
1567+ else
1568+ {
1569+ return instr;
1570+ }
1571+ })
1572+ .c_str());
1573+
1574+ g_spawn_async(nullptr, /* working dir */
1575+ (char**)(args.data()), /* args */
1576+ nullptr, /* env */
1577+ G_SPAWN_DEFAULT, /* flags */
1578+ nullptr, /* child setup */
1579+ nullptr, /* child setup userdata*/
1580+ nullptr, /* pid */
1581+ &error); /* error */
1582+
1583+ if (error != nullptr)
1584+ {
1585+ g_warning("Unable to launch OOM helper '" OOM_HELPER "' on PID '%d': %s", pid, error->message);
1586+ g_error_free(error);
1587+ return;
1588+ }
1589+}
1590+
1591+} // namespace instance
1592+
1593+} // namespace jobs
1594+} // namespace app_launch
1595+} // namespace ubuntu
1596
1597=== added file 'libubuntu-app-launch/jobs-base.h'
1598--- libubuntu-app-launch/jobs-base.h 1970-01-01 00:00:00 +0000
1599+++ libubuntu-app-launch/jobs-base.h 2016-11-10 21:58:30 +0000
1600@@ -0,0 +1,117 @@
1601+/*
1602+ * Copyright © 2016 Canonical Ltd.
1603+ *
1604+ * This program is free software: you can redistribute it and/or modify it
1605+ * under the terms of the GNU General Public License version 3, as published
1606+ * by the Free Software Foundation.
1607+ *
1608+ * This program is distributed in the hope that it will be useful, but
1609+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1610+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1611+ * PURPOSE. See the GNU General Public License for more details.
1612+ *
1613+ * You should have received a copy of the GNU General Public License along
1614+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1615+ *
1616+ * Authors:
1617+ * Ted Gould <ted.gould@canonical.com>
1618+ */
1619+
1620+#pragma once
1621+#include "application.h"
1622+
1623+namespace ubuntu
1624+{
1625+namespace app_launch
1626+{
1627+namespace jobs
1628+{
1629+namespace instance
1630+{
1631+
1632+class Base : public Application::Instance
1633+{
1634+public:
1635+ Base(const AppID& appId,
1636+ const std::string& job,
1637+ const std::string& instance,
1638+ const std::vector<Application::URL>& urls,
1639+ const std::shared_ptr<Registry>& registry);
1640+ virtual ~Base() = default;
1641+
1642+ bool isRunning() override;
1643+ bool hasPid(pid_t pid) override;
1644+ void pause() override;
1645+ void resume() override;
1646+
1647+ /* OOM Functions */
1648+ void setOomAdjustment(const oom::Score score) override;
1649+ const oom::Score getOomAdjustment() override;
1650+
1651+protected:
1652+ /** Application ID */
1653+ const AppID appId_;
1654+ /** Upstart job name */
1655+ const std::string job_;
1656+ /** Instance ID environment value, empty if none */
1657+ const std::string instance_;
1658+ /** The URLs that this was launched for. Only valid on launched jobs, we
1659+ should look at perhaps changing that. */
1660+ std::vector<Application::URL> urls_;
1661+ /** A link to the registry we're using for connections */
1662+ std::shared_ptr<Registry> registry_;
1663+
1664+private:
1665+ std::vector<pid_t> forAllPids(std::function<void(pid_t)> eachPid);
1666+ void signalToPid(pid_t pid, int signal);
1667+ std::string pidToOomPath(pid_t pid);
1668+ void oomValueToPid(pid_t pid, const oom::Score oomvalue);
1669+ void oomValueToPidHelper(pid_t pid, const oom::Score oomvalue);
1670+ void pidListToDbus(const std::vector<pid_t>& pids, const std::string& signal);
1671+};
1672+
1673+} // namespace instance
1674+
1675+namespace manager
1676+{
1677+
1678+/** Flag for whether we should include the testing environment variables */
1679+enum class launchMode
1680+{
1681+ STANDARD, /**< Standard variable set */
1682+ TEST /**< Include testing environment vars */
1683+};
1684+
1685+class Base
1686+{
1687+public:
1688+ Base(const std::shared_ptr<Registry>& registry);
1689+ virtual ~Base() = default;
1690+
1691+ virtual std::shared_ptr<Application::Instance> launch(
1692+ const AppID& appId,
1693+ const std::string& job,
1694+ const std::string& instance,
1695+ const std::vector<Application::URL>& urls,
1696+ launchMode mode,
1697+ std::function<std::list<std::pair<std::string, std::string>>(void)>& getenv) = 0;
1698+
1699+ virtual std::shared_ptr<Application::Instance> existing(const AppID& appId,
1700+ const std::string& job,
1701+ const std::string& instance,
1702+ const std::vector<Application::URL>& urls) = 0;
1703+
1704+ virtual std::list<std::shared_ptr<Application>> runningApps() = 0;
1705+
1706+ virtual std::vector<std::shared_ptr<instance::Base>> instances(const AppID& appID, const std::string& job) = 0;
1707+
1708+ static std::shared_ptr<Base> determineFactory(std::shared_ptr<Registry> registry);
1709+
1710+protected:
1711+ std::weak_ptr<Registry> registry_;
1712+};
1713+
1714+} // namespace manager
1715+} // namespace jobs
1716+} // namespace app_launch
1717+} // namespace ubuntu
1718
1719=== added file 'libubuntu-app-launch/jobs-upstart.cpp'
1720--- libubuntu-app-launch/jobs-upstart.cpp 1970-01-01 00:00:00 +0000
1721+++ libubuntu-app-launch/jobs-upstart.cpp 2016-11-10 21:58:30 +0000
1722@@ -0,0 +1,899 @@
1723+/*
1724+ * Copyright © 2016 Canonical Ltd.
1725+ *
1726+ * This program is free software: you can redistribute it and/or modify it
1727+ * under the terms of the GNU General Public License version 3, as published
1728+ * by the Free Software Foundation.
1729+ *
1730+ * This program is distributed in the hope that it will be useful, but
1731+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1732+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1733+ * PURPOSE. See the GNU General Public License for more details.
1734+ *
1735+ * You should have received a copy of the GNU General Public License along
1736+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1737+ *
1738+ * Authors:
1739+ * Ted Gould <ted.gould@canonical.com>
1740+ */
1741+
1742+#include <algorithm>
1743+#include <cerrno>
1744+#include <cstring>
1745+#include <map>
1746+#include <numeric>
1747+#include <regex>
1748+
1749+#include <cgmanager/cgmanager.h>
1750+#include <upstart.h>
1751+
1752+#include "helpers.h"
1753+#include "registry-impl.h"
1754+#include "second-exec-core.h"
1755+
1756+extern "C" {
1757+#include "ubuntu-app-launch-trace.h"
1758+}
1759+
1760+#include "jobs-upstart.h"
1761+
1762+namespace ubuntu
1763+{
1764+namespace app_launch
1765+{
1766+namespace jobs
1767+{
1768+namespace instance
1769+{
1770+
1771+/** An object that represents an instance of a job on Upstart. This
1772+ then implements everything needed by the instance interface. Most
1773+ applications tie into this today and use it as the backend for
1774+ their instances. */
1775+class Upstart : public Base
1776+{
1777+public:
1778+ explicit Upstart(const AppID& appId,
1779+ const std::string& job,
1780+ const std::string& instance,
1781+ const std::vector<Application::URL>& urls,
1782+ const std::shared_ptr<Registry>& registry);
1783+
1784+ /* Query lifecycle */
1785+ pid_t primaryPid() override;
1786+ std::string logPath() override;
1787+ std::vector<pid_t> pids() override;
1788+
1789+ /* Manage lifecycle */
1790+ void stop() override;
1791+
1792+ /* C Callback */
1793+ static void application_start_cb(GObject* obj, GAsyncResult* res, gpointer user_data);
1794+
1795+private:
1796+ std::string upstartJobPath(const std::string& job);
1797+ std::string upstartName();
1798+
1799+ static std::shared_ptr<gchar*> urlsToStrv(const std::vector<Application::URL>& urls);
1800+};
1801+
1802+/** Uses Upstart to get the primary PID of the instance using Upstart's
1803+ DBus interface */
1804+pid_t Upstart::primaryPid()
1805+{
1806+ auto jobpath = upstartJobPath(job_);
1807+ if (jobpath.empty())
1808+ {
1809+ g_debug("Unable to get a valid job path");
1810+ return 0;
1811+ }
1812+
1813+ return registry_->impl->thread.executeOnThread<pid_t>([this, &jobpath]() -> pid_t {
1814+ GError* error = nullptr;
1815+
1816+ std::string instancename = std::string(appId_);
1817+ if (job_ != "application-click")
1818+ {
1819+ instancename += "-" + instance_;
1820+ }
1821+
1822+ g_debug("Getting instance by name: %s", instance_.c_str());
1823+ GVariant* vinstance_path =
1824+ g_dbus_connection_call_sync(registry_->impl->_dbus.get(), /* connection */
1825+ DBUS_SERVICE_UPSTART, /* service */
1826+ jobpath.c_str(), /* object path */
1827+ DBUS_INTERFACE_UPSTART_JOB, /* iface */
1828+ "GetInstanceByName", /* method */
1829+ g_variant_new("(s)", instancename.c_str()), /* params */
1830+ G_VARIANT_TYPE("(o)"), /* return type */
1831+ G_DBUS_CALL_FLAGS_NONE, /* flags */
1832+ -1, /* timeout: default */
1833+ registry_->impl->thread.getCancellable().get(), /* cancellable */
1834+ &error);
1835+
1836+ if (error != nullptr)
1837+ {
1838+ g_warning("Unable to get instance '%s' of job '%s': %s", instance_.c_str(), job_.c_str(), error->message);
1839+ g_error_free(error);
1840+ return 0;
1841+ }
1842+
1843+ /* Jump rope to make this into a C++ type */
1844+ std::string instance_path;
1845+ gchar* cinstance_path = nullptr;
1846+ g_variant_get(vinstance_path, "(o)", &cinstance_path);
1847+ g_variant_unref(vinstance_path);
1848+ if (cinstance_path != nullptr)
1849+ {
1850+ instance_path = cinstance_path;
1851+ g_free(cinstance_path);
1852+ }
1853+
1854+ if (instance_path.empty())
1855+ {
1856+ g_debug("No instance object for instance name: %s", instance_.c_str());
1857+ return 0;
1858+ }
1859+
1860+ GVariant* props_tuple =
1861+ g_dbus_connection_call_sync(registry_->impl->_dbus.get(), /* connection */
1862+ DBUS_SERVICE_UPSTART, /* service */
1863+ instance_path.c_str(), /* object path */
1864+ "org.freedesktop.DBus.Properties", /* interface */
1865+ "GetAll", /* method */
1866+ g_variant_new("(s)", DBUS_INTERFACE_UPSTART_INSTANCE), /* params */
1867+ G_VARIANT_TYPE("(a{sv})"), /* return type */
1868+ G_DBUS_CALL_FLAGS_NONE, /* flags */
1869+ -1, /* timeout: default */
1870+ registry_->impl->thread.getCancellable().get(), /* cancellable */
1871+ &error);
1872+
1873+ if (error != nullptr)
1874+ {
1875+ g_warning("Unable to name of properties '%s': %s", instance_path.c_str(), error->message);
1876+ g_error_free(error);
1877+ error = nullptr;
1878+ return 0;
1879+ }
1880+
1881+ GVariant* props_dict = g_variant_get_child_value(props_tuple, 0);
1882+
1883+ pid_t retval = 0;
1884+ GVariant* processes = g_variant_lookup_value(props_dict, "processes", G_VARIANT_TYPE("a(si)"));
1885+ if (processes != nullptr && g_variant_n_children(processes) > 0)
1886+ {
1887+
1888+ GVariant* first_entry = g_variant_get_child_value(processes, 0);
1889+ GVariant* pidv = g_variant_get_child_value(first_entry, 1);
1890+
1891+ retval = g_variant_get_int32(pidv);
1892+
1893+ g_variant_unref(pidv);
1894+ g_variant_unref(first_entry);
1895+ }
1896+ else
1897+ {
1898+ g_debug("Unable to get 'processes' from properties of instance at path: %s", instance_path.c_str());
1899+ }
1900+
1901+ g_variant_unref(props_dict);
1902+
1903+ return retval;
1904+ });
1905+}
1906+
1907+/** Generate the full name of the Upstart job for the job, the
1908+ instance and how all those fit together.
1909+
1910+ Handles the special case of application-click which isn't designed
1911+ to have multi-instance apps.
1912+*/
1913+std::string Upstart::upstartName()
1914+{
1915+ std::string path = job_ + "-" + std::string(appId_);
1916+ if (job_ != "application-click")
1917+ {
1918+ path += "-";
1919+ }
1920+ if (!instance_.empty())
1921+ {
1922+ path += instance_;
1923+ }
1924+
1925+ return path;
1926+}
1927+
1928+/** Gets the path to the log file for this instance */
1929+std::string Upstart::logPath()
1930+{
1931+ std::string logfile = upstartName() + ".log";
1932+
1933+ gchar* cpath = g_build_filename(g_get_user_cache_dir(), "upstart", logfile.c_str(), nullptr);
1934+ std::string path(cpath);
1935+ g_free(cpath);
1936+
1937+ return path;
1938+}
1939+
1940+/** Returns all the PIDs that are in the cgroup for this application */
1941+std::vector<pid_t> Upstart::pids()
1942+{
1943+ auto manager = std::dynamic_pointer_cast<manager::Upstart>(registry_->impl->jobs);
1944+ auto pids = manager->pidsFromCgroup(upstartName());
1945+ g_debug("Got %d PIDs for AppID '%s'", int(pids.size()), std::string(appId_).c_str());
1946+ return pids;
1947+}
1948+
1949+/** Stops this instance by asking Upstart to stop it. Upstart will then
1950+ send a SIGTERM and five seconds later start killing things. */
1951+void Upstart::stop()
1952+{
1953+ if (!registry_->impl->thread.executeOnThread<bool>([this]() {
1954+ auto manager = std::dynamic_pointer_cast<manager::Upstart>(registry_->impl->jobs);
1955+
1956+ g_debug("Stopping job %s app_id %s instance_id %s", job_.c_str(), std::string(appId_).c_str(),
1957+ instance_.c_str());
1958+
1959+ auto jobpath = manager->upstartJobPath(job_);
1960+ if (jobpath.empty())
1961+ {
1962+ throw new std::runtime_error("Unable to get job path for Upstart job '" + job_ + "'");
1963+ }
1964+
1965+ GVariantBuilder builder;
1966+ g_variant_builder_init(&builder, G_VARIANT_TYPE_TUPLE);
1967+ g_variant_builder_open(&builder, G_VARIANT_TYPE_ARRAY);
1968+
1969+ g_variant_builder_add_value(
1970+ &builder, g_variant_new_take_string(g_strdup_printf("APP_ID=%s", std::string(appId_).c_str())));
1971+
1972+ if (!instance_.empty())
1973+ {
1974+ g_variant_builder_add_value(
1975+ &builder, g_variant_new_take_string(g_strdup_printf("INSTANCE_ID=%s", instance_.c_str())));
1976+ }
1977+
1978+ g_variant_builder_close(&builder);
1979+ g_variant_builder_add_value(&builder, g_variant_new_boolean(FALSE)); /* wait */
1980+
1981+ GError* error = nullptr;
1982+ GVariant* stop_variant =
1983+ g_dbus_connection_call_sync(registry_->impl->_dbus.get(), /* Dbus */
1984+ DBUS_SERVICE_UPSTART, /* Upstart name */
1985+ jobpath.c_str(), /* path */
1986+ DBUS_INTERFACE_UPSTART_JOB, /* interface */
1987+ "Stop", /* method */
1988+ g_variant_builder_end(&builder), /* params */
1989+ nullptr, /* return */
1990+ G_DBUS_CALL_FLAGS_NONE, /* flags */
1991+ -1, /* timeout: default */
1992+ registry_->impl->thread.getCancellable().get(), /* cancellable */
1993+ &error); /* error (hopefully not) */
1994+
1995+ g_clear_pointer(&stop_variant, g_variant_unref);
1996+
1997+ if (error != nullptr)
1998+ {
1999+ g_warning("Unable to stop job %s app_id %s instance_id %s: %s", job_.c_str(),
2000+ std::string(appId_).c_str(), instance_.c_str(), error->message);
2001+ g_error_free(error);
2002+ return false;
2003+ }
2004+
2005+ return true;
2006+ }))
2007+ {
2008+ g_warning("Unable to stop Upstart instance");
2009+ }
2010+}
2011+
2012+/** Create a new Upstart Instance object that can track the job and
2013+ get information about it.
2014+
2015+ \param appId Application ID
2016+ \param job Upstart job name
2017+ \param instance Upstart instance name
2018+ \param urls URLs sent to the application (only on launch today)
2019+ \param registry Registry of persistent connections to use
2020+*/
2021+Upstart::Upstart(const AppID& appId,
2022+ const std::string& job,
2023+ const std::string& instance,
2024+ const std::vector<Application::URL>& urls,
2025+ const std::shared_ptr<Registry>& registry)
2026+ : Base(appId, job, instance, urls, registry)
2027+{
2028+ g_debug("Creating a new Upstart for '%s' instance '%s'", std::string(appId).c_str(), instance.c_str());
2029+}
2030+
2031+/** Reformat a C++ vector of URLs into a C GStrv of strings
2032+
2033+ \param urls Vector of URLs to make into C strings
2034+*/
2035+std::shared_ptr<gchar*> Upstart::urlsToStrv(const std::vector<Application::URL>& urls)
2036+{
2037+ if (urls.empty())
2038+ {
2039+ return {};
2040+ }
2041+
2042+ auto array = g_array_new(TRUE, FALSE, sizeof(gchar*));
2043+
2044+ for (auto url : urls)
2045+ {
2046+ auto str = g_strdup(url.value().c_str());
2047+ g_debug("Converting URL: %s", str);
2048+ g_array_append_val(array, str);
2049+ }
2050+
2051+ return std::shared_ptr<gchar*>((gchar**)g_array_free(array, FALSE), g_strfreev);
2052+}
2053+
2054+/** Small helper that we can new/delete to work better with C stuff */
2055+struct StartCHelper
2056+{
2057+ std::shared_ptr<Upstart> ptr;
2058+};
2059+
2060+/** Callback from starting an application. It checks to see whether the
2061+ app is already running. If it is already running then we need to send
2062+ the URLs to it via DBus.
2063+
2064+ \param obj The GDBusConnection object
2065+ \param res Async result object
2066+ \param user_data A pointer to a StartCHelper structure
2067+*/
2068+void Upstart::application_start_cb(GObject* obj, GAsyncResult* res, gpointer user_data)
2069+{
2070+ auto data = static_cast<StartCHelper*>(user_data);
2071+ GError* error{nullptr};
2072+ GVariant* result{nullptr};
2073+
2074+ tracepoint(ubuntu_app_launch, libual_start_message_callback, std::string(data->ptr->appId_).c_str());
2075+
2076+ g_debug("Started Message Callback: %s", std::string(data->ptr->appId_).c_str());
2077+
2078+ result = g_dbus_connection_call_finish(G_DBUS_CONNECTION(obj), res, &error);
2079+
2080+ g_clear_pointer(&result, g_variant_unref);
2081+
2082+ if (error != nullptr)
2083+ {
2084+ if (g_dbus_error_is_remote_error(error))
2085+ {
2086+ gchar* remote_error = g_dbus_error_get_remote_error(error);
2087+ g_debug("Remote error: %s", remote_error);
2088+ if (g_strcmp0(remote_error, "com.ubuntu.Upstart0_6.Error.AlreadyStarted") == 0)
2089+ {
2090+ auto urls = urlsToStrv(data->ptr->urls_);
2091+ second_exec(data->ptr->registry_->impl->_dbus.get(), /* DBus */
2092+ data->ptr->registry_->impl->thread.getCancellable().get(), /* cancellable */
2093+ data->ptr->primaryPid(), /* primary pid */
2094+ std::string(data->ptr->appId_).c_str(), /* appid */
2095+ urls.get()); /* urls */
2096+ }
2097+
2098+ g_free(remote_error);
2099+ }
2100+ else
2101+ {
2102+ g_warning("Unable to emit event to start application: %s", error->message);
2103+ }
2104+ g_error_free(error);
2105+ }
2106+
2107+ delete data;
2108+}
2109+
2110+std::string Upstart::upstartJobPath(const std::string& job)
2111+{
2112+ auto manager = std::dynamic_pointer_cast<manager::Upstart>(registry_->impl->jobs);
2113+ return manager->upstartJobPath(job);
2114+}
2115+
2116+} // namespace instances
2117+
2118+namespace manager
2119+{
2120+
2121+Upstart::Upstart(std::shared_ptr<Registry> registry)
2122+ : Base(registry)
2123+{
2124+}
2125+
2126+Upstart::~Upstart()
2127+{
2128+}
2129+
2130+/** Launch an application and create a new Upstart instance object to track
2131+ its progress.
2132+
2133+ \param appId Application ID
2134+ \param job Upstart job name
2135+ \param instance Upstart instance name
2136+ \param urls URLs sent to the application (only on launch today)
2137+ \param mode Whether or not to setup the environment for testing
2138+ \param getenv A function to get additional environment variable when appropriate
2139+*/
2140+std::shared_ptr<Application::Instance> Upstart::launch(
2141+ const AppID& appId,
2142+ const std::string& job,
2143+ const std::string& instance,
2144+ const std::vector<Application::URL>& urls,
2145+ launchMode mode,
2146+ std::function<std::list<std::pair<std::string, std::string>>(void)>& getenv)
2147+{
2148+ if (appId.empty())
2149+ return {};
2150+
2151+ auto registry = registry_.lock();
2152+ return registry->impl->thread.executeOnThread<std::shared_ptr<instance::Upstart>>(
2153+ [&]() -> std::shared_ptr<instance::Upstart> {
2154+ auto manager = std::dynamic_pointer_cast<manager::Upstart>(registry->impl->jobs);
2155+ std::string appIdStr{appId};
2156+ g_debug("Initializing params for an new instance::Upstart for: %s", appIdStr.c_str());
2157+
2158+ tracepoint(ubuntu_app_launch, libual_start, appIdStr.c_str());
2159+
2160+ int timeout = 1;
2161+ if (ubuntu::app_launch::Registry::Impl::isWatchingAppStarting())
2162+ {
2163+ timeout = 0;
2164+ }
2165+
2166+ auto handshake = starting_handshake_start(appIdStr.c_str(), timeout);
2167+ if (handshake == nullptr)
2168+ {
2169+ g_warning("Unable to setup starting handshake");
2170+ }
2171+
2172+ /* Figure out the DBus path for the job */
2173+ auto jobpath = manager->upstartJobPath(job);
2174+
2175+ /* Build up our environment */
2176+ auto env = getenv();
2177+
2178+ env.emplace_back(std::make_pair("APP_ID", appIdStr)); /* Application ID */
2179+ env.emplace_back(std::make_pair("APP_LAUNCHER_PID", std::to_string(getpid()))); /* Who we are, for bugs */
2180+
2181+ if (!urls.empty())
2182+ {
2183+ auto accumfunc = [](const std::string& prev, Application::URL thisurl) -> std::string {
2184+ gchar* gescaped = g_shell_quote(thisurl.value().c_str());
2185+ std::string escaped;
2186+ if (gescaped != nullptr)
2187+ {
2188+ escaped = gescaped;
2189+ g_free(gescaped);
2190+ }
2191+ else
2192+ {
2193+ g_warning("Unable to escape URL: %s", thisurl.value().c_str());
2194+ return prev;
2195+ }
2196+
2197+ if (prev.empty())
2198+ {
2199+ return escaped;
2200+ }
2201+ else
2202+ {
2203+ return prev + " " + escaped;
2204+ }
2205+ };
2206+ auto urlstring = std::accumulate(urls.begin(), urls.end(), std::string{}, accumfunc);
2207+ env.emplace_back(std::make_pair("APP_URIS", urlstring));
2208+ }
2209+
2210+ if (mode == launchMode::TEST)
2211+ {
2212+ env.emplace_back(std::make_pair("QT_LOAD_TESTABILITY", "1"));
2213+ }
2214+
2215+ /* Convert to GVariant */
2216+ GVariantBuilder builder;
2217+ g_variant_builder_init(&builder, G_VARIANT_TYPE_TUPLE);
2218+
2219+ g_variant_builder_open(&builder, G_VARIANT_TYPE_ARRAY);
2220+
2221+ for (const auto& envvar : env)
2222+ {
2223+ g_variant_builder_add_value(&builder, g_variant_new_take_string(g_strdup_printf(
2224+ "%s=%s", envvar.first.c_str(), envvar.second.c_str())));
2225+ }
2226+
2227+ g_variant_builder_close(&builder);
2228+ g_variant_builder_add_value(&builder, g_variant_new_boolean(TRUE));
2229+
2230+ auto retval = std::make_shared<instance::Upstart>(appId, job, instance, urls, registry);
2231+ auto chelper = new instance::StartCHelper{};
2232+ chelper->ptr = retval;
2233+
2234+ tracepoint(ubuntu_app_launch, handshake_wait, appIdStr.c_str());
2235+ starting_handshake_wait(handshake);
2236+ tracepoint(ubuntu_app_launch, handshake_complete, appIdStr.c_str());
2237+
2238+ /* Call the job start function */
2239+ g_debug("Asking Upstart to start task for: %s", appIdStr.c_str());
2240+ g_dbus_connection_call(registry->impl->_dbus.get(), /* bus */
2241+ DBUS_SERVICE_UPSTART, /* service name */
2242+ jobpath.c_str(), /* Path */
2243+ DBUS_INTERFACE_UPSTART_JOB, /* interface */
2244+ "Start", /* method */
2245+ g_variant_builder_end(&builder), /* params */
2246+ nullptr, /* return */
2247+ G_DBUS_CALL_FLAGS_NONE, /* flags */
2248+ -1, /* default timeout */
2249+ registry->impl->thread.getCancellable().get(), /* cancellable */
2250+ instance::Upstart::application_start_cb, /* callback */
2251+ chelper /* object */
2252+ );
2253+
2254+ tracepoint(ubuntu_app_launch, libual_start_message_sent, appIdStr.c_str());
2255+
2256+ return retval;
2257+ });
2258+}
2259+
2260+/** Special characters that could be an application name that
2261+ would activate in a regex */
2262+const static std::regex regexCharacters("([\\.\\-])");
2263+
2264+std::shared_ptr<Application::Instance> Upstart::existing(const AppID& appId,
2265+ const std::string& job,
2266+ const std::string& instance,
2267+ const std::vector<Application::URL>& urls)
2268+{
2269+ return std::make_shared<instance::Upstart>(appId, job, instance, urls, registry_.lock());
2270+}
2271+
2272+std::vector<std::shared_ptr<instance::Base>> Upstart::instances(const AppID& appID, const std::string& job)
2273+{
2274+ std::vector<std::shared_ptr<instance::Base>> vect;
2275+ auto startsWith = std::string(appID);
2276+ if (job != "application-click")
2277+ {
2278+ startsWith += "-";
2279+ }
2280+
2281+ auto regexstr =
2282+ std::string{"^(?:"} + std::regex_replace(startsWith, regexCharacters, "\\$&") + std::string{")(\\d*)$"};
2283+ auto instanceRegex = std::regex(regexstr);
2284+
2285+ for (auto instance : upstartInstancesForJob(job))
2286+ {
2287+ std::smatch instanceMatch;
2288+ g_debug("Looking at job '%s' instance: %s", job.c_str(), instance.c_str());
2289+ if (std::regex_match(instance, instanceMatch, instanceRegex))
2290+ {
2291+ auto app = existing(appID, job, instanceMatch[1].str(), {});
2292+ vect.emplace_back(std::dynamic_pointer_cast<instance::Base>(app));
2293+ }
2294+ }
2295+
2296+ g_debug("App '%s' has %d instances", std::string(appID).c_str(), int(vect.size()));
2297+
2298+ return vect;
2299+}
2300+
2301+/** Initialize the CGManager connection, including a timeout to disconnect
2302+ as CGManager doesn't free resources entirely well. So it's better if
2303+ we connect and disconnect occationally */
2304+void Upstart::initCGManager()
2305+{
2306+ if (cgManager_)
2307+ return;
2308+
2309+ std::promise<std::shared_ptr<GDBusConnection>> promise;
2310+ auto future = promise.get_future();
2311+ auto registry = registry_.lock();
2312+
2313+ registry->impl->thread.executeOnThread([this, &promise, &registry]() {
2314+ bool use_session_bus = g_getenv("UBUNTU_APP_LAUNCH_CG_MANAGER_SESSION_BUS") != nullptr;
2315+ if (use_session_bus)
2316+ {
2317+ /* For working dbusmock */
2318+ g_debug("Connecting to CG Manager on session bus");
2319+ promise.set_value(registry->impl->_dbus);
2320+ return;
2321+ }
2322+
2323+ auto cancel =
2324+ std::shared_ptr<GCancellable>(g_cancellable_new(), [](GCancellable* cancel) { g_clear_object(&cancel); });
2325+
2326+ /* Ensure that we do not wait for more than a second */
2327+ registry->impl->thread.timeoutSeconds(std::chrono::seconds{1},
2328+ [cancel]() { g_cancellable_cancel(cancel.get()); });
2329+
2330+ g_dbus_connection_new_for_address(
2331+ CGMANAGER_DBUS_PATH, /* cgmanager path */
2332+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, /* flags */
2333+ nullptr, /* Auth Observer */
2334+ cancel.get(), /* Cancellable */
2335+ [](GObject* obj, GAsyncResult* res, gpointer data) -> void {
2336+ GError* error = nullptr;
2337+ auto promise = reinterpret_cast<std::promise<std::shared_ptr<GDBusConnection>>*>(data);
2338+
2339+ auto gcon = g_dbus_connection_new_for_address_finish(res, &error);
2340+ if (error != nullptr)
2341+ {
2342+ g_error_free(error);
2343+ }
2344+
2345+ auto con = std::shared_ptr<GDBusConnection>(gcon, [](GDBusConnection* con) { g_clear_object(&con); });
2346+ promise->set_value(con);
2347+ },
2348+ &promise);
2349+ });
2350+
2351+ cgManager_ = future.get();
2352+ registry->impl->thread.timeoutSeconds(std::chrono::seconds{10}, [this]() { cgManager_.reset(); });
2353+}
2354+
2355+/** Get a list of PIDs from a CGroup, uses the CGManager connection to list
2356+ all of the PIDs. It is important to note that this is an IPC call, so it can
2357+ by its nature, be racy. Once the message has been sent the group can change.
2358+ You should take that into account in your usage of it. */
2359+std::vector<pid_t> Upstart::pidsFromCgroup(const std::string& jobpath)
2360+{
2361+ initCGManager();
2362+ auto lmanager = cgManager_; /* Grab a local copy so we ensure it lasts through our lifetime */
2363+ auto registry = registry_.lock();
2364+
2365+ return registry->impl->thread.executeOnThread<std::vector<pid_t>>([&jobpath, lmanager]() -> std::vector<pid_t> {
2366+ GError* error = nullptr;
2367+ const gchar* name = g_getenv("UBUNTU_APP_LAUNCH_CG_MANAGER_NAME");
2368+ std::string groupname;
2369+ if (!jobpath.empty())
2370+ {
2371+ groupname = "upstart/" + jobpath;
2372+ }
2373+
2374+ g_debug("Looking for cg manager '%s' group '%s'", name, groupname.c_str());
2375+
2376+ GVariant* vtpids = g_dbus_connection_call_sync(
2377+ lmanager.get(), /* connection */
2378+ name, /* bus name for direct connection is NULL */
2379+ "/org/linuxcontainers/cgmanager", /* object */
2380+ "org.linuxcontainers.cgmanager0_0", /* interface */
2381+ "GetTasksRecursive", /* method */
2382+ g_variant_new("(ss)", "freezer", groupname.empty() ? "" : groupname.c_str()), /* params */
2383+ G_VARIANT_TYPE("(ai)"), /* output */
2384+ G_DBUS_CALL_FLAGS_NONE, /* flags */
2385+ -1, /* default timeout */
2386+ nullptr, /* cancellable */
2387+ &error); /* error */
2388+
2389+ if (error != nullptr)
2390+ {
2391+ g_warning("Unable to get PID list from cgroup manager: %s", error->message);
2392+ g_error_free(error);
2393+ return {};
2394+ }
2395+
2396+ GVariant* vpids = g_variant_get_child_value(vtpids, 0);
2397+ GVariantIter iter;
2398+ g_variant_iter_init(&iter, vpids);
2399+ gint32 pid;
2400+ std::vector<pid_t> pids;
2401+
2402+ while (g_variant_iter_loop(&iter, "i", &pid))
2403+ {
2404+ pids.push_back(pid);
2405+ }
2406+
2407+ g_variant_unref(vpids);
2408+ g_variant_unref(vtpids);
2409+
2410+ return pids;
2411+ });
2412+}
2413+
2414+/** Looks to find the Upstart object path for a specific Upstart job. This first
2415+ checks the cache, and otherwise does the lookup on DBus. */
2416+std::string Upstart::upstartJobPath(const std::string& job)
2417+{
2418+ try
2419+ {
2420+ return upstartJobPathCache_.at(job);
2421+ }
2422+ catch (std::out_of_range& e)
2423+ {
2424+ auto registry = registry_.lock();
2425+ auto path = registry->impl->thread.executeOnThread<std::string>([this, &job, &registry]() -> std::string {
2426+ GError* error = nullptr;
2427+ GVariant* job_path_variant =
2428+ g_dbus_connection_call_sync(registry->impl->_dbus.get(), /* connection */
2429+ DBUS_SERVICE_UPSTART, /* service */
2430+ DBUS_PATH_UPSTART, /* path */
2431+ DBUS_INTERFACE_UPSTART, /* iface */
2432+ "GetJobByName", /* method */
2433+ g_variant_new("(s)", job.c_str()), /* params */
2434+ G_VARIANT_TYPE("(o)"), /* return */
2435+ G_DBUS_CALL_FLAGS_NONE, /* flags */
2436+ -1, /* timeout: default */
2437+ registry->impl->thread.getCancellable().get(), /* cancellable */
2438+ &error); /* error */
2439+
2440+ if (error != nullptr)
2441+ {
2442+ g_warning("Unable to find job '%s': %s", job.c_str(), error->message);
2443+ g_error_free(error);
2444+ return {};
2445+ }
2446+
2447+ gchar* job_path = nullptr;
2448+ g_variant_get(job_path_variant, "(o)", &job_path);
2449+ g_variant_unref(job_path_variant);
2450+
2451+ if (job_path != nullptr)
2452+ {
2453+ std::string path(job_path);
2454+ g_free(job_path);
2455+ return path;
2456+ }
2457+ else
2458+ {
2459+ return {};
2460+ }
2461+ });
2462+
2463+ upstartJobPathCache_[job] = path;
2464+ return path;
2465+ }
2466+}
2467+
2468+/** Queries Upstart to get all the instances of a given job. This
2469+ can take a while as the number of dbus calls is n+1. It is
2470+ rare that apps have many instances though. */
2471+std::list<std::string> Upstart::upstartInstancesForJob(const std::string& job)
2472+{
2473+ std::string jobpath = upstartJobPath(job);
2474+ if (jobpath.empty())
2475+ {
2476+ return {};
2477+ }
2478+
2479+ auto registry = registry_.lock();
2480+ return registry->impl->thread.executeOnThread<std::list<std::string>>(
2481+ [this, &job, &jobpath, &registry]() -> std::list<std::string> {
2482+ GError* error = nullptr;
2483+ GVariant* instance_tuple =
2484+ g_dbus_connection_call_sync(registry->impl->_dbus.get(), /* connection */
2485+ DBUS_SERVICE_UPSTART, /* service */
2486+ jobpath.c_str(), /* object path */
2487+ DBUS_INTERFACE_UPSTART_JOB, /* iface */
2488+ "GetAllInstances", /* method */
2489+ nullptr, /* params */
2490+ G_VARIANT_TYPE("(ao)"), /* return type */
2491+ G_DBUS_CALL_FLAGS_NONE, /* flags */
2492+ -1, /* timeout: default */
2493+ registry->impl->thread.getCancellable().get(), /* cancellable */
2494+ &error);
2495+
2496+ if (error != nullptr)
2497+ {
2498+ g_warning("Unable to get instances of job '%s': %s", job.c_str(), error->message);
2499+ g_error_free(error);
2500+ return {};
2501+ }
2502+
2503+ if (instance_tuple == nullptr)
2504+ {
2505+ return {};
2506+ }
2507+
2508+ GVariant* instance_list = g_variant_get_child_value(instance_tuple, 0);
2509+ g_variant_unref(instance_tuple);
2510+
2511+ GVariantIter instance_iter;
2512+ g_variant_iter_init(&instance_iter, instance_list);
2513+ const gchar* instance_path = nullptr;
2514+ std::list<std::string> instances;
2515+
2516+ while (g_variant_iter_loop(&instance_iter, "&o", &instance_path))
2517+ {
2518+ GVariant* props_tuple =
2519+ g_dbus_connection_call_sync(registry->impl->_dbus.get(), /* connection */
2520+ DBUS_SERVICE_UPSTART, /* service */
2521+ instance_path, /* object path */
2522+ "org.freedesktop.DBus.Properties", /* interface */
2523+ "GetAll", /* method */
2524+ g_variant_new("(s)", DBUS_INTERFACE_UPSTART_INSTANCE), /* params */
2525+ G_VARIANT_TYPE("(a{sv})"), /* return type */
2526+ G_DBUS_CALL_FLAGS_NONE, /* flags */
2527+ -1, /* timeout: default */
2528+ registry->impl->thread.getCancellable().get(), /* cancellable */
2529+ &error);
2530+
2531+ if (error != nullptr)
2532+ {
2533+ g_warning("Unable to name of instance '%s': %s", instance_path, error->message);
2534+ g_error_free(error);
2535+ error = nullptr;
2536+ continue;
2537+ }
2538+
2539+ GVariant* props_dict = g_variant_get_child_value(props_tuple, 0);
2540+
2541+ GVariant* namev = g_variant_lookup_value(props_dict, "name", G_VARIANT_TYPE_STRING);
2542+ if (namev != nullptr)
2543+ {
2544+ auto name = g_variant_get_string(namev, NULL);
2545+ g_debug("Adding instance for job '%s': %s", job.c_str(), name);
2546+ instances.push_back(name);
2547+ g_variant_unref(namev);
2548+ }
2549+
2550+ g_variant_unref(props_dict);
2551+ g_variant_unref(props_tuple);
2552+ }
2553+
2554+ g_variant_unref(instance_list);
2555+
2556+ return instances;
2557+ });
2558+}
2559+
2560+std::list<std::shared_ptr<Application>> Upstart::runningApps()
2561+{
2562+ std::list<std::string> instances;
2563+
2564+ /* Get all the legacy instances */
2565+ instances.splice(instances.begin(), upstartInstancesForJob("application-legacy"));
2566+ /* Get all the snap instances */
2567+ instances.splice(instances.begin(), upstartInstancesForJob("application-snap"));
2568+
2569+ /* Remove the instance ID */
2570+ std::transform(instances.begin(), instances.end(), instances.begin(), [](std::string& instancename) -> std::string {
2571+ static const std::regex instanceregex("^(.*)-[0-9]*$");
2572+ std::smatch match;
2573+ if (std::regex_match(instancename, match, instanceregex))
2574+ {
2575+ return match[1].str();
2576+ }
2577+ else
2578+ {
2579+ g_warning("Unable to match instance name: %s", instancename.c_str());
2580+ return {};
2581+ }
2582+ });
2583+
2584+ /* Deduplicate Set */
2585+ std::set<std::string> instanceset;
2586+ for (auto instance : instances)
2587+ {
2588+ if (!instance.empty())
2589+ instanceset.insert(instance);
2590+ }
2591+
2592+ /* Add in the click instances */
2593+ for (auto instance : upstartInstancesForJob("application-click"))
2594+ {
2595+ instanceset.insert(instance);
2596+ }
2597+
2598+ g_debug("Overall there are %d instances: %s", int(instanceset.size()),
2599+ std::accumulate(instanceset.begin(), instanceset.end(), std::string{},
2600+ [](const std::string& instr, std::string instance) {
2601+ return instr.empty() ? instance : instr + ", " + instance;
2602+ })
2603+ .c_str());
2604+
2605+ /* Convert to Applications */
2606+ auto registry = registry_.lock();
2607+ std::list<std::shared_ptr<Application>> apps;
2608+ for (auto instance : instanceset)
2609+ {
2610+ auto appid = AppID::find(registry, instance);
2611+ auto app = Application::create(appid, registry);
2612+ apps.push_back(app);
2613+ }
2614+
2615+ return apps;
2616+}
2617+
2618+} // namespace manager
2619+} // namespace jobs
2620+} // namespace app_launch
2621+} // namespace ubuntu
2622
2623=== added file 'libubuntu-app-launch/jobs-upstart.h'
2624--- libubuntu-app-launch/jobs-upstart.h 1970-01-01 00:00:00 +0000
2625+++ libubuntu-app-launch/jobs-upstart.h 2016-11-10 21:58:30 +0000
2626@@ -0,0 +1,75 @@
2627+/*
2628+ * Copyright © 2016 Canonical Ltd.
2629+ *
2630+ * This program is free software: you can redistribute it and/or modify it
2631+ * under the terms of the GNU General Public License version 3, as published
2632+ * by the Free Software Foundation.
2633+ *
2634+ * This program is distributed in the hope that it will be useful, but
2635+ * WITHOUT ANY WARRANTY; without even the implied warranties of
2636+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2637+ * PURPOSE. See the GNU General Public License for more details.
2638+ *
2639+ * You should have received a copy of the GNU General Public License along
2640+ * with this program. If not, see <http://www.gnu.org/licenses/>.
2641+ *
2642+ * Authors:
2643+ * Ted Gould <ted.gould@canonical.com>
2644+ */
2645+
2646+#pragma once
2647+
2648+#include "jobs-base.h"
2649+#include <gio/gio.h>
2650+#include <map>
2651+
2652+namespace ubuntu
2653+{
2654+namespace app_launch
2655+{
2656+namespace jobs
2657+{
2658+namespace manager
2659+{
2660+
2661+class Upstart : public Base
2662+{
2663+public:
2664+ Upstart(std::shared_ptr<Registry> registry);
2665+ virtual ~Upstart();
2666+
2667+ virtual std::shared_ptr<Application::Instance> launch(
2668+ const AppID& appId,
2669+ const std::string& job,
2670+ const std::string& instance,
2671+ const std::vector<Application::URL>& urls,
2672+ launchMode mode,
2673+ std::function<std::list<std::pair<std::string, std::string>>(void)>& getenv) override;
2674+ virtual std::shared_ptr<Application::Instance> existing(const AppID& appId,
2675+ const std::string& job,
2676+ const std::string& instance,
2677+ const std::vector<Application::URL>& urls) override;
2678+
2679+ virtual std::list<std::shared_ptr<Application>> runningApps() override;
2680+
2681+ virtual std::vector<std::shared_ptr<instance::Base>> instances(const AppID& appID, const std::string& job) override;
2682+
2683+ std::vector<pid_t> pidsFromCgroup(const std::string& jobpath);
2684+
2685+ std::list<std::string> upstartInstancesForJob(const std::string& job);
2686+ std::string upstartJobPath(const std::string& job);
2687+
2688+private:
2689+ void initCGManager();
2690+
2691+ std::shared_ptr<GDBusConnection> cgManager_;
2692+
2693+ /** Getting the Upstart job path is relatively expensive in
2694+ that it requires a DBus call. Worth keeping a cache of. */
2695+ std::map<std::string, std::string> upstartJobPathCache_;
2696+};
2697+
2698+} // namespace manager
2699+} // namespace jobs
2700+} // namespace app_launch
2701+} // namespace ubuntu
2702
2703=== modified file 'libubuntu-app-launch/libubuntu-app-launch.map'
2704--- libubuntu-app-launch/libubuntu-app-launch.map 2016-05-19 16:24:11 +0000
2705+++ libubuntu-app-launch/libubuntu-app-launch.map 2016-11-10 21:58:30 +0000
2706@@ -14,6 +14,7 @@
2707 ubuntu::app_launch::Helper::*;
2708 typeinfo?for?ubuntu::app_launch::Helper;
2709 typeinfo?name?for?ubuntu::app_launch::Helper;
2710+ ubuntu::app_launch::operator*;
2711 ubuntu::app_launch::oom::*;
2712 };
2713 local:
2714
2715=== modified file 'libubuntu-app-launch/registry-impl.cpp'
2716--- libubuntu-app-launch/registry-impl.cpp 2016-09-23 22:30:51 +0000
2717+++ libubuntu-app-launch/registry-impl.cpp 2016-11-10 21:58:30 +0000
2718@@ -19,7 +19,7 @@
2719
2720 #include "registry-impl.h"
2721 #include "application-icon-finder.h"
2722-#include <cgmanager/cgmanager.h>
2723+#include <regex>
2724 #include <upstart.h>
2725
2726 namespace ubuntu
2727@@ -34,7 +34,24 @@
2728 _clickDB.reset();
2729
2730 zgLog_.reset();
2731- cgManager_.reset();
2732+ jobs.reset();
2733+
2734+ auto dohandle = [&](guint& handle) {
2735+ if (handle != 0)
2736+ {
2737+ g_dbus_connection_signal_unsubscribe(_dbus.get(), handle);
2738+ handle = 0;
2739+ }
2740+ };
2741+
2742+ dohandle(handle_appStarted);
2743+ dohandle(handle_appStopped);
2744+ dohandle(handle_appFailed);
2745+ dohandle(handle_appPaused);
2746+ dohandle(handle_appResumed);
2747+ dohandle(handle_managerSignalFocus);
2748+ dohandle(handle_managerSignalResume);
2749+ dohandle(handle_managerSignalStarting);
2750
2751 if (_dbus)
2752 g_dbus_connection_flush_sync(_dbus.get(), nullptr, nullptr);
2753@@ -42,7 +59,6 @@
2754 })
2755 , _registry(registry)
2756 , _iconFinders()
2757-// _manager(nullptr)
2758 {
2759 auto cancel = thread.getCancellable();
2760 _dbus = thread.executeOnThread<std::shared_ptr<GDBusConnection>>([cancel]() {
2761@@ -214,257 +230,6 @@
2762 });
2763 }
2764
2765-/** Initialize the CGManager connection, including a timeout to disconnect
2766- as CGManager doesn't free resources entirely well. So it's better if
2767- we connect and disconnect occationally */
2768-void Registry::Impl::initCGManager()
2769-{
2770- if (cgManager_)
2771- return;
2772-
2773- std::promise<std::shared_ptr<GDBusConnection>> promise;
2774- auto future = promise.get_future();
2775-
2776- thread.executeOnThread([this, &promise]() {
2777- bool use_session_bus = g_getenv("UBUNTU_APP_LAUNCH_CG_MANAGER_SESSION_BUS") != nullptr;
2778- if (use_session_bus)
2779- {
2780- /* For working dbusmock */
2781- g_debug("Connecting to CG Manager on session bus");
2782- promise.set_value(_dbus);
2783- return;
2784- }
2785-
2786- auto cancel =
2787- std::shared_ptr<GCancellable>(g_cancellable_new(), [](GCancellable* cancel) { g_clear_object(&cancel); });
2788-
2789- /* Ensure that we do not wait for more than a second */
2790- thread.timeoutSeconds(std::chrono::seconds{1}, [cancel]() { g_cancellable_cancel(cancel.get()); });
2791-
2792- g_dbus_connection_new_for_address(
2793- CGMANAGER_DBUS_PATH, /* cgmanager path */
2794- G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, /* flags */
2795- nullptr, /* Auth Observer */
2796- cancel.get(), /* Cancellable */
2797- [](GObject* obj, GAsyncResult* res, gpointer data) -> void {
2798- GError* error = nullptr;
2799- auto promise = reinterpret_cast<std::promise<std::shared_ptr<GDBusConnection>>*>(data);
2800-
2801- auto gcon = g_dbus_connection_new_for_address_finish(res, &error);
2802- if (error != nullptr)
2803- {
2804- g_error_free(error);
2805- }
2806-
2807- auto con = std::shared_ptr<GDBusConnection>(gcon, [](GDBusConnection* con) { g_clear_object(&con); });
2808- promise->set_value(con);
2809- },
2810- &promise);
2811- });
2812-
2813- cgManager_ = future.get();
2814- thread.timeoutSeconds(std::chrono::seconds{10}, [this]() { cgManager_.reset(); });
2815-}
2816-
2817-/** Get a list of PIDs from a CGroup, uses the CGManager connection to list
2818- all of the PIDs. It is important to note that this is an IPC call, so it can
2819- by its nature, be racy. Once the message has been sent the group can change.
2820- You should take that into account in your usage of it. */
2821-std::vector<pid_t> Registry::Impl::pidsFromCgroup(const std::string& jobpath)
2822-{
2823- initCGManager();
2824- auto lmanager = cgManager_; /* Grab a local copy so we ensure it lasts through our lifetime */
2825-
2826- return thread.executeOnThread<std::vector<pid_t>>([&jobpath, lmanager]() -> std::vector<pid_t> {
2827- GError* error = nullptr;
2828- const gchar* name = g_getenv("UBUNTU_APP_LAUNCH_CG_MANAGER_NAME");
2829- std::string groupname;
2830- if (!jobpath.empty())
2831- {
2832- groupname = "upstart/" + jobpath;
2833- }
2834-
2835- g_debug("Looking for cg manager '%s' group '%s'", name, groupname.c_str());
2836-
2837- GVariant* vtpids = g_dbus_connection_call_sync(
2838- lmanager.get(), /* connection */
2839- name, /* bus name for direct connection is NULL */
2840- "/org/linuxcontainers/cgmanager", /* object */
2841- "org.linuxcontainers.cgmanager0_0", /* interface */
2842- "GetTasksRecursive", /* method */
2843- g_variant_new("(ss)", "freezer", groupname.empty() ? "" : groupname.c_str()), /* params */
2844- G_VARIANT_TYPE("(ai)"), /* output */
2845- G_DBUS_CALL_FLAGS_NONE, /* flags */
2846- -1, /* default timeout */
2847- nullptr, /* cancellable */
2848- &error); /* error */
2849-
2850- if (error != nullptr)
2851- {
2852- g_warning("Unable to get PID list from cgroup manager: %s", error->message);
2853- g_error_free(error);
2854- return {};
2855- }
2856-
2857- GVariant* vpids = g_variant_get_child_value(vtpids, 0);
2858- GVariantIter iter;
2859- g_variant_iter_init(&iter, vpids);
2860- gint32 pid;
2861- std::vector<pid_t> pids;
2862-
2863- while (g_variant_iter_loop(&iter, "i", &pid))
2864- {
2865- pids.push_back(pid);
2866- }
2867-
2868- g_variant_unref(vpids);
2869- g_variant_unref(vtpids);
2870-
2871- return pids;
2872- });
2873-}
2874-
2875-/** Looks to find the Upstart object path for a specific Upstart job. This first
2876- checks the cache, and otherwise does the lookup on DBus. */
2877-std::string Registry::Impl::upstartJobPath(const std::string& job)
2878-{
2879- try
2880- {
2881- return upstartJobPathCache_.at(job);
2882- }
2883- catch (std::out_of_range& e)
2884- {
2885- auto path = thread.executeOnThread<std::string>([this, &job]() -> std::string {
2886- GError* error = nullptr;
2887- GVariant* job_path_variant = g_dbus_connection_call_sync(_dbus.get(), /* connection */
2888- DBUS_SERVICE_UPSTART, /* service */
2889- DBUS_PATH_UPSTART, /* path */
2890- DBUS_INTERFACE_UPSTART, /* iface */
2891- "GetJobByName", /* method */
2892- g_variant_new("(s)", job.c_str()), /* params */
2893- G_VARIANT_TYPE("(o)"), /* return */
2894- G_DBUS_CALL_FLAGS_NONE, /* flags */
2895- -1, /* timeout: default */
2896- thread.getCancellable().get(), /* cancellable */
2897- &error); /* error */
2898-
2899- if (error != nullptr)
2900- {
2901- g_warning("Unable to find job '%s': %s", job.c_str(), error->message);
2902- g_error_free(error);
2903- return {};
2904- }
2905-
2906- gchar* job_path = nullptr;
2907- g_variant_get(job_path_variant, "(o)", &job_path);
2908- g_variant_unref(job_path_variant);
2909-
2910- if (job_path != nullptr)
2911- {
2912- std::string path(job_path);
2913- g_free(job_path);
2914- return path;
2915- }
2916- else
2917- {
2918- return {};
2919- }
2920- });
2921-
2922- upstartJobPathCache_[job] = path;
2923- return path;
2924- }
2925-}
2926-
2927-/** Queries Upstart to get all the instances of a given job. This
2928- can take a while as the number of dbus calls is n+1. It is
2929- rare that apps have many instances though. */
2930-std::list<std::string> Registry::Impl::upstartInstancesForJob(const std::string& job)
2931-{
2932- std::string jobpath = upstartJobPath(job);
2933- if (jobpath.empty())
2934- {
2935- return {};
2936- }
2937-
2938- return thread.executeOnThread<std::list<std::string>>([this, &job, &jobpath]() -> std::list<std::string> {
2939- GError* error = nullptr;
2940- GVariant* instance_tuple = g_dbus_connection_call_sync(_dbus.get(), /* connection */
2941- DBUS_SERVICE_UPSTART, /* service */
2942- jobpath.c_str(), /* object path */
2943- DBUS_INTERFACE_UPSTART_JOB, /* iface */
2944- "GetAllInstances", /* method */
2945- nullptr, /* params */
2946- G_VARIANT_TYPE("(ao)"), /* return type */
2947- G_DBUS_CALL_FLAGS_NONE, /* flags */
2948- -1, /* timeout: default */
2949- thread.getCancellable().get(), /* cancellable */
2950- &error);
2951-
2952- if (error != nullptr)
2953- {
2954- g_warning("Unable to get instances of job '%s': %s", job.c_str(), error->message);
2955- g_error_free(error);
2956- return {};
2957- }
2958-
2959- if (instance_tuple == nullptr)
2960- {
2961- return {};
2962- }
2963-
2964- GVariant* instance_list = g_variant_get_child_value(instance_tuple, 0);
2965- g_variant_unref(instance_tuple);
2966-
2967- GVariantIter instance_iter;
2968- g_variant_iter_init(&instance_iter, instance_list);
2969- const gchar* instance_path = nullptr;
2970- std::list<std::string> instances;
2971-
2972- while (g_variant_iter_loop(&instance_iter, "&o", &instance_path))
2973- {
2974- GVariant* props_tuple =
2975- g_dbus_connection_call_sync(_dbus.get(), /* connection */
2976- DBUS_SERVICE_UPSTART, /* service */
2977- instance_path, /* object path */
2978- "org.freedesktop.DBus.Properties", /* interface */
2979- "GetAll", /* method */
2980- g_variant_new("(s)", DBUS_INTERFACE_UPSTART_INSTANCE), /* params */
2981- G_VARIANT_TYPE("(a{sv})"), /* return type */
2982- G_DBUS_CALL_FLAGS_NONE, /* flags */
2983- -1, /* timeout: default */
2984- thread.getCancellable().get(), /* cancellable */
2985- &error);
2986-
2987- if (error != nullptr)
2988- {
2989- g_warning("Unable to name of instance '%s': %s", instance_path, error->message);
2990- g_error_free(error);
2991- error = nullptr;
2992- continue;
2993- }
2994-
2995- GVariant* props_dict = g_variant_get_child_value(props_tuple, 0);
2996-
2997- GVariant* namev = g_variant_lookup_value(props_dict, "name", G_VARIANT_TYPE_STRING);
2998- if (namev != nullptr)
2999- {
3000- auto name = g_variant_get_string(namev, NULL);
3001- g_debug("Adding instance for job '%s': %s", job.c_str(), name);
3002- instances.push_back(name);
3003- g_variant_unref(namev);
3004- }
3005-
3006- g_variant_unref(props_dict);
3007- g_variant_unref(props_tuple);
3008- }
3009-
3010- g_variant_unref(instance_list);
3011-
3012- return instances;
3013- });
3014-}
3015-
3016 /** Send an event to Zietgeist using the registry thread so that
3017 the callback comes back in the right place. */
3018 void Registry::Impl::zgSendEvent(AppID appid, const std::string& eventtype)
3019@@ -536,24 +301,193 @@
3020 return _iconFinders[basePath];
3021 }
3022
3023-#if 0
3024-void
3025-Registry::Impl::setManager (Registry::Manager* manager)
3026-{
3027- if (_manager != nullptr)
3028+/** Structure to track the data needed for upstart events. This cleans
3029+ up the lifecycle as we're passing this as a pointer through the
3030+ GLib calls. */
3031+struct upstartEventData
3032+{
3033+ /** Keeping a weak pointer because the handle is held by
3034+ the registry implementation. */
3035+ std::weak_ptr<Registry> weakReg;
3036+};
3037+
3038+/** Take the GVariant of parameters and turn them into an application and
3039+ and instance. Easier to read in the smaller function */
3040+std::tuple<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>> Registry::Impl::managerParams(
3041+ const std::shared_ptr<GVariant>& params, const std::shared_ptr<Registry>& reg)
3042+{
3043+ std::shared_ptr<Application> app;
3044+ std::shared_ptr<Application::Instance> instance;
3045+
3046+ const gchar* cappid = nullptr;
3047+ g_variant_get(params.get(), "(&s)", &cappid);
3048+
3049+ auto appid = ubuntu::app_launch::AppID::find(reg, cappid);
3050+ app = ubuntu::app_launch::Application::create(appid, reg);
3051+
3052+ return std::make_tuple(app, instance);
3053+}
3054+
3055+/** Used to store data for manager based signal handlers. Has a link to the
3056+ registry and the callback to use in a C++ style. */
3057+struct managerEventData
3058+{
3059+ /* Keeping a weak pointer because the handle is held by
3060+ the registry implementation. */
3061+ std::weak_ptr<Registry> weakReg;
3062+ std::function<void(const std::shared_ptr<Registry>& reg,
3063+ const std::shared_ptr<Application>& app,
3064+ const std::shared_ptr<Application::Instance>& instance,
3065+ const std::shared_ptr<GDBusConnection>&,
3066+ const std::string&,
3067+ const std::shared_ptr<GVariant>&)>
3068+ func;
3069+};
3070+
3071+/** Register for a signal for the manager. All of the signals needed this same
3072+ code so it got pulled out into a function. Takes the same of the signal, the registry
3073+ that we're using and a function to call after we've messaged all the parameters
3074+ into being something C++-ish. */
3075+guint Registry::Impl::managerSignalHelper(const std::shared_ptr<Registry>& reg,
3076+ const std::string& signalname,
3077+ std::function<void(const std::shared_ptr<Registry>& reg,
3078+ const std::shared_ptr<Application>& app,
3079+ const std::shared_ptr<Application::Instance>& instance,
3080+ const std::shared_ptr<GDBusConnection>&,
3081+ const std::string&,
3082+ const std::shared_ptr<GVariant>&)> responsefunc)
3083+{
3084+ managerEventData* focusdata = new managerEventData{reg, responsefunc};
3085+
3086+ return g_dbus_connection_signal_subscribe(
3087+ reg->impl->_dbus.get(), /* bus */
3088+ nullptr, /* sender */
3089+ "com.canonical.UbuntuAppLaunch", /* interface */
3090+ signalname.c_str(), /* signal */
3091+ "/", /* path */
3092+ nullptr, /* arg0 */
3093+ G_DBUS_SIGNAL_FLAGS_NONE,
3094+ [](GDBusConnection* cconn, const gchar* csender, const gchar*, const gchar*, const gchar*, GVariant* params,
3095+ gpointer user_data) -> void {
3096+ auto data = reinterpret_cast<managerEventData*>(user_data);
3097+ auto reg = data->weakReg.lock();
3098+
3099+ /* If we're still conneted and the manager has been cleared
3100+ we'll just be a no-op */
3101+ if (!reg->impl->manager_)
3102+ {
3103+ return;
3104+ }
3105+
3106+ auto vparams = std::shared_ptr<GVariant>(g_variant_ref(params), g_variant_unref);
3107+ auto conn = std::shared_ptr<GDBusConnection>(reinterpret_cast<GDBusConnection*>(g_object_ref(cconn)),
3108+ [](GDBusConnection* con) { g_clear_object(&con); });
3109+ std::string sender = csender;
3110+ std::shared_ptr<Application> app;
3111+ std::shared_ptr<Application::Instance> instance;
3112+
3113+ std::tie(app, instance) = managerParams(vparams, reg);
3114+
3115+ data->func(reg, app, instance, conn, sender, vparams);
3116+ },
3117+ focusdata,
3118+ [](gpointer user_data) {
3119+ auto data = reinterpret_cast<managerEventData*>(user_data);
3120+ delete data;
3121+ }); /* user data destroy */
3122+}
3123+
3124+/** Set the manager for the registry. This includes tracking the pointer
3125+ as well as setting up the signals to call back into the manager. The
3126+ signals are only setup once per registry even if the manager is cleared
3127+ and changed again. They will just be no-op's in those cases.
3128+*/
3129+void Registry::Impl::setManager(std::shared_ptr<Registry::Manager> manager, std::shared_ptr<Registry> reg)
3130+{
3131+ if (reg->impl->manager_)
3132 {
3133 throw std::runtime_error("Already have a manager and trying to set another");
3134 }
3135
3136- _manager = manager;
3137+ g_debug("Setting a new manager");
3138+ reg->impl->manager_ = manager;
3139+
3140+ std::call_once(reg->impl->flag_managerSignals, [reg]() {
3141+ if (!reg->impl->thread.executeOnThread<bool>([reg]() {
3142+ reg->impl->handle_managerSignalFocus = managerSignalHelper(
3143+ reg, "UnityFocusRequest",
3144+ [](const std::shared_ptr<Registry>& reg, const std::shared_ptr<Application>& app,
3145+ const std::shared_ptr<Application::Instance>& instance,
3146+ const std::shared_ptr<GDBusConnection>& conn, const std::string& sender,
3147+ const std::shared_ptr<GVariant>& params) {
3148+ /* Nothing to do today */
3149+ reg->impl->manager_->focusRequest(app, instance, [](bool response) {
3150+ /* NOTE: We have no clue what thread this is gonna be
3151+ executed on, but since we're just talking to the GDBus
3152+ thread it isn't an issue today. Be careful in changing
3153+ this code. */
3154+ });
3155+ });
3156+ reg->impl->handle_managerSignalStarting = managerSignalHelper(
3157+ reg, "UnityStartingBroadcast",
3158+ [](const std::shared_ptr<Registry>& reg, const std::shared_ptr<Application>& app,
3159+ const std::shared_ptr<Application::Instance>& instance,
3160+ const std::shared_ptr<GDBusConnection>& conn, const std::string& sender,
3161+ const std::shared_ptr<GVariant>& params) {
3162+
3163+ reg->impl->manager_->startingRequest(app, instance, [conn, sender, params](bool response) {
3164+ /* NOTE: We have no clue what thread this is gonna be
3165+ executed on, but since we're just talking to the GDBus
3166+ thread it isn't an issue today. Be careful in changing
3167+ this code. */
3168+ if (response)
3169+ {
3170+ g_dbus_connection_emit_signal(conn.get(), sender.c_str(), /* destination */
3171+ "/", /* path */
3172+ "com.canonical.UbuntuAppLaunch", /* interface */
3173+ "UnityStartingSignal", /* signal */
3174+ params.get(), /* params, the same */
3175+ nullptr); /* error */
3176+ }
3177+ });
3178+ });
3179+ reg->impl->handle_managerSignalResume = managerSignalHelper(
3180+ reg, "UnityResumeRequest",
3181+ [](const std::shared_ptr<Registry>& reg, const std::shared_ptr<Application>& app,
3182+ const std::shared_ptr<Application::Instance>& instance,
3183+ const std::shared_ptr<GDBusConnection>& conn, const std::string& sender,
3184+ const std::shared_ptr<GVariant>& params) {
3185+ reg->impl->manager_->resumeRequest(app, instance, [conn, sender, params](bool response) {
3186+ /* NOTE: We have no clue what thread this is gonna be
3187+ executed on, but since we're just talking to the GDBus
3188+ thread it isn't an issue today. Be careful in changing
3189+ this code. */
3190+ if (response)
3191+ {
3192+ g_dbus_connection_emit_signal(conn.get(), sender.c_str(), /* destination */
3193+ "/", /* path */
3194+ "com.canonical.UbuntuAppLaunch", /* interface */
3195+ "UnityResumeResponse", /* signal */
3196+ params.get(), /* params, the same */
3197+ nullptr); /* error */
3198+ }
3199+ });
3200+ });
3201+
3202+ return true;
3203+ }))
3204+ {
3205+ g_warning("Unable to install manager signals");
3206+ }
3207+ });
3208 }
3209
3210-void
3211-Registry::Impl::clearManager ()
3212+/** Clear the manager pointer */
3213+void Registry::Impl::clearManager()
3214 {
3215- _manager = nullptr;
3216+ g_debug("Clearing the manager");
3217+ manager_.reset();
3218 }
3219-#endif
3220
3221 /** App start watching, if we're registered for the signal we
3222 can't wait on it. We are making this static right now because
3223@@ -576,5 +510,301 @@
3224 return watchingAppStarting_;
3225 }
3226
3227+/** Regex to parse the JOB environment variable from Upstart */
3228+std::regex jobenv_regex{"^JOB=(application\\-(?:click|snap|legacy))$"};
3229+/** Regex to parse the INSTANCE environment variable from Upstart */
3230+std::regex instanceenv_regex{"^INSTANCE=(.*?)(?:\\-([0-9]*))?+$"};
3231+
3232+/** Core of most of the events that come from Upstart directly. Includes parsing of the
3233+ Upstart event environment and calling the appropriate signal with the right Application
3234+ object and eventually its instance */
3235+void Registry::Impl::upstartEventEmitted(
3236+ core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>>& signal,
3237+ std::shared_ptr<GVariant> params,
3238+ const std::shared_ptr<Registry>& reg)
3239+{
3240+ std::string jobname;
3241+ std::string sappid;
3242+ std::string instance;
3243+
3244+ gchar* env = nullptr;
3245+ GVariant* envs = g_variant_get_child_value(params.get(), 1);
3246+ GVariantIter iter;
3247+ g_variant_iter_init(&iter, envs);
3248+
3249+ while (g_variant_iter_loop(&iter, "s", &env))
3250+ {
3251+ std::smatch match;
3252+ std::string senv = env;
3253+
3254+ if (std::regex_match(senv, match, jobenv_regex))
3255+ {
3256+ jobname = match[1].str();
3257+ }
3258+ else if (std::regex_match(senv, match, instanceenv_regex))
3259+ {
3260+ sappid = match[1].str();
3261+ instance = match[2].str();
3262+ }
3263+ }
3264+
3265+ g_variant_unref(envs);
3266+
3267+ if (jobname.empty())
3268+ {
3269+ return;
3270+ }
3271+
3272+ g_debug("Upstart Event for job '%s' appid '%s' instance '%s'", jobname.c_str(), sappid.c_str(), instance.c_str());
3273+
3274+ auto appid = AppID::find(reg, sappid);
3275+ auto app = Application::create(appid, reg);
3276+
3277+ // TODO: Figure otu creating instances
3278+
3279+ signal(app, {});
3280+}
3281+
3282+/** Grab the signal object for application startup. If we're not already listing for
3283+ those signals this sets up a listener for them. */
3284+core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>>& Registry::Impl::appStarted(
3285+ const std::shared_ptr<Registry>& reg)
3286+{
3287+ std::call_once(flag_appStarted, [reg]() {
3288+ reg->impl->thread.executeOnThread<bool>([reg]() {
3289+ upstartEventData* data = new upstartEventData{reg};
3290+
3291+ reg->impl->handle_appStarted = g_dbus_connection_signal_subscribe(
3292+ reg->impl->_dbus.get(), /* bus */
3293+ nullptr, /* sender */
3294+ DBUS_INTERFACE_UPSTART, /* interface */
3295+ "EventEmitted", /* signal */
3296+ DBUS_PATH_UPSTART, /* path */
3297+ "started", /* arg0 */
3298+ G_DBUS_SIGNAL_FLAGS_NONE,
3299+ [](GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar*, GVariant* params,
3300+ gpointer user_data) -> void {
3301+ auto data = reinterpret_cast<upstartEventData*>(user_data);
3302+ auto reg = data->weakReg.lock();
3303+ auto sparams = std::shared_ptr<GVariant>(g_variant_ref(params), g_variant_unref);
3304+ reg->impl->upstartEventEmitted(reg->impl->sig_appStarted, sparams, reg);
3305+ }, /* callback */
3306+ data, /* user data */
3307+ [](gpointer user_data) {
3308+ auto data = reinterpret_cast<upstartEventData*>(user_data);
3309+ delete data;
3310+ }); /* user data destroy */
3311+
3312+ return true;
3313+ });
3314+ });
3315+
3316+ return sig_appStarted;
3317+}
3318+
3319+/** Grab the signal object for application stopping. If we're not already listing for
3320+ those signals this sets up a listener for them. */
3321+core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>>& Registry::Impl::appStopped(
3322+ const std::shared_ptr<Registry>& reg)
3323+{
3324+ std::call_once(flag_appStopped, [reg]() {
3325+ reg->impl->thread.executeOnThread<bool>([reg]() {
3326+ upstartEventData* data = new upstartEventData{reg};
3327+
3328+ reg->impl->handle_appStopped = g_dbus_connection_signal_subscribe(
3329+ reg->impl->_dbus.get(), /* bus */
3330+ nullptr, /* sender */
3331+ DBUS_INTERFACE_UPSTART, /* interface */
3332+ "EventEmitted", /* signal */
3333+ DBUS_PATH_UPSTART, /* path */
3334+ "stopped", /* arg0 */
3335+ G_DBUS_SIGNAL_FLAGS_NONE,
3336+ [](GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar*, GVariant* params,
3337+ gpointer user_data) -> void {
3338+ auto data = reinterpret_cast<upstartEventData*>(user_data);
3339+ auto reg = data->weakReg.lock();
3340+ auto sparams = std::shared_ptr<GVariant>(g_variant_ref(params), g_variant_unref);
3341+ reg->impl->upstartEventEmitted(reg->impl->sig_appStopped, sparams, reg);
3342+ }, /* callback */
3343+ data, /* user data */
3344+ [](gpointer user_data) {
3345+ auto data = reinterpret_cast<upstartEventData*>(user_data);
3346+ delete data;
3347+ }); /* user data destroy */
3348+
3349+ return true;
3350+ });
3351+ });
3352+
3353+ return sig_appStopped;
3354+}
3355+
3356+/** Grab the signal object for application failing. If we're not already listing for
3357+ those signals this sets up a listener for them. */
3358+core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, Registry::FailureType>&
3359+ Registry::Impl::appFailed(const std::shared_ptr<Registry>& reg)
3360+{
3361+ std::call_once(flag_appFailed, [reg]() {
3362+ reg->impl->thread.executeOnThread<bool>([reg]() {
3363+ upstartEventData* data = new upstartEventData{reg};
3364+
3365+ reg->impl->handle_appFailed = g_dbus_connection_signal_subscribe(
3366+ reg->impl->_dbus.get(), /* bus */
3367+ nullptr, /* sender */
3368+ "com.canonical.UbuntuAppLaunch", /* interface */
3369+ "ApplicationFailed", /* signal */
3370+ "/", /* path */
3371+ nullptr, /* arg0 */
3372+ G_DBUS_SIGNAL_FLAGS_NONE,
3373+ [](GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar*, GVariant* params,
3374+ gpointer user_data) -> void {
3375+ auto data = reinterpret_cast<upstartEventData*>(user_data);
3376+ auto reg = data->weakReg.lock();
3377+
3378+ const gchar* sappid = NULL;
3379+ const gchar* typestr = NULL;
3380+
3381+ Registry::FailureType type = Registry::FailureType::CRASH;
3382+ g_variant_get(params, "(&s&s)", &sappid, &typestr);
3383+
3384+ if (g_strcmp0("crash", typestr) == 0)
3385+ {
3386+ type = Registry::FailureType::CRASH;
3387+ }
3388+ else if (g_strcmp0("start-failure", typestr) == 0)
3389+ {
3390+ type = Registry::FailureType::START_FAILURE;
3391+ }
3392+ else
3393+ {
3394+ g_warning("Application failure type '%s' unknown, reporting as a crash", typestr);
3395+ }
3396+
3397+ auto appid = AppID::find(reg, sappid);
3398+ auto app = Application::create(appid, reg);
3399+
3400+ /* TODO: Instance issues */
3401+
3402+ reg->impl->sig_appFailed(app, {}, type);
3403+ }, /* callback */
3404+ data, /* user data */
3405+ [](gpointer user_data) {
3406+ auto data = reinterpret_cast<upstartEventData*>(user_data);
3407+ delete data;
3408+ }); /* user data destroy */
3409+
3410+ return true;
3411+ });
3412+ });
3413+
3414+ return sig_appFailed;
3415+}
3416+
3417+/** Core handler for pause and resume events. Includes turning the GVariant
3418+ pid list into a std::vector and getting the application object. */
3419+void Registry::Impl::pauseEventEmitted(
3420+ core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, std::vector<pid_t>&>& signal,
3421+ const std::shared_ptr<GVariant>& params,
3422+ const std::shared_ptr<Registry>& reg)
3423+{
3424+ std::vector<pid_t> pids;
3425+ GVariant* vappid = g_variant_get_child_value(params.get(), 0);
3426+ GVariant* vpids = g_variant_get_child_value(params.get(), 1);
3427+ guint64 pid;
3428+ GVariantIter thispid;
3429+ g_variant_iter_init(&thispid, vpids);
3430+
3431+ while (g_variant_iter_loop(&thispid, "t", &pid))
3432+ {
3433+ pids.emplace_back(pid);
3434+ }
3435+
3436+ auto cappid = g_variant_get_string(vappid, NULL);
3437+ auto appid = ubuntu::app_launch::AppID::find(reg, cappid);
3438+ auto app = Application::create(appid, reg);
3439+
3440+ /* TODO: Instance */
3441+ signal(app, {}, pids);
3442+
3443+ g_variant_unref(vappid);
3444+ g_variant_unref(vpids);
3445+
3446+ return;
3447+}
3448+
3449+/** Grab the signal object for application paused. If we're not already listing for
3450+ those signals this sets up a listener for them. */
3451+core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, std::vector<pid_t>&>&
3452+ Registry::Impl::appPaused(const std::shared_ptr<Registry>& reg)
3453+{
3454+ std::call_once(flag_appPaused, [&]() {
3455+ reg->impl->thread.executeOnThread<bool>([reg]() {
3456+ upstartEventData* data = new upstartEventData{reg};
3457+
3458+ reg->impl->handle_appPaused = g_dbus_connection_signal_subscribe(
3459+ reg->impl->_dbus.get(), /* bus */
3460+ nullptr, /* sender */
3461+ "com.canonical.UbuntuAppLaunch", /* interface */
3462+ "ApplicationPaused", /* signal */
3463+ "/", /* path */
3464+ nullptr, /* arg0 */
3465+ G_DBUS_SIGNAL_FLAGS_NONE,
3466+ [](GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar*, GVariant* params,
3467+ gpointer user_data) -> void {
3468+ auto data = reinterpret_cast<upstartEventData*>(user_data);
3469+ auto reg = data->weakReg.lock();
3470+ auto sparams = std::shared_ptr<GVariant>(g_variant_ref(params), g_variant_unref);
3471+ reg->impl->pauseEventEmitted(reg->impl->sig_appPaused, sparams, reg);
3472+ }, /* callback */
3473+ data, /* user data */
3474+ [](gpointer user_data) {
3475+ auto data = reinterpret_cast<upstartEventData*>(user_data);
3476+ delete data;
3477+ }); /* user data destroy */
3478+
3479+ return true;
3480+ });
3481+ });
3482+
3483+ return sig_appPaused;
3484+}
3485+
3486+/** Grab the signal object for application resumed. If we're not already listing for
3487+ those signals this sets up a listener for them. */
3488+core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, std::vector<pid_t>&>&
3489+ Registry::Impl::appResumed(const std::shared_ptr<Registry>& reg)
3490+{
3491+ std::call_once(flag_appResumed, [&]() {
3492+ reg->impl->thread.executeOnThread<bool>([reg]() {
3493+ upstartEventData* data = new upstartEventData{reg};
3494+
3495+ reg->impl->handle_appResumed = g_dbus_connection_signal_subscribe(
3496+ reg->impl->_dbus.get(), /* bus */
3497+ nullptr, /* sender */
3498+ "com.canonical.UbuntuAppLaunch", /* interface */
3499+ "ApplicationResumed", /* signal */
3500+ "/", /* path */
3501+ nullptr, /* arg0 */
3502+ G_DBUS_SIGNAL_FLAGS_NONE,
3503+ [](GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar*, GVariant* params,
3504+ gpointer user_data) -> void {
3505+ auto data = reinterpret_cast<upstartEventData*>(user_data);
3506+ auto reg = data->weakReg.lock();
3507+ auto sparams = std::shared_ptr<GVariant>(g_variant_ref(params), g_variant_unref);
3508+ reg->impl->pauseEventEmitted(reg->impl->sig_appResumed, sparams, reg);
3509+ }, /* callback */
3510+ data, /* user data */
3511+ [](gpointer user_data) {
3512+ auto data = reinterpret_cast<upstartEventData*>(user_data);
3513+ delete data;
3514+ }); /* user data destroy */
3515+
3516+ return true;
3517+ });
3518+ });
3519+
3520+ return sig_appResumed;
3521+}
3522+
3523 } // namespace app_launch
3524 } // namespace ubuntu
3525
3526=== modified file 'libubuntu-app-launch/registry-impl.h'
3527--- libubuntu-app-launch/registry-impl.h 2016-09-23 22:30:51 +0000
3528+++ libubuntu-app-launch/registry-impl.h 2016-11-10 21:58:30 +0000
3529@@ -18,6 +18,7 @@
3530 */
3531
3532 #include "glib-thread.h"
3533+#include "jobs-base.h"
3534 #include "registry.h"
3535 #include "snapd-info.h"
3536 #include <click.h>
3537@@ -53,10 +54,8 @@
3538 std::list<AppID::Package> getClickPackages();
3539 std::string getClickDir(const std::string& package);
3540
3541-#if 0
3542- void setManager (Registry::Manager* manager);
3543- void clearManager ();
3544-#endif
3545+ static void setManager(std::shared_ptr<Registry::Manager> manager, std::shared_ptr<Registry> registry);
3546+ void clearManager();
3547
3548 /** Shared context thread for events and background tasks
3549 that UAL subtasks are doing */
3550@@ -69,19 +68,27 @@
3551 snapd::Info snapdInfo;
3552 #endif
3553
3554+ std::shared_ptr<jobs::manager::Base> jobs;
3555+
3556 std::shared_ptr<IconFinder> getIconFinder(std::string basePath);
3557
3558 void zgSendEvent(AppID appid, const std::string& eventtype);
3559
3560- std::vector<pid_t> pidsFromCgroup(const std::string& jobpath);
3561-
3562- /* Upstart Jobs */
3563- std::list<std::string> upstartInstancesForJob(const std::string& job);
3564- std::string upstartJobPath(const std::string& job);
3565-
3566 static std::string printJson(std::shared_ptr<JsonObject> jsonobj);
3567 static std::string printJson(std::shared_ptr<JsonNode> jsonnode);
3568
3569+ /* Signals to discover what is happening to apps */
3570+ core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>>& appStarted(
3571+ const std::shared_ptr<Registry>& reg);
3572+ core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>>& appStopped(
3573+ const std::shared_ptr<Registry>& reg);
3574+ core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, FailureType>& appFailed(
3575+ const std::shared_ptr<Registry>& reg);
3576+ core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, std::vector<pid_t>&>& appPaused(
3577+ const std::shared_ptr<Registry>& reg);
3578+ core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, std::vector<pid_t>&>& appResumed(
3579+ const std::shared_ptr<Registry>& reg);
3580+
3581 /* Signal Hints */
3582 /* NOTE: Static because we don't have registry instances in the C
3583 code right now. We want these to not be static in the future */
3584@@ -89,27 +96,73 @@
3585 static bool isWatchingAppStarting();
3586
3587 private:
3588- Registry* _registry;
3589-#if 0
3590- Registry::Manager* _manager;
3591-#endif
3592-
3593- std::shared_ptr<ClickDB> _clickDB;
3594- std::shared_ptr<ClickUser> _clickUser;
3595+ Registry* _registry; /**< The Registry that we're spawned from */
3596+ std::shared_ptr<Registry::Manager> manager_; /**< Application manager if registered */
3597+
3598+ std::shared_ptr<ClickDB> _clickDB; /**< Shared instance of the Click Database */
3599+ std::shared_ptr<ClickUser> _clickUser; /**< Click database filtered by the current user */
3600+
3601+ /** Signal object for applications started */
3602+ core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>> sig_appStarted;
3603+ /** Signal object for applications stopped */
3604+ core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>> sig_appStopped;
3605+ /** Signal object for applications failed */
3606+ core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, FailureType> sig_appFailed;
3607+ /** Signal object for applications paused */
3608+ core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, std::vector<pid_t>&>
3609+ sig_appPaused;
3610+ /** Signal object for applications resumed */
3611+ core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, std::vector<pid_t>&>
3612+ sig_appResumed;
3613+
3614+ guint handle_appStarted{0}; /**< GDBus signal watcher handle for app started signal */
3615+ guint handle_appStopped{0}; /**< GDBus signal watcher handle for app stopped signal */
3616+ guint handle_appFailed{0}; /**< GDBus signal watcher handle for app failed signal */
3617+ guint handle_appPaused{0}; /**< GDBus signal watcher handle for app paused signal */
3618+ guint handle_appResumed{0}; /**< GDBus signal watcher handle for app resumed signal */
3619+ guint handle_managerSignalFocus{0}; /**< GDBus signal watcher handle for app focused signal */
3620+ guint handle_managerSignalResume{0}; /**< GDBus signal watcher handle for app resumed signal */
3621+ guint handle_managerSignalStarting{0}; /**< GDBus signal watcher handle for app starting signal */
3622+
3623+ std::once_flag flag_appStarted; /**< Variable to track to see if signal handlers are installed for application
3624+ started */
3625+ std::once_flag flag_appStopped; /**< Variable to track to see if signal handlers are installed for application
3626+ stopped */
3627+ std::once_flag
3628+ flag_appFailed; /**< Variable to track to see if signal handlers are installed for application failed */
3629+ std::once_flag
3630+ flag_appPaused; /**< Variable to track to see if signal handlers are installed for application paused */
3631+ std::once_flag flag_appResumed; /**< Variable to track to see if signal handlers are installed for application
3632+ resumed */
3633+ std::once_flag flag_managerSignals; /**< Variable to track to see if signal handlers are installed for the manager
3634+ signals of focused, resumed and starting */
3635+
3636+ void upstartEventEmitted(core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>>& signal,
3637+ std::shared_ptr<GVariant> params,
3638+ const std::shared_ptr<Registry>& reg);
3639+ void pauseEventEmitted(
3640+ core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, std::vector<pid_t>&>& signal,
3641+ const std::shared_ptr<GVariant>& params,
3642+ const std::shared_ptr<Registry>& reg);
3643+ static std::tuple<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>> managerParams(
3644+ const std::shared_ptr<GVariant>& params, const std::shared_ptr<Registry>& reg);
3645+ static guint managerSignalHelper(const std::shared_ptr<Registry>& reg,
3646+ const std::string& signalname,
3647+ std::function<void(const std::shared_ptr<Registry>& reg,
3648+ const std::shared_ptr<Application>& app,
3649+ const std::shared_ptr<Application::Instance>& instance,
3650+ const std::shared_ptr<GDBusConnection>&,
3651+ const std::string&,
3652+ const std::shared_ptr<GVariant>&)> responsefunc);
3653
3654 void initClick();
3655
3656+ /** Shared instance of the Zeitgeist Log */
3657 std::shared_ptr<ZeitgeistLog> zgLog_;
3658
3659- std::shared_ptr<GDBusConnection> cgManager_;
3660-
3661- void initCGManager();
3662-
3663+ /** All of our icon finders based on the path that they're looking
3664+ into */
3665 std::unordered_map<std::string, std::shared_ptr<IconFinder>> _iconFinders;
3666-
3667- /** Getting the Upstart job path is relatively expensive in
3668- that it requires a DBus call. Worth keeping a cache of. */
3669- std::map<std::string, std::string> upstartJobPathCache_;
3670 };
3671
3672 } // namespace app_launch
3673
3674=== modified file 'libubuntu-app-launch/registry.cpp'
3675--- libubuntu-app-launch/registry.cpp 2016-08-26 17:33:34 +0000
3676+++ libubuntu-app-launch/registry.cpp 2016-11-10 21:58:30 +0000
3677@@ -47,61 +47,14 @@
3678 {
3679 }
3680
3681-std::list<std::shared_ptr<Application>> Registry::runningApps(std::shared_ptr<Registry> connection)
3682+std::list<std::shared_ptr<Application>> Registry::runningApps(std::shared_ptr<Registry> registry)
3683 {
3684- std::list<std::string> instances;
3685-
3686- /* Get all the legacy instances */
3687- instances.splice(instances.begin(), connection->impl->upstartInstancesForJob("application-legacy"));
3688- /* Get all the snap instances */
3689- instances.splice(instances.begin(), connection->impl->upstartInstancesForJob("application-snap"));
3690-
3691- /* Remove the instance ID */
3692- std::transform(instances.begin(), instances.end(), instances.begin(), [](std::string &instancename) -> std::string {
3693- static const std::regex instanceregex("^(.*)-[0-9]*$");
3694- std::smatch match;
3695- if (std::regex_match(instancename, match, instanceregex))
3696- {
3697- return match[1].str();
3698- }
3699- else
3700- {
3701- g_warning("Unable to match instance name: %s", instancename.c_str());
3702- return {};
3703- }
3704- });
3705-
3706- /* Deduplicate Set */
3707- std::set<std::string> instanceset;
3708- for (auto instance : instances)
3709- {
3710- if (!instance.empty())
3711- instanceset.insert(instance);
3712- }
3713-
3714- /* Add in the click instances */
3715- for (auto instance : connection->impl->upstartInstancesForJob("application-click"))
3716- {
3717- instanceset.insert(instance);
3718- }
3719-
3720- g_debug("Overall there are %d instances: %s", int(instanceset.size()),
3721- std::accumulate(instanceset.begin(), instanceset.end(), std::string{},
3722- [](const std::string &instr, std::string instance) {
3723- return instr.empty() ? instance : instr + ", " + instance;
3724- })
3725- .c_str());
3726-
3727- /* Convert to Applications */
3728- std::list<std::shared_ptr<Application>> apps;
3729- for (auto instance : instanceset)
3730- {
3731- auto appid = AppID::find(connection, instance);
3732- auto app = Application::create(appid, connection);
3733- apps.push_back(app);
3734- }
3735-
3736- return apps;
3737+ if (!registry->impl->jobs)
3738+ {
3739+ registry->impl->jobs = jobs::manager::Base::determineFactory(registry);
3740+ }
3741+
3742+ return registry->impl->jobs->runningApps();
3743 }
3744
3745 std::list<std::shared_ptr<Application>> Registry::installedApps(std::shared_ptr<Registry> connection)
3746@@ -127,6 +80,16 @@
3747 return list;
3748 }
3749
3750+void Registry::setManager(std::shared_ptr<Manager> manager, std::shared_ptr<Registry> registry)
3751+{
3752+ Registry::Impl::setManager(manager, registry);
3753+}
3754+
3755+void Registry::clearManager()
3756+{
3757+ impl->clearManager();
3758+}
3759+
3760 std::shared_ptr<Registry> defaultRegistry;
3761 std::shared_ptr<Registry> Registry::getDefault()
3762 {
3763@@ -143,5 +106,35 @@
3764 defaultRegistry.reset();
3765 }
3766
3767+core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>>& Registry::appStarted(
3768+ const std::shared_ptr<Registry>& reg)
3769+{
3770+ return reg->impl->appStarted(reg);
3771+}
3772+
3773+core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>>& Registry::appStopped(
3774+ const std::shared_ptr<Registry>& reg)
3775+{
3776+ return reg->impl->appStopped(reg);
3777+}
3778+
3779+core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, Registry::FailureType>&
3780+ Registry::appFailed(const std::shared_ptr<Registry>& reg)
3781+{
3782+ return reg->impl->appFailed(reg);
3783+}
3784+
3785+core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, std::vector<pid_t>&>&
3786+ Registry::appPaused(const std::shared_ptr<Registry>& reg)
3787+{
3788+ return reg->impl->appPaused(reg);
3789+}
3790+
3791+core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, std::vector<pid_t>&>&
3792+ Registry::appResumed(const std::shared_ptr<Registry>& reg)
3793+{
3794+ return reg->impl->appResumed(reg);
3795+}
3796+
3797 } // namespace app_launch
3798 } // namespace ubuntu
3799
3800=== modified file 'libubuntu-app-launch/registry.h'
3801--- libubuntu-app-launch/registry.h 2016-06-09 14:55:34 +0000
3802+++ libubuntu-app-launch/registry.h 2016-11-10 21:58:30 +0000
3803@@ -73,28 +73,134 @@
3804 */
3805 static std::list<std::shared_ptr<Application>> installedApps(std::shared_ptr<Registry> registry = getDefault());
3806
3807-#if 0 /* TODO -- In next MR */
3808 /* Signals to discover what is happening to apps */
3809- core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>> appStarted;
3810- core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>> appStopped;
3811- core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, FailureType> appFailed;
3812- core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>> appPaused;
3813- core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>> appResumed;
3814-
3815- /* The Application Manager, almost always if you're not Unity8, don't
3816- use this API. Testing is a special case. */
3817+ /** Get the signal object that is signaled when an application has been
3818+ started.
3819+
3820+ \note This singal handler is activated on the UAL thread, if you want
3821+ to execute on a different thread you'll need to move the work.
3822+
3823+ \param reg Registry to get the handler from
3824+ */
3825+ static core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>>& appStarted(
3826+ const std::shared_ptr<Registry>& reg = getDefault());
3827+
3828+ /** Get the signal object that is signaled when an application has stopped.
3829+
3830+ \note This singal handler is activated on the UAL thread, if you want
3831+ to execute on a different thread you'll need to move the work.
3832+
3833+ \param reg Registry to get the handler from
3834+ */
3835+ static core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>>& appStopped(
3836+ const std::shared_ptr<Registry>& reg = getDefault());
3837+
3838+ /** Get the signal object that is signaled when an application has failed.
3839+
3840+ \note This singal handler is activated on the UAL thread, if you want
3841+ to execute on a different thread you'll need to move the work.
3842+
3843+ \param reg Registry to get the handler from
3844+ */
3845+ static core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, FailureType>& appFailed(
3846+ const std::shared_ptr<Registry>& reg = getDefault());
3847+
3848+ /** Get the signal object that is signaled when an application has been
3849+ paused.
3850+
3851+ \note This singal handler is activated on the UAL thread, if you want
3852+ to execute on a different thread you'll need to move the work.
3853+
3854+ \param reg Registry to get the handler from
3855+ */
3856+ static core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, std::vector<pid_t>&>&
3857+ appPaused(const std::shared_ptr<Registry>& reg = getDefault());
3858+
3859+ /** Get the signal object that is signaled when an application has been
3860+ resumed.
3861+
3862+ \note This singal handler is activated on the UAL thread, if you want
3863+ to execute on a different thread you'll need to move the work.
3864+
3865+ \param reg Registry to get the handler from
3866+ */
3867+ static core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, std::vector<pid_t>&>&
3868+ appResumed(const std::shared_ptr<Registry>& reg = getDefault());
3869+
3870+ /** The Application Manager, almost always if you're not Unity8, don't
3871+ use this API. Testing is a special case. Subclass this interface and
3872+ implement these functions.
3873+
3874+ Each function here is being passed a function object that takes a boolean
3875+ to reply. This will accept or reject the request. The function object
3876+ can be copied to another thread and executed if needed.
3877+ */
3878 class Manager
3879 {
3880- virtual bool focusRequest (std::shared_ptr<Application> app, std::shared_ptr<Application::Instance> instance) = 0;
3881- virtual bool startingRequest (std::shared_ptr<Application> app, std::shared_ptr<Application::Instance> instance) = 0;
3882+ public:
3883+ /** Application wishes to startup
3884+
3885+ \note This singal handler is activated on the UAL thread, if you want
3886+ to execute on a different thread you'll need to move the work.
3887+
3888+ \param app Application requesting startup
3889+ \param instance Instance of the app, always valid but not useful
3890+ unless mulit-instance app.
3891+ \param reply Function object to reply if it is allowed to start
3892+ */
3893+ virtual void startingRequest(std::shared_ptr<Application> app,
3894+ std::shared_ptr<Application::Instance> instance,
3895+ std::function<void(bool)> reply) = 0;
3896+
3897+ /** Application wishes to have focus. Usually this occurs when
3898+ a URL for the application is activated and the running app is
3899+ requested.
3900+
3901+ \note This singal handler is activated on the UAL thread, if you want
3902+ to execute on a different thread you'll need to move the work.
3903+
3904+ \param app Application requesting focus
3905+ \param instance Instance of the app, always valid but not useful
3906+ unless mulit-instance app.
3907+ \param reply Function object to reply if it is allowed to focus
3908+ */
3909+ virtual void focusRequest(std::shared_ptr<Application> app,
3910+ std::shared_ptr<Application::Instance> instance,
3911+ std::function<void(bool)> reply) = 0;
3912+
3913+ /** Application wishes to resume. Usually this occurs when
3914+ a URL for the application is activated and the running app is
3915+ requested.
3916+
3917+ \note This singal handler is activated on the UAL thread, if you want
3918+ to execute on a different thread you'll need to move the work.
3919+
3920+ \param app Application requesting resume
3921+ \param instance Instance of the app, always valid but not useful
3922+ unless mulit-instance app.
3923+ \param reply Function object to reply if it is allowed to resume
3924+ */
3925+ virtual void resumeRequest(std::shared_ptr<Application> app,
3926+ std::shared_ptr<Application::Instance> instance,
3927+ std::function<void(bool)> reply) = 0;
3928
3929 protected:
3930 Manager() = default;
3931 };
3932
3933- void setManager (Manager* manager);
3934- void clearManager ();
3935-#endif
3936+ /** Set the manager of applications, which gives permissions for them to
3937+ start and gain focus. In almost all cases this should be Unity8 as it
3938+ will be controlling applications.
3939+
3940+ This function will failure if there is already a manager set.
3941+
3942+ \param manager A reference to the Manager object to call
3943+ \param registry Registry to register the manager on
3944+ */
3945+ static void setManager(std::shared_ptr<Manager> manager, std::shared_ptr<Registry> registry);
3946+
3947+ /** Remove the current manager on the registry */
3948+ void clearManager();
3949
3950 /* Helper Lists */
3951 /** Get a list of all the helpers for a given helper type
3952
3953=== modified file 'libubuntu-app-launch/ubuntu-app-launch.cpp'
3954--- libubuntu-app-launch/ubuntu-app-launch.cpp 2016-09-14 21:58:39 +0000
3955+++ libubuntu-app-launch/ubuntu-app-launch.cpp 2016-11-10 21:58:30 +0000
3956@@ -39,6 +39,7 @@
3957 #include "appid.h"
3958 #include "registry.h"
3959 #include "registry-impl.h"
3960+#include <algorithm>
3961
3962 static void free_helper (gpointer value);
3963 int kill (pid_t pid, int signal) noexcept;
3964@@ -271,155 +272,210 @@
3965 gpointer user_data;
3966 };
3967
3968-/* The data we keep for each failed observer */
3969-typedef struct _paused_resumed_observer_t paused_resumed_observer_t;
3970-struct _paused_resumed_observer_t {
3971- GDBusConnection * conn;
3972- guint sighandle;
3973- UbuntuAppLaunchAppPausedResumedObserver func;
3974- gpointer user_data;
3975- const gchar * lttng_signal;
3976-};
3977-
3978-/* The lists of Observers */
3979-static GList * starting_array = NULL;
3980-static GList * started_array = NULL;
3981-static GList * stop_array = NULL;
3982-static GList * focus_array = NULL;
3983-static GList * resume_array = NULL;
3984-static GList * failed_array = NULL;
3985-static GList * paused_array = NULL;
3986-static GList * resumed_array = NULL;
3987-
3988-static void
3989-observer_cb (GDBusConnection * conn, const gchar * sender, const gchar * object, const gchar * interface, const gchar * signal, GVariant * params, gpointer user_data)
3990-{
3991- observer_t * observer = (observer_t *)user_data;
3992-
3993- const gchar * signalname = NULL;
3994- g_variant_get_child(params, 0, "&s", &signalname);
3995-
3996- ual_tracepoint(observer_start, signalname);
3997-
3998- gchar * env = NULL;
3999- GVariant * envs = g_variant_get_child_value(params, 1);
4000- GVariantIter iter;
4001- g_variant_iter_init(&iter, envs);
4002-
4003- gboolean job_found = FALSE;
4004- gboolean job_legacy = FALSE;
4005- gchar * instance = NULL;
4006-
4007- while (g_variant_iter_loop(&iter, "s", &env)) {
4008- if (g_strcmp0(env, "JOB=application-click") == 0) {
4009- job_found = TRUE;
4010- } else if (g_strcmp0(env, "JOB=application-legacy") == 0) {
4011- job_found = TRUE;
4012- job_legacy = TRUE;
4013- } else if (g_strcmp0(env, "JOB=application-snap") == 0) {
4014- job_found = TRUE;
4015- job_legacy = TRUE;
4016- } else if (g_str_has_prefix(env, "INSTANCE=")) {
4017- instance = g_strdup(env + strlen("INSTANCE="));
4018- }
4019- }
4020-
4021- g_variant_unref(envs);
4022-
4023- if (job_legacy && instance != NULL) {
4024- gchar * dash = g_strrstr(instance, "-");
4025- if (dash != NULL) {
4026- dash[0] = '\0';
4027- }
4028- }
4029-
4030- if (job_found && instance != NULL) {
4031- observer->func(instance, observer->user_data);
4032- }
4033-
4034- ual_tracepoint(observer_finish, signalname);
4035-
4036- g_free(instance);
4037-}
4038-
4039-/* Creates the observer structure and registers for the signal with
4040- GDBus so that we can get a callback */
4041-static gboolean
4042-add_app_generic (UbuntuAppLaunchAppObserver observer, gpointer user_data, const gchar * signal, GList ** list)
4043-{
4044- GDBusConnection * conn = gdbus_upstart_ref();
4045-
4046- if (conn == NULL) {
4047- return FALSE;
4048- }
4049-
4050- observer_t * observert = g_new0(observer_t, 1);
4051-
4052- observert->conn = conn;
4053- observert->func = observer;
4054- observert->user_data = user_data;
4055-
4056- *list = g_list_prepend(*list, observert);
4057-
4058- observert->sighandle = g_dbus_connection_signal_subscribe(conn,
4059- NULL, /* sender */
4060- DBUS_INTERFACE_UPSTART, /* interface */
4061- "EventEmitted", /* signal */
4062- DBUS_PATH_UPSTART, /* path */
4063- signal, /* arg0 */
4064- G_DBUS_SIGNAL_FLAGS_NONE,
4065- observer_cb,
4066- observert,
4067- NULL); /* user data destroy */
4068-
4069- return TRUE;
4070-}
4071+/* Function to take a work function and have it execute on a given
4072+ GMainContext */
4073+static void executeOnContext (std::shared_ptr<GMainContext> context, std::function<void()> work)
4074+{
4075+ if (!context) {
4076+ work();
4077+ return;
4078+ }
4079+
4080+ auto heapWork = new std::function<void()>(work);
4081+
4082+ auto source = std::shared_ptr<GSource>(g_idle_source_new(), [](GSource* src) { g_clear_pointer(&src, g_source_unref); });
4083+ g_source_set_callback(source.get(),
4084+ [](gpointer data) {
4085+ auto heapWork = static_cast<std::function<void()>*>(data);
4086+ (*heapWork)();
4087+ return G_SOURCE_REMOVE;
4088+ },
4089+ heapWork,
4090+ [](gpointer data) {
4091+ auto heapWork = static_cast<std::function<void()>*>(data);
4092+ delete heapWork;
4093+ });
4094+
4095+ g_source_attach(source.get(), context.get());
4096+}
4097+
4098+/* Map of all the observers listening for app started */
4099+static std::map<std::pair<UbuntuAppLaunchAppObserver, gpointer>, core::ScopedConnection> appStartedObservers;
4100
4101 gboolean
4102 ubuntu_app_launch_observer_add_app_started (UbuntuAppLaunchAppObserver observer, gpointer user_data)
4103 {
4104- return add_app_generic(observer, user_data, "started", &started_array);
4105-}
4106+ auto context = std::shared_ptr<GMainContext>(g_main_context_ref_thread_default(), [](GMainContext * context) { g_clear_pointer(&context, g_main_context_unref); });
4107+
4108+ appStartedObservers.emplace(std::make_pair(
4109+ std::make_pair(observer, user_data),
4110+ core::ScopedConnection(
4111+ ubuntu::app_launch::Registry::appStarted().connect([context, observer, user_data](std::shared_ptr<ubuntu::app_launch::Application> app, std::shared_ptr<ubuntu::app_launch::Application::Instance> instance) {
4112+ std::string appid = app->appId();
4113+ executeOnContext(context, [appid, observer, user_data]() {
4114+ observer(appid.c_str(), user_data);
4115+ });
4116+ })
4117+ )
4118+ ));
4119+
4120+ return TRUE;
4121+}
4122+
4123+gboolean
4124+ubuntu_app_launch_observer_delete_app_started (UbuntuAppLaunchAppObserver observer, gpointer user_data)
4125+{
4126+ auto iter = appStartedObservers.find(std::make_pair(observer, user_data));
4127+
4128+ if (iter == appStartedObservers.end()) {
4129+ return FALSE;
4130+ }
4131+
4132+ appStartedObservers.erase(iter);
4133+ return TRUE;
4134+}
4135+
4136+/* Map of all the observers listening for app stopped */
4137+static std::map<std::pair<UbuntuAppLaunchAppObserver, gpointer>, core::ScopedConnection> appStoppedObservers;
4138
4139 gboolean
4140 ubuntu_app_launch_observer_add_app_stop (UbuntuAppLaunchAppObserver observer, gpointer user_data)
4141 {
4142- return add_app_generic(observer, user_data, "stopped", &stop_array);
4143+ auto context = std::shared_ptr<GMainContext>(g_main_context_ref_thread_default(), [](GMainContext * context) { g_clear_pointer(&context, g_main_context_unref); });
4144+
4145+ appStoppedObservers.emplace(std::make_pair(
4146+ std::make_pair(observer, user_data),
4147+ core::ScopedConnection(
4148+ ubuntu::app_launch::Registry::appStopped().connect([context, observer, user_data](std::shared_ptr<ubuntu::app_launch::Application> app, std::shared_ptr<ubuntu::app_launch::Application::Instance> instance) {
4149+ std::string appid = app->appId();
4150+ executeOnContext(context, [appid, observer, user_data]() {
4151+ observer(appid.c_str(), user_data);
4152+ });
4153+ })
4154+ )
4155+ ));
4156+
4157+ return TRUE;
4158 }
4159
4160-/* Creates the observer structure and registers for the signal with
4161- GDBus so that we can get a callback */
4162-static gboolean
4163-add_session_generic (UbuntuAppLaunchAppObserver observer, gpointer user_data, const gchar * signal, GList ** list, GDBusSignalCallback session_cb)
4164+gboolean
4165+ubuntu_app_launch_observer_delete_app_stop (UbuntuAppLaunchAppObserver observer, gpointer user_data)
4166 {
4167- GDBusConnection * conn = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
4168+ auto iter = appStoppedObservers.find(std::make_pair(observer, user_data));
4169
4170- if (conn == NULL) {
4171+ if (iter == appStoppedObservers.end()) {
4172 return FALSE;
4173 }
4174
4175- observer_t * observert = g_new0(observer_t, 1);
4176-
4177- observert->conn = conn;
4178- observert->func = observer;
4179- observert->user_data = user_data;
4180-
4181- *list = g_list_prepend(*list, observert);
4182-
4183- observert->sighandle = g_dbus_connection_signal_subscribe(conn,
4184- NULL, /* sender */
4185- "com.canonical.UbuntuAppLaunch", /* interface */
4186- signal, /* signal */
4187- "/", /* path */
4188- NULL, /* arg0 */
4189- G_DBUS_SIGNAL_FLAGS_NONE,
4190- session_cb,
4191- observert,
4192- NULL); /* user data destroy */
4193-
4194+ appStoppedObservers.erase(iter);
4195 return TRUE;
4196 }
4197
4198+class CManager : public ubuntu::app_launch::Registry::Manager
4199+{
4200+public:
4201+ CManager () {
4202+ g_debug("Creating the CManager object");
4203+ }
4204+
4205+ void startingRequest(std::shared_ptr<ubuntu::app_launch::Application> app,
4206+ std::shared_ptr<ubuntu::app_launch::Application::Instance> instance,
4207+ std::function<void(bool)> reply) override {
4208+ std::string sappid = app->appId();
4209+ g_debug("CManager starting: %s", sappid.c_str());
4210+
4211+ for (const auto &data : startingList) {
4212+ executeOnContext(data.context, [data, sappid]() {
4213+ data.observer(sappid.c_str(), data.user_data);
4214+ });
4215+ }
4216+
4217+ reply(true);
4218+ }
4219+
4220+ void focusRequest(std::shared_ptr<ubuntu::app_launch::Application> app,
4221+ std::shared_ptr<ubuntu::app_launch::Application::Instance> instance,
4222+ std::function<void(bool)> reply) override {
4223+ std::string sappid = app->appId();
4224+ g_debug("CManager focus: %s", sappid.c_str());
4225+
4226+ for (const auto &data : focusList) {
4227+ executeOnContext(data.context, [data, sappid]() {
4228+ data.observer(sappid.c_str(), data.user_data);
4229+ });
4230+ }
4231+
4232+ reply(true);
4233+ }
4234+
4235+ void resumeRequest(std::shared_ptr<ubuntu::app_launch::Application> app,
4236+ std::shared_ptr<ubuntu::app_launch::Application::Instance> instance,
4237+ std::function<void(bool)> reply) override {
4238+ std::string sappid = app->appId();
4239+ g_debug("CManager resume: %s", sappid.c_str());
4240+
4241+ for (const auto &data : resumeList) {
4242+ executeOnContext(data.context, [data, sappid]() {
4243+ data.observer(sappid.c_str(), data.user_data);
4244+ });
4245+ }
4246+
4247+ reply(true);
4248+ }
4249+
4250+private:
4251+ struct ObserverData {
4252+ UbuntuAppLaunchAppObserver observer;
4253+ gpointer user_data;
4254+ std::shared_ptr<GMainContext> context;
4255+
4256+ ObserverData(UbuntuAppLaunchAppObserver obs, gpointer ud)
4257+ : observer(obs)
4258+ , user_data(ud) {
4259+ context = std::shared_ptr<GMainContext>(g_main_context_ref_thread_default(), [](GMainContext * context) { g_clear_pointer(&context, g_main_context_unref); });
4260+ }
4261+ };
4262+
4263+ std::list<ObserverData> focusList;
4264+ std::list<ObserverData> resumeList;
4265+ std::list<ObserverData> startingList;
4266+
4267+ bool removeList (std::list<ObserverData> &list, UbuntuAppLaunchAppObserver observer, gpointer user_data) {
4268+ auto iter = std::find_if(list.begin(), list.end(), [observer, user_data](const ObserverData &data) {
4269+ return data.observer == observer && data.user_data == user_data;
4270+ });
4271+
4272+ if (iter == list.end()) {
4273+ return false;
4274+ }
4275+
4276+ list.erase(iter);
4277+ return true;
4278+ }
4279+
4280+public:
4281+ void addFocus (UbuntuAppLaunchAppObserver observer, gpointer user_data) {
4282+ focusList.emplace_back(ObserverData(observer, user_data));
4283+ }
4284+ void addResume (UbuntuAppLaunchAppObserver observer, gpointer user_data) {
4285+ resumeList.emplace_back(ObserverData(observer, user_data));
4286+ }
4287+ void addStarting (UbuntuAppLaunchAppObserver observer, gpointer user_data) {
4288+ startingList.emplace_back(ObserverData(observer, user_data));
4289+ }
4290+ bool deleteFocus (UbuntuAppLaunchAppObserver observer, gpointer user_data) {
4291+ return removeList(focusList, observer, user_data);
4292+ }
4293+ bool deleteResume (UbuntuAppLaunchAppObserver observer, gpointer user_data) {
4294+ return removeList(resumeList, observer, user_data);
4295+ }
4296+ bool deleteStarting (UbuntuAppLaunchAppObserver observer, gpointer user_data) {
4297+ return removeList(startingList, observer, user_data);
4298+ }
4299+};
4300+
4301+static std::weak_ptr<CManager> cmanager;
4302+
4303 /* Generic handler for a bunch of our signals */
4304 static inline void
4305 generic_signal_cb (GDBusConnection * conn, const gchar * sender, const gchar * object, const gchar * interface, const gchar * signal, GVariant * params, gpointer user_data)
4306@@ -433,328 +489,158 @@
4307 }
4308 }
4309
4310-/* Handle the focus signal when it occurs, call the observer */
4311-static void
4312-focus_signal_cb (GDBusConnection * conn, const gchar * sender, const gchar * object, const gchar * interface, const gchar * signal, GVariant * params, gpointer user_data)
4313-{
4314- ual_tracepoint(observer_start, "focus");
4315-
4316- generic_signal_cb(conn, sender, object, interface, signal, params, user_data);
4317-
4318- ual_tracepoint(observer_finish, "focus");
4319-}
4320-
4321 gboolean
4322 ubuntu_app_launch_observer_add_app_focus (UbuntuAppLaunchAppObserver observer, gpointer user_data)
4323 {
4324- return add_session_generic(observer, user_data, "UnityFocusRequest", &focus_array, focus_signal_cb);
4325+ auto manager = cmanager.lock();
4326+
4327+ if (!manager) {
4328+ manager = std::make_shared<CManager>();
4329+ ubuntu::app_launch::Registry::setManager(manager, ubuntu::app_launch::Registry::getDefault());
4330+ cmanager = manager;
4331+ }
4332+
4333+ manager->addFocus(observer, user_data);
4334+ return TRUE;
4335 }
4336
4337-/* Handle the resume signal when it occurs, call the observer, then send a signal back when we're done */
4338-static void
4339-resume_signal_cb (GDBusConnection * conn, const gchar * sender, const gchar * object, const gchar * interface, const gchar * signal, GVariant * params, gpointer user_data)
4340+gboolean
4341+ubuntu_app_launch_observer_delete_app_focus (UbuntuAppLaunchAppObserver observer, gpointer user_data)
4342 {
4343- ual_tracepoint(observer_start, "resume");
4344-
4345- generic_signal_cb(conn, sender, object, interface, signal, params, user_data);
4346-
4347- GError * error = NULL;
4348- g_dbus_connection_emit_signal(conn,
4349- sender, /* destination */
4350- "/", /* path */
4351- "com.canonical.UbuntuAppLaunch", /* interface */
4352- "UnityResumeResponse", /* signal */
4353- params, /* params, the same */
4354- &error);
4355-
4356- if (error != NULL) {
4357- g_warning("Unable to emit response signal: %s", error->message);
4358- g_error_free(error);
4359+ auto manager = cmanager.lock();
4360+
4361+ if (!manager) {
4362+ return FALSE;
4363 }
4364
4365- ual_tracepoint(observer_finish, "resume");
4366+ return manager->deleteFocus(observer, user_data) ? TRUE : FALSE;
4367 }
4368
4369 gboolean
4370 ubuntu_app_launch_observer_add_app_resume (UbuntuAppLaunchAppObserver observer, gpointer user_data)
4371 {
4372- return add_session_generic(observer, user_data, "UnityResumeRequest", &resume_array, resume_signal_cb);
4373+ auto manager = cmanager.lock();
4374+
4375+ if (!manager) {
4376+ manager = std::make_shared<CManager>();
4377+ ubuntu::app_launch::Registry::setManager(manager, ubuntu::app_launch::Registry::getDefault());
4378+ cmanager = manager;
4379+ }
4380+
4381+ manager->addResume(observer, user_data);
4382+ return TRUE;
4383 }
4384
4385-/* Handle the starting signal when it occurs, call the observer, then send a signal back when we're done */
4386-static void
4387-starting_signal_cb (GDBusConnection * conn, const gchar * sender, const gchar * object, const gchar * interface, const gchar * signal, GVariant * params, gpointer user_data)
4388+gboolean
4389+ubuntu_app_launch_observer_delete_app_resume (UbuntuAppLaunchAppObserver observer, gpointer user_data)
4390 {
4391- ual_tracepoint(observer_start, "starting");
4392-
4393- generic_signal_cb(conn, sender, object, interface, signal, params, user_data);
4394-
4395- GError * error = NULL;
4396- g_dbus_connection_emit_signal(conn,
4397- sender, /* destination */
4398- "/", /* path */
4399- "com.canonical.UbuntuAppLaunch", /* interface */
4400- "UnityStartingSignal", /* signal */
4401- params, /* params, the same */
4402- &error);
4403-
4404- if (error != NULL) {
4405- g_warning("Unable to emit response signal: %s", error->message);
4406- g_error_free(error);
4407+ auto manager = cmanager.lock();
4408+
4409+ if (!manager) {
4410+ return FALSE;
4411 }
4412
4413- ual_tracepoint(observer_finish, "starting");
4414+ return manager->deleteResume(observer, user_data) ? TRUE : FALSE;
4415 }
4416
4417 gboolean
4418 ubuntu_app_launch_observer_add_app_starting (UbuntuAppLaunchAppObserver observer, gpointer user_data)
4419 {
4420+ auto manager = cmanager.lock();
4421+
4422+ if (!manager) {
4423+ manager = std::make_shared<CManager>();
4424+ ubuntu::app_launch::Registry::setManager(manager, ubuntu::app_launch::Registry::getDefault());
4425+ cmanager = manager;
4426+ }
4427+
4428 ubuntu::app_launch::Registry::Impl::watchingAppStarting(true);
4429- return add_session_generic(observer, user_data, "UnityStartingBroadcast", &starting_array, starting_signal_cb);
4430-}
4431-
4432-/* Handle the failed signal when it occurs, call the observer */
4433-static void
4434-failed_signal_cb (GDBusConnection * conn, const gchar * sender, const gchar * object, const gchar * interface, const gchar * signal, GVariant * params, gpointer user_data)
4435-{
4436- failed_observer_t * observer = (failed_observer_t *)user_data;
4437- const gchar * appid = NULL;
4438- const gchar * typestr = NULL;
4439-
4440- ual_tracepoint(observer_start, "failed");
4441-
4442- if (observer->func != NULL) {
4443- UbuntuAppLaunchAppFailed type = UBUNTU_APP_LAUNCH_APP_FAILED_CRASH;
4444- g_variant_get(params, "(&s&s)", &appid, &typestr);
4445-
4446- if (g_strcmp0("crash", typestr) == 0) {
4447- type = UBUNTU_APP_LAUNCH_APP_FAILED_CRASH;
4448- } else if (g_strcmp0("start-failure", typestr) == 0) {
4449- type = UBUNTU_APP_LAUNCH_APP_FAILED_START_FAILURE;
4450- } else {
4451- g_warning("Application failure type '%s' unknown, reporting as a crash", typestr);
4452- }
4453-
4454- observer->func(appid, type, observer->user_data);
4455- }
4456-
4457- ual_tracepoint(observer_finish, "failed");
4458-}
4459-
4460-gboolean
4461-ubuntu_app_launch_observer_add_app_failed (UbuntuAppLaunchAppFailedObserver observer, gpointer user_data)
4462-{
4463- GDBusConnection * conn = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
4464-
4465- if (conn == NULL) {
4466- return FALSE;
4467- }
4468-
4469- failed_observer_t * observert = g_new0(failed_observer_t, 1);
4470-
4471- observert->conn = conn;
4472- observert->func = observer;
4473- observert->user_data = user_data;
4474-
4475- failed_array = g_list_prepend(failed_array, observert);
4476-
4477- observert->sighandle = g_dbus_connection_signal_subscribe(conn,
4478- NULL, /* sender */
4479- "com.canonical.UbuntuAppLaunch", /* interface */
4480- "ApplicationFailed", /* signal */
4481- "/", /* path */
4482- NULL, /* arg0 */
4483- G_DBUS_SIGNAL_FLAGS_NONE,
4484- failed_signal_cb,
4485- observert,
4486- NULL); /* user data destroy */
4487-
4488- return TRUE;
4489-}
4490-
4491-/* Handle the paused signal when it occurs, call the observer */
4492-static void
4493-paused_signal_cb (GDBusConnection * conn, const gchar * sender, const gchar * object, const gchar * interface, const gchar * signal, GVariant * params, gpointer user_data)
4494-{
4495- paused_resumed_observer_t * observer = (paused_resumed_observer_t *)user_data;
4496-
4497- ual_tracepoint(observer_start, observer->lttng_signal);
4498-
4499- if (observer->func != NULL) {
4500- GArray * pidarray = g_array_new(TRUE, TRUE, sizeof(GPid));
4501- GVariant * appid = g_variant_get_child_value(params, 0);
4502- GVariant * pids = g_variant_get_child_value(params, 1);
4503- guint64 pid;
4504- GVariantIter thispid;
4505- g_variant_iter_init(&thispid, pids);
4506-
4507- while (g_variant_iter_loop(&thispid, "t", &pid)) {
4508- GPid gpid = (GPid)pid; /* Should be a no-op for most architectures, but just in case */
4509- g_array_append_val(pidarray, gpid);
4510- }
4511-
4512- observer->func(g_variant_get_string(appid, NULL), (GPid *)pidarray->data, observer->user_data);
4513-
4514- g_array_free(pidarray, TRUE);
4515- g_variant_unref(appid);
4516- g_variant_unref(pids);
4517- }
4518-
4519- ual_tracepoint(observer_finish, observer->lttng_signal);
4520-}
4521-
4522-static gboolean
4523-paused_resumed_generic (UbuntuAppLaunchAppPausedResumedObserver observer, gpointer user_data, GList ** queue, const gchar * signal_name, const gchar * lttng_signal)
4524-{
4525- GDBusConnection * conn = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
4526-
4527- if (conn == NULL) {
4528- return FALSE;
4529- }
4530-
4531- paused_resumed_observer_t * observert = g_new0(paused_resumed_observer_t, 1);
4532-
4533- observert->conn = conn;
4534- observert->func = observer;
4535- observert->user_data = user_data;
4536- observert->lttng_signal = lttng_signal;
4537-
4538- *queue = g_list_prepend(*queue, observert);
4539-
4540- observert->sighandle = g_dbus_connection_signal_subscribe(conn,
4541- NULL, /* sender */
4542- "com.canonical.UbuntuAppLaunch", /* interface */
4543- signal_name, /* signal */
4544- "/", /* path */
4545- NULL, /* arg0 */
4546- G_DBUS_SIGNAL_FLAGS_NONE,
4547- paused_signal_cb,
4548- observert,
4549- NULL); /* user data destroy */
4550-
4551- return TRUE;
4552-}
4553-
4554-gboolean
4555-ubuntu_app_launch_observer_add_app_paused (UbuntuAppLaunchAppPausedResumedObserver observer, gpointer user_data)
4556-{
4557- return paused_resumed_generic(observer, user_data, &paused_array, "ApplicationPaused", "paused");
4558-}
4559-
4560-gboolean
4561-ubuntu_app_launch_observer_add_app_resumed (UbuntuAppLaunchAppPausedResumedObserver observer, gpointer user_data)
4562-{
4563- return paused_resumed_generic(observer, user_data, &resumed_array, "ApplicationResumed", "resumed");
4564-}
4565-
4566-static gboolean
4567-delete_app_generic (UbuntuAppLaunchAppObserver observer, gpointer user_data, GList ** list)
4568-{
4569- observer_t * observert = NULL;
4570- GList * look;
4571-
4572- for (look = *list; look != NULL; look = g_list_next(look)) {
4573- observert = (observer_t *)look->data;
4574-
4575- if (observert->func == observer && observert->user_data == user_data) {
4576- break;
4577- }
4578- }
4579-
4580- if (look == NULL) {
4581- return FALSE;
4582- }
4583-
4584- g_dbus_connection_signal_unsubscribe(observert->conn, observert->sighandle);
4585- g_object_unref(observert->conn);
4586-
4587- g_free(observert);
4588- *list = g_list_delete_link(*list, look);
4589-
4590- return TRUE;
4591-}
4592-
4593-gboolean
4594-ubuntu_app_launch_observer_delete_app_started (UbuntuAppLaunchAppObserver observer, gpointer user_data)
4595-{
4596- return delete_app_generic(observer, user_data, &started_array);
4597-}
4598-
4599-gboolean
4600-ubuntu_app_launch_observer_delete_app_stop (UbuntuAppLaunchAppObserver observer, gpointer user_data)
4601-{
4602- return delete_app_generic(observer, user_data, &stop_array);
4603-}
4604-
4605-gboolean
4606-ubuntu_app_launch_observer_delete_app_resume (UbuntuAppLaunchAppObserver observer, gpointer user_data)
4607-{
4608- return delete_app_generic(observer, user_data, &resume_array);
4609-}
4610-
4611-gboolean
4612-ubuntu_app_launch_observer_delete_app_focus (UbuntuAppLaunchAppObserver observer, gpointer user_data)
4613-{
4614- return delete_app_generic(observer, user_data, &focus_array);
4615+ manager->addStarting(observer, user_data);
4616+ return TRUE;
4617 }
4618
4619 gboolean
4620 ubuntu_app_launch_observer_delete_app_starting (UbuntuAppLaunchAppObserver observer, gpointer user_data)
4621 {
4622+ auto manager = cmanager.lock();
4623+
4624+ if (!manager) {
4625+ return FALSE;
4626+ }
4627+
4628 ubuntu::app_launch::Registry::Impl::watchingAppStarting(false);
4629- return delete_app_generic(observer, user_data, &starting_array);
4630+ return manager->deleteStarting(observer, user_data) ? TRUE : FALSE;
4631+}
4632+
4633+/* Map of all the observers listening for app stopped */
4634+static std::map<std::pair<UbuntuAppLaunchAppFailedObserver, gpointer>, core::ScopedConnection> appFailedObservers;
4635+
4636+gboolean
4637+ubuntu_app_launch_observer_add_app_failed (UbuntuAppLaunchAppFailedObserver observer, gpointer user_data)
4638+{
4639+ auto context = std::shared_ptr<GMainContext>(g_main_context_ref_thread_default(), [](GMainContext * context) { g_clear_pointer(&context, g_main_context_unref); });
4640+
4641+ appFailedObservers.emplace(std::make_pair(
4642+ std::make_pair(observer, user_data),
4643+ core::ScopedConnection(
4644+ ubuntu::app_launch::Registry::appFailed().connect([context, observer, user_data](std::shared_ptr<ubuntu::app_launch::Application> app, std::shared_ptr<ubuntu::app_launch::Application::Instance> instance, ubuntu::app_launch::Registry::FailureType type) {
4645+ std::string appid = app->appId();
4646+ executeOnContext(context, [appid, type, observer, user_data]() {
4647+ UbuntuAppLaunchAppFailed ctype;
4648+
4649+ switch (type) {
4650+ case ubuntu::app_launch::Registry::FailureType::CRASH:
4651+ ctype = UBUNTU_APP_LAUNCH_APP_FAILED_CRASH;
4652+ break;
4653+ case ubuntu::app_launch::Registry::FailureType::START_FAILURE:
4654+ ctype = UBUNTU_APP_LAUNCH_APP_FAILED_START_FAILURE;
4655+ break;
4656+ }
4657+
4658+ observer(appid.c_str(), ctype, user_data);
4659+ });
4660+ })
4661+ )
4662+ ));
4663+
4664+ return TRUE;
4665 }
4666
4667 gboolean
4668 ubuntu_app_launch_observer_delete_app_failed (UbuntuAppLaunchAppFailedObserver observer, gpointer user_data)
4669 {
4670- failed_observer_t * observert = NULL;
4671- GList * look;
4672-
4673- for (look = failed_array; look != NULL; look = g_list_next(look)) {
4674- observert = (failed_observer_t *)look->data;
4675-
4676- if (observert->func == observer && observert->user_data == user_data) {
4677- break;
4678- }
4679- }
4680-
4681- if (look == NULL) {
4682+ auto iter = appFailedObservers.find(std::make_pair(observer, user_data));
4683+
4684+ if (iter == appFailedObservers.end()) {
4685 return FALSE;
4686 }
4687
4688- g_dbus_connection_signal_unsubscribe(observert->conn, observert->sighandle);
4689- g_object_unref(observert->conn);
4690-
4691- g_free(observert);
4692- failed_array = g_list_delete_link(failed_array, look);
4693-
4694+ appFailedObservers.erase(iter);
4695 return TRUE;
4696 }
4697
4698-static gboolean
4699-paused_resumed_delete (UbuntuAppLaunchAppPausedResumedObserver observer, gpointer user_data, GList ** list)
4700+static std::map<std::pair<UbuntuAppLaunchAppPausedResumedObserver, gpointer>, core::ScopedConnection> appPausedObservers;
4701+
4702+gboolean
4703+ubuntu_app_launch_observer_add_app_paused (UbuntuAppLaunchAppPausedResumedObserver observer, gpointer user_data)
4704 {
4705- paused_resumed_observer_t * observert = NULL;
4706- GList * look;
4707-
4708- for (look = *list; look != NULL; look = g_list_next(look)) {
4709- observert = (paused_resumed_observer_t *)look->data;
4710-
4711- if (observert->func == observer && observert->user_data == user_data) {
4712- break;
4713- }
4714- }
4715-
4716- if (look == NULL) {
4717- return FALSE;
4718- }
4719-
4720- g_dbus_connection_signal_unsubscribe(observert->conn, observert->sighandle);
4721- g_object_unref(observert->conn);
4722-
4723- g_free(observert);
4724- *list = g_list_delete_link(*list, look);
4725+ auto context = std::shared_ptr<GMainContext>(g_main_context_ref_thread_default(), [](GMainContext * context) { g_clear_pointer(&context, g_main_context_unref); });
4726+
4727+ appPausedObservers.emplace(std::make_pair(
4728+ std::make_pair(observer, user_data),
4729+ core::ScopedConnection(
4730+ ubuntu::app_launch::Registry::appPaused().connect([context, observer, user_data](std::shared_ptr<ubuntu::app_launch::Application> app, std::shared_ptr<ubuntu::app_launch::Application::Instance> instance, std::vector<pid_t> &pids) {
4731+ std::vector<pid_t> lpids = pids;
4732+ lpids.emplace_back(0);
4733+
4734+ std::string appid = app->appId();
4735+
4736+ executeOnContext(context, [appid, observer, user_data, lpids]() {
4737+ observer(appid.c_str(), (int *)(lpids.data()), user_data);
4738+ });
4739+ })
4740+ )
4741+ ));
4742
4743 return TRUE;
4744 }
4745@@ -762,13 +648,53 @@
4746 gboolean
4747 ubuntu_app_launch_observer_delete_app_paused (UbuntuAppLaunchAppPausedResumedObserver observer, gpointer user_data)
4748 {
4749- return paused_resumed_delete(observer, user_data, &paused_array);
4750+ auto iter = appPausedObservers.find(std::make_pair(observer, user_data));
4751+
4752+ if (iter == appPausedObservers.end()) {
4753+ return FALSE;
4754+ }
4755+
4756+ appPausedObservers.erase(iter);
4757+ return TRUE;
4758+}
4759+
4760+static std::map<std::pair<UbuntuAppLaunchAppPausedResumedObserver, gpointer>, core::ScopedConnection> appResumedObservers;
4761+
4762+gboolean
4763+ubuntu_app_launch_observer_add_app_resumed (UbuntuAppLaunchAppPausedResumedObserver observer, gpointer user_data)
4764+{
4765+ auto context = std::shared_ptr<GMainContext>(g_main_context_ref_thread_default(), [](GMainContext * context) { g_clear_pointer(&context, g_main_context_unref); });
4766+
4767+ appResumedObservers.emplace(std::make_pair(
4768+ std::make_pair(observer, user_data),
4769+ core::ScopedConnection(
4770+ ubuntu::app_launch::Registry::appResumed().connect([context, observer, user_data](std::shared_ptr<ubuntu::app_launch::Application> app, std::shared_ptr<ubuntu::app_launch::Application::Instance> instance, std::vector<pid_t>& pids) {
4771+ std::vector<pid_t> lpids = pids;
4772+ lpids.emplace_back(0);
4773+
4774+ std::string appid = app->appId();
4775+
4776+ executeOnContext(context, [appid, observer, user_data, lpids]() {
4777+ observer(appid.c_str(), (int *)(lpids.data()), user_data);
4778+ });
4779+ })
4780+ )
4781+ ));
4782+
4783+ return TRUE;
4784 }
4785
4786 gboolean
4787 ubuntu_app_launch_observer_delete_app_resumed (UbuntuAppLaunchAppPausedResumedObserver observer, gpointer user_data)
4788 {
4789- return paused_resumed_delete(observer, user_data, &resumed_array);
4790+ auto iter = appResumedObservers.find(std::make_pair(observer, user_data));
4791+
4792+ if (iter == appResumedObservers.end()) {
4793+ return FALSE;
4794+ }
4795+
4796+ appResumedObservers.erase(iter);
4797+ return TRUE;
4798 }
4799
4800 typedef void (*per_instance_func_t) (GDBusConnection * con, GVariant * prop_dict, gpointer user_data);
4801
4802=== modified file 'tests/CMakeLists.txt'
4803--- tests/CMakeLists.txt 2016-09-14 16:43:36 +0000
4804+++ tests/CMakeLists.txt 2016-11-10 21:58:30 +0000
4805@@ -157,6 +157,7 @@
4806
4807 add_custom_target(format-tests
4808 COMMAND clang-format -i -style=file
4809+ failure-test.cc
4810 application-info-desktop.cpp
4811 libual-cpp-test.cc
4812 list-apps.cpp
4813
4814=== modified file 'tests/failure-test.cc'
4815--- tests/failure-test.cc 2016-08-25 18:13:44 +0000
4816+++ tests/failure-test.cc 2016-11-10 21:58:30 +0000
4817@@ -17,120 +17,147 @@
4818 * Ted Gould <ted.gould@canonical.com>
4819 */
4820
4821+#include "eventually-fixture.h"
4822+#include "registry.h"
4823+#include <gio/gio.h>
4824+#include <glib/gstdio.h>
4825 #include <gtest/gtest.h>
4826-#include <glib/gstdio.h>
4827-#include <gio/gio.h>
4828-#include <ubuntu-app-launch.h>
4829-#include "eventually-fixture.h"
4830
4831 class FailureTest : public EventuallyFixture
4832 {
4833- private:
4834- GTestDBus * testbus = NULL;
4835-
4836- protected:
4837- virtual void SetUp() {
4838- testbus = g_test_dbus_new(G_TEST_DBUS_NONE);
4839- g_test_dbus_up(testbus);
4840- }
4841-
4842- virtual void TearDown() {
4843- g_test_dbus_down(testbus);
4844- g_clear_object(&testbus);
4845- }
4846+private:
4847+ GTestDBus* testbus = NULL;
4848+
4849+protected:
4850+ std::shared_ptr<ubuntu::app_launch::Registry> registry;
4851+
4852+ virtual void SetUp()
4853+ {
4854+ /* Click DB test mode */
4855+ g_setenv("TEST_CLICK_DB", "click-db-dir", TRUE);
4856+ g_setenv("TEST_CLICK_USER", "test-user", TRUE);
4857+
4858+ gchar* linkfarmpath = g_build_filename(CMAKE_SOURCE_DIR, "link-farm", NULL);
4859+ g_setenv("UBUNTU_APP_LAUNCH_LINK_FARM", linkfarmpath, TRUE);
4860+ g_free(linkfarmpath);
4861+
4862+ g_setenv("XDG_DATA_DIRS", CMAKE_SOURCE_DIR, TRUE);
4863+ g_setenv("XDG_CACHE_HOME", CMAKE_SOURCE_DIR "/libertine-data", TRUE);
4864+ g_setenv("XDG_DATA_HOME", CMAKE_SOURCE_DIR "/libertine-home", TRUE);
4865+
4866+ testbus = g_test_dbus_new(G_TEST_DBUS_NONE);
4867+ g_test_dbus_up(testbus);
4868+
4869+ registry = std::make_shared<ubuntu::app_launch::Registry>();
4870+ }
4871+
4872+ virtual void TearDown()
4873+ {
4874+ registry.reset();
4875+
4876+ g_test_dbus_down(testbus);
4877+ g_clear_object(&testbus);
4878+ }
4879 };
4880
4881-static void
4882-failed_observer (const gchar * appid, UbuntuAppLaunchAppFailed reason, gpointer user_data)
4883-{
4884- if (reason == UBUNTU_APP_LAUNCH_APP_FAILED_CRASH) {
4885- std::string * last = static_cast<std::string *>(user_data);
4886- *last = appid;
4887- }
4888-}
4889-
4890 TEST_F(FailureTest, CrashTest)
4891 {
4892- g_setenv("EXIT_STATUS", "-100", TRUE);
4893- g_setenv("JOB", "application-click", TRUE);
4894- g_setenv("INSTANCE", "foo", TRUE);
4895-
4896- std::string last_observer;
4897- ASSERT_TRUE(ubuntu_app_launch_observer_add_app_failed(failed_observer, &last_observer));
4898-
4899- /* Status based */
4900- ASSERT_TRUE(g_spawn_command_line_sync(APP_FAILED_TOOL, NULL, NULL, NULL, NULL));
4901-
4902- EXPECT_EVENTUALLY_EQ("foo", last_observer);
4903-
4904- last_observer.clear();
4905- g_unsetenv("EXIT_STATUS");
4906- g_setenv("EXIT_SIGNAL", "KILL", TRUE);
4907-
4908- /* Signal based */
4909- ASSERT_TRUE(g_spawn_command_line_sync(APP_FAILED_TOOL, NULL, NULL, NULL, NULL));
4910-
4911- EXPECT_EVENTUALLY_EQ("foo", last_observer);
4912-
4913- ASSERT_TRUE(ubuntu_app_launch_observer_delete_app_failed(failed_observer, &last_observer));
4914+ g_setenv("EXIT_STATUS", "-100", TRUE);
4915+ g_setenv("JOB", "application-click", TRUE);
4916+ g_setenv("INSTANCE", "foo", TRUE);
4917+
4918+ std::string last_observer;
4919+ ubuntu::app_launch::Registry::appFailed(registry).connect(
4920+ [&last_observer](std::shared_ptr<ubuntu::app_launch::Application> app,
4921+ std::shared_ptr<ubuntu::app_launch::Application::Instance> instance,
4922+ ubuntu::app_launch::Registry::FailureType type) {
4923+ if (type == ubuntu::app_launch::Registry::FailureType::CRASH)
4924+ {
4925+ last_observer = app->appId();
4926+ }
4927+ });
4928+
4929+ /* Status based */
4930+ ASSERT_TRUE(g_spawn_command_line_sync(APP_FAILED_TOOL, NULL, NULL, NULL, NULL));
4931+
4932+ EXPECT_EVENTUALLY_EQ("foo", last_observer);
4933+
4934+ last_observer.clear();
4935+ g_unsetenv("EXIT_STATUS");
4936+ g_setenv("EXIT_SIGNAL", "KILL", TRUE);
4937+
4938+ /* Signal based */
4939+ ASSERT_TRUE(g_spawn_command_line_sync(APP_FAILED_TOOL, NULL, NULL, NULL, NULL));
4940+
4941+ EXPECT_EVENTUALLY_EQ("foo", last_observer);
4942 }
4943
4944 TEST_F(FailureTest, LegacyTest)
4945 {
4946- g_setenv("EXIT_STATUS", "-100", TRUE);
4947- g_setenv("JOB", "application-legacy", TRUE);
4948- g_setenv("INSTANCE", "foo-1234", TRUE);
4949-
4950- std::string last_observer;
4951- ASSERT_TRUE(ubuntu_app_launch_observer_add_app_failed(failed_observer, &last_observer));
4952-
4953- /* Status based */
4954- ASSERT_TRUE(g_spawn_command_line_sync(APP_FAILED_TOOL, NULL, NULL, NULL, NULL));
4955-
4956- EXPECT_EVENTUALLY_EQ("foo", last_observer);
4957-
4958- ASSERT_TRUE(ubuntu_app_launch_observer_delete_app_failed(failed_observer, &last_observer));
4959+ g_setenv("EXIT_STATUS", "-100", TRUE);
4960+ g_setenv("JOB", "application-legacy", TRUE);
4961+ g_setenv("INSTANCE", "foo-1234", TRUE);
4962+
4963+ std::string last_observer;
4964+ ubuntu::app_launch::Registry::appFailed(registry).connect(
4965+ [&last_observer](std::shared_ptr<ubuntu::app_launch::Application> app,
4966+ std::shared_ptr<ubuntu::app_launch::Application::Instance> instance,
4967+ ubuntu::app_launch::Registry::FailureType type) {
4968+ g_debug("Signal handler called");
4969+ if (type == ubuntu::app_launch::Registry::FailureType::CRASH)
4970+ {
4971+ last_observer = app->appId();
4972+ }
4973+ });
4974+
4975+ /* Status based */
4976+ ASSERT_TRUE(g_spawn_command_line_sync(APP_FAILED_TOOL, NULL, NULL, NULL, NULL));
4977+
4978+ EXPECT_EVENTUALLY_EQ("foo", last_observer);
4979 }
4980
4981 TEST_F(FailureTest, SnapTest)
4982 {
4983- g_setenv("EXIT_STATUS", "-100", TRUE);
4984- g_setenv("JOB", "application-snap", TRUE);
4985- g_setenv("INSTANCE", "foo_bar_x123-1234", TRUE);
4986-
4987- std::string last_observer;
4988- ASSERT_TRUE(ubuntu_app_launch_observer_add_app_failed(failed_observer, &last_observer));
4989-
4990- /* Status based */
4991- ASSERT_TRUE(g_spawn_command_line_sync(APP_FAILED_TOOL, NULL, NULL, NULL, NULL));
4992-
4993- EXPECT_EVENTUALLY_EQ("foo_bar_x123", last_observer);
4994-
4995- ASSERT_TRUE(ubuntu_app_launch_observer_delete_app_failed(failed_observer, &last_observer));
4996-}
4997-
4998-static void
4999-failed_start_observer (const gchar * appid, UbuntuAppLaunchAppFailed reason, gpointer user_data)
5000-{
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches