Merge lp:~bregma/libertine/scan-desktop-files into lp:libertine

Proposed by Stephen M. Webb
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
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-container-manager to scan a container for launchable applications and report them in JSON format, together with icons and MIME types.

To post a comment you must log in.
Revision history for this message
Libertine CI Bot (libertine-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Libertine CI Bot (libertine-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Christopher Townsend (townsend) wrote :

As discussed on IRC, mb-panel-manager.desktop has:

Icon=mbpanelmgr.png

but the output of list-apps has this:

        {
            "exec_line": "matchbox-panel-manager",
            "icons": [],
            "mime_types": [],
            "name": "Panel Manager",
            "no_display": false
        }

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:)

review: Needs Fixing
153. By Stephen M. Webb

synched trunk again

154. By Stephen M. Webb

added unit tests

Revision history for this message
Libertine CI Bot (libertine-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
155. By Stephen M. Webb

extended test environment

Revision history for this message
Libertine CI Bot (libertine-ci-bot) wrote :
review: Approve (continuous-integration)
156. By Stephen M. Webb

fixed some typos

Revision history for this message
Libertine CI Bot (libertine-ci-bot) wrote :
review: Approve (continuous-integration)
157. By Stephen M. Webb

added a non-JSON default output for list-apps

Revision history for this message
Libertine CI Bot (libertine-ci-bot) wrote :
review: Approve (continuous-integration)
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

Revision history for this message
Libertine CI Bot (libertine-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
160. By Stephen M. Webb

used liblibertine to retrieve the container name

Revision history for this message
Libertine CI Bot (libertine-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
161. By Stephen M. Webb

allowed the config file to not exist

Revision history for this message
Libertine CI Bot (libertine-ci-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Christopher Townsend (townsend) wrote :

Ok, looks good now.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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

Subscribers

People subscribed via source and target branches