Merge lp:~ted/url-dispatcher/url-overlay into lp:url-dispatcher/15.10

Proposed by Ted Gould
Status: Merged
Approved by: Ted Gould
Approved revision: 138
Merged at revision: 86
Proposed branch: lp:~ted/url-dispatcher/url-overlay
Merge into: lp:url-dispatcher/15.10
Prerequisite: lp:~ted/url-dispatcher/upstart-build-dep
Diff against target: 2046 lines (+1506/-41)
34 files modified
CMakeLists.txt (+5/-2)
data/CMakeLists.txt (+7/-2)
data/url-dispatcher.url-overlay.click-hook.in (+3/-0)
debian/control (+2/-1)
debian/rules (+5/-0)
debian/url-dispatcher.install (+1/-0)
service/CMakeLists.txt (+26/-2)
service/dispatcher.c (+96/-13)
service/dispatcher.h (+6/-3)
service/glib-thread.cpp (+179/-0)
service/glib-thread.h (+81/-0)
service/overlay-tracker-iface.h (+26/-0)
service/overlay-tracker-mir.cpp (+153/-0)
service/overlay-tracker-mir.h (+49/-0)
service/overlay-tracker.cpp (+54/-0)
service/overlay-tracker.h (+28/-0)
service/recoverable-problem.c (+6/-0)
service/service.c (+3/-1)
service/url-db.c (+1/-1)
service/url-db.h (+1/-1)
service/url-overlay.c (+174/-0)
tests/CMakeLists.txt (+47/-1)
tests/app-id-test.cc (+3/-1)
tests/dispatcher-test.cc (+25/-1)
tests/exec-tool-test.cc (+118/-0)
tests/mir-mock.cpp (+124/-0)
tests/mir-mock.h (+36/-0)
tests/overlay-dir/com.test.good_application_1.2.3.desktop (+2/-0)
tests/overlay-tracker-mock.h (+30/-0)
tests/overlay-tracker-test.cpp (+127/-0)
tests/service-test.cc (+3/-8)
tests/test-config.h.in (+4/-0)
tests/ubuntu-app-launch-mock.c (+67/-2)
tests/ubuntu-app-launch-mock.h (+14/-2)
To merge this branch: bzr merge lp:~ted/url-dispatcher/url-overlay
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Charles Kerr (community) Approve
Review via email: mp+261415@code.launchpad.net

This proposal supersedes a proposal from 2015-05-17.

Commit message

Support URL Overlays of content in a trusted session without a desktop file

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~ted/url-dispatcher/url-overlay updated
132. By Ted Gould

Make the exec-tool test work with the directory feature (and test it)

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~ted/url-dispatcher/url-overlay updated
133. By Ted Gould

Fixing the dispatcher test to match the fixes in the exec tool test

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
lp:~ted/url-dispatcher/url-overlay updated
134. By Ted Gould

Allow dir to be NULL

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Charles Kerr (charlesk) wrote :

Some questions/comments inline, no blockers though.

review: Approve
Revision history for this message
Ted Gould (ted) wrote :

On Thu, 2015-06-11 at 17:26 +0000, Charles Kerr wrote:

> Some questions/comments inline, no blockers though.

Great, thanks for reviewing, long MR!

> > + -lpthread
>
> -lpthread reportedly doesn't work on clang, maybe use -pthread instead here?

Oh, didn't realize that. Fixed r135.

> > +ContextThread::~ContextThread (void)
>
> not a blocker, but the 'void' isn't needed in C++ for functions taking no args

Not *needed* but enjoyed. Removed r136.

> > + void removeSession (MirPromptSession * session);
> > +
> > + static void sessionStateChangedStatic (MirPromptSession * session, MirPromptSessionState state, void * user_data);
> > + void sessionStateChanged (MirPromptSession * session, MirPromptSessionState state);
> > +
> > + static void untrustedHelperStoppedStatic (const gchar * appid, const gchar * instanceid, const gchar * helpertype, gpointer user_data);
> > + void untrustedHelperStopped(const gchar * appid, const gchar * instanceid, const gchar * helpertype);
>
> These five should be private

Fixed r137.

> > + OverlayTrackerMir * cpptracker = new OverlayTrackerMir();
> > + return reinterpret_cast<OverlayTracker *>(cpptracker);
>
> I don't think reinterpret_cast<> is needed here; you should be able to upcast implicitly; e.g.
>
> try {
> return new OverlayTrackerMir();
> }
> catch(...) {
> return nullptr;
> }

We're not actually upcasting here, we're casting to an anonymous C
pointer, so that's why we need it. Trying to merge the C and C++ worlds.

> > + } catch (...) {
> > + return nullptr;
> > + }
> > +}
> > +
> > +void
> > +overlay_tracker_delete (OverlayTracker * tracker) {
> > + g_return_if_fail(tracker != nullptr);
> > +
> > + auto cpptracker = reinterpret_cast<OverlayTrackerMir *>(tracker);
> > + delete cpptracker;
>
> no need to reinterpret_cast here either; "delete tracker" should suffice

Same, OverlayTracker isn't a C++ object so it can't be deleted.

> > + /* Allow disabling for testing, we don't want to report bugs on
> > + our tests ;-) */
> > + if (G_UNLIKELY(g_getenv("URL_DISPATCHER_DISABLE_RECOVERABLE_ERROR") != NULL)) {
> > + return;
> > + }
> > +
>
> :-)

As an aside I built this into the libwhoopsie version as well. Make sure
to use it :-)

> > GError * error = NULL;
> > gint error_stdin = 0;
> > GPid pid = 0;
> >
> > === modified file 'service/service.c'
> > --- service/service.c 2014-03-14 19:50:37 +0000
> > +++ service/service.c 2015-06-08 20:42:15 +0000
> > @@ -39,13 +39,15 @@
> >
> > guint term_source = g_unix_signal_add(SIGTERM, sig_term, mainloop);
> >
> > - dispatcher_init(mainloop);
> > + OverlayTracker * tracker = overlay_tracker_new();
> > + dispatcher_init(mainloop, tracker);
>
> Looks like dispatcher.c is the only user of this tracker; why not make it a private variable there, instantiated by dispatcher_init() and released by dispatcher_shutdown()?

Mostly to make the injection of the mock easier in the dispatcher tests.
It doesn't look as nice in C as it does in C++ when you do that. Would
be nice to change main.c to main.cpp, but this MR is big enough ☺

Also fixed a couple warning I hadn't noticed before, r138.

lp:~ted/url-dispatcher/url-overlay updated
135. By Ted Gould

Switch linking option to not include the pthread library, but instead ask for it directly

136. By Ted Gould

Remove (void) as a function prototype

137. By Ted Gould

Making internal functions private

138. By Ted Gould

Clean up a couple of warnings

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2014-10-28 12:46:32 +0000
3+++ CMakeLists.txt 2015-06-12 15:51:31 +0000
4@@ -52,7 +52,7 @@
5 set(CMAKE_C_FLAGS "${CMAKE_CXX_FLAGS} -Werror")
6 endif()
7
8-pkg_check_modules(UBUNTU_APP_LAUNCH REQUIRED ubuntu-app-launch-2)
9+pkg_check_modules(UBUNTU_APP_LAUNCH REQUIRED ubuntu-app-launch-2>=0.5)
10 include_directories(${UBUNTU_APP_LAUNCH_INCLUDE_DIRS})
11
12 pkg_check_modules(GLIB2 REQUIRED glib-2.0)
13@@ -76,6 +76,9 @@
14 pkg_check_modules(SQLITE REQUIRED sqlite3)
15 include_directories(${SQLITE_INCLUDE_DIRS})
16
17+pkg_check_modules(CLICK REQUIRED click-0.4)
18+include_directories(${CLICK_INCLUDE_DIRS})
19+
20 if(${LOCAL_INSTALL})
21 set(DBUSSERVICEDIR "${CMAKE_INSTALL_DATADIR}/dbus-1/services/")
22 else()
23@@ -105,7 +108,7 @@
24 if (${enable_tests})
25 set (GTEST_SOURCE_DIR /usr/src/gtest/src)
26 set (GTEST_INCLUDE_DIR ${GTEST_SOURCE_DIR}/..)
27- set (GTEST_LIBS -lpthread)
28+ set (GTEST_LIBS -pthread)
29 enable_testing ()
30 if (${enable_lcov})
31 # include(GCov)
32
33=== modified file 'data/CMakeLists.txt'
34--- data/CMakeLists.txt 2014-08-20 18:58:49 +0000
35+++ data/CMakeLists.txt 2015-06-12 15:51:31 +0000
36@@ -92,8 +92,13 @@
37 # Click Hook
38 ###########################
39
40-configure_file("url-dispatcher.click-hook.in"
41- "${CMAKE_SOURCE_DIR}/debian/url-dispatcher.click-hook"
42+configure_file("url-dispatcher.urls.click-hook.in"
43+ "${CMAKE_SOURCE_DIR}/debian/url-dispatcher.urls.click-hook"
44+ @ONLY
45+)
46+
47+configure_file("url-dispatcher.url-overlay.click-hook.in"
48+ "${CMAKE_SOURCE_DIR}/debian/url-dispatcher.url-overlay.click-hook"
49 @ONLY
50 )
51
52
53=== added file 'data/url-dispatcher.url-overlay.click-hook.in'
54--- data/url-dispatcher.url-overlay.click-hook.in 1970-01-01 00:00:00 +0000
55+++ data/url-dispatcher.url-overlay.click-hook.in 2015-06-12 15:51:31 +0000
56@@ -0,0 +1,3 @@
57+Pattern: ${home}/.cache/url-dispatcher/url-overlays/${id}.desktop
58+User-Level: yes
59+Hook-Name: url-overlay
60
61=== renamed file 'data/url-dispatcher.click-hook.in' => 'data/url-dispatcher.urls.click-hook.in'
62=== modified file 'debian/control'
63--- debian/control 2015-06-12 15:51:30 +0000
64+++ debian/control 2015-06-12 15:51:31 +0000
65@@ -9,13 +9,14 @@
66 dh-autoreconf,
67 gtester2xunit,
68 intltool,
69+ libclick-0.4-dev,
70 libdbus-1-dev,
71 libdbustest1-dev (>= 14.04.0),
72 libglib2.0-dev,
73 libjson-glib-dev,
74 libgtest-dev,
75 libsqlite3-dev,
76- libubuntu-app-launch2-dev (>= 0.3),
77+ libubuntu-app-launch2-dev (>= 0.5),
78 python3,
79 python3-dbusmock,
80 python3-fixtures,
81
82=== modified file 'debian/rules'
83--- debian/rules 2014-07-20 11:45:00 +0000
84+++ debian/rules 2015-06-12 15:51:31 +0000
85@@ -2,6 +2,7 @@
86
87 export DPKG_GENSYMBOLS_CHECK_LEVEL = 4
88 export G_MESSAGES_DEBUG=all
89+export URL_DISPATCHER_DISABLE_RECOVERABLE_ERROR=1
90
91 %:
92 dh $@ --fail-missing --with click
93@@ -12,3 +13,7 @@
94 mkdir -p debian/url-dispatcher/etc/apport/crashdb.conf.d/
95 install -m 644 debian/url-dispatcher-crashdb.conf debian/url-dispatcher/etc/apport/crashdb.conf.d/
96 dh_install --fail-missing
97+
98+override_dh_click:
99+ dh_click --name urls
100+ dh_click --name url-overlay
101
102=== modified file 'debian/url-dispatcher.install'
103--- debian/url-dispatcher.install 2013-06-28 16:35:12 +0000
104+++ debian/url-dispatcher.install 2015-06-12 15:51:31 +0000
105@@ -1,3 +1,4 @@
106 usr/lib/*/url-dispatcher
107+usr/lib/*/ubuntu-app-launch/url-overlay/exec-tool
108 usr/share/dbus-1
109 usr/share/upstart/sessions
110
111=== modified file 'service/CMakeLists.txt'
112--- service/CMakeLists.txt 2014-05-26 12:56:47 +0000
113+++ service/CMakeLists.txt 2015-06-12 15:51:31 +0000
114@@ -2,6 +2,8 @@
115 include(UseConstantBuilder)
116 include_directories(${CMAKE_CURRENT_SOURCE_DIR})
117
118+add_definitions( -DOVERLAY_SYSTEM_DIRECTORY="${CMAKE_INSTALL_FULL_DATADIR}/url-dispatcher/url-overlays" )
119+
120 ###########################
121 # Generated Lib
122 ###########################
123@@ -38,15 +40,24 @@
124
125 add_library(dispatcher-lib STATIC
126 dispatcher.h
127- dispatcher.c)
128+ dispatcher.c
129+ glib-thread.h
130+ glib-thread.cpp
131+ overlay-tracker.h
132+ overlay-tracker.cpp
133+ overlay-tracker-iface.h
134+ overlay-tracker-mir.h
135+ overlay-tracker-mir.cpp)
136
137 target_link_libraries(dispatcher-lib
138 url-db-lib
139 service-generated
140+ -pthread
141 ${GLIB2_LIBRARIES}
142 ${GOBJECT2_LIBRARIES}
143 ${GIO2_LIBRARIES}
144 ${SQLITE_LIBRARIES}
145+ ${UBUNTU_APP_LAUNCH_LIBRARIES}
146 )
147
148 ###########################
149@@ -84,7 +95,7 @@
150
151 set_target_properties(service-exec PROPERTIES OUTPUT_NAME "url-dispatcher")
152
153-target_link_libraries(service-exec dispatcher-lib ${UBUNTU_APP_LAUNCH_LIBRARIES})
154+target_link_libraries(service-exec dispatcher-lib)
155
156 ###########################
157 # Update Directory
158@@ -95,6 +106,14 @@
159 target_link_libraries(update-directory ${GIO2_LIBRARIES} ${JSONGLIB_LIBRARIES} url-db-lib)
160
161 ###########################
162+# URL Overlay Exec Tool
163+###########################
164+
165+add_executable(url-overlay-exec-tool url-overlay.c recoverable-problem.c)
166+set_target_properties(url-overlay-exec-tool PROPERTIES OUTPUT_NAME "exec-tool")
167+target_link_libraries(url-overlay-exec-tool ${GIO2_LIBRARIES} ${UBUNTU_APP_LAUNCH_LIBRARIES} ${CLICK_LIBRARIES})
168+
169+###########################
170 # Installation
171 ###########################
172
173@@ -103,3 +122,8 @@
174 RUNTIME DESTINATION "${CMAKE_INSTALL_FULL_LIBEXECDIR}/url-dispatcher"
175 )
176
177+install(
178+ TARGETS url-overlay-exec-tool
179+ RUNTIME DESTINATION "${CMAKE_INSTALL_FULL_LIBEXECDIR}/ubuntu-app-launch/url-overlay"
180+)
181+
182
183=== modified file 'service/dispatcher.c'
184--- service/dispatcher.c 2015-01-23 14:34:30 +0000
185+++ service/dispatcher.c 2015-06-12 15:51:31 +0000
186@@ -26,6 +26,7 @@
187 #include "url-db.h"
188
189 /* Globals */
190+static OverlayTracker * tracker = NULL;
191 static GCancellable * cancellable = NULL;
192 static ServiceIfaceComCanonicalURLDispatcher * skel = NULL;
193 static GRegex * applicationre = NULL;
194@@ -44,7 +45,7 @@
195
196 /* Register our errors */
197 static void
198-register_dbus_errors (void)
199+register_dbus_errors ()
200 {
201 g_dbus_error_register_error(url_dispatcher_error_quark(), ERROR_BAD_URL, "com.canonical.URLDispatcher.BadURL");
202 g_dbus_error_register_error(url_dispatcher_error_quark(), ERROR_RESTRICTED_URL, "com.canonical.URLDispatcher.RestrictedURL");
203@@ -78,11 +79,7 @@
204 NULL
205 };
206
207- /* Allow disabling for testing, we don't want to report bugs on
208- our tests ;-) */
209- if (g_getenv("URL_DISPATCHER_DISABLE_RECOVERABLE_ERROR") == NULL) {
210- report_recoverable_problem("url-dispatcher-bad-url", pid, FALSE, additional);
211- }
212+ report_recoverable_problem("url-dispatcher-bad-url", pid, FALSE, additional);
213
214 g_free(badurl);
215
216@@ -111,7 +108,7 @@
217 bad_url (GDBusMethodInvocation * invocation, const gchar * url)
218 {
219 const gchar * sender = g_dbus_method_invocation_get_sender(invocation);
220- GDBusConnection * conn = g_dbus_method_invocation_get_connection(invocation);
221+ GDBusConnection * conn = g_dbus_method_invocation_get_connection(invocation); /* transfer: none */
222
223 g_dbus_connection_call(conn,
224 "org.freedesktop.DBus",
225@@ -213,6 +210,73 @@
226 return TRUE;
227 }
228
229+/* Handles setting up the overlay with the URL */
230+gboolean
231+dispatcher_send_to_overlay (const gchar * app_id, const gchar * url, GDBusConnection * conn, const gchar * sender)
232+{
233+ GError * error = NULL;
234+
235+ /* TODO: Detect if a scope is what we need to overlay on */
236+ GVariant * callret = g_dbus_connection_call_sync(conn,
237+ "org.freedesktop.DBus",
238+ "/",
239+ "org.freedesktop.DBus",
240+ "GetConnectionUnixProcessID",
241+ g_variant_new("(s)", sender),
242+ G_VARIANT_TYPE("(u)"),
243+ G_DBUS_CALL_FLAGS_NONE,
244+ -1, /* timeout */
245+ NULL, /* cancellable */
246+ &error);
247+
248+ if (error != NULL) {
249+ g_warning("Unable to get PID for '%s' when processing URL '%s': %s", sender, url, error->message);
250+ g_error_free(error);
251+ return FALSE;
252+ }
253+
254+ unsigned int pid = 0;
255+ g_variant_get_child(callret, 0, "u", &pid);
256+ g_variant_unref(callret);
257+
258+ return overlay_tracker_add(tracker, app_id, pid, url);
259+}
260+
261+/* Check to see if this is an overlay AppID */
262+gboolean
263+dispatcher_is_overlay (const gchar * appid)
264+{
265+ const gchar * systemdir = NULL;
266+ gboolean found = FALSE;
267+ gchar * desktopname = g_strdup_printf("%s.desktop", appid);
268+
269+ /* First time, check the environment */
270+ if (G_UNLIKELY(systemdir == NULL)) {
271+ systemdir = g_getenv("URL_DISPATCHER_OVERLAY_DIR");
272+ if (systemdir == NULL) {
273+ systemdir = OVERLAY_SYSTEM_DIRECTORY;
274+ }
275+ }
276+
277+ /* Check system dir */
278+ if (!found) {
279+ gchar * sysdir = g_build_filename(systemdir, desktopname, NULL);
280+ found = g_file_test(sysdir, G_FILE_TEST_EXISTS);
281+ g_free(sysdir);
282+ }
283+
284+ /* Check user dir (clicks) */
285+ if (!found) {
286+ gchar * usrdir = g_build_filename(g_get_user_cache_dir(), "url-dispatcher", "url-overlays", desktopname, NULL);
287+ found = g_file_test(usrdir, G_FILE_TEST_EXISTS);
288+ g_free(usrdir);
289+ }
290+
291+ g_free(desktopname);
292+
293+ return found;
294+}
295+
296 /* Whether we should restrict this appid based on the package name */
297 gboolean
298 dispatcher_appid_restrict (const gchar * appid, const gchar * package)
299@@ -277,12 +341,25 @@
300 }
301
302 /* We're cleared to continue */
303- dispatcher_send_to_app(appid, outurl);
304+ gboolean sent = FALSE;
305+ if (!dispatcher_is_overlay(appid)) {
306+ sent = dispatcher_send_to_app(appid, outurl);
307+ } else {
308+ sent = dispatcher_send_to_overlay(
309+ appid,
310+ outurl,
311+ g_dbus_method_invocation_get_connection(invocation),
312+ g_dbus_method_invocation_get_sender(invocation));
313+ }
314 g_free(appid);
315
316- g_dbus_method_invocation_return_value(invocation, NULL);
317+ if (sent) {
318+ g_dbus_method_invocation_return_value(invocation, NULL);
319+ } else {
320+ bad_url(invocation, url);
321+ }
322
323- return TRUE;
324+ return sent;
325 }
326
327 /* Test a URL to find it's AppID */
328@@ -347,6 +424,9 @@
329 gboolean
330 dispatcher_url_to_appid (const gchar * url, gchar ** out_appid, const gchar ** out_url)
331 {
332+ g_return_val_if_fail(url != NULL, FALSE);
333+ g_return_val_if_fail(out_appid != NULL, FALSE);
334+
335 /* Special case the app id */
336 GMatchInfo * appidmatch = NULL;
337 if (g_regex_match(appidre, url, 0, &appidmatch)) {
338@@ -398,7 +478,9 @@
339
340 if (*out_appid != NULL) {
341 found = TRUE;
342- *out_url = url;
343+ if (out_url != NULL) {
344+ *out_url = url;
345+ }
346 }
347
348 g_free(protocol);
349@@ -465,8 +547,9 @@
350
351 /* Initialize all the globals */
352 gboolean
353-dispatcher_init (GMainLoop * mainloop)
354+dispatcher_init (GMainLoop * mainloop, OverlayTracker * intracker)
355 {
356+ tracker = intracker;
357 cancellable = g_cancellable_new();
358
359 urldb = url_db_create_database();
360@@ -487,7 +570,7 @@
361
362 /* Clean up all the globals */
363 gboolean
364-dispatcher_shutdown (void)
365+dispatcher_shutdown ()
366 {
367 g_cancellable_cancel(cancellable);
368
369
370=== modified file 'service/dispatcher.h'
371--- service/dispatcher.h 2014-10-24 18:55:11 +0000
372+++ service/dispatcher.h 2015-06-12 15:51:31 +0000
373@@ -20,15 +20,18 @@
374 #ifndef DISPATCHER_H
375 #define DISPATCHER_H 1
376
377-#include <glib.h>
378+#include <gio/gio.h>
379+#include "overlay-tracker.h"
380
381 G_BEGIN_DECLS
382
383-gboolean dispatcher_init (GMainLoop * mainloop);
384-gboolean dispatcher_shutdown (void);
385+gboolean dispatcher_init (GMainLoop * mainloop, OverlayTracker * tracker);
386+gboolean dispatcher_shutdown ();
387 gboolean dispatcher_url_to_appid (const gchar * url, gchar ** out_appid, const gchar ** out_url);
388 gboolean dispatcher_appid_restrict (const gchar * appid, const gchar * package);
389+gboolean dispatcher_is_overlay (const gchar * appid);
390 gboolean dispatcher_send_to_app (const gchar * appid, const gchar * url);
391+gboolean dispatcher_send_to_overlay (const gchar * app_id, const gchar * url, GDBusConnection * conn, const gchar * sender);
392
393 G_END_DECLS
394
395
396=== added file 'service/glib-thread.cpp'
397--- service/glib-thread.cpp 1970-01-01 00:00:00 +0000
398+++ service/glib-thread.cpp 2015-06-12 15:51:31 +0000
399@@ -0,0 +1,179 @@
400+/*
401+ * Copyright © 2015 Canonical Ltd.
402+ *
403+ * This program is free software: you can redistribute it and/or modify it
404+ * under the terms of the GNU General Public License version 3, as published
405+ * by the Free Software Foundation.
406+ *
407+ * This program is distributed in the hope that it will be useful, but
408+ * WITHOUT ANY WARRANTY; without even the implied warranties of
409+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
410+ * PURPOSE. See the GNU General Public License for more details.
411+ *
412+ * You should have received a copy of the GNU General Public License along
413+ * with this program. If not, see <http://www.gnu.org/licenses/>.
414+ *
415+ * Authors:
416+ * Ted Gould <ted.gould@canonical.com>
417+ */
418+
419+#include "glib-thread.h"
420+
421+namespace GLib
422+{
423+
424+
425+ContextThread::ContextThread (std::function<void()> beforeLoop, std::function<void()> afterLoop)
426+ : _context(nullptr)
427+ , _loop(nullptr)
428+{
429+ _cancel = std::shared_ptr<GCancellable>(g_cancellable_new(), [](GCancellable * cancel)
430+ {
431+ if (cancel != nullptr)
432+ {
433+ g_cancellable_cancel(cancel);
434+ g_object_unref(cancel);
435+ }
436+ });
437+ std::promise<std::pair<std::shared_ptr<GMainContext>, std::shared_ptr<GMainLoop>>> context_promise;
438+
439+ /* NOTE: We copy afterLoop but reference beforeLoop. We're blocking so we
440+ know that beforeLoop will stay valid long enough, but we can't say the
441+ same for afterLoop */
442+ _thread = std::thread([&context_promise, &beforeLoop, afterLoop, this]() -> void
443+ {
444+ /* Build up the context and loop for the async events and a place
445+ for GDBus to send its events back to */
446+ auto context = std::shared_ptr<GMainContext>(g_main_context_new(), [](GMainContext * context)
447+ {
448+ g_clear_pointer(&context, g_main_context_unref);
449+ });
450+ auto loop = std::shared_ptr<GMainLoop>(g_main_loop_new(context.get(), FALSE), [](GMainLoop * loop)
451+ {
452+ g_clear_pointer(&loop, g_main_loop_unref);
453+ });
454+
455+ g_main_context_push_thread_default(context.get());
456+
457+ beforeLoop();
458+
459+ /* Free's the constructor to continue */
460+ auto pair = std::pair<std::shared_ptr<GMainContext>, std::shared_ptr<GMainLoop>>(context, loop);
461+ context_promise.set_value(pair);
462+
463+ if (!g_cancellable_is_cancelled(_cancel.get()))
464+ {
465+ g_main_loop_run(loop.get());
466+ }
467+
468+ afterLoop();
469+ });
470+
471+ /* We need to have the context and the mainloop ready before
472+ other functions on this object can work properly. So we wait
473+ for them and set them on this thread. */
474+ auto context_future = context_promise.get_future();
475+ context_future.wait();
476+ auto context_value = context_future.get();
477+
478+ _context = context_value.first;
479+ _loop = context_value.second;
480+
481+ if (_context == nullptr || _loop == nullptr)
482+ {
483+ throw std::runtime_error("Unable to create GLib Thread");
484+ }
485+}
486+
487+ContextThread::~ContextThread ()
488+{
489+ quit();
490+}
491+
492+void ContextThread::quit ()
493+{
494+ g_cancellable_cancel(_cancel.get()); /* Force the cancellation on ongoing tasks */
495+ if (_loop != nullptr)
496+ {
497+ g_main_loop_quit(_loop.get()); /* Quit the loop */
498+ }
499+
500+ /* Joining here because we want to ensure that the final afterLoop()
501+ function is run before returning */
502+ if (std::this_thread::get_id() != _thread.get_id())
503+ {
504+ if (_thread.joinable())
505+ {
506+ _thread.join();
507+ }
508+ }
509+}
510+
511+bool ContextThread::isCancelled ()
512+{
513+ return g_cancellable_is_cancelled(_cancel.get()) == TRUE;
514+}
515+
516+std::shared_ptr<GCancellable> ContextThread::getCancellable ()
517+{
518+ return _cancel;
519+}
520+
521+void ContextThread::simpleSource (std::function<GSource * ()> srcBuilder, std::function<void()> work)
522+{
523+ if (isCancelled())
524+ {
525+ throw std::runtime_error("Trying to execute work on a GLib thread that is shutting down.");
526+ }
527+
528+ /* Copy the work so that we can reuse it */
529+ /* Lifecycle is handled with the source pointer when we attach
530+ it to the context. */
531+ auto heapWork = new std::function<void()>(work);
532+
533+ auto source = std::shared_ptr<GSource>(srcBuilder(),
534+ [](GSource * src)
535+ {
536+ g_clear_pointer(&src, g_source_unref);
537+ }
538+ );
539+ g_source_set_callback(source.get(),
540+ [](gpointer data) -> gboolean
541+ {
542+ std::function<void()>* heapWork = reinterpret_cast<std::function<void()> *>(data);
543+ (*heapWork)();
544+ return G_SOURCE_REMOVE;
545+ }, heapWork,
546+ [](gpointer data)
547+ {
548+ std::function<void()>* heapWork = reinterpret_cast<std::function<void()> *>(data);
549+ delete heapWork;
550+ });
551+
552+ g_source_attach(source.get(), _context.get());
553+}
554+
555+void ContextThread::executeOnThread (std::function<void()> work)
556+{
557+ simpleSource(g_idle_source_new, work);
558+}
559+
560+void ContextThread::timeout (const std::chrono::milliseconds& length,
561+ std::function<void()> work)
562+{
563+ simpleSource([length]()
564+ {
565+ return g_timeout_source_new(length.count());
566+ }, work);
567+}
568+
569+void ContextThread::timeoutSeconds (const std::chrono::seconds& length,
570+ std::function<void()> work)
571+{
572+ simpleSource([length]()
573+ {
574+ return g_timeout_source_new_seconds(length.count());
575+ }, work);
576+}
577+
578+} // ns GLib
579
580=== added file 'service/glib-thread.h'
581--- service/glib-thread.h 1970-01-01 00:00:00 +0000
582+++ service/glib-thread.h 2015-06-12 15:51:31 +0000
583@@ -0,0 +1,81 @@
584+/*
585+ * Copyright © 2015 Canonical Ltd.
586+ *
587+ * This program is free software: you can redistribute it and/or modify it
588+ * under the terms of the GNU General Public License version 3, as published
589+ * by the Free Software Foundation.
590+ *
591+ * This program is distributed in the hope that it will be useful, but
592+ * WITHOUT ANY WARRANTY; without even the implied warranties of
593+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
594+ * PURPOSE. See the GNU General Public License for more details.
595+ *
596+ * You should have received a copy of the GNU General Public License along
597+ * with this program. If not, see <http://www.gnu.org/licenses/>.
598+ *
599+ * Authors:
600+ * Ted Gould <ted.gould@canonical.com>
601+ */
602+
603+#include <thread>
604+#include <future>
605+
606+#include <gio/gio.h>
607+
608+namespace GLib
609+{
610+
611+class ContextThread
612+{
613+ std::thread _thread;
614+ std::shared_ptr<GMainContext> _context;
615+ std::shared_ptr<GMainLoop> _loop;
616+ std::shared_ptr<GCancellable> _cancel;
617+
618+public:
619+ ContextThread (std::function<void()> beforeLoop = [] {}, std::function<void()> afterLoop = [] {});
620+ ~ContextThread ();
621+
622+ void quit ();
623+ bool isCancelled ();
624+ std::shared_ptr<GCancellable> getCancellable ();
625+
626+ void executeOnThread (std::function<void()> work);
627+ template<typename T> auto executeOnThread (std::function<T()> work) -> T
628+ {
629+ if (std::this_thread::get_id() == _thread.get_id())
630+ {
631+ /* Don't block if we're on the same thread */
632+ return work();
633+ }
634+
635+ std::promise<T> promise;
636+ std::function<void()> magicFunc = [&promise, &work] () -> void {
637+ promise.set_value(work());
638+ };
639+
640+ executeOnThread(magicFunc);
641+
642+ auto future = promise.get_future();
643+ future.wait();
644+ return future.get();
645+ }
646+
647+ void timeout (const std::chrono::milliseconds& length, std::function<void()> work);
648+ template<class Rep, class Period> void timeout (const std::chrono::duration<Rep, Period>& length,
649+ std::function<void()> work)
650+ {
651+ return timeout(std::chrono::duration_cast<std::chrono::milliseconds>(length), work);
652+ }
653+
654+ void timeoutSeconds (const std::chrono::seconds& length, std::function<void()> work);
655+ template<class Rep, class Period> void timeoutSeconds (const std::chrono::duration<Rep, Period>& length,
656+ std::function<void()> work)
657+ {
658+ return timeoutSeconds(std::chrono::duration_cast<std::chrono::seconds>(length), work);
659+ }
660+
661+private:
662+ void simpleSource (std::function<GSource * ()> srcBuilder, std::function<void()> work);
663+};
664+}
665
666=== added file 'service/overlay-tracker-iface.h'
667--- service/overlay-tracker-iface.h 1970-01-01 00:00:00 +0000
668+++ service/overlay-tracker-iface.h 2015-06-12 15:51:31 +0000
669@@ -0,0 +1,26 @@
670+/*
671+ * Copyright © 2015 Canonical Ltd.
672+ *
673+ * This program is free software: you can redistribute it and/or modify it
674+ * under the terms of the GNU General Public License version 3, as published
675+ * by the Free Software Foundation.
676+ *
677+ * This program is distributed in the hope that it will be useful, but
678+ * WITHOUT ANY WARRANTY; without even the implied warranties of
679+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
680+ * PURPOSE. See the GNU General Public License for more details.
681+ *
682+ * You should have received a copy of the GNU General Public License along
683+ * with this program. If not, see <http://www.gnu.org/licenses/>.
684+ *
685+ * Authors:
686+ * Ted Gould <ted.gould@canonical.com>
687+ */
688+
689+#pragma once
690+
691+class OverlayTrackerIface {
692+public:
693+ virtual ~OverlayTrackerIface() = default;
694+ virtual bool addOverlay (const char * appid, unsigned long pid, const char * url) = 0;
695+};
696
697=== added file 'service/overlay-tracker-mir.cpp'
698--- service/overlay-tracker-mir.cpp 1970-01-01 00:00:00 +0000
699+++ service/overlay-tracker-mir.cpp 2015-06-12 15:51:31 +0000
700@@ -0,0 +1,153 @@
701+/*
702+ * Copyright © 2015 Canonical Ltd.
703+ *
704+ * This program is free software: you can redistribute it and/or modify it
705+ * under the terms of the GNU General Public License version 3, as published
706+ * by the Free Software Foundation.
707+ *
708+ * This program is distributed in the hope that it will be useful, but
709+ * WITHOUT ANY WARRANTY; without even the implied warranties of
710+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
711+ * PURPOSE. See the GNU General Public License for more details.
712+ *
713+ * You should have received a copy of the GNU General Public License along
714+ * with this program. If not, see <http://www.gnu.org/licenses/>.
715+ *
716+ * Authors:
717+ * Ted Gould <ted.gould@canonical.com>
718+ */
719+
720+#include "overlay-tracker-mir.h"
721+#include <ubuntu-app-launch.h>
722+
723+static const char * HELPER_TYPE = "url-overlay";
724+
725+OverlayTrackerMir::OverlayTrackerMir ()
726+ : thread([this] {
727+ /* Setup Helper Observer */
728+ ubuntu_app_launch_observer_add_helper_stop(untrustedHelperStoppedStatic, HELPER_TYPE, this);
729+ },
730+ [this] {
731+ /* Remove Helper Observer */
732+ ubuntu_app_launch_observer_delete_helper_stop(untrustedHelperStoppedStatic, HELPER_TYPE, this);
733+ })
734+{
735+ mir = std::shared_ptr<MirConnection>([] {
736+ gchar * path = g_build_filename(g_get_user_runtime_dir(), "mir_socket_trusted", NULL);
737+ MirConnection * con = mir_connect_sync(path, "url-dispatcher");
738+ g_free(path);
739+ return con;
740+ }(),
741+ [] (MirConnection * connection) {
742+ if (connection != nullptr)
743+ mir_connection_release(connection);
744+ });
745+
746+ if (!mir) {
747+ throw std::runtime_error("Unable to connect to Mir");
748+ }
749+}
750+
751+/* Enforce a shutdown order, sessions before connection */
752+OverlayTrackerMir::~OverlayTrackerMir ()
753+{
754+ thread.executeOnThread<bool>([this] {
755+ while (!ongoingSessions.empty()) {
756+ removeSession(std::get<2>(*ongoingSessions.begin()).get());
757+ }
758+
759+ return true;
760+ });
761+
762+ mir.reset();
763+}
764+
765+bool
766+OverlayTrackerMir::addOverlay (const char * appid, unsigned long pid, const char * url)
767+{
768+ std::string sappid(appid);
769+ std::string surl(url);
770+
771+ return thread.executeOnThread<bool>([this, sappid, pid, surl] {
772+ g_debug("Setting up over lay for PID %d with '%s'", pid, sappid.c_str());
773+
774+ auto session = std::shared_ptr<MirPromptSession>(
775+ mir_connection_create_prompt_session_sync(mir.get(), pid, sessionStateChangedStatic, this),
776+ [] (MirPromptSession * session) { if (session) mir_prompt_session_release_sync(session); });
777+ if (!session) {
778+ g_critical("Unable to create trusted prompt session for %d with appid '%s'", pid, sappid.c_str());
779+ return false;
780+ }
781+
782+ std::array<const char *, 2> urls { surl.c_str(), nullptr };
783+ auto instance = ubuntu_app_launch_start_session_helper(HELPER_TYPE, session.get(), sappid.c_str(), urls.data());
784+ if (instance == nullptr) {
785+ g_critical("Unable to start helper for %d with appid '%s'", pid, sappid.c_str());
786+ return false;
787+ }
788+
789+ ongoingSessions.emplace(std::make_tuple(sappid, std::string(instance), session));
790+ g_free(instance);
791+ return true;
792+ });
793+}
794+
795+void
796+OverlayTrackerMir::sessionStateChangedStatic (MirPromptSession * session, MirPromptSessionState state, void * user_data)
797+{
798+ reinterpret_cast<OverlayTrackerMir *>(user_data)->sessionStateChanged(session, state);
799+}
800+
801+void
802+OverlayTrackerMir::removeSession (MirPromptSession * session)
803+{
804+ for (auto it = ongoingSessions.begin(); it != ongoingSessions.end(); it++) {
805+ if (std::get<2>(*it).get() == session) {
806+ ubuntu_app_launch_stop_multiple_helper(HELPER_TYPE, std::get<0>(*it).c_str(), std::get<1>(*it).c_str());
807+ ongoingSessions.erase(it);
808+ break;
809+ }
810+ }
811+}
812+
813+void
814+OverlayTrackerMir::sessionStateChanged (MirPromptSession * session, MirPromptSessionState state)
815+{
816+ if (state != mir_prompt_session_state_stopped) {
817+ /* We only care about the stopped state */
818+ return;
819+ }
820+
821+ /* Executing on the Mir thread, which is nice and all, but we
822+ want to get back on our thread */
823+ thread.executeOnThread([this, session]() {
824+ removeSession(session);
825+ });
826+}
827+
828+void
829+OverlayTrackerMir::untrustedHelperStoppedStatic (const gchar * appid, const gchar * instanceid, const gchar * helpertype, gpointer user_data)
830+{
831+ reinterpret_cast<OverlayTrackerMir *>(user_data)->untrustedHelperStopped(appid, instanceid, helpertype);
832+}
833+
834+void
835+OverlayTrackerMir::untrustedHelperStopped(const gchar * appid, const gchar * instanceid, const gchar * helpertype)
836+{
837+ /* This callback will happen on our thread already, we don't need
838+ to proxy it on */
839+ if (g_strcmp0(helpertype, HELPER_TYPE) != 0) {
840+ return;
841+ }
842+
843+ /* Making the code in the loop easier to read by using std::string outside */
844+ std::string sappid(appid);
845+ std::string sinstanceid(instanceid);
846+
847+ for (auto it = ongoingSessions.begin(); it != ongoingSessions.end(); it++) {
848+ if (std::get<0>(*it) == sappid && std::get<1>(*it) == sinstanceid) {
849+ ongoingSessions.erase(it);
850+ break;
851+ }
852+ }
853+}
854
855=== added file 'service/overlay-tracker-mir.h'
856--- service/overlay-tracker-mir.h 1970-01-01 00:00:00 +0000
857+++ service/overlay-tracker-mir.h 2015-06-12 15:51:31 +0000
858@@ -0,0 +1,49 @@
859+/*
860+ * Copyright © 2015 Canonical Ltd.
861+ *
862+ * This program is free software: you can redistribute it and/or modify it
863+ * under the terms of the GNU General Public License version 3, as published
864+ * by the Free Software Foundation.
865+ *
866+ * This program is distributed in the hope that it will be useful, but
867+ * WITHOUT ANY WARRANTY; without even the implied warranties of
868+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
869+ * PURPOSE. See the GNU General Public License for more details.
870+ *
871+ * You should have received a copy of the GNU General Public License along
872+ * with this program. If not, see <http://www.gnu.org/licenses/>.
873+ *
874+ * Authors:
875+ * Ted Gould <ted.gould@canonical.com>
876+ */
877+
878+#pragma once
879+
880+#include <set>
881+
882+#include <mir_toolkit/mir_connection.h>
883+#include <mir_toolkit/mir_prompt_session.h>
884+
885+#include "glib-thread.h"
886+#include "overlay-tracker-iface.h"
887+
888+class OverlayTrackerMir : public OverlayTrackerIface {
889+private:
890+ GLib::ContextThread thread;
891+ std::shared_ptr<MirConnection> mir;
892+ std::set<std::tuple<std::string, std::string, std::shared_ptr<MirPromptSession>>> ongoingSessions;
893+
894+public:
895+ OverlayTrackerMir ();
896+ ~OverlayTrackerMir ();
897+ bool addOverlay (const char * appid, unsigned long pid, const char * url) override;
898+
899+private:
900+ void removeSession (MirPromptSession * session);
901+
902+ static void sessionStateChangedStatic (MirPromptSession * session, MirPromptSessionState state, void * user_data);
903+ void sessionStateChanged (MirPromptSession * session, MirPromptSessionState state);
904+
905+ static void untrustedHelperStoppedStatic (const gchar * appid, const gchar * instanceid, const gchar * helpertype, gpointer user_data);
906+ void untrustedHelperStopped(const gchar * appid, const gchar * instanceid, const gchar * helpertype);
907+};
908
909=== added file 'service/overlay-tracker.cpp'
910--- service/overlay-tracker.cpp 1970-01-01 00:00:00 +0000
911+++ service/overlay-tracker.cpp 2015-06-12 15:51:31 +0000
912@@ -0,0 +1,54 @@
913+/*
914+ * Copyright © 2015 Canonical Ltd.
915+ *
916+ * This program is free software: you can redistribute it and/or modify it
917+ * under the terms of the GNU General Public License version 3, as published
918+ * by the Free Software Foundation.
919+ *
920+ * This program is distributed in the hope that it will be useful, but
921+ * WITHOUT ANY WARRANTY; without even the implied warranties of
922+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
923+ * PURPOSE. See the GNU General Public License for more details.
924+ *
925+ * You should have received a copy of the GNU General Public License along
926+ * with this program. If not, see <http://www.gnu.org/licenses/>.
927+ *
928+ * Authors:
929+ * Ted Gould <ted.gould@canonical.com>
930+ */
931+
932+extern "C" {
933+#include "overlay-tracker.h"
934+}
935+
936+#include "overlay-tracker-iface.h"
937+#include "overlay-tracker-mir.h"
938+
939+OverlayTracker *
940+overlay_tracker_new () {
941+ try {
942+ OverlayTrackerMir * cpptracker = new OverlayTrackerMir();
943+ return reinterpret_cast<OverlayTracker *>(cpptracker);
944+ } catch (...) {
945+ return nullptr;
946+ }
947+}
948+
949+void
950+overlay_tracker_delete (OverlayTracker * tracker) {
951+ g_return_if_fail(tracker != nullptr);
952+
953+ auto cpptracker = reinterpret_cast<OverlayTrackerMir *>(tracker);
954+ delete cpptracker;
955+ return;
956+}
957+
958+gboolean
959+overlay_tracker_add (OverlayTracker * tracker, const char * appid, unsigned long pid, const gchar * url) {
960+ g_return_val_if_fail(tracker != nullptr, FALSE);
961+ g_return_val_if_fail(appid != nullptr, FALSE);
962+ g_return_val_if_fail(pid != 0, FALSE);
963+ g_return_val_if_fail(url != nullptr, FALSE);
964+
965+ return reinterpret_cast<OverlayTrackerIface *>(tracker)->addOverlay(appid, pid, url) ? TRUE : FALSE;
966+}
967
968=== added file 'service/overlay-tracker.h'
969--- service/overlay-tracker.h 1970-01-01 00:00:00 +0000
970+++ service/overlay-tracker.h 2015-06-12 15:51:31 +0000
971@@ -0,0 +1,28 @@
972+/*
973+ * Copyright © 2015 Canonical Ltd.
974+ *
975+ * This program is free software: you can redistribute it and/or modify it
976+ * under the terms of the GNU General Public License version 3, as published
977+ * by the Free Software Foundation.
978+ *
979+ * This program is distributed in the hope that it will be useful, but
980+ * WITHOUT ANY WARRANTY; without even the implied warranties of
981+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
982+ * PURPOSE. See the GNU General Public License for more details.
983+ *
984+ * You should have received a copy of the GNU General Public License along
985+ * with this program. If not, see <http://www.gnu.org/licenses/>.
986+ *
987+ * Authors:
988+ * Ted Gould <ted.gould@canonical.com>
989+ */
990+
991+#pragma once
992+#include <glib.h>
993+
994+typedef struct _OverlayTracker OverlayTracker;
995+
996+OverlayTracker * overlay_tracker_new ();
997+void overlay_tracker_delete (OverlayTracker * tracker);
998+gboolean overlay_tracker_add (OverlayTracker * tracker, const char * appid, unsigned long pid, const char * url);
999+
1000
1001=== modified file 'service/recoverable-problem.c'
1002--- service/recoverable-problem.c 2014-05-27 16:51:53 +0000
1003+++ service/recoverable-problem.c 2015-06-12 15:51:31 +0000
1004@@ -63,6 +63,12 @@
1005 void
1006 report_recoverable_problem (const gchar * signature, GPid report_pid, gboolean wait, const gchar * additional_properties[])
1007 {
1008+ /* Allow disabling for testing, we don't want to report bugs on
1009+ our tests ;-) */
1010+ if (G_UNLIKELY(g_getenv("URL_DISPATCHER_DISABLE_RECOVERABLE_ERROR") != NULL)) {
1011+ return;
1012+ }
1013+
1014 GError * error = NULL;
1015 gint error_stdin = 0;
1016 GPid pid = 0;
1017
1018=== modified file 'service/service.c'
1019--- service/service.c 2014-03-14 19:50:37 +0000
1020+++ service/service.c 2015-06-12 15:51:31 +0000
1021@@ -39,13 +39,15 @@
1022
1023 guint term_source = g_unix_signal_add(SIGTERM, sig_term, mainloop);
1024
1025- dispatcher_init(mainloop);
1026+ OverlayTracker * tracker = overlay_tracker_new();
1027+ dispatcher_init(mainloop, tracker);
1028
1029 /* Run Main */
1030 g_main_loop_run(mainloop);
1031
1032 /* Clean up globals */
1033 dispatcher_shutdown();
1034+ overlay_tracker_delete(tracker);
1035 g_source_remove(term_source);
1036 g_main_loop_unref(mainloop);
1037
1038
1039=== modified file 'service/url-db.c'
1040--- service/url-db.c 2014-10-31 15:51:23 +0000
1041+++ service/url-db.c 2015-06-12 15:51:31 +0000
1042@@ -24,7 +24,7 @@
1043 #define DB_SCHEMA_VERSION "1"
1044
1045 sqlite3 *
1046-url_db_create_database (void)
1047+url_db_create_database ()
1048 {
1049 const gchar * cachedir = g_getenv("URL_DISPATCHER_CACHE_DIR"); /* Mostly for testing */
1050
1051
1052=== modified file 'service/url-db.h'
1053--- service/url-db.h 2014-10-24 18:55:11 +0000
1054+++ service/url-db.h 2015-06-12 15:51:31 +0000
1055@@ -25,7 +25,7 @@
1056
1057 G_BEGIN_DECLS
1058
1059-sqlite3 * url_db_create_database (void);
1060+sqlite3 * url_db_create_database ();
1061 gboolean url_db_get_file_motification_time (sqlite3 * db,
1062 const gchar * filename,
1063 GTimeVal * timeval);
1064
1065=== added file 'service/url-overlay.c'
1066--- service/url-overlay.c 1970-01-01 00:00:00 +0000
1067+++ service/url-overlay.c 2015-06-12 15:51:31 +0000
1068@@ -0,0 +1,174 @@
1069+/*
1070+ * Copyright © 2014 Canonical Ltd.
1071+ *
1072+ * This program is free software: you can redistribute it and/or modify it
1073+ * under the terms of the GNU General Public License version 3, as published
1074+ * by the Free Software Foundation.
1075+ *
1076+ * This program is distributed in the hope that it will be useful, but
1077+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1078+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1079+ * PURPOSE. See the GNU General Public License for more details.
1080+ *
1081+ * You should have received a copy of the GNU General Public License along
1082+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1083+ *
1084+ * Authors:
1085+ * Ted Gould <ted.gould@canonical.com>
1086+ */
1087+
1088+#include <glib.h>
1089+#include <click.h>
1090+#include <ubuntu-app-launch.h>
1091+
1092+#include "recoverable-problem.h"
1093+
1094+gchar *
1095+build_exec (const gchar * appid, const gchar * directory)
1096+{
1097+ gchar * appid_desktop = g_strdup_printf("%s.desktop", appid);
1098+ gchar * desktopfilepath = g_build_filename(directory, appid_desktop, NULL);
1099+ g_free(appid_desktop);
1100+
1101+ if (!g_file_test(desktopfilepath, G_FILE_TEST_EXISTS)) {
1102+ g_free(desktopfilepath);
1103+ return NULL;
1104+ }
1105+
1106+ GError * error = NULL;
1107+ GKeyFile * keyfile = g_key_file_new();
1108+ g_key_file_load_from_file(keyfile, desktopfilepath, G_KEY_FILE_NONE, &error);
1109+
1110+ if (error != NULL) {
1111+ g_error("Unable to read url-overlay desktop file '%s': %s", desktopfilepath, error->message);
1112+ g_free(desktopfilepath);
1113+ g_key_file_free(keyfile);
1114+ g_error_free(error);
1115+ return NULL;
1116+ }
1117+
1118+ g_free(desktopfilepath);
1119+
1120+ if (!g_key_file_has_key(keyfile, "Desktop Entry", "Exec", NULL)) {
1121+ g_error("Desktop file for '%s' in '%s' does not have 'Exec' key", appid, directory);
1122+ g_key_file_free(keyfile);
1123+ return NULL;
1124+ }
1125+
1126+ gchar * exec = g_key_file_get_string(keyfile, "Desktop Entry", "Exec", NULL);
1127+ g_key_file_free(keyfile);
1128+
1129+ return exec;
1130+}
1131+
1132+gchar *
1133+build_dir (const gchar * appid)
1134+{
1135+ GError * error = NULL;
1136+ gchar * package = NULL;
1137+
1138+ /* 'Parse' the App ID */
1139+ if (!ubuntu_app_launch_app_id_parse(appid, &package, NULL, NULL)) {
1140+ g_warning("Unable to parse App ID: '%s'", appid);
1141+ return NULL;
1142+ }
1143+
1144+ /* Check click to find out where the files are */
1145+ ClickDB * db = click_db_new();
1146+
1147+ /* If TEST_CLICK_DB is unset, this reads the system database. */
1148+ click_db_read(db, g_getenv("TEST_CLICK_DB"), &error);
1149+ if (error != NULL) {
1150+ g_warning("Unable to read Click database: %s", error->message);
1151+ g_error_free(error);
1152+ g_free(package);
1153+ return NULL;
1154+ }
1155+
1156+ /* If TEST_CLICK_USER is unset, this uses the current user name. */
1157+ ClickUser * user = click_user_new_for_user(db, g_getenv("TEST_CLICK_USER"), &error);
1158+ if (error != NULL) {
1159+ g_warning("Unable to read Click database: %s", error->message);
1160+ g_error_free(error);
1161+ g_free(package);
1162+ g_object_unref(db);
1163+ return NULL;
1164+ }
1165+
1166+ gchar * pkgdir = click_user_get_path(user, package, &error);
1167+
1168+ g_object_unref(user);
1169+ g_object_unref(db);
1170+ g_free(package);
1171+
1172+ if (error != NULL) {
1173+ g_warning("Unable to get the Click package directory for %s: %s", package, error->message);
1174+ g_error_free(error);
1175+ return NULL;
1176+ }
1177+
1178+ return pkgdir;
1179+}
1180+
1181+int
1182+main (int argc, char * argv[])
1183+{
1184+ /* Build up our exec */
1185+ const gchar * appid = g_getenv("APP_ID");
1186+ if (appid == NULL) {
1187+ report_recoverable_problem("url-dispatcher-url-overlay-no-appid", 0, TRUE, NULL);
1188+ return -1;
1189+ }
1190+
1191+ gchar * exec = NULL;
1192+
1193+ /* Allow for environment override */
1194+ const gchar * envdir = g_getenv("URL_DISPATCHER_OVERLAY_DIR");
1195+ if (G_UNLIKELY(envdir != NULL)) { /* Mostly for testing */
1196+ exec = build_exec(appid, envdir);
1197+ }
1198+
1199+ /* Try the system directory */
1200+ if (exec == NULL) {
1201+ exec = build_exec(appid, OVERLAY_SYSTEM_DIRECTORY);
1202+ }
1203+
1204+ /* If not there look to the user directory (click) */
1205+ if (exec == NULL) {
1206+ gchar * userdir = g_build_filename(g_get_user_cache_dir(), "url-dispatcher", "url-overlays", NULL);
1207+ exec = build_exec(appid, userdir);
1208+ g_free(userdir);
1209+ }
1210+
1211+ if (exec == NULL) {
1212+ const gchar * props[3] = {
1213+ "AppID",
1214+ appid,
1215+ NULL
1216+ };
1217+
1218+ report_recoverable_problem("url-dispatcher-url-overlay-bad-appid", 0, TRUE, props);
1219+ return -1;
1220+ }
1221+
1222+ gchar * dir = build_dir(appid);
1223+ /* NOTE: Dir will be NULL for system apps */
1224+
1225+ GDBusConnection * bus = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
1226+ g_return_val_if_fail(bus != NULL, -1);
1227+
1228+ gboolean sended = ubuntu_app_launch_helper_set_exec(exec, dir);
1229+ g_free(exec);
1230+ g_free(dir);
1231+
1232+ /* Ensuring the messages get on the bus before we quit */
1233+ g_dbus_connection_flush_sync(bus, NULL, NULL);
1234+ g_clear_object(&bus);
1235+
1236+ if (sended) {
1237+ return 0;
1238+ } else {
1239+ g_critical("Unable to send exec to Upstart");
1240+ return -1;
1241+ }
1242+}
1243
1244=== modified file 'tests/CMakeLists.txt'
1245--- tests/CMakeLists.txt 2015-01-09 16:11:45 +0000
1246+++ tests/CMakeLists.txt 2015-06-12 15:51:31 +0000
1247@@ -34,6 +34,23 @@
1248 )
1249
1250 ###########################
1251+# Mir Mock Lib
1252+###########################
1253+
1254+add_library(mir-mock-lib SHARED
1255+ mir-mock.h
1256+ mir-mock.cpp)
1257+
1258+target_link_libraries(mir-mock-lib
1259+ -pthread
1260+ ${GLIB2_LIBRARIES}
1261+)
1262+
1263+set_target_properties(mir-mock-lib PROPERTIES
1264+ OUTPUT_NAME "mir-mock"
1265+)
1266+
1267+###########################
1268 # Dispatcher test
1269 ###########################
1270
1271@@ -55,7 +72,7 @@
1272
1273 include_directories("${CMAKE_BINARY_DIR}/service")
1274
1275-add_executable (app-id-test app-id-test.cc "${CMAKE_SOURCE_DIR}/service/dispatcher.c")
1276+add_executable (app-id-test app-id-test.cc)
1277 target_link_libraries (app-id-test
1278 dispatcher-lib
1279 mock-lib
1280@@ -128,3 +145,32 @@
1281
1282 add_test (url-db-test url-db-test)
1283 add_subdirectory(url_dispatcher_testability)
1284+
1285+###########################
1286+# exec tool test
1287+###########################
1288+
1289+add_executable (exec-tool-test exec-tool-test.cc)
1290+target_link_libraries (exec-tool-test
1291+ gtest
1292+ ${GTEST_LIBS}
1293+ ${GIO2_LIBRARIES}
1294+ ${DBUSTEST_LIBRARIES})
1295+
1296+add_test (exec-tool-test exec-tool-test)
1297+
1298+###########################
1299+# overlay tracker test
1300+###########################
1301+
1302+add_executable (overlay-tracker-test overlay-tracker-test.cpp)
1303+target_link_libraries (overlay-tracker-test
1304+ dispatcher-lib
1305+ mir-mock-lib
1306+ mock-lib
1307+ gtest
1308+ ${GTEST_LIBS}
1309+ ${GIO2_LIBRARIES}
1310+ ${DBUSTEST_LIBRARIES})
1311+
1312+add_test (overlay-tracker-test overlay-tracker-test)
1313
1314=== modified file 'tests/app-id-test.cc'
1315--- tests/app-id-test.cc 2014-10-28 09:49:29 +0000
1316+++ tests/app-id-test.cc 2015-06-12 15:51:31 +0000
1317@@ -21,6 +21,7 @@
1318 #include <gtest/gtest.h>
1319 #include "dispatcher.h"
1320 #include "ubuntu-app-launch-mock.h"
1321+#include "overlay-tracker-mock.h"
1322
1323 class AppIdTest : public ::testing::Test
1324 {
1325@@ -28,6 +29,7 @@
1326 GTestDBus * testbus = nullptr;
1327 GMainLoop * mainloop = nullptr;
1328 gchar * cachedir = nullptr;
1329+ OverlayTrackerMock tracker;
1330
1331 protected:
1332 virtual void SetUp() {
1333@@ -41,7 +43,7 @@
1334 g_test_dbus_up(testbus);
1335
1336 mainloop = g_main_loop_new(nullptr, FALSE);
1337- dispatcher_init(mainloop);
1338+ dispatcher_init(mainloop, reinterpret_cast<OverlayTracker *>(&tracker));
1339
1340 return;
1341 }
1342
1343=== modified file 'tests/dispatcher-test.cc'
1344--- tests/dispatcher-test.cc 2015-01-09 16:11:45 +0000
1345+++ tests/dispatcher-test.cc 2015-06-12 15:51:31 +0000
1346@@ -21,6 +21,7 @@
1347 #include <gtest/gtest.h>
1348 #include "dispatcher.h"
1349 #include "ubuntu-app-launch-mock.h"
1350+#include "overlay-tracker-mock.h"
1351 #include "url-db.h"
1352
1353 class DispatcherTest : public ::testing::Test
1354@@ -31,9 +32,13 @@
1355 gchar * cachedir = nullptr;
1356
1357 protected:
1358+ OverlayTrackerMock tracker;
1359+ GDBusConnection * session = nullptr;
1360+
1361 virtual void SetUp() {
1362 g_setenv("TEST_CLICK_DB", "click-db", TRUE);
1363 g_setenv("TEST_CLICK_USER", "test-user", TRUE);
1364+ g_setenv("URL_DISPATCHER_OVERLAY_DIR", OVERLAY_TEST_DIR, TRUE);
1365
1366 cachedir = g_build_filename(CMAKE_BINARY_DIR, "dispatcher-test-cache", nullptr);
1367 g_setenv("URL_DISPATCHER_CACHE_DIR", cachedir, TRUE);
1368@@ -66,8 +71,10 @@
1369 testbus = g_test_dbus_new(G_TEST_DBUS_NONE);
1370 g_test_dbus_up(testbus);
1371
1372+ session = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
1373+
1374 mainloop = g_main_loop_new(nullptr, FALSE);
1375- dispatcher_init(mainloop);
1376+ dispatcher_init(mainloop, reinterpret_cast<OverlayTracker *>(&tracker));
1377
1378 return;
1379 }
1380@@ -87,6 +94,8 @@
1381 /* let other threads settle */
1382 g_usleep(500000);
1383
1384+ g_clear_object(&session);
1385+
1386 g_test_dbus_down(testbus);
1387 g_object_unref(testbus);
1388
1389@@ -250,3 +259,18 @@
1390
1391 return;
1392 }
1393+
1394+TEST_F(DispatcherTest, OverlayTest)
1395+{
1396+ EXPECT_TRUE(dispatcher_is_overlay("com.test.good_application_1.2.3"));
1397+ EXPECT_FALSE(dispatcher_is_overlay("com.test.bad_application_1.2.3"));
1398+
1399+ EXPECT_TRUE(dispatcher_send_to_overlay ("com.test.good_application_1.2.3", "overlay://ubuntu.com", session, g_dbus_connection_get_unique_name(session)));
1400+
1401+ ASSERT_EQ(1, tracker.addedOverlays.size());
1402+ EXPECT_EQ("com.test.good_application_1.2.3", std::get<0>(tracker.addedOverlays[0]));
1403+ EXPECT_EQ(getpid(), std::get<1>(tracker.addedOverlays[0]));
1404+ EXPECT_EQ("overlay://ubuntu.com", std::get<2>(tracker.addedOverlays[0]));
1405+
1406+ return;
1407+}
1408
1409=== added file 'tests/exec-tool-test.cc'
1410--- tests/exec-tool-test.cc 1970-01-01 00:00:00 +0000
1411+++ tests/exec-tool-test.cc 2015-06-12 15:51:31 +0000
1412@@ -0,0 +1,118 @@
1413+/**
1414+ * Copyright © 2013-2015 Canonical, Ltd.
1415+ *
1416+ * This program is free software: you can redistribute it and/or modify it under
1417+ * the terms of the GNU Lesser General Public License version 3, as published by
1418+ * the Free Software Foundation.
1419+ *
1420+ * This program is distributed in the hope that it will be useful, but WITHOUT
1421+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
1422+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1423+ * Lesser General Public License for more details.
1424+ *
1425+ * You should have received a copy of the GNU Lesser General Public License
1426+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1427+ *
1428+ */
1429+
1430+#include "test-config.h"
1431+
1432+#include <gio/gio.h>
1433+#include <gtest/gtest.h>
1434+#include <libdbustest/dbus-test.h>
1435+
1436+class ExecToolTest : public ::testing::Test
1437+{
1438+ protected:
1439+ DbusTestService * service = nullptr;
1440+ DbusTestDbusMock * mock = nullptr;
1441+ DbusTestDbusMockObject * obj = nullptr;
1442+ GDBusConnection * bus = nullptr;
1443+
1444+ virtual void SetUp() {
1445+ g_setenv("URL_DISPATCHER_DISABLE_RECOVERABLE_ERROR", "1", TRUE);
1446+ g_setenv("URL_DISPATCHER_OVERLAY_DIR", OVERLAY_TEST_DIR, TRUE);
1447+
1448+ g_setenv("TEST_CLICK_DB", "click-db", TRUE);
1449+ g_setenv("TEST_CLICK_USER", "test-user", TRUE);
1450+
1451+ service = dbus_test_service_new(nullptr);
1452+
1453+ /* Upstart Mock */
1454+ mock = dbus_test_dbus_mock_new("com.ubuntu.Upstart");
1455+ obj = dbus_test_dbus_mock_get_object(mock, "/com/ubuntu/Upstart", "com.ubuntu.Upstart0_6", nullptr);
1456+
1457+ dbus_test_dbus_mock_object_add_method(mock, obj,
1458+ "SetEnv",
1459+ G_VARIANT_TYPE("(assb)"),
1460+ NULL,
1461+ "",
1462+ NULL);
1463+
1464+ dbus_test_task_set_name(DBUS_TEST_TASK(mock), "Upstart");
1465+ dbus_test_service_add_task(service, DBUS_TEST_TASK(mock));
1466+
1467+ /* Start your engines! */
1468+ dbus_test_service_start_tasks(service);
1469+
1470+ bus = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr);
1471+ g_dbus_connection_set_exit_on_close(bus, FALSE);
1472+ g_object_add_weak_pointer(G_OBJECT(bus), (gpointer *)&bus);
1473+ return;
1474+ }
1475+
1476+ virtual void TearDown() {
1477+ /* dbustest should probably do this, not sure */
1478+
1479+ g_clear_object(&mock);
1480+ g_clear_object(&service);
1481+
1482+ g_object_unref(bus);
1483+
1484+ unsigned int cleartry = 0;
1485+ while (bus != nullptr && cleartry < 100) {
1486+ pause(100);
1487+ cleartry++;
1488+ }
1489+
1490+ return;
1491+ }
1492+
1493+ static gboolean quit_loop (gpointer ploop) {
1494+ g_main_loop_quit((GMainLoop *)ploop);
1495+ return FALSE;
1496+ }
1497+
1498+ void pause (int time) {
1499+ GMainLoop * loop = g_main_loop_new(nullptr, FALSE);
1500+ g_timeout_add(time, quit_loop, loop);
1501+ g_main_loop_run(loop);
1502+ g_main_loop_unref(loop);
1503+ }
1504+};
1505+
1506+TEST_F(ExecToolTest, SetOverlay)
1507+{
1508+ g_unsetenv("APP_ID");
1509+ gint retval = 0;
1510+ EXPECT_TRUE(g_spawn_command_line_sync(EXEC_TOOL, nullptr, nullptr, &retval, nullptr));
1511+ EXPECT_NE(0, retval);
1512+
1513+ g_setenv("APP_ID", "com.test.good_application_1.2.3", TRUE);
1514+ g_setenv("UPSTART_JOB", "fubar", TRUE);
1515+ EXPECT_TRUE(g_spawn_command_line_sync(EXEC_TOOL, nullptr, nullptr, &retval, nullptr));
1516+ EXPECT_EQ(0, retval);
1517+
1518+ guint len = 0;
1519+ const DbusTestDbusMockCall * calls = dbus_test_dbus_mock_object_get_method_calls(mock, obj, "SetEnv", &len, NULL);
1520+ ASSERT_NE(nullptr, calls);
1521+ ASSERT_EQ(2, len);
1522+
1523+ GVariant * appexecenv = g_variant_get_child_value(calls[0].params, 1);
1524+ EXPECT_STREQ("APP_EXEC=foobar", g_variant_get_string(appexecenv, nullptr));
1525+ g_variant_unref(appexecenv);
1526+
1527+ GVariant * appdirenv = g_variant_get_child_value(calls[1].params, 1);
1528+ EXPECT_STREQ("APP_DIR=" CLICK_DATA_DIR "/.click/users/test-user/com.test.good", g_variant_get_string(appdirenv, nullptr));
1529+ g_variant_unref(appdirenv);
1530+}
1531
1532=== added file 'tests/mir-mock.cpp'
1533--- tests/mir-mock.cpp 1970-01-01 00:00:00 +0000
1534+++ tests/mir-mock.cpp 2015-06-12 15:51:31 +0000
1535@@ -0,0 +1,124 @@
1536+/**
1537+ * Copyright © 2015 Canonical, Ltd.
1538+ *
1539+ * This program is free software: you can redistribute it and/or modify it under
1540+ * the terms of the GNU General Public License version 3, as published by
1541+ * the Free Software Foundation.
1542+ * * This program is distributed in the hope that it will be useful, but WITHOUT
1543+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
1544+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1545+ * General Public License for more details.
1546+ *
1547+ * You should have received a copy of the GNU General Public License
1548+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1549+ *
1550+ */
1551+
1552+#include "mir-mock.h"
1553+
1554+#include <vector>
1555+#include <iostream>
1556+#include <thread>
1557+
1558+MirPromptSession * mir_mock_valid_trust_session = (MirPromptSession *)"In the circle of trust";
1559+static bool valid_trust_connection = true;
1560+static int trusted_fd = 1234;
1561+MirPromptSession * mir_mock_last_released_session = NULL;
1562+pid_t mir_mock_last_trust_pid = 0;
1563+void (*mir_mock_last_trust_func)(MirPromptSession *, MirPromptSessionState, void*data) = NULL;
1564+void * mir_mock_last_trust_data = NULL;
1565+
1566+MirPromptSession *
1567+mir_connection_create_prompt_session_sync(MirConnection *, pid_t pid, void (*func)(MirPromptSession *, MirPromptSessionState, void*data), void * context) {
1568+ mir_mock_last_trust_pid = pid;
1569+ mir_mock_last_trust_func = func;
1570+ mir_mock_last_trust_data = context;
1571+
1572+ if (valid_trust_connection) {
1573+ return mir_mock_valid_trust_session;
1574+ } else {
1575+ return nullptr;
1576+ }
1577+}
1578+
1579+void
1580+mir_prompt_session_release_sync (MirPromptSession * session)
1581+{
1582+ mir_mock_last_released_session = session;
1583+ if (session != mir_mock_valid_trust_session) {
1584+ std::cerr << "Releasing a Mir Trusted Prompt that isn't valid" << std::endl;
1585+ exit(1);
1586+ }
1587+}
1588+
1589+MirWaitHandle *
1590+mir_prompt_session_new_fds_for_prompt_providers (MirPromptSession * session, unsigned int numfds, mir_client_fd_callback cb, void * data) {
1591+ if (session != mir_mock_valid_trust_session) {
1592+ std::cerr << "Releasing a Mir Trusted Prompt that isn't valid" << std::endl;
1593+ exit(1);
1594+ }
1595+
1596+ std::thread * thread = new std::thread([session, numfds, cb, data]() {
1597+ std::vector<int> fdlist(numfds);
1598+
1599+ for (unsigned int i = 0; i < numfds; i++)
1600+ fdlist[i] = trusted_fd;
1601+
1602+ cb(session, numfds, fdlist.data(), data);
1603+ });
1604+
1605+ return reinterpret_cast<MirWaitHandle *>(thread);
1606+}
1607+
1608+void
1609+mir_wait_for (MirWaitHandle * wait)
1610+{
1611+ auto thread = reinterpret_cast<std::thread *>(wait);
1612+
1613+ if (thread->joinable())
1614+ thread->join();
1615+
1616+ delete thread;
1617+}
1618+
1619+static const char * valid_connection_str = "Valid Mir Connection";
1620+static std::pair<std::string, std::string> last_connection;
1621+static bool valid_connection = true;
1622+
1623+void
1624+mir_mock_connect_return_valid (bool valid)
1625+{
1626+ valid_connection = valid;
1627+}
1628+
1629+std::pair<std::string, std::string>
1630+mir_mock_connect_last_connect ()
1631+{
1632+ return last_connection;
1633+}
1634+
1635+MirConnection *
1636+mir_connect_sync (char const * server, char const * appname)
1637+{
1638+ last_connection = std::pair<std::string, std::string>(server, appname);
1639+
1640+ if (valid_connection) {
1641+ return (MirConnection *)(valid_connection_str);
1642+ } else {
1643+ return nullptr;
1644+ }
1645+}
1646+
1647+void
1648+mir_connection_release (MirConnection * con)
1649+{
1650+ if (reinterpret_cast<char *>(con) != valid_connection_str) {
1651+ std::cerr << "Releasing a Mir Connection that isn't valid" << std::endl;
1652+ exit(1);
1653+ }
1654+}
1655+
1656+void mir_mock_set_trusted_fd (int fd)
1657+{
1658+ trusted_fd = fd;
1659+}
1660
1661=== added file 'tests/mir-mock.h'
1662--- tests/mir-mock.h 1970-01-01 00:00:00 +0000
1663+++ tests/mir-mock.h 2015-06-12 15:51:31 +0000
1664@@ -0,0 +1,36 @@
1665+/**
1666+ * Copyright © 2015 Canonical, Ltd.
1667+ *
1668+ * This program is free software: you can redistribute it and/or modify it under
1669+ * the terms of the GNU General Public License version 3, as published by
1670+ * the Free Software Foundation.
1671+ * * This program is distributed in the hope that it will be useful, but WITHOUT
1672+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
1673+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1674+ * General Public License for more details.
1675+ *
1676+ * You should have received a copy of the GNU General Public License
1677+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1678+ *
1679+ */
1680+
1681+#ifndef MIR_MOCK_H
1682+#define MIR_MOCK_H 1
1683+
1684+#include <string>
1685+#include <utility>
1686+
1687+#include <mir_toolkit/mir_connection.h>
1688+#include <mir_toolkit/mir_prompt_session.h>
1689+
1690+void mir_mock_connect_return_valid (bool valid);
1691+std::pair<std::string, std::string> mir_mock_connect_last_connect ();
1692+void mir_mock_set_trusted_fd (int fd);
1693+
1694+extern MirPromptSession * mir_mock_valid_trust_session;
1695+extern MirPromptSession * mir_mock_last_released_session;
1696+extern pid_t mir_mock_last_trust_pid;
1697+extern void (*mir_mock_last_trust_func)(MirPromptSession *, MirPromptSessionState, void*data);
1698+extern void * mir_mock_last_trust_data;
1699+
1700+#endif // MIR_MOCK_H
1701
1702=== added directory 'tests/overlay-dir'
1703=== added file 'tests/overlay-dir/com.test.good_application_1.2.3.desktop'
1704--- tests/overlay-dir/com.test.good_application_1.2.3.desktop 1970-01-01 00:00:00 +0000
1705+++ tests/overlay-dir/com.test.good_application_1.2.3.desktop 2015-06-12 15:51:31 +0000
1706@@ -0,0 +1,2 @@
1707+[Desktop Entry]
1708+Exec=foobar
1709
1710=== added file 'tests/overlay-tracker-mock.h'
1711--- tests/overlay-tracker-mock.h 1970-01-01 00:00:00 +0000
1712+++ tests/overlay-tracker-mock.h 2015-06-12 15:51:31 +0000
1713@@ -0,0 +1,30 @@
1714+/**
1715+ * Copyright © 2015 Canonical, Ltd.
1716+ *
1717+ * This program is free software: you can redistribute it and/or modify it under
1718+ * the terms of the GNU General Public License version 3, as published by
1719+ * the Free Software Foundation.
1720+ * * This program is distributed in the hope that it will be useful, but WITHOUT
1721+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
1722+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1723+ * General Public License for more details.
1724+ *
1725+ * You should have received a copy of the GNU General Public License
1726+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1727+ *
1728+ */
1729+
1730+#pragma once
1731+#include "overlay-tracker-iface.h"
1732+#include <utility>
1733+
1734+class OverlayTrackerMock : public OverlayTrackerIface
1735+{
1736+ public:
1737+ std::vector<std::tuple<std::string, unsigned long, std::string>> addedOverlays;
1738+
1739+ bool addOverlay (const char * appid, unsigned long pid, const char * url) {
1740+ addedOverlays.push_back(std::make_tuple(std::string(appid), pid, std::string(url)));
1741+ return true;
1742+ }
1743+};
1744
1745=== added file 'tests/overlay-tracker-test.cpp'
1746--- tests/overlay-tracker-test.cpp 1970-01-01 00:00:00 +0000
1747+++ tests/overlay-tracker-test.cpp 2015-06-12 15:51:31 +0000
1748@@ -0,0 +1,127 @@
1749+/**
1750+ * Copyright © 2015 Canonical, Ltd.
1751+ *
1752+ * This program is free software: you can redistribute it and/or modify it under
1753+ * the terms of the GNU General Public License version 3, as published by
1754+ * the Free Software Foundation.
1755+ * * This program is distributed in the hope that it will be useful, but WITHOUT
1756+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
1757+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1758+ * General Public License for more details.
1759+ *
1760+ * You should have received a copy of the GNU General Public License
1761+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1762+ *
1763+ */
1764+
1765+#include <random>
1766+
1767+#include "test-config.h"
1768+
1769+#include <gio/gio.h>
1770+#include <gtest/gtest.h>
1771+#include <libdbustest/dbus-test.h>
1772+
1773+#include "overlay-tracker-mir.h"
1774+#include "ubuntu-app-launch-mock.h"
1775+#include "mir-mock.h"
1776+
1777+class OverlayTrackerTest : public ::testing::Test
1778+{
1779+ protected:
1780+ virtual void SetUp() {
1781+ g_setenv("URL_DISPATCHER_DISABLE_RECOVERABLE_ERROR", "1", TRUE);
1782+
1783+ return;
1784+ }
1785+
1786+ virtual void TearDown() {
1787+ return;
1788+ }
1789+
1790+ static gboolean quit_loop (gpointer ploop) {
1791+ g_main_loop_quit((GMainLoop *)ploop);
1792+ return FALSE;
1793+ }
1794+
1795+ void pause (int time) {
1796+ GMainLoop * loop = g_main_loop_new(nullptr, FALSE);
1797+ g_timeout_add(time, quit_loop, loop);
1798+ g_main_loop_run(loop);
1799+ g_main_loop_unref(loop);
1800+ }
1801+};
1802+
1803+TEST_F(OverlayTrackerTest, BasicCreation) {
1804+ auto tracker = new OverlayTrackerMir();
1805+ delete tracker;
1806+}
1807+
1808+TEST_F(OverlayTrackerTest, AddOverlay) {
1809+ auto tracker = new OverlayTrackerMir();
1810+
1811+ auto mirconn = mir_mock_connect_last_connect();
1812+ EXPECT_EQ("mir_socket_trusted", mirconn.first.substr(mirconn.first.size() - 18));
1813+ EXPECT_EQ("url-dispatcher", mirconn.second);
1814+
1815+ EXPECT_TRUE(tracker->addOverlay("app-id", 5, "http://no-name-yet.com"));
1816+
1817+ EXPECT_EQ(5, mir_mock_last_trust_pid);
1818+
1819+ EXPECT_STREQ("url-overlay", ubuntu_app_launch_mock_last_start_session_helper);
1820+ EXPECT_STREQ("app-id", ubuntu_app_launch_mock_last_start_session_appid);
1821+ EXPECT_STREQ("http://no-name-yet.com", ubuntu_app_launch_mock_last_start_session_uris[0]);
1822+
1823+ delete tracker;
1824+
1825+ EXPECT_STREQ("url-overlay", ubuntu_app_launch_mock_last_stop_helper);
1826+ EXPECT_STREQ("app-id", ubuntu_app_launch_mock_last_stop_appid);
1827+ EXPECT_STREQ("instance", ubuntu_app_launch_mock_last_stop_instance);
1828+}
1829+
1830+TEST_F(OverlayTrackerTest, OverlayABunch) {
1831+ OverlayTrackerMir tracker;
1832+ std::uniform_int_distribution<> randpid(1, 32000);
1833+ std::mt19937 rand;
1834+
1835+ /* Testing adding a bunch of overlays, we're using pretty standard
1836+ data structures, but let's make sure we didn't break 'em */
1837+ for (auto name : std::vector<std::string>{"warty", "hoary", "breezy", "dapper", "edgy", "feisty", "gutsy", "hardy", "intrepid", "jaunty", "karmic", "lucid", "maverick", "natty", "oneiric", "precise", "quantal", "raring", "saucy", "trusty", "utopic", "vivid", "wily"}) {
1838+ int pid = randpid(rand);
1839+ tracker.addOverlay(name.c_str(), pid, "http://ubuntu.com/releases");
1840+
1841+ EXPECT_EQ(pid, mir_mock_last_trust_pid);
1842+ EXPECT_EQ(name, ubuntu_app_launch_mock_last_start_session_appid);
1843+ }
1844+}
1845+
1846+TEST_F(OverlayTrackerTest, UALSignalStop) {
1847+ OverlayTrackerMir tracker;
1848+
1849+ /* Call with the overlay before it is set */
1850+ ubuntu_app_launch_mock_observer_helper_stop_func("app-id", "instance", "url-overlay", ubuntu_app_launch_mock_observer_helper_stop_user_data);
1851+
1852+ EXPECT_TRUE(tracker.addOverlay("app-id", 5, "http://no-name-yet.com"));
1853+
1854+ mir_mock_last_released_session = nullptr;
1855+ ubuntu_app_launch_mock_observer_helper_stop_func("app-id", "instance", "url-overlay", ubuntu_app_launch_mock_observer_helper_stop_user_data);
1856+ EXPECT_NE(nullptr, mir_mock_last_released_session);
1857+}
1858+
1859+TEST_F(OverlayTrackerTest, MirSignalStop) {
1860+ OverlayTrackerMir tracker;
1861+
1862+ EXPECT_TRUE(tracker.addOverlay("app-id", 5, "http://no-name-yet.com"));
1863+
1864+ /* Try a badie */
1865+ mir_mock_last_trust_func((MirPromptSession *)1337, mir_prompt_session_state_stopped, mir_mock_last_trust_data);
1866+
1867+ EXPECT_NE(nullptr, mir_mock_last_trust_func);
1868+ mir_mock_last_trust_func(mir_mock_valid_trust_session, mir_prompt_session_state_stopped, mir_mock_last_trust_data);
1869+
1870+ pause(100);
1871+
1872+ EXPECT_STREQ("url-overlay", ubuntu_app_launch_mock_last_stop_helper);
1873+ EXPECT_STREQ("app-id", ubuntu_app_launch_mock_last_stop_appid);
1874+ EXPECT_STREQ("instance", ubuntu_app_launch_mock_last_stop_instance);
1875+}
1876
1877=== modified file 'tests/service-test.cc'
1878--- tests/service-test.cc 2014-10-28 12:46:32 +0000
1879+++ tests/service-test.cc 2015-06-12 15:51:31 +0000
1880@@ -39,6 +39,7 @@
1881 g_setenv("UBUNTU_APP_LAUNCH_USE_SESSION", "1", TRUE);
1882 g_setenv("URL_DISPATCHER_DISABLE_RECOVERABLE_ERROR", "1", TRUE);
1883 g_setenv("XDG_DATA_DIRS", XDG_DATA_DIRS, TRUE);
1884+ g_setenv("LD_PRELOAD", MIR_MOCK_PATH, TRUE);
1885
1886 SetUpDb();
1887
1888@@ -94,10 +95,6 @@
1889 }
1890
1891 virtual void TearDown() {
1892- /* dbustest should probably do this, not sure */
1893- kill(dbus_test_process_get_pid(dispatcher), SIGTERM);
1894- g_usleep(50000);
1895-
1896 g_clear_object(&dispatcher);
1897 g_clear_object(&mock);
1898 g_clear_object(&dashmock);
1899@@ -107,9 +104,7 @@
1900
1901 unsigned int cleartry = 0;
1902 while (bus != nullptr && cleartry < 100) {
1903- g_usleep(100000);
1904- while (g_main_pending())
1905- g_main_iteration(TRUE);
1906+ pause(100);
1907 cleartry++;
1908 }
1909
1910@@ -249,7 +244,7 @@
1911 }
1912
1913 void
1914-focus_signal_cb (GDBusConnection */*connection*/, const gchar */*sender_name*/, const gchar */*object_path*/, const gchar */*interface_name*/, const gchar */*signal_name*/, GVariant */*parameters*/, gpointer user_data)
1915+focus_signal_cb (GDBusConnection * /*connection*/, const gchar * /*sender_name*/, const gchar * /*object_path*/, const gchar * /*interface_name*/, const gchar * /*signal_name*/, GVariant * /*parameters*/, gpointer user_data)
1916 {
1917 guint * focus_count = (guint *)user_data;
1918 *focus_count = *focus_count + 1;
1919
1920=== modified file 'tests/test-config.h.in'
1921--- tests/test-config.h.in 2015-01-09 16:11:45 +0000
1922+++ tests/test-config.h.in 2015-06-12 15:51:31 +0000
1923@@ -10,3 +10,7 @@
1924 #define UPDATE_DIRECTORY_URLS "@CMAKE_CURRENT_SOURCE_DIR@/test-urls-simple"
1925 #define UPDATE_DIRECTORY_VARIED "@CMAKE_CURRENT_SOURCE_DIR@/test-urls-varied"
1926 #define UPDATE_DIRECTORY_INTENT "@CMAKE_CURRENT_SOURCE_DIR@/test-urls-intent"
1927+#define OVERLAY_TEST_DIR "@CMAKE_CURRENT_SOURCE_DIR@/overlay-dir"
1928+#define EXEC_TOOL "@CMAKE_BINARY_DIR@/service/exec-tool"
1929+#define MIR_MOCK_PATH "@CMAKE_CURRENT_BINARY_DIR@/libmir-mock.so"
1930+#define CLICK_DATA_DIR "@CMAKE_CURRENT_SOURCE_DIR@/click-data"
1931
1932=== modified file 'tests/ubuntu-app-launch-mock.c'
1933--- tests/ubuntu-app-launch-mock.c 2014-05-26 12:56:47 +0000
1934+++ tests/ubuntu-app-launch-mock.c 2015-06-12 15:51:31 +0000
1935@@ -29,7 +29,7 @@
1936 }
1937
1938 void
1939-ubuntu_app_launch_mock_clear_last_app_id (void)
1940+ubuntu_app_launch_mock_clear_last_app_id ()
1941 {
1942 g_free(last_appid);
1943 last_appid = NULL;
1944@@ -37,7 +37,72 @@
1945 }
1946
1947 gchar *
1948-ubuntu_app_launch_mock_get_last_app_id (void)
1949+ubuntu_app_launch_mock_get_last_app_id ()
1950 {
1951 return last_appid;
1952 }
1953+
1954+UbuntuAppLaunchHelperObserver ubuntu_app_launch_mock_observer_helper_stop_func = NULL;
1955+gchar * ubuntu_app_launch_mock_observer_helper_stop_type = NULL;
1956+void * ubuntu_app_launch_mock_observer_helper_stop_user_data = NULL;
1957+
1958+gboolean
1959+ubuntu_app_launch_observer_add_helper_stop (UbuntuAppLaunchHelperObserver func, const gchar * type, gpointer user_data)
1960+{
1961+ ubuntu_app_launch_mock_observer_helper_stop_func = func;
1962+ ubuntu_app_launch_mock_observer_helper_stop_type = g_strdup(type);
1963+ ubuntu_app_launch_mock_observer_helper_stop_user_data = user_data;
1964+
1965+ return TRUE;
1966+}
1967+
1968+gboolean
1969+ubuntu_app_launch_observer_delete_helper_stop (UbuntuAppLaunchHelperObserver func, const gchar * type, gpointer user_data)
1970+{
1971+ gboolean same = ubuntu_app_launch_mock_observer_helper_stop_func == func &&
1972+ g_strcmp0(ubuntu_app_launch_mock_observer_helper_stop_type, type) == 0 &&
1973+ ubuntu_app_launch_mock_observer_helper_stop_user_data == user_data;
1974+
1975+ ubuntu_app_launch_mock_observer_helper_stop_func = NULL;
1976+ g_clear_pointer(&ubuntu_app_launch_mock_observer_helper_stop_type, g_free);
1977+ ubuntu_app_launch_mock_observer_helper_stop_user_data = NULL;
1978+
1979+ return same;
1980+}
1981+
1982+gchar * ubuntu_app_launch_mock_last_start_session_helper = NULL;
1983+MirPromptSession * ubuntu_app_launch_mock_last_start_session_session = NULL;
1984+gchar * ubuntu_app_launch_mock_last_start_session_appid = NULL;
1985+gchar ** ubuntu_app_launch_mock_last_start_session_uris = NULL;
1986+
1987+gchar *
1988+ubuntu_app_launch_start_session_helper (const gchar * type, MirPromptSession * session, const gchar * appid, const gchar * const * uris)
1989+{
1990+ g_clear_pointer(&ubuntu_app_launch_mock_last_start_session_helper, g_free);
1991+ g_clear_pointer(&ubuntu_app_launch_mock_last_start_session_appid, g_free);
1992+ g_clear_pointer(&ubuntu_app_launch_mock_last_start_session_uris, g_strfreev);
1993+
1994+ ubuntu_app_launch_mock_last_start_session_helper = g_strdup(type);
1995+ ubuntu_app_launch_mock_last_start_session_session = session;
1996+ ubuntu_app_launch_mock_last_start_session_appid = g_strdup(appid);
1997+ ubuntu_app_launch_mock_last_start_session_uris = g_strdupv((gchar **)uris);
1998+
1999+ return g_strdup("instance");
2000+}
2001+
2002+gchar * ubuntu_app_launch_mock_last_stop_helper = NULL;
2003+gchar * ubuntu_app_launch_mock_last_stop_appid = NULL;
2004+gchar * ubuntu_app_launch_mock_last_stop_instance = NULL;
2005+
2006+gboolean
2007+ubuntu_app_launch_stop_multiple_helper (const gchar * helper_type, const gchar * appid, const gchar * instance) {
2008+ g_clear_pointer(&ubuntu_app_launch_mock_last_stop_helper, g_free);
2009+ g_clear_pointer(&ubuntu_app_launch_mock_last_stop_appid, g_free);
2010+ g_clear_pointer(&ubuntu_app_launch_mock_last_stop_instance, g_free);
2011+
2012+ ubuntu_app_launch_mock_last_stop_helper = g_strdup(helper_type);
2013+ ubuntu_app_launch_mock_last_stop_appid = g_strdup(appid);
2014+ ubuntu_app_launch_mock_last_stop_instance = g_strdup(instance);
2015+
2016+ return TRUE;
2017+}
2018
2019=== modified file 'tests/ubuntu-app-launch-mock.h'
2020--- tests/ubuntu-app-launch-mock.h 2014-10-24 18:55:11 +0000
2021+++ tests/ubuntu-app-launch-mock.h 2015-06-12 15:51:31 +0000
2022@@ -19,11 +19,23 @@
2023 #define UPSTART_APP_LAUNCH_MOCK 1
2024
2025 #include <glib.h>
2026+#include <ubuntu-app-launch.h>
2027
2028 G_BEGIN_DECLS
2029
2030-void ubuntu_app_launch_mock_clear_last_app_id (void);
2031-gchar * ubuntu_app_launch_mock_get_last_app_id (void);
2032+void ubuntu_app_launch_mock_clear_last_app_id ();
2033+gchar * ubuntu_app_launch_mock_get_last_app_id ();
2034+
2035+extern UbuntuAppLaunchHelperObserver ubuntu_app_launch_mock_observer_helper_stop_func;
2036+extern gchar * ubuntu_app_launch_mock_observer_helper_stop_type;
2037+extern void * ubuntu_app_launch_mock_observer_helper_stop_user_data;
2038+extern gchar * ubuntu_app_launch_mock_last_start_session_helper;
2039+extern MirPromptSession * ubuntu_app_launch_mock_last_start_session_session;
2040+extern gchar * ubuntu_app_launch_mock_last_start_session_appid;
2041+extern gchar ** ubuntu_app_launch_mock_last_start_session_uris;
2042+extern gchar * ubuntu_app_launch_mock_last_stop_helper;
2043+extern gchar * ubuntu_app_launch_mock_last_stop_appid;
2044+extern gchar * ubuntu_app_launch_mock_last_stop_instance;
2045
2046 G_END_DECLS
2047

Subscribers

People subscribed via source and target branches