Merge lp:~ted/ubuntu-app-launch/snappy-backend into lp:ubuntu-app-launch/16.10
- snappy-backend
- Merge into trunk.16.10
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Ted Gould | ||||
Approved revision: | 383 | ||||
Merged at revision: | 253 | ||||
Proposed branch: | lp:~ted/ubuntu-app-launch/snappy-backend | ||||
Merge into: | lp:ubuntu-app-launch/16.10 | ||||
Prerequisite: | lp:~ted/ubuntu-app-launch/snappy-backend-no-snap | ||||
Diff against target: |
2609 lines (+2102/-13) 28 files modified
.bzrignore (+1/-0) CMakeLists.txt (+3/-0) application-failed.c (+2/-1) debian/control (+1/-0) docs/index.rst (+30/-0) libubuntu-app-launch/CMakeLists.txt (+12/-0) libubuntu-app-launch/appid.h (+4/-1) libubuntu-app-launch/application-impl-legacy.cpp (+13/-0) libubuntu-app-launch/application-impl-snap.cpp (+459/-0) libubuntu-app-launch/application-impl-snap.h (+98/-0) libubuntu-app-launch/application.cpp (+14/-0) libubuntu-app-launch/registry-impl.h (+9/-0) libubuntu-app-launch/registry.cpp (+8/-0) libubuntu-app-launch/snapd-info.cpp (+471/-0) libubuntu-app-launch/snapd-info.h (+76/-0) libubuntu-app-launch/ubuntu-app-launch.cpp (+3/-0) tests/CMakeLists.txt (+18/-0) tests/exec-util-test.cc (+1/-0) tests/failure-test.cc (+17/-9) tests/libual-cpp-test.cc (+199/-1) tests/libual-test.cc (+2/-0) tests/list-apps.cpp (+79/-0) tests/snapd-info-test.cpp (+150/-0) tests/snapd-mock.h (+384/-0) upstart-jobs/CMakeLists.txt (+8/-0) upstart-jobs/application-failed.conf.in (+1/-1) upstart-jobs/application-logrotate.conf (+1/-0) upstart-jobs/application-snap.conf.in (+38/-0) |
||||
To merge this branch: | bzr merge lp:~ted/ubuntu-app-launch/snappy-backend | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Charles Kerr (community) | Approve | ||
unity-api-1-bot | continuous-integration | Approve | |
Review via email:
|
This proposal supersedes a proposal from 2016-07-25.
Commit message
Support launching applications installed as snaps
Description of the change
Adds a new Application Implementation for Snaps. This also includes the snap-info object which is used to connect to snapd. There are also several adjustments to shared classes to make sure they take the new backend into account.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
unity-api-1-bot (unity-api-1-bot) wrote : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
unity-api-1-bot (unity-api-1-bot) wrote : | # |
FAILED: Continuous integration, rev:347
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
Click here to trigger a rebuild:
https:/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
unity-api-1-bot (unity-api-1-bot) wrote : | # |
FAILED: Continuous integration, rev:348
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
Click here to trigger a rebuild:
https:/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
unity-api-1-bot (unity-api-1-bot) wrote : | # |
FAILED: Continuous integration, rev:350
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
Click here to trigger a rebuild:
https:/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
unity-api-1-bot (unity-api-1-bot) wrote : | # |
FAILED: Continuous integration, rev:354
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
Click here to trigger a rebuild:
https:/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Charles Kerr (charlesk) wrote : | # |
eod reached, review comments part 1 inline
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ted Gould (ted) wrote : | # |
On Wed, 2016-08-24 at 23:35 +0000, Charles Kerr wrote:
> > Â gchar * appid = g_strdup(instance);
> > - if (g_strcmp0(job, "application-
> > + if (g_strcmp0(job, "application-
> > + || g_strcmp0(job, "application-snap") ==
> > 0) {
> > Â gchar * lasthyphenstanding = g_strrstr(appid, "-
> > ");
> (not-even-
The answer is that everything else was g_, so went with g_.
> > +/** Path that snapd puts desktop files, we don't want to read
> > those directly
> > +Â Â Â Â in the Legacy backend. We want to use the snap backend. */
> > +const std::string snappyDesktopPa
> This is interesting, I haven't seen this before. Usually when I see a
> file-private variable it's placed inside an unnamed namespace to
> preserve anonymity, eg
>
> namespace
> {
> const std::string snappyDesktopPa
> }
>
> but this variable here, even though not declared in any header, is
> put here inside an ubuntu:
>
> Not a blocker, but I'd like to understand this more, what are the
> advantages/
> namespace advertised in application-
So I've been trying the other way, trying to get someone to say why
those should be in the anonymous namespace. It seems to me, if it's
related and used by the object it should be in the same namespace as
the object. But I haven't seen any rationale other than "it makes the
symbol name smaller" which seems like a premature optimization at best.
> > +Â Â Â Â if (std::equal(
> > snappyDesktopPa
> (bikeshed) g_str_has_prefix() would be clearer
Eh, yeah, trying to keep C++ techniques with C++ strings and g_
techniques with gchar strings. I do hate that the C++ string class
doesn't have a "bool prefix(
that it seems every other string library has. :-/
> > +Â Â Â Â /** Return the xMirEnable value based on whether the interface
> > is
> > +Â Â Â Â Â Â Â Â in the list of interfaces using XMir */
> > +Â Â Â Â XMirEnable xMirEnable() override
> > +Â Â Â Â {
> > +Â Â Â Â Â Â Â Â if (XMIR_INTERFACE
> > XMIR_INTERFACES
> > +Â Â Â Â Â Â Â Â {
> > +Â Â Â Â Â Â Â Â Â Â Â Â return XMirEnable:
> I should have called this out in snappy-
> I don't see what type-tagging a bool accomplishes. How does this
> improve on just using a bool?
Well, I think it is as useful as type-tagging any variable. Just to
keep the type in the code and say when you're using it. But this is
internal, and so that's not as big a reason. I did it here just to make
it feel like the other variables and have the same semantics of usage.
> > +Â Â Â Â /** Figures out the exec line for a snappy command. We're not
> > using
> > +Â Â Â Â Â Â Â Â the Exec in the desktop file exactly, but assuming that it
> > is kinda
> > +Â Â Â Â Â Â Â Â what we want to be run. So we're replacing that with the
> > script, which
> > +Â Â Â Â Â Â Â Â we have to use as we can't get the command that is in the
> > snap
> > +Â Â Â Â Â Â Â Â metadata as Snapd won't give it to us. So we're parsing
> > the Exec line
> > +Â Â Â Â Â Â Â Â and replaci...
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
unity-api-1-bot (unity-api-1-bot) wrote : | # |
FAILED: Continuous integration, rev:368
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
Click here to trigger a rebuild:
https:/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Charles Kerr (charlesk) wrote : | # |
Some more comments
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
unity-api-1-bot (unity-api-1-bot) wrote : | # |
FAILED: Continuous integration, rev:369
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
Click here to trigger a rebuild:
https:/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
unity-api-1-bot (unity-api-1-bot) wrote : | # |
FAILED: Continuous integration, rev:370
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
Click here to trigger a rebuild:
https:/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
unity-api-1-bot (unity-api-1-bot) wrote : | # |
FAILED: Continuous integration, rev:371
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
Click here to trigger a rebuild:
https:/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
unity-api-1-bot (unity-api-1-bot) wrote : | # |
FAILED: Continuous integration, rev:372
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
Click here to trigger a rebuild:
https:/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
unity-api-1-bot (unity-api-1-bot) wrote : | # |
PASSED: Continuous integration, rev:373
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild:
https:/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ted Gould (ted) wrote : | # |
On Thu, 2016-08-25 at 21:55 +0000, Charles Kerr wrote:
> > +Â Â Â Â Â Â Â Â /******
> > +        /* Validation of the object we got        */
> > +Â Â Â Â Â Â Â Â /******
> > +Â Â Â Â Â Â Â Â for (auto member : {"name", "status", "revision", "type",
> > "version", "apps"})
> auto& :-)
Not sure it helps here since the auto resolves to "const char *" it
would just be copying the pointer. But consistency is good.
r374
> > +Â Â Â Â Â Â Â Â {
> > +Â Â Â Â Â Â Â Â Â Â Â Â if (!json_
> > +Â Â Â Â Â Â Â Â Â Â Â Â {
> > +Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â throw std::runtime_
> > '" + std::string(member) + "'");
> > +Â Â Â Â Â Â Â Â Â Â Â Â }
> > +Â Â Â Â Â Â Â Â }
> > +
> > +Â Â Â Â Â Â Â Â std::string namestr =
> > json_object_
> nullptr guard, here and below for 'status', 'type', 'version',
> 'revision', 'name'
r375
> > +static size_t snapd_writefunc
> > nmemb, void *userdata)
> > +{
> > +Â Â Â Â unsigned int i;
> > +Â Â Â Â std::vector<char> *data = static_
> > *>(userdata);
> redundant type info, suggest auto on lhs
r376
> >
> > +Â Â Â Â data->reserve(
> > */
> > +Â Â Â Â for (i = 0; i < size * nmemb; i++)
> > +Â Â Â Â {
> > +Â Â Â Â Â Â Â Â data->push_
> > +Â Â Â Â }
> I'd replace 1033-1037 with "data->
> ptr+(size*nmemb));"
r377
> > +Â Â Â Â if (error != nullptr)
> > +Â Â Â Â {
> > +Â Â Â Â Â Â Â Â g_warning("Can not parse! %s", error->message);
> > +Â Â Â Â Â Â Â Â g_error_
> > +Â Â Â Â Â Â Â Â throw std::runtime_
> marginally better if this contained error->message
r378
> > +Â Â Â Â std::string statusstr = json_object_
> > "status");
> nullptr guard here and "type" below
r379
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
unity-api-1-bot (unity-api-1-bot) wrote : | # |
PASSED: Continuous integration, rev:380
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild:
https:/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
unity-api-1-bot (unity-api-1-bot) wrote : | # |
FAILED: Continuous integration, rev:381
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild:
https:/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
unity-api-1-bot (unity-api-1-bot) wrote : | # |
PASSED: Continuous integration, rev:382
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild:
https:/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Charles Kerr (charlesk) wrote : | # |
Thanks for the fixes!
Preview Diff
1 | === modified file '.bzrignore' |
2 | --- .bzrignore 2015-12-10 21:01:26 +0000 |
3 | +++ .bzrignore 2016-09-13 17:38:18 +0000 |
4 | @@ -11,3 +11,4 @@ |
5 | libubuntu-app-launch/proxy-socket-demangler.c |
6 | libubuntu-app-launch/proxy-socket-demangler.h |
7 | libubuntu-app-launch/ubuntu-app-launch-2.pc |
8 | +application-snap.conf |
9 | |
10 | === modified file 'CMakeLists.txt' |
11 | --- CMakeLists.txt 2016-08-17 15:24:23 +0000 |
12 | +++ CMakeLists.txt 2016-09-13 17:38:18 +0000 |
13 | @@ -102,6 +102,9 @@ |
14 | pkg_check_modules(LIBERTINE libertine) |
15 | include_directories(${LIBERTINE_INCLUDE_DIRS}) |
16 | |
17 | +pkg_check_modules(CURL libcurl>=7.47) |
18 | +include_directories(${CURL_INCLUDE_DIRS}) |
19 | + |
20 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}) |
21 | |
22 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC") |
23 | |
24 | === modified file 'application-failed.c' |
25 | --- application-failed.c 2014-04-30 19:51:19 +0000 |
26 | +++ application-failed.c 2016-09-13 17:38:18 +0000 |
27 | @@ -35,7 +35,8 @@ |
28 | } |
29 | |
30 | gchar * appid = g_strdup(instance); |
31 | - if (g_strcmp0(job, "application-legacy") == 0) { |
32 | + if (g_strcmp0(job, "application-legacy") == 0 |
33 | + || g_strcmp0(job, "application-snap") == 0) { |
34 | gchar * lasthyphenstanding = g_strrstr(appid, "-"); |
35 | if (lasthyphenstanding != NULL) { |
36 | lasthyphenstanding[0] = '\0'; |
37 | |
38 | === modified file 'debian/control' |
39 | --- debian/control 2016-08-17 15:24:23 +0000 |
40 | +++ debian/control 2016-09-13 17:38:18 +0000 |
41 | @@ -11,6 +11,7 @@ |
42 | debhelper (>= 9), |
43 | libcgmanager-dev, |
44 | libclick-0.4-dev, |
45 | + libcurl4-dev | libcurl4-gnutls-dev, |
46 | libdbus-1-dev, |
47 | libdbustest1-dev (>= 14.04.0), |
48 | libgirepository1.0-dev, |
49 | |
50 | === modified file 'docs/index.rst' |
51 | --- docs/index.rst 2016-09-13 17:38:17 +0000 |
52 | +++ docs/index.rst 2016-09-13 17:38:18 +0000 |
53 | @@ -96,6 +96,16 @@ |
54 | :private-members: |
55 | :undoc-members: |
56 | |
57 | +Application Implementation Snappy |
58 | +--------------------------------- |
59 | + |
60 | +.. doxygenclass:: ubuntu::app_launch::app_impls::Snap |
61 | + :project: libubuntu-app-launch |
62 | + :members: |
63 | + :protected-members: |
64 | + :private-members: |
65 | + :undoc-members: |
66 | + |
67 | Application Info Desktop |
68 | ------------------------ |
69 | |
70 | @@ -106,6 +116,16 @@ |
71 | :private-members: |
72 | :undoc-members: |
73 | |
74 | +Application Info Snap |
75 | +------------------------ |
76 | + |
77 | +.. doxygenclass:: ubuntu::app_launch::app_impls::SnapInfo |
78 | + :project: libubuntu-app-launch |
79 | + :members: |
80 | + :protected-members: |
81 | + :private-members: |
82 | + :undoc-members: |
83 | + |
84 | Application Icon Finder |
85 | ------------------------ |
86 | |
87 | @@ -136,6 +156,16 @@ |
88 | :private-members: |
89 | :undoc-members: |
90 | |
91 | +Snapd Info |
92 | +---------- |
93 | + |
94 | +.. doxygenclass:: ubuntu::app_launch::snapd::Info |
95 | + :project: libubuntu-app-launch |
96 | + :members: |
97 | + :protected-members: |
98 | + :private-members: |
99 | + :undoc-members: |
100 | + |
101 | Type Tagger |
102 | ----------- |
103 | |
104 | |
105 | === modified file 'libubuntu-app-launch/CMakeLists.txt' |
106 | --- libubuntu-app-launch/CMakeLists.txt 2016-09-13 17:38:17 +0000 |
107 | +++ libubuntu-app-launch/CMakeLists.txt 2016-09-13 17:38:18 +0000 |
108 | @@ -61,6 +61,16 @@ |
109 | app-info.c |
110 | ) |
111 | |
112 | +if(CURL_FOUND) |
113 | +add_definitions ( -DENABLE_SNAPPY=1 ) |
114 | +list(APPEND LAUNCHER_CPP_SOURCES |
115 | +application-impl-snap.h |
116 | +application-impl-snap.cpp |
117 | +snapd-info.h |
118 | +snapd-info.cpp |
119 | +) |
120 | +endif() |
121 | + |
122 | add_custom_target(format |
123 | COMMAND clang-format -i -style=file ${LAUNCHER_CPP_HEADERS} ${LAUNCHER_CPP_SOURCES} |
124 | ) |
125 | @@ -83,6 +93,7 @@ |
126 | ${ZEITGEIST_LIBRARIES} |
127 | ${MIR_LIBRARIES} |
128 | ${LIBERTINE_LIBRARIES} |
129 | + ${CURL_LIBRARIES} |
130 | -lpthread |
131 | helpers |
132 | -Wl,--no-undefined |
133 | @@ -109,6 +120,7 @@ |
134 | ${ZEITGEIST_LIBRARIES} |
135 | ${MIR_LIBRARIES} |
136 | ${LIBERTINE_LIBRARIES} |
137 | + ${CURL_LIBRARIES} |
138 | -lpthread |
139 | helpers |
140 | -Wl,--no-undefined |
141 | |
142 | === modified file 'libubuntu-app-launch/appid.h' |
143 | --- libubuntu-app-launch/appid.h 2016-09-13 17:38:17 +0000 |
144 | +++ libubuntu-app-launch/appid.h 2016-09-13 17:38:18 +0000 |
145 | @@ -71,7 +71,10 @@ |
146 | under the "hooks" key in the JSON manifest. */ |
147 | AppName appname; |
148 | /** Version of the package that is installed. This is always resolved when |
149 | - creating the struct. */ |
150 | + creating the struct. |
151 | + |
152 | + \note For snaps this is actually the 'revision' instead of the version |
153 | + since that is unique where 'version' is not. */ |
154 | Version version; |
155 | |
156 | /** Turn the structure into a string. This is required for many older C based |
157 | |
158 | === modified file 'libubuntu-app-launch/application-impl-legacy.cpp' |
159 | --- libubuntu-app-launch/application-impl-legacy.cpp 2016-09-13 17:38:17 +0000 |
160 | +++ libubuntu-app-launch/application-impl-legacy.cpp 2016-09-13 17:38:18 +0000 |
161 | @@ -30,8 +30,16 @@ |
162 | namespace app_impls |
163 | { |
164 | |
165 | +/** Path that snapd puts desktop files, we don't want to read those directly |
166 | + in the Legacy backend. We want to use the snap backend. */ |
167 | +const std::string snappyDesktopPath{"/var/lib/snapd"}; |
168 | + |
169 | +/*********************************** |
170 | + Prototypes |
171 | + ***********************************/ |
172 | std::tuple<std::string, std::shared_ptr<GKeyFile>, std::string> keyfileForApp(const AppID::AppName& name); |
173 | |
174 | +/** Helper function to put on shared_ptr's for keyfiles */ |
175 | void clear_keyfile(GKeyFile* keyfile) |
176 | { |
177 | if (keyfile != nullptr) |
178 | @@ -53,6 +61,11 @@ |
179 | { |
180 | throw std::runtime_error{"Unable to find keyfile for legacy application: " + appname.value()}; |
181 | } |
182 | + |
183 | + if (std::equal(snappyDesktopPath.begin(), snappyDesktopPath.end(), _basedir.begin())) |
184 | + { |
185 | + throw std::runtime_error{"Looking like a legacy app, but should be a Snap: " + appname.value()}; |
186 | + } |
187 | } |
188 | |
189 | std::tuple<std::string, std::shared_ptr<GKeyFile>, std::string> keyfileForApp(const AppID::AppName& name) |
190 | |
191 | === added file 'libubuntu-app-launch/application-impl-snap.cpp' |
192 | --- libubuntu-app-launch/application-impl-snap.cpp 1970-01-01 00:00:00 +0000 |
193 | +++ libubuntu-app-launch/application-impl-snap.cpp 2016-09-13 17:38:18 +0000 |
194 | @@ -0,0 +1,459 @@ |
195 | +/* |
196 | + * Copyright © 2016 Canonical Ltd. |
197 | + * |
198 | + * This program is free software: you can redistribute it and/or modify it |
199 | + * under the terms of the GNU General Public License version 3, as published |
200 | + * by the Free Software Foundation. |
201 | + * |
202 | + * This program is distributed in the hope that it will be useful, but |
203 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
204 | + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
205 | + * PURPOSE. See the GNU General Public License for more details. |
206 | + * |
207 | + * You should have received a copy of the GNU General Public License along |
208 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
209 | + * |
210 | + * Authors: |
211 | + * Ted Gould <ted.gould@canonical.com> |
212 | + */ |
213 | + |
214 | +#include <regex> |
215 | + |
216 | +#include "application-impl-snap.h" |
217 | +#include "application-info-desktop.h" |
218 | +#include "registry-impl.h" |
219 | + |
220 | +namespace ubuntu |
221 | +{ |
222 | +namespace app_launch |
223 | +{ |
224 | +namespace app_impls |
225 | +{ |
226 | + |
227 | +/************************ |
228 | + ** Interface Lists |
229 | + ************************/ |
230 | + |
231 | +/** All the interfaces that we support running applications with */ |
232 | +const std::set<std::string> SUPPORTED_INTERFACES{"unity8", "unity7", "x11"}; |
233 | +/** All the interfaces that we run XMir for by default */ |
234 | +const std::set<std::string> XMIR_INTERFACES{"unity7", "x11"}; |
235 | +/** All the interfaces that we tell Unity support lifecycle */ |
236 | +const std::set<std::string> LIFECYCLE_INTERFACES{"unity8"}; |
237 | +/** Snappy has more restrictive appnames than everyone else */ |
238 | +const std::regex appnameRegex{"^[a-zA-Z0-9](?:-?[a-zA-Z0-9])*$"}; |
239 | + |
240 | +/************************ |
241 | + ** Info support |
242 | + ************************/ |
243 | + |
244 | +/** Subclassing the desktop info object so that we can override a couple |
245 | + of properties with interface definitions. This may grow as we add more |
246 | + fields to the desktop spec that come from Snappy interfaces. */ |
247 | +class SnapInfo : public app_info::Desktop |
248 | +{ |
249 | + /** The core interface for this snap */ |
250 | + std::string interface_; |
251 | + /** AppID of snap */ |
252 | + AppID appId_; |
253 | + |
254 | +public: |
255 | + SnapInfo(const AppID& appid, |
256 | + const std::shared_ptr<Registry>& registry, |
257 | + const std::string& interface, |
258 | + const std::string& snapDir) |
259 | + : Desktop( |
260 | + [appid, snapDir]() -> std::shared_ptr<GKeyFile> { |
261 | + /* This is a function to get the keyfile out of the snap using |
262 | + the paths that snappy places things inside the dir. */ |
263 | + std::string path = snapDir + "/meta/gui/" + appid.appname.value() + ".desktop"; |
264 | + std::shared_ptr<GKeyFile> keyfile(g_key_file_new(), g_key_file_free); |
265 | + GError* error = nullptr; |
266 | + g_key_file_load_from_file(keyfile.get(), path.c_str(), G_KEY_FILE_NONE, &error); |
267 | + if (error != nullptr) |
268 | + { |
269 | + auto perror = std::shared_ptr<GError>(error, g_error_free); |
270 | + throw std::runtime_error("Unable to find keyfile for '" + std::string(appid) + "' at '" + path + |
271 | + "' because: " + perror.get()->message); |
272 | + } |
273 | + |
274 | + return keyfile; |
275 | + }(), |
276 | + snapDir, |
277 | + app_info::DesktopFlags::NONE, |
278 | + registry) |
279 | + , interface_(interface) |
280 | + , appId_(appid) |
281 | + { |
282 | + } |
283 | + |
284 | + /** Return the xMirEnable value based on whether the interface is |
285 | + in the list of interfaces using XMir */ |
286 | + XMirEnable xMirEnable() override |
287 | + { |
288 | + if (XMIR_INTERFACES.find(interface_) != XMIR_INTERFACES.end()) |
289 | + { |
290 | + return XMirEnable::from_raw(true); |
291 | + } |
292 | + else |
293 | + { |
294 | + return XMirEnable::from_raw(false); |
295 | + } |
296 | + } |
297 | + |
298 | + /** Return the xMirEnable value based on whether the interface is |
299 | + in the list of interfaces supporting the lifecycle */ |
300 | + UbuntuLifecycle supportsUbuntuLifecycle() override |
301 | + { |
302 | + if (LIFECYCLE_INTERFACES.find(interface_) != LIFECYCLE_INTERFACES.end()) |
303 | + { |
304 | + return UbuntuLifecycle::from_raw(true); |
305 | + } |
306 | + else |
307 | + { |
308 | + return UbuntuLifecycle::from_raw(false); |
309 | + } |
310 | + } |
311 | + |
312 | + /** Figures out the exec line for a snappy command. We're not using |
313 | + the Exec in the desktop file exactly, but assuming that it is kinda |
314 | + what we want to be run. So we're replacing that with the script, which |
315 | + we have to use as we can't get the command that is in the snap |
316 | + metadata as Snapd won't give it to us. So we're parsing the Exec line |
317 | + and replacing the first entry. Then putting it back together again. */ |
318 | + Exec execLine() override |
319 | + { |
320 | + std::string keyfile = _exec.value(); |
321 | + gchar** parsed = nullptr; |
322 | + GError* error = nullptr; |
323 | + |
324 | + g_shell_parse_argv(keyfile.c_str(), nullptr, &parsed, &error); |
325 | + |
326 | + if (error != nullptr) |
327 | + { |
328 | + g_warning("Unable to parse exec line '%s': %s", keyfile.c_str(), error->message); |
329 | + g_error_free(error); |
330 | + return Exec::from_raw({}); |
331 | + } |
332 | + |
333 | + if (g_strv_length(parsed) == 0) |
334 | + { |
335 | + g_warning("Parse resulted in a blank line"); |
336 | + g_strfreev(parsed); |
337 | + return Exec::from_raw({}); |
338 | + } |
339 | + |
340 | + /* Skip the first entry */ |
341 | + gchar** parsedpp = &(parsed[1]); |
342 | + |
343 | + gchar* params = g_strjoinv(" ", parsedpp); |
344 | + g_strfreev(parsed); |
345 | + |
346 | + std::string binname; |
347 | + if (appId_.package.value() == appId_.appname.value()) |
348 | + { |
349 | + binname = appId_.package.value(); |
350 | + } |
351 | + else |
352 | + { |
353 | + binname = appId_.package.value() + " " + appId_.appname.value(); |
354 | + } |
355 | + |
356 | + binname = "/snap/bin/" + binname + " " + params; |
357 | + g_free(params); |
358 | + |
359 | + return Exec::from_raw(binname); |
360 | + } |
361 | +}; |
362 | + |
363 | +/************************ |
364 | + ** Snap implementation |
365 | + ************************/ |
366 | + |
367 | +/** Creates a Snap application object. Will throw exceptions if the AppID |
368 | + doesn't resolve into a valid package or that package doesn't have a desktop |
369 | + file that matches the app name. |
370 | + |
371 | + \param appid Application ID of the snap |
372 | + \param registry Registry to use for persistent connections |
373 | + \param interface Primary interface that we found this snap for |
374 | +*/ |
375 | +Snap::Snap(const AppID& appid, const std::shared_ptr<Registry>& registry, const std::string& interface) |
376 | + : Base(registry) |
377 | + , appid_(appid) |
378 | + , interface_(interface) |
379 | +{ |
380 | + pkgInfo_ = registry->impl->snapdInfo.pkgInfo(appid.package); |
381 | + if (!pkgInfo_) |
382 | + { |
383 | + throw std::runtime_error("Unable to get snap package info for AppID: " + std::string(appid)); |
384 | + } |
385 | + |
386 | + if (!checkPkgInfo(pkgInfo_, appid_)) |
387 | + { |
388 | + throw std::runtime_error("AppID does not match installed package for: " + std::string(appid)); |
389 | + } |
390 | + |
391 | + info_ = std::make_shared<SnapInfo>(appid_, _registry, interface_, pkgInfo_->directory); |
392 | +} |
393 | + |
394 | +/** Uses the findInterface() function to find the interface if we don't |
395 | + have one. |
396 | + |
397 | + \param appid Application ID of the snap |
398 | + \param registry Registry to use for persistent connections |
399 | +*/ |
400 | +Snap::Snap(const AppID& appid, const std::shared_ptr<Registry>& registry) |
401 | + : Snap(appid, registry, findInterface(appid, registry)) |
402 | +{ |
403 | +} |
404 | + |
405 | +/** Lists all the Snappy apps that are using one of our supported interfaces. |
406 | + Also makes sure they're valid. |
407 | + |
408 | + \param registry Registry to use for persistent connections |
409 | +*/ |
410 | +std::list<std::shared_ptr<Application>> Snap::list(const std::shared_ptr<Registry>& registry) |
411 | +{ |
412 | + std::list<std::shared_ptr<Application>> apps; |
413 | + |
414 | + for (const auto& interface : SUPPORTED_INTERFACES) |
415 | + { |
416 | + for (const auto& id : registry->impl->snapdInfo.appsForInterface(interface)) |
417 | + { |
418 | + try |
419 | + { |
420 | + auto app = std::make_shared<Snap>(id, registry, interface); |
421 | + apps.emplace_back(app); |
422 | + } |
423 | + catch (std::runtime_error& e) |
424 | + { |
425 | + g_warning("Unable to make Snap object for '%s': %s", std::string(id).c_str(), e.what()); |
426 | + } |
427 | + } |
428 | + } |
429 | + |
430 | + return apps; |
431 | +} |
432 | + |
433 | +/** Returns the stored AppID */ |
434 | +AppID Snap::appId() |
435 | +{ |
436 | + return appid_; |
437 | +} |
438 | + |
439 | +/** Asks Snapd for the interfaces to determine which one the application |
440 | + can support. |
441 | + |
442 | + \param appid Application ID of the snap |
443 | + \param registry Registry to use for persistent connections |
444 | +*/ |
445 | +std::string Snap::findInterface(const AppID& appid, const std::shared_ptr<Registry>& registry) |
446 | +{ |
447 | + auto ifaceset = registry->impl->snapdInfo.interfacesForAppId(appid); |
448 | + |
449 | + for (const auto& interface : SUPPORTED_INTERFACES) |
450 | + { |
451 | + if (ifaceset.find(interface) != ifaceset.end()) |
452 | + { |
453 | + return interface; |
454 | + } |
455 | + } |
456 | + |
457 | + throw std::runtime_error("Interface not found for: " + std::string(appid)); |
458 | +} |
459 | + |
460 | +/** Checks a PkgInfo structure to ensure that it matches the AppID */ |
461 | +bool Snap::checkPkgInfo(const std::shared_ptr<snapd::Info::PkgInfo>& pkginfo, const AppID& appid) |
462 | +{ |
463 | + if (!pkginfo) |
464 | + { |
465 | + return false; |
466 | + } |
467 | + |
468 | + return pkginfo->revision == appid.version.value() && |
469 | + pkginfo->appnames.find(appid.appname) != pkginfo->appnames.end(); |
470 | +} |
471 | + |
472 | +/** Checks if an AppID could be a snap. Note it doesn't look for a desktop |
473 | + file just the package, app and version. This is done to make the lookup |
474 | + quickly, as this function can be used to select which backend to use |
475 | + and we want to reject quickly. |
476 | + |
477 | + \param appid Application ID of the snap |
478 | + \param registry Registry to use for persistent connections |
479 | +*/ |
480 | +bool Snap::hasAppId(const AppID& appId, const std::shared_ptr<Registry>& registry) |
481 | +{ |
482 | + if (!std::regex_match(appId.appname.value(), appnameRegex)) |
483 | + { |
484 | + return false; |
485 | + } |
486 | + |
487 | + auto pkginfo = registry->impl->snapdInfo.pkgInfo(appId.package); |
488 | + return checkPkgInfo(pkginfo, appId); |
489 | +} |
490 | + |
491 | +/** Look to see if a package is a valid Snap package name |
492 | + |
493 | + \param package Package name |
494 | + \param registry Registry to use for persistent connections |
495 | +*/ |
496 | +bool Snap::verifyPackage(const AppID::Package& package, const std::shared_ptr<Registry>& registry) |
497 | +{ |
498 | + try |
499 | + { |
500 | + auto pkgInfo = registry->impl->snapdInfo.pkgInfo(package); |
501 | + return pkgInfo != nullptr; |
502 | + } |
503 | + catch (std::runtime_error& e) |
504 | + { |
505 | + return false; |
506 | + } |
507 | +} |
508 | + |
509 | +/** Look to see if an appname is a valid for a Snap package |
510 | + |
511 | + \param package Package name |
512 | + \param appname Command name |
513 | + \param registry Registry to use for persistent connections |
514 | +*/ |
515 | +bool Snap::verifyAppname(const AppID::Package& package, |
516 | + const AppID::AppName& appname, |
517 | + const std::shared_ptr<Registry>& registry) |
518 | +{ |
519 | + if (!std::regex_match(appname.value(), appnameRegex)) |
520 | + { |
521 | + return false; |
522 | + } |
523 | + |
524 | + auto pkgInfo = registry->impl->snapdInfo.pkgInfo(package); |
525 | + return pkgInfo->appnames.find(appname) != pkgInfo->appnames.end(); |
526 | +} |
527 | + |
528 | +/** Look for an application name on a Snap package based on a |
529 | + wildcard type. |
530 | + |
531 | + \param package Package name |
532 | + \param card Wildcard to use for finding the appname |
533 | + \param registry Registry to use for persistent connections |
534 | +*/ |
535 | +AppID::AppName Snap::findAppname(const AppID::Package& package, |
536 | + AppID::ApplicationWildcard card, |
537 | + const std::shared_ptr<Registry>& registry) |
538 | +{ |
539 | + auto pkgInfo = registry->impl->snapdInfo.pkgInfo(package); |
540 | + |
541 | + if (pkgInfo->appnames.empty()) |
542 | + { |
543 | + throw std::runtime_error("No apps in package '" + package.value() + "' to find"); |
544 | + } |
545 | + |
546 | + switch (card) |
547 | + { |
548 | + case AppID::ApplicationWildcard::FIRST_LISTED: |
549 | + return AppID::AppName::from_raw(*pkgInfo->appnames.begin()); |
550 | + case AppID::ApplicationWildcard::LAST_LISTED: |
551 | + return AppID::AppName::from_raw(*pkgInfo->appnames.rbegin()); |
552 | + case AppID::ApplicationWildcard::ONLY_LISTED: |
553 | + if (pkgInfo->appnames.size() != 1) |
554 | + { |
555 | + throw std::runtime_error("More than a single app in package '" + package.value() + |
556 | + "' when requested to find only app"); |
557 | + } |
558 | + return AppID::AppName::from_raw(*pkgInfo->appnames.begin()); |
559 | + } |
560 | + |
561 | + throw std::logic_error("Got a value of the app wildcard enum that can't exist"); |
562 | +} |
563 | + |
564 | +/** Look for a version of a Snap package |
565 | + |
566 | + \param package Package name |
567 | + \param appname Not used for snaps |
568 | + \param registry Registry to use for persistent connections |
569 | +*/ |
570 | +AppID::Version Snap::findVersion(const AppID::Package& package, |
571 | + const AppID::AppName& appname, |
572 | + const std::shared_ptr<Registry>& registry) |
573 | +{ |
574 | + auto pkgInfo = registry->impl->snapdInfo.pkgInfo(package); |
575 | + return AppID::Version::from_raw(pkgInfo->revision); |
576 | +} |
577 | + |
578 | +/** Returns a reference to the info for the snap */ |
579 | +std::shared_ptr<Application::Info> Snap::info() |
580 | +{ |
581 | + return info_; |
582 | +} |
583 | + |
584 | +/** Get all of the instances of this snap package that are running */ |
585 | +std::vector<std::shared_ptr<Application::Instance>> Snap::instances() |
586 | +{ |
587 | + std::vector<std::shared_ptr<Instance>> vect; |
588 | + auto startsWith = std::string(appid_) + "-"; |
589 | + |
590 | + for (const auto& instance : _registry->impl->upstartInstancesForJob("application-snap")) |
591 | + { |
592 | + if (std::equal(startsWith.begin(), startsWith.end(), instance.begin())) |
593 | + { |
594 | + vect.emplace_back(std::make_shared<UpstartInstance>(appid_, "application-snap", instance, |
595 | + std::vector<Application::URL>{}, _registry)); |
596 | + } |
597 | + } |
598 | + |
599 | + return vect; |
600 | +} |
601 | + |
602 | +/** Return the launch environment for this snap. That includes whether |
603 | + or not it needs help from XMir (including Libertine helpers) |
604 | +*/ |
605 | +std::list<std::pair<std::string, std::string>> Snap::launchEnv() |
606 | +{ |
607 | + g_debug("Getting snap specific environment"); |
608 | + std::list<std::pair<std::string, std::string>> retval; |
609 | + |
610 | + retval.emplace_back(std::make_pair("APP_XMIR_ENABLE", info_->xMirEnable().value() ? "1" : "0")); |
611 | + if (info_->xMirEnable()) |
612 | + { |
613 | + /* If we're setting up XMir we also need the other helpers |
614 | + that libertine is helping with */ |
615 | + /* retval.emplace_back(std::make_pair("APP_EXEC", "libertine-launch --no-container " + |
616 | + * info_->execLine().value())); */ |
617 | + /* Not yet */ |
618 | + retval.emplace_back(std::make_pair("APP_EXEC", info_->execLine().value())); |
619 | + } |
620 | + else |
621 | + { |
622 | + retval.emplace_back(std::make_pair("APP_EXEC", info_->execLine().value())); |
623 | + } |
624 | + |
625 | + return retval; |
626 | +} |
627 | + |
628 | +/** Create a new instance of this Snap |
629 | + |
630 | + \param urls URLs to pass to the command |
631 | +*/ |
632 | +std::shared_ptr<Application::Instance> Snap::launch(const std::vector<Application::URL>& urls) |
633 | +{ |
634 | + std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this]() { return launchEnv(); }; |
635 | + return UpstartInstance::launch(appid_, "application-snap", std::string(appid_) + "-", urls, _registry, |
636 | + UpstartInstance::launchMode::STANDARD, envfunc); |
637 | +} |
638 | + |
639 | +/** Create a new instance of this Snap with a testing environment |
640 | + setup for it. |
641 | + |
642 | + \param urls URLs to pass to the command |
643 | +*/ |
644 | +std::shared_ptr<Application::Instance> Snap::launchTest(const std::vector<Application::URL>& urls) |
645 | +{ |
646 | + std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this]() { return launchEnv(); }; |
647 | + return UpstartInstance::launch(appid_, "application-snap", std::string(appid_) + "-", urls, _registry, |
648 | + UpstartInstance::launchMode::TEST, envfunc); |
649 | +} |
650 | + |
651 | +} // namespace app_impls |
652 | +} // namespace app_launch |
653 | +} // namespace ubuntu |
654 | |
655 | === added file 'libubuntu-app-launch/application-impl-snap.h' |
656 | --- libubuntu-app-launch/application-impl-snap.h 1970-01-01 00:00:00 +0000 |
657 | +++ libubuntu-app-launch/application-impl-snap.h 2016-09-13 17:38:18 +0000 |
658 | @@ -0,0 +1,98 @@ |
659 | +/* |
660 | + * Copyright © 2016 Canonical Ltd. |
661 | + * |
662 | + * This program is free software: you can redistribute it and/or modify it |
663 | + * under the terms of the GNU General Public License version 3, as published |
664 | + * by the Free Software Foundation. |
665 | + * |
666 | + * This program is distributed in the hope that it will be useful, but |
667 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
668 | + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
669 | + * PURPOSE. See the GNU General Public License for more details. |
670 | + * |
671 | + * You should have received a copy of the GNU General Public License along |
672 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
673 | + * |
674 | + * Authors: |
675 | + * Ted Gould <ted.gould@canonical.com> |
676 | + */ |
677 | + |
678 | +#include "application-impl-base.h" |
679 | +#include "application-info-desktop.h" |
680 | +#include "snapd-info.h" |
681 | + |
682 | +#pragma once |
683 | + |
684 | +namespace ubuntu |
685 | +{ |
686 | +namespace app_launch |
687 | +{ |
688 | +namespace app_impls |
689 | +{ |
690 | + |
691 | +/** Class implementing a Applications that are installed in the |
692 | + system as Snaps. This class connects to snapd to get information |
693 | + on the interfaces of the installed snaps and sees if any of them |
694 | + are applicable to the user session. Currently that means if the |
695 | + command has the unity8, unity7 or x11 interface. |
696 | + |
697 | + For Application IDs snaps use a very similar scheme to Click |
698 | + packages. The package field is the name of the snap package, typically |
699 | + this is the overall application name. The appname is the command |
700 | + in the snap package, which needs to be associated with one of our |
701 | + supported interfaces and have a desktop file. Lastly the version |
702 | + field is actually the snap revision, this value changes even on |
703 | + updates between channels of the same version so it provides a |
704 | + greater amount of uniqueness. |
705 | +*/ |
706 | +class Snap : public Base |
707 | +{ |
708 | +public: |
709 | + Snap(const AppID& appid, const std::shared_ptr<Registry>& registry); |
710 | + Snap(const AppID& appid, const std::shared_ptr<Registry>& registry, const std::string& interface); |
711 | + |
712 | + static std::list<std::shared_ptr<Application>> list(const std::shared_ptr<Registry>& registry); |
713 | + |
714 | + AppID appId() override; |
715 | + |
716 | + std::shared_ptr<Info> info() override; |
717 | + |
718 | + std::vector<std::shared_ptr<Instance>> instances() override; |
719 | + |
720 | + std::shared_ptr<Instance> launch(const std::vector<Application::URL>& urls = {}) override; |
721 | + std::shared_ptr<Instance> launchTest(const std::vector<Application::URL>& urls = {}) override; |
722 | + |
723 | + static bool hasAppId(const AppID& appId, const std::shared_ptr<Registry>& registry); |
724 | + |
725 | + static bool verifyPackage(const AppID::Package& package, const std::shared_ptr<Registry>& registry); |
726 | + static bool verifyAppname(const AppID::Package& package, |
727 | + const AppID::AppName& appname, |
728 | + const std::shared_ptr<Registry>& registry); |
729 | + static AppID::AppName findAppname(const AppID::Package& package, |
730 | + AppID::ApplicationWildcard card, |
731 | + const std::shared_ptr<Registry>& registry); |
732 | + static AppID::Version findVersion(const AppID::Package& package, |
733 | + const AppID::AppName& appname, |
734 | + const std::shared_ptr<Registry>& registry); |
735 | + |
736 | +private: |
737 | + /** AppID of the Snap. Should be the name of the snap package. |
738 | + The name of the command. And then the revision. */ |
739 | + AppID appid_; |
740 | + /** The app's displayed information. Should be from a desktop |
741 | + file that is put in ${SNAP_DIR}/meta/gui/${command}.desktop */ |
742 | + std::shared_ptr<app_info::Desktop> info_; |
743 | + /** Which interface we thing we're coming from. Depends on how |
744 | + we handle setting up the environment for the command. */ |
745 | + std::string interface_; |
746 | + /** Information that we get from Snapd on the package */ |
747 | + std::shared_ptr<snapd::Info::PkgInfo> pkgInfo_; |
748 | + |
749 | + std::list<std::pair<std::string, std::string>> launchEnv(); |
750 | + static std::string findInterface(const AppID& appid, const std::shared_ptr<Registry>& registry); |
751 | + static bool checkPkgInfo(const std::shared_ptr<snapd::Info::PkgInfo>& pkginfo, const AppID& appid); |
752 | +}; |
753 | + |
754 | +} // namespace app_impls |
755 | +} // namespace app_launch |
756 | +} // namespace ubuntu |
757 | |
758 | === modified file 'libubuntu-app-launch/application.cpp' |
759 | --- libubuntu-app-launch/application.cpp 2016-09-13 17:38:17 +0000 |
760 | +++ libubuntu-app-launch/application.cpp 2016-09-13 17:38:18 +0000 |
761 | @@ -24,6 +24,9 @@ |
762 | #include "application-impl-click.h" |
763 | #include "application-impl-legacy.h" |
764 | #include "application-impl-libertine.h" |
765 | +#ifdef ENABLE_SNAPPY |
766 | +#include "application-impl-snap.h" |
767 | +#endif |
768 | #include "application.h" |
769 | #include "registry.h" |
770 | |
771 | @@ -47,6 +50,12 @@ |
772 | { |
773 | return std::make_shared<app_impls::Click>(appid, registry); |
774 | } |
775 | +#ifdef ENABLE_SNAPPY |
776 | + else if (app_impls::Snap::hasAppId(appid, registry)) |
777 | + { |
778 | + return std::make_shared<app_impls::Snap>(appid, registry); |
779 | + } |
780 | +#endif |
781 | else if (app_impls::Libertine::hasAppId(appid, registry)) |
782 | { |
783 | return std::make_shared<app_impls::Libertine>(appid.package, appid.appname, registry); |
784 | @@ -197,6 +206,11 @@ |
785 | /* Click */ |
786 | {app_impls::Click::verifyPackage, app_impls::Click::verifyAppname, app_impls::Click::findAppname, |
787 | app_impls::Click::findVersion, app_impls::Click::hasAppId}, |
788 | +#ifdef ENABLE_SNAPPY |
789 | + /* Snap */ |
790 | + {app_impls::Snap::verifyPackage, app_impls::Snap::verifyAppname, app_impls::Snap::findAppname, |
791 | + app_impls::Snap::findVersion, app_impls::Snap::hasAppId}, |
792 | +#endif |
793 | /* Libertine */ |
794 | {app_impls::Libertine::verifyPackage, app_impls::Libertine::verifyAppname, app_impls::Libertine::findAppname, |
795 | app_impls::Libertine::findVersion, app_impls::Libertine::hasAppId}, |
796 | |
797 | === modified file 'libubuntu-app-launch/registry-impl.h' |
798 | --- libubuntu-app-launch/registry-impl.h 2016-09-13 17:38:17 +0000 |
799 | +++ libubuntu-app-launch/registry-impl.h 2016-09-13 17:38:18 +0000 |
800 | @@ -19,6 +19,7 @@ |
801 | |
802 | #include "glib-thread.h" |
803 | #include "registry.h" |
804 | +#include "snapd-info.h" |
805 | #include <click.h> |
806 | #include <gio/gio.h> |
807 | #include <json-glib/json-glib.h> |
808 | @@ -57,9 +58,17 @@ |
809 | void clearManager (); |
810 | #endif |
811 | |
812 | + /** Shared context thread for events and background tasks |
813 | + that UAL subtasks are doing */ |
814 | GLib::ContextThread thread; |
815 | + /** DBus shared connection for the session bus */ |
816 | std::shared_ptr<GDBusConnection> _dbus; |
817 | |
818 | +#ifdef ENABLE_SNAPPY |
819 | + /** Snapd information object */ |
820 | + snapd::Info snapdInfo; |
821 | +#endif |
822 | + |
823 | std::shared_ptr<IconFinder> getIconFinder(std::string basePath); |
824 | |
825 | void zgSendEvent(AppID appid, const std::string& eventtype); |
826 | |
827 | === modified file 'libubuntu-app-launch/registry.cpp' |
828 | --- libubuntu-app-launch/registry.cpp 2016-09-13 17:38:17 +0000 |
829 | +++ libubuntu-app-launch/registry.cpp 2016-09-13 17:38:18 +0000 |
830 | @@ -27,6 +27,9 @@ |
831 | #include "application-impl-click.h" |
832 | #include "application-impl-legacy.h" |
833 | #include "application-impl-libertine.h" |
834 | +#ifdef ENABLE_SNAPPY |
835 | +#include "application-impl-snap.h" |
836 | +#endif |
837 | |
838 | #include "helper-impl-click.h" |
839 | |
840 | @@ -50,6 +53,8 @@ |
841 | |
842 | /* Get all the legacy instances */ |
843 | instances.splice(instances.begin(), connection->impl->upstartInstancesForJob("application-legacy")); |
844 | + /* Get all the snap instances */ |
845 | + instances.splice(instances.begin(), connection->impl->upstartInstancesForJob("application-snap")); |
846 | |
847 | /* Remove the instance ID */ |
848 | std::transform(instances.begin(), instances.end(), instances.begin(), [](std::string &instancename) -> std::string { |
849 | @@ -106,6 +111,9 @@ |
850 | list.splice(list.begin(), app_impls::Click::list(connection)); |
851 | list.splice(list.begin(), app_impls::Legacy::list(connection)); |
852 | list.splice(list.begin(), app_impls::Libertine::list(connection)); |
853 | +#ifdef ENABLE_SNAPPY |
854 | + list.splice(list.begin(), app_impls::Snap::list(connection)); |
855 | +#endif |
856 | |
857 | return list; |
858 | } |
859 | |
860 | === added file 'libubuntu-app-launch/snapd-info.cpp' |
861 | --- libubuntu-app-launch/snapd-info.cpp 1970-01-01 00:00:00 +0000 |
862 | +++ libubuntu-app-launch/snapd-info.cpp 2016-09-13 17:38:18 +0000 |
863 | @@ -0,0 +1,471 @@ |
864 | +/* |
865 | + * Copyright © 2016 Canonical Ltd. |
866 | + * |
867 | + * This program is free software: you can redistribute it and/or modify it |
868 | + * under the terms of the GNU General Public License version 3, as published |
869 | + * by the Free Software Foundation. |
870 | + * |
871 | + * This program is distributed in the hope that it will be useful, but |
872 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
873 | + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
874 | + * PURPOSE. See the GNU General Public License for more details. |
875 | + * |
876 | + * You should have received a copy of the GNU General Public License along |
877 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
878 | + * |
879 | + * Authors: |
880 | + * Ted Gould <ted.gould@canonical.com> |
881 | + */ |
882 | + |
883 | +#include "snapd-info.h" |
884 | + |
885 | +#include "registry-impl.h" |
886 | + |
887 | +#include <curl/curl.h> |
888 | +#include <vector> |
889 | + |
890 | +namespace ubuntu |
891 | +{ |
892 | +namespace app_launch |
893 | +{ |
894 | +namespace snapd |
895 | +{ |
896 | + |
897 | +/** Initializes the info object which mostly means checking what is overridden |
898 | + by environment variables (mostly for testing) and making sure there is a |
899 | + snapd socket available to us. */ |
900 | +Info::Info() |
901 | +{ |
902 | + auto snapdEnv = g_getenv("UBUNTU_APP_LAUNCH_SNAPD_SOCKET"); |
903 | + if (G_UNLIKELY(snapdEnv != nullptr)) |
904 | + { |
905 | + snapdSocket = snapdEnv; |
906 | + } |
907 | + else |
908 | + { |
909 | + snapdSocket = "/run/snapd.socket"; |
910 | + } |
911 | + |
912 | + auto snapcBasedir = g_getenv("UBUNTU_APP_LAUNCH_SNAP_BASEDIR"); |
913 | + if (G_UNLIKELY(snapcBasedir != nullptr)) |
914 | + { |
915 | + snapBasedir = snapcBasedir; |
916 | + } |
917 | + else |
918 | + { |
919 | + snapBasedir = "/snap"; |
920 | + } |
921 | + |
922 | + if (g_file_test(snapdSocket.c_str(), G_FILE_TEST_EXISTS)) |
923 | + { |
924 | + snapdExists = true; |
925 | + } |
926 | +} |
927 | + |
928 | +/** Gets package information out of snapd by using the REST |
929 | + interface and turning the JSON object into a C++ Struct |
930 | + |
931 | + \param package Name of the package to look for |
932 | +*/ |
933 | +std::shared_ptr<Info::PkgInfo> Info::pkgInfo(const AppID::Package &package) const |
934 | +{ |
935 | + if (!snapdExists) |
936 | + { |
937 | + return {}; |
938 | + } |
939 | + |
940 | + try |
941 | + { |
942 | + auto snapnode = snapdJson("/v2/snaps/" + package.value()); |
943 | + auto snapobject = json_node_get_object(snapnode.get()); |
944 | + if (snapobject == nullptr) |
945 | + { |
946 | + throw std::runtime_error("Results returned by snapd were not a valid JSON object"); |
947 | + } |
948 | + |
949 | + /******************************************/ |
950 | + /* Validation of the object we got */ |
951 | + /******************************************/ |
952 | + for (const auto &member : {"apps"}) |
953 | + { |
954 | + if (!json_object_has_member(snapobject, member)) |
955 | + { |
956 | + throw std::runtime_error("Snap JSON didn't have a '" + std::string(member) + "'"); |
957 | + } |
958 | + } |
959 | + |
960 | + for (const auto &member : {"name", "status", "revision", "type", "version"}) |
961 | + { |
962 | + if (!json_object_has_member(snapobject, member)) |
963 | + { |
964 | + throw std::runtime_error("Snap JSON didn't have a '" + std::string(member) + "'"); |
965 | + } |
966 | + |
967 | + auto node = json_object_get_member(snapobject, member); |
968 | + if (json_node_get_node_type(node) != JSON_NODE_VALUE) |
969 | + { |
970 | + throw std::runtime_error{"Snap JSON had a '" + std::string(member) + "' but it's an object!"}; |
971 | + } |
972 | + |
973 | + if (json_node_get_value_type(node) != G_TYPE_STRING) |
974 | + { |
975 | + throw std::runtime_error{"Snap JSON had a '" + std::string(member) + "' but it's not a string!"}; |
976 | + } |
977 | + } |
978 | + |
979 | + std::string namestr = json_object_get_string_member(snapobject, "name"); |
980 | + if (namestr != package.value()) |
981 | + { |
982 | + throw std::runtime_error("Snapd returned information for snap '" + namestr + "' when we asked for '" + |
983 | + package.value() + "'"); |
984 | + } |
985 | + |
986 | + std::string statusstr = json_object_get_string_member(snapobject, "status"); |
987 | + if (statusstr != "active") |
988 | + { |
989 | + throw std::runtime_error("Snap is not in the 'active' state."); |
990 | + } |
991 | + |
992 | + std::string typestr = json_object_get_string_member(snapobject, "type"); |
993 | + if (typestr != "app") |
994 | + { |
995 | + throw std::runtime_error("Specified snap is not an application, we only support applications"); |
996 | + } |
997 | + |
998 | + /******************************************/ |
999 | + /* Validation complete — build the object */ |
1000 | + /******************************************/ |
1001 | + |
1002 | + auto pkgstruct = std::make_shared<PkgInfo>(); |
1003 | + pkgstruct->name = namestr; |
1004 | + pkgstruct->version = json_object_get_string_member(snapobject, "version"); |
1005 | + std::string revisionstr = json_object_get_string_member(snapobject, "revision"); |
1006 | + pkgstruct->revision = revisionstr; |
1007 | + |
1008 | + /* TODO: Seems like snapd should give this to us */ |
1009 | + auto gdir = g_build_filename(snapBasedir.c_str(), namestr.c_str(), revisionstr.c_str(), nullptr); |
1010 | + pkgstruct->directory = gdir; |
1011 | + g_free(gdir); |
1012 | + |
1013 | + auto appsarray = json_object_get_array_member(snapobject, "apps"); |
1014 | + for (unsigned int i = 0; i < json_array_get_length(appsarray); i++) |
1015 | + { |
1016 | + auto appobj = json_array_get_object_element(appsarray, i); |
1017 | + if (json_object_has_member(appobj, "name")) |
1018 | + { |
1019 | + auto appname = json_object_get_string_member(appobj, "name"); |
1020 | + if (appname) |
1021 | + { |
1022 | + pkgstruct->appnames.insert(appname); |
1023 | + } |
1024 | + } |
1025 | + } |
1026 | + |
1027 | + return pkgstruct; |
1028 | + } |
1029 | + catch (std::runtime_error &e) |
1030 | + { |
1031 | + g_warning("Unable to get snap information for '%s': %s", package.value().c_str(), e.what()); |
1032 | + return {}; |
1033 | + } |
1034 | +} |
1035 | + |
1036 | +/** Function that acts as the return from cURL to add data to |
1037 | + our storage vector. |
1038 | + |
1039 | + \param ptr incoming data |
1040 | + \param size block size |
1041 | + \param nmemb number of blocks |
1042 | + \param userdata our local vector to store things in |
1043 | +*/ |
1044 | +static size_t snapd_writefunc(char *ptr, size_t size, size_t nmemb, void *userdata) |
1045 | +{ |
1046 | + auto data = static_cast<std::vector<char> *>(userdata); |
1047 | + data->insert(data->end(), ptr, ptr + (size * nmemb)); |
1048 | + return size * nmemb; |
1049 | +} |
1050 | + |
1051 | +/** Asks the snapd process for some JSON. This function parses the basic |
1052 | + response JSON that snapd returns and will error if a return code error |
1053 | + is in the JSON. It then passes on the "result" part of the response |
1054 | + to the caller. |
1055 | + |
1056 | + \param endpoint End of the URL to pass to snapd |
1057 | +*/ |
1058 | +std::shared_ptr<JsonNode> Info::snapdJson(const std::string &endpoint) const |
1059 | +{ |
1060 | + /* Setup the CURL connection and suck some data */ |
1061 | + CURL *curl = curl_easy_init(); |
1062 | + if (curl == nullptr) |
1063 | + { |
1064 | + throw std::runtime_error("Unable to create new cURL connection"); |
1065 | + } |
1066 | + |
1067 | + std::vector<char> data; |
1068 | + |
1069 | + /* Configure the command */ |
1070 | + // curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); |
1071 | + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L); |
1072 | + curl_easy_setopt(curl, CURLOPT_URL, ("http://snapd" + endpoint).c_str()); |
1073 | + curl_easy_setopt(curl, CURLOPT_UNIX_SOCKET_PATH, snapdSocket.c_str()); |
1074 | + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data); |
1075 | + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, snapd_writefunc); |
1076 | + |
1077 | + /* Overridable timeout */ |
1078 | + if (g_getenv("UBUNTU_APP_LAUNCH_DISABLE_SNAPD_TIMEOUT") != nullptr) |
1079 | + { |
1080 | + curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 100L); |
1081 | + } |
1082 | + |
1083 | + /* Run the actual request (blocking) */ |
1084 | + auto res = curl_easy_perform(curl); |
1085 | + |
1086 | + if (res != CURLE_OK) |
1087 | + { |
1088 | + curl_easy_cleanup(curl); |
1089 | + throw std::runtime_error("snapd HTTP server returned an error: " + std::string(curl_easy_strerror(res))); |
1090 | + } |
1091 | + else |
1092 | + { |
1093 | + g_debug("Got %d bytes from snapd", int(data.size())); |
1094 | + } |
1095 | + |
1096 | + curl_easy_cleanup(curl); |
1097 | + |
1098 | + /* Cool, we have data */ |
1099 | + auto parser = std::shared_ptr<JsonParser>(json_parser_new(), [](JsonParser *parser) { g_clear_object(&parser); }); |
1100 | + GError *error = nullptr; |
1101 | + json_parser_load_from_data(parser.get(), /* parser */ |
1102 | + data.data(), /* array */ |
1103 | + data.size(), /* size */ |
1104 | + &error); /* error */ |
1105 | + |
1106 | + if (error != nullptr) |
1107 | + { |
1108 | + std::string message{"Can not parse JSON: " + std::string(error->message)}; |
1109 | + g_error_free(error); |
1110 | + throw std::runtime_error{message}; |
1111 | + } |
1112 | + |
1113 | + auto root = json_parser_get_root(parser.get()); |
1114 | + auto rootobj = json_node_get_object(root); |
1115 | + |
1116 | + if (rootobj == nullptr) |
1117 | + { |
1118 | + throw std::runtime_error("Root of JSON result isn't an object"); |
1119 | + } |
1120 | + |
1121 | + /* Check members */ |
1122 | + for (const auto &member : {"status-code", "result"}) |
1123 | + { |
1124 | + if (!json_object_has_member(rootobj, member)) |
1125 | + { |
1126 | + throw std::runtime_error("Resulting JSON didn't have a '" + std::string(member) + "'"); |
1127 | + } |
1128 | + } |
1129 | + |
1130 | + for (const auto &member : {"status", "type"}) |
1131 | + { |
1132 | + if (!json_object_has_member(rootobj, member)) |
1133 | + { |
1134 | + throw std::runtime_error("Snap JSON didn't have a '" + std::string(member) + "'"); |
1135 | + } |
1136 | + |
1137 | + auto node = json_object_get_member(rootobj, member); |
1138 | + if (json_node_get_node_type(node) != JSON_NODE_VALUE) |
1139 | + { |
1140 | + throw std::runtime_error{"Snap JSON had a '" + std::string(member) + "' but it's an object!"}; |
1141 | + } |
1142 | + |
1143 | + if (json_node_get_value_type(node) != G_TYPE_STRING) |
1144 | + { |
1145 | + throw std::runtime_error{"Snap JSON had a '" + std::string(member) + "' but it's not a string!"}; |
1146 | + } |
1147 | + } |
1148 | + |
1149 | + auto status = json_object_get_int_member(rootobj, "status-code"); |
1150 | + if (status != 200) |
1151 | + { |
1152 | + throw std::runtime_error("Status code is: " + std::to_string(status)); |
1153 | + } |
1154 | + |
1155 | + std::string statusstr = json_object_get_string_member(rootobj, "status"); |
1156 | + if (statusstr != "OK") |
1157 | + { |
1158 | + throw std::runtime_error("Status string is: " + statusstr); |
1159 | + } |
1160 | + |
1161 | + std::string typestr = json_object_get_string_member(rootobj, "type"); |
1162 | + if (typestr != "sync") |
1163 | + { |
1164 | + throw std::runtime_error("We only support 'sync' results right now, but we got a: " + typestr); |
1165 | + } |
1166 | + |
1167 | + auto result = std::shared_ptr<JsonNode>(json_node_ref(json_object_get_member(rootobj, "result")), json_node_unref); |
1168 | + |
1169 | + return result; |
1170 | +} |
1171 | + |
1172 | +/** Looks through all the plugs in the interfaces and runs a function |
1173 | + based on them. Avoids pulling objects out of the parsed JSON structure |
1174 | + from Snappy and making sure they have the same lifecycle as the parser |
1175 | + object which seems to destroy them when it dies. |
1176 | + |
1177 | + \param plugfunc Function to execute on each plug |
1178 | +*/ |
1179 | +void Info::forAllPlugs(std::function<void(JsonObject *plugobj)> plugfunc) const |
1180 | +{ |
1181 | + if (!snapdExists) |
1182 | + { |
1183 | + return; |
1184 | + } |
1185 | + |
1186 | + auto interfacesnode = snapdJson("/v2/interfaces"); |
1187 | + auto interface = json_node_get_object(interfacesnode.get()); |
1188 | + if (interface == nullptr) |
1189 | + { |
1190 | + throw std::runtime_error("Interfaces result isn't an object: " + Registry::Impl::printJson(interfacesnode)); |
1191 | + } |
1192 | + |
1193 | + for (const auto &member : {"plugs", "slots"}) |
1194 | + { |
1195 | + if (!json_object_has_member(interface, member)) |
1196 | + { |
1197 | + throw std::runtime_error("Interface JSON didn't have a '" + std::string(member) + "'"); |
1198 | + } |
1199 | + } |
1200 | + |
1201 | + auto plugarray = json_object_get_array_member(interface, "plugs"); |
1202 | + for (unsigned int i = 0; i < json_array_get_length(plugarray); i++) |
1203 | + { |
1204 | + auto ifaceobj = json_array_get_object_element(plugarray, i); |
1205 | + try |
1206 | + { |
1207 | + for (const auto &member : {"snap", "interface", "apps"}) |
1208 | + { |
1209 | + if (!json_object_has_member(ifaceobj, member)) |
1210 | + { |
1211 | + throw std::runtime_error("Interface JSON didn't have a '" + std::string(member) + "'"); |
1212 | + } |
1213 | + } |
1214 | + |
1215 | + plugfunc(ifaceobj); |
1216 | + } |
1217 | + catch (std::runtime_error &e) |
1218 | + { |
1219 | + /* We'll check the others even if one is bad */ |
1220 | + // g_debug("Malformed inteface instance: %s", e.what()); |
1221 | + continue; |
1222 | + } |
1223 | + } |
1224 | +} |
1225 | + |
1226 | +/** Gets all the apps that are available for a given interface. It asks snapd |
1227 | + for the list of interfaces and then finds this one, turning it into a set |
1228 | + of AppIDs |
1229 | + |
1230 | + \param in_interface Which interface to get the set of apps for |
1231 | +*/ |
1232 | +std::set<AppID> Info::appsForInterface(const std::string &in_interface) const |
1233 | +{ |
1234 | + bool interfacefound = false; |
1235 | + std::set<AppID> appids; |
1236 | + |
1237 | + try |
1238 | + { |
1239 | + forAllPlugs([this, &interfacefound, &appids, in_interface](JsonObject *ifaceobj) { |
1240 | + std::string interfacename = json_object_get_string_member(ifaceobj, "interface"); |
1241 | + if (interfacename != in_interface) |
1242 | + { |
1243 | + return; |
1244 | + } |
1245 | + |
1246 | + interfacefound = true; |
1247 | + |
1248 | + auto cname = json_object_get_string_member(ifaceobj, "snap"); |
1249 | + if (cname == nullptr) |
1250 | + { |
1251 | + return; |
1252 | + } |
1253 | + std::string snapname(cname); |
1254 | + |
1255 | + auto pkginfo = pkgInfo(AppID::Package::from_raw(snapname)); |
1256 | + if (!pkginfo) |
1257 | + { |
1258 | + return; |
1259 | + } |
1260 | + |
1261 | + std::string revision = pkginfo->revision; |
1262 | + |
1263 | + auto apps = json_object_get_array_member(ifaceobj, "apps"); |
1264 | + for (unsigned int k = 0; k < json_array_get_length(apps); k++) |
1265 | + { |
1266 | + std::string appname = json_array_get_string_element(apps, k); |
1267 | + |
1268 | + appids.emplace(AppID(AppID::Package::from_raw(snapname), /* package */ |
1269 | + AppID::AppName::from_raw(appname), /* appname */ |
1270 | + AppID::Version::from_raw(revision))); /* version */ |
1271 | + } |
1272 | + }); |
1273 | + |
1274 | + if (!interfacefound) |
1275 | + { |
1276 | + g_debug("Unable to find information on interface '%s'", in_interface.c_str()); |
1277 | + } |
1278 | + } |
1279 | + catch (std::runtime_error &e) |
1280 | + { |
1281 | + g_warning("Unable to get interface information: %s", e.what()); |
1282 | + } |
1283 | + |
1284 | + return appids; |
1285 | +} |
1286 | + |
1287 | +/** Finds all the interfaces for a specific appid |
1288 | + |
1289 | + \param appid AppID to search for |
1290 | +*/ |
1291 | +std::set<std::string> Info::interfacesForAppId(const AppID &appid) const |
1292 | +{ |
1293 | + |
1294 | + std::set<std::string> interfaces; |
1295 | + |
1296 | + try |
1297 | + { |
1298 | + forAllPlugs([&interfaces, appid](JsonObject *ifaceobj) { |
1299 | + auto snapname = json_object_get_string_member(ifaceobj, "snap"); |
1300 | + if (snapname != appid.package.value()) |
1301 | + { |
1302 | + return; |
1303 | + } |
1304 | + |
1305 | + auto cinterfacename = json_object_get_string_member(ifaceobj, "interface"); |
1306 | + if (cinterfacename == nullptr) |
1307 | + { |
1308 | + return; |
1309 | + } |
1310 | + |
1311 | + std::string interfacename(cinterfacename); |
1312 | + |
1313 | + auto apps = json_object_get_array_member(ifaceobj, "apps"); |
1314 | + for (unsigned int k = 0; k < json_array_get_length(apps); k++) |
1315 | + { |
1316 | + std::string appname = json_array_get_string_element(apps, k); |
1317 | + if (appname == appid.appname.value()) |
1318 | + { |
1319 | + interfaces.insert(interfacename); |
1320 | + } |
1321 | + } |
1322 | + }); |
1323 | + } |
1324 | + catch (std::runtime_error &e) |
1325 | + { |
1326 | + g_warning("Unable to get interface information: %s", e.what()); |
1327 | + } |
1328 | + |
1329 | + return interfaces; |
1330 | +} |
1331 | + |
1332 | +} // namespace snapd |
1333 | +} // namespace app_launch |
1334 | +} // namespace ubuntu |
1335 | |
1336 | === added file 'libubuntu-app-launch/snapd-info.h' |
1337 | --- libubuntu-app-launch/snapd-info.h 1970-01-01 00:00:00 +0000 |
1338 | +++ libubuntu-app-launch/snapd-info.h 2016-09-13 17:38:18 +0000 |
1339 | @@ -0,0 +1,76 @@ |
1340 | +/* |
1341 | + * Copyright © 2016 Canonical Ltd. |
1342 | + * |
1343 | + * This program is free software: you can redistribute it and/or modify it |
1344 | + * under the terms of the GNU General Public License version 3, as published |
1345 | + * by the Free Software Foundation. |
1346 | + * |
1347 | + * This program is distributed in the hope that it will be useful, but |
1348 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
1349 | + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1350 | + * PURPOSE. See the GNU General Public License for more details. |
1351 | + * |
1352 | + * You should have received a copy of the GNU General Public License along |
1353 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
1354 | + * |
1355 | + * Authors: |
1356 | + * Ted Gould <ted.gould@canonical.com> |
1357 | + */ |
1358 | + |
1359 | +#pragma once |
1360 | + |
1361 | +#include <list> |
1362 | +#include <memory> |
1363 | +#include <set> |
1364 | + |
1365 | +#include <json-glib/json-glib.h> |
1366 | + |
1367 | +#include "appid.h" |
1368 | + |
1369 | +namespace ubuntu |
1370 | +{ |
1371 | +namespace app_launch |
1372 | +{ |
1373 | +namespace snapd |
1374 | +{ |
1375 | + |
1376 | +/** Class that implements the connection to Snapd allowing us to get info |
1377 | + from it in a C++ friendly way. */ |
1378 | +class Info |
1379 | +{ |
1380 | +public: |
1381 | + Info(); |
1382 | + virtual ~Info() = default; |
1383 | + |
1384 | + /** Information that we can get from snapd about a package */ |
1385 | + struct PkgInfo |
1386 | + { |
1387 | + std::string name; /**< Name of the package */ |
1388 | + std::string version; /**< Version string provided by the package */ |
1389 | + std::string revision; /**< Numerical always incrementing revision of the package */ |
1390 | + std::string directory; /**< Directory that the snap is uncompressed into */ |
1391 | + std::set<std::string> appnames; /**< List of appnames in the snap */ |
1392 | + }; |
1393 | + std::shared_ptr<PkgInfo> pkgInfo(const AppID::Package &package) const; |
1394 | + |
1395 | + std::set<AppID> appsForInterface(const std::string &interface) const; |
1396 | + |
1397 | + std::set<std::string> interfacesForAppId(const AppID &appid) const; |
1398 | + |
1399 | +private: |
1400 | + /** Path to the socket of snapd */ |
1401 | + std::string snapdSocket; |
1402 | + /** Directory to use as the base for all snap packages when making paths. This |
1403 | + can be overridden with UBUNTU_APP_LAUNCH_SNAP_BASEDIR */ |
1404 | + std::string snapBasedir; |
1405 | + /** Result of a check at init to see if the socket is available. If |
1406 | + not all functions will return null results. */ |
1407 | + bool snapdExists = false; |
1408 | + |
1409 | + std::shared_ptr<JsonNode> snapdJson(const std::string &endpoint) const; |
1410 | + void forAllPlugs(std::function<void(JsonObject *plugobj)> plugfunc) const; |
1411 | +}; |
1412 | + |
1413 | +} // namespace snapd |
1414 | +} // namespace app_launch |
1415 | +} // namespace ubuntu |
1416 | |
1417 | === modified file 'libubuntu-app-launch/ubuntu-app-launch.cpp' |
1418 | --- libubuntu-app-launch/ubuntu-app-launch.cpp 2016-09-13 17:38:17 +0000 |
1419 | +++ libubuntu-app-launch/ubuntu-app-launch.cpp 2016-09-13 17:38:18 +0000 |
1420 | @@ -316,6 +316,9 @@ |
1421 | } else if (g_strcmp0(env, "JOB=application-legacy") == 0) { |
1422 | job_found = TRUE; |
1423 | job_legacy = TRUE; |
1424 | + } else if (g_strcmp0(env, "JOB=application-snap") == 0) { |
1425 | + job_found = TRUE; |
1426 | + job_legacy = TRUE; |
1427 | } else if (g_str_has_prefix(env, "INSTANCE=")) { |
1428 | instance = g_strdup(env + strlen("INSTANCE=")); |
1429 | } |
1430 | |
1431 | === modified file 'tests/CMakeLists.txt' |
1432 | --- tests/CMakeLists.txt 2016-09-13 17:38:17 +0000 |
1433 | +++ tests/CMakeLists.txt 2016-09-13 17:38:18 +0000 |
1434 | @@ -3,6 +3,11 @@ |
1435 | configure_file ("click-db-dir/test.conf.in" "${CMAKE_CURRENT_BINARY_DIR}/click-db-dir/test.conf" @ONLY) |
1436 | set_directory_properties (PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${CMAKE_CURRENT_BINARY_DIR}/click-db-dir/test.conf") |
1437 | |
1438 | +if(CURL_FOUND) |
1439 | +add_definitions ( -DENABLE_SNAPPY=1 ) |
1440 | +endif() |
1441 | + |
1442 | + |
1443 | # Google Test |
1444 | |
1445 | include_directories(${GTEST_INCLUDE_DIR}) |
1446 | @@ -35,6 +40,7 @@ |
1447 | add_definitions ( -DSOCKET_DEMANGLER="${CMAKE_BINARY_DIR}/socket-demangler" ) |
1448 | add_definitions ( -DSOCKET_DEMANGLER_INSTALL="${pkglibexecdir}/socket-demangler" ) |
1449 | add_definitions ( -DSOCKET_TOOL="${CMAKE_CURRENT_BINARY_DIR}/socket-tool" ) |
1450 | +add_definitions ( -DSNAP_BASEDIR="${CMAKE_CURRENT_SOURCE_DIR}/snap-basedir" ) |
1451 | |
1452 | add_executable (libual-test |
1453 | libual-test.cc |
1454 | @@ -57,6 +63,16 @@ |
1455 | add_test (NAME libual-test COMMAND libual-test) |
1456 | add_test (NAME libual-cpp-test COMMAND libual-cpp-test) |
1457 | |
1458 | +# Snapd Info Test |
1459 | + |
1460 | +if(CURL_FOUND) |
1461 | +add_definitions ( -DSNAPD_TEST_SOCKET="/tmp/snapd-test-socket" ) |
1462 | +add_executable (snapd-info-test |
1463 | + snapd-info-test.cpp) |
1464 | +target_link_libraries (snapd-info-test gtest ${GTEST_LIBS} launcher-static) |
1465 | +add_test (NAME snapd-info-test COMMAND snapd-info-test) |
1466 | +endif() |
1467 | + |
1468 | # List Apps |
1469 | |
1470 | add_executable (list-apps |
1471 | @@ -144,5 +160,7 @@ |
1472 | libual-cpp-test.cc |
1473 | list-apps.cpp |
1474 | eventually-fixture.h |
1475 | + snapd-info-test.cpp |
1476 | + snapd-mock.h |
1477 | zg-test.cc |
1478 | ) |
1479 | |
1480 | === modified file 'tests/exec-util-test.cc' |
1481 | --- tests/exec-util-test.cc 2016-09-13 17:38:17 +0000 |
1482 | +++ tests/exec-util-test.cc 2016-09-13 17:38:18 +0000 |
1483 | @@ -44,6 +44,7 @@ |
1484 | g_setenv("XDG_CACHE_HOME", CMAKE_SOURCE_DIR "/libertine-data", TRUE); |
1485 | g_setenv("XDG_DATA_HOME", CMAKE_SOURCE_DIR "/libertine-home", TRUE); |
1486 | g_setenv("UBUNTU_APP_LAUNCH_LIBERTINE_LAUNCH", "libertine-launch", TRUE); |
1487 | + g_setenv("UBUNTU_APP_LAUNCH_SNAPD_SOCKET", "/this/should/not/exist", TRUE); |
1488 | |
1489 | service = dbus_test_service_new(NULL); |
1490 | |
1491 | |
1492 | === modified file 'tests/failure-test.cc' |
1493 | --- tests/failure-test.cc 2016-08-06 02:57:13 +0000 |
1494 | +++ tests/failure-test.cc 2016-09-13 17:38:18 +0000 |
1495 | @@ -37,7 +37,6 @@ |
1496 | virtual void TearDown() { |
1497 | g_test_dbus_down(testbus); |
1498 | g_clear_object(&testbus); |
1499 | - return; |
1500 | } |
1501 | }; |
1502 | |
1503 | @@ -48,7 +47,6 @@ |
1504 | std::string * last = static_cast<std::string *>(user_data); |
1505 | *last = appid; |
1506 | } |
1507 | - return; |
1508 | } |
1509 | |
1510 | TEST_F(FailureTest, CrashTest) |
1511 | @@ -75,8 +73,6 @@ |
1512 | EXPECT_EVENTUALLY_EQ("foo", last_observer); |
1513 | |
1514 | ASSERT_TRUE(ubuntu_app_launch_observer_delete_app_failed(failed_observer, &last_observer)); |
1515 | - |
1516 | - return; |
1517 | } |
1518 | |
1519 | TEST_F(FailureTest, LegacyTest) |
1520 | @@ -94,8 +90,23 @@ |
1521 | EXPECT_EVENTUALLY_EQ("foo", last_observer); |
1522 | |
1523 | ASSERT_TRUE(ubuntu_app_launch_observer_delete_app_failed(failed_observer, &last_observer)); |
1524 | - |
1525 | - return; |
1526 | +} |
1527 | + |
1528 | +TEST_F(FailureTest, SnapTest) |
1529 | +{ |
1530 | + g_setenv("EXIT_STATUS", "-100", TRUE); |
1531 | + g_setenv("JOB", "application-snap", TRUE); |
1532 | + g_setenv("INSTANCE", "foo_bar_x123-1234", TRUE); |
1533 | + |
1534 | + std::string last_observer; |
1535 | + ASSERT_TRUE(ubuntu_app_launch_observer_add_app_failed(failed_observer, &last_observer)); |
1536 | + |
1537 | + /* Status based */ |
1538 | + ASSERT_TRUE(g_spawn_command_line_sync(APP_FAILED_TOOL, NULL, NULL, NULL, NULL)); |
1539 | + |
1540 | + EXPECT_EVENTUALLY_EQ("foo_bar_x123", last_observer); |
1541 | + |
1542 | + ASSERT_TRUE(ubuntu_app_launch_observer_delete_app_failed(failed_observer, &last_observer)); |
1543 | } |
1544 | |
1545 | static void |
1546 | @@ -105,7 +116,6 @@ |
1547 | std::string * last = static_cast<std::string *>(user_data); |
1548 | *last = appid; |
1549 | } |
1550 | - return; |
1551 | } |
1552 | |
1553 | TEST_F(FailureTest, StartTest) |
1554 | @@ -123,6 +133,4 @@ |
1555 | EXPECT_EVENTUALLY_EQ("foo", last_observer); |
1556 | |
1557 | ASSERT_TRUE(ubuntu_app_launch_observer_delete_app_failed(failed_start_observer, &last_observer)); |
1558 | - |
1559 | - return; |
1560 | } |
1561 | |
1562 | === modified file 'tests/libual-cpp-test.cc' |
1563 | --- tests/libual-cpp-test.cc 2016-09-13 17:38:17 +0000 |
1564 | +++ tests/libual-cpp-test.cc 2016-09-13 17:38:18 +0000 |
1565 | @@ -38,6 +38,10 @@ |
1566 | #include "eventually-fixture.h" |
1567 | #include "mir-mock.h" |
1568 | |
1569 | +#ifdef ENABLE_SNAPPY |
1570 | +#include "snapd-mock.h" |
1571 | +#endif |
1572 | + |
1573 | class LibUAL : public EventuallyFixture |
1574 | { |
1575 | protected: |
1576 | @@ -103,6 +107,13 @@ |
1577 | g_setenv("XDG_CACHE_HOME", CMAKE_SOURCE_DIR "/libertine-data", TRUE); |
1578 | g_setenv("XDG_DATA_HOME", CMAKE_SOURCE_DIR "/libertine-home", TRUE); |
1579 | |
1580 | +#ifdef ENABLE_SNAPPY |
1581 | + g_setenv("UBUNTU_APP_LAUNCH_SNAPD_SOCKET", SNAPD_TEST_SOCKET, TRUE); |
1582 | + g_setenv("UBUNTU_APP_LAUNCH_SNAP_BASEDIR", SNAP_BASEDIR, TRUE); |
1583 | + g_setenv("UBUNTU_APP_LAUNCH_DISABLE_SNAPD_TIMEOUT", "You betcha!", TRUE); |
1584 | + g_unlink(SNAPD_TEST_SOCKET); |
1585 | +#endif |
1586 | + |
1587 | service = dbus_test_service_new(NULL); |
1588 | |
1589 | debugConnection(); |
1590 | @@ -117,6 +128,8 @@ |
1591 | dbus_test_dbus_mock_object_add_method(mock, obj, "GetJobByName", G_VARIANT_TYPE("s"), G_VARIANT_TYPE("o"), |
1592 | "if args[0] == 'application-click':\n" |
1593 | " ret = dbus.ObjectPath('/com/test/application_click')\n" |
1594 | + "elif args[0] == 'application-snap':\n" |
1595 | + " ret = dbus.ObjectPath('/com/test/application_snap')\n" |
1596 | "elif args[0] == 'application-legacy':\n" |
1597 | " ret = dbus.ObjectPath('/com/test/application_legacy')\n" |
1598 | "elif args[0] == 'untrusted-helper':\n" |
1599 | @@ -153,6 +166,36 @@ |
1600 | g_variant_new_parsed(process_var), NULL); |
1601 | g_free(process_var); |
1602 | |
1603 | +#ifdef ENABLE_SNAPPY |
1604 | + /* Snap App */ |
1605 | + auto snapjobobj = |
1606 | + dbus_test_dbus_mock_get_object(mock, "/com/test/application_snap", "com.ubuntu.Upstart0_6.Job", NULL); |
1607 | + |
1608 | + dbus_test_dbus_mock_object_add_method( |
1609 | + mock, snapjobobj, "Start", G_VARIANT_TYPE("(asb)"), NULL, |
1610 | + "if args[0][0] == 'APP_ID=unity8-package_foo_x123':" |
1611 | + " raise dbus.exceptions.DBusException('Foo running', name='com.ubuntu.Upstart0_6.Error.AlreadyStarted')", |
1612 | + NULL); |
1613 | + |
1614 | + dbus_test_dbus_mock_object_add_method(mock, snapjobobj, "Stop", G_VARIANT_TYPE("(asb)"), NULL, "", NULL); |
1615 | + |
1616 | + dbus_test_dbus_mock_object_add_method(mock, snapjobobj, "GetAllInstances", NULL, G_VARIANT_TYPE("ao"), |
1617 | + "ret = [ dbus.ObjectPath('/com/test/snapp_instance') ]", NULL); |
1618 | + |
1619 | + dbus_test_dbus_mock_object_add_method(mock, snapjobobj, "GetInstanceByName", G_VARIANT_TYPE_STRING, |
1620 | + G_VARIANT_TYPE("o"), "ret = dbus.ObjectPath('/com/test/snapp_instance')", |
1621 | + NULL); |
1622 | + |
1623 | + auto snapinstobj = |
1624 | + dbus_test_dbus_mock_get_object(mock, "/com/test/snapp_instance", "com.ubuntu.Upstart0_6.Instance", NULL); |
1625 | + dbus_test_dbus_mock_object_add_property(mock, snapinstobj, "name", G_VARIANT_TYPE_STRING, |
1626 | + g_variant_new_string("unity8-package_foo_x123-"), NULL); |
1627 | + gchar* snapprocess_var = g_strdup_printf("[('main', %d)]", getpid()); |
1628 | + dbus_test_dbus_mock_object_add_property(mock, snapinstobj, "processes", G_VARIANT_TYPE("a(si)"), |
1629 | + g_variant_new_parsed(snapprocess_var), NULL); |
1630 | + g_free(snapprocess_var); |
1631 | +#endif |
1632 | + |
1633 | /* Legacy App */ |
1634 | DbusTestDbusMockObject* ljobobj = |
1635 | dbus_test_dbus_mock_get_object(mock, "/com/test/application_legacy", "com.ubuntu.Upstart0_6.Job", NULL); |
1636 | @@ -249,6 +292,10 @@ |
1637 | g_object_unref(bus); |
1638 | |
1639 | ASSERT_EVENTUALLY_EQ(nullptr, bus); |
1640 | + |
1641 | +#ifdef ENABLE_SNAPPY |
1642 | + g_unlink(SNAPD_TEST_SOCKET); |
1643 | +#endif |
1644 | } |
1645 | |
1646 | GVariant* find_env(GVariant* env_array, const gchar* var) |
1647 | @@ -412,6 +459,146 @@ |
1648 | ASSERT_EQ(dbus_test_dbus_mock_object_check_method_call(mock, obj, "Stop", NULL, NULL), 1); |
1649 | } |
1650 | |
1651 | +#ifdef ENABLE_SNAPPY |
1652 | +/* Snapd mock data */ |
1653 | +static std::pair<std::string, std::string> interfaces{ |
1654 | + "GET /v2/interfaces HTTP/1.1\r\nHost: snapd\r\nAccept: */*\r\n\r\n", |
1655 | + SnapdMock::httpJsonResponse( |
1656 | + SnapdMock::snapdOkay(SnapdMock::interfacesJson({{"unity8", "unity8-package", {"foo", "single"}}})))}; |
1657 | +static std::pair<std::string, std::string> u8Package{ |
1658 | + "GET /v2/snaps/unity8-package HTTP/1.1\r\nHost: snapd\r\nAccept: */*\r\n\r\n", |
1659 | + SnapdMock::httpJsonResponse(SnapdMock::snapdOkay( |
1660 | + SnapdMock::packageJson("unity8-package", "active", "app", "1.2.3.4", "x123", {"foo", "single"})))}; |
1661 | + |
1662 | +TEST_F(LibUAL, ApplicationIdSnap) |
1663 | +{ |
1664 | + SnapdMock snapd{SNAPD_TEST_SOCKET, |
1665 | + {u8Package, u8Package, u8Package, u8Package, u8Package, u8Package, u8Package, u8Package, u8Package, |
1666 | + u8Package, u8Package, u8Package, u8Package, u8Package, u8Package, u8Package}}; |
1667 | + registry = std::make_shared<ubuntu::app_launch::Registry>(); |
1668 | + |
1669 | + EXPECT_EQ("unity8-package_foo_x123", (std::string)ubuntu::app_launch::AppID::discover(registry, "unity8-package")); |
1670 | + EXPECT_EQ("unity8-package_foo_x123", |
1671 | + (std::string)ubuntu::app_launch::AppID::discover(registry, "unity8-package", "foo")); |
1672 | + EXPECT_EQ("unity8-package_single_x123", |
1673 | + (std::string)ubuntu::app_launch::AppID::discover(registry, "unity8-package", "single")); |
1674 | + EXPECT_EQ("unity8-package_single_x123", |
1675 | + (std::string)ubuntu::app_launch::AppID::discover( |
1676 | + registry, "unity8-package", ubuntu::app_launch::AppID::ApplicationWildcard::LAST_LISTED)); |
1677 | + EXPECT_EQ("unity8-package_foo_x123", |
1678 | + (std::string)ubuntu::app_launch::AppID::discover(registry, "unity8-package", "foo", "x123")); |
1679 | + |
1680 | + EXPECT_EQ("", (std::string)ubuntu::app_launch::AppID::discover(registry, "unity7-package")); |
1681 | +} |
1682 | + |
1683 | +TEST_F(LibUAL, StartSnapApplication) |
1684 | +{ |
1685 | + SnapdMock snapd{SNAPD_TEST_SOCKET, {u8Package, interfaces, u8Package}}; |
1686 | + registry = std::make_shared<ubuntu::app_launch::Registry>(); |
1687 | + |
1688 | + auto obj = dbus_test_dbus_mock_get_object(mock, "/com/test/application_snap", "com.ubuntu.Upstart0_6.Job", NULL); |
1689 | + |
1690 | + /* Basic make sure we can send the event */ |
1691 | + auto appid = ubuntu::app_launch::AppID::parse("unity8-package_single_x123"); |
1692 | + auto app = ubuntu::app_launch::Application::create(appid, registry); |
1693 | + app->launch(); |
1694 | + |
1695 | + EXPECT_EQ(1, dbus_test_dbus_mock_object_check_method_call(mock, obj, "Start", NULL, NULL)); |
1696 | + |
1697 | + ASSERT_TRUE(dbus_test_dbus_mock_object_clear_method_calls(mock, obj, NULL)); |
1698 | + |
1699 | + /* Now look at the details of the call */ |
1700 | + app->launch(); |
1701 | + |
1702 | + guint len = 0; |
1703 | + const DbusTestDbusMockCall* calls = dbus_test_dbus_mock_object_get_method_calls(mock, obj, "Start", &len, NULL); |
1704 | + EXPECT_NE(nullptr, calls); |
1705 | + EXPECT_EQ(1, len); |
1706 | + |
1707 | + EXPECT_STREQ("Start", calls->name); |
1708 | + EXPECT_EQ(2, g_variant_n_children(calls->params)); |
1709 | + |
1710 | + GVariant* block = g_variant_get_child_value(calls->params, 1); |
1711 | + EXPECT_TRUE(g_variant_get_boolean(block)); |
1712 | + g_variant_unref(block); |
1713 | + |
1714 | + GVariant* env = g_variant_get_child_value(calls->params, 0); |
1715 | + EXPECT_TRUE(check_env(env, "APP_ID", "unity8-package_single_x123")); |
1716 | + g_variant_unref(env); |
1717 | + |
1718 | + ASSERT_TRUE(dbus_test_dbus_mock_object_clear_method_calls(mock, obj, NULL)); |
1719 | + |
1720 | + /* Let's pass some URLs */ |
1721 | + std::vector<ubuntu::app_launch::Application::URL> urls{ |
1722 | + ubuntu::app_launch::Application::URL::from_raw("http://ubuntu.com/"), |
1723 | + ubuntu::app_launch::Application::URL::from_raw("https://ubuntu.com/"), |
1724 | + ubuntu::app_launch::Application::URL::from_raw("file:///home/phablet/test.txt")}; |
1725 | + |
1726 | + app->launch(urls); |
1727 | + |
1728 | + len = 0; |
1729 | + calls = dbus_test_dbus_mock_object_get_method_calls(mock, obj, "Start", &len, NULL); |
1730 | + EXPECT_NE(nullptr, calls); |
1731 | + EXPECT_EQ(1, len); |
1732 | + |
1733 | + env = g_variant_get_child_value(calls->params, 0); |
1734 | + EXPECT_TRUE(check_env(env, "APP_ID", "unity8-package_single_x123")); |
1735 | + EXPECT_TRUE( |
1736 | + check_env(env, "APP_URIS", "'http://ubuntu.com/' 'https://ubuntu.com/' 'file:///home/phablet/test.txt'")); |
1737 | + g_variant_unref(env); |
1738 | + |
1739 | + return; |
1740 | +} |
1741 | + |
1742 | +TEST_F(LibUAL, StartSnapApplicationTest) |
1743 | +{ |
1744 | + SnapdMock snapd{SNAPD_TEST_SOCKET, {u8Package, interfaces, u8Package}}; |
1745 | + registry = std::make_shared<ubuntu::app_launch::Registry>(); |
1746 | + |
1747 | + auto obj = dbus_test_dbus_mock_get_object(mock, "/com/test/application_snap", "com.ubuntu.Upstart0_6.Job", NULL); |
1748 | + |
1749 | + /* Basic make sure we can send the event */ |
1750 | + auto appid = ubuntu::app_launch::AppID::parse("unity8-package_single_x123"); |
1751 | + auto app = ubuntu::app_launch::Application::create(appid, registry); |
1752 | + app->launchTest(); |
1753 | + |
1754 | + guint len = 0; |
1755 | + auto calls = dbus_test_dbus_mock_object_get_method_calls(mock, obj, "Start", &len, NULL); |
1756 | + EXPECT_NE(nullptr, calls); |
1757 | + EXPECT_EQ(1, len); |
1758 | + |
1759 | + EXPECT_STREQ("Start", calls->name); |
1760 | + EXPECT_EQ(2, g_variant_n_children(calls->params)); |
1761 | + |
1762 | + GVariant* block = g_variant_get_child_value(calls->params, 1); |
1763 | + EXPECT_TRUE(g_variant_get_boolean(block)); |
1764 | + g_variant_unref(block); |
1765 | + |
1766 | + GVariant* env = g_variant_get_child_value(calls->params, 0); |
1767 | + EXPECT_TRUE(check_env(env, "APP_ID", "unity8-package_single_x123")); |
1768 | + EXPECT_TRUE(check_env(env, "QT_LOAD_TESTABILITY", "1")); |
1769 | + g_variant_unref(env); |
1770 | +} |
1771 | + |
1772 | +TEST_F(LibUAL, StopSnapApplication) |
1773 | +{ |
1774 | + SnapdMock snapd{SNAPD_TEST_SOCKET, {u8Package, interfaces, u8Package}}; |
1775 | + registry = std::make_shared<ubuntu::app_launch::Registry>(); |
1776 | + |
1777 | + auto obj = dbus_test_dbus_mock_get_object(mock, "/com/test/application_snap", "com.ubuntu.Upstart0_6.Job", NULL); |
1778 | + |
1779 | + auto appid = ubuntu::app_launch::AppID::parse("unity8-package_foo_x123"); |
1780 | + auto app = ubuntu::app_launch::Application::create(appid, registry); |
1781 | + |
1782 | + ASSERT_TRUE(app->hasInstances()); |
1783 | + EXPECT_EQ(1, app->instances().size()); |
1784 | + |
1785 | + app->instances()[0]->stop(); |
1786 | + |
1787 | + ASSERT_EQ(dbus_test_dbus_mock_object_check_method_call(mock, obj, "Stop", NULL, NULL), 1); |
1788 | +} |
1789 | +#endif |
1790 | + |
1791 | /* NOTE: The fact that there is 'libertine-data' in these strings is because |
1792 | we're using one CACHE_HOME for this test suite and the libertine functions |
1793 | need to pull things from there, where these are only comparisons. It's just |
1794 | @@ -579,9 +766,18 @@ |
1795 | |
1796 | TEST_F(LibUAL, ApplicationList) |
1797 | { |
1798 | +#ifdef ENABLE_SNAPPY |
1799 | + SnapdMock snapd{SNAPD_TEST_SOCKET, {u8Package, u8Package, u8Package, interfaces, u8Package}}; |
1800 | + registry = std::make_shared<ubuntu::app_launch::Registry>(); |
1801 | +#endif |
1802 | + |
1803 | auto apps = ubuntu::app_launch::Registry::runningApps(registry); |
1804 | |
1805 | +#ifdef ENABLE_SNAPPY |
1806 | + ASSERT_EQ(4, apps.size()); |
1807 | +#else |
1808 | ASSERT_EQ(3, apps.size()); |
1809 | +#endif |
1810 | |
1811 | apps.sort([](const std::shared_ptr<ubuntu::app_launch::Application>& a, |
1812 | const std::shared_ptr<ubuntu::app_launch::Application>& b) { |
1813 | @@ -592,7 +788,9 @@ |
1814 | }); |
1815 | |
1816 | EXPECT_EQ("com.test.good_application_1.2.3", (std::string)apps.front()->appId()); |
1817 | - EXPECT_EQ("single", (std::string)apps.back()->appId()); |
1818 | +#ifdef ENABLE_SNAPPY |
1819 | + EXPECT_EQ("unity8-package_foo_x123", (std::string)apps.back()->appId()); |
1820 | +#endif |
1821 | } |
1822 | |
1823 | typedef struct |
1824 | |
1825 | === modified file 'tests/libual-test.cc' |
1826 | --- tests/libual-test.cc 2016-09-13 17:38:17 +0000 |
1827 | +++ tests/libual-test.cc 2016-09-13 17:38:18 +0000 |
1828 | @@ -89,6 +89,8 @@ |
1829 | g_setenv("XDG_CACHE_HOME", CMAKE_SOURCE_DIR "/libertine-data", TRUE); |
1830 | g_setenv("XDG_DATA_HOME", CMAKE_SOURCE_DIR "/libertine-home", TRUE); |
1831 | |
1832 | + g_setenv("UBUNTU_APP_LAUNCH_SNAPD_SOCKET", "/this/should/not/exist", TRUE); |
1833 | + |
1834 | service = dbus_test_service_new(NULL); |
1835 | |
1836 | debugConnection(); |
1837 | |
1838 | === modified file 'tests/list-apps.cpp' |
1839 | --- tests/list-apps.cpp 2016-09-13 17:38:17 +0000 |
1840 | +++ tests/list-apps.cpp 2016-09-13 17:38:18 +0000 |
1841 | @@ -27,9 +27,15 @@ |
1842 | #include "application-impl-click.h" |
1843 | #include "application-impl-legacy.h" |
1844 | #include "application-impl-libertine.h" |
1845 | +#include "application-impl-snap.h" |
1846 | #include "application.h" |
1847 | #include "registry.h" |
1848 | |
1849 | +#ifdef ENABLE_SNAPPY |
1850 | +#include "snapd-mock.h" |
1851 | +#define SNAPD_LIST_APPS_SOCKET SNAPD_TEST_SOCKET "-list-apps" |
1852 | +#endif |
1853 | + |
1854 | class ListApps : public EventuallyFixture |
1855 | { |
1856 | protected: |
1857 | @@ -38,6 +44,11 @@ |
1858 | |
1859 | virtual void SetUp() |
1860 | { |
1861 | +#ifdef ENABLE_SNAPPY |
1862 | + /* Ensure it is cleared */ |
1863 | + g_unlink(SNAPD_LIST_APPS_SOCKET); |
1864 | +#endif |
1865 | + |
1866 | /* Click DB test mode */ |
1867 | g_setenv("TEST_CLICK_DB", CMAKE_BINARY_DIR "/click-db-dir", TRUE); |
1868 | g_setenv("TEST_CLICK_USER", "test-user", TRUE); |
1869 | @@ -50,6 +61,12 @@ |
1870 | g_setenv("XDG_CACHE_HOME", CMAKE_SOURCE_DIR "/libertine-data", TRUE); |
1871 | g_setenv("XDG_DATA_HOME", CMAKE_SOURCE_DIR "/libertine-home", TRUE); |
1872 | |
1873 | +#ifdef ENABLE_SNAPPY |
1874 | + g_setenv("UBUNTU_APP_LAUNCH_SNAPD_SOCKET", SNAPD_LIST_APPS_SOCKET, TRUE); |
1875 | + g_setenv("UBUNTU_APP_LAUNCH_SNAP_BASEDIR", SNAP_BASEDIR, TRUE); |
1876 | + g_setenv("UBUNTU_APP_LAUNCH_DISABLE_SNAPD_TIMEOUT", "You betcha!", TRUE); |
1877 | +#endif |
1878 | + |
1879 | testbus = g_test_dbus_new(G_TEST_DBUS_NONE); |
1880 | g_test_dbus_up(testbus); |
1881 | |
1882 | @@ -60,6 +77,10 @@ |
1883 | |
1884 | virtual void TearDown() |
1885 | { |
1886 | +#ifdef ENABLE_SNAPPY |
1887 | + g_unlink(SNAPD_LIST_APPS_SOCKET); |
1888 | +#endif |
1889 | + |
1890 | g_object_unref(bus); |
1891 | |
1892 | g_test_dbus_down(testbus); |
1893 | @@ -159,8 +180,62 @@ |
1894 | EXPECT_TRUE(findApp(apps, "container-name_user-app_0.0")); |
1895 | } |
1896 | |
1897 | +#ifdef ENABLE_SNAPPY |
1898 | +static std::pair<std::string, std::string> interfaces{ |
1899 | + "GET /v2/interfaces HTTP/1.1\r\nHost: snapd\r\nAccept: */*\r\n\r\n", |
1900 | + SnapdMock::httpJsonResponse( |
1901 | + SnapdMock::snapdOkay(SnapdMock::interfacesJson({{"unity8", "unity8-package", {"foo", "bar"}}, |
1902 | + {"unity7", "unity7-package", {"single", "multiple"}}, |
1903 | + {"x11", "x11-package", {"multiple", "hidden"}} |
1904 | + |
1905 | + })))}; |
1906 | +static std::pair<std::string, std::string> u8Package{ |
1907 | + "GET /v2/snaps/unity8-package HTTP/1.1\r\nHost: snapd\r\nAccept: */*\r\n\r\n", |
1908 | + SnapdMock::httpJsonResponse(SnapdMock::snapdOkay( |
1909 | + SnapdMock::packageJson("unity8-package", "active", "app", "1.2.3.4", "x123", {"foo", "bar"})))}; |
1910 | +static std::pair<std::string, std::string> u7Package{ |
1911 | + "GET /v2/snaps/unity7-package HTTP/1.1\r\nHost: snapd\r\nAccept: */*\r\n\r\n", |
1912 | + SnapdMock::httpJsonResponse(SnapdMock::snapdOkay(SnapdMock::packageJson( |
1913 | + "unity7-package", "active", "app", "1.2.3.4", "x123", {"scope", "single", "multiple"})))}; |
1914 | +static std::pair<std::string, std::string> x11Package{ |
1915 | + "GET /v2/snaps/x11-package HTTP/1.1\r\nHost: snapd\r\nAccept: */*\r\n\r\n", |
1916 | + SnapdMock::httpJsonResponse(SnapdMock::snapdOkay( |
1917 | + SnapdMock::packageJson("x11-package", "active", "app", "1.2.3.4", "x123", {"multiple", "hidden"})))}; |
1918 | + |
1919 | +TEST_F(ListApps, ListSnap) |
1920 | +{ |
1921 | + SnapdMock mock{SNAPD_LIST_APPS_SOCKET, |
1922 | + {interfaces, u7Package, u7Package, u7Package, /* unity7 check */ |
1923 | + interfaces, u8Package, u8Package, u8Package, /* unity8 check */ |
1924 | + interfaces, x11Package, x11Package, x11Package}}; /* x11 check */ |
1925 | + auto registry = std::make_shared<ubuntu::app_launch::Registry>(); |
1926 | + |
1927 | + auto apps = ubuntu::app_launch::app_impls::Snap::list(registry); |
1928 | + |
1929 | + printApps(apps); |
1930 | + |
1931 | + mock.result(); |
1932 | + |
1933 | + EXPECT_EQ(4, apps.size()); |
1934 | + EXPECT_TRUE(findApp(apps, "unity8-package_foo_x123")); |
1935 | + EXPECT_TRUE(findApp(apps, "unity7-package_single_x123")); |
1936 | + EXPECT_TRUE(findApp(apps, "unity7-package_multiple_x123")); |
1937 | + EXPECT_TRUE(findApp(apps, "x11-package_multiple_x123")); |
1938 | + |
1939 | + EXPECT_FALSE(findApp(apps, "unity8-package_bar_x123")); |
1940 | + EXPECT_FALSE(findApp(apps, "unity7-package_scope_x123")); |
1941 | + EXPECT_FALSE(findApp(apps, "x11-package_hidden_x123")); |
1942 | +} |
1943 | +#endif |
1944 | + |
1945 | TEST_F(ListApps, ListAll) |
1946 | { |
1947 | +#ifdef ENABLE_SNAPPY |
1948 | + SnapdMock mock{SNAPD_LIST_APPS_SOCKET, |
1949 | + {interfaces, u7Package, u7Package, u7Package, /* unity7 check */ |
1950 | + interfaces, u8Package, u8Package, u8Package, /* unity8 check */ |
1951 | + interfaces, x11Package, x11Package, x11Package}}; /* x11 check */ |
1952 | +#endif |
1953 | auto registry = std::make_shared<ubuntu::app_launch::Registry>(); |
1954 | |
1955 | /* Get all the apps */ |
1956 | @@ -168,5 +243,9 @@ |
1957 | |
1958 | printApps(apps); |
1959 | |
1960 | +#ifdef ENABLE_SNAPPY |
1961 | + EXPECT_EQ(18, apps.size()); |
1962 | +#else |
1963 | EXPECT_EQ(14, apps.size()); |
1964 | +#endif |
1965 | } |
1966 | |
1967 | === added directory 'tests/snap-basedir' |
1968 | === added directory 'tests/snap-basedir/unity7-package' |
1969 | === added directory 'tests/snap-basedir/unity7-package/x123' |
1970 | === added directory 'tests/snap-basedir/unity7-package/x123/meta' |
1971 | === added symlink 'tests/snap-basedir/unity7-package/x123/meta/gui' |
1972 | === target is u'../../../../applications/' |
1973 | === added directory 'tests/snap-basedir/unity8-package' |
1974 | === added directory 'tests/snap-basedir/unity8-package/x123' |
1975 | === added directory 'tests/snap-basedir/unity8-package/x123/meta' |
1976 | === added symlink 'tests/snap-basedir/unity8-package/x123/meta/gui' |
1977 | === target is u'../../../../applications/' |
1978 | === added directory 'tests/snap-basedir/x11-package' |
1979 | === added directory 'tests/snap-basedir/x11-package/x123' |
1980 | === added directory 'tests/snap-basedir/x11-package/x123/meta' |
1981 | === added symlink 'tests/snap-basedir/x11-package/x123/meta/gui' |
1982 | === target is u'../../../../applications/' |
1983 | === added file 'tests/snapd-info-test.cpp' |
1984 | --- tests/snapd-info-test.cpp 1970-01-01 00:00:00 +0000 |
1985 | +++ tests/snapd-info-test.cpp 2016-09-13 17:38:18 +0000 |
1986 | @@ -0,0 +1,150 @@ |
1987 | +/* |
1988 | + * Copyright © 2016 Canonical Ltd. |
1989 | + * |
1990 | + * This program is free software: you can redistribute it and/or modify it |
1991 | + * under the terms of the GNU General Public License version 3, as published |
1992 | + * by the Free Software Foundation. |
1993 | + * |
1994 | + * This program is distributed in the hope that it will be useful, but |
1995 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
1996 | + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1997 | + * PURPOSE. See the GNU General Public License for more details. |
1998 | + * |
1999 | + * You should have received a copy of the GNU General Public License along |
2000 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
2001 | + * |
2002 | + * Authors: |
2003 | + * Ted Gould <ted.gould@canonical.com> |
2004 | + */ |
2005 | + |
2006 | +#include "snapd-info.h" |
2007 | +#include "snapd-mock.h" |
2008 | +#include <gio/gio.h> |
2009 | +#include <glib/gstdio.h> |
2010 | +#include <gtest/gtest.h> |
2011 | + |
2012 | +class SnapdInfo : public ::testing::Test |
2013 | +{ |
2014 | +protected: |
2015 | + virtual void SetUp() |
2016 | + { |
2017 | + g_setenv("UBUNTU_APP_LAUNCH_SNAPD_SOCKET", SNAPD_TEST_SOCKET, TRUE); |
2018 | + } |
2019 | + |
2020 | + virtual void TearDown() |
2021 | + { |
2022 | + g_unlink(SNAPD_TEST_SOCKET); |
2023 | + } |
2024 | +}; |
2025 | + |
2026 | +TEST_F(SnapdInfo, Init) |
2027 | +{ |
2028 | + auto info = std::make_shared<ubuntu::app_launch::snapd::Info>(); |
2029 | + |
2030 | + info.reset(); |
2031 | +} |
2032 | + |
2033 | +TEST_F(SnapdInfo, PackageInfo) |
2034 | +{ |
2035 | + SnapdMock mock{SNAPD_TEST_SOCKET, |
2036 | + {{"GET /v2/snaps/test-package HTTP/1.1\r\nHost: snapd\r\nAccept: */*\r\n\r\n", |
2037 | + SnapdMock::httpJsonResponse(SnapdMock::snapdOkay(SnapdMock::packageJson( |
2038 | + "test-package", "active", "app", "1.2.3.4", "x123", {"foo", "bar"})))}}}; |
2039 | + auto info = std::make_shared<ubuntu::app_launch::snapd::Info>(); |
2040 | + |
2041 | + auto pkginfo = info->pkgInfo(ubuntu::app_launch::AppID::Package::from_raw("test-package")); |
2042 | + |
2043 | + mock.result(); |
2044 | + |
2045 | + ASSERT_NE(nullptr, pkginfo); |
2046 | + EXPECT_EQ("test-package", pkginfo->name); |
2047 | + EXPECT_EQ("1.2.3.4", pkginfo->version); |
2048 | + EXPECT_EQ("x123", pkginfo->revision); |
2049 | + EXPECT_EQ("/snap/test-package/x123", pkginfo->directory); |
2050 | + EXPECT_NE(pkginfo->appnames.end(), pkginfo->appnames.find("foo")); |
2051 | + EXPECT_NE(pkginfo->appnames.end(), pkginfo->appnames.find("bar")); |
2052 | +} |
2053 | + |
2054 | +TEST_F(SnapdInfo, AppsForInterface) |
2055 | +{ |
2056 | + SnapdMock mock{SNAPD_TEST_SOCKET, |
2057 | + {{"GET /v2/interfaces HTTP/1.1\r\nHost: snapd\r\nAccept: */*\r\n\r\n", |
2058 | + SnapdMock::httpJsonResponse(SnapdMock::snapdOkay( |
2059 | + SnapdMock::interfacesJson({{"unity8", "test-package", {"foo", "bar"}}})))}, |
2060 | + {"GET /v2/snaps/test-package HTTP/1.1\r\nHost: snapd\r\nAccept: */*\r\n\r\n", |
2061 | + SnapdMock::httpJsonResponse(SnapdMock::snapdOkay(SnapdMock::packageJson( |
2062 | + "test-package", "active", "app", "1.2.3.4", "x123", {"foo", "bar"})))}}}; |
2063 | + |
2064 | + auto info = std::make_shared<ubuntu::app_launch::snapd::Info>(); |
2065 | + |
2066 | + auto apps = info->appsForInterface("unity8"); |
2067 | + |
2068 | + mock.result(); |
2069 | + |
2070 | + EXPECT_EQ(2, apps.size()); |
2071 | + EXPECT_NE(apps.end(), apps.find(ubuntu::app_launch::AppID::parse("test-package_foo_x123"))); |
2072 | + EXPECT_NE(apps.end(), apps.find(ubuntu::app_launch::AppID::parse("test-package_bar_x123"))); |
2073 | +} |
2074 | + |
2075 | +TEST_F(SnapdInfo, InterfacesForAppID) |
2076 | +{ |
2077 | + SnapdMock mock{SNAPD_TEST_SOCKET, |
2078 | + {{"GET /v2/interfaces HTTP/1.1\r\nHost: snapd\r\nAccept: */*\r\n\r\n", |
2079 | + SnapdMock::httpJsonResponse(SnapdMock::snapdOkay( |
2080 | + SnapdMock::interfacesJson({{"unity8", "test-package", {"foo"}}, |
2081 | + {"noniface", "test-package", {"bar", "bamf", "bunny"}}, |
2082 | + {"unity7", "test-package", {"bar", "foo"}}})))} |
2083 | + |
2084 | + }}; |
2085 | + |
2086 | + auto info = std::make_shared<ubuntu::app_launch::snapd::Info>(); |
2087 | + auto appid = ubuntu::app_launch::AppID::parse("test-package_foo_x123"); |
2088 | + |
2089 | + auto ifaces = info->interfacesForAppId(appid); |
2090 | + |
2091 | + mock.result(); |
2092 | + |
2093 | + EXPECT_EQ(2, ifaces.size()); |
2094 | + EXPECT_NE(ifaces.end(), ifaces.find("unity7")); |
2095 | + EXPECT_NE(ifaces.end(), ifaces.find("unity8")); |
2096 | +} |
2097 | + |
2098 | +TEST_F(SnapdInfo, BadJson) |
2099 | +{ |
2100 | + SnapdMock mock{ |
2101 | + SNAPD_TEST_SOCKET, |
2102 | + { |
2103 | + {"GET /v2/snaps/test-package HTTP/1.1\r\nHost: snapd\r\nAccept: */*\r\n\r\n", |
2104 | + SnapdMock::httpJsonResponse("«This is not valid JSON»")}, |
2105 | + {"GET /v2/snaps/test-package HTTP/1.1\r\nHost: snapd\r\nAccept: */*\r\n\r\n", |
2106 | + SnapdMock::httpJsonResponse("{ 'status': 'FAIL', 'status-code': 404, 'type': 'sync', 'result': { } }")}, |
2107 | + {"GET /v2/snaps/test-package HTTP/1.1\r\nHost: snapd\r\nAccept: */*\r\n\r\n", |
2108 | + SnapdMock::httpJsonResponse(SnapdMock::snapdOkay("'«This is not an object»'"))}, |
2109 | + |
2110 | + }}; |
2111 | + auto info = std::make_shared<ubuntu::app_launch::snapd::Info>(); |
2112 | + |
2113 | + auto badjson = info->pkgInfo(ubuntu::app_launch::AppID::Package::from_raw("test-package")); |
2114 | + |
2115 | + EXPECT_EQ(nullptr, badjson); |
2116 | + |
2117 | + auto err404 = info->pkgInfo(ubuntu::app_launch::AppID::Package::from_raw("test-package")); |
2118 | + |
2119 | + EXPECT_EQ(nullptr, err404); |
2120 | + |
2121 | + auto noobj = info->pkgInfo(ubuntu::app_launch::AppID::Package::from_raw("test-package")); |
2122 | + |
2123 | + EXPECT_EQ(nullptr, noobj); |
2124 | + |
2125 | + /* We should still be getting good requests, so let's check them */ |
2126 | + mock.result(); |
2127 | +} |
2128 | + |
2129 | +TEST_F(SnapdInfo, NoSocket) |
2130 | +{ |
2131 | + auto info = std::make_shared<ubuntu::app_launch::snapd::Info>(); |
2132 | + |
2133 | + auto nosocket = info->pkgInfo(ubuntu::app_launch::AppID::Package::from_raw("test-package")); |
2134 | + |
2135 | + EXPECT_EQ(nullptr, nosocket); |
2136 | +} |
2137 | |
2138 | === added file 'tests/snapd-mock.h' |
2139 | --- tests/snapd-mock.h 1970-01-01 00:00:00 +0000 |
2140 | +++ tests/snapd-mock.h 2016-09-13 17:38:18 +0000 |
2141 | @@ -0,0 +1,384 @@ |
2142 | +/* |
2143 | + * Copyright © 2016 Canonical Ltd. |
2144 | + * |
2145 | + * This program is free software: you can redistribute it and/or modify it |
2146 | + * under the terms of the GNU General Public License version 3, as published |
2147 | + * by the Free Software Foundation. |
2148 | + * |
2149 | + * This program is distributed in the hope that it will be useful, but |
2150 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
2151 | + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
2152 | + * PURPOSE. See the GNU General Public License for more details. |
2153 | + * |
2154 | + * You should have received a copy of the GNU General Public License along |
2155 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
2156 | + * |
2157 | + * Authors: |
2158 | + * Ted Gould <ted.gould@canonical.com> |
2159 | + */ |
2160 | + |
2161 | +#include "glib-thread.h" |
2162 | +#include <future> |
2163 | +#include <gio/gio.h> |
2164 | +#include <gio/gunixsocketaddress.h> |
2165 | +#include <gtest/gtest.h> |
2166 | +#include <list> |
2167 | +#include <numeric> |
2168 | + |
2169 | +class SnapdMock |
2170 | +{ |
2171 | +public: |
2172 | + /** Initialize the mock with a list of files to use as |
2173 | + input and output. Each will be sent in order. */ |
2174 | + SnapdMock(const std::string &socketPath, std::list<std::pair<std::string, std::string>> interactions) |
2175 | + : thread() |
2176 | + { |
2177 | + for (auto interaction : interactions) |
2178 | + { |
2179 | + TestCase testcase{interaction.first, interaction.second, {}, {}}; |
2180 | + testCases.push_back(testcase); |
2181 | + } |
2182 | + |
2183 | + /* Build the socket */ |
2184 | + socketService = thread.executeOnThread<std::shared_ptr<GSocketService>>([this, socketPath]() { |
2185 | + auto service = std::shared_ptr<GSocketService>(g_socket_service_new(), [](GSocketService *service) { |
2186 | + if (service != nullptr) |
2187 | + { |
2188 | + g_socket_service_stop(service); |
2189 | + g_object_unref(service); |
2190 | + } |
2191 | + }); |
2192 | + |
2193 | + GError *error = nullptr; |
2194 | + auto socket = g_socket_new(G_SOCKET_FAMILY_UNIX, /* unix */ |
2195 | + G_SOCKET_TYPE_STREAM, /* type */ |
2196 | + G_SOCKET_PROTOCOL_DEFAULT, /* protocol */ |
2197 | + &error); |
2198 | + |
2199 | + if (error != nullptr) |
2200 | + { |
2201 | + std::string message = "Failed to create socket: " + std::string(error->message); |
2202 | + g_error_free(error); |
2203 | + throw std::runtime_error(message); |
2204 | + } |
2205 | + |
2206 | + auto socketaddr = g_unix_socket_address_new(socketPath.c_str()); |
2207 | + if (socketaddr == nullptr) |
2208 | + { |
2209 | + throw std::runtime_error("Unable to create a socket address for: " + socketPath); |
2210 | + } |
2211 | + |
2212 | + g_socket_bind(socket, socketaddr, TRUE, &error); |
2213 | + if (error != nullptr) |
2214 | + { |
2215 | + std::string message = |
2216 | + "Unable to connect socket to address '" + socketPath + "': " + std::string(error->message); |
2217 | + g_error_free(error); |
2218 | + throw std::runtime_error(message); |
2219 | + } |
2220 | + |
2221 | + g_socket_listener_add_socket(G_SOCKET_LISTENER(service.get()), socket, nullptr, &error); |
2222 | + if (error != nullptr) |
2223 | + { |
2224 | + std::string message = "Unable to listen to socket: " + std::string(error->message); |
2225 | + g_error_free(error); |
2226 | + throw std::runtime_error(message); |
2227 | + } |
2228 | + |
2229 | + g_signal_connect(service.get(), "incoming", G_CALLBACK(serviceConnectedStatic), this); |
2230 | + |
2231 | + g_socket_service_start(service.get()); |
2232 | + |
2233 | + g_socket_listen(socket, &error); |
2234 | + if (error != nullptr) |
2235 | + { |
2236 | + std::string message = "Unable to listen to socket: " + std::string(error->message); |
2237 | + g_error_free(error); |
2238 | + throw std::runtime_error(message); |
2239 | + } |
2240 | + |
2241 | + g_debug("Initialized snapd-mock with %d test cases", int(testCases.size())); |
2242 | + return service; |
2243 | + }); |
2244 | + } |
2245 | + |
2246 | + ~SnapdMock() |
2247 | + { |
2248 | + thread.executeOnThread<bool>([this]() { |
2249 | + for (auto testcase : testCases) |
2250 | + { |
2251 | + testcase.connection.reset(); /* ensure these get dropped on teh thread */ |
2252 | + } |
2253 | + socketService.reset(); |
2254 | + |
2255 | + return true; |
2256 | + }); |
2257 | + thread.quit(); |
2258 | + } |
2259 | + |
2260 | + /** Check to see if the mock was used successfully */ |
2261 | + inline void result() |
2262 | + { |
2263 | + /* Ensure we get queued events off the mainloop */ |
2264 | + std::promise<void> promise; |
2265 | + thread.timeout(std::chrono::milliseconds{10}, [&promise]() { promise.set_value(); }); |
2266 | + promise.get_future().wait(); |
2267 | + |
2268 | + for (auto testcase : testCases) |
2269 | + { |
2270 | + EXPECT_EQ(testcase.input, testcase.result); |
2271 | + } |
2272 | + |
2273 | + EXPECT_EQ(0, extraCases.size()); |
2274 | + |
2275 | + for (auto testcase : extraCases) |
2276 | + { |
2277 | + EXPECT_EQ(std::string{}, testcase.result); |
2278 | + } |
2279 | + } |
2280 | + |
2281 | +private: |
2282 | + GLib::ContextThread thread; |
2283 | + std::shared_ptr<GSocketService> socketService; |
2284 | + |
2285 | + struct TestCase |
2286 | + { |
2287 | + std::string input; |
2288 | + std::string output; |
2289 | + std::string result; |
2290 | + std::shared_ptr<GSocketConnection> connection; |
2291 | + }; |
2292 | + |
2293 | + std::list<TestCase> testCases; |
2294 | + std::list<TestCase> extraCases; |
2295 | + |
2296 | + static gboolean serviceConnectedStatic(GSocketService *service, |
2297 | + GSocketConnection *connection, |
2298 | + GObject *source_obj, |
2299 | + gpointer userdata) noexcept |
2300 | + { |
2301 | + auto obj = reinterpret_cast<SnapdMock *>(userdata); |
2302 | + auto cppconn = std::shared_ptr<GSocketConnection>(G_SOCKET_CONNECTION(g_object_ref(connection)), |
2303 | + [](GSocketConnection *con) { g_clear_object(&con); }); |
2304 | + return obj->serviceConnected(cppconn) ? TRUE : FALSE; |
2305 | + } |
2306 | + |
2307 | + bool serviceConnected(std::shared_ptr<GSocketConnection> connection) |
2308 | + { |
2309 | + for (auto &testcase : testCases) |
2310 | + { |
2311 | + if (testcase.connection) |
2312 | + { |
2313 | + /* We don't want ones that already have a connection */ |
2314 | + continue; |
2315 | + } |
2316 | + |
2317 | + testcase.connection = connection; |
2318 | + |
2319 | + auto input = g_io_stream_get_input_stream(G_IO_STREAM(connection.get())); // transfer: none |
2320 | + g_input_stream_read_bytes_async(input, /* stream */ |
2321 | + 1024, /* 1K at a time */ |
2322 | + G_PRIORITY_DEFAULT, /* default priority */ |
2323 | + thread.getCancellable().get(), /* cancel */ |
2324 | + caseInputStatic, /* callback */ |
2325 | + &testcase); |
2326 | + |
2327 | + auto output = g_io_stream_get_output_stream(G_IO_STREAM(connection.get())); // transfer: none |
2328 | + if (output == nullptr) |
2329 | + { |
2330 | + g_warning("No output stream avilable with connection!"); |
2331 | + } |
2332 | + |
2333 | + g_output_stream_write_all_async( |
2334 | + output, /* output stream */ |
2335 | + testcase.output.c_str(), /* data */ |
2336 | + testcase.output.size(), /* size */ |
2337 | + G_PRIORITY_DEFAULT, /* priority */ |
2338 | + thread.getCancellable().get(), /* cancel */ |
2339 | + [](GObject *obj, GAsyncResult *res, gpointer userdata) -> void { |
2340 | + auto testcase = reinterpret_cast<TestCase *>(userdata); |
2341 | + gsize bytesout = 0; |
2342 | + GError *error = nullptr; |
2343 | + |
2344 | + g_output_stream_write_all_finish(G_OUTPUT_STREAM(obj), res, &bytesout, &error); |
2345 | + |
2346 | + if (error != nullptr) |
2347 | + { |
2348 | + g_warning("Unable to write out snapd connection: %s", error->message); |
2349 | + g_error_free(error); |
2350 | + return; |
2351 | + } |
2352 | + |
2353 | + if (bytesout != testcase->output.size()) |
2354 | + { |
2355 | + g_warning("Wrote out %d bytes in snapd socket but expected to write out %d", int(bytesout), |
2356 | + int(testcase->output.size())); |
2357 | + } |
2358 | + |
2359 | + g_output_stream_close(G_OUTPUT_STREAM(obj), nullptr, nullptr); |
2360 | + checkConnection(testcase); |
2361 | + }, /* callback */ |
2362 | + &testcase); /* expected size */ |
2363 | + |
2364 | + /* We got this one */ |
2365 | + return true; |
2366 | + } |
2367 | + |
2368 | + g_warning("Couldn't find a test case to use for the connection"); |
2369 | + return false; |
2370 | + } |
2371 | + |
2372 | + static void caseInputStatic(GObject *obj, GAsyncResult *res, gpointer userdata) noexcept |
2373 | + { |
2374 | + auto testcase = reinterpret_cast<TestCase *>(userdata); |
2375 | + GError *error = nullptr; |
2376 | + auto bytes = g_input_stream_read_bytes_finish(G_INPUT_STREAM(obj), res, &error); |
2377 | + |
2378 | + if (error != nullptr) |
2379 | + { |
2380 | + g_warning("Error reading input socket: %s", error->message); |
2381 | + g_error_free(error); |
2382 | + return; |
2383 | + } |
2384 | + |
2385 | + auto bytessize = g_bytes_get_size(bytes); |
2386 | + if (bytessize > 0) // zero means closed |
2387 | + { |
2388 | + auto data = reinterpret_cast<const char *>(g_bytes_get_data(bytes, nullptr)); |
2389 | + |
2390 | + for (unsigned int i = 0; i < bytessize; i++) |
2391 | + { |
2392 | + testcase->result.push_back(data[i]); |
2393 | + } |
2394 | + |
2395 | + g_input_stream_read_bytes_async(G_INPUT_STREAM(obj), /* stream */ |
2396 | + 1024, /* 1K at a time */ |
2397 | + G_PRIORITY_DEFAULT, /* default priority */ |
2398 | + nullptr, /* TODO? cancel */ |
2399 | + caseInputStatic, /* callback */ |
2400 | + userdata); |
2401 | + } |
2402 | + else |
2403 | + { |
2404 | + // g_debug("Request: %s", testcase->result.c_str()); |
2405 | + g_input_stream_close(G_INPUT_STREAM(obj), nullptr, nullptr); |
2406 | + checkConnection(testcase); |
2407 | + } |
2408 | + |
2409 | + g_bytes_unref(bytes); |
2410 | + } |
2411 | + |
2412 | + static void checkConnection(TestCase *testcase) |
2413 | + { |
2414 | + auto input = g_io_stream_get_input_stream(G_IO_STREAM(testcase->connection.get())); // transfer: none |
2415 | + auto output = g_io_stream_get_output_stream(G_IO_STREAM(testcase->connection.get())); // transfer: none |
2416 | + |
2417 | + if (g_input_stream_is_closed(input) && g_output_stream_is_closed(output)) |
2418 | + { |
2419 | + g_io_stream_close(G_IO_STREAM(testcase->connection.get()), nullptr, nullptr); |
2420 | + } |
2421 | + } |
2422 | + |
2423 | +public: |
2424 | + static std::string httpJsonResponse(const std::string &json) |
2425 | + { |
2426 | + return "HTTP/1.1 200 OK\r\n" /* okay */ |
2427 | + "Content-Type: application/json\r\n" /* json stuff */ |
2428 | + "Content-Length: " + |
2429 | + std::to_string(json.size()) + "\r\n\r\n" + /* size of data */ |
2430 | + json; |
2431 | + } |
2432 | + |
2433 | + static std::string snapdOkay(const std::string &result) |
2434 | + { |
2435 | + return "{ 'status': 'OK', 'status-code': 200, " /* all okay */ |
2436 | + " 'type': 'sync', " /* This lib is only sync */ |
2437 | + " 'result': " + |
2438 | + result + /* here is what we were given */ |
2439 | + "}"; |
2440 | + } |
2441 | + |
2442 | + static std::string packageJson(const std::string &name, |
2443 | + const std::string &status, |
2444 | + const std::string &type, |
2445 | + const std::string &version, |
2446 | + const std::string &revision, |
2447 | + std::list<std::string> apps) |
2448 | + { |
2449 | + std::string response = "{\n"; |
2450 | + |
2451 | + response += "'name': '" + name + "',\n"; |
2452 | + response += "'status': '" + status + "',\n"; |
2453 | + response += "'type': '" + type + "',\n"; |
2454 | + response += "'version': '" + version + "',\n"; |
2455 | + response += "'revision': '" + revision + "',\n"; |
2456 | + |
2457 | + response += "'apps': [ " + std::accumulate(apps.begin(), apps.end(), std::string{}, |
2458 | + [](const std::string &builder, std::string entry) { |
2459 | + std::string json = "\n { 'name': '" + entry + "' }"; |
2460 | + if (builder.empty()) |
2461 | + { |
2462 | + return json; |
2463 | + } |
2464 | + else |
2465 | + { |
2466 | + return builder + "," + json; |
2467 | + } |
2468 | + }) + |
2469 | + "\n]\n"; |
2470 | + |
2471 | + return response + "}"; |
2472 | + } |
2473 | + |
2474 | + struct SnapdPlug |
2475 | + { |
2476 | + std::string interface; |
2477 | + std::string snap; |
2478 | + std::list<std::string> apps; |
2479 | + }; |
2480 | + |
2481 | + static std::string interfacesJson(const std::list<SnapdPlug> &plugs) |
2482 | + { |
2483 | + std::string response = "{\n"; |
2484 | + |
2485 | + response += "'plugs': [ " + |
2486 | + std::accumulate(plugs.begin(), plugs.end(), std::string{}, |
2487 | + [](const std::string &builder, SnapdPlug plug) { |
2488 | + std::string json = "\n{\n"; |
2489 | + |
2490 | + json += "'interface': '" + plug.interface + "',\n"; |
2491 | + json += "'snap': '" + plug.snap + "',\n"; |
2492 | + |
2493 | + json += "'apps': [ " + |
2494 | + std::accumulate(plug.apps.begin(), plug.apps.end(), std::string{}, |
2495 | + [](const std::string &builder, std::string entry) { |
2496 | + std::string json = "'" + entry + "'"; |
2497 | + if (builder.empty()) |
2498 | + { |
2499 | + return json; |
2500 | + } |
2501 | + else |
2502 | + { |
2503 | + return builder + ", " + json; |
2504 | + } |
2505 | + }) + |
2506 | + " ]\n"; |
2507 | + |
2508 | + json += "}"; |
2509 | + |
2510 | + if (builder.empty()) |
2511 | + { |
2512 | + return json; |
2513 | + } |
2514 | + else |
2515 | + { |
2516 | + return builder + "," + json; |
2517 | + } |
2518 | + }) + |
2519 | + "\n],\n"; |
2520 | + |
2521 | + response += "'slots': [ { 'foo': 'bar' } ]\n"; /* TODO: For future use */ |
2522 | + |
2523 | + return response + "}"; |
2524 | + } |
2525 | +}; |
2526 | |
2527 | === modified file 'upstart-jobs/CMakeLists.txt' |
2528 | --- upstart-jobs/CMakeLists.txt 2014-08-30 02:31:30 +0000 |
2529 | +++ upstart-jobs/CMakeLists.txt 2016-09-13 17:38:18 +0000 |
2530 | @@ -24,6 +24,14 @@ |
2531 | add_test(application-click.conf.test "${CMAKE_CURRENT_SOURCE_DIR}/test-conffile.sh" "${CMAKE_CURRENT_BINARY_DIR}/application-click.conf") |
2532 | |
2533 | #################### |
2534 | +# application-snap.conf |
2535 | +#################### |
2536 | + |
2537 | +configure_file("application-snap.conf.in" "${CMAKE_CURRENT_BINARY_DIR}/application-snap.conf" @ONLY) |
2538 | +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/application-snap.conf" DESTINATION "${CMAKE_INSTALL_DATADIR}/upstart/sessions") |
2539 | +add_test(application-snap.conf.test "${CMAKE_CURRENT_SOURCE_DIR}/test-conffile.sh" "${CMAKE_CURRENT_BINARY_DIR}/application-snap.conf") |
2540 | + |
2541 | +#################### |
2542 | # application-failed.conf |
2543 | #################### |
2544 | |
2545 | |
2546 | === modified file 'upstart-jobs/application-failed.conf.in' |
2547 | --- upstart-jobs/application-failed.conf.in 2014-01-22 16:28:06 +0000 |
2548 | +++ upstart-jobs/application-failed.conf.in 2016-09-13 17:38:18 +0000 |
2549 | @@ -1,6 +1,6 @@ |
2550 | description "Application Failing" |
2551 | |
2552 | -start on stopped application-legacy RESULT=failed or stopped application-click RESULT=failed |
2553 | +start on stopped application-legacy RESULT=failed or stopped application-click RESULT=failed or stopped application-snap RESULT=failed |
2554 | task |
2555 | |
2556 | exec @pkglibexecdir@/application-failed |
2557 | |
2558 | === modified file 'upstart-jobs/application-logrotate.conf' |
2559 | --- upstart-jobs/application-logrotate.conf 2014-08-30 02:23:36 +0000 |
2560 | +++ upstart-jobs/application-logrotate.conf 2016-09-13 17:38:18 +0000 |
2561 | @@ -11,4 +11,5 @@ |
2562 | # |
2563 | find ${logdir} -mtime +2 -name "application-click-*.log.[1-9].gz" -delete |
2564 | find ${logdir} -mtime +2 -name "application-legacy-*.log.[1-9].gz" -delete |
2565 | + find ${logdir} -mtime +2 -name "application-snap-*.log.[1-9].gz" -delete |
2566 | end script |
2567 | |
2568 | === added file 'upstart-jobs/application-snap.conf.in' |
2569 | --- upstart-jobs/application-snap.conf.in 1970-01-01 00:00:00 +0000 |
2570 | +++ upstart-jobs/application-snap.conf.in 2016-09-13 17:38:18 +0000 |
2571 | @@ -0,0 +1,38 @@ |
2572 | +description "Application Launching for Snap Applications" |
2573 | +author "Ted Gould <ted@canonical.com>" |
2574 | + |
2575 | +instance ${APP_ID}-${INSTANCE_ID} |
2576 | + |
2577 | +start on application-snap-start |
2578 | +stop on application-end or desktop-end |
2579 | + |
2580 | +env APP_ID |
2581 | +env APP_EXEC |
2582 | +env APP_URIS |
2583 | +env APP_DIR |
2584 | +env APP_DESKTOP_FILE_PATH |
2585 | +env APP_XMIR_ENABLE |
2586 | +env INSTANCE_ID="" |
2587 | + |
2588 | +env UBUNTU_APP_LAUNCH_ARCH="@ubuntu_app_launch_arch@" |
2589 | +export UBUNTU_APP_LAUNCH_ARCH |
2590 | + |
2591 | +# apparmor is taken care of by confine |
2592 | +cgroup freezer |
2593 | + |
2594 | +# Initial OOM Score |
2595 | +oom score 100 |
2596 | + |
2597 | +# Remember, this is confined |
2598 | +exec @pkglibexecdir@/exec-line-exec |
2599 | + |
2600 | +post-start exec @pkglibexecdir@/zg-report-app open |
2601 | +post-stop script |
2602 | + @pkglibexecdir@/zg-report-app close |
2603 | + @pkglibexecdir@/cgroup-reap-all |
2604 | + |
2605 | + DEVELOPER_MODE=`gdbus call --system --dest com.canonical.PropertyService --object-path /com/canonical/PropertyService --method com.canonical.PropertyService.GetProperty adb` |
2606 | + if [ "$DEVELOPER_MODE" != "(true,)" ] ; then |
2607 | + rm -f ${HOME}/.cache/upstart/application-snap-${APP_ID}-${INSTANCE_ID}.log* |
2608 | + fi |
2609 | +end script |
FAILED: Continuous integration, rev:346 /jenkins. canonical. com/unity- api-1/job/ lp-ubuntu- app-launch- ci/31/ /jenkins. canonical. com/unity- api-1/job/ build/302/ console /jenkins. canonical. com/unity- api-1/job/ build-0- fetch/308 /jenkins. canonical. com/unity- api-1/job/ build-1- sourcepkg/ release= vivid+overlay/ 237 /jenkins. canonical. com/unity- api-1/job/ build-1- sourcepkg/ release= xenial+ overlay/ 237 /jenkins. canonical. com/unity- api-1/job/ build-1- sourcepkg/ release= yakkety/ 237 /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=amd64, release= vivid+overlay/ 166/console /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=amd64, release= xenial+ overlay/ 166/console /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=amd64, release= yakkety/ 166/console /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=armhf, release= vivid+overlay/ 166/console /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=armhf, release= xenial+ overlay/ 166/console /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=armhf, release= yakkety/ 166/console /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=i386, release= vivid+overlay/ 166/console /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=i386, release= xenial+ overlay/ 166/console /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=i386, release= yakkety/ 166/console
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
Click here to trigger a rebuild: /jenkins. canonical. com/unity- api-1/job/ lp-ubuntu- app-launch- ci/31/rebuild
https:/