Merge lp:~bregma/libertine/scan-desktop-files into lp:libertine
- scan-desktop-files
- Merge into devel
Status: | Merged |
---|---|
Approved by: | Christopher Townsend |
Approved revision: | 161 |
Merged at revision: | 153 |
Proposed branch: | lp:~bregma/libertine/scan-desktop-files |
Merge into: | lp:libertine |
Diff against target: |
719 lines (+475/-51) 10 files modified
debian/python3-libertine.install (+2/-1) python/libertine/AppDiscovery.py (+220/-0) python/libertine/ChrootContainer.py (+42/-42) python/libertine/Libertine.py (+59/-6) python/libertine/LxcContainer.py (+1/-0) python/libertine/utils.py (+7/-2) tests/unit/CMakeLists.txt (+6/-0) tests/unit/test_app_discovery.py (+52/-0) tests/unit/test_libertine_container.py (+62/-0) tools/libertine-container-manager (+24/-0) |
To merge this branch: | bzr merge lp:~bregma/libertine/scan-desktop-files |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Christopher Townsend | Approve | ||
Libertine CI Bot | continuous-integration | Approve | |
Review via email: mp+280613@code.launchpad.net |
Commit message
added scanning for launchable applications in a container
Description of the change
Adds a function to the libertine-
Libertine CI Bot (libertine-ci-bot) wrote : | # |
Libertine CI Bot (libertine-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:152
http://
Executed test runs:
None: https:/
Click here to trigger a rebuild:
http://
Christopher Townsend (townsend) wrote : | # |
As discussed on IRC, mb-panel-
Icon=mbpanelmgr.png
but the output of list-apps has this:
{
"name": "Panel Manager",
}
Nothing in "icons", so this is still not working correctly.
I'm sure there will be other corner cases, but we might as well fix what we know about:)
- 153. By Stephen M. Webb
-
synched trunk again
- 154. By Stephen M. Webb
-
added unit tests
Libertine CI Bot (libertine-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:154
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
- 155. By Stephen M. Webb
-
extended test environment
Libertine CI Bot (libertine-ci-bot) wrote : | # |
PASSED: Continuous integration, rev:155
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
- 156. By Stephen M. Webb
-
fixed some typos
Libertine CI Bot (libertine-ci-bot) wrote : | # |
PASSED: Continuous integration, rev:156
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
- 157. By Stephen M. Webb
-
added a non-JSON default output for list-apps
Libertine CI Bot (libertine-ci-bot) wrote : | # |
PASSED: Continuous integration, rev:157
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
- 158. By Stephen M. Webb
-
used actual container name and root path when discovering app launchers
- 159. By Stephen M. Webb
-
added some unit tests for LibertineContainer
Libertine CI Bot (libertine-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:159
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
- 160. By Stephen M. Webb
-
used liblibertine to retrieve the container name
Libertine CI Bot (libertine-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:160
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
- 161. By Stephen M. Webb
-
allowed the config file to not exist
Libertine CI Bot (libertine-ci-bot) wrote : | # |
PASSED: Continuous integration, rev:161
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
Christopher Townsend (townsend) wrote : | # |
Ok, looks good now.
Preview Diff
1 | === modified file 'debian/python3-libertine.install' |
2 | --- debian/python3-libertine.install 2015-11-30 14:37:36 +0000 |
3 | +++ debian/python3-libertine.install 2015-12-23 02:34:33 +0000 |
4 | @@ -1,3 +1,4 @@ |
5 | -usr/lib/python*/*/libertine/__init__.py |
6 | +usr/lib/python*/*/libertine/AppDiscovery.py |
7 | usr/lib/python*/*/libertine/Libertine.py |
8 | usr/lib/python*/*/libertine/utils.py |
9 | +usr/lib/python*/*/libertine/__init__.py |
10 | |
11 | === added file 'python/libertine/AppDiscovery.py' |
12 | --- python/libertine/AppDiscovery.py 1970-01-01 00:00:00 +0000 |
13 | +++ python/libertine/AppDiscovery.py 2015-12-23 02:34:33 +0000 |
14 | @@ -0,0 +1,220 @@ |
15 | +# Copyright 2015 Canonical Ltd. |
16 | +# |
17 | +# This program is free software: you can redistribute it and/or modify it |
18 | +# under the terms of the GNU General Public License version 3, as published |
19 | +# by the Free Software Foundation. |
20 | +# |
21 | +# This program is distributed in the hope that it will be useful, but |
22 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
23 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
24 | +# PURPOSE. See the GNU General Public License for more details. |
25 | +# |
26 | +# You should have received a copy of the GNU General Public License along |
27 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
28 | + |
29 | +from . import utils |
30 | +from xdg.BaseDirectory import xdg_data_dirs |
31 | +import configparser |
32 | +import glob |
33 | +import io |
34 | +import json |
35 | +import os |
36 | +import re |
37 | +import sys |
38 | + |
39 | + |
40 | +class IconCache(object): |
41 | + """ |
42 | + Caches the names of all icon files available in the standard places (possibly |
43 | + in a container) and provides a search function to deliver icon file names |
44 | + matching a given icon name. |
45 | + |
46 | + See the `Freedesktop.org icon theme specification |
47 | + <http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html>`_ |
48 | + for the detailed specification. |
49 | + """ |
50 | + |
51 | + def __init__(self, root_path, file_loader=None): |
52 | + """ |
53 | + :param root_path: Where to start the file scan. |
54 | + :type root_path: A valid filesystem path string. |
55 | + :param file_loader: A function that builds a cache of filenames. |
56 | + :type file_loader: Function returning a list of filenames. |
57 | + """ |
58 | + if file_loader: |
59 | + self._icon_cache = file_loader(root_path) |
60 | + else: |
61 | + self._icon_cache = self._file_loader(root_path) |
62 | + |
63 | + def _get_icon_search_paths(self, root_path): |
64 | + """ |
65 | + Gets a list of paths on which to search for icons. |
66 | + |
67 | + :param root_path: Where to start the file scan. |
68 | + :type root_path: A valid filesystem path string. |
69 | + :rtype: A list of filesystem patch to serarch for qualifying icon files. |
70 | + """ |
71 | + icon_search_paths = [] |
72 | + icon_search_paths.append(os.path.join(root_path, os.environ['HOME'], ".icons")) |
73 | + for d in reversed(xdg_data_dirs): |
74 | + icon_search_paths.append(os.path.join(root_path, d.lstrip('/'), "icons")) |
75 | + icon_search_paths.append(os.path.join(root_path, "usr/share/pixmaps")) |
76 | + return icon_search_paths |
77 | + |
78 | + def _file_loader(self, root_path): |
79 | + """ |
80 | + Loads a cache of file names by scanning the filesystem rooted at |
81 | + ``root_path``. |
82 | + |
83 | + :param root_path: Where to start the file scan. |
84 | + :type root_path: A valid filesystem path. |
85 | + :rtype: A list of fully-qualified file paths. |
86 | + """ |
87 | + file_names = [] |
88 | + pattern = re.compile(r".*\.(png|svg|xpm)") |
89 | + for path in self._get_icon_search_paths(root_path): |
90 | + for base, dirs, files in os.walk(path): |
91 | + for file in files: |
92 | + if pattern.match(file): |
93 | + file_names.append(os.path.join(base, file)) |
94 | + return file_names |
95 | + |
96 | + def _find_icon_files(self, icon_name): |
97 | + """ |
98 | + Finds a list of file name strings matching the given icon name. |
99 | + |
100 | + :param icon_name: An icon name, pobably from a .desktop file. |
101 | + :rtype: A list of filename strings matching the icon name. |
102 | + """ |
103 | + icon_file_names = [] |
104 | + match_string = r"/" + os.path.splitext(icon_name)[0] + r"\....$" |
105 | + pattern = re.compile(match_string) |
106 | + for icon_file in self._icon_cache: |
107 | + if pattern.search(icon_file): |
108 | + icon_file_names.append(icon_file) |
109 | + return icon_file_names |
110 | + |
111 | + def expand_icons(self, desktop_icon_list): |
112 | + """ |
113 | + Expands a string containing a list of icon names into a list of matching |
114 | + file names. |
115 | + |
116 | + :param desktop_icon_list: A string containing a list of icon names |
117 | + separated by semicolons. |
118 | + :rtype: A list of filename stings matching the icon names passed in. |
119 | + |
120 | + See `the Freedesktop.org desktop file specification |
121 | + <http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#recognized-keys>`_ |
122 | + for more information. |
123 | + """ |
124 | + if desktop_icon_list: |
125 | + icon_list = desktop_icon_list.split(';') |
126 | + if icon_list[0][0] == '/': |
127 | + return icon_list |
128 | + icon_files = [] |
129 | + for i in icon_list: |
130 | + icon_files += self._find_icon_files(i) |
131 | + return icon_files |
132 | + return [] |
133 | + |
134 | + |
135 | +def expand_mime_types(desktop_mime_types): |
136 | + if desktop_mime_types: |
137 | + return desktop_mime_types.split(';') |
138 | + return [] |
139 | + |
140 | + |
141 | +class AppInfo(object): |
142 | + |
143 | + def __init__(self, config_entry, icon_cache): |
144 | + self.name = config_entry.get('Name') |
145 | + if not self.name: |
146 | + raise RuntimeError("required Name attribute is missing") |
147 | + d = config_entry.get('NoDisplay') |
148 | + self.no_display = (d != None and d == 'true') |
149 | + self.exec_line = config_entry.get('Exec') |
150 | + if not self.exec_line: |
151 | + raise RuntimeError("required Exec attribute is missing") |
152 | + self.icons = icon_cache.expand_icons(config_entry.get('Icon')) |
153 | + self.mime_types = expand_mime_types(config_entry.get('MimeType')) |
154 | + |
155 | + def __str__(self): |
156 | + with io.StringIO() as ostr: |
157 | + print(self.name, file=ostr) |
158 | + print(" no-display={}".format(self.no_display), file=ostr) |
159 | + print(" exec='{}'".format(self.exec_line), file=ostr) |
160 | + for icon in self.icons: |
161 | + print(" icon: {}".format(icon), file=ostr) |
162 | + for mime in self.mime_types: |
163 | + print(" mime: {}".format(mime), file=ostr) |
164 | + return ostr.getvalue() |
165 | + |
166 | + def to_json(self): |
167 | + return json.dumps(self.__dict__) |
168 | + |
169 | + |
170 | +def desktop_file_is_showable(desktop_entry): |
171 | + """ |
172 | + Determines if a particular application entry should be reported: the entry |
173 | + can not be hidden and must be showable in Unity. |
174 | + """ |
175 | + t = desktop_entry.get('Type') |
176 | + if t != 'Application': |
177 | + return False |
178 | + n = desktop_entry.get('Hidden') |
179 | + if n and n == 'true': |
180 | + return False |
181 | + n = desktop_entry.get('NoShowIn') |
182 | + if n: |
183 | + targets = n.split(';') |
184 | + if 'Unity' in targets: |
185 | + return False |
186 | + n = desktop_entry.get('OnlyShowIn') |
187 | + if n: |
188 | + targets = n.split(';') |
189 | + if 'Unity' not in targets: |
190 | + return False |
191 | + return True |
192 | + |
193 | + |
194 | +def get_app_info(desktop_path, icon_cache): |
195 | + for desktop_file_name in glob.glob(desktop_path): |
196 | + desktop_file = configparser.ConfigParser(strict=False, interpolation=None) |
197 | + try: |
198 | + desktop_file.read(desktop_file_name) |
199 | + desktop_entry = desktop_file['Desktop Entry'] |
200 | + if desktop_file_is_showable(desktop_entry): |
201 | + yield AppInfo(desktop_entry, icon_cache) |
202 | + except Exception as ex: |
203 | + print("error processing {}: {}".format(desktop_file_name, ex), file=sys.stderr) |
204 | + |
205 | + |
206 | +class AppLauncherCache(object): |
207 | + """ |
208 | + Caches a list of application launcher information (derived from .desktop |
209 | + files installed in a container. |
210 | + """ |
211 | + |
212 | + def __init__(self, name, root_path): |
213 | + self.name = name |
214 | + self.app_launchers = [] |
215 | + icon_cache = IconCache(root_path) |
216 | + for dir in reversed(xdg_data_dirs): |
217 | + path = os.path.join(root_path, dir.lstrip('/'), "applications") |
218 | + for app_info in get_app_info(os.path.join(path, "*.desktop"), icon_cache): |
219 | + self.app_launchers.append(app_info) |
220 | + |
221 | + def __str__(self): |
222 | + with io.StringIO() as ostr: |
223 | + print("{}\n".format(self.name), file=ostr) |
224 | + for app_info in self.app_launchers: |
225 | + print(" {}".format(app_info), file=ostr) |
226 | + return ostr.getvalue() |
227 | + |
228 | + def to_json(self): |
229 | + return json.dumps(self, |
230 | + default=lambda o: o.__dict__, |
231 | + sort_keys=True, |
232 | + indent=4) |
233 | + |
234 | + |
235 | |
236 | === modified file 'python/libertine/ChrootContainer.py' |
237 | --- python/libertine/ChrootContainer.py 2015-12-14 18:08:13 +0000 |
238 | +++ python/libertine/ChrootContainer.py 2015-12-23 02:34:33 +0000 |
239 | @@ -40,36 +40,6 @@ |
240 | os.chown(path, uid, gid) |
241 | |
242 | |
243 | -def build_proot_command(container_id): |
244 | - proot_cmd = '/usr/bin/proot' |
245 | - if not os.path.isfile(proot_cmd) or not os.access(proot_cmd, os.X_OK): |
246 | - raise RuntimeError('executable proot not found') |
247 | - proot_cmd += " -R " + utils.get_libertine_container_rootfs_path(container_id) |
248 | - |
249 | - # Bind-mount the host's locale(s) |
250 | - proot_cmd += " -b /usr/lib/locale" |
251 | - |
252 | - # Bind-mount extrausers on the phone |
253 | - if os.path.exists("/var/lib/extrausers"): |
254 | - proot_cmd += " -b /var/lib/extrausers" |
255 | - |
256 | - home_path = os.environ['HOME'] |
257 | - |
258 | - # Bind-mount common XDG direcotries |
259 | - bind_mounts = ( |
260 | - " -b %s:%s" |
261 | - % (utils.get_libertine_container_userdata_dir_path(container_id), home_path) |
262 | - ) |
263 | - |
264 | - xdg_user_dirs = ['Documents', 'Music', 'Pictures', 'Videos'] |
265 | - for user_dir in xdg_user_dirs: |
266 | - user_dir_path = os.path.join(home_path, user_dir) |
267 | - bind_mounts += " -b %s:%s" % (user_dir_path, user_dir_path) |
268 | - |
269 | - proot_cmd += bind_mounts |
270 | - return proot_cmd |
271 | - |
272 | - |
273 | class LibertineChroot(BaseContainer): |
274 | """ |
275 | A concrete container type implemented using a plain old chroot. |
276 | @@ -77,7 +47,7 @@ |
277 | |
278 | def __init__(self, container_id): |
279 | super().__init__(container_id) |
280 | - self.chroot_path = utils.get_libertine_container_rootfs_path(container_id) |
281 | + self.container_type = "chroot" |
282 | os.environ['FAKECHROOT_CMD_SUBST'] = '$FAKECHROOT_CMD_SUBST:/usr/bin/chfn=/bin/true' |
283 | os.environ['DEBIAN_FRONTEND'] = 'noninteractive' |
284 | |
285 | @@ -87,35 +57,35 @@ |
286 | proot_cmd = '/usr/bin/proot' |
287 | if not os.path.isfile(proot_cmd) or not os.access(proot_cmd, os.X_OK): |
288 | raise RuntimeError('executable proot not found') |
289 | - command_prefix = proot_cmd + " -b /usr/lib/locale -S " + self.chroot_path |
290 | + command_prefix = proot_cmd + " -b /usr/lib/locale -S " + self.root_path |
291 | else: |
292 | - command_prefix = "fakechroot fakeroot chroot " + self.chroot_path |
293 | + command_prefix = "fakechroot fakeroot chroot " + self.root_path |
294 | args = shlex.split(command_prefix + ' ' + command_string) |
295 | cmd = subprocess.Popen(args) |
296 | return cmd.wait() |
297 | |
298 | def destroy_libertine_container(self): |
299 | - shutil.rmtree(self.chroot_path) |
300 | + shutil.rmtree(self.root_path) |
301 | |
302 | def create_libertine_container(self, password=None, verbosity=1): |
303 | installed_release = self.get_container_distro(self.container_id) |
304 | |
305 | # Create the actual chroot |
306 | if installed_release == "trusty": |
307 | - command_line = "debootstrap --verbose " + installed_release + " " + self.chroot_path |
308 | + command_line = "debootstrap --verbose " + installed_release + " " + self.root_path |
309 | else: |
310 | command_line = "fakechroot fakeroot debootstrap --verbose --variant=fakechroot {} {}".format( |
311 | - installed_release, self.chroot_path) |
312 | + installed_release, self.root_path) |
313 | args = shlex.split(command_line) |
314 | subprocess.Popen(args).wait() |
315 | |
316 | # Remove symlinks as they can ill-behaved recursive behavior in the chroot |
317 | if installed_release != "trusty": |
318 | print("Fixing chroot symlinks...") |
319 | - os.remove(os.path.join(self.chroot_path, 'dev')) |
320 | - os.remove(os.path.join(self.chroot_path, 'proc')) |
321 | + os.remove(os.path.join(self.root_path, 'dev')) |
322 | + os.remove(os.path.join(self.root_path, 'proc')) |
323 | |
324 | - with open(os.path.join(self.chroot_path, 'usr', 'sbin', 'policy-rc.d'), 'w+') as fd: |
325 | + with open(os.path.join(self.root_path, 'usr', 'sbin', 'policy-rc.d'), 'w+') as fd: |
326 | fd.write("#!/bin/sh\n\n") |
327 | fd.write("while true; do\n") |
328 | fd.write("case \"$1\" in\n") |
329 | @@ -134,7 +104,7 @@ |
330 | |
331 | if verbosity == 1: |
332 | print("Updating chroot's sources.list entries...") |
333 | - with open(os.path.join(self.chroot_path, 'etc', 'apt', 'sources.list'), 'a') as fd: |
334 | + with open(os.path.join(self.root_path, 'etc', 'apt', 'sources.list'), 'a') as fd: |
335 | fd.write(archive + installed_release + " universe\n") |
336 | fd.write(archive + installed_release + "-updates main\n") |
337 | fd.write(archive + installed_release + "-updates universe\n") |
338 | @@ -147,7 +117,7 @@ |
339 | proot_cmd = '/usr/bin/proot' |
340 | if not os.path.isfile(proot_cmd) or not os.access(proot_cmd, os.X_OK): |
341 | raise RuntimeError('executable proot not found') |
342 | - cmd_line_prefix = proot_cmd + " -b /usr/lib/locale -S " + self.chroot_path |
343 | + cmd_line_prefix = proot_cmd + " -b /usr/lib/locale -S " + self.root_path |
344 | |
345 | command_line = cmd_line_prefix + " dpkg-divert --local --rename --add /etc/init.d/systemd-logind" |
346 | args = shlex.split(command_line) |
347 | @@ -193,8 +163,38 @@ |
348 | # Check if the container was created as root and chown the user directories as necessary |
349 | chown_recursive_dirs(utils.get_libertine_container_userdata_dir_path(self.container_id)) |
350 | |
351 | + def _build_proot_command(self): |
352 | + proot_cmd = '/usr/bin/proot' |
353 | + if not os.path.isfile(proot_cmd) or not os.access(proot_cmd, os.X_OK): |
354 | + raise RuntimeError('executable proot not found') |
355 | + |
356 | + proot_cmd += " -R " + self.root_path |
357 | + |
358 | + # Bind-mount the host's locale(s) |
359 | + proot_cmd += " -b /usr/lib/locale" |
360 | + |
361 | + # Bind-mount extrausers on the phone |
362 | + if os.path.exists("/var/lib/extrausers"): |
363 | + proot_cmd += " -b /var/lib/extrausers" |
364 | + |
365 | + home_path = os.environ['HOME'] |
366 | + |
367 | + # Bind-mount common XDG direcotries |
368 | + bind_mounts = ( |
369 | + " -b %s:%s" |
370 | + % (utils.get_libertine_container_userdata_dir_path(self.container_id), home_path) |
371 | + ) |
372 | + |
373 | + xdg_user_dirs = ['Documents', 'Music', 'Pictures', 'Videos'] |
374 | + for user_dir in xdg_user_dirs: |
375 | + user_dir_path = os.path.join(home_path, user_dir) |
376 | + bind_mounts += " -b %s:%s" % (user_dir_path, user_dir_path) |
377 | + |
378 | + proot_cmd += bind_mounts |
379 | + return proot_cmd |
380 | + |
381 | def launch_application(self, app_exec_line): |
382 | - proot_cmd = build_proot_command(self.container_id) |
383 | + proot_cmd = self._build_proot_command() |
384 | |
385 | args = shlex.split(proot_cmd) |
386 | args.extend(utils.setup_window_manager(self.container_id)) |
387 | |
388 | === modified file 'python/libertine/Libertine.py' |
389 | --- python/libertine/Libertine.py 2015-12-18 21:40:39 +0000 |
390 | +++ python/libertine/Libertine.py 2015-12-23 02:34:33 +0000 |
391 | @@ -12,6 +12,8 @@ |
392 | # You should have received a copy of the GNU General Public License along |
393 | # with this program. If not, see <http://www.gnu.org/licenses/>. |
394 | |
395 | +from .AppDiscovery import AppLauncherCache |
396 | +from gi.repository import Libertine |
397 | import abc |
398 | import contextlib |
399 | import json |
400 | @@ -23,12 +25,16 @@ |
401 | Retrieves the type of container for a given container ID. |
402 | :param container_id: The Container ID to search for. |
403 | """ |
404 | - with open(libertine.utils.get_libertine_database_file_path()) as fd: |
405 | - container_list = json.load(fd) |
406 | - |
407 | - for container in container_list["containerList"]: |
408 | - if container["id"] == container_id: |
409 | - return container["type"] |
410 | + try: |
411 | + with open(libertine.utils.get_libertine_database_file_path()) as fd: |
412 | + container_list = json.load(fd) |
413 | + |
414 | + for container in container_list["containerList"]: |
415 | + if container["id"] == container_id: |
416 | + return container["type"] |
417 | + |
418 | + except FileNotFoundError: |
419 | + pass |
420 | |
421 | # Return lxc as the default container type |
422 | return "lxc" |
423 | @@ -59,7 +65,9 @@ |
424 | :param container_id: The machine-readable container name. |
425 | """ |
426 | def __init__(self, container_id): |
427 | + self.container_type = 'unknown' |
428 | self.container_id = container_id |
429 | + self.root_path = libertine.utils.get_libertine_container_rootfs_path(self.container_id) |
430 | |
431 | def create_libertine_container(self, password=None, verbosity=1): |
432 | pass |
433 | @@ -131,12 +139,24 @@ |
434 | |
435 | return "" |
436 | |
437 | + @property |
438 | + def name(self): |
439 | + """ |
440 | + The human-readable name of the container. |
441 | + """ |
442 | + name = Libertine.container_name(self.container_id) |
443 | + if not name: |
444 | + name = 'Unknown' |
445 | + return name |
446 | + |
447 | + |
448 | class LibertineMock(BaseContainer): |
449 | """ |
450 | A concrete mock container type. Used for unit testing. |
451 | """ |
452 | def __init__(self, container_id): |
453 | super().__init__(container_id) |
454 | + self.container_type = "mock" |
455 | |
456 | def run_in_container(self, command_string): |
457 | return 0 |
458 | @@ -188,6 +208,22 @@ |
459 | else: |
460 | raise RuntimeError("Unsupported container type %s" % container_type) |
461 | |
462 | + @property |
463 | + def container_id(self): |
464 | + return self.container.container_id |
465 | + |
466 | + @property |
467 | + def name(self): |
468 | + return self.container.name |
469 | + |
470 | + @property |
471 | + def container_type(self): |
472 | + return self.container.container_type |
473 | + |
474 | + @property |
475 | + def root_path(self): |
476 | + return self.container.root_path |
477 | + |
478 | def destroy_libertine_container(self): |
479 | """ |
480 | Destroys the container and releases all its system resources. |
481 | @@ -245,3 +281,20 @@ |
482 | self.container.launch_application(app_exec_line) |
483 | else: |
484 | raise RuntimeError("Container with id %s does not exist." % self.container.container_id) |
485 | + |
486 | + def list_app_launchers(self, use_json=False): |
487 | + """ |
488 | + Enumerates all application launchers (based on .desktop files) available |
489 | + in the container. |
490 | + |
491 | + :param use_json: Indicates the returned string should be i JSON format. |
492 | + The default format is some human-readble format. |
493 | + :rtype: A printable string containing a list of application launchers |
494 | + available in the container. |
495 | + """ |
496 | + if use_json: |
497 | + return AppLauncherCache(self.container.name, |
498 | + self.container.root_path).to_json() |
499 | + else: |
500 | + return str(AppLauncherCache(self.container.name, |
501 | + self.container.root_path)) |
502 | |
503 | === modified file 'python/libertine/LxcContainer.py' |
504 | --- python/libertine/LxcContainer.py 2015-12-14 18:08:13 +0000 |
505 | +++ python/libertine/LxcContainer.py 2015-12-23 02:34:33 +0000 |
506 | @@ -70,6 +70,7 @@ |
507 | |
508 | def __init__(self, container_id): |
509 | super().__init__(container_id) |
510 | + self.container_type = "lxc" |
511 | self.container = lxc_container(container_id) |
512 | |
513 | def is_running(self): |
514 | |
515 | === modified file 'python/libertine/utils.py' |
516 | --- python/libertine/utils.py 2015-12-18 16:47:39 +0000 |
517 | +++ python/libertine/utils.py 2015-12-23 02:34:33 +0000 |
518 | @@ -52,11 +52,16 @@ |
519 | |
520 | |
521 | def get_libertine_containers_dir_path(): |
522 | - return basedir.save_cache_path('libertine-container') |
523 | + xdg_cache_home = os.getenv('XDG_CACHE_HOME', |
524 | + os.path.join(os.getenv('HOME'), '.cache')) |
525 | + |
526 | + return os.path.join(xdg_cache_home, 'libertine-container') |
527 | |
528 | |
529 | def get_libertine_database_dir_path(): |
530 | - return os.path.join(basedir.xdg_data_home, 'libertine') |
531 | + xdg_data_home = os.getenv('XDG_DATA_HOME', |
532 | + os.path.join(os.getenv('HOME'), '.local', 'share')) |
533 | + return os.path.join(xdg_data_home, 'libertine') |
534 | |
535 | |
536 | def get_libertine_database_file_path(): |
537 | |
538 | === modified file 'tests/unit/CMakeLists.txt' |
539 | --- tests/unit/CMakeLists.txt 2015-12-17 17:34:24 +0000 |
540 | +++ tests/unit/CMakeLists.txt 2015-12-23 02:34:33 +0000 |
541 | @@ -30,6 +30,12 @@ |
542 | PROPERTIES |
543 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") |
544 | |
545 | +add_test(test_libertine "/usr/bin/python3" "-m" "testtools.run" "discover" "-s" "${CMAKE_CURRENT_SOURCE_DIR}") |
546 | +set_tests_properties(test_libertine |
547 | + PROPERTIES |
548 | + ENVIRONMENT |
549 | + "GI_TYPELIB_PATH=${CMAKE_BINARY_DIR}/liblibertine;LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/liblibertine:${LD_LIBRARY_PATH};PYTHONPATH=${CMAKE_SOURCE_DIR}/python") |
550 | + |
551 | add_test(test_libertine_container_manager |
552 | "/usr/bin/python3" "-m" "testtools.run" "libertine_container_manager_tests" |
553 | ) |
554 | |
555 | === added file 'tests/unit/test_app_discovery.py' |
556 | --- tests/unit/test_app_discovery.py 1970-01-01 00:00:00 +0000 |
557 | +++ tests/unit/test_app_discovery.py 2015-12-23 02:34:33 +0000 |
558 | @@ -0,0 +1,52 @@ |
559 | +"""Unit tests for the AppLauncher module.""" |
560 | +# Copyright 2015 Canonical Ltd. |
561 | +# |
562 | +# This program is free software: you can redistribute it and/or modify it |
563 | +# under the terms of the GNU General Public License version 3, as published |
564 | +# by the Free Software Foundation. |
565 | +# |
566 | +# This program is distributed in the hope that it will be useful, but |
567 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
568 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
569 | +# PURPOSE. See the GNU General Public License for more details. |
570 | +# |
571 | +# You should have received a copy of the GNU General Public License along |
572 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
573 | + |
574 | +from libertine.AppDiscovery import IconCache |
575 | +from testtools import TestCase |
576 | +from testtools.matchers import Equals |
577 | + |
578 | + |
579 | +class TestIconSearching(TestCase): |
580 | + |
581 | + def mock_file_loader(self, root_path): |
582 | + return [ |
583 | + "/some/path/128x128/icon.png", |
584 | + "/somepath/icon.png", |
585 | + "/somepath/testme.png" |
586 | + ]; |
587 | + |
588 | + def test_full_path(self): |
589 | + some_root = "/some_root" |
590 | + icon_cache = IconCache(some_root, file_loader=self.mock_file_loader) |
591 | + |
592 | + some_full_path = "/this/is/a/full/path" |
593 | + self.assertThat(icon_cache.expand_icons(some_full_path)[0], |
594 | + Equals(some_full_path)) |
595 | + |
596 | + def test_icon_name(self): |
597 | + some_root = "/some_root" |
598 | + icon_cache = IconCache(some_root, file_loader=self.mock_file_loader) |
599 | + |
600 | + some_icon_list = "testme" |
601 | + self.assertThat(icon_cache.expand_icons(some_icon_list)[0], |
602 | + Equals("/somepath/testme.png")) |
603 | + |
604 | + def test_edge_case_relative_file_name(self): |
605 | + some_root = "/some_root" |
606 | + icon_cache = IconCache(some_root, file_loader=self.mock_file_loader) |
607 | + |
608 | + some_icon_list = "testme.png" |
609 | + self.assertThat(icon_cache.expand_icons(some_icon_list)[0], |
610 | + Equals("/somepath/testme.png")) |
611 | |
612 | === added file 'tests/unit/test_libertine_container.py' |
613 | --- tests/unit/test_libertine_container.py 1970-01-01 00:00:00 +0000 |
614 | +++ tests/unit/test_libertine_container.py 2015-12-23 02:34:33 +0000 |
615 | @@ -0,0 +1,62 @@ |
616 | +"""Unit tests for the LibertineContainer interface.""" |
617 | +# Copyright 2015 Canonical Ltd. |
618 | +# |
619 | +# This program is free software: you can redistribute it and/or modify it |
620 | +# under the terms of the GNU General Public License version 3, as published |
621 | +# by the Free Software Foundation. |
622 | +# |
623 | +# This program is distributed in the hope that it will be useful, but |
624 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
625 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
626 | +# PURPOSE. See the GNU General Public License for more details. |
627 | +# |
628 | +# You should have received a copy of the GNU General Public License along |
629 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
630 | + |
631 | +from libertine import Libertine |
632 | +from testtools import TestCase |
633 | +from testtools.matchers import Equals |
634 | +import os |
635 | +import shutil |
636 | +import tempfile |
637 | + |
638 | + |
639 | +class TestLibertineContainer(TestCase): |
640 | + |
641 | + def setUp(self): |
642 | + super().setUp() |
643 | + self._working_dir = tempfile.mkdtemp() |
644 | + os.environ['XDG_CACHE_HOME'] = self._working_dir |
645 | + os.environ['XDG_DATA_HOME'] = self._working_dir |
646 | + |
647 | + def tearDown(self): |
648 | + shutil.rmtree(self._working_dir) |
649 | + super().tearDown() |
650 | + |
651 | + def test_container_id(self): |
652 | + container_id = "test-id-1" |
653 | + container = Libertine.LibertineContainer(container_id) |
654 | + |
655 | + self.assertThat(container.container_id, Equals(container_id)) |
656 | + |
657 | + def test_container_type_default(self): |
658 | + container_id = "test-id-2" |
659 | + container = Libertine.LibertineContainer(container_id) |
660 | + |
661 | + self.assertThat(container.container_type, Equals("lxc")) |
662 | + |
663 | + def test_container_name_default(self): |
664 | + container_id = "test-id-3" |
665 | + container = Libertine.LibertineContainer(container_id) |
666 | + |
667 | + self.assertThat(container.name, Equals("Unknown")) |
668 | + |
669 | + def test_container_root_path(self): |
670 | + container_id = "test-id-4" |
671 | + container = Libertine.LibertineContainer(container_id) |
672 | + |
673 | + expected_root_path = os.path.join(self._working_dir, |
674 | + "libertine-container", |
675 | + container_id, |
676 | + "rootfs") |
677 | + self.assertThat(container.root_path, Equals(expected_root_path)) |
678 | |
679 | === modified file 'tools/libertine-container-manager' |
680 | --- tools/libertine-container-manager 2015-12-17 17:34:24 +0000 |
681 | +++ tools/libertine-container-manager 2015-12-23 02:34:33 +0000 |
682 | @@ -322,6 +322,17 @@ |
683 | print("%s" % container) |
684 | |
685 | |
686 | +def list_apps(args): |
687 | + if args.id and not libertine.utils.container_exists(args.id): |
688 | + print("Container id \'%s\' does not exist." % args.id, file=sys.stderr) |
689 | + sys.exit(1) |
690 | + elif not args.id: |
691 | + args.id = get_default_container_id() |
692 | + |
693 | + container = LibertineContainer(args.id) |
694 | + print(container.list_app_launchers(use_json=args.json)) |
695 | + |
696 | + |
697 | if __name__ == '__main__': |
698 | parser = argparse.ArgumentParser(description="Legacy X application support for Unity 8") |
699 | parser.add_argument('-q', '--quiet', |
700 | @@ -414,6 +425,19 @@ |
701 | help=("List all Libertine containers.")) |
702 | parser_list.set_defaults(func=list) |
703 | |
704 | + parser_list_apps = subparsers.add_parser( |
705 | + 'list-apps', |
706 | + help=("List availsble app launchers in a container.")) |
707 | + parser_list_apps.add_argument( |
708 | + '-i', '--id', |
709 | + help=("Container identifier. Default container is used if omitted.")) |
710 | + parser_list_apps.add_argument( |
711 | + '-j', '--json', |
712 | + action='store_true', |
713 | + help=("use JSON output format.")) |
714 | + parser_list_apps.set_defaults(func=list_apps) |
715 | + |
716 | +# Actually parse the args |
717 | args = parser.parse_args() |
718 | if args.verbosity is None: |
719 | args.verbosity = 1 |
FAILED: Continuous integration, rev:151 libertine- jenkins- be.internal: 8080/libertine/ job/libertine- ci-build- and-test/ 947/ /jenkins. canonical. com/libertine/ job/libertine- ci-update- mp/950/ console
http://
Executed test runs:
None: https:/
Click here to trigger a rebuild: libertine- jenkins- be.internal: 8080/libertine/ job/libertine- ci-build- and-test/ 947/rebuild
http://