Merge lp:~townsend/libertine/1.6-release into lp:libertine/trunk
- 1.6-release
- Merge into trunk
Status: | Merged | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Approved by: | Stephen M. Webb | ||||||||||||||||||||
Approved revision: | 187 | ||||||||||||||||||||
Merged at revision: | 185 | ||||||||||||||||||||
Proposed branch: | lp:~townsend/libertine/1.6-release | ||||||||||||||||||||
Merge into: | lp:libertine/trunk | ||||||||||||||||||||
Diff against target: |
4853 lines (+1240/-1763) 74 files modified
CMakeLists.txt (+2/-2) cmake/FindGObjectIntrospection.cmake (+0/-61) cmake/ListOperations.cmake (+0/-18) cmake/UseGObjectIntrospection.cmake (+0/-102) common/LibertineConfig.cpp (+10/-1) data/CMakeLists.txt (+1/-3) data/com.canonical.libertine.LxcManager.service (+0/-3) data/com.canonical.libertine.LxdManager.service (+0/-3) data/com.canonical.libertine.Service.service (+1/-1) data/libertine-xmir.conf (+1/-1) data/snap-runner.wrapper (+2/-1) debian/changelog (+40/-0) debian/control (+22/-18) debian/gir1.2-libertine.install (+0/-2) debian/libertine-tools.install (+0/-2) debian/libertined.install (+3/-0) debian/python3-libertine-lxc.install (+0/-2) debian/python3-libertine-lxd.install (+0/-2) debian/python3-libertine.install (+0/-3) debian/rules (+1/-4) liblibertine/CMakeLists.txt (+3/-30) liblibertine/libertine.cpp (+20/-131) liblibertine/libertine.h (+4/-4) liblibertine/libertined.cpp (+199/-0) liblibertine/libertined.h (+25/-0) python/libertine/AppDiscovery.py (+0/-220) python/libertine/ChrootContainer.py (+6/-13) python/libertine/ContainersConfig.py (+21/-2) python/libertine/HostInfo.py (+8/-1) python/libertine/Libertine.py (+62/-52) python/libertine/LxcContainer.py (+137/-65) python/libertine/LxdContainer.py (+131/-131) python/libertine/launcher/config.py (+1/-4) python/libertine/lifecycle/ContainerLifecycleService.py (+0/-108) python/libertine/lifecycle/ContainerLifecycleServiceRunner.py (+0/-46) python/libertine/lifecycle/LifecycleResult.py (+0/-37) python/libertine/lifecycle/__init__.py (+0/-23) python/libertine/service/apt.py (+2/-6) python/libertine/service/container.py (+23/-8) python/libertine/service/manager.py (+40/-4) python/libertine/service/progress.py (+3/-3) python/libertine/service/task_dispatcher.py (+7/-8) python/libertine/service/tasks/__init__.py (+3/-3) python/libertine/service/tasks/base_task.py (+2/-4) python/libertine/service/tasks/container_info_task.py (+21/-5) python/libertine/service/tasks/list_app_ids_task.py (+35/-0) python/libertine/service/tasks/list_apps_task.py (+0/-30) python/libertine/service/tasks/list_task.py (+6/-4) python/libertine/utils.py (+29/-59) setup/gui/libertine-manager-app.desktop (+12/-0) snapcraft.yaml (+40/-6) tests/integration/CMakeLists.txt (+1/-1) tests/integration/test_libertine_service.py (+40/-22) tests/unit/service/tasks/CMakeLists.txt (+1/-1) tests/unit/service/tasks/test_container_info_task.py (+14/-2) tests/unit/service/tasks/test_list_app_ids_task.py (+59/-0) tests/unit/service/tasks/test_list_apps_task.py (+0/-59) tests/unit/service/tasks/test_list_task.py (+8/-6) tests/unit/service/test_apt.py (+37/-45) tests/unit/service/test_container.py (+33/-47) tests/unit/service/test_task_dispatcher.py (+17/-17) tests/unit/test_app_discovery.py (+0/-52) tests/unit/test_libertine_container.py (+2/-1) tests/unit/test_libertine_gir.py (+0/-65) tools/CMakeLists.txt (+3/-3) tools/completions/libertine-container-manager (+9/-5) tools/libertine-container-manager (+65/-11) tools/libertine-container-manager.1 (+25/-3) tools/libertine-lxc-manager (+0/-109) tools/libertine-lxc-manager.1 (+0/-9) tools/libertine-lxd-manager (+0/-57) tools/libertine-lxd-manager.1 (+0/-9) tools/libertine-lxd-setup (+1/-1) tools/libertine-xmir (+2/-2) |
||||||||||||||||||||
To merge this branch: | bzr merge lp:~townsend/libertine/1.6-release | ||||||||||||||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Stephen M. Webb (community) | Approve | ||
Review via email:
|
Commit message
* Get libertine-
* Create a special desktop file to satisfy the snappy store.
* Update snap definition to include aliases, renamed commands, and internal desktop file.
* Ensure absolute path lxd bind-mounts have a valid name by using the full path as a backup.
* Refactor liblibertine as a client wrapper for libertined and bump version to 1.5.2.
* Protect against containers that aren't started when running commands.
* Migrate the list-apps subcommand to list application ids. (LP: #1657877)
* Run the libertined integration test with its own dbus sesison to prevent collisions with running service.
* Discover if a session is running and if so, use it's dbus session to start the container manager. (LP: #1657490)
* Replace all pylxd execute()'s with calls to 'lxc exec' as pylxd has performance issues in execute().
* Add ability to freeze and unfreeze LXC/LXD containers. This is disabled by default for now. (LP: #1654355)
* Add ability to enable and disable the freezing of LXC/LXD containers when not in use. (LP: #1654355)
* Deprecate the lx[cd] managers and use libertined for managing the containers and move the actual starting and stopping of containers to the respective backends. (LP: #1660685)
* Add a restart subcommand to libertine-
* Enable Xmir '-rootless' mode by default and remove using Matchbox for the window manager. (LP: #1662555)
* Bump release version to 1.6.
Description of the change
- 186. By Christopher Townsend
-
Fix CMake file typo.
- 187. By Christopher Townsend
-
Change when the libertine-xmir Upstart gets started to account for Unity 8 now directly starting apps.
- 188. By Christopher Townsend
-
Return True instead of pass for start_container() in the base class. Fixes running operation in chroot's.
Preview Diff
1 | === modified file 'CMakeLists.txt' |
2 | --- CMakeLists.txt 2017-01-13 20:11:25 +0000 |
3 | +++ CMakeLists.txt 2017-02-09 16:08:46 +0000 |
4 | @@ -2,12 +2,11 @@ |
5 | cmake_policy(SET CMP0048 NEW) |
6 | |
7 | project(libertine |
8 | - VERSION 1.5.1) |
9 | + VERSION 1.6) |
10 | |
11 | set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake" "${CMAKE_MODULE_PATH}") |
12 | |
13 | find_package(PkgConfig REQUIRED) |
14 | -find_package(GObjectIntrospection REQUIRED) |
15 | include(GNUInstallDirs) |
16 | include(CheckIncludeFile) |
17 | include(CheckFunctionExists) |
18 | @@ -17,6 +16,7 @@ |
19 | include_directories(${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}) |
20 | |
21 | find_package(Qt5Core REQUIRED) |
22 | +find_package(Qt5DBus REQUIRED) |
23 | find_package(Qt5Gui REQUIRED) |
24 | find_package(Qt5Quick REQUIRED) |
25 | find_package(Qt5Widgets REQUIRED) |
26 | |
27 | === removed directory 'cmake' |
28 | === removed file 'cmake/FindGObjectIntrospection.cmake' |
29 | --- cmake/FindGObjectIntrospection.cmake 2015-08-17 20:42:31 +0000 |
30 | +++ cmake/FindGObjectIntrospection.cmake 1970-01-01 00:00:00 +0000 |
31 | @@ -1,61 +0,0 @@ |
32 | -# - try to find gobject-introspection |
33 | -# |
34 | -# Once done this will define |
35 | -# |
36 | -# INTROSPECTION_FOUND - system has gobject-introspection |
37 | -# INTROSPECTION_SCANNER - the gobject-introspection scanner, g-ir-scanner |
38 | -# INTROSPECTION_COMPILER - the gobject-introspection compiler, g-ir-compiler |
39 | -# INTROSPECTION_GENERATE - the gobject-introspection generate, g-ir-generate |
40 | -# INTROSPECTION_GIRDIR |
41 | -# INTROSPECTION_TYPELIBDIR |
42 | -# INTROSPECTION_CFLAGS |
43 | -# INTROSPECTION_LIBS |
44 | -# |
45 | -# Copyright (C) 2010, Pino Toscano, <pino@kde.org> |
46 | -# |
47 | -# Redistribution and use is allowed according to the terms of the BSD license. |
48 | -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. |
49 | - |
50 | -macro(_GIR_GET_PKGCONFIG_VAR _outvar _varname) |
51 | - execute_process( |
52 | - COMMAND ${PKG_CONFIG_EXECUTABLE} --variable=${_varname} gobject-introspection-1.0 |
53 | - OUTPUT_VARIABLE _result |
54 | - RESULT_VARIABLE _null |
55 | - ) |
56 | - |
57 | - if (_null) |
58 | - else() |
59 | - string(REGEX REPLACE "[\r\n]" " " _result "${_result}") |
60 | - string(REGEX REPLACE " +$" "" _result "${_result}") |
61 | - separate_arguments(_result) |
62 | - set(${_outvar} ${_result} CACHE INTERNAL "") |
63 | - endif() |
64 | -endmacro(_GIR_GET_PKGCONFIG_VAR) |
65 | - |
66 | -find_package(PkgConfig) |
67 | -if(PKG_CONFIG_FOUND) |
68 | - if(PACKAGE_FIND_VERSION_COUNT GREATER 0) |
69 | - set(_gir_version_cmp ">=${PACKAGE_FIND_VERSION}") |
70 | - endif() |
71 | - pkg_check_modules(_pc_gir gobject-introspection-1.0${_gir_version_cmp}) |
72 | - if(_pc_gir_FOUND) |
73 | - set(INTROSPECTION_FOUND TRUE) |
74 | - _gir_get_pkgconfig_var(INTROSPECTION_SCANNER "g_ir_scanner") |
75 | - _gir_get_pkgconfig_var(INTROSPECTION_COMPILER "g_ir_compiler") |
76 | - _gir_get_pkgconfig_var(INTROSPECTION_GENERATE "g_ir_generate") |
77 | - _gir_get_pkgconfig_var(INTROSPECTION_GIRDIR "girdir") |
78 | - _gir_get_pkgconfig_var(INTROSPECTION_TYPELIBDIR "typelibdir") |
79 | - set(INTROSPECTION_CFLAGS "${_pc_gir_CFLAGS}") |
80 | - set(INTROSPECTION_LIBS "${_pc_gir_LIBS}") |
81 | - endif() |
82 | -endif() |
83 | - |
84 | -mark_as_advanced( |
85 | - INTROSPECTION_SCANNER |
86 | - INTROSPECTION_COMPILER |
87 | - INTROSPECTION_GENERATE |
88 | - INTROSPECTION_GIRDIR |
89 | - INTROSPECTION_TYPELIBDIR |
90 | - INTROSPECTION_CFLAGS |
91 | - INTROSPECTION_LIBS |
92 | -) |
93 | |
94 | === removed file 'cmake/ListOperations.cmake' |
95 | --- cmake/ListOperations.cmake 2015-08-17 20:42:31 +0000 |
96 | +++ cmake/ListOperations.cmake 1970-01-01 00:00:00 +0000 |
97 | @@ -1,18 +0,0 @@ |
98 | - |
99 | -macro(list_prefix _outvar _listvar _prefix) |
100 | - set(${_outvar}) |
101 | - foreach(_item IN LISTS ${_listvar}) |
102 | - list(APPEND ${_outvar} ${_prefix}${_item}) |
103 | - endforeach() |
104 | -endmacro(list_prefix) |
105 | - |
106 | -macro(list_make_absolute _outvar _listvar _prefix) |
107 | - set(${_outvar}) |
108 | - foreach(_item IN LISTS ${_listvar}) |
109 | - if(IS_ABSOLUTE ${_item}) |
110 | - list(APPEND ${_outvar} ${_item}) |
111 | - else() |
112 | - list(APPEND ${_outvar} ${_prefix}${_item}) |
113 | - endif() |
114 | - endforeach() |
115 | -endmacro(list_make_absolute) |
116 | |
117 | === removed file 'cmake/UseGObjectIntrospection.cmake' |
118 | --- cmake/UseGObjectIntrospection.cmake 2015-08-17 20:42:31 +0000 |
119 | +++ cmake/UseGObjectIntrospection.cmake 1970-01-01 00:00:00 +0000 |
120 | @@ -1,102 +0,0 @@ |
121 | -# Copyright (C) 2010, Pino Toscano, <pino@kde.org> |
122 | -# |
123 | -# Redistribution and use is allowed according to the terms of the BSD license. |
124 | -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. |
125 | - |
126 | -include(ListOperations) |
127 | - |
128 | -macro(_gir_list_prefix _outvar _listvar _prefix) |
129 | - set(${_outvar}) |
130 | - foreach(_item IN LISTS ${_listvar}) |
131 | - list(APPEND ${_outvar} ${_prefix}${_item}) |
132 | - endforeach() |
133 | -endmacro(_gir_list_prefix) |
134 | - |
135 | -macro(gir_add_introspections introspections_girs) |
136 | - |
137 | - foreach(gir IN LISTS ${introspections_girs}) |
138 | - |
139 | - set(_gir_name "${gir}") |
140 | - |
141 | - ## Transform the gir filename to something which can reference through a variable |
142 | - ## without automake/make complaining, eg Gtk-2.0.gir -> Gtk_2_0_gir |
143 | - string(REPLACE "-" "_" _gir_name "${_gir_name}") |
144 | - string(REPLACE "." "_" _gir_name "${_gir_name}") |
145 | - |
146 | - # Namespace and Version is either fetched from the gir filename |
147 | - # or the _NAMESPACE/_VERSION variable combo |
148 | - set(_gir_namespace "${${_gir_name}_NAMESPACE}") |
149 | - if (_gir_namespace STREQUAL "") |
150 | - string(REGEX REPLACE "([^-]+)-.*" "\\1" _gir_namespace "${gir}") |
151 | - endif () |
152 | - set(_gir_version "${${_gir_name}_VERSION}") |
153 | - if (_gir_version STREQUAL "") |
154 | - string(REGEX REPLACE ".*-([^-]+).gir" "\\1" _gir_version "${gir}") |
155 | - endif () |
156 | - |
157 | - # _PROGRAM is an optional variable which needs it's own --program argument |
158 | - set(_gir_program "${${_gir_name}_PROGRAM}") |
159 | - if (NOT _gir_program STREQUAL "") |
160 | - set(_gir_program "--program=${_gir_program}") |
161 | - endif () |
162 | - |
163 | - # Variables which provides a list of things |
164 | - _gir_list_prefix(_gir_libraries ${_gir_name}_LIBS "--library=") |
165 | - _gir_list_prefix(_gir_packages ${_gir_name}_PACKAGES "--pkg=") |
166 | - _gir_list_prefix(_gir_includes ${_gir_name}_INCLUDES "--include=") |
167 | - _gir_list_prefix(_gir_export_packages ${_gir_name}_EXPORT_PACKAGES "--pkg-export=") |
168 | - |
169 | - # Reuse the LIBTOOL variable from by automake if it's set |
170 | - set(_gir_libtool "--no-libtool") |
171 | - |
172 | - add_custom_command( |
173 | - COMMAND ${INTROSPECTION_SCANNER} |
174 | - ${INTROSPECTION_SCANNER_ARGS} |
175 | - --quiet |
176 | - --warn-all |
177 | - --namespace=${_gir_namespace} |
178 | - --nsversion=${_gir_version} |
179 | - ${_gir_libtool} |
180 | - ${_gir_program} |
181 | - ${_gir_libraries} |
182 | - ${_gir_packages} |
183 | - ${_gir_includes} |
184 | - ${_gir_export_packages} |
185 | - ${${_gir_name}_SCANNERFLAGS} |
186 | - ${${_gir_name}_CFLAGS} |
187 | - ${${_gir_name}_FILES} |
188 | - --output ${CMAKE_CURRENT_BINARY_DIR}/${gir} |
189 | - DEPENDS ${${_gir_name}_FILES} |
190 | - ${${_gir_name}_LIBS} |
191 | - OUTPUT ${gir} |
192 | - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} |
193 | - VERBATIM |
194 | - ) |
195 | - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${gir} DESTINATION share/gir-1.0) |
196 | - |
197 | - string(REPLACE ".gir" ".typelib" _typelib "${gir}") |
198 | - add_custom_command( |
199 | - COMMAND ${INTROSPECTION_COMPILER} |
200 | - ${INTROSPECTION_COMPILER_ARGS} |
201 | - --includedir=. |
202 | - ${CMAKE_CURRENT_BINARY_DIR}/${gir} |
203 | - -o ${CMAKE_CURRENT_BINARY_DIR}/${_typelib} |
204 | - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${gir} |
205 | - OUTPUT ${_typelib} |
206 | - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} |
207 | - ) |
208 | - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${_typelib} DESTINATION ${CMAKE_INSTALL_LIBDIR}/girepository-1.0) |
209 | - |
210 | - add_custom_target(gir-${gir} ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${gir}) |
211 | - add_custom_target(gir-typelibs-${_typelib} ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${_typelib}) |
212 | - endforeach() |
213 | - |
214 | -endmacro(gir_add_introspections) |
215 | - |
216 | -macro(gir_get_cflags _output) |
217 | - get_directory_property(_tmp_includes INCLUDE_DIRECTORIES) |
218 | - list_prefix(_includes _tmp_includes "-I") |
219 | - get_directory_property(_tmp_compile_definitions COMPILE_DEFINITIONS) |
220 | - list_prefix(_compile_definitions _tmp_compile_definitions "-D") |
221 | - set(${_output} ${_includes} ${_compile_definitions}) |
222 | -endmacro(gir_get_cflags) |
223 | |
224 | === modified file 'common/LibertineConfig.cpp' |
225 | --- common/LibertineConfig.cpp 2016-09-26 18:17:07 +0000 |
226 | +++ common/LibertineConfig.cpp 2017-02-09 16:08:46 +0000 |
227 | @@ -3,7 +3,7 @@ |
228 | * @brief Libertine Manager application-wide configuration module |
229 | */ |
230 | /* |
231 | - * Copyright 2015 Canonical Ltd |
232 | + * Copyright 2015-2017 Canonical Ltd |
233 | * |
234 | * Libertine is free software: you can redistribute it and/or modify it under |
235 | * the terms of the GNU General Public License, version 3, as published by the |
236 | @@ -18,6 +18,7 @@ |
237 | */ |
238 | #include "common/LibertineConfig.h" |
239 | |
240 | +#include <cstdlib> |
241 | #include <QtCore/QDir> |
242 | #include <QtCore/QFile> |
243 | #include <QtCore/QStandardPaths> |
244 | @@ -27,6 +28,14 @@ |
245 | containers_config_file_name() const |
246 | { |
247 | QString path = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/libertine"; |
248 | + |
249 | + // if running from a snap |
250 | + auto snap_common = std::getenv("SNAP_USER_COMMON"); |
251 | + if (snap_common != nullptr) |
252 | + { |
253 | + path.replace(std::getenv("HOME"), snap_common); |
254 | + } |
255 | + |
256 | QDir dir(path); |
257 | |
258 | if (!dir.exists()) |
259 | |
260 | === modified file 'data/CMakeLists.txt' |
261 | --- data/CMakeLists.txt 2016-12-21 20:57:15 +0000 |
262 | +++ data/CMakeLists.txt 2017-02-09 16:08:46 +0000 |
263 | @@ -8,7 +8,5 @@ |
264 | DESTINATION ${CMAKE_INSTALL_DATADIR}/upstart/sessions) |
265 | install(FILES libertine-lxc-sudo libertine-lxd-sudo |
266 | DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/sudoers.d) |
267 | -install(FILES com.canonical.libertine.ContainerManager.service |
268 | - com.canonical.libertine.LxcManager.service |
269 | - com.canonical.libertine.LxdManager.service |
270 | +install(FILES com.canonical.libertine.Service.service |
271 | DESTINATION ${CMAKE_INSTALL_DATADIR}/dbus-1/services) |
272 | |
273 | === removed file 'data/com.canonical.libertine.LxcManager.service' |
274 | --- data/com.canonical.libertine.LxcManager.service 2016-08-19 14:02:31 +0000 |
275 | +++ data/com.canonical.libertine.LxcManager.service 1970-01-01 00:00:00 +0000 |
276 | @@ -1,3 +0,0 @@ |
277 | -[D-BUS Service] |
278 | -Name=com.canonical.libertine.LxcManager |
279 | -Exec=/usr/bin/libertine-lxc-manager |
280 | |
281 | === removed file 'data/com.canonical.libertine.LxdManager.service' |
282 | --- data/com.canonical.libertine.LxdManager.service 2016-12-20 20:23:16 +0000 |
283 | +++ data/com.canonical.libertine.LxdManager.service 1970-01-01 00:00:00 +0000 |
284 | @@ -1,3 +0,0 @@ |
285 | -[D-BUS Service] |
286 | -Name=com.canonical.libertine.LxdManager |
287 | -Exec=/usr/bin/libertine-lxd-manager |
288 | |
289 | === renamed file 'data/com.canonical.libertine.ContainerManager.service' => 'data/com.canonical.libertine.Service.service' |
290 | --- data/com.canonical.libertine.ContainerManager.service 2016-11-01 20:10:58 +0000 |
291 | +++ data/com.canonical.libertine.Service.service 2017-02-09 16:08:46 +0000 |
292 | @@ -1,3 +1,3 @@ |
293 | [D-BUS Service] |
294 | -Name=com.canonical.libertine.ContainerManager |
295 | +Name=com.canonical.libertine.Service |
296 | Exec=/usr/bin/libertined --cache-output |
297 | |
298 | === modified file 'data/libertine-xmir.conf' |
299 | --- data/libertine-xmir.conf 2016-02-18 15:49:20 +0000 |
300 | +++ data/libertine-xmir.conf 2017-02-09 16:08:46 +0000 |
301 | @@ -1,7 +1,7 @@ |
302 | description "Set global environment variable to tell u-a-l where to look to start Xmir" |
303 | author "Christopher Townsend <christopher.townsend@canonical.com>" |
304 | |
305 | -start on started unity8 |
306 | +start on starting unity8 |
307 | |
308 | pre-start script |
309 | initctl set-env --global UBUNTU_APP_LAUNCH_XMIR_PATH="/usr/bin/libertine-xmir" |
310 | |
311 | === modified file 'data/snap-runner.wrapper' |
312 | --- data/snap-runner.wrapper 2016-12-06 16:14:42 +0000 |
313 | +++ data/snap-runner.wrapper 2017-02-09 16:08:46 +0000 |
314 | @@ -3,9 +3,10 @@ |
315 | |
316 | export LD_LIBRARY_PATH=$SNAP/usr/lib/$ARCH/fakechroot:$LD_LIBRARY_PATH |
317 | export DEBOOTSTRAP_DIR=$SNAP/usr/share/debootstrap |
318 | +export LXD_DIR=/var/snap/lxd/common/lxd |
319 | |
320 | # Useful debug variables |
321 | # export FAKECHROOT_DEBUG=1 |
322 | # export LIBERTINE_DEBUG=1 |
323 | |
324 | -exec $SNAP/bin/desktop-launch $@ |
325 | +exec $SNAP/bin/desktop-launch "$@" |
326 | |
327 | === modified file 'debian/changelog' |
328 | --- debian/changelog 2017-01-18 14:50:33 +0000 |
329 | +++ debian/changelog 2017-02-09 16:08:46 +0000 |
330 | @@ -1,3 +1,43 @@ |
331 | +libertine (1.6-0ubuntu1) UNRELEASED; urgency=medium |
332 | + |
333 | + [ Larry Price ] |
334 | + * Get libertine-container-manager and libertine-launch working in a confined |
335 | + environment. |
336 | + * Create a special desktop file to satisfy the snappy store. |
337 | + * Update snap definition to include aliases, renamed commands, and internal |
338 | + desktop file. |
339 | + * Ensure absolute path lxd bind-mounts have a valid name by using the full |
340 | + path as a backup. |
341 | + * Refactor liblibertine as a client wrapper for libertined and bump version |
342 | + to 1.5.2. |
343 | + * Protect against containers that aren't started when running commands. |
344 | + * Migrate the list-apps subcommand to list application ids. (LP: #1657877) |
345 | + * Run the libertined integration test with its own dbus sesison to prevent |
346 | + collisions with running service. |
347 | + |
348 | + [ Chris Townsend ] |
349 | + * Discover if a session is running and if so, use it's dbus session to start |
350 | + the container manager. (LP: #1657490) |
351 | + * Replace all pylxd execute()'s with calls to 'lxc exec' as pylxd has |
352 | + performance issues in execute(). |
353 | + * Add ability to freeze and unfreeze LXC/LXD containers. This is disabled by |
354 | + default for now. (LP: #1654355) |
355 | + * Add ability to enable and disable the freezing of LXC/LXD containers when |
356 | + not in use. (LP: #1654355) |
357 | + * Deprecate the lx[cd] managers and use libertined for managing the containers |
358 | + and move the actual starting and stopping of containers to the respective |
359 | + backends. (LP: #1660685) |
360 | + * Add a restart subcommand to libertine-container-manager. Also try to restart |
361 | + the container when adding/removing bind-mounts, if appropriate. |
362 | + (LP: #1654355) |
363 | + * Enable Xmir '-rootless' mode by default and remove using Matchbox for the |
364 | + window manager. (LP: #1662555) |
365 | + * Change when the libertine-xmir Upstart gets started to account for Unity 8 |
366 | + now directly starting apps. |
367 | + * Bump release version to 1.6. |
368 | + |
369 | + -- Chris Townsend <christopher.townsend@canonical.com> Wed, 08 Feb 2017 13:36:05 -0500 |
370 | + |
371 | libertine (1.5.1+17.04.20170118-0ubuntu1) zesty; urgency=medium |
372 | |
373 | [ Chris Townsend ] |
374 | |
375 | === modified file 'debian/control' |
376 | --- debian/control 2016-12-07 17:45:28 +0000 |
377 | +++ debian/control 2017-02-09 16:08:46 +0000 |
378 | @@ -23,6 +23,7 @@ |
379 | python3-distro-info, |
380 | python3-gi, |
381 | python3-lxc, |
382 | + python3-pexpect, |
383 | python3-psutil, |
384 | python3-pytest, |
385 | python3-testtools, |
386 | @@ -84,7 +85,6 @@ |
387 | Architecture: any |
388 | Depends: libglib2.0-bin, |
389 | lsb-release, |
390 | - python3-apt, |
391 | python3-dbus, |
392 | python3-distro-info, |
393 | python3-libertine, |
394 | @@ -104,7 +104,6 @@ |
395 | libqt5core5a, |
396 | libqt5gui5, |
397 | libqt5widgets5, |
398 | - matchbox-window-manager, |
399 | xmir [amd64 armhf arm64 i386], |
400 | ${misc:Depends}, |
401 | ${python3:Depends}, |
402 | @@ -115,15 +114,33 @@ |
403 | Helper applications for using and interacting with Xmir such as launching |
404 | Xmir and allowing copy and paste. |
405 | |
406 | +Package: libertined |
407 | +Architecture: any |
408 | +Multi-Arch: same |
409 | +Depends: python3-libertine, |
410 | + python3-apt, |
411 | + python3-dbus, |
412 | + python3-gi, |
413 | + ${misc:Depends}, |
414 | + ${shlibs:Depends}, |
415 | + ${python3:Depends} |
416 | +Breaks: libertine-tools (<< 1.5.2) |
417 | +Replaces: libertine-tools (<< 1.5.2) |
418 | +Description: d-bus service for managing libertine containers |
419 | + D-Bus service for asynchronously discovering and managing classic applications |
420 | + using libertine containers. Allows installation and removal of deb-packaged |
421 | + X11 apps within an isolated sandbox. |
422 | + |
423 | Package: liblibertine1 |
424 | Architecture: any |
425 | Multi-Arch: same |
426 | -Depends: ${misc:Depends}, |
427 | +Depends: libertined, |
428 | + ${misc:Depends}, |
429 | ${shlibs:Depends} |
430 | Description: runtime for running deb-packaged X11 apps on Ubuntu Personal |
431 | Runtime library for creating and using the Ubuntu Personal sandbox for legacy |
432 | Deb-packaged X11 applicatons. This library is used by the Libertine tools |
433 | - and other software interacting wit hthe Libertine container, such as scopes |
434 | + and other software interacting with the Libertine container, such as scopes |
435 | or application launchers. |
436 | |
437 | Package: liblibertine-dev |
438 | @@ -137,24 +154,11 @@ |
439 | Headers and shared libraries used to create the tools for creating and using |
440 | the Ubuntu Personal sandbox for legacy Deb-packaged X11 applicatons. |
441 | |
442 | -Package: gir1.2-libertine |
443 | -Architecture: any |
444 | -Multi-Arch: same |
445 | -Depends: liblibertine1 (= ${binary:Version}), |
446 | - ${gir:Depends}, |
447 | - ${misc:Depends} |
448 | -Description: GObject introspection files for the Libertine application sandbox |
449 | - The GObject introspection description files for the Libertine application |
450 | - sandbox runtime. This package allows the Libertine API to be used from |
451 | - GIR-compliant languages such as Python. |
452 | - |
453 | Package: python3-libertine |
454 | Architecture: any |
455 | Section: python |
456 | Multi-Arch: allowed |
457 | -Depends: gir1.2-libertine, |
458 | - python3-gi, |
459 | - python3-psutil, |
460 | +Depends: python3-psutil, |
461 | python3-xdg, |
462 | xdg-user-dirs, |
463 | ${misc:Depends}, |
464 | |
465 | === removed file 'debian/gir1.2-libertine.install' |
466 | --- debian/gir1.2-libertine.install 2015-08-24 16:54:54 +0000 |
467 | +++ debian/gir1.2-libertine.install 1970-01-01 00:00:00 +0000 |
468 | @@ -1,2 +0,0 @@ |
469 | -usr/lib/*/girepository-1.0/Libertine-1.typelib |
470 | -usr/share/gir-1.0/Libertine-1.gir |
471 | |
472 | === modified file 'debian/libertine-tools.install' |
473 | --- debian/libertine-tools.install 2016-11-01 20:10:58 +0000 |
474 | +++ debian/libertine-tools.install 2017-02-09 16:08:46 +0000 |
475 | @@ -1,6 +1,4 @@ |
476 | usr/bin/libertine-container-manager |
477 | usr/bin/libertine-launch |
478 | -usr/bin/libertined |
479 | -usr/share/dbus-1/services/com.canonical.libertine.ContainerManager.service |
480 | usr/share/bash-completion/completions/libertine-container-manager |
481 | usr/share/man |
482 | |
483 | === added file 'debian/libertined.install' |
484 | --- debian/libertined.install 1970-01-01 00:00:00 +0000 |
485 | +++ debian/libertined.install 2017-02-09 16:08:46 +0000 |
486 | @@ -0,0 +1,3 @@ |
487 | +usr/bin/libertined |
488 | +usr/lib/python*/*/libertine/service |
489 | +usr/share/dbus-1/services/com.canonical.libertine.Service.service |
490 | |
491 | === modified file 'debian/python3-libertine-lxc.install' |
492 | --- debian/python3-libertine-lxc.install 2016-10-07 21:09:41 +0000 |
493 | +++ debian/python3-libertine-lxc.install 2017-02-09 16:08:46 +0000 |
494 | @@ -1,6 +1,4 @@ |
495 | etc/sudoers.d/libertine-lxc-sudo |
496 | -usr/bin/libertine-lxc-manager |
497 | usr/bin/libertine-lxc-setup |
498 | usr/lib/python*/*/libertine/LxcContainer.py |
499 | -usr/share/dbus-1/services/com.canonical.libertine.LxcManager.service |
500 | usr/share/libertine/libertine-lxc.conf |
501 | |
502 | === modified file 'debian/python3-libertine-lxd.install' |
503 | --- debian/python3-libertine-lxd.install 2016-12-21 20:37:57 +0000 |
504 | +++ debian/python3-libertine-lxd.install 2017-02-09 16:08:46 +0000 |
505 | @@ -1,5 +1,3 @@ |
506 | etc/sudoers.d/libertine-lxd-sudo |
507 | usr/bin/libertine-lxd-setup |
508 | -usr/bin/libertine-lxd-manager |
509 | usr/lib/python*/*/libertine/LxdContainer.py |
510 | -usr/share/dbus-1/services/com.canonical.libertine.LxdManager.service |
511 | |
512 | === modified file 'debian/python3-libertine.install' |
513 | --- debian/python3-libertine.install 2016-12-21 18:07:07 +0000 |
514 | +++ debian/python3-libertine.install 2017-02-09 16:08:46 +0000 |
515 | @@ -1,9 +1,6 @@ |
516 | -usr/lib/python*/*/libertine/AppDiscovery.py |
517 | usr/lib/python*/*/libertine/ContainersConfig.py |
518 | usr/lib/python*/*/libertine/HostInfo.py |
519 | usr/lib/python*/*/libertine/Libertine.py |
520 | usr/lib/python*/*/libertine/__init__.py |
521 | usr/lib/python*/*/libertine/launcher |
522 | -usr/lib/python*/*/libertine/lifecycle |
523 | -usr/lib/python*/*/libertine/service |
524 | usr/lib/python*/*/libertine/utils.py |
525 | |
526 | === modified file 'debian/rules' |
527 | --- debian/rules 2016-12-06 20:52:55 +0000 |
528 | +++ debian/rules 2017-02-09 16:08:46 +0000 |
529 | @@ -1,7 +1,4 @@ |
530 | #!/usr/bin/make -f |
531 | |
532 | %: |
533 | - dh $@ --with python3,gir |
534 | - |
535 | -override_dh_auto_test: |
536 | - dbus-run-session -- dh_auto_test |
537 | + dh $@ --with python3 |
538 | |
539 | === modified file 'liblibertine/CMakeLists.txt' |
540 | --- liblibertine/CMakeLists.txt 2016-11-03 19:19:40 +0000 |
541 | +++ liblibertine/CMakeLists.txt 2017-02-09 16:08:46 +0000 |
542 | @@ -1,15 +1,12 @@ |
543 | set(API_VERSION 1) |
544 | set(ABI_VERSION 1) |
545 | |
546 | -set(COMMON_DIR ${CMAKE_SOURCE_DIR}/common) |
547 | - |
548 | add_library( |
549 | ${LIBERTINE_CORE} SHARED |
550 | libertine.cpp |
551 | |
552 | - ${COMMON_DIR}/ContainerConfig.cpp |
553 | - ${COMMON_DIR}/ContainerConfigList.cpp |
554 | - ${COMMON_DIR}/LibertineConfig.cpp |
555 | + # libertined client |
556 | + libertined.cpp |
557 | ) |
558 | |
559 | set_target_properties(${LIBERTINE_CORE} PROPERTIES |
560 | @@ -22,6 +19,7 @@ |
561 | ${GLIB2_LIBRARIES} |
562 | ${PYTHON3_LIBRARIES} |
563 | Qt5::Core |
564 | + Qt5::DBus |
565 | ) |
566 | |
567 | # "liblibertine_headers_path" is used in libertine.pc.in |
568 | @@ -32,28 +30,3 @@ |
569 | configure_file(libertine.pc.in ${CMAKE_BINARY_DIR}/libertine.pc @ONLY) |
570 | install(FILES ${CMAKE_BINARY_DIR}/libertine.pc |
571 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) |
572 | - |
573 | -########################## |
574 | -# Introspection |
575 | -########################## |
576 | - |
577 | -include(UseGObjectIntrospection) |
578 | - |
579 | -set(INTROSPECTION_GIRS) |
580 | -set(_introspection_files libertine.h) |
581 | -set(Libertine_1_gir "libertine") |
582 | -set(Libertine_1_gir_INCLUDES GObject-2.0) |
583 | - |
584 | -gir_get_cflags(_cflags) |
585 | -set(Libertine_1_gir_CFLAGS ${c_flags}) |
586 | -set(Libertine_1_gir_LIBS libertine) |
587 | - |
588 | -list_make_absolute(_abs_introspection_files _introspection_files "${CMAKE_CURRENT_SOURCE_DIR}/") |
589 | -set(Libertine_1_gir_FILES ${_abs_introspection_files}) |
590 | -set(Libertine_1_gir_SCANNERFLAGS --c-include "libertine.h") |
591 | -set(Libertine_1_gir_EXPORT_PACKAGES "libertine-${API_VERSION}") |
592 | - |
593 | -list(APPEND INTROSPECTION_GIRS Libertine-1.gir) |
594 | -gir_add_introspections(INTROSPECTION_GIRS) |
595 | - |
596 | -install(FILES "${CMAKE_CURRENT_BINARY_DIR}/Libertine-1.typelib" DESTINATION "${CMAKE_INSTALL_LIBDIR}/girepository-1.0") |
597 | |
598 | === modified file 'liblibertine/libertine.cpp' |
599 | --- liblibertine/libertine.cpp 2017-01-12 18:18:49 +0000 |
600 | +++ liblibertine/libertine.cpp 2017-02-09 16:08:46 +0000 |
601 | @@ -1,9 +1,9 @@ |
602 | /** |
603 | - * @file libertine_common.cpp |
604 | + * @file libertine.cpp |
605 | * @brief The Libertine Common shared library |
606 | */ |
607 | /* |
608 | - * Copyright 2015 Canonical Ltd. |
609 | + * Copyright 2015-2017 Canonical Ltd. |
610 | * |
611 | * This program is free software: you can redistribute it and/or modify it under |
612 | * the terms of the GNU General Public License, version 3, as published by the |
613 | @@ -17,103 +17,21 @@ |
614 | * You should have received a copy of the GNU General Public License |
615 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
616 | */ |
617 | + |
618 | #include "liblibertine/libertine.h" |
619 | - |
620 | -#include "common/ContainerConfigList.h" |
621 | -#include "common/LibertineConfig.h" |
622 | - |
623 | - |
624 | -namespace |
625 | -{ |
626 | -constexpr auto DESKTOP_EXTENSION = ".desktop"; |
627 | -constexpr auto GLOBAL_APPLICATIONS = "usr/share/applications"; |
628 | -constexpr auto LOCAL_APPLICATIONS = ".local/share/applications"; |
629 | - |
630 | - |
631 | -GError* |
632 | -list_apps_from_path(gchar* path, const gchar* container_id, GArray* apps) |
633 | -{ |
634 | - GError* error = nullptr; |
635 | - GDir* dir = g_dir_open(path, 0, &error); |
636 | - if (error != nullptr) |
637 | - { |
638 | - return error; |
639 | - } |
640 | - |
641 | - const gchar * files; |
642 | - while ((files = g_dir_read_name(dir)) != nullptr) |
643 | - { |
644 | - gchar *file = g_build_filename(path, files, nullptr); |
645 | - if (g_file_test(file, G_FILE_TEST_IS_REGULAR) && g_str_has_suffix(files, DESKTOP_EXTENSION)) |
646 | - { |
647 | - auto name = g_strdup(files); |
648 | - name[strlen(name)-strlen(DESKTOP_EXTENSION)] = 0; // truncate the file extension |
649 | - |
650 | - gchar * app_id = g_strjoin("_", g_strdup(container_id), name, "0.0", nullptr); |
651 | - g_array_append_val(apps, app_id); |
652 | - g_free(name); |
653 | - } |
654 | - else if (g_file_test(file, G_FILE_TEST_IS_DIR)) |
655 | - { |
656 | - error = list_apps_from_path(file, container_id, apps); |
657 | - if (error != nullptr) |
658 | - { |
659 | - return error; |
660 | - } |
661 | - } |
662 | - g_free(file); |
663 | - } |
664 | - |
665 | - g_dir_close(dir); |
666 | - return nullptr; |
667 | -} |
668 | - |
669 | - |
670 | -gchar* |
671 | -id_from_list_index(const ContainerConfigList& container_list, guint index) |
672 | -{ |
673 | - return (gchar*)container_list.data(container_list.index(index, 0), |
674 | - (int)ContainerConfigList::DataRole::ContainerId) |
675 | - .toString().toStdString().c_str(); |
676 | -} |
677 | -} |
678 | +#include "liblibertine/libertined.h" |
679 | + |
680 | |
681 | gchar** |
682 | libertine_list_apps_for_container(const gchar* container_id) |
683 | { |
684 | g_return_val_if_fail(container_id != nullptr, nullptr); |
685 | - gchar* path = libertine_container_path(container_id); |
686 | - GError* error = nullptr; |
687 | GArray* apps = g_array_new(TRUE, TRUE, sizeof(gchar*)); |
688 | - |
689 | - if (path != nullptr) |
690 | - { |
691 | - auto global_path = g_build_filename("/", g_strdup(path), GLOBAL_APPLICATIONS, nullptr); |
692 | - error = list_apps_from_path(global_path, container_id, apps); |
693 | - if (error != nullptr) |
694 | - { |
695 | - g_free(global_path); |
696 | - g_free(path); |
697 | - g_error_free(error); |
698 | - return (gchar**)g_array_free(apps, FALSE); |
699 | - } |
700 | - g_free(global_path); |
701 | - } |
702 | - g_free(path); |
703 | - |
704 | - auto home_path = libertine_container_home_path(container_id); |
705 | - if (home_path != nullptr) |
706 | - { |
707 | - auto local_path = g_build_filename(home_path, LOCAL_APPLICATIONS, nullptr); |
708 | - |
709 | - error = list_apps_from_path(local_path, container_id, apps); |
710 | - if (error != nullptr) |
711 | - { |
712 | - g_error_free(error); // free error, but return previously found apps |
713 | - } |
714 | - g_free(local_path); |
715 | - } |
716 | - g_free(home_path); |
717 | + for (auto const& app: libertined_list_app_ids(container_id)) |
718 | + { |
719 | + auto app_id = g_strdup((gchar *)app.toString().toStdString().c_str()); |
720 | + g_array_append_val(apps, app_id); |
721 | + } |
722 | |
723 | return (gchar**)g_array_free(apps, FALSE); |
724 | } |
725 | @@ -122,22 +40,12 @@ |
726 | gchar ** |
727 | libertine_list_containers(void) |
728 | { |
729 | - guint container_count; |
730 | - guint i; |
731 | - LibertineConfig config; |
732 | - ContainerConfigList container_list(&config); |
733 | - GArray * containers = g_array_new(TRUE, TRUE, sizeof(gchar *)); |
734 | - QVariant id; |
735 | - |
736 | - container_count = (guint)container_list.size(); |
737 | - |
738 | - for (i = 0; i < container_count; ++i) |
739 | + auto containers = g_array_new(TRUE, TRUE, sizeof(gchar *)); |
740 | + for (auto const& container: libertined_list()) |
741 | { |
742 | - id = container_list.data(container_list.index(i, 0), (int)ContainerConfigList::DataRole::ContainerId); |
743 | - gchar * container_id = g_strdup((gchar *)id.toString().toStdString().c_str()); |
744 | + auto container_id = g_strdup((gchar *)container.toString().toStdString().c_str()); |
745 | g_array_append_val(containers, container_id); |
746 | } |
747 | - |
748 | return (gchar **)g_array_free(containers, FALSE); |
749 | } |
750 | |
751 | @@ -146,11 +54,8 @@ |
752 | libertine_container_path(const gchar * container_id) |
753 | { |
754 | g_return_val_if_fail(container_id != nullptr, nullptr); |
755 | - LibertineConfig config; |
756 | - ContainerConfigList container_list(&config); |
757 | - |
758 | - gchar * path = g_build_filename(g_get_user_cache_dir(), "libertine-container", container_id, "rootfs", nullptr); |
759 | - |
760 | + |
761 | + gchar* path = g_strdup((gchar *)libertined_container_path(container_id).toStdString().c_str()); |
762 | if (g_file_test(path, G_FILE_TEST_EXISTS)) |
763 | { |
764 | return path; |
765 | @@ -165,11 +70,8 @@ |
766 | libertine_container_home_path(const gchar * container_id) |
767 | { |
768 | g_return_val_if_fail(container_id != nullptr, nullptr); |
769 | - LibertineConfig config; |
770 | - ContainerConfigList container_list(&config); |
771 | - |
772 | - gchar * path = g_build_filename(g_get_user_data_dir(), "libertine-container", "user-data", container_id, nullptr); |
773 | - |
774 | + |
775 | + gchar* path = g_strdup((gchar *)libertined_container_home_path(container_id).toStdString().c_str()); |
776 | if (g_file_test(path, G_FILE_TEST_EXISTS)) |
777 | { |
778 | return path; |
779 | @@ -183,20 +85,7 @@ |
780 | gchar * |
781 | libertine_container_name(const gchar * container_id) |
782 | { |
783 | - gchar * container_name = nullptr; |
784 | - LibertineConfig config; |
785 | - ContainerConfigList container_list(&config); |
786 | - guint container_count = (guint)container_list.size(); |
787 | - |
788 | - for (guint i = 0; i < container_count; ++i) |
789 | - { |
790 | - if (g_strcmp0(id_from_list_index(container_list, i), container_id) == 0) |
791 | - { |
792 | - QVariant name = container_list.data(container_list.index(i, 0), (int)ContainerConfigList::DataRole::ContainerName); |
793 | - container_name = g_strdup(name.toString().toStdString().c_str()); |
794 | - break; |
795 | - } |
796 | - } |
797 | - |
798 | - return container_name; |
799 | + g_return_val_if_fail(container_id != nullptr, nullptr); |
800 | + |
801 | + return g_strdup((gchar *)libertined_container_name(container_id).toStdString().c_str()); |
802 | } |
803 | |
804 | === modified file 'liblibertine/libertine.h' |
805 | --- liblibertine/libertine.h 2016-09-06 12:36:47 +0000 |
806 | +++ liblibertine/libertine.h 2017-02-09 16:08:46 +0000 |
807 | @@ -1,5 +1,5 @@ |
808 | /* |
809 | - * Copyright 2015 Canonical Ltd. |
810 | + * Copyright 2015-2017 Canonical Ltd. |
811 | * |
812 | * This program is free software: you can redistribute it and/or modify it under |
813 | * the terms of the GNU General Public License, version 3, as published by the |
814 | @@ -15,8 +15,8 @@ |
815 | */ |
816 | #include <glib.h> |
817 | |
818 | -#ifndef _LIBERTINE_COMMON_H_ |
819 | -#define _LIBERTINE_COMMON_H_ |
820 | +#ifndef _LIBLIBERTINE_H_ |
821 | +#define _LIBLIBERTINE_H_ |
822 | |
823 | #ifdef __cplusplus |
824 | extern "C" { |
825 | @@ -80,4 +80,4 @@ |
826 | } |
827 | #endif |
828 | |
829 | -#endif /* _LIBERTINE_COMMON_H_ */ |
830 | +#endif /* _LIBLIBERTINE_H_ */ |
831 | |
832 | === added file 'liblibertine/libertined.cpp' |
833 | --- liblibertine/libertined.cpp 1970-01-01 00:00:00 +0000 |
834 | +++ liblibertine/libertined.cpp 2017-02-09 16:08:46 +0000 |
835 | @@ -0,0 +1,199 @@ |
836 | +/* |
837 | + * Copyright 2017 Canonical Ltd |
838 | + * |
839 | + * Libertine is free software: you can redistribute it and/or modify it under |
840 | + * the terms of the GNU General Public License, version 3, as published by the |
841 | + * Free Software Foundation. |
842 | + * |
843 | + * Libertine is distributed in the hope that it will be useful, but WITHOUT ANY |
844 | + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR |
845 | + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
846 | + * |
847 | + * You should have received a copy of the GNU General Public License |
848 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
849 | + */ |
850 | + |
851 | + |
852 | +#include "libertined.h" |
853 | + |
854 | +#include <QDBusMessage> |
855 | +#include <QDBusInterface> |
856 | +#include <QDebug> |
857 | +#include <thread> |
858 | +#include <chrono> |
859 | +#include <QJsonDocument> |
860 | +#include <QJsonArray> |
861 | +#include <QJsonObject> |
862 | + |
863 | +namespace |
864 | +{ |
865 | +static const auto SERVICE_INTERFACE = "com.canonical.libertine.Service"; |
866 | +static const auto PROGRESS_INTERFACE = "com.canonical.libertine.Service.Progress"; |
867 | + |
868 | +static QVariantList |
869 | +dbusCall(QDBusConnection const& bus, QString const& iface, QString const& path, |
870 | + QString const& method, QVariantList const& args = QVariantList()) |
871 | +{ |
872 | + auto message = QDBusMessage::createMethodCall(SERVICE_INTERFACE, path, iface, method); |
873 | + message.setArguments(args); |
874 | + auto response = bus.call(message); |
875 | + if (response.type() == QDBusMessage::ErrorMessage) |
876 | + { |
877 | + qWarning() << "error calling result" << response.errorMessage(); |
878 | + return QVariantList(); |
879 | + } |
880 | + |
881 | + return response.arguments(); |
882 | +} |
883 | + |
884 | +static bool |
885 | +isRunning(QDBusConnection const& bus, QString const& path) |
886 | +{ |
887 | + auto args = dbusCall(bus, PROGRESS_INTERFACE, path, "running", QVariantList()); |
888 | + |
889 | + if (args.isEmpty()) |
890 | + { |
891 | + qWarning() << "lastError - no arguments?"; |
892 | + return false; |
893 | + } |
894 | + |
895 | + return args.first().toBool(); |
896 | +} |
897 | + |
898 | +static QString |
899 | +result(QDBusConnection const& bus, QString const& path) |
900 | +{ |
901 | + auto args = dbusCall(bus, PROGRESS_INTERFACE, path, "result"); |
902 | + |
903 | + if (args.isEmpty()) |
904 | + { |
905 | + qWarning() << "lastError - no arguments?"; |
906 | + return ""; |
907 | + } |
908 | + |
909 | + return args.first().toString(); |
910 | +} |
911 | + |
912 | +static QString |
913 | +lastError(QDBusConnection const& bus, QString const& path) |
914 | +{ |
915 | + auto args = dbusCall(bus, PROGRESS_INTERFACE, path, "last_error"); |
916 | + |
917 | + if (args.isEmpty()) |
918 | + { |
919 | + qWarning() << "lastError - no arguments?"; |
920 | + return ""; |
921 | + } |
922 | + |
923 | + return args.first().toString(); |
924 | +} |
925 | + |
926 | +static QString |
927 | +call(QDBusConnection const& bus, QString const& method, QVariantList const& args) |
928 | +{ |
929 | + auto results = dbusCall(bus, SERVICE_INTERFACE, "/Manager", method, args); |
930 | + |
931 | + if (results.isEmpty()) |
932 | + { |
933 | + return QString(); |
934 | + } |
935 | + |
936 | + return qvariant_cast<QDBusObjectPath>(results.first()).path(); |
937 | +} |
938 | + |
939 | +static bool |
940 | +waitForFinish(QDBusConnection const& bus, QString const& path) |
941 | +{ |
942 | + std::chrono::microseconds wait(500); |
943 | + for (auto i = 0; i < 2000; ++i) |
944 | + { |
945 | + if (!isRunning(bus, path)) |
946 | + { |
947 | + return true; |
948 | + } |
949 | + std::this_thread::sleep_for(wait); |
950 | + } |
951 | + return !isRunning(bus, path); |
952 | +} |
953 | + |
954 | +QString |
955 | +container_info(char const* container_id, QString const& key) |
956 | +{ |
957 | + auto bus = QDBusConnection::sessionBus(); |
958 | + auto path = call(bus, "container_info", QVariantList{QVariant(container_id)}); |
959 | + |
960 | + if (!waitForFinish(bus, path)) |
961 | + { |
962 | + return QString(); |
963 | + } |
964 | + |
965 | + auto error = lastError(bus, path); |
966 | + if (!error.isEmpty()) |
967 | + { |
968 | + qWarning() << "error:" << error; |
969 | + return QString(); |
970 | + } |
971 | + |
972 | + return QJsonDocument::fromJson(result(bus, path).toLatin1()).object().value(key).toString(); |
973 | +} |
974 | +} |
975 | + |
976 | +QJsonArray |
977 | +libertined_list() |
978 | +{ |
979 | + auto bus = QDBusConnection::sessionBus(); |
980 | + auto path = call(bus, "list", QVariantList()); |
981 | + |
982 | + if (!waitForFinish(bus, path)) |
983 | + { |
984 | + return QJsonArray(); |
985 | + } |
986 | + |
987 | + auto error = lastError(bus, path); |
988 | + if (!error.isEmpty()) |
989 | + { |
990 | + qWarning() << "error:" << error; |
991 | + return QJsonArray(); |
992 | + } |
993 | + |
994 | + return QJsonDocument::fromJson(result(bus, path).toLatin1()).array(); |
995 | +} |
996 | + |
997 | +QJsonArray |
998 | +libertined_list_app_ids(char const* container_id) |
999 | +{ |
1000 | + auto bus = QDBusConnection::sessionBus(); |
1001 | + auto path = call(bus, "list_app_ids", QVariantList{QVariant(container_id)}); |
1002 | + |
1003 | + if (!waitForFinish(bus, path)) |
1004 | + { |
1005 | + return QJsonArray(); |
1006 | + } |
1007 | + |
1008 | + auto error = lastError(bus, path); |
1009 | + if (!error.isEmpty()) |
1010 | + { |
1011 | + qWarning() << "error:" << error; |
1012 | + return QJsonArray(); |
1013 | + } |
1014 | + |
1015 | + return QJsonDocument::fromJson(result(bus, path).toLatin1()).array(); |
1016 | +} |
1017 | + |
1018 | +QString |
1019 | +libertined_container_path(char const* container_id) |
1020 | +{ |
1021 | + return container_info(container_id, "root"); |
1022 | +} |
1023 | + |
1024 | +QString |
1025 | +libertined_container_home_path(char const* container_id) |
1026 | +{ |
1027 | + return container_info(container_id, "home"); |
1028 | +} |
1029 | + |
1030 | +QString |
1031 | +libertined_container_name(char const* container_id) |
1032 | +{ |
1033 | + return container_info(container_id, "name"); |
1034 | +} |
1035 | |
1036 | === added file 'liblibertine/libertined.h' |
1037 | --- liblibertine/libertined.h 1970-01-01 00:00:00 +0000 |
1038 | +++ liblibertine/libertined.h 2017-02-09 16:08:46 +0000 |
1039 | @@ -0,0 +1,25 @@ |
1040 | +/* |
1041 | + * Copyright 2017 Canonical Ltd |
1042 | + * |
1043 | + * Libertine is free software: you can redistribute it and/or modify it under |
1044 | + * the terms of the GNU General Public License, version 3, as published by the |
1045 | + * Free Software Foundation. |
1046 | + * |
1047 | + * Libertine is distributed in the hope that it will be useful, but WITHOUT ANY |
1048 | + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR |
1049 | + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
1050 | + * |
1051 | + * You should have received a copy of the GNU General Public License |
1052 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1053 | + */ |
1054 | + |
1055 | +#pragma once |
1056 | + |
1057 | +#include <QDBusConnection> |
1058 | +#include <QJsonArray> |
1059 | + |
1060 | +QJsonArray libertined_list(); |
1061 | +QString libertined_container_path(char const* container_id); |
1062 | +QString libertined_container_home_path(char const* container_id); |
1063 | +QString libertined_container_name(char const* container_id); |
1064 | +QJsonArray libertined_list_app_ids(char const* container_id); |
1065 | |
1066 | === removed file 'python/libertine/AppDiscovery.py' |
1067 | --- python/libertine/AppDiscovery.py 2016-12-07 21:50:36 +0000 |
1068 | +++ python/libertine/AppDiscovery.py 1970-01-01 00:00:00 +0000 |
1069 | @@ -1,220 +0,0 @@ |
1070 | -# Copyright 2015 Canonical Ltd. |
1071 | -# |
1072 | -# This program is free software: you can redistribute it and/or modify it |
1073 | -# under the terms of the GNU General Public License version 3, as published |
1074 | -# by the Free Software Foundation. |
1075 | -# |
1076 | -# This program is distributed in the hope that it will be useful, but |
1077 | -# WITHOUT ANY WARRANTY; without even the implied warranties of |
1078 | -# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1079 | -# PURPOSE. See the GNU General Public License for more details. |
1080 | -# |
1081 | -# You should have received a copy of the GNU General Public License along |
1082 | -# with this program. If not, see <http://www.gnu.org/licenses/>. |
1083 | - |
1084 | -from . import utils |
1085 | -from xdg.BaseDirectory import xdg_data_dirs |
1086 | -import configparser |
1087 | -import glob |
1088 | -import io |
1089 | -import json |
1090 | -import os |
1091 | -import re |
1092 | -import sys |
1093 | - |
1094 | - |
1095 | -class IconCache(object): |
1096 | - """ |
1097 | - Caches the names of all icon files available in the standard places (possibly |
1098 | - in a container) and provides a search function to deliver icon file names |
1099 | - matching a given icon name. |
1100 | - |
1101 | - See the `Freedesktop.org icon theme specification |
1102 | - <http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html>`_ |
1103 | - for the detailed specification. |
1104 | - """ |
1105 | - |
1106 | - def __init__(self, root_path, file_loader=None): |
1107 | - """ |
1108 | - :param root_path: Where to start the file scan. |
1109 | - :type root_path: A valid filesystem path string. |
1110 | - :param file_loader: A function that builds a cache of filenames. |
1111 | - :type file_loader: Function returning a list of filenames. |
1112 | - """ |
1113 | - if file_loader: |
1114 | - self._icon_cache = file_loader(root_path) |
1115 | - else: |
1116 | - self._icon_cache = self._file_loader(root_path) |
1117 | - |
1118 | - def _get_icon_search_paths(self, root_path): |
1119 | - """ |
1120 | - Gets a list of paths on which to search for icons. |
1121 | - |
1122 | - :param root_path: Where to start the file scan. |
1123 | - :type root_path: A valid filesystem path string. |
1124 | - :rtype: A list of filesystem patch to serarch for qualifying icon files. |
1125 | - """ |
1126 | - icon_search_paths = [] |
1127 | - icon_search_paths.append(os.path.join(root_path, os.environ['HOME'], ".icons")) |
1128 | - for d in reversed(xdg_data_dirs): |
1129 | - icon_search_paths.append(os.path.join(root_path, d.lstrip('/'), "icons")) |
1130 | - icon_search_paths.append(os.path.join(root_path, "usr/share/pixmaps")) |
1131 | - return icon_search_paths |
1132 | - |
1133 | - def _file_loader(self, root_path): |
1134 | - """ |
1135 | - Loads a cache of file names by scanning the filesystem rooted at |
1136 | - ``root_path``. |
1137 | - |
1138 | - :param root_path: Where to start the file scan. |
1139 | - :type root_path: A valid filesystem path. |
1140 | - :rtype: A list of fully-qualified file paths. |
1141 | - """ |
1142 | - file_names = [] |
1143 | - pattern = re.compile(r".*\.(png|svg|xpm)") |
1144 | - for path in self._get_icon_search_paths(root_path): |
1145 | - for base, dirs, files in os.walk(path): |
1146 | - for file in files: |
1147 | - if pattern.match(file): |
1148 | - file_names.append(os.path.join(base, file)) |
1149 | - return file_names |
1150 | - |
1151 | - def _find_icon_files(self, icon_name): |
1152 | - """ |
1153 | - Finds a list of file name strings matching the given icon name. |
1154 | - |
1155 | - :param icon_name: An icon name, pobably from a .desktop file. |
1156 | - :rtype: A list of filename strings matching the icon name. |
1157 | - """ |
1158 | - icon_file_names = [] |
1159 | - match_string = r"/" + os.path.splitext(icon_name)[0] + r"\....$" |
1160 | - pattern = re.compile(match_string) |
1161 | - for icon_file in self._icon_cache: |
1162 | - if pattern.search(icon_file): |
1163 | - icon_file_names.append(icon_file) |
1164 | - return icon_file_names |
1165 | - |
1166 | - def expand_icons(self, desktop_icon_list): |
1167 | - """ |
1168 | - Expands a string containing a list of icon names into a list of matching |
1169 | - file names. |
1170 | - |
1171 | - :param desktop_icon_list: A string containing a list of icon names |
1172 | - separated by semicolons. |
1173 | - :rtype: A list of filename stings matching the icon names passed in. |
1174 | - |
1175 | - See `the Freedesktop.org desktop file specification |
1176 | - <http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#recognized-keys>`_ |
1177 | - for more information. |
1178 | - """ |
1179 | - if desktop_icon_list: |
1180 | - icon_list = desktop_icon_list.split(';') |
1181 | - if icon_list[0][0] == '/': |
1182 | - return icon_list |
1183 | - icon_files = [] |
1184 | - for i in icon_list: |
1185 | - icon_files += self._find_icon_files(i) |
1186 | - return icon_files |
1187 | - return [] |
1188 | - |
1189 | - |
1190 | -def expand_mime_types(desktop_mime_types): |
1191 | - if desktop_mime_types: |
1192 | - return desktop_mime_types.split(';') |
1193 | - return [] |
1194 | - |
1195 | - |
1196 | -class AppInfo(object): |
1197 | - |
1198 | - def __init__(self, desktop_file_name, config_entry, icon_cache): |
1199 | - self.desktop_file_name = desktop_file_name |
1200 | - self.name = config_entry.get('Name') |
1201 | - if not self.name: |
1202 | - raise RuntimeError("required Name attribute is missing") |
1203 | - d = config_entry.get('NoDisplay') |
1204 | - self.no_display = (d != None and d == 'true') |
1205 | - self.exec_line = config_entry.get('Exec') |
1206 | - if not self.exec_line: |
1207 | - raise RuntimeError("required Exec attribute is missing") |
1208 | - self.icons = icon_cache.expand_icons(config_entry.get('Icon')) |
1209 | - self.mime_types = expand_mime_types(config_entry.get('MimeType')) |
1210 | - |
1211 | - def __str__(self): |
1212 | - with io.StringIO() as ostr: |
1213 | - print(self.name, file=ostr) |
1214 | - print(" desktop_file={}".format(self.desktop_file_name), file=ostr) |
1215 | - print(" no-display={}".format(self.no_display), file=ostr) |
1216 | - print(" exec='{}'".format(self.exec_line), file=ostr) |
1217 | - for icon in self.icons: |
1218 | - print(" icon: {}".format(icon), file=ostr) |
1219 | - for mime in self.mime_types: |
1220 | - print(" mime: {}".format(mime), file=ostr) |
1221 | - return ostr.getvalue() |
1222 | - |
1223 | - def to_json(self): |
1224 | - return json.dumps(self.__dict__) |
1225 | - |
1226 | - |
1227 | -def desktop_file_is_showable(desktop_entry): |
1228 | - """ |
1229 | - Determines if a particular application entry should be reported: the entry |
1230 | - can not be hidden and must be showable in Unity. |
1231 | - """ |
1232 | - t = desktop_entry.get('Type') |
1233 | - if t != 'Application': |
1234 | - return False |
1235 | - n = desktop_entry.get('Hidden') |
1236 | - if n and n == 'true': |
1237 | - return False |
1238 | - n = desktop_entry.get('NoShowIn') |
1239 | - if n: |
1240 | - targets = n.split(';') |
1241 | - if 'Unity' in targets: |
1242 | - return False |
1243 | - n = desktop_entry.get('OnlyShowIn') |
1244 | - if n: |
1245 | - targets = n.split(';') |
1246 | - if 'Unity' not in targets: |
1247 | - return False |
1248 | - return True |
1249 | - |
1250 | - |
1251 | -def get_app_info(desktop_path, icon_cache): |
1252 | - for desktop_file_name in glob.glob(desktop_path): |
1253 | - desktop_file = configparser.ConfigParser(strict=False, interpolation=None) |
1254 | - try: |
1255 | - desktop_file.read(desktop_file_name) |
1256 | - desktop_entry = desktop_file['Desktop Entry'] |
1257 | - if desktop_file_is_showable(desktop_entry): |
1258 | - yield AppInfo(desktop_file_name, desktop_entry, icon_cache) |
1259 | - except Exception as ex: |
1260 | - utils.get_logger().error("error processing {}: {}".format(desktop_file_name, ex)) |
1261 | - |
1262 | - |
1263 | -class AppLauncherCache(object): |
1264 | - """ |
1265 | - Caches a list of application launcher information (derived from .desktop |
1266 | - files installed in a container. |
1267 | - """ |
1268 | - |
1269 | - def __init__(self, name, root_path): |
1270 | - self.name = name |
1271 | - self.app_launchers = [] |
1272 | - icon_cache = IconCache(root_path) |
1273 | - for dir in reversed(xdg_data_dirs): |
1274 | - path = os.path.join(root_path, dir.lstrip('/'), "applications") |
1275 | - for app_info in get_app_info(os.path.join(path, "*.desktop"), icon_cache): |
1276 | - self.app_launchers.append(app_info) |
1277 | - |
1278 | - def __str__(self): |
1279 | - with io.StringIO() as ostr: |
1280 | - print("{}\n".format(self.name), file=ostr) |
1281 | - for app_info in self.app_launchers: |
1282 | - print(" {}".format(app_info), file=ostr) |
1283 | - return ostr.getvalue() |
1284 | - |
1285 | - def to_json(self): |
1286 | - return json.dumps(self, |
1287 | - default=lambda o: o.__dict__, |
1288 | - sort_keys=True, |
1289 | - indent=4) |
1290 | |
1291 | === modified file 'python/libertine/ChrootContainer.py' |
1292 | --- python/libertine/ChrootContainer.py 2017-01-17 20:19:30 +0000 |
1293 | +++ python/libertine/ChrootContainer.py 2017-02-09 16:08:46 +0000 |
1294 | @@ -1,4 +1,4 @@ |
1295 | -# Copyright 2015-2016 Canonical Ltd. |
1296 | +# Copyright 2015-2017 Canonical Ltd. |
1297 | # |
1298 | # This program is free software: you can redistribute it and/or modify it |
1299 | # under the terms of the GNU General Public License version 3, as published |
1300 | @@ -47,10 +47,8 @@ |
1301 | A concrete container type implemented using a plain old chroot. |
1302 | """ |
1303 | |
1304 | - def __init__(self, container_id): |
1305 | - super().__init__(container_id) |
1306 | - self.container_type = "chroot" |
1307 | - self._window_manager = None |
1308 | + def __init__(self, container_id, config): |
1309 | + super().__init__(container_id, 'chroot', config) |
1310 | # FIXME: Disabling seccomp is a temporary measure until we fully understand why we need |
1311 | # it or figure out when we need it. |
1312 | os.environ['PROOT_NO_SECCOMP'] = '1' |
1313 | @@ -112,7 +110,7 @@ |
1314 | fd.write(archive + self.installed_release + " multiverse\n") |
1315 | fd.write(archive + self.installed_release + "-updates multiverse\n") |
1316 | |
1317 | - utils.create_libertine_user_data_dir(self.container_id) |
1318 | + self._create_libertine_user_data_dir() |
1319 | |
1320 | self.update_locale() |
1321 | |
1322 | @@ -140,7 +138,7 @@ |
1323 | self.update_packages() |
1324 | |
1325 | # Check if the container was created as root and chown the user directories as necessary |
1326 | - chown_recursive_dirs(utils.get_libertine_container_userdata_dir_path(self.container_id)) |
1327 | + chown_recursive_dirs(utils.get_libertine_container_home_dir(self.container_id)) |
1328 | |
1329 | super().create_libertine_container() |
1330 | |
1331 | @@ -186,7 +184,7 @@ |
1332 | # Bind-mount common XDG direcotries |
1333 | bind_mounts = ( |
1334 | " -b %s:%s" |
1335 | - % (utils.get_libertine_container_userdata_dir_path(self.container_id), home_path) |
1336 | + % (utils.get_libertine_container_home_dir(self.container_id), home_path) |
1337 | ) |
1338 | |
1339 | mounts = self._sanitize_bind_mounts(utils.get_common_xdg_user_directories() + \ |
1340 | @@ -227,15 +225,10 @@ |
1341 | proot_cmd = self._build_proot_command() |
1342 | |
1343 | args = shlex.split(proot_cmd) |
1344 | - args.extend(self.setup_window_manager(enable_toolbars=True)) |
1345 | - self._window_manager = psutil.Popen(args, env=environ) |
1346 | - |
1347 | - args = shlex.split(proot_cmd) |
1348 | args.extend(app_exec_line) |
1349 | return psutil.Popen(args, env=environ) |
1350 | |
1351 | def finish_application(self, app): |
1352 | - utils.terminate_window_manager(self._window_manager) |
1353 | app.wait() |
1354 | |
1355 | def _run_ldconfig(self): |
1356 | |
1357 | === modified file 'python/libertine/ContainersConfig.py' |
1358 | --- python/libertine/ContainersConfig.py 2017-01-12 20:46:05 +0000 |
1359 | +++ python/libertine/ContainersConfig.py 2017-02-09 16:08:46 +0000 |
1360 | @@ -1,4 +1,4 @@ |
1361 | -# Copyright 2016 Canonical Ltd. |
1362 | +# Copyright 2016-2017 Canonical Ltd. |
1363 | # |
1364 | # This program is free software: you can redistribute it and/or modify it |
1365 | # under the terms of the GNU General Public License version 3, as published |
1366 | @@ -102,7 +102,8 @@ |
1367 | if not container: |
1368 | return |
1369 | |
1370 | - if type(value) is str: |
1371 | + if (type(value) is str or |
1372 | + type(value) is bool): |
1373 | container[key] = value |
1374 | elif type(value) is dict: |
1375 | if key not in container: |
1376 | @@ -241,6 +242,9 @@ |
1377 | if write_json: |
1378 | write_container_config_file(self.container_list) |
1379 | |
1380 | + def get_containers(self): |
1381 | + return [c["id"] for c in self.container_list.get('containerList', [])] |
1382 | + |
1383 | """ |
1384 | Operations for the container itself. |
1385 | """ |
1386 | @@ -302,6 +306,12 @@ |
1387 | def get_container_locale(self, container_id): |
1388 | return self._get_value_by_key(container_id, 'locale') |
1389 | |
1390 | + def get_container_name(self, container_id): |
1391 | + return self._get_value_by_key(container_id, 'name') |
1392 | + |
1393 | + def get_container_install_status(self, container_id): |
1394 | + return self._get_value_by_key(container_id, 'installStatus') |
1395 | + |
1396 | """ |
1397 | Operations for archive (PPA) maintenance in a Libertine container. |
1398 | """ |
1399 | @@ -375,6 +385,15 @@ |
1400 | self._delete_array_object_by_value(container_id, 'bindMounts', mount_path) |
1401 | |
1402 | """ |
1403 | + Operations for setting container freeze on stop command. |
1404 | + """ |
1405 | + def update_freeze_on_stop(self, container_id, freeze_on_stop=True): |
1406 | + self._set_value_by_key(container_id, 'freezeOnStop', freeze_on_stop) |
1407 | + |
1408 | + def get_freeze_on_stop(self, container_id): |
1409 | + return self._get_value_by_key(container_id, 'freezeOnStop') or False |
1410 | + |
1411 | + """ |
1412 | Fetcher functions for various configuration information. |
1413 | """ |
1414 | def get_container_distro(self, container_id): |
1415 | |
1416 | === modified file 'python/libertine/HostInfo.py' |
1417 | --- python/libertine/HostInfo.py 2017-01-12 20:46:05 +0000 |
1418 | +++ python/libertine/HostInfo.py 2017-02-09 16:08:46 +0000 |
1419 | @@ -12,6 +12,7 @@ |
1420 | # You should have received a copy of the GNU General Public License along |
1421 | # with this program. If not, see <http://www.gnu.org/licenses/>. |
1422 | |
1423 | +import libertine.utils |
1424 | import locale |
1425 | import lsb_release |
1426 | import os |
1427 | @@ -25,7 +26,10 @@ |
1428 | |
1429 | def select_container_type_by_kernel(self): |
1430 | if self.has_lxc_support(): |
1431 | - return "lxc" |
1432 | + if libertine.utils.is_snap_environment(): |
1433 | + return "lxd" |
1434 | + else: |
1435 | + return "lxc" |
1436 | else: |
1437 | return "chroot" |
1438 | |
1439 | @@ -64,6 +68,9 @@ |
1440 | return None |
1441 | |
1442 | def get_host_architecture(self): |
1443 | + if 'ARCH' in os.environ: |
1444 | + return os.environ['ARCH'] |
1445 | + |
1446 | dpkg = subprocess.Popen(['dpkg', '--print-architecture'], |
1447 | stdout=subprocess.PIPE, |
1448 | universal_newlines=True) |
1449 | |
1450 | === modified file 'python/libertine/Libertine.py' |
1451 | --- python/libertine/Libertine.py 2017-01-17 20:28:15 +0000 |
1452 | +++ python/libertine/Libertine.py 2017-02-09 16:08:46 +0000 |
1453 | @@ -1,4 +1,4 @@ |
1454 | -# Copyright 2015-2016 Canonical Ltd. |
1455 | +# Copyright 2015-2017 Canonical Ltd. |
1456 | # |
1457 | # This program is free software: you can redistribute it and/or modify it |
1458 | # under the terms of the GNU General Public License version 3, as published |
1459 | @@ -12,8 +12,6 @@ |
1460 | # You should have received a copy of the GNU General Public License along |
1461 | # with this program. If not, see <http://www.gnu.org/licenses/>. |
1462 | |
1463 | -from .AppDiscovery import AppLauncherCache |
1464 | -from gi.repository import Libertine |
1465 | import abc |
1466 | import contextlib |
1467 | import os |
1468 | @@ -81,17 +79,14 @@ |
1469 | |
1470 | :param container_id: The machine-readable container name. |
1471 | """ |
1472 | - def __init__(self, container_id, containers_config=None): |
1473 | - if containers_config is None: |
1474 | - containers_config = ContainersConfig() |
1475 | - |
1476 | - self.container_type = 'unknown' |
1477 | + def __init__(self, container_id, container_type, config): |
1478 | + self.container_type = container_type |
1479 | self.container_id = container_id |
1480 | + self._config = config |
1481 | self.root_path = utils.get_libertine_container_rootfs_path(self.container_id) |
1482 | - self.locale = containers_config.get_container_locale(container_id) |
1483 | + self.locale = self._config.get_container_locale(container_id) |
1484 | self.language = self._get_language_from_locale() |
1485 | - self.default_packages = ['matchbox-window-manager', |
1486 | - 'libnss-extrausers', |
1487 | + self.default_packages = ['libnss-extrausers', |
1488 | 'humanity-icon-theme', |
1489 | 'maliit-inputcontext-gtk2', |
1490 | 'maliit-inputcontext-gtk3', |
1491 | @@ -126,16 +121,6 @@ |
1492 | utils.get_logger().error("%s" % e) |
1493 | return False |
1494 | |
1495 | - def setup_window_manager(self, enable_toolbars=False): |
1496 | - if self._binary_exists('matchbox-window-manager'): |
1497 | - if enable_toolbars: |
1498 | - return ['matchbox-window-manager'] |
1499 | - |
1500 | - return ['matchbox-window-manager', '-use_titlebar', 'no'] |
1501 | - else: |
1502 | - return ['compiz'] |
1503 | - |
1504 | - |
1505 | def check_language_support(self): |
1506 | if not self._binary_exists('check-language-support'): |
1507 | self.install_package('language-selector-common', update_cache=False) |
1508 | @@ -185,7 +170,7 @@ |
1509 | 'running' state, the meaning of which depends on the type of the |
1510 | container. |
1511 | """ |
1512 | - pass |
1513 | + return True |
1514 | |
1515 | def stop_container(self): |
1516 | """ |
1517 | @@ -193,6 +178,12 @@ |
1518 | """ |
1519 | pass |
1520 | |
1521 | + def restart_container(self): |
1522 | + """ |
1523 | + Restarts the container. |
1524 | + """ |
1525 | + pass |
1526 | + |
1527 | @abc.abstractmethod |
1528 | def run_in_container(self, command_string): |
1529 | """ |
1530 | @@ -247,10 +238,10 @@ |
1531 | self.delete_file_in_container(dest) |
1532 | |
1533 | return ret |
1534 | - else: |
1535 | - if no_dialog: |
1536 | - os.environ['DEBIAN_FRONTEND'] = 'teletype' |
1537 | - ret = self.run_in_container(_apt_command_prefix() + " install '" + package_name + "'") == 0 |
1538 | + |
1539 | + if no_dialog: |
1540 | + os.environ['DEBIAN_FRONTEND'] = 'teletype' |
1541 | + ret = self.run_in_container(_apt_command_prefix() + " install '" + package_name + "'") == 0 |
1542 | |
1543 | self.check_language_support() |
1544 | |
1545 | @@ -313,19 +304,26 @@ |
1546 | """ |
1547 | The human-readable name of the container. |
1548 | """ |
1549 | - name = Libertine.container_name(self.container_id) |
1550 | - if not name: |
1551 | - name = 'Unknown' |
1552 | - return name |
1553 | + return self._config.get_container_name(self.container_id) or 'Unknown' |
1554 | + |
1555 | + def _create_libertine_user_data_dir(self): |
1556 | + user_data = utils.get_libertine_container_home_dir(self.container_id) |
1557 | + |
1558 | + if not os.path.exists(user_data): |
1559 | + os.makedirs(user_data) |
1560 | + |
1561 | + config_path = os.path.join(user_data, ".config", "dconf") |
1562 | + |
1563 | + if not os.path.exists(config_path): |
1564 | + os.makedirs(config_path) |
1565 | |
1566 | |
1567 | class LibertineMock(BaseContainer): |
1568 | """ |
1569 | A concrete mock container type. Used for unit testing. |
1570 | """ |
1571 | - def __init__(self, container_id, containers_config=None): |
1572 | - super().__init__(container_id, containers_config) |
1573 | - self.container_type = "mock" |
1574 | + def __init__(self, container_id, config): |
1575 | + super().__init__(container_id, 'mock', config) |
1576 | |
1577 | def create_libertine_container(self, password=None, multiarch=False): |
1578 | return True |
1579 | @@ -354,6 +352,9 @@ |
1580 | def finish_application(self, app): |
1581 | app.wait() |
1582 | |
1583 | + def start_container(self): |
1584 | + return True |
1585 | + |
1586 | |
1587 | class ContainerRunning(contextlib.ExitStack): |
1588 | """ |
1589 | @@ -364,7 +365,9 @@ |
1590 | """ |
1591 | def __init__(self, container): |
1592 | super().__init__() |
1593 | - container.start_container() |
1594 | + if not container.start_container(): |
1595 | + raise RuntimeError("Container failed to start.") |
1596 | + |
1597 | self.callback(lambda: container.stop_container()) |
1598 | |
1599 | |
1600 | @@ -389,13 +392,13 @@ |
1601 | |
1602 | if container_type == "lxc": |
1603 | from libertine.LxcContainer import LibertineLXC |
1604 | - self.container = LibertineLXC(container_id) |
1605 | + self.container = LibertineLXC(container_id, self.containers_config) |
1606 | elif container_type == "lxd": |
1607 | from libertine.LxdContainer import LibertineLXD |
1608 | self.container = LibertineLXD(container_id, self.containers_config) |
1609 | elif container_type == "chroot": |
1610 | from libertine.ChrootContainer import LibertineChroot |
1611 | - self.container = LibertineChroot(container_id) |
1612 | + self.container = LibertineChroot(container_id, self.containers_config) |
1613 | elif container_type == "mock": |
1614 | self.container = LibertineMock(container_id, self.containers_config) |
1615 | else: |
1616 | @@ -480,6 +483,12 @@ |
1617 | except RuntimeError as e: |
1618 | return handle_runtime_error(e) |
1619 | |
1620 | + def restart_libertine_container(self): |
1621 | + """ |
1622 | + Restarts a frozen container. |
1623 | + """ |
1624 | + return self.container.restart_container() |
1625 | + |
1626 | def connect(self): |
1627 | """ |
1628 | Connects to the container in preparation to launch an application. May |
1629 | @@ -509,22 +518,23 @@ |
1630 | """ |
1631 | self.container.finish_application(app) |
1632 | |
1633 | - def list_app_launchers(self, use_json=False): |
1634 | - """ |
1635 | - Enumerates all application launchers (based on .desktop files) available |
1636 | - in the container. |
1637 | - |
1638 | - :param use_json: Indicates the returned string should be i JSON format. |
1639 | - The default format is some human-readble format. |
1640 | - :rtype: A printable string containing a list of application launchers |
1641 | - available in the container. |
1642 | - """ |
1643 | - if use_json: |
1644 | - return AppLauncherCache(self.container.name, |
1645 | - self.container.root_path).to_json() |
1646 | - else: |
1647 | - return str(AppLauncherCache(self.container.name, |
1648 | - self.container.root_path)) |
1649 | + def list_app_ids(self): |
1650 | + """ |
1651 | + Finds application ids (based on .desktop files) available in the |
1652 | + container. |
1653 | + |
1654 | + :rtype: A list of app ids consumable by tools such as ubuntu-app-launch |
1655 | + """ |
1656 | + home = utils.get_libertine_container_home_dir(self.container_id) |
1657 | + app_ids = [] |
1658 | + for apps_dir in ["{}/usr/share/applications".format(self.root_path), |
1659 | + "{}/usr/local/share/applications".format(self.root_path), |
1660 | + "{}/.local/share/applications".format(home)]: |
1661 | + if os.path.exists(apps_dir): |
1662 | + for root, dirs, files in os.walk(apps_dir): |
1663 | + app_ids.extend(["{}_{}_0.0".format(self.container_id, f[:-8]) for f in files if f.endswith(".desktop")]) |
1664 | + |
1665 | + return app_ids |
1666 | |
1667 | def exec_command(self, exec_line): |
1668 | """ |
1669 | |
1670 | === modified file 'python/libertine/LxcContainer.py' |
1671 | --- python/libertine/LxcContainer.py 2017-01-17 15:41:51 +0000 |
1672 | +++ python/libertine/LxcContainer.py 2017-02-09 16:08:46 +0000 |
1673 | @@ -1,4 +1,4 @@ |
1674 | -# Copyright 2015-2016 Canonical Ltd. |
1675 | +# Copyright 2015-2017 Canonical Ltd. |
1676 | # |
1677 | # This program is free software: you can redistribute it and/or modify it |
1678 | # under the terms of the GNU General Public License version 3, as published |
1679 | @@ -22,17 +22,15 @@ |
1680 | import subprocess |
1681 | import sys |
1682 | import tempfile |
1683 | +import time |
1684 | |
1685 | -from .lifecycle import LifecycleResult |
1686 | from .Libertine import BaseContainer |
1687 | +from .service.manager import LIBERTINE_MANAGER_NAME, LIBERTINE_STORE_PATH |
1688 | from . import utils, HostInfo |
1689 | |
1690 | |
1691 | home_path = os.environ['HOME'] |
1692 | |
1693 | -LIBERTINE_LXC_MANAGER_NAME = "com.canonical.libertine.LxcManager" |
1694 | -LIBERTINE_LXC_MANAGER_PATH = "/LxcManager" |
1695 | - |
1696 | |
1697 | def _check_lxc_net_entry(entry): |
1698 | lxc_net_file = open('/etc/lxc/lxc-usernet') |
1699 | @@ -57,14 +55,6 @@ |
1700 | return os.path.join(home_path, '.config', 'lxc') |
1701 | |
1702 | |
1703 | -def get_lxc_manager_dbus_name(): |
1704 | - return LIBERTINE_LXC_MANAGER_NAME |
1705 | - |
1706 | - |
1707 | -def get_lxc_manager_dbus_path(): |
1708 | - return LIBERTINE_LXC_MANAGER_PATH |
1709 | - |
1710 | - |
1711 | def lxc_container(container_id): |
1712 | config_path = utils.get_libertine_containers_dir_path() |
1713 | if not os.path.exists(config_path): |
1714 | @@ -101,22 +91,43 @@ |
1715 | def lxc_start(container): |
1716 | lxc_log_file = get_logfile(container) |
1717 | |
1718 | - if not container.start(): |
1719 | - return LifecycleResult("Container failed to start.") |
1720 | + if container.state == 'STOPPED': |
1721 | + if not container.start(): |
1722 | + utils.get_logger().error("Container failed to start.") |
1723 | + return False |
1724 | + elif container.state == 'FROZEN': |
1725 | + if not container.unfreeze(): |
1726 | + utils.get_logger().error("Container failed to unfreeze.") |
1727 | + return False |
1728 | |
1729 | if not container.wait("RUNNING", 10): |
1730 | - return LifecycleResult("Container failed to enter the RUNNING state.") |
1731 | + utils.get_logger().error("Container failed to enter the RUNNING state.") |
1732 | + return False |
1733 | |
1734 | if not container.get_ips(timeout=30): |
1735 | lxc_stop(container) |
1736 | - return LifecycleResult("Not able to connect to the network.") |
1737 | - |
1738 | - return LifecycleResult() |
1739 | - |
1740 | - |
1741 | -def lxc_stop(container): |
1742 | - if container.running: |
1743 | + utils.get_logger().error("Not able to connect to the network.") |
1744 | + return False |
1745 | + |
1746 | + return True |
1747 | + |
1748 | + |
1749 | +def lxc_stop(container, freeze_on_stop=False): |
1750 | + if container.state == 'STOPPED' or not container.running: |
1751 | + return True |
1752 | + |
1753 | + if freeze_on_stop: |
1754 | + container.freeze() |
1755 | + if not container.wait("FROZEN", 10): |
1756 | + utils.get_logger().error("Container failed to enter the FROZEN state.") |
1757 | + return False |
1758 | + else: |
1759 | container.stop() |
1760 | + if not container.wait("STOPPED", 10): |
1761 | + utils.get_logger().error("Container failed to enter the STOPPED state.") |
1762 | + return False |
1763 | + |
1764 | + return True |
1765 | |
1766 | |
1767 | class EnvLxcSettings(contextlib.ExitStack): |
1768 | @@ -149,21 +160,62 @@ |
1769 | A concrete container type implemented using an LXC container. |
1770 | """ |
1771 | |
1772 | - def __init__(self, container_id): |
1773 | - super().__init__(container_id) |
1774 | - self.container_type = "lxc" |
1775 | + def __init__(self, container_id, config): |
1776 | + super().__init__(container_id, 'lxc', config) |
1777 | self.container = lxc_container(container_id) |
1778 | self.lxc_manager_interface = None |
1779 | - self.window_manager = None |
1780 | self.host_info = HostInfo.HostInfo() |
1781 | - |
1782 | - utils.set_session_dbus_env_var() |
1783 | - |
1784 | - try: |
1785 | - bus = dbus.SessionBus() |
1786 | - self.lxc_manager_interface = bus.get_object(get_lxc_manager_dbus_name(), get_lxc_manager_dbus_path()) |
1787 | - except dbus.exceptions.DBusException: |
1788 | - pass |
1789 | + self._freeze_on_stop = config.get_freeze_on_stop(self.container_id) |
1790 | + |
1791 | + if utils.set_session_dbus_env_var(): |
1792 | + try: |
1793 | + bus = dbus.SessionBus() |
1794 | + self.lxc_manager_interface = bus.get_object(LIBERTINE_MANAGER_NAME, LIBERTINE_STORE_PATH) |
1795 | + except dbus.exceptions.DBusException: |
1796 | + pass |
1797 | + |
1798 | + def _setup_pulse(self): |
1799 | + pulse_socket_path = os.path.join(utils.get_libertine_runtime_dir(), 'pulse_socket') |
1800 | + |
1801 | + os.environ['PULSE_SERVER'] = pulse_socket_path |
1802 | + |
1803 | + lsof_cmd = 'lsof -n %s' % pulse_socket_path |
1804 | + args = shlex.split(lsof_cmd) |
1805 | + lsof = subprocess.Popen(args, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL) |
1806 | + lsof.wait() |
1807 | + |
1808 | + if not os.path.exists(pulse_socket_path) or lsof.returncode == 1: |
1809 | + pactl_cmd = ( |
1810 | + 'pactl load-module module-native-protocol-unix auth-anonymous=1 socket=%s' |
1811 | + % pulse_socket_path) |
1812 | + args = shlex.split(pactl_cmd) |
1813 | + subprocess.Popen(args).wait() |
1814 | + |
1815 | + def _dynamic_bind_mounts(self): |
1816 | + self._config.refresh_database() |
1817 | + mounts = self._sanitize_bind_mounts(utils.get_common_xdg_user_directories() + \ |
1818 | + self._config.get_container_bind_mounts(self.container_id)) |
1819 | + |
1820 | + data_dir = utils.get_libertine_container_home_dir(self.container_id) |
1821 | + for user_dir in utils.generate_binding_directories(mounts, home_path): |
1822 | + if os.path.isabs(user_dir[1]): |
1823 | + path = user_dir[1].strip('/') |
1824 | + fullpath = os.path.join(utils.get_libertine_container_rootfs_path(self.container_id), path) |
1825 | + else: |
1826 | + path = "{}/{}".format(home_path.strip('/'), user_dir[1]) |
1827 | + fullpath = os.path.join(data_dir, user_dir[1]) |
1828 | + |
1829 | + os.makedirs(fullpath, exist_ok=True) |
1830 | + |
1831 | + utils.get_logger().debug("Mounting {}:{} in container {}".format(user_dir[0], path, self.container_id)) |
1832 | + xdg_user_dir_entry = ( |
1833 | + "%s %s none bind,create=dir,optional" |
1834 | + % (user_dir[0], path) |
1835 | + ) |
1836 | + self.container.append_config_item("lxc.mount.entry", xdg_user_dir_entry) |
1837 | + |
1838 | + def _sanitize_bind_mounts(self, mounts): |
1839 | + return [mount.replace(" ", "\\040") for mount in mounts] |
1840 | |
1841 | def timezone_needs_update(self): |
1842 | with open(os.path.join(self.root_path, 'etc', 'timezone'), 'r') as fd: |
1843 | @@ -171,19 +223,54 @@ |
1844 | |
1845 | def start_container(self): |
1846 | if self.lxc_manager_interface: |
1847 | - result = LifecycleResult.from_dict(self.lxc_manager_interface.operation_start(self.container_id)) |
1848 | - else: |
1849 | - result = lxc_start(self.container) |
1850 | - |
1851 | - if not result.success: |
1852 | - _dump_lxc_log(result.logfile) |
1853 | - raise RuntimeError(result.error) |
1854 | + retries = 0 |
1855 | + while not self.lxc_manager_interface.container_operation_start(self.container_id): |
1856 | + retries += 1 |
1857 | + if retries > 5: |
1858 | + return False |
1859 | + time.sleep(.5) |
1860 | + |
1861 | + if self.container.state == 'RUNNING': |
1862 | + return True |
1863 | + |
1864 | + if self.container.state == 'STOPPED': |
1865 | + self._dynamic_bind_mounts() |
1866 | + self._setup_pulse() |
1867 | + |
1868 | + if not lxc_start(self.container): |
1869 | + _dump_lxc_log(get_logfile(self.container)) |
1870 | + return False |
1871 | + |
1872 | + return True |
1873 | |
1874 | def stop_container(self): |
1875 | if self.lxc_manager_interface: |
1876 | - self.lxc_manager_interface.operation_stop(self.container_id) |
1877 | + if self.lxc_manager_interface.container_operation_finished(self.container_id): |
1878 | + if not lxc_stop(self.container, self._freeze_on_stop): |
1879 | + return False |
1880 | + self.lxc_manager_interface.container_stopped(self.container_id) |
1881 | + return True |
1882 | + else: |
1883 | + return False |
1884 | else: |
1885 | - lxc_stop(self.container) |
1886 | + return lxc_stop(self.container, self._freeze_on_stop) |
1887 | + |
1888 | + def restart_container(self): |
1889 | + if self.container.state != 'FROZEN': |
1890 | + utils.get_logger().warning("Container {} is not frozen. Cannot restart.".format(self.container_id)) |
1891 | + return False |
1892 | + |
1893 | + orig_freeze = self._freeze_on_stop |
1894 | + self._freeze_on_stop = False |
1895 | + |
1896 | + # We never want to use the manager when restarting. |
1897 | + self.lxc_manager_interface = None |
1898 | + |
1899 | + if not (self.stop_container() and self.start_container()): |
1900 | + return False |
1901 | + |
1902 | + self._freeze_on_stop = orig_freeze |
1903 | + return self.stop_container() |
1904 | |
1905 | def run_in_container(self, command_string): |
1906 | cmd_args = shlex.split(command_string) |
1907 | @@ -238,7 +325,7 @@ |
1908 | |
1909 | self.container.load_config(config_file) |
1910 | |
1911 | - utils.create_libertine_user_data_dir(self.container_id) |
1912 | + self._create_libertine_user_data_dir() |
1913 | |
1914 | with EnvLxcSettings(): |
1915 | lxc_logfile = get_logfile(self.container) |
1916 | @@ -253,10 +340,7 @@ |
1917 | self.create_libertine_config() |
1918 | |
1919 | utils.get_logger().info("starting container ...") |
1920 | - try: |
1921 | - self.start_container() |
1922 | - except RuntimeError as e: |
1923 | - utils.get_logger().error("Container failed to start: %s" % e) |
1924 | + if not self.start_container(): |
1925 | self.destroy_libertine_container() |
1926 | return False |
1927 | |
1928 | @@ -290,7 +374,7 @@ |
1929 | user_id = os.getuid() |
1930 | home_entry = ( |
1931 | "%s %s none bind,create=dir" |
1932 | - % (utils.get_libertine_container_userdata_dir_path(self.container_id), |
1933 | + % (utils.get_libertine_container_home_dir(self.container_id), |
1934 | home_path.strip('/')) |
1935 | ) |
1936 | |
1937 | @@ -325,19 +409,10 @@ |
1938 | os.environ.clear() |
1939 | os.environ.update(environ) |
1940 | |
1941 | - result = LifecycleResult.from_dict(self.lxc_manager_interface.app_start(self.container_id)) |
1942 | - |
1943 | - if not result.success: |
1944 | - _dump_lxc_log(get_logfile(self.container)) |
1945 | - utils.get_logger().error("%s" % result.error) |
1946 | + if not self.start_container(): |
1947 | + self.lxc_manager_interface.container_stopped(self.container_id) |
1948 | return |
1949 | |
1950 | - self.window_manager = self.container.attach(lxc.attach_run_command, |
1951 | - self.setup_window_manager()) |
1952 | - |
1953 | - # Setup pulse to work inside the container |
1954 | - os.environ['PULSE_SERVER'] = utils.get_libertine_lxc_pulse_socket_path() |
1955 | - |
1956 | app_launch_cmd = "sudo -E -u " + os.environ['USER'] + " env PATH=" + os.environ['PATH'] |
1957 | cmd = shlex.split(app_launch_cmd) |
1958 | app = self.container.attach(lxc.attach_run_command, |
1959 | @@ -347,7 +422,4 @@ |
1960 | def finish_application(self, app): |
1961 | os.waitpid(app.pid, 0) |
1962 | |
1963 | - utils.terminate_window_manager(psutil.Process(self.window_manager)) |
1964 | - |
1965 | - # Tell libertine-lxc-manager that the app has stopped. |
1966 | - self.lxc_manager_interface.app_stop(self.container_id) |
1967 | + self.stop_container() |
1968 | |
1969 | === modified file 'python/libertine/LxdContainer.py' |
1970 | --- python/libertine/LxdContainer.py 2017-01-17 20:35:37 +0000 |
1971 | +++ python/libertine/LxdContainer.py 2017-02-09 16:08:46 +0000 |
1972 | @@ -1,4 +1,4 @@ |
1973 | -# Copyright 2016 Canonical Ltd. |
1974 | +# Copyright 2016-2017 Canonical Ltd. |
1975 | # |
1976 | # This program is free software: you can redistribute it and/or modify it |
1977 | # under the terms of the GNU General Public License version 3, as published |
1978 | @@ -22,20 +22,8 @@ |
1979 | import subprocess |
1980 | import time |
1981 | |
1982 | -from .lifecycle import LifecycleResult |
1983 | from libertine import Libertine, utils, HostInfo |
1984 | - |
1985 | - |
1986 | -LIBERTINE_LXC_MANAGER_NAME = "com.canonical.libertine.LxdManager" |
1987 | -LIBERTINE_LXC_MANAGER_PATH = "/LxdManager" |
1988 | - |
1989 | - |
1990 | -def get_lxd_manager_dbus_name(): |
1991 | - return LIBERTINE_LXC_MANAGER_NAME |
1992 | - |
1993 | - |
1994 | -def get_lxd_manager_dbus_path(): |
1995 | - return LIBERTINE_LXC_MANAGER_PATH |
1996 | +from .service.manager import LIBERTINE_MANAGER_NAME, LIBERTINE_STORE_PATH |
1997 | |
1998 | |
1999 | def _get_devices_map(): |
2000 | @@ -74,6 +62,10 @@ |
2001 | return source |
2002 | |
2003 | def _setup_lxd(): |
2004 | + if utils.is_snap_environment(): |
2005 | + utils.get_logger().warning("Running in snap environment, skipping automatic lxd setup.") |
2006 | + return True |
2007 | + |
2008 | utils.get_logger().info("Running LXD setup.") |
2009 | import pexpect |
2010 | child = pexpect.spawnu('sudo libertine-lxd-setup {}'.format(os.environ['USER']), env={'TERM': 'dumb'}) |
2011 | @@ -113,7 +105,7 @@ |
2012 | [ -n /dev/video0 ] && chgrp video /dev/video0 |
2013 | '''[1:-1] |
2014 | container.files.put('/usr/bin/libertine-lxd-mount-update', script.format(uid=uid, username=username).encode('utf-8')) |
2015 | - container.execute(shlex.split('chmod 755 /usr/bin/libertine-lxd-mount-update')) |
2016 | + subprocess.Popen(shlex.split("lxc exec {} -- chmod 755 /usr/bin/libertine-lxd-mount-update".format(container.name))) |
2017 | |
2018 | |
2019 | def lxd_container(client, container_id): |
2020 | @@ -125,7 +117,9 @@ |
2021 | |
2022 | def _wait_for_network(container): |
2023 | for retries in range(0, 10): |
2024 | - out, err = container.execute(shlex.split('ping -c 1 ubuntu.com')) |
2025 | + ping = subprocess.Popen(shlex.split("lxc exec {} -- ping -c 1 ubuntu.com".format(container.name)), |
2026 | + stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
2027 | + out, err = ping.communicate() |
2028 | if out: |
2029 | utils.get_logger().info("Network connection active") |
2030 | return True |
2031 | @@ -134,28 +128,40 @@ |
2032 | |
2033 | |
2034 | def lxd_start(container): |
2035 | - if container.status != 'Running': |
2036 | + if container.status == 'Stopped': |
2037 | container.start(wait=True) |
2038 | + elif container.status == 'Frozen': |
2039 | + container.unfreeze(wait=True) |
2040 | |
2041 | container.sync(rollback=True) # required for pylxd=2.0.x |
2042 | |
2043 | if container.status != 'Running': |
2044 | - return LifecycleResult("Container {} failed to start".format(container.name)) |
2045 | - |
2046 | - return LifecycleResult() |
2047 | - |
2048 | - |
2049 | -def lxd_stop(container, wait): |
2050 | + utils.get_logger().error("Container {} failed to start".format(container.name)) |
2051 | + return False |
2052 | + |
2053 | + return True |
2054 | + |
2055 | + |
2056 | +def lxd_stop(container, wait=True, freeze_on_stop=False): |
2057 | if container.status == 'Stopped': |
2058 | - return LifecycleResult() |
2059 | - |
2060 | - container.stop(wait=wait) |
2061 | + return True |
2062 | + |
2063 | + if freeze_on_stop: |
2064 | + container.freeze(wait=wait) |
2065 | + else: |
2066 | + container.stop(wait=wait) |
2067 | + |
2068 | container.sync(rollback=True) # required for pylxd=2.0.x |
2069 | |
2070 | - if wait and container.status != 'Stopped': |
2071 | - return LifecycleResult("Container {} failed to stop".format(container.name)) |
2072 | + if wait: |
2073 | + if freeze_on_stop and container.status != 'Frozen': |
2074 | + utils.get_logger().error("Container {} failed to freeze".format(container.name)) |
2075 | + return False |
2076 | + elif not freeze_on_stop and container.status != 'Stopped': |
2077 | + utils.get_logger().error("Container {} failed to stop".format(container.name)) |
2078 | + return False |
2079 | |
2080 | - return LifecycleResult() |
2081 | + return True |
2082 | |
2083 | |
2084 | def _lxd_save(entity, error, wait=True): |
2085 | @@ -210,9 +216,8 @@ |
2086 | f.write(container.files.get(filepath)) |
2087 | |
2088 | |
2089 | -def update_bind_mounts(container, config): |
2090 | - home_path = os.environ['HOME'] |
2091 | - userdata_dir = utils.get_libertine_container_userdata_dir_path(container.name) |
2092 | +def update_bind_mounts(container, config, home_path): |
2093 | + userdata_dir = utils.get_libertine_container_home_dir(container.name) |
2094 | |
2095 | container.devices.clear() |
2096 | container.devices['root'] = {'type': 'disk', 'path': '/'} |
2097 | @@ -235,9 +240,13 @@ |
2098 | run_user = '/run/user/{}'.format(os.getuid()) |
2099 | container.devices[run_user] = {'source': run_user, 'path': '/var/tmp{}'.format(run_user), 'type': 'disk'} |
2100 | |
2101 | - mounts = utils.get_common_xdg_user_directories() + \ |
2102 | - config.get_container_bind_mounts(container.name) |
2103 | - for user_dir in utils.generate_binding_directories(mounts, home_path): |
2104 | + mounts = config.get_container_bind_mounts(container.name) |
2105 | + if utils.is_snap_environment(): |
2106 | + mounts += [os.path.join(home_path, d) for d in ["Documents", "Downloads", "Music", "Videos", "Pictures"]] |
2107 | + else: |
2108 | + mounts += utils.get_common_xdg_user_directories() |
2109 | + |
2110 | + for user_dir in utils.generate_binding_directories(mounts, home_path.rstrip('/')): |
2111 | if not os.path.exists(user_dir[0]): |
2112 | utils.get_logger().warning('Bind-mount path \'{}\' does not exist.'.format(user_dir[0])) |
2113 | continue |
2114 | @@ -251,7 +260,7 @@ |
2115 | |
2116 | utils.get_logger().debug("Mounting {}:{} in container {}".format(user_dir[0], path, container.name)) |
2117 | |
2118 | - container.devices[user_dir[1]] = { |
2119 | + container.devices[user_dir[1] or user_dir[0]] = { |
2120 | 'source': _readlink(user_dir[0]), |
2121 | 'path': path, |
2122 | 'optional': 'true', |
2123 | @@ -275,29 +284,33 @@ |
2124 | client.profiles.create('libertine', config={'raw.idmap': 'both 1000 1000'}, devices=_get_devices_map()) |
2125 | |
2126 | |
2127 | +def env_home_path(): |
2128 | + if utils.is_snap_environment(): |
2129 | + return '/home/{}'.format(os.environ['USER']) |
2130 | + return os.environ['HOME'] |
2131 | + |
2132 | + |
2133 | class LibertineLXD(Libertine.BaseContainer): |
2134 | def __init__(self, name, config): |
2135 | - super().__init__(name) |
2136 | - self._id = name |
2137 | - self._config = config |
2138 | + super().__init__(name, 'lxd', config) |
2139 | self._host_info = HostInfo.HostInfo() |
2140 | self._container = None |
2141 | - self._matchbox_pid = None |
2142 | + self._manager = None |
2143 | + self._freeze_on_stop = config.get_freeze_on_stop(self.container_id) |
2144 | |
2145 | if not _setup_lxd(): |
2146 | raise Exception("Failed to setup lxd.") |
2147 | |
2148 | self._client = pylxd.Client() |
2149 | - self._window_manager = None |
2150 | |
2151 | - utils.set_session_dbus_env_var() |
2152 | try: |
2153 | - bus = dbus.SessionBus() |
2154 | - self._manager = bus.get_object(get_lxd_manager_dbus_name(), get_lxd_manager_dbus_path()) |
2155 | + if utils.set_session_dbus_env_var(): |
2156 | + bus = dbus.SessionBus() |
2157 | + self._manager = bus.get_object(LIBERTINE_MANAGER_NAME, LIBERTINE_STORE_PATH) |
2158 | + except PermissionError as e: |
2159 | + utils.get_logger().warning("Failed to set dbus session env var") |
2160 | except dbus.exceptions.DBusException: |
2161 | utils.get_logger().warning("D-Bus Service not found.") |
2162 | - self._manager = None |
2163 | - |
2164 | |
2165 | def create_libertine_container(self, password=None, multiarch=False): |
2166 | if self._try_get_container(): |
2167 | @@ -306,17 +319,17 @@ |
2168 | |
2169 | update_libertine_profile(self._client) |
2170 | |
2171 | - utils.get_logger().info("Creating container '%s' with distro '%s'" % (self._id, self.installed_release)) |
2172 | + utils.get_logger().info("Creating container '%s' with distro '%s'" % (self.container_id, self.installed_release)) |
2173 | create = subprocess.Popen(shlex.split('lxc launch ubuntu-daily:{distro} {id} --profile ' |
2174 | 'default --profile libertine'.format( |
2175 | - distro=self.installed_release, id=self._id))) |
2176 | + distro=self.installed_release, id=self.container_id))) |
2177 | if create.wait() is not 0: |
2178 | - utils.get_logger().error("Creating container '{}' failed with code '{}'".format(self._id, create.returncode)) |
2179 | + utils.get_logger().error("Creating container '{}' failed with code '{}'".format(self.container_id, create.returncode)) |
2180 | return False |
2181 | |
2182 | self._try_get_container() |
2183 | _sync_application_dirs_to_host(self._container) |
2184 | - update_bind_mounts(self._container, self._config) |
2185 | + update_bind_mounts(self._container, self._config, env_home_path()) |
2186 | |
2187 | self.update_locale() |
2188 | |
2189 | @@ -328,18 +341,18 @@ |
2190 | self.run_in_container("mkdir -p /home/{}".format(username)) |
2191 | self.run_in_container("chown {0}:{0} /home/{0}".format(username)) |
2192 | |
2193 | - utils.create_libertine_user_data_dir(self._id) |
2194 | + self._create_libertine_user_data_dir() |
2195 | |
2196 | _setup_bind_mount_service(self._container, uid, username) |
2197 | |
2198 | if multiarch and self.architecture == 'amd64': |
2199 | - utils.get_logger().info("Adding i386 multiarch support to container '{}'".format(self._id)) |
2200 | + utils.get_logger().info("Adding i386 multiarch support to container '{}'".format(self.container_id)) |
2201 | self.run_in_container("dpkg --add-architecture i386") |
2202 | |
2203 | self.update_packages() |
2204 | |
2205 | for package in self.default_packages: |
2206 | - utils.get_logger().info("Installing package '%s' in container '%s'" % (package, self._id)) |
2207 | + utils.get_logger().info("Installing package '%s' in container '%s'" % (package, self.container_id)) |
2208 | if not self.install_package(package, no_dialog=True, update_cache=False): |
2209 | utils.get_logger().error("Failure installing '%s' during container creation" % package) |
2210 | self.destroy_libertine_container() |
2211 | @@ -357,13 +370,13 @@ |
2212 | self.run_in_container("dpkg-reconfigure -f noninteractive tzdata") |
2213 | |
2214 | _sync_application_dirs_to_host(self._container) |
2215 | - update_bind_mounts(self._container, self._config) |
2216 | + update_bind_mounts(self._container, self._config, env_home_path()) |
2217 | |
2218 | return super().update_packages(update_locale) |
2219 | |
2220 | def destroy_libertine_container(self): |
2221 | if not self._try_get_container(): |
2222 | - utils.get_logger().error("No such container '%s'" % self._id) |
2223 | + utils.get_logger().error("No such container '%s'" % self.container_id) |
2224 | return False |
2225 | |
2226 | if not self.stop_container(wait=True): |
2227 | @@ -384,7 +397,7 @@ |
2228 | for k, v in environ.items(): |
2229 | env_as_args += '--env {}="{}" '.format(k, v) |
2230 | |
2231 | - return shlex.split('lxc exec {}{}-- {}'.format(self._id, |
2232 | + return shlex.split('lxc exec {}{}-- {}'.format(self.container_id, |
2233 | env_as_args, |
2234 | command)) |
2235 | |
2236 | @@ -392,108 +405,95 @@ |
2237 | proc = subprocess.Popen(self._lxc_args(command)) |
2238 | return proc.wait() |
2239 | |
2240 | - def start_container(self): |
2241 | + def start_container(self, home=env_home_path()): |
2242 | if not self._try_get_container(): |
2243 | return False |
2244 | |
2245 | if self._manager: |
2246 | - result = LifecycleResult.from_dict(self._manager.operation_start(self._id)) |
2247 | - else: |
2248 | - result = lxd_start(self._container) |
2249 | - |
2250 | - if not result.success: |
2251 | - utils.get_logger().error(result.error) |
2252 | + retries = 0 |
2253 | + while not self._manager.container_operation_start(self.container_id): |
2254 | + retries += 1 |
2255 | + if retries > 5: |
2256 | + return False |
2257 | + time.sleep(.5) |
2258 | + |
2259 | + if self._container.status == 'Running': |
2260 | + return True |
2261 | + |
2262 | + requires_remount = self._container.status == 'Stopped' |
2263 | + |
2264 | + if requires_remount: |
2265 | + update_libertine_profile(self._client) |
2266 | + update_bind_mounts(self._container, self._config, home) |
2267 | + |
2268 | + if not lxd_start(self._container): |
2269 | + if self._manager: |
2270 | + self._manager.container_stopped(self.container_id) |
2271 | return False |
2272 | |
2273 | if not _wait_for_network(self._container): |
2274 | - utils.get_logger().warning("Network unavailable in container '{}'".format(self._id)) |
2275 | - |
2276 | - return result.success |
2277 | + utils.get_logger().warning("Network unavailable in container '{}'".format(self.container_id)) |
2278 | + |
2279 | + if requires_remount: |
2280 | + self.run_in_container("/usr/bin/libertine-lxd-mount-update") |
2281 | + |
2282 | + return True |
2283 | |
2284 | def stop_container(self, wait=False): |
2285 | if not self._try_get_container(): |
2286 | return False |
2287 | |
2288 | if self._manager: |
2289 | - result = LifecycleResult.from_dict(self._manager.operation_stop(self._id, {'wait': wait})) |
2290 | + if self._manager.container_operation_finished(self.container_id): |
2291 | + if not lxd_stop(self._container, freeze_on_stop=self._freeze_on_stop): |
2292 | + return False |
2293 | + self._manager.container_stopped(self.container_id) |
2294 | + return True |
2295 | + else: |
2296 | + return False |
2297 | else: |
2298 | - result = lxd_stop(self._container, wait) |
2299 | - |
2300 | - if not result.success: |
2301 | - utils.get_logger().error(result.error) |
2302 | - |
2303 | - return result.success |
2304 | - |
2305 | - def _get_matchbox_pids(self): |
2306 | - p = subprocess.Popen(self._lxc_args('pgrep matchbox'), stdout=subprocess.PIPE) |
2307 | - out, err = p.communicate() |
2308 | - return out.decode('utf-8').split('\n') |
2309 | - |
2310 | - # FIXME: Remove once window management logic has been moved to the host |
2311 | - def _start_window_manager(self, args): |
2312 | - args.extend(self.setup_window_manager()) |
2313 | - |
2314 | - if 'matchbox-window-manager' in args: |
2315 | - pids = self._get_matchbox_pids() |
2316 | - |
2317 | - self._window_manager = psutil.Popen(args) |
2318 | - |
2319 | - time.sleep(.25) # Give matchbox a moment to start in the container |
2320 | - if self._window_manager.poll() is not None: |
2321 | - utils.get_logger().warning("Window manager terminated prematurely with exit code '{}'".format( |
2322 | - self._window_manager.returncode)) |
2323 | - self._window_manager = None |
2324 | - elif 'matchbox-window-manager' in args: |
2325 | - after_pids = self._get_matchbox_pids() |
2326 | - if len(after_pids) > len(pids): |
2327 | - self._matchbox_pid = (set(after_pids) - set(pids)).pop() |
2328 | - |
2329 | + return lxd_stop(self._container, freeze_on_stop=self._freeze_on_stop) |
2330 | + |
2331 | + def restart_container(self, wait=True): |
2332 | + if not self._try_get_container(): |
2333 | + return False |
2334 | + |
2335 | + if self._container.status != 'Frozen': |
2336 | + utils.get_logger().warning("Container {} is not frozen. Cannot restart.".format(self._container.name)) |
2337 | + return False |
2338 | + |
2339 | + orig_freeze = self._freeze_on_stop |
2340 | + self._freeze_on_stop = False |
2341 | + |
2342 | + # We never want to use the manager when restarting. |
2343 | + self._manager = None |
2344 | + |
2345 | + if not (self.stop_container(wait=True) and self.start_container()): |
2346 | + return False |
2347 | + |
2348 | + self._freeze_on_stop = orig_freeze |
2349 | + return self.stop_container(wait=True) |
2350 | + |
2351 | def start_application(self, app_exec_line, environ): |
2352 | if not self._try_get_container(): |
2353 | - utils.get_logger().error("Could not get container '{}'".format(self._id)) |
2354 | + utils.get_logger().error("Could not get container '{}'".format(self.container_id)) |
2355 | return None |
2356 | |
2357 | - requires_remount = self._container.status != 'Running' |
2358 | - |
2359 | - if self._manager: |
2360 | - result = LifecycleResult.from_dict(self._manager.app_start(self._id)) |
2361 | - else: |
2362 | - update_libertine_profile(self._client) |
2363 | - update_bind_mounts(self._container, self._config) |
2364 | - result = lxd_start(self._container) |
2365 | - |
2366 | - if not result.success: |
2367 | - utils.get_logger().error(result.error) |
2368 | + if utils.is_snap_environment(): |
2369 | + environ['HOME'] = '/home/{}'.format(environ['USER']) |
2370 | + |
2371 | + if not self.start_container(home=environ['HOME']): |
2372 | return False |
2373 | |
2374 | - if requires_remount: |
2375 | - self._container.execute(shlex.split('/usr/bin/libertine-lxd-mount-update')) |
2376 | - |
2377 | args = self._lxc_args("sudo -E -u {} env PATH={}".format(environ['USER'], environ['PATH']), environ) |
2378 | |
2379 | - self._start_window_manager(args.copy()) |
2380 | - |
2381 | args.extend(app_exec_line) |
2382 | return psutil.Popen(args) |
2383 | |
2384 | def finish_application(self, app): |
2385 | - if self._window_manager is not None: |
2386 | - utils.terminate_window_manager(self._window_manager) |
2387 | - self._window_manager = None |
2388 | - |
2389 | - # This is a workaround for an issue on xenial where the process |
2390 | - # running the window manager is not killed with the application |
2391 | - if self._matchbox_pid is not None: |
2392 | - utils.get_logger().debug("Manually killing matchbox-window-manager") |
2393 | - self.run_in_container("kill -9 {}".format(self._matchbox_pid)) |
2394 | - self._matchbox_pid = None |
2395 | - |
2396 | app.wait() |
2397 | |
2398 | - if self._manager: |
2399 | - self._manager.app_stop(self.container_id) |
2400 | - else: |
2401 | - lxd_stop(self._container, False) |
2402 | + self.stop_container() |
2403 | |
2404 | def copy_file_to_container(self, source, dest): |
2405 | with open(source, 'rb') as f: |
2406 | @@ -504,6 +504,6 @@ |
2407 | |
2408 | def _try_get_container(self): |
2409 | if self._container is None: |
2410 | - self._container = lxd_container(self._client, self._id) |
2411 | + self._container = lxd_container(self._client, self.container_id) |
2412 | |
2413 | return self._container is not None |
2414 | |
2415 | === modified file 'python/libertine/launcher/config.py' |
2416 | --- python/libertine/launcher/config.py 2016-12-05 18:33:47 +0000 |
2417 | +++ python/libertine/launcher/config.py 2017-02-09 16:08:46 +0000 |
2418 | @@ -1,4 +1,4 @@ |
2419 | -# Copyright 2016 Canonical Ltd. |
2420 | +# Copyright 2016-2017 Canonical Ltd. |
2421 | # |
2422 | # This program is free software: you can redistribute it and/or modify it |
2423 | # under the terms of the GNU General Public License version 3, as published |
2424 | @@ -215,9 +215,6 @@ |
2425 | tasks = [] |
2426 | tasks.append(TaskConfig(TaskType.LAUNCH_SERVICE, ["pasted"])) |
2427 | |
2428 | - if self.container_id is None: |
2429 | - tasks.append(TaskConfig(TaskType.LAUNCH_SERVICE, ['matchbox-window-manager', '-use_titlebar', 'no'])) |
2430 | - |
2431 | return tasks |
2432 | |
2433 | def _create_socket_bridges(self): |
2434 | |
2435 | === removed directory 'python/libertine/lifecycle' |
2436 | === removed file 'python/libertine/lifecycle/ContainerLifecycleService.py' |
2437 | --- python/libertine/lifecycle/ContainerLifecycleService.py 2017-01-12 16:09:37 +0000 |
2438 | +++ python/libertine/lifecycle/ContainerLifecycleService.py 1970-01-01 00:00:00 +0000 |
2439 | @@ -1,108 +0,0 @@ |
2440 | -#!/usr/bin/python3 |
2441 | -# -*- coding: utf-8 -*- |
2442 | - |
2443 | -# Copyright (C) 2016 Canonical Ltd. |
2444 | - |
2445 | -# This program is free software: you can redistribute it and/or modify |
2446 | -# it under the terms of the GNU General Public License as published by |
2447 | -# the Free Software Foundation; version 3 of the License. |
2448 | -# |
2449 | -# This program is distributed in the hope that it will be useful, |
2450 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2451 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2452 | -# GNU General Public License for more details. |
2453 | -# |
2454 | -# You should have received a copy of the GNU General Public License |
2455 | -# along with this program. If not, see <http://www.gnu.org/licenses/>. |
2456 | - |
2457 | - |
2458 | -import dbus.exceptions |
2459 | -import dbus.service |
2460 | - |
2461 | -from .LifecycleResult import LifecycleResult |
2462 | -from collections import Counter |
2463 | -from dbus.mainloop.glib import DBusGMainLoop |
2464 | -from libertine import utils |
2465 | - |
2466 | - |
2467 | -LIBERTINE_CONTAINER_LIFECYCLE_INTERFACE = 'com.canonical.libertine.ContainerLifecycle' |
2468 | - |
2469 | - |
2470 | -class ContainerLifecycleService(dbus.service.Object): |
2471 | - def __init__(self, service_name, service_path): |
2472 | - self._apps = Counter() |
2473 | - self._operations = Counter() |
2474 | - |
2475 | - DBusGMainLoop(set_as_default=True) |
2476 | - try: |
2477 | - bus_name = dbus.service.BusName(service_name, |
2478 | - bus=dbus.SessionBus(), |
2479 | - do_not_queue=True) |
2480 | - except dbus.exceptions.NameExistsException: |
2481 | - utils.get_logger().error("service is already running") |
2482 | - raise |
2483 | - super().__init__(bus_name, service_path) |
2484 | - |
2485 | - def start(self, container): |
2486 | - raise NotImplementedError("Subclasses must implement start(container)") |
2487 | - |
2488 | - def stop(self, container, options={}): |
2489 | - raise NotImplementedError("Subclasses must implement stop(container)") |
2490 | - |
2491 | - @dbus.service.method(LIBERTINE_CONTAINER_LIFECYCLE_INTERFACE, |
2492 | - in_signature='s', |
2493 | - out_signature='a{ss}') |
2494 | - def app_start(self, container): |
2495 | - utils.get_logger().debug("app_start({})".format(container)) |
2496 | - if self._operations[container] != 0: |
2497 | - return LifecycleResult("Libertine container operation already running: cannot launch application.").to_dict() |
2498 | - |
2499 | - result = self.start(container, True) |
2500 | - |
2501 | - if result.success: |
2502 | - self._apps[container] += 1 |
2503 | - |
2504 | - return result.to_dict() |
2505 | - |
2506 | - @dbus.service.method(LIBERTINE_CONTAINER_LIFECYCLE_INTERFACE, |
2507 | - in_signature='s', |
2508 | - out_signature='a{ss}') |
2509 | - def app_stop(self, container): |
2510 | - utils.get_logger().debug("app_stop({})".format(container)) |
2511 | - self._apps[container] -= 1 |
2512 | - result = LifecycleResult() |
2513 | - |
2514 | - if self._apps[container] == 0: |
2515 | - result = self.stop(container) |
2516 | - del self._apps[container] |
2517 | - |
2518 | - return result.to_dict() |
2519 | - |
2520 | - @dbus.service.method(LIBERTINE_CONTAINER_LIFECYCLE_INTERFACE, |
2521 | - in_signature='s', |
2522 | - out_signature='a{ss}') |
2523 | - def operation_start(self, container): |
2524 | - utils.get_logger().debug("operation_start({})".format(container)) |
2525 | - if self._apps[container] != 0: |
2526 | - return LifecycleResult("Application already running in container: cannot run operation.").to_dict() |
2527 | - |
2528 | - result = self.start(container, False) |
2529 | - |
2530 | - if result.success: |
2531 | - self._operations[container] += 1 |
2532 | - |
2533 | - return result.to_dict() |
2534 | - |
2535 | - @dbus.service.method(LIBERTINE_CONTAINER_LIFECYCLE_INTERFACE, |
2536 | - in_signature='sa{ss}', |
2537 | - out_signature='a{ss}') |
2538 | - def operation_stop(self, container, options={}): |
2539 | - utils.get_logger().debug("operation_stop({}, {})".format(container, options)) |
2540 | - self._operations[container] -= 1 |
2541 | - result = LifecycleResult() |
2542 | - |
2543 | - if self._operations[container] == 0: |
2544 | - result = self.stop(container, options) |
2545 | - del self._operations[container] |
2546 | - |
2547 | - return result.to_dict() |
2548 | |
2549 | === removed file 'python/libertine/lifecycle/ContainerLifecycleServiceRunner.py' |
2550 | --- python/libertine/lifecycle/ContainerLifecycleServiceRunner.py 2016-12-21 20:39:40 +0000 |
2551 | +++ python/libertine/lifecycle/ContainerLifecycleServiceRunner.py 1970-01-01 00:00:00 +0000 |
2552 | @@ -1,46 +0,0 @@ |
2553 | -#!/usr/bin/python3 |
2554 | -# -*- coding: utf-8 -*- |
2555 | - |
2556 | -# Copyright (C) 2016 Canonical Ltd. |
2557 | - |
2558 | -# This program is free software: you can redistribute it and/or modify |
2559 | -# it under the terms of the GNU General Public License as published by |
2560 | -# the Free Software Foundation; version 3 of the License. |
2561 | -# |
2562 | -# This program is distributed in the hope that it will be useful, |
2563 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2564 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2565 | -# GNU General Public License for more details. |
2566 | -# |
2567 | -# You should have received a copy of the GNU General Public License |
2568 | -# along with this program. If not, see <http://www.gnu.org/licenses/>. |
2569 | - |
2570 | -import signal |
2571 | - |
2572 | -from gi.repository import GLib |
2573 | -from libertine import utils |
2574 | - |
2575 | - |
2576 | -class ContainerLifecycleServiceRunner(object): |
2577 | - def __init__(self, service): |
2578 | - self._service = service |
2579 | - |
2580 | - def _sigterm(self, sig): |
2581 | - utils.get_logger().warning("Received SIGTERM") |
2582 | - self._shutdown() |
2583 | - |
2584 | - def _shutdown(self): |
2585 | - utils.get_logger().info("Shutting down") |
2586 | - GLib.MainLoop().quit() |
2587 | - |
2588 | - def run(self): |
2589 | - GLib.unix_signal_add(GLib.PRIORITY_HIGH, |
2590 | - signal.SIGTERM, |
2591 | - self._sigterm, |
2592 | - None) |
2593 | - |
2594 | - try: |
2595 | - utils.get_logger().info("Starting main loop") |
2596 | - GLib.MainLoop().run() |
2597 | - except KeyboardInterrupt: |
2598 | - self._shutdown() |
2599 | |
2600 | === removed file 'python/libertine/lifecycle/LifecycleResult.py' |
2601 | --- python/libertine/lifecycle/LifecycleResult.py 2016-12-21 18:26:51 +0000 |
2602 | +++ python/libertine/lifecycle/LifecycleResult.py 1970-01-01 00:00:00 +0000 |
2603 | @@ -1,37 +0,0 @@ |
2604 | -#!/usr/bin/python3 |
2605 | -# -*- coding: utf-8 -*- |
2606 | - |
2607 | -# Copyright (C) 2016 Canonical Ltd. |
2608 | - |
2609 | -# This program is free software: you can redistribute it and/or modify |
2610 | -# it under the terms of the GNU General Public License as published by |
2611 | -# the Free Software Foundation; version 3 of the License. |
2612 | -# |
2613 | -# This program is distributed in the hope that it will be useful, |
2614 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2615 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2616 | -# GNU General Public License for more details. |
2617 | -# |
2618 | -# You should have received a copy of the GNU General Public License |
2619 | -# along with this program. If not, see <http://www.gnu.org/licenses/>. |
2620 | - |
2621 | -class LifecycleResult(object): |
2622 | - def __init__(self, error=''): |
2623 | - self._error = error |
2624 | - |
2625 | - @property |
2626 | - def error(self): |
2627 | - return self._error or '' |
2628 | - |
2629 | - @property |
2630 | - def success(self): |
2631 | - return self.error == '' |
2632 | - |
2633 | - @classmethod |
2634 | - def from_dict(kls, d): |
2635 | - return LifecycleResult(d.get('error', None)) |
2636 | - |
2637 | - def to_dict(self): |
2638 | - return { |
2639 | - 'error': self.error |
2640 | - } |
2641 | |
2642 | === removed file 'python/libertine/lifecycle/__init__.py' |
2643 | --- python/libertine/lifecycle/__init__.py 2016-12-21 18:26:51 +0000 |
2644 | +++ python/libertine/lifecycle/__init__.py 1970-01-01 00:00:00 +0000 |
2645 | @@ -1,23 +0,0 @@ |
2646 | -# Copyright 2016 Canonical Ltd. |
2647 | -# |
2648 | -# This program is free software: you can redistribute it and/or modify it |
2649 | -# under the terms of the GNU General Public License version 3, as published |
2650 | -# by the Free Software Foundation. |
2651 | -# |
2652 | -# This program is distributed in the hope that it will be useful, but |
2653 | -# WITHOUT ANY WARRANTY; without even the implied warranties of |
2654 | -# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
2655 | -# PURPOSE. See the GNU General Public License for more details. |
2656 | -# |
2657 | -# You should have received a copy of the GNU General Public License along |
2658 | -# with this program. If not, see <http://www.gnu.org/licenses/>. |
2659 | - |
2660 | -from .ContainerLifecycleServiceRunner import ContainerLifecycleServiceRunner |
2661 | -from .ContainerLifecycleService import ContainerLifecycleService |
2662 | -from .LifecycleResult import LifecycleResult |
2663 | - |
2664 | -__all__ = [ |
2665 | - 'ContainerLifecycleServiceRunner', |
2666 | - 'ContainerLifecycleService', |
2667 | - 'LifecycleResult' |
2668 | - ] |
2669 | |
2670 | === modified file 'python/libertine/service/apt.py' |
2671 | --- python/libertine/service/apt.py 2016-11-08 15:37:58 +0000 |
2672 | +++ python/libertine/service/apt.py 2017-02-09 16:08:46 +0000 |
2673 | @@ -1,4 +1,4 @@ |
2674 | -# Copyright 2016 Canonical Ltd. |
2675 | +# Copyright 2016-2017 Canonical Ltd. |
2676 | # |
2677 | # This program is free software: you can redistribute it and/or modify |
2678 | # it under the terms of the GNU General Public License as published by |
2679 | @@ -15,10 +15,6 @@ |
2680 | import apt |
2681 | import re |
2682 | |
2683 | -import gi |
2684 | -gi.require_version('Libertine', '1') |
2685 | -from gi.repository import Libertine |
2686 | - |
2687 | from libertine import utils |
2688 | from os import path |
2689 | from threading import Lock |
2690 | @@ -67,7 +63,7 @@ |
2691 | if self._cache is None: |
2692 | try: |
2693 | utils.get_logger().debug("Trying aptcache for container %s" % self._container) |
2694 | - container_path = Libertine.container_path(self._container) |
2695 | + container_path = utils.get_libertine_container_rootfs_path(self._container) |
2696 | if not container_path or not path.exists(container_path): |
2697 | raise PermissionError |
2698 | |
2699 | |
2700 | === modified file 'python/libertine/service/container.py' |
2701 | --- python/libertine/service/container.py 2016-11-08 15:37:58 +0000 |
2702 | +++ python/libertine/service/container.py 2017-02-09 16:08:46 +0000 |
2703 | @@ -1,4 +1,4 @@ |
2704 | -# Copyright 2016 Canonical Ltd. |
2705 | +# Copyright 2016-2017 Canonical Ltd. |
2706 | # |
2707 | # This program is free software: you can redistribute it and/or modify |
2708 | # it under the terms of the GNU General Public License as published by |
2709 | @@ -14,18 +14,27 @@ |
2710 | |
2711 | from libertine.service.tasks import * |
2712 | from libertine import utils |
2713 | -from libertine.service import apt |
2714 | +from threading import Lock |
2715 | + |
2716 | + |
2717 | +if not utils.is_snap_environment(): |
2718 | + from libertine.service import apt |
2719 | |
2720 | |
2721 | class Container(object): |
2722 | - def __init__(self, container_id, config, lock, connection, callback): |
2723 | + def __init__(self, container_id, config, connection, callback): |
2724 | self._id = container_id |
2725 | self._connection = connection |
2726 | self._callback = callback |
2727 | self._config = config |
2728 | - self._lock = lock |
2729 | + self._lock = Lock() |
2730 | self._tasks = [] |
2731 | - self._cache = apt.AptCache(self.id) |
2732 | + |
2733 | + if utils.is_snap_environment(): |
2734 | + utils.get_logger().warning("Using AptCache not currently supported in snap environment") |
2735 | + self._cache = None |
2736 | + else: |
2737 | + self._cache = apt.AptCache(self.id) |
2738 | |
2739 | def _cleanup_task(self, task): |
2740 | utils.get_logger().debug("cleaning up tasks for container '%s'" % self.id) |
2741 | @@ -47,6 +56,9 @@ |
2742 | def search(self, query): |
2743 | utils.get_logger().debug("search container '%s' for package '%s'" % (self.id, query)) |
2744 | |
2745 | + if utils.is_snap_environment(): |
2746 | + raise Exception("This operation is not currently supported within the snap") |
2747 | + |
2748 | task = SearchTask(self.id, self._cache, query, self._connection, self._cleanup_task) |
2749 | self._tasks.append(task) |
2750 | task.start() |
2751 | @@ -56,6 +68,9 @@ |
2752 | def app_info(self, package_name): |
2753 | utils.get_logger().debug("get info for package '%s' in container '%s'" % (package_name, self.id)) |
2754 | |
2755 | + if utils.is_snap_environment(): |
2756 | + raise Exception("This operation is not currently supported within the snap") |
2757 | + |
2758 | related_task_ids = [t.id for t in self._tasks if t.package == package_name and t.running] |
2759 | task = AppInfoTask(self.id, self._cache, package_name, related_task_ids, self._config, self._connection, self._cleanup_task) |
2760 | |
2761 | @@ -129,10 +144,10 @@ |
2762 | task.start() |
2763 | return task.id |
2764 | |
2765 | - def list_apps(self): |
2766 | - utils.get_logger().debug("List all apps in container '%s'" % self.id) |
2767 | + def list_app_ids(self): |
2768 | + utils.get_logger().debug("List all app ids in container '%s'" % self.id) |
2769 | |
2770 | - task = ListAppsTask(self.id, self._config, self._connection, self._cleanup_task) |
2771 | + task = ListAppIdsTask(self.id, self._config, self._connection, self._cleanup_task) |
2772 | |
2773 | self._tasks.append(task) |
2774 | task.start() |
2775 | |
2776 | === modified file 'python/libertine/service/manager.py' |
2777 | --- python/libertine/service/manager.py 2016-10-31 18:31:00 +0000 |
2778 | +++ python/libertine/service/manager.py 2017-02-09 16:08:46 +0000 |
2779 | @@ -1,4 +1,4 @@ |
2780 | -# Copyright 2016 Canonical Ltd. |
2781 | +# Copyright 2016-2017 Canonical Ltd. |
2782 | # |
2783 | # This program is free software: you can redistribute it and/or modify |
2784 | # it under the terms of the GNU General Public License as published by |
2785 | @@ -15,6 +15,7 @@ |
2786 | import dbus |
2787 | import dbus.service |
2788 | import libertine.service.task_dispatcher |
2789 | +from collections import Counter |
2790 | from dbus.mainloop.glib import DBusGMainLoop |
2791 | from libertine.service import container |
2792 | from libertine import utils |
2793 | @@ -28,6 +29,7 @@ |
2794 | class Manager(dbus.service.Object): |
2795 | def __init__(self): |
2796 | utils.get_logger().debug("creating service") |
2797 | + self._operations = Counter() |
2798 | DBusGMainLoop(set_as_default=True) |
2799 | try: |
2800 | bus_name = dbus.service.BusName(LIBERTINE_MANAGER_NAME, |
2801 | @@ -67,9 +69,9 @@ |
2802 | @dbus.service.method(LIBERTINE_MANAGER_INTERFACE, |
2803 | in_signature='s', |
2804 | out_signature='o') |
2805 | - def list_apps(self, container_id): |
2806 | - utils.get_logger().debug("list_apps('{}')".format(container_id)) |
2807 | - return self._dispatcher.list_apps(container_id) |
2808 | + def list_app_ids(self, container_id): |
2809 | + utils.get_logger().debug("list_app_ids('{}')".format(container_id)) |
2810 | + return self._dispatcher.list_app_ids(container_id) |
2811 | |
2812 | @dbus.service.method(LIBERTINE_MANAGER_INTERFACE, |
2813 | out_signature='o') |
2814 | @@ -113,3 +115,37 @@ |
2815 | def remove(self, container_id, package_name): |
2816 | utils.get_logger().debug("remove('%s', '%s')" % (container_id, package_name)) |
2817 | return self._dispatcher.remove(container_id, package_name) |
2818 | + |
2819 | + # Container Lifecycle |
2820 | + |
2821 | + @dbus.service.method(LIBERTINE_MANAGER_INTERFACE, |
2822 | + in_signature='s', |
2823 | + out_signature='b') |
2824 | + def container_operation_start(self, container): |
2825 | + utils.get_logger().debug("container_operation_start({})".format(container)) |
2826 | + |
2827 | + if self._operations[container] == -1: |
2828 | + return False |
2829 | + |
2830 | + self._operations[container] += 1 |
2831 | + return True |
2832 | + |
2833 | + @dbus.service.method(LIBERTINE_MANAGER_INTERFACE, |
2834 | + in_signature='s', |
2835 | + out_signature='b') |
2836 | + def container_operation_finished(self, container): |
2837 | + utils.get_logger().debug("container_operation_finished({})".format(container)) |
2838 | + stop = False |
2839 | + self._operations[container] -= 1 |
2840 | + |
2841 | + if self._operations[container] == 0: |
2842 | + self._operations[container] -= 1 |
2843 | + stop = True |
2844 | + |
2845 | + return stop |
2846 | + |
2847 | + @dbus.service.method(LIBERTINE_MANAGER_INTERFACE, |
2848 | + in_signature='s') |
2849 | + def container_stopped(self, container): |
2850 | + utils.get_logger().debug("container_stopped({})".format(container)) |
2851 | + del self._operations[container] |
2852 | |
2853 | === modified file 'python/libertine/service/progress.py' |
2854 | --- python/libertine/service/progress.py 2016-11-29 17:38:38 +0000 |
2855 | +++ python/libertine/service/progress.py 2017-02-09 16:08:46 +0000 |
2856 | @@ -1,4 +1,4 @@ |
2857 | -# Copyright 2016 Canonical Ltd. |
2858 | +# Copyright 2016-2017 Canonical Ltd. |
2859 | # |
2860 | # This program is free software: you can redistribute it and/or modify |
2861 | # it under the terms of the GNU General Public License as published by |
2862 | @@ -18,7 +18,7 @@ |
2863 | from time import time |
2864 | |
2865 | DOWNLOAD_INTERFACE = "com.canonical.applications.Download" |
2866 | -PROGRESS_INTERFACE = "com.canonical.libertine.Progress" |
2867 | +PROGRESS_INTERFACE = "com.canonical.libertine.Service.Progress" |
2868 | |
2869 | class Progress(dbus.service.Object): |
2870 | def __init__(self, connection): |
2871 | @@ -28,7 +28,7 @@ |
2872 | self._error = '' |
2873 | dbus.service.Object.__init__(self, conn=connection, object_path=("/Progress/%s" % hex(int(time()*10000000))[2:])) |
2874 | |
2875 | - self.emit_processing() |
2876 | + # self.emit_processing() # Disabled until something requires the Download interface |
2877 | |
2878 | @property |
2879 | def id(self): |
2880 | |
2881 | === modified file 'python/libertine/service/task_dispatcher.py' |
2882 | --- python/libertine/service/task_dispatcher.py 2016-11-01 18:50:32 +0000 |
2883 | +++ python/libertine/service/task_dispatcher.py 2017-02-09 16:08:46 +0000 |
2884 | @@ -1,4 +1,4 @@ |
2885 | -# Copyright 2016 Canonical Ltd. |
2886 | +# Copyright 2016-2017 Canonical Ltd. |
2887 | # |
2888 | # This program is free software: you can redistribute it and/or modify |
2889 | # it under the terms of the GNU General Public License as published by |
2890 | @@ -24,7 +24,6 @@ |
2891 | def __init__(self, connection): |
2892 | self._connection = connection |
2893 | self._config = libertine.ContainersConfig.ContainersConfig() |
2894 | - self._lock = Lock() |
2895 | self._containerless_tasks = [] |
2896 | self._tasks = [] |
2897 | self._containers = [] |
2898 | @@ -35,7 +34,7 @@ |
2899 | self._tasks.remove(task) |
2900 | |
2901 | def _cleanup_container(self, container): |
2902 | - utils.get_logger().debug("cleaning up container '%s'" % container) |
2903 | + utils.get_logger().debug("cleaning up container '%s'" % container.id) |
2904 | if container in self._containers: |
2905 | self._containers.remove(container) |
2906 | |
2907 | @@ -45,7 +44,7 @@ |
2908 | if container is not None: |
2909 | utils.get_logger().debug("using existing container '%s'" % container_id) |
2910 | return container |
2911 | - container = Container(container_id, self._config, self._lock, self._connection, self._cleanup_container) |
2912 | + container = Container(container_id, self._config, self._connection, self._cleanup_container) |
2913 | self._containers.append(container) |
2914 | |
2915 | return container |
2916 | @@ -85,9 +84,9 @@ |
2917 | utils.get_logger().debug("dispatching update container '%s'" % container_id) |
2918 | return self._find_or_create_container(container_id).update() |
2919 | |
2920 | - def list_apps(self, container_id): |
2921 | - utils.get_logger().debug("dispatching list all apps in container '%s'" % container_id) |
2922 | - return self._find_or_create_container(container_id).list_apps() |
2923 | + def list_app_ids(self, container_id): |
2924 | + utils.get_logger().debug("dispatching list apps ids in container '%s'" % container_id) |
2925 | + return self._find_or_create_container(container_id).list_app_ids() |
2926 | |
2927 | # Containerless Tasks |
2928 | |
2929 | @@ -107,7 +106,7 @@ |
2930 | def list(self): |
2931 | utils.get_logger().debug("dispatching list all containers") |
2932 | |
2933 | - task = ListTask(self._connection, self._cleanup_task) |
2934 | + task = ListTask(self._config, self._connection, self._cleanup_task) |
2935 | self._tasks.append(task) |
2936 | task.start() |
2937 | |
2938 | |
2939 | === modified file 'python/libertine/service/tasks/__init__.py' |
2940 | --- python/libertine/service/tasks/__init__.py 2016-11-01 17:38:38 +0000 |
2941 | +++ python/libertine/service/tasks/__init__.py 2017-02-09 16:08:46 +0000 |
2942 | @@ -1,4 +1,4 @@ |
2943 | -# Copyright 2016 Canonical Ltd. |
2944 | +# Copyright 2016-2017 Canonical Ltd. |
2945 | # |
2946 | # This program is free software: you can redistribute it and/or modify it |
2947 | # under the terms of the GNU General Public License version 3, as published |
2948 | @@ -22,7 +22,7 @@ |
2949 | from .search_task import SearchTask |
2950 | from .update_task import UpdateTask |
2951 | from .list_task import ListTask |
2952 | -from .list_apps_task import ListAppsTask |
2953 | +from .list_app_ids_task import ListAppIdsTask |
2954 | |
2955 | __all__ = [ |
2956 | 'AppInfoTask', |
2957 | @@ -35,5 +35,5 @@ |
2958 | 'SearchTask', |
2959 | 'UpdateTask', |
2960 | 'ListTask', |
2961 | - 'ListAppsTask' |
2962 | + 'ListAppIdsTask' |
2963 | ] |
2964 | |
2965 | === modified file 'python/libertine/service/tasks/base_task.py' |
2966 | --- python/libertine/service/tasks/base_task.py 2016-11-07 20:37:54 +0000 |
2967 | +++ python/libertine/service/tasks/base_task.py 2017-02-09 16:08:46 +0000 |
2968 | @@ -1,4 +1,4 @@ |
2969 | -# Copyright 2016 Canonical Ltd. |
2970 | +# Copyright 2016-2017 Canonical Ltd. |
2971 | # |
2972 | # This program is free software: you can redistribute it and/or modify |
2973 | # it under the terms of the GNU General Public License as published by |
2974 | @@ -15,7 +15,6 @@ |
2975 | import libertine.service.progress |
2976 | import threading |
2977 | from abc import ABCMeta, abstractmethod |
2978 | -from libertine import utils |
2979 | |
2980 | |
2981 | class BaseTask(metaclass=ABCMeta): |
2982 | @@ -60,7 +59,7 @@ |
2983 | if self._instant_callback: |
2984 | self._callback(self) |
2985 | else: |
2986 | - threading.Timer(30, lambda: self._callback(self)).start() |
2987 | + threading.Timer(10, lambda: self._callback(self)).start() |
2988 | |
2989 | def start(self): |
2990 | self._progress = libertine.service.progress.Progress(self._connection) |
2991 | @@ -82,7 +81,6 @@ |
2992 | |
2993 | if self.running: |
2994 | self._progress.finished(self.container) |
2995 | - utils.refresh_libertine_scope() |
2996 | |
2997 | self._delayed_callback() |
2998 | |
2999 | |
3000 | === modified file 'python/libertine/service/tasks/container_info_task.py' |
3001 | --- python/libertine/service/tasks/container_info_task.py 2016-11-01 17:38:38 +0000 |
3002 | +++ python/libertine/service/tasks/container_info_task.py 2017-02-09 16:08:46 +0000 |
3003 | @@ -1,4 +1,4 @@ |
3004 | -# Copyright 2016 Canonical Ltd. |
3005 | +# Copyright 2016-2017 Canonical Ltd. |
3006 | # |
3007 | # This program is free software: you can redistribute it and/or modify |
3008 | # it under the terms of the GNU General Public License as published by |
3009 | @@ -13,6 +13,8 @@ |
3010 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
3011 | |
3012 | |
3013 | +import json |
3014 | + |
3015 | from .base_task import BaseTask |
3016 | from libertine import utils |
3017 | |
3018 | @@ -23,7 +25,21 @@ |
3019 | self._tasks = tasks |
3020 | |
3021 | def _run(self): |
3022 | - container = {'id': str(self._container)} |
3023 | - container['status'] = self._config._get_value_by_key(self._container, 'installStatus') or '' |
3024 | - container['task_ids'] = self._tasks |
3025 | - self._progress.data(str(container)) |
3026 | + utils.get_logger().debug("Gathering info for container '{}'".format(self._container)) |
3027 | + container = {'id': str(self._container), 'task_ids': self._tasks} |
3028 | + |
3029 | + container['status'] = self._config.get_container_install_status(self._container) or '' |
3030 | + container['name'] = self._config.get_container_name(self._container) or '' |
3031 | + |
3032 | + container_type = self._config.get_container_type(self._container) |
3033 | + container['root'] = utils.get_libertine_container_rootfs_path(self._container) |
3034 | + container['home'] = utils.get_libertine_container_home_dir(self._container) |
3035 | + |
3036 | + self._progress.data(json.dumps(container)) |
3037 | + |
3038 | + def _before(self): |
3039 | + if not self._config.container_exists(self._container): |
3040 | + self._progress.error("Container '%s' does not exist, ignoring info request" % self._container) |
3041 | + return False |
3042 | + |
3043 | + return True |
3044 | |
3045 | === added file 'python/libertine/service/tasks/list_app_ids_task.py' |
3046 | --- python/libertine/service/tasks/list_app_ids_task.py 1970-01-01 00:00:00 +0000 |
3047 | +++ python/libertine/service/tasks/list_app_ids_task.py 2017-02-09 16:08:46 +0000 |
3048 | @@ -0,0 +1,35 @@ |
3049 | +# Copyright 2017 Canonical Ltd. |
3050 | +# |
3051 | +# This program is free software: you can redistribute it and/or modify |
3052 | +# it under the terms of the GNU General Public License as published by |
3053 | +# the Free Software Foundation; version 3 of the License. |
3054 | +# |
3055 | +# This program is distributed in the hope that it will be useful, |
3056 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3057 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3058 | +# GNU General Public License for more details. |
3059 | +# |
3060 | +# You should have received a copy of the GNU General Public License |
3061 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
3062 | + |
3063 | + |
3064 | +import json |
3065 | +from .base_task import BaseTask |
3066 | +from libertine import LibertineContainer, utils |
3067 | +import time |
3068 | + |
3069 | + |
3070 | +class ListAppIdsTask(BaseTask): |
3071 | + def __init__(self, container_id, config, connection, callback): |
3072 | + super().__init__(lock=None, container_id=container_id, config=config, connection=connection, callback=callback) |
3073 | + |
3074 | + def _run(self): |
3075 | + utils.get_logger().debug("Listing app ids from container '%s'" % self._container) |
3076 | + self._progress.data(json.dumps(LibertineContainer(self._container, self._config).list_app_ids())) |
3077 | + |
3078 | + def _before(self): |
3079 | + if not self._config.container_exists(self._container): |
3080 | + self._progress.error("Container '%s' does not exist, skipping list" % self._container) |
3081 | + return False |
3082 | + |
3083 | + return True |
3084 | |
3085 | === removed file 'python/libertine/service/tasks/list_apps_task.py' |
3086 | --- python/libertine/service/tasks/list_apps_task.py 2016-11-01 17:38:38 +0000 |
3087 | +++ python/libertine/service/tasks/list_apps_task.py 1970-01-01 00:00:00 +0000 |
3088 | @@ -1,30 +0,0 @@ |
3089 | -# Copyright 2016 Canonical Ltd. |
3090 | -# |
3091 | -# This program is free software: you can redistribute it and/or modify |
3092 | -# it under the terms of the GNU General Public License as published by |
3093 | -# the Free Software Foundation; version 3 of the License. |
3094 | -# |
3095 | -# This program is distributed in the hope that it will be useful, |
3096 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3097 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3098 | -# GNU General Public License for more details. |
3099 | -# |
3100 | -# You should have received a copy of the GNU General Public License |
3101 | -# along with this program. If not, see <http://www.gnu.org/licenses/>. |
3102 | - |
3103 | - |
3104 | -from .base_task import BaseTask |
3105 | -from libertine import LibertineContainer, utils |
3106 | - |
3107 | - |
3108 | -class ListAppsTask(BaseTask): |
3109 | - def __init__(self, container_id, config, connection, callback): |
3110 | - super().__init__(lock=None, container_id=container_id, config=config, connection=connection, callback=callback) |
3111 | - |
3112 | - def _run(self): |
3113 | - utils.get_logger().debug("Listing apps in container '%s'" % self._container) |
3114 | - if not self._config.container_exists(self._container): |
3115 | - self._progress.error("Container '%s' does not exist, skipping list" % self._container) |
3116 | - else: |
3117 | - container = LibertineContainer(self._container, self._config) |
3118 | - self._progress.data(str(container.list_app_launchers(use_json=True))) |
3119 | |
3120 | === modified file 'python/libertine/service/tasks/list_task.py' |
3121 | --- python/libertine/service/tasks/list_task.py 2016-11-01 18:50:32 +0000 |
3122 | +++ python/libertine/service/tasks/list_task.py 2017-02-09 16:08:46 +0000 |
3123 | @@ -1,4 +1,4 @@ |
3124 | -# Copyright 2016 Canonical Ltd. |
3125 | +# Copyright 2016-2017 Canonical Ltd. |
3126 | # |
3127 | # This program is free software: you can redistribute it and/or modify |
3128 | # it under the terms of the GNU General Public License as published by |
3129 | @@ -13,13 +13,15 @@ |
3130 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
3131 | |
3132 | |
3133 | +import json |
3134 | + |
3135 | from .base_task import BaseTask |
3136 | from libertine import utils |
3137 | |
3138 | |
3139 | class ListTask(BaseTask): |
3140 | - def __init__(self, connection, callback): |
3141 | - super().__init__(lock=None, container_id=None, config=None, connection=connection, callback=callback) |
3142 | + def __init__(self, config, connection, callback): |
3143 | + super().__init__(lock=None, container_id=None, config=config, connection=connection, callback=callback) |
3144 | |
3145 | def _run(self): |
3146 | - self._progress.data(str(utils.Libertine.list_containers())) |
3147 | + self._progress.data(json.dumps(self._config.get_containers())) |
3148 | |
3149 | === modified file 'python/libertine/utils.py' |
3150 | --- python/libertine/utils.py 2017-01-17 15:41:51 +0000 |
3151 | +++ python/libertine/utils.py 2017-02-09 16:08:46 +0000 |
3152 | @@ -1,6 +1,6 @@ |
3153 | # -*- coding: utf-8 -*- |
3154 | |
3155 | -# Copyright (C) 2015-2016 Canonical Ltd. |
3156 | +# Copyright (C) 2015-2017 Canonical Ltd. |
3157 | # Author: Christopher Townsend <christopher.townsend@canonical.com> |
3158 | |
3159 | # This program is free software: you can redistribute it and/or modify |
3160 | @@ -17,14 +17,11 @@ |
3161 | |
3162 | import logging |
3163 | import os |
3164 | +import psutil |
3165 | import shlex |
3166 | import subprocess |
3167 | import xdg.BaseDirectory as basedir |
3168 | |
3169 | -from gi import require_version |
3170 | -require_version('Libertine', '1') |
3171 | -from gi.repository import Libertine |
3172 | - |
3173 | |
3174 | def get_logger(): |
3175 | logger = logging.getLogger('__libertine_logger__') |
3176 | @@ -63,12 +60,7 @@ |
3177 | |
3178 | |
3179 | def get_libertine_container_rootfs_path(container_id): |
3180 | - path = Libertine.container_path(container_id) |
3181 | - |
3182 | - if path is None: |
3183 | - path = os.path.join(get_libertine_containers_dir_path(), container_id, 'rootfs') |
3184 | - |
3185 | - return path |
3186 | + return os.path.join(get_libertine_containers_dir_path(), container_id, 'rootfs') |
3187 | |
3188 | |
3189 | def get_libertine_containers_dir_path(): |
3190 | @@ -82,13 +74,15 @@ |
3191 | |
3192 | |
3193 | def get_libertine_database_dir_path(): |
3194 | - xdg_data_home = os.getenv('XDG_DATA_HOME', |
3195 | - os.path.join(os.getenv('HOME'), '.local', 'share')) |
3196 | + if is_snap_environment(): |
3197 | + xdg_data_home = os.path.join(os.getenv('SNAP_USER_COMMON'), '.local', 'share') |
3198 | + else: |
3199 | + xdg_data_home = os.getenv('XDG_DATA_HOME', |
3200 | + os.path.join(os.getenv('HOME'), '.local', 'share')) |
3201 | |
3202 | libertine_database_dir = os.path.join(xdg_data_home, 'libertine') |
3203 | |
3204 | - if not os.path.exists(libertine_database_dir): |
3205 | - os.makedirs(libertine_database_dir) |
3206 | + os.makedirs(libertine_database_dir, exist_ok=True) |
3207 | |
3208 | return libertine_database_dir |
3209 | |
3210 | @@ -97,11 +91,8 @@ |
3211 | return os.path.join(get_libertine_database_dir_path(), 'ContainersConfig.json') |
3212 | |
3213 | |
3214 | -def get_libertine_container_userdata_dir_path(container_id): |
3215 | - path = Libertine.container_home_path(container_id) |
3216 | - |
3217 | - if path is None: |
3218 | - path = os.path.join(basedir.xdg_data_home, 'libertine-container', 'user-data', container_id) |
3219 | +def get_libertine_container_home_dir(container_id): |
3220 | + path = os.path.join(basedir.xdg_data_home, 'libertine-container', 'user-data', container_id) |
3221 | |
3222 | if is_snap_environment(): |
3223 | path = path.replace(os.environ['HOME'], os.getenv('SNAP_USER_COMMON')) |
3224 | @@ -150,31 +141,10 @@ |
3225 | return dirs |
3226 | |
3227 | |
3228 | -def create_libertine_user_data_dir(container_id): |
3229 | - user_data = get_libertine_container_userdata_dir_path(container_id) |
3230 | - |
3231 | - if not os.path.exists(user_data): |
3232 | - os.makedirs(user_data) |
3233 | - |
3234 | - config_path = os.path.join(user_data, ".config", "dconf") |
3235 | - |
3236 | - if not os.path.exists(config_path): |
3237 | - os.makedirs(config_path) |
3238 | - |
3239 | - |
3240 | def get_libertine_lxc_pulse_socket_path(): |
3241 | return os.path.join(get_libertine_runtime_dir(), 'pulse_socket') |
3242 | |
3243 | |
3244 | -def terminate_window_manager(window_manager): |
3245 | - for child in window_manager.children(): |
3246 | - child.terminate() |
3247 | - child.wait() |
3248 | - |
3249 | - window_manager.terminate() |
3250 | - window_manager.wait() |
3251 | - |
3252 | - |
3253 | def refresh_libertine_scope(): |
3254 | scopes_object_path = "/com/canonical/unity/scopes" |
3255 | invalidate_signal = "com.canonical.unity.scopes.InvalidateResults" |
3256 | @@ -187,27 +157,27 @@ |
3257 | |
3258 | |
3259 | def set_session_dbus_env_var(): |
3260 | - if not 'DBUS_SESSION_BUS_ADDRESS' in os.environ: |
3261 | - dbus_session_path = os.path.join('/', 'run', 'user', str(os.getuid()), 'dbus-session') |
3262 | - |
3263 | - if os.path.exists(dbus_session_path): |
3264 | - with open(dbus_session_path, 'r') as fd: |
3265 | - dbus_session_str = fd.read() |
3266 | - |
3267 | - os.environ['DBUS_SESSION_BUS_ADDRESS'] = dbus_session_str.partition('DBUS_SESSION_BUS_ADDRESS=')[2].rstrip('\n') |
3268 | - |
3269 | - return True |
3270 | - else: |
3271 | - return False |
3272 | - |
3273 | - return True |
3274 | + dbus_session_set = 'SESSION' in os.environ |
3275 | + |
3276 | + if not dbus_session_set: |
3277 | + for p in psutil.process_iter(): |
3278 | + try: |
3279 | + if p.name() == 'unity8' or p.name() == 'compiz': |
3280 | + p_environ = subprocess.check_output(["cat", "/proc/{}/environ".format(p.pid)]) |
3281 | + for line in p_environ.decode().split('\0'): |
3282 | + if line.startswith('DBUS_SESSION_BUS_ADDRESS'): |
3283 | + os.environ['DBUS_SESSION_BUS_ADDRESS'] = line.partition('DBUS_SESSION_BUS_ADDRESS=')[2].rstrip('\n') |
3284 | + dbus_session_set = True |
3285 | + break |
3286 | + break |
3287 | + except psutil.NoSuchProcess as e: |
3288 | + get_logger().warning(str(e)) |
3289 | + |
3290 | + return dbus_session_set |
3291 | |
3292 | |
3293 | def is_snap_environment(): |
3294 | - if 'SNAP' in os.environ: |
3295 | - return True |
3296 | - else: |
3297 | - return False |
3298 | + return 'SNAP' in os.environ |
3299 | |
3300 | |
3301 | def get_deb_package_name(package): |
3302 | |
3303 | === added directory 'setup' |
3304 | === added directory 'setup/gui' |
3305 | === added file 'setup/gui/libertine-manager-app.desktop' |
3306 | --- setup/gui/libertine-manager-app.desktop 1970-01-01 00:00:00 +0000 |
3307 | +++ setup/gui/libertine-manager-app.desktop 2017-02-09 16:08:46 +0000 |
3308 | @@ -0,0 +1,12 @@ |
3309 | +[Desktop Entry] |
3310 | +Version=1.0 |
3311 | +Name=Libertine Manager |
3312 | +Comment=Legacy Application Sandbox |
3313 | +Exec=libertine.manager-app %U |
3314 | +Terminal=false |
3315 | +Type=Application |
3316 | +Icon=${SNAP}/usr/share/libertine/libertine_64.png |
3317 | +Categories= |
3318 | +GenericName=Application Sandbox |
3319 | +X-Ubuntu-Touch=true |
3320 | + |
3321 | |
3322 | === modified file 'snapcraft.yaml' |
3323 | --- snapcraft.yaml 2017-01-18 14:50:33 +0000 |
3324 | +++ snapcraft.yaml 2017-02-09 16:08:46 +0000 |
3325 | @@ -1,18 +1,52 @@ |
3326 | name: libertine |
3327 | -version: 1.5.1+17.04.20170118-0ubuntu1 |
3328 | +version: 1.6 |
3329 | summary: Libertine suite |
3330 | description: | |
3331 | Suite for maintaining deb-based applications in a non-deb environment |
3332 | -confinement: devmode |
3333 | +confinement: strict # devmode # TODO: update to 'strict' |
3334 | grade: devel |
3335 | |
3336 | +slots: |
3337 | + libertined: |
3338 | + interface: dbus |
3339 | + name: com.canonical.libertine.Service |
3340 | + bus: session |
3341 | + |
3342 | apps: |
3343 | - libertine-container-manager: |
3344 | + launch: |
3345 | + command: usr/bin/snap-runner.wrapper libertine-launch |
3346 | + aliases: |
3347 | + - libertine-launch |
3348 | + plugs: |
3349 | + - lxd |
3350 | + - network |
3351 | + - network-bind |
3352 | + - home |
3353 | + - mir |
3354 | + - x11 |
3355 | + - unity7 |
3356 | + - opengl |
3357 | + - pulseaudio |
3358 | + - alsa |
3359 | + libertined: |
3360 | + command: usr/bin/snap-runner.wrapper libertined |
3361 | + plugs: |
3362 | + - lxd |
3363 | + - network-bind |
3364 | + # daemon: simple # Waiting on LP:1613420 |
3365 | + container-manager: |
3366 | command: usr/bin/snap-runner.wrapper libertine-container-manager |
3367 | - libertine-manager-app: |
3368 | + aliases: |
3369 | + - libertine-container-manager |
3370 | + plugs: |
3371 | + - lxd |
3372 | + - network |
3373 | + manager-app: |
3374 | command: usr/bin/snap-runner.wrapper libertine-manager-app |
3375 | - libertine-launch: |
3376 | - command: usr/bin/snap-runner.wrapper libertine-launch |
3377 | + # desktop command currently being labeled unknown by app store, restore with snapcraft 2.26 release |
3378 | + # desktop: usr/share/applications/libertine-manager-app.desktop |
3379 | + aliases: |
3380 | + - libertine-manager-app |
3381 | |
3382 | # For debugging purposes |
3383 | bash: |
3384 | |
3385 | === modified file 'tests/integration/CMakeLists.txt' |
3386 | --- tests/integration/CMakeLists.txt 2016-11-07 17:08:16 +0000 |
3387 | +++ tests/integration/CMakeLists.txt 2017-02-09 16:08:46 +0000 |
3388 | @@ -1,4 +1,4 @@ |
3389 | -add_test(test_libertine_service /usr/bin/python3 -m testtools.run test_libertine_service) |
3390 | +add_test(test_libertine_service dbus-run-session -- /usr/bin/python3 -m testtools.run test_libertine_service) |
3391 | set_tests_properties(test_libertine_service |
3392 | PROPERTIES ENVIRONMENT |
3393 | "GI_TYPELIB_PATH=${CMAKE_BINARY_DIR}/liblibertine;LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/liblibertine:${LD_LIBRARY_PATH};PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}:${CMAKE_SOURCE_DIR}/python;LIBERTINE_DATA_DIR=${CMAKE_CURRENT_SOURCE_DIR};PATH=${CMAKE_SOURCE_DIR}/tools:$ENV{PATH};") |
3394 | |
3395 | === modified file 'tests/integration/test_libertine_service.py' |
3396 | --- tests/integration/test_libertine_service.py 2016-11-29 17:38:38 +0000 |
3397 | +++ tests/integration/test_libertine_service.py 2017-02-09 16:08:46 +0000 |
3398 | @@ -1,4 +1,4 @@ |
3399 | -# Copyright 2016 Canonical Ltd. |
3400 | +# Copyright 2016-2017 Canonical Ltd. |
3401 | # |
3402 | # This program is free software: you can redistribute it and/or modify it |
3403 | # under the terms of the GNU General Public License version 3, as published |
3404 | @@ -12,20 +12,26 @@ |
3405 | # You should have received a copy of the GNU General Public License along |
3406 | # with this program. If not, see <http://www.gnu.org/licenses/>. |
3407 | |
3408 | + |
3409 | +import ast |
3410 | import dbus |
3411 | import dbus.mainloop.glib |
3412 | +import os |
3413 | +import pexpect |
3414 | +import sys |
3415 | +import tempfile |
3416 | +import threading |
3417 | +import time |
3418 | import unittest.mock |
3419 | -from unittest import TestCase |
3420 | + |
3421 | +from gi.repository import GLib |
3422 | +from gi.repository import GObject |
3423 | +from libertine import utils |
3424 | from libertine.service import tasks, apt |
3425 | from libertine.ContainersConfig import ContainersConfig |
3426 | from subprocess import Popen, PIPE |
3427 | -import time |
3428 | -from gi.repository import GLib |
3429 | -from gi.repository import GObject |
3430 | -import os |
3431 | -import tempfile |
3432 | -import threading |
3433 | -import ast |
3434 | +from unittest import TestCase |
3435 | + |
3436 | |
3437 | class TestLibertineService(TestCase): |
3438 | _process = None |
3439 | @@ -39,7 +45,17 @@ |
3440 | |
3441 | environ = os.environ.copy() |
3442 | environ['XDG_DATA_HOME'] = cls._tempdir.name |
3443 | - cls._process = Popen(['libertined', '--debug'], stdout=PIPE, stderr=PIPE, env=environ) |
3444 | + |
3445 | + cls._process = pexpect.spawnu('libertined --debug', env=environ) |
3446 | + cls._process.logfile = sys.stdout |
3447 | + |
3448 | + # give libertined enough time to start the whole process |
3449 | + verbosity = environ.get('LIBERTINE_DEBUG', '1') |
3450 | + if verbosity == '1': |
3451 | + cls._process.expect(['.+\n', pexpect.TIMEOUT], timeout=1) |
3452 | + elif environ['LIBERTINE_DEBUG'] == '2': |
3453 | + cls._process.expect(['.+\n.+\n.+\n', pexpect.TIMEOUT], timeout=1) |
3454 | + |
3455 | dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) |
3456 | cls._loop = GObject.MainLoop() |
3457 | |
3458 | @@ -49,9 +65,7 @@ |
3459 | @classmethod |
3460 | def tearDownClass(cls): |
3461 | cls._loop.quit() |
3462 | - cls._process.kill() |
3463 | - out, err = cls._process.communicate() |
3464 | - print(err) |
3465 | + cls._process.close() |
3466 | cls._tempdir.cleanup() |
3467 | |
3468 | def setUp(self): |
3469 | @@ -91,7 +105,7 @@ |
3470 | signals.append(self._bus.add_signal_receiver(path=obj_path, handler_function=self._finished_handler, |
3471 | dbus_interface='com.canonical.applications.Download', signal_name='finished')) |
3472 | signals.append(self._bus.add_signal_receiver(path=obj_path, handler_function=self._data_handler, |
3473 | - dbus_interface='com.canonical.libertine.Progress', signal_name='data')) |
3474 | + dbus_interface='com.canonical.libertine.Service.Progress', signal_name='data')) |
3475 | signals.append(self._bus.add_signal_receiver(path=obj_path, handler_function=self._error_handler, |
3476 | dbus_interface='com.canonical.applications.Download', signal_name='error')) |
3477 | |
3478 | @@ -110,20 +124,25 @@ |
3479 | |
3480 | def test_container_management(self): |
3481 | try: |
3482 | - self.assertEqual('[]', self._send(lambda: self._libertined.list())) |
3483 | + self.assertEqual([], ast.literal_eval(self._send(lambda: self._libertined.list()))) |
3484 | self._send(lambda: self._libertined.create('rey', 'Rey', 'xenial', 'mock')) |
3485 | - self.assertEqual('[\'rey\']', self._send(lambda: self._libertined.list())) |
3486 | + self.assertEqual(['rey'], ast.literal_eval(self._send(lambda: self._libertined.list()))) |
3487 | |
3488 | self._send(lambda: self._libertined.create('kylo', 'Kylo Ren', 'xenial', 'mock')) |
3489 | - self.assertEqual('[\'rey\', \'kylo\']', self._send(lambda: self._libertined.list())) |
3490 | + self.assertEqual(['rey', 'kylo'], ast.literal_eval(self._send(lambda: self._libertined.list()))) |
3491 | |
3492 | self._send(lambda: self._libertined.update('kylo')) |
3493 | |
3494 | - self.assertEqual({'id': 'rey', 'status': 'ready', 'task_ids': []}, |
3495 | - ast.literal_eval(self._send(lambda: self._libertined.container_info('rey')))) |
3496 | + self.assertEqual({'id': 'rey', |
3497 | + 'status': 'ready', |
3498 | + 'name': 'Rey', |
3499 | + 'task_ids': [], |
3500 | + 'root': utils.get_libertine_container_rootfs_path('rey'), |
3501 | + 'home': '{}/libertine-container/user-data/rey'.format(TestLibertineService._tempdir.name) |
3502 | + }, ast.literal_eval(self._send(lambda: self._libertined.container_info('rey')))) |
3503 | |
3504 | self._send(lambda: self._libertined.destroy('kylo')) |
3505 | - self.assertEqual('[\'rey\']', self._send(lambda: self._libertined.list())) |
3506 | + self.assertEqual(['rey'], ast.literal_eval(self._send(lambda: self._libertined.list()))) |
3507 | except AssertionError as e: |
3508 | raise |
3509 | except Exception as e: |
3510 | @@ -137,8 +156,7 @@ |
3511 | config = ContainersConfig() |
3512 | |
3513 | self._send(lambda: self._libertined.create('jarjar', 'JarJar Binks', 'xenial', 'mock')) |
3514 | - self.assertEqual({'name': 'JarJar Binks', 'app_launchers': []}, |
3515 | - ast.literal_eval(self._send(lambda: self._libertined.list_apps('jarjar')))) |
3516 | + self.assertEqual([], ast.literal_eval(self._send(lambda: self._libertined.list_app_ids('jarjar')))) |
3517 | |
3518 | self._send(lambda: self._libertined.install('jarjar', 'the-force')) |
3519 | config.refresh_database() |
3520 | |
3521 | === added directory 'tests/unit/service/containerroot/libertine-container' |
3522 | === added directory 'tests/unit/service/containerroot/libertine-container/palpatine' |
3523 | === added directory 'tests/unit/service/containerroot/libertine-container/palpatine/rootfs' |
3524 | === modified file 'tests/unit/service/tasks/CMakeLists.txt' |
3525 | --- tests/unit/service/tasks/CMakeLists.txt 2016-11-01 19:49:32 +0000 |
3526 | +++ tests/unit/service/tasks/CMakeLists.txt 2017-02-09 16:08:46 +0000 |
3527 | @@ -4,7 +4,7 @@ |
3528 | create_service_unit_test(test_destroy_task) |
3529 | create_service_unit_test(test_install_task) |
3530 | create_service_unit_test(test_list_task) |
3531 | -create_service_unit_test(test_list_apps_task) |
3532 | +create_service_unit_test(test_list_app_ids_task) |
3533 | create_service_unit_test(test_remove_task) |
3534 | create_service_unit_test(test_search_task) |
3535 | create_service_unit_test(test_update_task) |
3536 | |
3537 | === modified file 'tests/unit/service/tasks/test_container_info_task.py' |
3538 | --- tests/unit/service/tasks/test_container_info_task.py 2016-11-07 18:51:17 +0000 |
3539 | +++ tests/unit/service/tasks/test_container_info_task.py 2017-02-09 16:08:46 +0000 |
3540 | @@ -13,8 +13,10 @@ |
3541 | # with this program. If not, see <http://www.gnu.org/licenses/>. |
3542 | |
3543 | |
3544 | +import ast |
3545 | import unittest.mock |
3546 | from unittest import TestCase |
3547 | +from libertine import utils |
3548 | from libertine.service import tasks |
3549 | from libertine.ContainersConfig import ContainersConfig |
3550 | |
3551 | @@ -33,12 +35,22 @@ |
3552 | progress = MockProgress.return_value |
3553 | progress.done = False |
3554 | |
3555 | - self.config._get_value_by_key.return_value = 'ready' |
3556 | + self.config.get_container_install_status.return_value = 'ready' |
3557 | + self.config.get_container_name.return_value = 'Palpatine' |
3558 | task = tasks.ContainerInfoTask('palpatine', [1, 2, 3], self.config, self.connection, callback) |
3559 | task._instant_callback = True |
3560 | task.start().join() |
3561 | |
3562 | - progress.data.assert_called_once_with(str({'id': 'palpatine', 'status': 'ready', 'task_ids': [1, 2, 3]})) |
3563 | + progress.data.assert_called_once_with(unittest.mock.ANY) |
3564 | + args, kwargs = progress.data.call_args |
3565 | + self.assertEqual({'id': 'palpatine', |
3566 | + 'status': 'ready', |
3567 | + 'task_ids': [1, 2, 3], |
3568 | + 'name': 'Palpatine', |
3569 | + 'root': utils.get_libertine_container_rootfs_path('palpatine'), |
3570 | + 'home': utils.get_libertine_container_home_dir('palpatine')}, |
3571 | + ast.literal_eval(args[0])) |
3572 | + |
3573 | progress.finished.assert_called_once_with('palpatine') |
3574 | |
3575 | self.assertEqual(task, self.called_with) |
3576 | |
3577 | === added file 'tests/unit/service/tasks/test_list_app_ids_task.py' |
3578 | --- tests/unit/service/tasks/test_list_app_ids_task.py 1970-01-01 00:00:00 +0000 |
3579 | +++ tests/unit/service/tasks/test_list_app_ids_task.py 2017-02-09 16:08:46 +0000 |
3580 | @@ -0,0 +1,59 @@ |
3581 | +# Copyright 2017 Canonical Ltd. |
3582 | +# |
3583 | +# This program is free software: you can redistribute it and/or modify it |
3584 | +# under the terms of the GNU General Public License version 3, as published |
3585 | +# by the Free Software Foundation. |
3586 | +# |
3587 | +# This program is distributed in the hope that it will be useful, but |
3588 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
3589 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
3590 | +# PURPOSE. See the GNU General Public License for more details. |
3591 | +# |
3592 | +# You should have received a copy of the GNU General Public License along |
3593 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
3594 | + |
3595 | +import json |
3596 | +import unittest.mock |
3597 | +from unittest import TestCase |
3598 | +from libertine.service import tasks |
3599 | +from libertine.ContainersConfig import ContainersConfig |
3600 | + |
3601 | + |
3602 | +class TestListAppIdsTask(TestCase): |
3603 | + def setUp(self): |
3604 | + self.config = unittest.mock.create_autospec(ContainersConfig) |
3605 | + self.connection = unittest.mock.Mock() |
3606 | + self.lock = unittest.mock.MagicMock() |
3607 | + self.called_with = None |
3608 | + |
3609 | + def callback(self, task): |
3610 | + self.called_with = task |
3611 | + |
3612 | + def test_sends_error_on_non_existent_container(self): |
3613 | + self.config.container_exists.return_value = False |
3614 | + with unittest.mock.patch('libertine.service.tasks.base_task.libertine.service.progress.Progress') as MockProgress: |
3615 | + progress = MockProgress.return_value |
3616 | + task = tasks.ListAppIdsTask('palpatine', self.config, self.connection, self.callback) |
3617 | + task._instant_callback = True |
3618 | + |
3619 | + with unittest.mock.patch('libertine.service.tasks.list_app_ids_task.LibertineContainer') as MockContainer: |
3620 | + task.start().join() |
3621 | + |
3622 | + progress.error.assert_called_once_with('Container \'palpatine\' does not exist, skipping list') |
3623 | + self.assertEqual(task, self.called_with) |
3624 | + |
3625 | + def test_successfully_lists_apps(self): |
3626 | + self.config.container_exists.return_value = True |
3627 | + with unittest.mock.patch('libertine.service.tasks.base_task.libertine.service.progress.Progress') as MockProgress: |
3628 | + progress = MockProgress.return_value |
3629 | + progress.done = False |
3630 | + task = tasks.ListAppIdsTask('palpatine', self.config, self.connection, self.callback) |
3631 | + task._instant_callback = True |
3632 | + |
3633 | + with unittest.mock.patch('libertine.service.tasks.list_app_ids_task.LibertineContainer') as MockContainer: |
3634 | + MockContainer.return_value.list_app_ids.return_value = '["palpatine_gedit_0.0","palpatine_xterm_0.0"]' |
3635 | + task.start().join() |
3636 | + |
3637 | + progress.finished.assert_called_once_with('palpatine') |
3638 | + progress.data.assert_called_once_with(json.dumps('["palpatine_gedit_0.0","palpatine_xterm_0.0"]')) |
3639 | + self.assertEqual(task, self.called_with) |
3640 | |
3641 | === removed file 'tests/unit/service/tasks/test_list_apps_task.py' |
3642 | --- tests/unit/service/tasks/test_list_apps_task.py 2016-11-07 18:51:17 +0000 |
3643 | +++ tests/unit/service/tasks/test_list_apps_task.py 1970-01-01 00:00:00 +0000 |
3644 | @@ -1,59 +0,0 @@ |
3645 | -# Copyright 2016 Canonical Ltd. |
3646 | -# |
3647 | -# This program is free software: you can redistribute it and/or modify it |
3648 | -# under the terms of the GNU General Public License version 3, as published |
3649 | -# by the Free Software Foundation. |
3650 | -# |
3651 | -# This program is distributed in the hope that it will be useful, but |
3652 | -# WITHOUT ANY WARRANTY; without even the implied warranties of |
3653 | -# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
3654 | -# PURPOSE. See the GNU General Public License for more details. |
3655 | -# |
3656 | -# You should have received a copy of the GNU General Public License along |
3657 | -# with this program. If not, see <http://www.gnu.org/licenses/>. |
3658 | - |
3659 | - |
3660 | -import unittest.mock |
3661 | -from unittest import TestCase |
3662 | -from libertine.service import tasks |
3663 | -from libertine.ContainersConfig import ContainersConfig |
3664 | - |
3665 | - |
3666 | -class TestListAppsTask(TestCase): |
3667 | - def setUp(self): |
3668 | - self.config = unittest.mock.create_autospec(ContainersConfig) |
3669 | - self.connection = unittest.mock.Mock() |
3670 | - self.lock = unittest.mock.MagicMock() |
3671 | - self.called_with = None |
3672 | - |
3673 | - def callback(self, task): |
3674 | - self.called_with = task |
3675 | - |
3676 | - def test_sends_error_on_non_existent_container(self): |
3677 | - self.config.container_exists.return_value = False |
3678 | - with unittest.mock.patch('libertine.service.tasks.base_task.libertine.service.progress.Progress') as MockProgress: |
3679 | - progress = MockProgress.return_value |
3680 | - task = tasks.ListAppsTask('palpatine', self.config, self.connection, self.callback) |
3681 | - task._instant_callback = True |
3682 | - |
3683 | - with unittest.mock.patch('libertine.service.tasks.list_apps_task.LibertineContainer') as MockContainer: |
3684 | - task.start().join() |
3685 | - |
3686 | - progress.error.assert_called_once_with('Container \'palpatine\' does not exist, skipping list') |
3687 | - self.assertEqual(task, self.called_with) |
3688 | - |
3689 | - def test_successfully_lists_apps(self): |
3690 | - self.config.container_exists.return_value = True |
3691 | - with unittest.mock.patch('libertine.service.tasks.base_task.libertine.service.progress.Progress') as MockProgress: |
3692 | - progress = MockProgress.return_value |
3693 | - progress.done = False |
3694 | - task = tasks.ListAppsTask('palpatine', self.config, self.connection, self.callback) |
3695 | - task._instant_callback = True |
3696 | - |
3697 | - with unittest.mock.patch('libertine.service.tasks.list_apps_task.LibertineContainer') as MockContainer: |
3698 | - MockContainer.return_value.list_app_launchers.return_value = 'jarjar\nsidius' |
3699 | - task.start().join() |
3700 | - |
3701 | - progress.finished.assert_called_once_with('palpatine') |
3702 | - progress.data.assert_called_once_with('jarjar\nsidius') |
3703 | - self.assertEqual(task, self.called_with) |
3704 | |
3705 | === modified file 'tests/unit/service/tasks/test_list_task.py' |
3706 | --- tests/unit/service/tasks/test_list_task.py 2016-11-07 18:51:17 +0000 |
3707 | +++ tests/unit/service/tasks/test_list_task.py 2017-02-09 16:08:46 +0000 |
3708 | @@ -1,4 +1,4 @@ |
3709 | -# Copyright 2016 Canonical Ltd. |
3710 | +# Copyright 2016-2017 Canonical Ltd. |
3711 | # |
3712 | # This program is free software: you can redistribute it and/or modify it |
3713 | # under the terms of the GNU General Public License version 3, as published |
3714 | @@ -13,7 +13,9 @@ |
3715 | # with this program. If not, see <http://www.gnu.org/licenses/>. |
3716 | |
3717 | |
3718 | +import json |
3719 | import unittest.mock |
3720 | + |
3721 | from unittest import TestCase |
3722 | from libertine.service import tasks |
3723 | from libertine.ContainersConfig import ContainersConfig |
3724 | @@ -32,14 +34,14 @@ |
3725 | with unittest.mock.patch('libertine.service.tasks.base_task.libertine.service.progress.Progress') as MockProgress: |
3726 | progress = MockProgress.return_value |
3727 | progress.done = False |
3728 | - task = tasks.ListTask(self.connection, callback) |
3729 | + |
3730 | + task = tasks.ListTask(self.config, self.connection, callback) |
3731 | task._instant_callback = True |
3732 | |
3733 | - with unittest.mock.patch('libertine.service.tasks.list_task.utils.Libertine') as MockLibertine: |
3734 | - MockLibertine.list_containers.return_value = 'palpatine\nvader\nmaul' |
3735 | - task.start().join() |
3736 | + self.config.get_containers.return_value = ['palatine', 'vader', 'maul'] |
3737 | + task.start().join() |
3738 | |
3739 | - progress.data.assert_called_once_with('palpatine\nvader\nmaul') |
3740 | + progress.data.assert_called_once_with(json.dumps(['palatine', 'vader', 'maul'])) |
3741 | progress.finished.assert_called_once_with('') |
3742 | |
3743 | self.assertEqual(task, self.called_with) |
3744 | |
3745 | === modified file 'tests/unit/service/test_apt.py' |
3746 | --- tests/unit/service/test_apt.py 2016-11-03 20:10:47 +0000 |
3747 | +++ tests/unit/service/test_apt.py 2017-02-09 16:08:46 +0000 |
3748 | @@ -1,4 +1,4 @@ |
3749 | -# Copyright 2016 Canonical Ltd. |
3750 | +# Copyright 2016-2017 Canonical Ltd. |
3751 | # |
3752 | # This program is free software: you can redistribute it and/or modify it |
3753 | # under the terms of the GNU General Public License version 3, as published |
3754 | @@ -14,8 +14,9 @@ |
3755 | |
3756 | import os |
3757 | import unittest.mock |
3758 | +from libertine import utils |
3759 | +from libertine.service import apt |
3760 | from unittest import TestCase |
3761 | -from libertine.service import apt |
3762 | |
3763 | |
3764 | def build_mock_app(name, summary, website, description): |
3765 | @@ -30,13 +31,13 @@ |
3766 | |
3767 | |
3768 | class TestAptCache(TestCase): |
3769 | + def setUp(self): |
3770 | + os.environ['XDG_CACHE_HOME'] = "{}/containerroot".format(os.environ['LIBERTINE_DATA_DIR']) |
3771 | + |
3772 | def test_search_returns_empty_when_no_matching_results(self): |
3773 | with unittest.mock.patch('libertine.service.apt.apt.Cache') as MockCache: |
3774 | MockCache.return_value.keys.return_value = [] |
3775 | - |
3776 | - with unittest.mock.patch('libertine.service.apt.Libertine') as MockLibertine: |
3777 | - MockLibertine.container_path.return_value = '/some/junk' |
3778 | - self.assertEqual(apt.AptCache('palpatine').search('vim'), []) |
3779 | + self.assertEqual(apt.AptCache('palpatine').search('vim'), []) |
3780 | |
3781 | def test_search_returns_matching_results(self): |
3782 | with unittest.mock.patch('libertine.service.apt.apt.Cache') as MockCache: |
3783 | @@ -47,9 +48,7 @@ |
3784 | "vim-common": build_mock_app("vim-common", "common vim stuff", "vim.common", "dependencies") |
3785 | } |
3786 | |
3787 | - with unittest.mock.patch('libertine.service.apt.Libertine') as MockLibertine: |
3788 | - MockLibertine.container_path.return_value = '/some/junk' |
3789 | - results = apt.AptCache('palpatine').search('vim') |
3790 | + results = apt.AptCache('palpatine').search('vim') |
3791 | |
3792 | self.assertEqual(len(results), 2) |
3793 | results = sorted(results, key=lambda xx: xx['id']) |
3794 | @@ -67,15 +66,13 @@ |
3795 | self.assertEqual(results[1]['website'], 'vim.common') |
3796 | self.assertEqual(results[1]['package'], 'vim-common') |
3797 | |
3798 | - MockCache.assert_called_once_with() |
3799 | + MockCache.assert_called_once_with(rootdir=utils.get_libertine_container_rootfs_path('palpatine')) |
3800 | |
3801 | def test_app_info_returns_empty_dict_when_no_such_app_exists(self): |
3802 | with unittest.mock.patch('libertine.service.apt.apt.Cache') as MockCache: |
3803 | MockCache.return_value = {} |
3804 | - with unittest.mock.patch('libertine.service.apt.Libertine') as MockLibertine: |
3805 | - MockLibertine.container_path.return_value = '/some/junk' |
3806 | - self.assertEqual(apt.AptCache('palpatine').app_info("vim"), {}) |
3807 | - MockCache.assert_called_once_with() |
3808 | + self.assertEqual(apt.AptCache('palpatine').app_info("vim"), {}) |
3809 | + MockCache.assert_called_once_with(rootdir=utils.get_libertine_container_rootfs_path('palpatine')) |
3810 | |
3811 | def test_app_info_returns_values_for_app(self): |
3812 | with unittest.mock.patch('libertine.service.apt.apt.Cache') as MockCache: |
3813 | @@ -83,17 +80,15 @@ |
3814 | "vim": build_mock_app("vim", "vi improved", "vim.com", "who even uses raw vi"), |
3815 | "gimp": build_mock_app("gimp", "foss photoshop", "gimp.com", "visual text editor"), |
3816 | } |
3817 | - with unittest.mock.patch('libertine.service.apt.Libertine') as MockLibertine: |
3818 | - MockLibertine.container_path.return_value = '/some/junk' |
3819 | - self.assertEqual(apt.AptCache('palpatine').app_info("vim"), { |
3820 | - 'name': 'vim', |
3821 | - 'id': 'vim', |
3822 | - 'package': 'vim', |
3823 | - 'summary': 'vi improved', |
3824 | - 'description': 'who even uses raw vi', |
3825 | - 'website': 'vim.com' |
3826 | - }) |
3827 | - MockCache.assert_called_once_with() |
3828 | + self.assertEqual(apt.AptCache('palpatine').app_info("vim"), { |
3829 | + 'name': 'vim', |
3830 | + 'id': 'vim', |
3831 | + 'package': 'vim', |
3832 | + 'summary': 'vi improved', |
3833 | + 'description': 'who even uses raw vi', |
3834 | + 'website': 'vim.com' |
3835 | + }) |
3836 | + MockCache.assert_called_once_with(rootdir=utils.get_libertine_container_rootfs_path('palpatine')) |
3837 | |
3838 | def test_loads_cache_from_container_directory(self): |
3839 | with unittest.mock.patch('libertine.service.apt.apt.Cache') as MockCache: |
3840 | @@ -101,19 +96,17 @@ |
3841 | "vim": build_mock_app("vim", "vi improved", "vim.com", "who even uses raw vi"), |
3842 | "gimp": build_mock_app("gimp", "foss photoshop", "gimp.com", "visual text editor"), |
3843 | } |
3844 | - with unittest.mock.patch('libertine.service.apt.Libertine') as MockLibertine: |
3845 | - containerpath = "%s/containerroot" % os.path.dirname(os.path.realpath(__file__)) |
3846 | - MockLibertine.container_path.return_value = containerpath |
3847 | - self.assertEqual(apt.AptCache('palpatine').app_info("vim"), { |
3848 | - 'name': 'vim', |
3849 | - 'id': 'vim', |
3850 | - 'package': 'vim', |
3851 | - 'summary': 'vi improved', |
3852 | - 'description': 'who even uses raw vi', |
3853 | - 'website': 'vim.com' |
3854 | - }) |
3855 | - |
3856 | - MockCache.assert_called_once_with(rootdir=containerpath) |
3857 | + |
3858 | + self.assertEqual(apt.AptCache('palpatine').app_info("vim"), { |
3859 | + 'name': 'vim', |
3860 | + 'id': 'vim', |
3861 | + 'package': 'vim', |
3862 | + 'summary': 'vi improved', |
3863 | + 'description': 'who even uses raw vi', |
3864 | + 'website': 'vim.com' |
3865 | + }) |
3866 | + |
3867 | + MockCache.assert_called_once_with(rootdir=utils.get_libertine_container_rootfs_path('palpatine')) |
3868 | |
3869 | def test_loads_cache_only_once(self): |
3870 | with unittest.mock.patch('libertine.service.apt.apt.Cache') as MockCache: |
3871 | @@ -121,13 +114,12 @@ |
3872 | "vim": build_mock_app("vim", "vi improved", "vim.com", "who even uses raw vi"), |
3873 | "gimp": build_mock_app("gimp", "foss photoshop", "gimp.com", "visual text editor"), |
3874 | } |
3875 | - with unittest.mock.patch('libertine.service.apt.Libertine') as MockLibertine: |
3876 | - MockLibertine.container_path.return_value = '/some/junk' |
3877 | - cache = apt.AptCache('palpatine') |
3878 | - cache.app_info("vim") |
3879 | - cache.app_info("vim") |
3880 | - |
3881 | - MockCache.assert_called_once_with() |
3882 | + |
3883 | + cache = apt.AptCache('palpatine') |
3884 | + cache.app_info("vim") |
3885 | + cache.app_info("vim") |
3886 | + |
3887 | + MockCache.assert_called_once_with(rootdir=utils.get_libertine_container_rootfs_path('palpatine')) |
3888 | |
3889 | |
3890 | if __name__ == '__main__': |
3891 | |
3892 | === modified file 'tests/unit/service/test_container.py' |
3893 | --- tests/unit/service/test_container.py 2016-11-04 15:54:34 +0000 |
3894 | +++ tests/unit/service/test_container.py 2017-02-09 16:08:46 +0000 |
3895 | @@ -1,4 +1,4 @@ |
3896 | -# Copyright 2016 Canonical Ltd. |
3897 | +# Copyright 2016-2017 Canonical Ltd. |
3898 | # |
3899 | # This program is free software: you can redistribute it and/or modify it |
3900 | # under the terms of the GNU General Public License version 3, as published |
3901 | @@ -22,12 +22,11 @@ |
3902 | def setUp(self): |
3903 | self._connection = unittest.mock.Mock() |
3904 | self._config = unittest.mock.Mock() |
3905 | - self._lock = unittest.mock.Mock() |
3906 | |
3907 | def test_search_creates_search_task(self): |
3908 | with unittest.mock.patch('libertine.service.container.apt.AptCache') as MockCache: |
3909 | cache = MockCache.return_value |
3910 | - c = container.Container('palpatine', self._config, self._lock, self._connection, lambda task: task) |
3911 | + c = container.Container('palpatine', self._config, self._connection, lambda task: task) |
3912 | with unittest.mock.patch('libertine.service.container.SearchTask') as MockSearchTask: |
3913 | c.search('darkseid') |
3914 | MockSearchTask.assert_called_once_with('palpatine', cache, 'darkseid', self._connection, unittest.mock.ANY) |
3915 | @@ -36,7 +35,7 @@ |
3916 | def test_app_info_creates_app_info_task(self): |
3917 | with unittest.mock.patch('libertine.service.container.apt.AptCache') as MockCache: |
3918 | cache = MockCache.return_value |
3919 | - c = container.Container('palpatine', self._config, self._lock, self._connection, lambda task: task) |
3920 | + c = container.Container('palpatine', self._config, self._connection, lambda task: task) |
3921 | with unittest.mock.patch('libertine.service.container.AppInfoTask') as MockAppInfoTask: |
3922 | c.app_info('force') |
3923 | MockAppInfoTask.assert_called_once_with('palpatine', cache, 'force', [], self._config, self._connection, unittest.mock.ANY) |
3924 | @@ -45,7 +44,7 @@ |
3925 | def test_app_info_gets_related_task_info(self): |
3926 | with unittest.mock.patch('libertine.service.container.apt.AptCache') as MockCache: |
3927 | cache = MockCache.return_value |
3928 | - c = container.Container('palpatine', self._config, self._lock, self._connection, lambda task: task) |
3929 | + c = container.Container('palpatine', self._config, self._connection, lambda task: task) |
3930 | with unittest.mock.patch('libertine.service.container.InstallTask') as MockInstallTask: |
3931 | MockInstallTask.return_value.package = 'darkside' |
3932 | MockInstallTask.return_value.matches.return_value = False |
3933 | @@ -60,119 +59,107 @@ |
3934 | |
3935 | def test_install_creates_install_task(self): |
3936 | with unittest.mock.patch('libertine.service.container.apt.AptCache') as MockCache: |
3937 | - cache = MockCache.return_value |
3938 | - c = container.Container('palpatine', self._config, self._lock, self._connection, lambda task: task) |
3939 | + c = container.Container('palpatine', self._config, self._connection, lambda task: task) |
3940 | with unittest.mock.patch('libertine.service.container.InstallTask') as MockInstallTask: |
3941 | c.install('force') |
3942 | - MockInstallTask.assert_called_once_with('force', 'palpatine', self._config, self._lock, self._connection, unittest.mock.ANY) |
3943 | + MockInstallTask.assert_called_once_with('force', 'palpatine', self._config, unittest.mock.ANY, self._connection, unittest.mock.ANY) |
3944 | MockInstallTask.return_value.start.assert_called_once_with() |
3945 | |
3946 | def test_install_only_calls_once_when_unfinished(self): |
3947 | with unittest.mock.patch('libertine.service.container.apt.AptCache') as MockCache: |
3948 | - cache = MockCache.return_value |
3949 | - c = container.Container('palpatine', self._config, self._lock, self._connection, lambda task: task) |
3950 | + c = container.Container('palpatine', self._config, self._connection, lambda task: task) |
3951 | with unittest.mock.patch('libertine.service.container.InstallTask') as MockInstallTask: |
3952 | c.install('darkside') |
3953 | c.install('darkside') |
3954 | c.install('darkside') |
3955 | - # MockInstallTask.assert_called_once_with('darkside', 'palpatine', self._config, self._lock, self._connection, unittest.mock.ANY) |
3956 | + MockInstallTask.assert_called_once_with('darkside', 'palpatine', self._config, unittest.mock.ANY, self._connection, unittest.mock.ANY) |
3957 | MockInstallTask.return_value.start.assert_called_once_with() |
3958 | |
3959 | def test_remove_creates_remove_task(self): |
3960 | with unittest.mock.patch('libertine.service.container.apt.AptCache') as MockCache: |
3961 | - cache = MockCache.return_value |
3962 | - c = container.Container('palpatine', self._config, self._lock, self._connection, lambda task: task) |
3963 | + c = container.Container('palpatine', self._config, self._connection, lambda task: task) |
3964 | with unittest.mock.patch('libertine.service.container.RemoveTask') as MockRemoveTask: |
3965 | c.remove('force') |
3966 | - MockRemoveTask.assert_called_once_with('force', 'palpatine', self._config, self._lock, self._connection, unittest.mock.ANY) |
3967 | + MockRemoveTask.assert_called_once_with('force', 'palpatine', self._config, unittest.mock.ANY, self._connection, unittest.mock.ANY) |
3968 | MockRemoveTask.return_value.start.assert_called_once_with() |
3969 | |
3970 | def test_remove_only_calls_once_when_unfinished(self): |
3971 | with unittest.mock.patch('libertine.service.container.apt.AptCache') as MockCache: |
3972 | - cache = MockCache.return_value |
3973 | - c = container.Container('palpatine', self._config, self._lock, self._connection, lambda task: task) |
3974 | + c = container.Container('palpatine', self._config, self._connection, lambda task: task) |
3975 | with unittest.mock.patch('libertine.service.container.RemoveTask') as MockRemoveTask: |
3976 | c.remove('darkside') |
3977 | c.remove('darkside') |
3978 | c.remove('darkside') |
3979 | - # MockRemoveTask.assert_called_once_with('darkside', 'palpatine', self._config, self._lock, self._connection, unittest.mock.ANY) |
3980 | + MockRemoveTask.assert_called_once_with('darkside', 'palpatine', self._config, unittest.mock.ANY, self._connection, unittest.mock.ANY) |
3981 | MockRemoveTask.return_value.start.assert_called_once_with() |
3982 | |
3983 | def test_create_creates_create_task(self): |
3984 | with unittest.mock.patch('libertine.service.container.apt.AptCache') as MockCache: |
3985 | - cache = MockCache.return_value |
3986 | - c = container.Container('palpatine', self._config, self._lock, self._connection, lambda task: task) |
3987 | + c = container.Container('palpatine', self._config, self._connection, lambda task: task) |
3988 | with unittest.mock.patch('libertine.service.container.CreateTask') as MockCreateTask: |
3989 | c.create('Emperor Palpatine', 'zesty', 'lxd', False) |
3990 | MockCreateTask.assert_called_once_with('palpatine', 'Emperor Palpatine', 'zesty', 'lxd', False, |
3991 | - self._config, self._lock, self._connection, unittest.mock.ANY) |
3992 | + self._config, unittest.mock.ANY, self._connection, unittest.mock.ANY) |
3993 | MockCreateTask.return_value.start.assert_called_once_with() |
3994 | |
3995 | def test_create_only_calls_once_when_unfinished(self): |
3996 | with unittest.mock.patch('libertine.service.container.apt.AptCache') as MockCache: |
3997 | - cache = MockCache.return_value |
3998 | - c = container.Container('palpatine', self._config, self._lock, self._connection, lambda task: task) |
3999 | + c = container.Container('palpatine', self._config, self._connection, lambda task: task) |
4000 | with unittest.mock.patch('libertine.service.container.CreateTask') as MockCreateTask: |
4001 | c.create('Emperor Palpatine', 'zesty', 'lxd', False) |
4002 | c.create('Emperor Palpatine', 'zesty', 'lxd', False) |
4003 | c.create('Emperor Palpatine', 'zesty', 'lxd', False) |
4004 | - # MockCreateTask.assert_called_once_with('palpatine', 'Emperor Palpatine', 'zesty', 'lxd', False, |
4005 | - # self._config, self._lock, self._connection, unittest.mock.ANY) |
4006 | + MockCreateTask.assert_called_once_with('palpatine', 'Emperor Palpatine', 'zesty', 'lxd', False, |
4007 | + self._config, unittest.mock.ANY, self._connection, unittest.mock.ANY) |
4008 | MockCreateTask.return_value.start.assert_called_once_with() |
4009 | |
4010 | def test_destroy_creates_destroy_task(self): |
4011 | with unittest.mock.patch('libertine.service.container.apt.AptCache') as MockCache: |
4012 | - cache = MockCache.return_value |
4013 | - c = container.Container('palpatine', self._config, self._lock, self._connection, lambda task: task) |
4014 | + c = container.Container('palpatine', self._config, self._connection, lambda task: task) |
4015 | with unittest.mock.patch('libertine.service.container.DestroyTask') as MockDestroyTask: |
4016 | c.destroy() |
4017 | - # MockDestroyTask.assert_called_once_with('palpatine', self._config, self._lock, self._connection, unittest.mock.ANY) |
4018 | + MockDestroyTask.assert_called_once_with('palpatine', self._config, unittest.mock.ANY, self._connection, unittest.mock.ANY) |
4019 | MockDestroyTask.return_value.start.assert_called_once_with() |
4020 | |
4021 | def test_destroy_only_calls_once_when_unfinished(self): |
4022 | with unittest.mock.patch('libertine.service.container.apt.AptCache') as MockCache: |
4023 | - cache = MockCache.return_value |
4024 | - c = container.Container('palpatine', self._config, self._lock, self._connection, lambda task: task) |
4025 | + c = container.Container('palpatine', self._config, self._connection, lambda task: task) |
4026 | with unittest.mock.patch('libertine.service.container.DestroyTask') as MockDestroyTask: |
4027 | c.destroy() |
4028 | c.destroy() |
4029 | c.destroy() |
4030 | - # MockDestroyTask.assert_called_once_with('palpatine', self._config, self._lock, self._connection, unittest.mock.ANY) |
4031 | + MockDestroyTask.assert_called_once_with('palpatine', self._config, unittest.mock.ANY, self._connection, unittest.mock.ANY) |
4032 | MockDestroyTask.return_value.start.assert_called_once_with() |
4033 | |
4034 | def test_update_creates_update_task(self): |
4035 | with unittest.mock.patch('libertine.service.container.apt.AptCache') as MockCache: |
4036 | - cache = MockCache.return_value |
4037 | - c = container.Container('palpatine', self._config, self._lock, self._connection, lambda task: task) |
4038 | + c = container.Container('palpatine', self._config, self._connection, lambda task: task) |
4039 | with unittest.mock.patch('libertine.service.container.UpdateTask') as MockUpdateTask: |
4040 | c.update() |
4041 | - MockUpdateTask.assert_called_once_with('palpatine', self._config, self._lock, self._connection, unittest.mock.ANY) |
4042 | + MockUpdateTask.assert_called_once_with('palpatine', self._config, unittest.mock.ANY, self._connection, unittest.mock.ANY) |
4043 | MockUpdateTask.return_value.start.assert_called_once_with() |
4044 | |
4045 | def test_update_only_calls_once_when_unfinished(self): |
4046 | with unittest.mock.patch('libertine.service.container.apt.AptCache') as MockCache: |
4047 | - cache = MockCache.return_value |
4048 | - c = container.Container('palpatine', self._config, self._lock, self._connection, lambda task: task) |
4049 | + c = container.Container('palpatine', self._config, self._connection, lambda task: task) |
4050 | with unittest.mock.patch('libertine.service.container.UpdateTask') as MockUpdateTask: |
4051 | c.update() |
4052 | c.update() |
4053 | c.update() |
4054 | - # MockUpdateTask.assert_called_once_with('palpatine', self._config, self._lock, self._connection, unittest.mock.ANY) |
4055 | + MockUpdateTask.assert_called_once_with('palpatine', self._config, unittest.mock.ANY, self._connection, unittest.mock.ANY) |
4056 | MockUpdateTask.return_value.start.assert_called_once_with() |
4057 | |
4058 | - def test_list_apps_creates_list_apps_task(self): |
4059 | + def test_list_app_ids_creates_list_app_ids_task(self): |
4060 | with unittest.mock.patch('libertine.service.container.apt.AptCache') as MockCache: |
4061 | - cache = MockCache.return_value |
4062 | - c = container.Container('palpatine', self._config, self._lock, self._connection, lambda task: task) |
4063 | - with unittest.mock.patch('libertine.service.container.ListAppsTask') as MockListAppsTask: |
4064 | - c.list_apps() |
4065 | - MockListAppsTask.assert_called_once_with('palpatine', self._config, self._connection, unittest.mock.ANY) |
4066 | - MockListAppsTask.return_value.start.assert_called_once_with() |
4067 | + c = container.Container('palpatine', self._config, self._connection, lambda task: task) |
4068 | + with unittest.mock.patch('libertine.service.container.ListAppIdsTask') as MockListAppIdsTask: |
4069 | + c.list_app_ids() |
4070 | + MockListAppIdsTask.assert_called_once_with('palpatine', self._config, self._connection, unittest.mock.ANY) |
4071 | + MockListAppIdsTask.return_value.start.assert_called_once_with() |
4072 | |
4073 | def test_removes_task_during_callback(self): |
4074 | with unittest.mock.patch('libertine.service.container.apt.AptCache') as MockCache: |
4075 | - cache = MockCache.return_value |
4076 | - c = container.Container('palpatine', self._config, self._lock, self._connection, lambda task: task) |
4077 | + c = container.Container('palpatine', self._config, self._connection, lambda task: task) |
4078 | with unittest.mock.patch('libertine.service.container.InstallTask') as MockInstallTask: |
4079 | MockInstallTask.return_value.package = 'force' |
4080 | c.install('force') |
4081 | @@ -186,11 +173,10 @@ |
4082 | |
4083 | def test_completing_all_tasks_fires_callback(self): |
4084 | with unittest.mock.patch('libertine.service.container.apt.AptCache') as MockCache: |
4085 | - cache = MockCache.return_value |
4086 | self._container_id = None |
4087 | def callback(container): |
4088 | self._container_id = container.id |
4089 | - c = container.Container('palpatine', self._config, self._lock, self._connection, callback) |
4090 | + c = container.Container('palpatine', self._config, self._connection, callback) |
4091 | with unittest.mock.patch('libertine.service.container.InstallTask') as MockInstallTask: |
4092 | c.install('force') |
4093 | name, args, kwargs = MockInstallTask.mock_calls[0] |
4094 | |
4095 | === modified file 'tests/unit/service/test_task_dispatcher.py' |
4096 | --- tests/unit/service/test_task_dispatcher.py 2016-11-03 20:20:15 +0000 |
4097 | +++ tests/unit/service/test_task_dispatcher.py 2017-02-09 16:08:46 +0000 |
4098 | @@ -1,4 +1,4 @@ |
4099 | -# Copyright 2016 Canonical Ltd. |
4100 | +# Copyright 2016-2017 Canonical Ltd. |
4101 | # |
4102 | # This program is free software: you can redistribute it and/or modify it |
4103 | # under the terms of the GNU General Public License version 3, as published |
4104 | @@ -76,34 +76,34 @@ |
4105 | self.assertEqual(123, self._dispatcher.update('palpatine')) |
4106 | c.update.assert_called_once_with() |
4107 | |
4108 | - def test_list_apps_calls_list_apps_on_container(self): |
4109 | + def test_list_app_ids_calls_list_app_ids_on_container(self): |
4110 | with unittest.mock.patch('libertine.service.task_dispatcher.Container') as MockContainer: |
4111 | c = MockContainer.return_value |
4112 | - c.list_apps.return_value = 123 |
4113 | - self.assertEqual(123, self._dispatcher.list_apps('palpatine')) |
4114 | - c.list_apps.assert_called_once_with() |
4115 | + c.list_app_ids.return_value = 123 |
4116 | + self.assertEqual(123, self._dispatcher.list_app_ids('palpatine')) |
4117 | + c.list_app_ids.assert_called_once_with() |
4118 | |
4119 | def test_containers_reused_on_subsequent_calls(self): |
4120 | with unittest.mock.patch('libertine.service.task_dispatcher.Container') as MockContainer: |
4121 | c = MockContainer.return_value |
4122 | c.id = 'palpatine' |
4123 | - self._dispatcher.list_apps('palpatine') |
4124 | - self._dispatcher.list_apps('palpatine') |
4125 | - self._dispatcher.list_apps('palpatine') |
4126 | - MockContainer.assert_called_once_with('palpatine', unittest.mock.ANY, unittest.mock.ANY, self._connection, unittest.mock.ANY) |
4127 | + self._dispatcher.list_app_ids('palpatine') |
4128 | + self._dispatcher.list_app_ids('palpatine') |
4129 | + self._dispatcher.list_app_ids('palpatine') |
4130 | + MockContainer.assert_called_once_with('palpatine', unittest.mock.ANY, self._connection, unittest.mock.ANY) |
4131 | |
4132 | def test_container_callback_removes_container(self): |
4133 | with unittest.mock.patch('libertine.service.task_dispatcher.Container') as MockContainer: |
4134 | c = MockContainer.return_value |
4135 | c.id = 'palpatine' |
4136 | - self._dispatcher.list_apps('palpatine') |
4137 | - MockContainer.assert_called_once_with('palpatine', unittest.mock.ANY, unittest.mock.ANY, self._connection, unittest.mock.ANY) |
4138 | + self._dispatcher.list_app_ids('palpatine') |
4139 | + MockContainer.assert_called_once_with('palpatine', unittest.mock.ANY, self._connection, unittest.mock.ANY) |
4140 | name, args, kwargs = MockContainer.mock_calls[0] |
4141 | args[len(args)-1](MockContainer.return_value) |
4142 | - self._dispatcher.list_apps('palpatine') |
4143 | + self._dispatcher.list_app_ids('palpatine') |
4144 | MockContainer.assert_has_calls([ # verify container constructed twice |
4145 | - unittest.mock.call('palpatine', unittest.mock.ANY, unittest.mock.ANY, self._connection, unittest.mock.ANY), |
4146 | - unittest.mock.call('palpatine', unittest.mock.ANY, unittest.mock.ANY, self._connection, unittest.mock.ANY) |
4147 | + unittest.mock.call('palpatine', unittest.mock.ANY, self._connection, unittest.mock.ANY), |
4148 | + unittest.mock.call('palpatine', unittest.mock.ANY, self._connection, unittest.mock.ANY) |
4149 | ], any_order=True) |
4150 | |
4151 | def test_container_info_creates_container_info_task(self): |
4152 | @@ -121,7 +121,7 @@ |
4153 | c = MockContainer.return_value |
4154 | c.tasks = [1, 2, 3] |
4155 | c.id = 'palpatine' |
4156 | - self._dispatcher.list_apps('palpatine') # creates container |
4157 | + self._dispatcher.list_app_ids('palpatine') # creates container |
4158 | self._dispatcher.container_info('palpatine') |
4159 | MockContainerInfoTask.assert_called_once_with('palpatine', [1, 2, 3], unittest.mock.ANY, self._connection, unittest.mock.ANY) |
4160 | task.start.assert_called_once_with() |
4161 | @@ -131,13 +131,13 @@ |
4162 | task = MockListTask.return_value |
4163 | task.id = 123 |
4164 | self.assertEqual(123, self._dispatcher.list()) |
4165 | - MockListTask.assert_called_once_with(self._connection, unittest.mock.ANY) |
4166 | + MockListTask.assert_called_once_with(unittest.mock.ANY, self._connection, unittest.mock.ANY) |
4167 | task.start.assert_called_once_with() |
4168 | |
4169 | def test_containerless_tasks_are_cleaned_up(self): |
4170 | with unittest.mock.patch('libertine.service.task_dispatcher.ListTask') as MockListTask: |
4171 | self._dispatcher.list() |
4172 | - MockListTask.assert_called_once_with(self._connection, unittest.mock.ANY) |
4173 | + MockListTask.assert_called_once_with(unittest.mock.ANY, self._connection, unittest.mock.ANY) |
4174 | name, args, kwargs = MockListTask.mock_calls[0] |
4175 | self.assertEqual(1, len(self._dispatcher._tasks)) |
4176 | args[len(args)-1](MockListTask.return_value) |
4177 | |
4178 | === removed file 'tests/unit/test_app_discovery.py' |
4179 | --- tests/unit/test_app_discovery.py 2015-12-22 17:28:10 +0000 |
4180 | +++ tests/unit/test_app_discovery.py 1970-01-01 00:00:00 +0000 |
4181 | @@ -1,52 +0,0 @@ |
4182 | -"""Unit tests for the AppLauncher module.""" |
4183 | -# Copyright 2015 Canonical Ltd. |
4184 | -# |
4185 | -# This program is free software: you can redistribute it and/or modify it |
4186 | -# under the terms of the GNU General Public License version 3, as published |
4187 | -# by the Free Software Foundation. |
4188 | -# |
4189 | -# This program is distributed in the hope that it will be useful, but |
4190 | -# WITHOUT ANY WARRANTY; without even the implied warranties of |
4191 | -# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
4192 | -# PURPOSE. See the GNU General Public License for more details. |
4193 | -# |
4194 | -# You should have received a copy of the GNU General Public License along |
4195 | -# with this program. If not, see <http://www.gnu.org/licenses/>. |
4196 | - |
4197 | -from libertine.AppDiscovery import IconCache |
4198 | -from testtools import TestCase |
4199 | -from testtools.matchers import Equals |
4200 | - |
4201 | - |
4202 | -class TestIconSearching(TestCase): |
4203 | - |
4204 | - def mock_file_loader(self, root_path): |
4205 | - return [ |
4206 | - "/some/path/128x128/icon.png", |
4207 | - "/somepath/icon.png", |
4208 | - "/somepath/testme.png" |
4209 | - ]; |
4210 | - |
4211 | - def test_full_path(self): |
4212 | - some_root = "/some_root" |
4213 | - icon_cache = IconCache(some_root, file_loader=self.mock_file_loader) |
4214 | - |
4215 | - some_full_path = "/this/is/a/full/path" |
4216 | - self.assertThat(icon_cache.expand_icons(some_full_path)[0], |
4217 | - Equals(some_full_path)) |
4218 | - |
4219 | - def test_icon_name(self): |
4220 | - some_root = "/some_root" |
4221 | - icon_cache = IconCache(some_root, file_loader=self.mock_file_loader) |
4222 | - |
4223 | - some_icon_list = "testme" |
4224 | - self.assertThat(icon_cache.expand_icons(some_icon_list)[0], |
4225 | - Equals("/somepath/testme.png")) |
4226 | - |
4227 | - def test_edge_case_relative_file_name(self): |
4228 | - some_root = "/some_root" |
4229 | - icon_cache = IconCache(some_root, file_loader=self.mock_file_loader) |
4230 | - |
4231 | - some_icon_list = "testme.png" |
4232 | - self.assertThat(icon_cache.expand_icons(some_icon_list)[0], |
4233 | - Equals("/somepath/testme.png")) |
4234 | |
4235 | === modified file 'tests/unit/test_libertine_container.py' |
4236 | --- tests/unit/test_libertine_container.py 2016-11-10 17:40:14 +0000 |
4237 | +++ tests/unit/test_libertine_container.py 2017-02-09 16:08:46 +0000 |
4238 | @@ -1,5 +1,5 @@ |
4239 | """Unit tests for the LibertineContainer interface.""" |
4240 | -# Copyright 2015 Canonical Ltd. |
4241 | +# Copyright 2015-2017 Canonical Ltd. |
4242 | # |
4243 | # This program is free software: you can redistribute it and/or modify it |
4244 | # under the terms of the GNU General Public License version 3, as published |
4245 | @@ -52,6 +52,7 @@ |
4246 | |
4247 | def test_container_name_default(self): |
4248 | container_id = "test-id-3" |
4249 | + self._config.get_container_name.return_value = None |
4250 | container = Libertine.LibertineContainer(container_id, self._config) |
4251 | |
4252 | self.assertThat(container.name, Equals("Unknown")) |
4253 | |
4254 | === removed file 'tests/unit/test_libertine_gir.py' |
4255 | --- tests/unit/test_libertine_gir.py 2016-11-21 19:54:35 +0000 |
4256 | +++ tests/unit/test_libertine_gir.py 1970-01-01 00:00:00 +0000 |
4257 | @@ -1,65 +0,0 @@ |
4258 | -# Copyright 2015 Canonical Ltd. |
4259 | -# |
4260 | -# This program is free software: you can redistribute it and/or modify it |
4261 | -# under the terms of the GNU General Public License version 3, as published |
4262 | -# by the Free Software Foundation. |
4263 | -# |
4264 | -# This program is distributed in the hope that it will be useful, but |
4265 | -# WITHOUT ANY WARRANTY; without even the implied warranties of |
4266 | -# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
4267 | -# PURPOSE. See the GNU General Public License for more details. |
4268 | -# |
4269 | -# You should have received a copy of the GNU General Public License along |
4270 | -# with this program. If not, see <http://www.gnu.org/licenses/>. |
4271 | - |
4272 | -import os |
4273 | -from testtools import TestCase |
4274 | -from testtools.matchers import Equals |
4275 | -from gi.repository import Libertine |
4276 | -from unittest import skip |
4277 | -from unittest.mock import patch |
4278 | - |
4279 | - |
4280 | -class TestLibertineGir(TestCase): |
4281 | - |
4282 | - def setUp(self): |
4283 | - super(TestLibertineGir, self).setUp() |
4284 | - self.cmake_source_dir = os.environ['LIBERTINE_DATA_DIR'] |
4285 | - |
4286 | - def test_list_containers(self): |
4287 | - with patch.dict('os.environ', {'XDG_DATA_HOME': self.cmake_source_dir + '/libertine-config'}): |
4288 | - containers = Libertine.list_containers() |
4289 | - |
4290 | - self.assertThat(containers[0], Equals('wily')) |
4291 | - self.assertThat(containers[1], Equals('wily-2')) |
4292 | - |
4293 | - @skip("need to work around cached globals in glib") |
4294 | - def test_container_path(self): |
4295 | - container_id = 'wily' |
4296 | - with patch.dict('os.environ', {'XDG_CACHE_HOME': self.cmake_source_dir + '/libertine-data'}): |
4297 | - container_path = Libertine.container_path(container_id) |
4298 | - |
4299 | - self.assertThat(container_path, Equals(self.cmake_source_dir + '/libertine-data/libertine-container/wily/rootfs')) |
4300 | - |
4301 | - def test_container_home_path(self): |
4302 | - container_id = 'wily' |
4303 | - with patch.dict('os.environ', {'XDG_DATA_HOME': self.cmake_source_dir + '/libertine-home'}): |
4304 | - container_home_path = Libertine.container_home_path(container_id) |
4305 | - |
4306 | - self.assertThat(container_home_path, Equals(self.cmake_source_dir + '/libertine-home/libertine-container/user-data/wily')) |
4307 | - |
4308 | - def test_container_name(self): |
4309 | - with patch.dict('os.environ', {'XDG_DATA_HOME': self.cmake_source_dir + '/libertine-config'}): |
4310 | - container_name = Libertine.container_name('wily') |
4311 | - |
4312 | - self.assertThat(container_name, Equals("Ubuntu 'Wily Werewolf'")) |
4313 | - |
4314 | - container_name = Libertine.container_name('wily-2') |
4315 | - |
4316 | - self.assertThat(container_name, Equals("Ubuntu 'Wily Werewolf' (2)")) |
4317 | - |
4318 | - def test_list_apps_for_container(self): |
4319 | - with patch.dict('os.environ', {'XDG_DATA_HOME': self.cmake_source_dir + '/libertine-config'}): |
4320 | - apps = Libertine.list_apps_for_container('wily') |
4321 | - |
4322 | - self.assertThat(len(apps), Equals(0)) |
4323 | |
4324 | === modified file 'tools/CMakeLists.txt' |
4325 | --- tools/CMakeLists.txt 2016-12-21 20:57:15 +0000 |
4326 | +++ tools/CMakeLists.txt 2017-02-09 16:08:46 +0000 |
4327 | @@ -1,8 +1,8 @@ |
4328 | -install(PROGRAMS libertine-container-manager libertine-launch libertine-lxc-manager libertine-lxd-manager |
4329 | +install(PROGRAMS libertine-container-manager libertine-launch |
4330 | libertine-xmir libertine-lxc-setup libertine-lxd-setup libertined |
4331 | DESTINATION ${CMAKE_INSTALL_BINDIR}) |
4332 | -install(FILES libertine-launch.1 libertine-container-manager.1 libertine-lxc-manager.1 |
4333 | - libertine-lxd-manager.1 libertine-xmir.1 |
4334 | +install(FILES libertine-launch.1 libertine-container-manager.1 |
4335 | + libertine-xmir.1 |
4336 | DESTINATION ${CMAKE_INSTALL_MANDIR}/man1 |
4337 | COMPONENT doc) |
4338 | install(FILES completions/libertine-container-manager |
4339 | |
4340 | === modified file 'tools/completions/libertine-container-manager' |
4341 | --- tools/completions/libertine-container-manager 2016-10-14 14:44:17 +0000 |
4342 | +++ tools/completions/libertine-container-manager 2017-02-09 16:08:46 +0000 |
4343 | @@ -1,5 +1,6 @@ |
4344 | -# Copyright (C) 2016 Canonical Ltd. |
4345 | -# Author: Larry Price <larry.price@canonical.com> |
4346 | +# Copyright (C) 2016-2017 Canonical Ltd. |
4347 | +# Authors: Larry Price <larry.price@canonical.com> |
4348 | +# Chris Townsend <christopher.townsend@canonical.com> |
4349 | |
4350 | # This program is free software: you can redistribute it and/or modify |
4351 | # it under the terms of the GNU General Public License as published by |
4352 | @@ -48,11 +49,14 @@ |
4353 | opts="--help" |
4354 | ;; |
4355 | "configure" ) |
4356 | - opts="--help --id --multiarch --archive --bind-mount" |
4357 | + opts="--help --id --multiarch --archive --bind-mount --freeze" |
4358 | ;; |
4359 | "set-default" ) |
4360 | opts="--help --id --clear" |
4361 | ;; |
4362 | + "restart" ) |
4363 | + opts="--help --id" |
4364 | + ;; |
4365 | * ) |
4366 | opts="--help --quiet --verbose" |
4367 | ;; |
4368 | @@ -60,7 +64,7 @@ |
4369 | fi |
4370 | |
4371 | if [[ ${cmd} == "configure" ]]; then |
4372 | - if [[ "${COMP_WORDS[COMP_CWORD-1]}" == "--multiarch" ]]; then |
4373 | + if [[ "${COMP_WORDS[COMP_CWORD-1]}" == "--multiarch" ]] || [ "${COMP_WORDS[COMP_CWORD-1]}" == "--freeze" ]; then |
4374 | opts="enable disable" |
4375 | elif [ "${COMP_WORDS[COMP_CWORD-1]}" == "--archive" ] || [ "${COMP_WORDS[COMP_CWORD-1]}" == "--bind-mount" ]; then |
4376 | opts="add remove" |
4377 | @@ -72,7 +76,7 @@ |
4378 | fi |
4379 | |
4380 | if [[ -z ${opts} && "${COMP_CWORD}" == "1" ]]; then |
4381 | - opts="create destroy install-package remove-package search-cache update list list-apps configure set-default" |
4382 | + opts="create destroy install-package remove-package search-cache update list list-apps configure set-default restart" |
4383 | fi |
4384 | |
4385 | if [[ -n "${opts}" ]]; then |
4386 | |
4387 | === modified file 'tools/libertine-container-manager' |
4388 | --- tools/libertine-container-manager 2017-01-13 19:03:19 +0000 |
4389 | +++ tools/libertine-container-manager 2017-02-09 16:08:46 +0000 |
4390 | @@ -1,7 +1,7 @@ |
4391 | #!/usr/bin/python3 |
4392 | # -*- coding: utf-8 -*- |
4393 | |
4394 | -# Copyright (C) 2014-2016 Canonical Ltd. |
4395 | +# Copyright (C) 2014-2017 Canonical Ltd. |
4396 | # Author: Christopher Townsend <christopher.townsend@canonical.com> |
4397 | |
4398 | # This program is free software: you can redistribute it and/or modify |
4399 | @@ -17,8 +17,9 @@ |
4400 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
4401 | |
4402 | import argparse |
4403 | +import getpass |
4404 | +import json |
4405 | import libertine.utils |
4406 | -import getpass |
4407 | import os |
4408 | import sys |
4409 | import re |
4410 | @@ -36,7 +37,7 @@ |
4411 | |
4412 | def _get_updated_locale(self, container_id): |
4413 | host_locale = self.host_info.get_host_locale() |
4414 | - |
4415 | + |
4416 | if host_locale == self.containers_config.get_container_locale(container_id): |
4417 | return None |
4418 | else: |
4419 | @@ -218,15 +219,18 @@ |
4420 | self.containers_config.update_container_install_status(container_id, "ready") |
4421 | |
4422 | def list(self, args): |
4423 | - containers = libertine.utils.Libertine.list_containers() |
4424 | - for container in containers: |
4425 | + for container in ContainersConfig().get_containers(): |
4426 | print("%s" % container) |
4427 | |
4428 | def list_apps(self, args): |
4429 | container_id = self.containers_config.check_container_id(args.id) |
4430 | |
4431 | - container = LibertineContainer(container_id) |
4432 | - print(container.list_app_launchers(use_json=args.json)) |
4433 | + app_ids = LibertineContainer(container_id).list_app_ids() |
4434 | + if args.json: |
4435 | + print(json.dumps(app_ids)) |
4436 | + else: |
4437 | + for app in app_ids: |
4438 | + print(app) |
4439 | |
4440 | def exec(self, args): |
4441 | container_id = self.containers_config.check_container_id(args.id) |
4442 | @@ -249,8 +253,6 @@ |
4443 | def configure(self, args): |
4444 | container_id = self.containers_config.check_container_id(args.id) |
4445 | |
4446 | - container = LibertineContainer(container_id) |
4447 | - |
4448 | if args.multiarch and self.host_info.get_host_architecture() == 'amd64': |
4449 | multiarch = 'disabled' |
4450 | if args.multiarch == 'enable': |
4451 | @@ -261,7 +263,7 @@ |
4452 | libertine.utils.get_logger().error("i386 multiarch support is already %s" % multiarch) |
4453 | sys.exit(1) |
4454 | |
4455 | - if container.configure_multiarch(args.multiarch) is not 0: |
4456 | + if LibertineContainer(container_id).configure_multiarch(args.multiarch) is not 0: |
4457 | sys.exit(1) |
4458 | |
4459 | self.containers_config.update_container_multiarch_support(container_id, multiarch) |
4460 | @@ -281,7 +283,7 @@ |
4461 | |
4462 | self.containers_config.add_container_archive(container_id, archive_name) |
4463 | self.containers_config.update_archive_install_status(container_id, archive_name, 'installing') |
4464 | - if container.configure_add_archive(archive_name_esc, args.public_key_file) is not 0: |
4465 | + if LibertineContainer(container_id).configure_add_archive(archive_name_esc, args.public_key_file) is not 0: |
4466 | self.containers_config.delete_container_archive(container_id, archive_name) |
4467 | sys.exit(1) |
4468 | |
4469 | @@ -328,6 +330,27 @@ |
4470 | sys.exit(1) |
4471 | self.containers_config.delete_bind_mount(container_id, mount_path) |
4472 | |
4473 | + container_type = self.containers_config.get_container_type(container_id) |
4474 | + |
4475 | + if (container_type == 'lxc' or container_type == 'lxd' and |
4476 | + self.containers_config.get_freeze_on_stop(container_id)): |
4477 | + if not LibertineContainer(container_id).restart_libertine_container(): |
4478 | + libertine.utils.get_logger().warning("Container cannot be restarted at this time. You will need to " |
4479 | + "restart the container at a later time using the \'restart\' subcommand.") |
4480 | + |
4481 | + elif args.freeze is not None: |
4482 | + container_type = self.containers_config.get_container_type(container_id) |
4483 | + |
4484 | + if container_type != 'lxc' and container_type != 'lxd': |
4485 | + libertine.utils.get_logger().error("Configuring freeze is only valid on LXC and LXD container types.") |
4486 | + sys.exit(1) |
4487 | + |
4488 | + current_freeze = self.containers_config.get_freeze_on_stop(container_id) |
4489 | + config_freeze = args.freeze == 'enable' |
4490 | + |
4491 | + if current_freeze != config_freeze: |
4492 | + self.containers_config.update_freeze_on_stop(container_id, config_freeze) |
4493 | + |
4494 | else: |
4495 | libertine.utils.get_logger().error("Configure called with no subcommand. See configure --help for usage.") |
4496 | sys.exit(1) |
4497 | @@ -362,6 +385,19 @@ |
4498 | |
4499 | self.containers_config.set_default_container_id(container_id, True) |
4500 | |
4501 | + def restart(self, args): |
4502 | + container_id = self.containers_config.check_container_id(args.id) |
4503 | + |
4504 | + container_type = self.containers_config.get_container_type(container_id) |
4505 | + |
4506 | + if container_type != 'lxc' and container_type != 'lxd': |
4507 | + libertine.utils.get_logger().error("The restart subcommand is only valid for LXC and LXD type containers.") |
4508 | + sys.exit(1) |
4509 | + |
4510 | + container = LibertineContainer(container_id) |
4511 | + |
4512 | + container.restart_libertine_container() |
4513 | + |
4514 | |
4515 | if __name__ == '__main__': |
4516 | parser = argparse.ArgumentParser(description="Legacy X application support for Unity 8") |
4517 | @@ -552,6 +588,14 @@ |
4518 | metavar='Mount path', |
4519 | help=("The absolute host path to bind-mount.")) |
4520 | |
4521 | + freeze_group = parser_configure.add_argument_group("Freeze container support", |
4522 | + "Enable or disable freezing LXC/LXD containers when not in use.") |
4523 | + freeze_group.add_argument( |
4524 | + '-f', '--freeze', |
4525 | + choices=['enable', 'disable'], |
4526 | + help=("Enables or disables freezing of LXC/LXD containers when not in use." |
4527 | + " When disabled, the container will stop.")) |
4528 | + |
4529 | parser_configure.set_defaults(func=container_manager.configure) |
4530 | |
4531 | # Handle merging another ContainersConfig.json file into the main ContainersConfig.json file |
4532 | @@ -582,6 +626,16 @@ |
4533 | help=("Clear the default container.")) |
4534 | parser_default.set_defaults(func=container_manager.set_default) |
4535 | |
4536 | + # Handle the restart command and its options |
4537 | + parser_update = subparsers.add_parser( |
4538 | + 'restart', |
4539 | + help=("Restart a frozen Libertine container. This only works on LXC " |
4540 | + "and LXD type containers.")) |
4541 | + parser_update.add_argument( |
4542 | + '-i', '--id', |
4543 | + help=("Container identifier. Default container is used if omitted.")) |
4544 | + parser_update.set_defaults(func=container_manager.restart) |
4545 | + |
4546 | # Actually parse the args |
4547 | args = parser.parse_args() |
4548 | |
4549 | |
4550 | === modified file 'tools/libertine-container-manager.1' |
4551 | --- tools/libertine-container-manager.1 2017-01-12 20:46:05 +0000 |
4552 | +++ tools/libertine-container-manager.1 2017-02-09 16:08:46 +0000 |
4553 | @@ -1,4 +1,4 @@ |
4554 | -.TH libertine-container-manager "1" " April 2016" "libertine-container-manager 0.99" "User Commands" |
4555 | +.TH libertine-container-manager "1" " February 2017" "libertine-container-manager 1.5.2" "User Commands" |
4556 | |
4557 | .SH NAME |
4558 | libertine-container-manager \- Manage Libertine containers for supporting legacy X applications on Unity 8 |
4559 | @@ -68,6 +68,9 @@ |
4560 | .TP |
4561 | .B libertine-container-manager set-default [options] |
4562 | Sets default container. |
4563 | +.TP |
4564 | +.B libertine-container-manager restart [options] |
4565 | +Restarts a frozen LXC or LXD Libertine container. |
4566 | |
4567 | .SH COMMAND REFERENCE |
4568 | .TP |
4569 | @@ -275,7 +278,7 @@ |
4570 | Enable i386 support. |
4571 | .RE |
4572 | .IP |
4573 | -.BR \-a " {add,remove}, " \-\-archive "{add,remove}" "" |
4574 | +.BR \-a " {add,remove}, " \-\-archive " {add,remove}" "" |
4575 | .RS 14 |
4576 | Adds or removes an archive in the specified container. |
4577 | .RE |
4578 | @@ -290,7 +293,7 @@ |
4579 | File containing public key used to sign new archive. |
4580 | .RE |
4581 | .IP |
4582 | -.BR \-b " {add,remove}, " \-\-bind-mount "{add,remove}" "" |
4583 | +.BR \-b " {add,remove}, " \-\-bind-mount " {add,remove}" "" |
4584 | .RS 14 |
4585 | Adds or removes a bind-mount in the specified container. |
4586 | .RE |
4587 | @@ -299,6 +302,11 @@ |
4588 | .RS 14 |
4589 | Absolute host path to be added or removed. |
4590 | .RE |
4591 | +.IP |
4592 | +.BR \-f " {enable,disable}, " \-\-freeze " {enable,disable}" "" |
4593 | +.RS 14 |
4594 | +Enable or disable freezing LXC/LXD containers when not in use. |
4595 | +.RE |
4596 | .TP |
4597 | |
4598 | .B libertine-container-manager set-default [options] |
4599 | @@ -319,6 +327,20 @@ |
4600 | Clear default container. |
4601 | .RE |
4602 | .TP |
4603 | + |
4604 | +.B libertine-container-manager restart [options] |
4605 | +.TP |
4606 | +.SS Options: |
4607 | +.BR \-h ", " \-\-help "" |
4608 | +.RS 14 |
4609 | +Prints help for this command and exits. |
4610 | +.RE |
4611 | +.IP |
4612 | +.BR \-i " ID, " \-\-id " ID" "" |
4613 | +.RS 14 |
4614 | +Container identifier. |
4615 | +.RE |
4616 | +.TP |
4617 | .BR |
4618 | |
4619 | .SH ENVIRONMENT VARIABLES |
4620 | |
4621 | === removed file 'tools/libertine-lxc-manager' |
4622 | --- tools/libertine-lxc-manager 2017-01-12 16:09:37 +0000 |
4623 | +++ tools/libertine-lxc-manager 1970-01-01 00:00:00 +0000 |
4624 | @@ -1,109 +0,0 @@ |
4625 | -#!/usr/bin/python3 |
4626 | -# -*- coding: utf-8 -*- |
4627 | - |
4628 | -# Copyright (C) 2016 Canonical Ltd. |
4629 | -# Author: Christopher Townsend <christopher.townsend@canonical.com> |
4630 | - |
4631 | -# This program is free software: you can redistribute it and/or modify |
4632 | -# it under the terms of the GNU General Public License as published by |
4633 | -# the Free Software Foundation; version 3 of the License. |
4634 | -# |
4635 | -# This program is distributed in the hope that it will be useful, |
4636 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
4637 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
4638 | -# GNU General Public License for more details. |
4639 | -# |
4640 | -# You should have received a copy of the GNU General Public License |
4641 | -# along with this program. If not, see <http://www.gnu.org/licenses/>. |
4642 | - |
4643 | -import libertine.LxcContainer |
4644 | -import libertine.utils |
4645 | -import os |
4646 | -import shlex |
4647 | -import subprocess |
4648 | - |
4649 | -from libertine.ContainersConfig import ContainersConfig |
4650 | -from libertine.lifecycle import * |
4651 | - |
4652 | - |
4653 | -LIBERTINE_LXC_MANAGER_NAME = libertine.LxcContainer.get_lxc_manager_dbus_name() |
4654 | -LIBERTINE_LXC_MANAGER_PATH = libertine.LxcContainer.get_lxc_manager_dbus_path() |
4655 | - |
4656 | - |
4657 | -class Service(ContainerLifecycleService): |
4658 | - |
4659 | - def __init__(self): |
4660 | - super().__init__(LIBERTINE_LXC_MANAGER_NAME, LIBERTINE_LXC_MANAGER_PATH) |
4661 | - self._home = os.environ['HOME'] |
4662 | - self._containers_config = ContainersConfig() |
4663 | - self._is_pulse_setup = False |
4664 | - |
4665 | - def start(self, container_id, launchable): |
4666 | - container = libertine.LxcContainer.lxc_container(container_id) |
4667 | - |
4668 | - if not container.defined: |
4669 | - return LifecycleResult("Container {} is not valid".format(container_id)) |
4670 | - |
4671 | - if launchable and not self._is_pulse_setup: |
4672 | - self._setup_pulse() |
4673 | - |
4674 | - if not container.running: |
4675 | - if launchable: |
4676 | - self._dynamic_bind_mounts(container, container_id) |
4677 | - |
4678 | - return libertine.LxcContainer.lxc_start(container) |
4679 | - |
4680 | - return LifecycleResult() |
4681 | - |
4682 | - def stop(self, container_id, options={}): |
4683 | - container = libertine.LxcContainer.lxc_container(container_id) |
4684 | - libertine.LxcContainer.lxc_stop(container) |
4685 | - |
4686 | - return LifecycleResult() # no error case |
4687 | - |
4688 | - def _setup_pulse(self): |
4689 | - pulse_socket_path = os.path.join(libertine.utils.get_libertine_runtime_dir(), 'pulse_socket') |
4690 | - |
4691 | - lsof_cmd = 'lsof -n %s' % pulse_socket_path |
4692 | - args = shlex.split(lsof_cmd) |
4693 | - lsof = subprocess.Popen(args, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL) |
4694 | - lsof.wait() |
4695 | - |
4696 | - if not os.path.exists(pulse_socket_path) or lsof.returncode == 1: |
4697 | - pactl_cmd = ( |
4698 | - 'pactl load-module module-native-protocol-unix auth-anonymous=1 socket=%s' |
4699 | - % pulse_socket_path) |
4700 | - args = shlex.split(pactl_cmd) |
4701 | - subprocess.Popen(args).wait() |
4702 | - |
4703 | - self.is_pulse_setup = True |
4704 | - |
4705 | - def _dynamic_bind_mounts(self, container, container_id): |
4706 | - self._containers_config.refresh_database() |
4707 | - mounts = self._sanitize_bind_mounts(libertine.utils.get_common_xdg_user_directories() + \ |
4708 | - self._containers_config.get_container_bind_mounts(container_id)) |
4709 | - |
4710 | - data_dir = libertine.utils.get_libertine_container_userdata_dir_path(container_id) |
4711 | - for user_dir in libertine.utils.generate_binding_directories(mounts, self._home): |
4712 | - if os.path.isabs(user_dir[1]): |
4713 | - path = user_dir[1].strip('/') |
4714 | - fullpath = os.path.join(libertine.utils.get_libertine_container_rootfs_path(container_id), path) |
4715 | - else: |
4716 | - path = "{}/{}".format(self._home.strip('/'), user_dir[1]) |
4717 | - fullpath = os.path.join(data_dir, user_dir[1]) |
4718 | - |
4719 | - os.makedirs(fullpath, exist_ok=True) |
4720 | - |
4721 | - libertine.utils.get_logger().debug("Mounting {}:{} in container {}".format(user_dir[0], path, container_id)) |
4722 | - xdg_user_dir_entry = ( |
4723 | - "%s %s none bind,create=dir,optional" |
4724 | - % (user_dir[0], path) |
4725 | - ) |
4726 | - container.append_config_item("lxc.mount.entry", xdg_user_dir_entry) |
4727 | - |
4728 | - def _sanitize_bind_mounts(self, mounts): |
4729 | - return [mount.replace(" ", "\\040") for mount in mounts] |
4730 | - |
4731 | - |
4732 | -if __name__ == '__main__': |
4733 | - ContainerLifecycleServiceRunner(Service()).run() |
4734 | |
4735 | === removed file 'tools/libertine-lxc-manager.1' |
4736 | --- tools/libertine-lxc-manager.1 2016-04-06 12:31:26 +0000 |
4737 | +++ tools/libertine-lxc-manager.1 1970-01-01 00:00:00 +0000 |
4738 | @@ -1,9 +0,0 @@ |
4739 | -.TH libertine-lxc-manager "1" "April 2016" "libertine-lxc-manager 0.99" "User Commands" |
4740 | - |
4741 | -.SH NAME |
4742 | -libertine-lxc-manager \- monitors LXC activity performed by libertine |
4743 | - |
4744 | -.SH DESCRIPTION |
4745 | -usage: libertine\-lxc\-manager |
4746 | -.PP |
4747 | -monitors LXC activity performed by libertine |
4748 | |
4749 | === removed file 'tools/libertine-lxd-manager' |
4750 | --- tools/libertine-lxd-manager 2017-01-12 16:09:37 +0000 |
4751 | +++ tools/libertine-lxd-manager 1970-01-01 00:00:00 +0000 |
4752 | @@ -1,57 +0,0 @@ |
4753 | -#!/usr/bin/python3 |
4754 | -# -*- coding: utf-8 -*- |
4755 | - |
4756 | -# Copyright (C) 2016 Canonical Ltd. |
4757 | - |
4758 | -# This program is free software: you can redistribute it and/or modify |
4759 | -# it under the terms of the GNU General Public License as published by |
4760 | -# the Free Software Foundation; version 3 of the License. |
4761 | -# |
4762 | -# This program is distributed in the hope that it will be useful, |
4763 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
4764 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
4765 | -# GNU General Public License for more details. |
4766 | -# |
4767 | -# You should have received a copy of the GNU General Public License |
4768 | -# along with this program. If not, see <http://www.gnu.org/licenses/>. |
4769 | - |
4770 | -import libertine.LxdContainer |
4771 | -import libertine.utils |
4772 | -import pylxd |
4773 | - |
4774 | -from libertine.ContainersConfig import ContainersConfig |
4775 | -from libertine.lifecycle import * |
4776 | - |
4777 | - |
4778 | -LIBERTINE_LXD_MANAGER_NAME = libertine.LxdContainer.get_lxd_manager_dbus_name() |
4779 | -LIBERTINE_LXD_MANAGER_PATH = libertine.LxdContainer.get_lxd_manager_dbus_path() |
4780 | - |
4781 | - |
4782 | -class Service(ContainerLifecycleService): |
4783 | - |
4784 | - def __init__(self): |
4785 | - super().__init__(LIBERTINE_LXD_MANAGER_NAME, LIBERTINE_LXD_MANAGER_PATH) |
4786 | - self._config = ContainersConfig() |
4787 | - self._client = pylxd.Client() |
4788 | - |
4789 | - def start(self, container_id, launchable): |
4790 | - container = libertine.LxdContainer.lxd_container(self._client, container_id) |
4791 | - |
4792 | - if not container: |
4793 | - return LifecycleResult("Container {} is not valid".format(container_id)) |
4794 | - |
4795 | - if container.status != 'Running': |
4796 | - libertine.LxdContainer.update_libertine_profile(self._client) |
4797 | - if launchable: |
4798 | - libertine.LxdContainer.update_bind_mounts(container, self._config) |
4799 | - |
4800 | - return libertine.LxdContainer.lxd_start(container) |
4801 | - |
4802 | - return LifecycleResult() |
4803 | - |
4804 | - def stop(self, container_id, options={}): |
4805 | - return libertine.LxdContainer.lxd_stop(libertine.LxdContainer.lxd_container(self._client, container_id), options.get('wait', False)) |
4806 | - |
4807 | - |
4808 | -if __name__ == '__main__': |
4809 | - ContainerLifecycleServiceRunner(Service()).run() |
4810 | |
4811 | === removed file 'tools/libertine-lxd-manager.1' |
4812 | --- tools/libertine-lxd-manager.1 2016-12-21 20:57:15 +0000 |
4813 | +++ tools/libertine-lxd-manager.1 1970-01-01 00:00:00 +0000 |
4814 | @@ -1,9 +0,0 @@ |
4815 | -.TH libertine-lxd-manager "1" "Dec 2016" "libertine-lxd-manager 0.99" "User Commands" |
4816 | - |
4817 | -.SH NAME |
4818 | -libertine-lxd-manager \- monitors LXD activity performed by libertine |
4819 | - |
4820 | -.SH DESCRIPTION |
4821 | -usage: libertine\-lxd\-manager |
4822 | -.PP |
4823 | -monitors LXD activity performed by libertine |
4824 | |
4825 | === modified file 'tools/libertine-lxd-setup' |
4826 | --- tools/libertine-lxd-setup 2017-01-05 15:25:21 +0000 |
4827 | +++ tools/libertine-lxd-setup 2017-02-09 16:08:46 +0000 |
4828 | @@ -32,7 +32,7 @@ |
4829 | fi |
4830 | |
4831 | # Run lxd init if there are no containers already on the system |
4832 | -if [ 3 -ge `${lxc} list | wc -l` ]; then |
4833 | +if [ 2 -ge `${lxc} list | grep -e "^+" | wc -l` ]; then |
4834 | lxd init |
4835 | fi |
4836 | |
4837 | |
4838 | === modified file 'tools/libertine-xmir' |
4839 | --- tools/libertine-xmir 2016-02-18 15:49:20 +0000 |
4840 | +++ tools/libertine-xmir 2017-02-09 16:08:46 +0000 |
4841 | @@ -1,5 +1,5 @@ |
4842 | #!/bin/sh |
4843 | -# Copyright (C) 2016 Canonical Ltd. |
4844 | +# Copyright (C) 2016-2017 Canonical Ltd. |
4845 | # Author: Christopher Townsend <christopher.townsend@canonical.com> |
4846 | |
4847 | # This program is free software: you can redistribute it and/or modify |
4848 | @@ -16,4 +16,4 @@ |
4849 | |
4850 | |
4851 | # Add and/or remove Xmir options here |
4852 | -exec Xmir -title @ $@ |
4853 | +exec Xmir -rootless $@ |
Sane.