Merge lp:~townsend/libertine/1.4.3-release into lp:libertine/trunk
- 1.4.3-release
- Merge into trunk
Status: | Merged | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Approved by: | Larry Price | ||||||||||||||||
Approved revision: | 174 | ||||||||||||||||
Merged at revision: | 173 | ||||||||||||||||
Proposed branch: | lp:~townsend/libertine/1.4.3-release | ||||||||||||||||
Merge into: | lp:libertine/trunk | ||||||||||||||||
Diff against target: |
5305 lines (+2656/-1293) 77 files modified
.bzrignore (+2/-0) CMakeLists.txt (+12/-5) common/CMakeLists.txt (+13/-0) common/ContainerAppsList.cpp (+4/-2) common/ContainerAppsList.h (+2/-3) common/ContainerArchivesList.cpp (+4/-2) common/ContainerArchivesList.h (+0/-2) common/ContainerConfig.cpp (+2/-2) common/ContainerConfigList.cpp (+6/-7) common/ContainerManager.cpp (+12/-1) common/LibertineConfig.cpp (+2/-27) common/LibertineConfig.h (+3/-7) common/PackageOperationDetails.cpp (+78/-0) common/PackageOperationDetails.h (+43/-0) data/CMakeLists.txt (+1/-1) data/libertine-manager-app.desktop (+2/-2) debian/changelog (+7/-0) debian/control (+66/-7) debian/libertine-manager-app.install (+3/-4) debian/libertine-qt-common.install (+2/-0) debian/libertine-tools.install (+1/-4) debian/libertine-xmir-tools.install (+3/-0) debian/python3-libertine-chroot.install (+1/-1) debian/python3-libertine-lxc.install (+3/-3) debian/python3-libertine.install (+3/-2) debian/ubuntu-system-settings-libertine.install (+4/-0) libertine/CMakeLists.txt (+10/-5) libertine/config.h.in (+1/-1) libertine/libertine.cpp (+27/-14) libertine/libertine.h (+2/-0) liblibertine/CMakeLists.txt (+12/-11) liblibertine/libertine.cpp (+35/-28) python/libertine/ChrootContainer.py (+14/-7) python/libertine/ContainersConfig.py (+15/-0) python/libertine/Libertine.py (+55/-281) python/libertine/LxcContainer.py (+93/-65) python/libertine/__init__.py (+2/-11) python/libertine/launcher/__init__.py (+28/-0) python/libertine/launcher/config.py (+297/-0) python/libertine/launcher/session.py (+358/-0) python/libertine/launcher/task.py (+75/-0) python/libertine/utils.py (+21/-10) qml/CMakeLists.txt (+3/-0) qml/common/AddExtraArchiveView.qml (+4/-2) qml/common/ContainerEditView.qml (+7/-8) qml/common/ContainerInfoView.qml (+5/-7) qml/common/ContainerOptionsDialog.qml (+5/-5) qml/common/ContainersList.qml (+131/-0) qml/common/ExtraArchivesView.qml (+1/-1) qml/common/ManageContainer.qml (+12/-10) qml/common/PackageInfoView.qml (+13/-13) qml/common/SearchPackagesDialog.qml (+3/-2) qml/common/SearchResults.qml (+3/-1) qml/common/SearchResultsView.qml (+11/-9) qml/gui/ContainersView.qml (+8/-103) qml/gui/WelcomeView.qml (+3/-2) qml/gui/libertine.qml (+10/-68) qml/plugin/MainSettingsPage.qml (+58/-0) system-settings-plugin/CMakeLists.txt (+38/-0) system-settings-plugin/config.h.in (+16/-0) system-settings-plugin/libertine.settings.in (+14/-0) system-settings-plugin/plugin.cpp (+105/-0) system-settings-plugin/plugin.h (+34/-0) tests/unit/CMakeLists.txt (+10/-55) tests/unit/ContainerConfigListTests.cpp (+1/-1) tests/unit/ContainerConfigTests.cpp (+1/-1) tests/unit/libertine_public_gir_tests.py (+0/-63) tests/unit/libertine_session_bridge_tests.py (+0/-106) tests/unit/libertine_socket_tests.py (+0/-72) tests/unit/test_launcher.py (+563/-52) tests/unit/test_launcher_with_dbus.py (+103/-0) tests/unit/test_libertine_gir.py (+67/-0) tests/unit/test_logger.py (+16/-20) tools/libertine-container-manager (+4/-0) tools/libertine-launch (+20/-139) tools/libertine-lxc-manager (+62/-22) tools/update-puritine-containers (+1/-16) |
||||||||||||||||
To merge this branch: | bzr merge lp:~townsend/libertine/1.4.3-release | ||||||||||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Larry Price | Approve | ||
Review via email: mp+309700@code.launchpad.net |
Commit message
* Use libertine-
* Add a new entry to ContainersConfig for keeping track of running processes in a container. (LP: #1632729)
* libertine-launch: refactored core components of application session management.
* Split out Xmir helper apps into their own package.
* Add support for starting pasted and matchbox for snap based/ containerless apps in U8. (LP: #1637535)
* Add implementation for libertine gui as a system-settings plugin. (LP: #1623946)
* Manually require archive name during archive configuration.
* Add a warning to the top of ContainersConfi
* Missing error dialogs in PackageInfoView when fetching version results in an error.
* Avoid searching for apps based on empty directories.
* Bump version to 1.4.3.
Description of the change
- 173. By Christopher Townsend
-
Merge in lp:libertine for fixes.
- 174. By Christopher Townsend
-
Set cmake project version to 1.4.3.
- 175. By Christopher Townsend
-
Merge lp:libertine for an additional fix.
- 176. By Christopher Townsend
-
Add arm64 to xmir and restrict the archs the libertine-
xmir-package is built for. - 177. By Christopher Townsend
-
Move libertine-
xmir-tools to Recommends instead of Depends for libertine-tools. - 178. By Christopher Townsend
-
Set libertine package breaks/replaces to less then 1.4.3.
- 179. By Christopher Townsend
-
Fix old libertine package breaks/replaces version for proper upgrading.
- 180. By Christopher Townsend
-
Merge lp:libertine/trunk.
Preview Diff
1 | === modified file '.bzrignore' |
2 | --- .bzrignore 2016-02-29 18:07:02 +0000 |
3 | +++ .bzrignore 2016-11-21 19:42:42 +0000 |
4 | @@ -1,2 +1,4 @@ |
5 | po/POTFILES.in |
6 | po/Makefile.in.in |
7 | +__pycache__ |
8 | +tests/unit/.cache |
9 | |
10 | === modified file 'CMakeLists.txt' |
11 | --- CMakeLists.txt 2016-09-13 18:17:52 +0000 |
12 | +++ CMakeLists.txt 2016-11-21 19:42:42 +0000 |
13 | @@ -1,8 +1,8 @@ |
14 | cmake_minimum_required(VERSION 3.0.2) |
15 | cmake_policy(SET CMP0048 NEW) |
16 | |
17 | -project(libertine |
18 | - VERSION 1.4.1) |
19 | +project(libertine |
20 | + VERSION 1.4.3) |
21 | |
22 | set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake" "${CMAKE_MODULE_PATH}") |
23 | |
24 | @@ -32,13 +32,21 @@ |
25 | |
26 | set(CMAKE_AUTOMOC ON) |
27 | |
28 | +set(LIBERTINE_COMMON libertine-common) |
29 | +set(LIBERTINE_CORE libertine) |
30 | +set(LIBERTINE_EXE_NAME libertine-manager-app) |
31 | +set(LIBERTINE_QML_PATH ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/${CMAKE_PROJECT_NAME}/qml) |
32 | + |
33 | +add_subdirectory(qml) |
34 | +add_subdirectory(liblibertine) |
35 | +add_subdirectory(common) |
36 | add_subdirectory(libertine) |
37 | +add_subdirectory(system-settings-plugin) |
38 | add_subdirectory(python) |
39 | add_subdirectory(data) |
40 | add_subdirectory(tools) |
41 | -add_subdirectory(liblibertine) |
42 | +add_subdirectory(pasted) |
43 | add_subdirectory(po) |
44 | -add_subdirectory(pasted) |
45 | |
46 | include(CTest) |
47 | add_subdirectory(tests) |
48 | @@ -50,4 +58,3 @@ |
49 | | xz >${CMAKE_BINARY_DIR}/${ARCHIVE_NAME}.tar.xz |
50 | && rm .gitattributes |
51 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) |
52 | - |
53 | |
54 | === added directory 'common' |
55 | === added file 'common/CMakeLists.txt' |
56 | --- common/CMakeLists.txt 1970-01-01 00:00:00 +0000 |
57 | +++ common/CMakeLists.txt 2016-11-21 19:42:42 +0000 |
58 | @@ -0,0 +1,13 @@ |
59 | +add_library(${LIBERTINE_COMMON} |
60 | + ContainerAppsList.cpp |
61 | + ContainerArchivesList.cpp |
62 | + ContainerConfig.cpp |
63 | + ContainerConfigList.cpp |
64 | + ContainerManager.cpp |
65 | + LibertineConfig.cpp |
66 | + PackageOperationDetails.cpp |
67 | +) |
68 | + |
69 | +target_link_libraries(${LIBERTINE_COMMON} ${LIBERTINE_CORE} Qt5::Core) |
70 | + |
71 | +install(TARGETS ${LIBERTINE_COMMON_NAME} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) |
72 | |
73 | === renamed file 'libertine/ContainerAppsList.cpp' => 'common/ContainerAppsList.cpp' |
74 | --- libertine/ContainerAppsList.cpp 2016-08-12 19:39:50 +0000 |
75 | +++ common/ContainerAppsList.cpp 2016-11-21 19:42:42 +0000 |
76 | @@ -16,8 +16,10 @@ |
77 | * You should have received a copy of the GNU General Public License |
78 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
79 | */ |
80 | -#include "libertine/ContainerAppsList.h" |
81 | -#include "libertine/ContainerConfigList.h" |
82 | +#include "common/ContainerAppsList.h" |
83 | + |
84 | +#include "common/ContainerConfig.h" |
85 | +#include "common/ContainerConfigList.h" |
86 | |
87 | ContainerAppsList:: |
88 | ContainerAppsList(ContainerConfigList* container_config_list, |
89 | |
90 | === renamed file 'libertine/ContainerAppsList.h' => 'common/ContainerAppsList.h' |
91 | --- libertine/ContainerAppsList.h 2016-08-12 19:39:50 +0000 |
92 | +++ common/ContainerAppsList.h 2016-11-21 19:42:42 +0000 |
93 | @@ -3,7 +3,7 @@ |
94 | * @brief Libertine Manager list of container applications |
95 | */ |
96 | /* |
97 | - * Copyright 2015 Canonical Ltd |
98 | + * Copyright 2015-2016 Canonical Ltd |
99 | * |
100 | * Libertine is free software: you can redistribute it and/or modify it under |
101 | * the terms of the GNU General Public License, version 3, as published by the |
102 | @@ -19,8 +19,6 @@ |
103 | #ifndef _CONTAINER_APPS_LIST_H_ |
104 | #define _CONTAINER_APPS_LIST_H_ |
105 | |
106 | -#include "libertine/ContainerConfig.h" |
107 | - |
108 | #include <QtCore/QAbstractListModel> |
109 | #include <QtCore/QList> |
110 | #include <QtCore/QObject> |
111 | @@ -30,6 +28,7 @@ |
112 | class ContainerApps; |
113 | class ContainerConfigList; |
114 | |
115 | + |
116 | class ContainerAppsList |
117 | : public QAbstractListModel |
118 | { |
119 | |
120 | === renamed file 'libertine/ContainerArchivesList.cpp' => 'common/ContainerArchivesList.cpp' |
121 | --- libertine/ContainerArchivesList.cpp 2016-08-12 19:39:50 +0000 |
122 | +++ common/ContainerArchivesList.cpp 2016-11-21 19:42:42 +0000 |
123 | @@ -16,8 +16,10 @@ |
124 | * You should have received a copy of the GNU General Public License |
125 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
126 | */ |
127 | -#include "libertine/ContainerArchivesList.h" |
128 | -#include "libertine/ContainerConfigList.h" |
129 | +#include "common/ContainerArchivesList.h" |
130 | + |
131 | +#include "common/ContainerConfig.h" |
132 | +#include "common/ContainerConfigList.h" |
133 | |
134 | ContainerArchivesList:: |
135 | ContainerArchivesList(ContainerConfigList* container_config_list, |
136 | |
137 | === renamed file 'libertine/ContainerArchivesList.h' => 'common/ContainerArchivesList.h' |
138 | --- libertine/ContainerArchivesList.h 2016-08-12 19:39:50 +0000 |
139 | +++ common/ContainerArchivesList.h 2016-11-21 19:42:42 +0000 |
140 | @@ -19,8 +19,6 @@ |
141 | #ifndef _CONTAINER_ARCHIVES_LIST_H_ |
142 | #define _CONTAINER_ARCHIVES_LIST_H_ |
143 | |
144 | -#include "libertine/ContainerConfig.h" |
145 | - |
146 | #include <QtCore/QAbstractListModel> |
147 | #include <QtCore/QList> |
148 | #include <QtCore/QObject> |
149 | |
150 | === renamed file 'libertine/ContainerConfig.cpp' => 'common/ContainerConfig.cpp' |
151 | --- libertine/ContainerConfig.cpp 2016-04-13 18:02:56 +0000 |
152 | +++ common/ContainerConfig.cpp 2016-11-21 19:42:42 +0000 |
153 | @@ -16,7 +16,7 @@ |
154 | * You should have received a copy of the GNU General Public License |
155 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
156 | */ |
157 | -#include "libertine/ContainerConfig.h" |
158 | +#include "common/ContainerConfig.h" |
159 | |
160 | #include <QtCore/QDebug> |
161 | #include <QtCore/QJsonArray> |
162 | @@ -73,7 +73,7 @@ |
163 | } |
164 | } |
165 | } |
166 | - |
167 | + |
168 | return name; |
169 | } |
170 | |
171 | |
172 | === renamed file 'libertine/ContainerConfig.h' => 'common/ContainerConfig.h' |
173 | === renamed file 'libertine/ContainerConfigList.cpp' => 'common/ContainerConfigList.cpp' |
174 | --- libertine/ContainerConfigList.cpp 2016-08-22 18:20:43 +0000 |
175 | +++ common/ContainerConfigList.cpp 2016-11-21 19:42:42 +0000 |
176 | @@ -16,12 +16,11 @@ |
177 | * You should have received a copy of the GNU General Public License |
178 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
179 | */ |
180 | -#include "libertine/ContainerManager.h" |
181 | -#include "libertine/ContainerConfigList.h" |
182 | -#include "libertine/LibertineConfig.h" |
183 | +#include "common/ContainerConfigList.h" |
184 | |
185 | +#include "common/ContainerConfig.h" |
186 | +#include "common/LibertineConfig.h" |
187 | #include <algorithm> |
188 | -#include "libertine/ContainerConfig.h" |
189 | #include <QtCore/QDebug> |
190 | #include <QtCore/QDir> |
191 | #include <QtCore/QFile> |
192 | @@ -95,7 +94,7 @@ |
193 | reloadContainerList() |
194 | { |
195 | beginResetModel(); |
196 | - endResetModel(); |
197 | + endResetModel(); |
198 | } |
199 | |
200 | |
201 | @@ -128,7 +127,7 @@ |
202 | deleteContainer() |
203 | { |
204 | beginResetModel(); |
205 | - endResetModel(); |
206 | + endResetModel(); |
207 | } |
208 | |
209 | |
210 | @@ -360,7 +359,7 @@ |
211 | getHostDistroDescription() |
212 | { |
213 | QSettings distro_info("/etc/lsb-release", QSettings::NativeFormat); |
214 | - |
215 | + |
216 | return distro_info.value("DISTRIB_DESCRIPTION").toString().section(' ', 0, 2); |
217 | } |
218 | |
219 | |
220 | === renamed file 'libertine/ContainerConfigList.h' => 'common/ContainerConfigList.h' |
221 | === renamed file 'libertine/ContainerManager.cpp' => 'common/ContainerManager.cpp' |
222 | --- libertine/ContainerManager.cpp 2016-09-30 18:55:22 +0000 |
223 | +++ common/ContainerManager.cpp 2016-11-21 19:42:42 +0000 |
224 | @@ -16,7 +16,8 @@ |
225 | * You should have received a copy of the GNU General Public License |
226 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
227 | */ |
228 | -#include "libertine/ContainerManager.h" |
229 | +#include "common/ContainerManager.h" |
230 | + |
231 | #include <QTemporaryFile> |
232 | |
233 | |
234 | @@ -32,6 +33,7 @@ |
235 | static const QString RUN_COMMAND_FAILED = QObject::tr("Running command %1 failed"); |
236 | static const QString CONTAINER_CONFIGURE_FAILED = QObject::tr("Attempt to configure container %1 failed"); |
237 | static const QString SET_DEFAULT_CONTAINER_FAILED = QObject::tr("Attempt to set container as default failed"); |
238 | +static const QString GENERAL_ERROR = QObject::tr("An error occurred"); |
239 | constexpr auto libertine_container_manager_tool = "libertine-container-manager"; |
240 | |
241 | |
242 | @@ -66,6 +68,15 @@ |
243 | ContainerManagerWorker() |
244 | { |
245 | connect(&process_, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, &QObject::deleteLater); |
246 | + connect(&process_, |
247 | +#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) |
248 | + &QProcess::errorOccurred, |
249 | +#else |
250 | + static_cast<void(QProcess::*)(QProcess::ProcessError)>(&QProcess::error), |
251 | +#endif |
252 | + [=](QProcess::ProcessError) { |
253 | + emit error(GENERAL_ERROR, process_.errorString()); |
254 | + }); |
255 | } |
256 | |
257 | |
258 | |
259 | === renamed file 'libertine/ContainerManager.h' => 'common/ContainerManager.h' |
260 | === renamed file 'libertine/LibertineConfig.cpp' => 'common/LibertineConfig.cpp' |
261 | --- libertine/LibertineConfig.cpp 2016-04-06 19:14:10 +0000 |
262 | +++ common/LibertineConfig.cpp 2016-11-21 19:42:42 +0000 |
263 | @@ -16,38 +16,13 @@ |
264 | * You should have received a copy of the GNU General Public License |
265 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
266 | */ |
267 | -#include "libertine/LibertineConfig.h" |
268 | +#include "common/LibertineConfig.h" |
269 | |
270 | -#include "libertine/libertine.h" |
271 | -#include <QtCore/QCommandLineParser> |
272 | #include <QtCore/QDir> |
273 | #include <QtCore/QFile> |
274 | #include <QtCore/QStandardPaths> |
275 | |
276 | |
277 | -LibertineConfig:: |
278 | -LibertineConfig(Libertine const& libertine) |
279 | -{ |
280 | - QCommandLineParser commandlineParser; |
281 | - commandlineParser.setApplicationDescription("manage sandboxes for running legacy DEB-packaged X11-based applications"); |
282 | - commandlineParser.addHelpOption(); |
283 | - commandlineParser.addVersionOption(); |
284 | - commandlineParser.process(libertine); |
285 | -} |
286 | - |
287 | - |
288 | -LibertineConfig:: |
289 | -LibertineConfig() |
290 | -{ |
291 | -} |
292 | - |
293 | - |
294 | -LibertineConfig:: |
295 | -~LibertineConfig() |
296 | -{ |
297 | -} |
298 | - |
299 | - |
300 | QString LibertineConfig:: |
301 | containers_config_file_name() const |
302 | { |
303 | @@ -67,6 +42,6 @@ |
304 | file.open(QIODevice::WriteOnly); |
305 | file.close(); |
306 | } |
307 | - |
308 | + |
309 | return file_name; |
310 | } |
311 | |
312 | === renamed file 'libertine/LibertineConfig.h' => 'common/LibertineConfig.h' |
313 | --- libertine/LibertineConfig.h 2015-06-22 12:31:13 +0000 |
314 | +++ common/LibertineConfig.h 2016-11-21 19:42:42 +0000 |
315 | @@ -3,7 +3,7 @@ |
316 | * @brief Libertine Manager application-wide configuration module |
317 | */ |
318 | /* |
319 | - * Copyright 2015 Canonical Ltd |
320 | + * Copyright 2015-2016 Canonical Ltd |
321 | * |
322 | * Libertine is free software: you can redistribute it and/or modify it under |
323 | * the terms of the GNU General Public License, version 3, as published by the |
324 | @@ -22,18 +22,14 @@ |
325 | #include <QtCore/QString> |
326 | |
327 | |
328 | -class Libertine; |
329 | - |
330 | - |
331 | /** |
332 | * The runtime configuration of the Libertine tools. |
333 | */ |
334 | class LibertineConfig |
335 | { |
336 | public: |
337 | - LibertineConfig(Libertine const& libertine); |
338 | - LibertineConfig(); |
339 | - ~LibertineConfig(); |
340 | + explicit LibertineConfig() = default; |
341 | + virtual ~LibertineConfig() = default; |
342 | |
343 | QString |
344 | containers_config_file_name() const; |
345 | |
346 | === added file 'common/PackageOperationDetails.cpp' |
347 | --- common/PackageOperationDetails.cpp 1970-01-01 00:00:00 +0000 |
348 | +++ common/PackageOperationDetails.cpp 2016-11-21 19:42:42 +0000 |
349 | @@ -0,0 +1,78 @@ |
350 | +/* |
351 | + * Copyright 2016 Canonical Ltd |
352 | + * |
353 | + * Libertine is free software: you can redistribute it and/or modify it under |
354 | + * the terms of the GNU General Public License, version 3, as published by the |
355 | + * Free Software Foundation. |
356 | + * |
357 | + * Libertine is distributed in the hope that it will be useful, but WITHOUT ANY |
358 | + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR |
359 | + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
360 | + * |
361 | + * You should have received a copy of the GNU General Public License |
362 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
363 | + */ |
364 | + |
365 | + |
366 | +#include "PackageOperationDetails.h" |
367 | + |
368 | +namespace |
369 | +{ |
370 | +static bool has_key(QMap<QString, QMap<QString, QString> > details, |
371 | + QString const& container_id, QString const& package_id) |
372 | +{ |
373 | + return details.constFind(container_id) != details.constEnd() && |
374 | + details[container_id].constFind(package_id) != details[container_id].constEnd(); |
375 | +} |
376 | +} |
377 | + |
378 | + |
379 | +PackageOperationDetails:: |
380 | +PackageOperationDetails(QObject* parent) |
381 | + : QObject(parent) |
382 | +{ |
383 | +} |
384 | + |
385 | + |
386 | +QString PackageOperationDetails:: |
387 | +details(QString const& container_id, QString const& package_id) const |
388 | +{ |
389 | + if (has_key(details_, container_id, package_id)) |
390 | + { |
391 | + return details_[container_id][package_id]; |
392 | + } |
393 | + return ""; |
394 | +} |
395 | + |
396 | + |
397 | +void PackageOperationDetails:: |
398 | +clear(QString const& container_id, QString const& package_id) |
399 | +{ |
400 | + if (has_key(details_, container_id, package_id)) |
401 | + { |
402 | + details_[container_id].remove(package_id); |
403 | + if (details_[container_id].empty()) |
404 | + { |
405 | + details_.remove(container_id); |
406 | + } |
407 | + } |
408 | +} |
409 | + |
410 | + |
411 | +void PackageOperationDetails:: |
412 | +update(QString const& container_id, QString const& package_id, QString const& new_details) |
413 | +{ |
414 | + if (has_key(details_, container_id, package_id)) |
415 | + { |
416 | + details_[container_id][package_id] += new_details; |
417 | + } |
418 | + else |
419 | + { |
420 | + if (details_.constFind(container_id) == details_.constEnd()) |
421 | + { |
422 | + details_[container_id] = QMap<QString, QString>{{package_id, new_details}}; |
423 | + } |
424 | + } |
425 | + |
426 | + emit updated(container_id, package_id, new_details); |
427 | +} |
428 | |
429 | === added file 'common/PackageOperationDetails.h' |
430 | --- common/PackageOperationDetails.h 1970-01-01 00:00:00 +0000 |
431 | +++ common/PackageOperationDetails.h 2016-11-21 19:42:42 +0000 |
432 | @@ -0,0 +1,43 @@ |
433 | +/* |
434 | + * Copyright 2016 Canonical Ltd |
435 | + * |
436 | + * Libertine is free software: you can redistribute it and/or modify it under |
437 | + * the terms of the GNU General Public License, version 3, as published by the |
438 | + * Free Software Foundation. |
439 | + * |
440 | + * Libertine is distributed in the hope that it will be useful, but WITHOUT ANY |
441 | + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR |
442 | + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
443 | + * |
444 | + * You should have received a copy of the GNU General Public License |
445 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
446 | + */ |
447 | + |
448 | +#pragma once |
449 | + |
450 | +#include <QObject> |
451 | +#include <QMap> |
452 | + |
453 | + |
454 | +class PackageOperationDetails : public QObject |
455 | +{ |
456 | + Q_OBJECT |
457 | + |
458 | +public: |
459 | + explicit PackageOperationDetails(QObject* parent = nullptr); |
460 | + virtual ~PackageOperationDetails() = default; |
461 | + |
462 | + Q_INVOKABLE QString details(QString const& container_id, QString const& package_id) const; |
463 | + Q_INVOKABLE void clear(QString const& container_id, QString const& package_id); |
464 | + |
465 | +public slots: |
466 | + void update(QString const& container_id, QString const& package_id, QString const& new_details); |
467 | + |
468 | +signals: |
469 | + void updated(QString const& container_id, QString const& package_id, QString const& new_details); |
470 | + void send(QString const& input); |
471 | + void error(QString const& short_description, QString const& details); |
472 | + |
473 | +private: |
474 | + QMap<QString, QMap<QString, QString> > details_; |
475 | +}; |
476 | |
477 | === modified file 'data/CMakeLists.txt' |
478 | --- data/CMakeLists.txt 2016-09-06 12:36:47 +0000 |
479 | +++ data/CMakeLists.txt 2016-11-21 19:42:42 +0000 |
480 | @@ -1,6 +1,6 @@ |
481 | set(CMAKE_INSTALL_SYSCONFDIR "/etc") |
482 | |
483 | -install(FILES libertine.desktop |
484 | +install(FILES libertine-manager-app.desktop |
485 | DESTINATION ${CMAKE_INSTALL_DATADIR}/applications) |
486 | install(FILES libertine_64.png libertine-lxc.conf |
487 | DESTINATION ${CMAKE_INSTALL_DATADIR}/${CMAKE_PROJECT_NAME}) |
488 | |
489 | === renamed file 'data/libertine.desktop' => 'data/libertine-manager-app.desktop' |
490 | --- data/libertine.desktop 2016-02-20 22:23:41 +0000 |
491 | +++ data/libertine-manager-app.desktop 2016-11-21 19:42:42 +0000 |
492 | @@ -1,8 +1,8 @@ |
493 | [Desktop Entry] |
494 | Version=1.0 |
495 | -Name=Libertine |
496 | +Name=Libertine Manager |
497 | Comment=Legacy Application Sandbox |
498 | -Exec=libertine |
499 | +Exec=libertine-manager-app |
500 | Terminal=false |
501 | Type=Application |
502 | Icon=/usr/share/libertine/libertine_64.png |
503 | |
504 | === modified file 'debian/changelog' |
505 | --- debian/changelog 2016-11-21 19:37:59 +0000 |
506 | +++ debian/changelog 2016-11-21 19:42:42 +0000 |
507 | @@ -1,3 +1,10 @@ |
508 | +libertine (1.4.3-0ubuntu1) UNRELEASED; urgency=medium |
509 | + |
510 | + * Fix old libertine package breaks/replaces version for proper |
511 | + upgrading. |
512 | + |
513 | + -- Chris Townsend <christopher.townsend@canonical.com> Mon, 21 Nov 2016 12:51:42 -0500 |
514 | + |
515 | libertine (1.4.3+17.04.20161118.1-0ubuntu1) zesty; urgency=medium |
516 | |
517 | [ Chris Townsend] |
518 | |
519 | === modified file 'debian/control' |
520 | --- debian/control 2016-09-12 17:59:56 +0000 |
521 | +++ debian/control 2016-11-21 19:42:42 +0000 |
522 | @@ -13,33 +13,74 @@ |
523 | libgirepository1.0-dev, |
524 | libglib2.0-dev, |
525 | libgtest-dev, |
526 | + libsystemsettings-dev, |
527 | libx11-dev, |
528 | lsb-release, |
529 | + pkg-config, |
530 | python3-apt, |
531 | + python3-dbus, |
532 | + python3-dbusmock, |
533 | python3-dev, |
534 | python3-distro-info, |
535 | python3-gi, |
536 | python3-lxc, |
537 | python3-psutil, |
538 | + python3-pytest, |
539 | python3-testtools, |
540 | python3-xdg, |
541 | - python3-dbus, |
542 | qtdeclarative5-dev |
543 | -Standards-Version: 3.9.6 |
544 | +Standards-Version: 3.9.8 |
545 | Homepage: https://launchpad.net/libertine |
546 | |
547 | +Package: libertine-manager-app |
548 | +Architecture: any |
549 | +Replaces: libertine (<= 1.4.3+17.04.20161118.1-0ubuntu1) |
550 | +Breaks: libertine (<= 1.4.3+17.04.20161118.1-0ubuntu1) |
551 | +Depends: libertine-qt-common, |
552 | + libertine-tools, |
553 | + python3-libertine-lxc, |
554 | + ${misc:Depends}, |
555 | + ${shlibs:Depends} |
556 | +Description: sandbox for running deb-packaged X11 apps on Ubuntu Personal |
557 | + Provides a container and related tools to be able to create, maintain, and |
558 | + integrate legacy deb-packaged applications generally based around the X11 |
559 | + display manager on an Ubuntu "Snappy Personal" system. |
560 | + |
561 | Package: libertine |
562 | +Depends: libertine-manager-app, |
563 | + ${misc:Depends} |
564 | +Architecture: all |
565 | +Priority: extra |
566 | +Section: oldlibs |
567 | +Description: transitional dummy package for libertine-manager-app |
568 | + This is a transitional package to rename libertine as libertine-manager-app. |
569 | + It can safely be removed. |
570 | + |
571 | +Package: ubuntu-system-settings-libertine |
572 | Architecture: any |
573 | -Depends: libertine-tools, |
574 | +Depends: libertine-qt-common, |
575 | + libertine-tools, |
576 | python3-libertine-lxc, |
577 | qml-module-qtquick2, |
578 | qtdeclarative5-ubuntu-ui-toolkit-plugin, |
579 | ${misc:Depends}, |
580 | ${shlibs:Depends} |
581 | -Description: sandbox for running deb-packaged X11 apps on Ubuntu Personal |
582 | - Provides a container and releated tools to be able to create, maintain, and |
583 | - integrate legacy deb-packaged applications generally based around the X11 |
584 | - display manager on a Ubuntu "Snappy Personal" system. |
585 | +Enhances: ubuntu-system-settings |
586 | +Description: system settings plugin for managing container-based applications |
587 | + System Settings graphical interface to assist in the maintenance of legacy |
588 | + deb-packaged applications generally based around the X11 display manager on |
589 | + an Ubuntu "Snappy Personal" system. |
590 | + |
591 | +Package: libertine-qt-common |
592 | +Architecture: any |
593 | +Depends: qml-module-qtquick2, |
594 | + qtdeclarative5-ubuntu-ui-toolkit-plugin, |
595 | + ${misc:Depends}, |
596 | + ${shlibs:Depends} |
597 | +Description: common files for qt-based libertine applications |
598 | + Common QML source files and translations for managing libertine containers |
599 | + and packages used in the standalone libertine application and system settings |
600 | + plugin. |
601 | |
602 | Package: libertine-tools |
603 | Architecture: any |
604 | @@ -52,12 +93,30 @@ |
605 | ${misc:Depends}, |
606 | ${python3:Depends}, |
607 | ${shlibs:Depends} |
608 | +Recommends: libertine-xmir-tools (>= 1.4.3) |
609 | Breaks: libertine-demo |
610 | Replaces: libertine-demo |
611 | Description: CLI tools for running deb-packaged X11 apps on Ubuntu Personal |
612 | Command-line tools that can be used for creating, manipulating, and using |
613 | the Ubuntu Personal sandbox for legacy Deb-packaged X11 applicatons. |
614 | |
615 | +Package: libertine-xmir-tools |
616 | +Architecture: amd64 armhf arm64 i386 |
617 | +Depends: libcontent-hub0, |
618 | + libqt5core5a, |
619 | + libqt5gui5, |
620 | + libqt5widgets5, |
621 | + matchbox-window-manager, |
622 | + xmir [amd64 armhf arm64 i386], |
623 | + ${misc:Depends}, |
624 | + ${python3:Depends}, |
625 | + ${shlibs:Depends} |
626 | +Breaks: libertine-tools (<< 1.4.3) |
627 | +Replaces: libertine-tools (<< 1.4.3) |
628 | +Description: helper apps for using and interacting with Xmir |
629 | + Helper applications for using and interacting with Xmir such as launching |
630 | + Xmir and allowing copy and paste. |
631 | + |
632 | Package: liblibertine1 |
633 | Architecture: any |
634 | Multi-Arch: same |
635 | |
636 | === renamed file 'debian/libertine.install' => 'debian/libertine-manager-app.install' |
637 | --- debian/libertine.install 2016-05-02 20:45:21 +0000 |
638 | +++ debian/libertine-manager-app.install 2016-11-21 19:42:42 +0000 |
639 | @@ -1,5 +1,4 @@ |
640 | -usr/bin/libertine |
641 | -usr/share/applications/libertine.desktop |
642 | +usr/bin/libertine-manager-app |
643 | +usr/share/applications/libertine-manager-app.desktop |
644 | usr/share/libertine/libertine_64.png |
645 | -usr/share/libertine/qml/* |
646 | -usr/share/locale/*/LC_MESSAGES |
647 | +usr/share/libertine/qml/gui/* |
648 | |
649 | === added file 'debian/libertine-qt-common.install' |
650 | --- debian/libertine-qt-common.install 1970-01-01 00:00:00 +0000 |
651 | +++ debian/libertine-qt-common.install 2016-11-21 19:42:42 +0000 |
652 | @@ -0,0 +1,2 @@ |
653 | +usr/share/libertine/qml/common/* |
654 | +usr/share/locale/*/LC_MESSAGES |
655 | |
656 | === modified file 'debian/libertine-tools.install' |
657 | --- debian/libertine-tools.install 2016-09-06 12:36:47 +0000 |
658 | +++ debian/libertine-tools.install 2016-11-21 19:42:42 +0000 |
659 | @@ -1,7 +1,4 @@ |
660 | +usr/bin/libertine-container-manager |
661 | usr/bin/libertine-launch |
662 | -usr/bin/libertine-container-manager |
663 | -usr/bin/libertine-xmir |
664 | -usr/bin/pasted |
665 | usr/share/bash-completion/completions/libertine-container-manager |
666 | usr/share/man |
667 | -usr/share/upstart/sessions/libertine-xmir.conf |
668 | |
669 | === added file 'debian/libertine-xmir-tools.install' |
670 | --- debian/libertine-xmir-tools.install 1970-01-01 00:00:00 +0000 |
671 | +++ debian/libertine-xmir-tools.install 2016-11-21 19:42:42 +0000 |
672 | @@ -0,0 +1,3 @@ |
673 | +usr/bin/libertine-xmir |
674 | +usr/bin/pasted |
675 | +usr/share/upstart/sessions/libertine-xmir.conf |
676 | |
677 | === modified file 'debian/python3-libertine-chroot.install' |
678 | --- debian/python3-libertine-chroot.install 2016-09-06 12:36:47 +0000 |
679 | +++ debian/python3-libertine-chroot.install 2016-11-21 19:42:42 +0000 |
680 | @@ -1,2 +1,2 @@ |
681 | +usr/lib/*/libertine/update-puritine-containers |
682 | usr/lib/python*/*/libertine/ChrootContainer.py |
683 | -usr/lib/*/libertine/update-puritine-containers |
684 | |
685 | === modified file 'debian/python3-libertine-lxc.install' |
686 | --- debian/python3-libertine-lxc.install 2016-08-19 13:33:27 +0000 |
687 | +++ debian/python3-libertine-lxc.install 2016-11-21 19:42:42 +0000 |
688 | @@ -1,6 +1,6 @@ |
689 | +etc/sudoers.d/libertine-lxc-sudo |
690 | +usr/bin/libertine-lxc-manager |
691 | +usr/bin/libertine-lxc-setup |
692 | usr/lib/python*/*/libertine/LxcContainer.py |
693 | -usr/bin/libertine-lxc-manager |
694 | usr/share/dbus-1/services/com.canonical.libertine.LxcManager.service |
695 | usr/share/libertine/libertine-lxc.conf |
696 | -etc/sudoers.d/libertine-lxc-sudo |
697 | -usr/bin/libertine-lxc-setup |
698 | |
699 | === modified file 'debian/python3-libertine.install' |
700 | --- debian/python3-libertine.install 2016-06-28 14:30:01 +0000 |
701 | +++ debian/python3-libertine.install 2016-11-21 19:42:42 +0000 |
702 | @@ -1,6 +1,7 @@ |
703 | usr/lib/python*/*/libertine/AppDiscovery.py |
704 | -usr/lib/python*/*/libertine/Libertine.py |
705 | usr/lib/python*/*/libertine/ContainersConfig.py |
706 | usr/lib/python*/*/libertine/HostInfo.py |
707 | +usr/lib/python*/*/libertine/Libertine.py |
708 | +usr/lib/python*/*/libertine/__init__.py |
709 | +usr/lib/python*/*/libertine/launcher |
710 | usr/lib/python*/*/libertine/utils.py |
711 | -usr/lib/python*/*/libertine/__init__.py |
712 | |
713 | === added file 'debian/ubuntu-system-settings-libertine.install' |
714 | --- debian/ubuntu-system-settings-libertine.install 1970-01-01 00:00:00 +0000 |
715 | +++ debian/ubuntu-system-settings-libertine.install 2016-11-21 19:42:42 +0000 |
716 | @@ -0,0 +1,4 @@ |
717 | +usr/lib/*/ubuntu-system-settings/liblibertine-plugin.so* |
718 | +usr/share/libertine/qml/plugin/* |
719 | +usr/share/ubuntu/settings/system/icons/libertine-plugin.png |
720 | +usr/share/ubuntu/settings/system/libertine.settings |
721 | |
722 | === modified file 'libertine/CMakeLists.txt' |
723 | --- libertine/CMakeLists.txt 2015-10-07 18:59:50 +0000 |
724 | +++ libertine/CMakeLists.txt 2016-11-21 19:42:42 +0000 |
725 | @@ -1,14 +1,19 @@ |
726 | configure_file(config.h.in config.h) |
727 | |
728 | -file(GLOB_RECURSE QML_SRC *.qml *.js *.json) |
729 | +file(GLOB_RECURSE QML_SRC |
730 | + ${CMAKE_SOURCE_DIR}/qml/common/*.qml |
731 | + ${CMAKE_SOURCE_DIR}/qml/gui/*.qml |
732 | +) |
733 | set(libertine_SRC |
734 | libertine.cpp |
735 | main.cpp |
736 | - ${QML_SRC} |
737 | + |
738 | + ${QML_SRC} # so the QML files show up in Qt Creator |
739 | ) |
740 | |
741 | -add_executable(libertine ${libertine_SRC}) |
742 | -target_link_libraries(libertine libertine-common Qt5::Core Qt5::Quick Qt5::Gui) |
743 | +add_executable(${LIBERTINE_EXE_NAME} ${libertine_SRC}) |
744 | +target_link_libraries(${LIBERTINE_EXE_NAME} ${LIBERTINE_COMMON} ${LIBERTINE_CORE} Qt5::Core Qt5::Quick Qt5::Gui) |
745 | |
746 | install(DIRECTORY qml DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${CMAKE_PROJECT_NAME}) |
747 | -install(TARGETS libertine RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) |
748 | + |
749 | +install(TARGETS ${LIBERTINE_EXE_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) |
750 | |
751 | === modified file 'libertine/config.h.in' |
752 | --- libertine/config.h.in 2015-06-02 02:33:28 +0000 |
753 | +++ libertine/config.h.in 2016-11-21 19:42:42 +0000 |
754 | @@ -19,6 +19,6 @@ |
755 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
756 | */ |
757 | |
758 | -#define LIBERTINE_APPLICATION_NAME "@PROJECT_NAME@" |
759 | +#define LIBERTINE_APPLICATION_NAME "@LIBERTINE_EXE_NAME@" |
760 | #define LIBERTINE_VERSION "@PROJECT_VERSION@" |
761 | |
762 | |
763 | === modified file 'libertine/libertine.cpp' |
764 | --- libertine/libertine.cpp 2016-10-03 15:54:26 +0000 |
765 | +++ libertine/libertine.cpp 2016-11-21 19:42:42 +0000 |
766 | @@ -3,7 +3,7 @@ |
767 | * @brief Libertine app wrapper |
768 | */ |
769 | /* |
770 | - * Copyright 2015 Canonical Ltd |
771 | + * Copyright 2015-2016 Canonical Ltd |
772 | * |
773 | * Libertine is free software: you can redistribute it and/or modify it under |
774 | * the terms of the GNU General Public License, version 3, as published by the |
775 | @@ -16,16 +16,18 @@ |
776 | * You should have received a copy of the GNU General Public License |
777 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
778 | */ |
779 | +#include "libertine/libertine.h" |
780 | + |
781 | +#include "common/ContainerManager.h" |
782 | +#include "common/ContainerAppsList.h" |
783 | +#include "common/ContainerArchivesList.h" |
784 | +#include "common/ContainerConfig.h" |
785 | +#include "common/ContainerConfigList.h" |
786 | +#include "common/LibertineConfig.h" |
787 | +#include "common/PackageOperationDetails.h" |
788 | #include "libertine/config.h" |
789 | - |
790 | #include <cstdlib> |
791 | -#include "libertine/ContainerManager.h" |
792 | -#include "libertine/ContainerAppsList.h" |
793 | -#include "libertine/ContainerArchivesList.h" |
794 | -#include "libertine/ContainerConfig.h" |
795 | -#include "libertine/ContainerConfigList.h" |
796 | -#include "libertine/libertine.h" |
797 | -#include "libertine/LibertineConfig.h" |
798 | +#include <QtCore/QCommandLineParser> |
799 | #include <QtCore/QDebug> |
800 | #include <QtCore/QDir> |
801 | #include <QtCore/QFile> |
802 | @@ -38,7 +40,7 @@ |
803 | |
804 | namespace |
805 | { |
806 | -static QString const s_main_QML_source_file = "qml/libertine.qml"; |
807 | +static QString const s_main_QML_source_file = "qml/gui/libertine.qml"; |
808 | |
809 | /** |
810 | * Searches for the main QML source file. |
811 | @@ -55,7 +57,7 @@ |
812 | find_main_qml_source_file() |
813 | { |
814 | static const QStringList sub_paths = { "", "libertine/" }; |
815 | - QStringList paths = QStandardPaths::standardLocations(QStandardPaths::DataLocation); |
816 | + auto paths = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); |
817 | paths.prepend(QDir::currentPath()); |
818 | paths.prepend(QCoreApplication::applicationDirPath()); |
819 | for (auto const& path: paths) |
820 | @@ -88,11 +90,20 @@ |
821 | : QGuiApplication(argc, argv) |
822 | , main_qml_source_file_(find_main_qml_source_file()) |
823 | { |
824 | + qmlRegisterType<ContainerConfig>("Libertine", 1, 0, "ContainerConfig"); |
825 | + qmlRegisterType<ContainerManagerWorker>("Libertine", 1, 0, "ContainerManagerWorker"); |
826 | + qmlRegisterType<PackageOperationDetails>("Libertine", 1, 0, "PackageOperationDetails"); |
827 | + |
828 | setApplicationName(LIBERTINE_APPLICATION_NAME); |
829 | setApplicationVersion(LIBERTINE_VERSION); |
830 | - config_.reset(new LibertineConfig(*this)); |
831 | - qmlRegisterType<ContainerConfig>("Libertine", 1, 0, "ContainerConfig"); |
832 | - qmlRegisterType<ContainerManagerWorker>("Libertine", 1, 0, "ContainerManagerWorker"); |
833 | + |
834 | + QCommandLineParser commandlineParser; |
835 | + commandlineParser.setApplicationDescription("manage sandboxes for running legacy DEB-packaged X11-based applications"); |
836 | + commandlineParser.addHelpOption(); |
837 | + commandlineParser.addVersionOption(); |
838 | + commandlineParser.process(*this); |
839 | + |
840 | + config_.reset(new LibertineConfig()); |
841 | |
842 | watcher_.addPath(config_.data()->containers_config_file_name()); |
843 | connect(&watcher_, SIGNAL(fileChanged(QString)), SLOT(reload_config(QString))); |
844 | @@ -105,6 +116,7 @@ |
845 | containers_ = new ContainerConfigList(config_.data(), this); |
846 | container_apps_ = new ContainerAppsList(containers_, this); |
847 | container_archives_ = new ContainerArchivesList(containers_, this); |
848 | + package_operation_details_ = new PackageOperationDetails(this); |
849 | |
850 | initialize_view(); |
851 | view_.show(); |
852 | @@ -131,6 +143,7 @@ |
853 | ctxt->setContextProperty("containerConfigList", containers_); |
854 | ctxt->setContextProperty("containerAppsList", container_apps_); |
855 | ctxt->setContextProperty("containerArchivesList", container_archives_); |
856 | + ctxt->setContextProperty("packageOperationDetails", package_operation_details_); |
857 | |
858 | view_.setSource(QUrl::fromLocalFile(main_qml_source_file_)); |
859 | connect(view_.engine(), SIGNAL(quit()), SLOT(quit())); |
860 | |
861 | === modified file 'libertine/libertine.h' |
862 | --- libertine/libertine.h 2016-10-03 15:48:22 +0000 |
863 | +++ libertine/libertine.h 2016-11-21 19:42:42 +0000 |
864 | @@ -30,6 +30,7 @@ |
865 | class LibertineConfig; |
866 | class ContainerAppsList; |
867 | class ContainerArchivesList; |
868 | +class PackageOperationDetails; |
869 | |
870 | |
871 | class Libertine |
872 | @@ -56,6 +57,7 @@ |
873 | ContainerConfigList* containers_; |
874 | ContainerAppsList* container_apps_; |
875 | ContainerArchivesList* container_archives_; |
876 | + PackageOperationDetails* package_operation_details_; |
877 | QQuickView view_; |
878 | }; |
879 | |
880 | |
881 | === modified file 'liblibertine/CMakeLists.txt' |
882 | --- liblibertine/CMakeLists.txt 2016-09-06 12:36:47 +0000 |
883 | +++ liblibertine/CMakeLists.txt 2016-11-21 19:42:42 +0000 |
884 | @@ -1,32 +1,33 @@ |
885 | set(API_VERSION 1) |
886 | set(ABI_VERSION 1) |
887 | |
888 | -set(libertine_src ${CMAKE_SOURCE_DIR}/libertine) |
889 | +set(COMMON_DIR ${CMAKE_SOURCE_DIR}/common) |
890 | |
891 | add_library( |
892 | - libertine-common SHARED |
893 | + ${LIBERTINE_CORE} SHARED |
894 | libertine.cpp |
895 | - ${libertine_src}/ContainerConfigList.cpp |
896 | - ${libertine_src}/LibertineConfig.cpp |
897 | - ${libertine_src}/ContainerConfig.cpp |
898 | - ${libertine_src}/ContainerManager.cpp |
899 | - ${libertine_src}/ContainerAppsList.cpp |
900 | - ${libertine_src}/ContainerArchivesList.cpp |
901 | + |
902 | + ${COMMON_DIR}/ContainerConfig.cpp |
903 | + ${COMMON_DIR}/ContainerConfigList.cpp |
904 | + ${COMMON_DIR}/LibertineConfig.cpp |
905 | ) |
906 | -set_target_properties(libertine-common PROPERTIES |
907 | + |
908 | +set_target_properties(${LIBERTINE_CORE} PROPERTIES |
909 | VERSION ${ABI_VERSION}.0.0 |
910 | SOVERSION ${ABI_VERSION} |
911 | OUTPUT_NAME "libertine" |
912 | ) |
913 | -target_link_libraries(libertine-common |
914 | + |
915 | +target_link_libraries(${LIBERTINE_CORE} |
916 | ${GLIB2_LIBRARIES} |
917 | ${PYTHON3_LIBRARIES} |
918 | Qt5::Core |
919 | ) |
920 | |
921 | +# "liblibertine_headers_path" is used in libertine.pc.in |
922 | set(liblibertine_headers_path "${CMAKE_INSTALL_FULL_INCLUDEDIR}/liblibertine") |
923 | install(FILES libertine.h DESTINATION ${liblibertine_headers_path}) |
924 | -install(TARGETS libertine-common LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) |
925 | +install(TARGETS ${LIBERTINE_CORE} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) |
926 | |
927 | configure_file(libertine.pc.in ${CMAKE_BINARY_DIR}/libertine.pc @ONLY) |
928 | install(FILES ${CMAKE_BINARY_DIR}/libertine.pc |
929 | |
930 | === modified file 'liblibertine/libertine.cpp' |
931 | --- liblibertine/libertine.cpp 2016-09-06 12:36:47 +0000 |
932 | +++ liblibertine/libertine.cpp 2016-11-21 19:42:42 +0000 |
933 | @@ -18,8 +18,9 @@ |
934 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
935 | */ |
936 | #include "liblibertine/libertine.h" |
937 | -#include "libertine/ContainerConfigList.h" |
938 | -#include "libertine/LibertineConfig.h" |
939 | + |
940 | +#include "common/ContainerConfigList.h" |
941 | +#include "common/LibertineConfig.h" |
942 | |
943 | |
944 | namespace |
945 | @@ -77,29 +78,35 @@ |
946 | GError* error = nullptr; |
947 | GArray* apps = g_array_new(TRUE, TRUE, sizeof(gchar*)); |
948 | |
949 | - auto globalPath = g_build_filename("/", g_strdup(path), GLOBAL_APPLICATIONS, nullptr); |
950 | - error = list_apps_from_path(globalPath, container_id, apps); |
951 | - if (error != nullptr) |
952 | + if (path != nullptr) |
953 | { |
954 | - g_free(globalPath); |
955 | - g_free(path); |
956 | - g_error_free(error); |
957 | - return (gchar**)g_array_free(apps, FALSE); |
958 | + auto global_path = g_build_filename("/", g_strdup(path), GLOBAL_APPLICATIONS, nullptr); |
959 | + error = list_apps_from_path(global_path, container_id, apps); |
960 | + if (error != nullptr) |
961 | + { |
962 | + g_free(global_path); |
963 | + g_free(path); |
964 | + g_error_free(error); |
965 | + return (gchar**)g_array_free(apps, FALSE); |
966 | + } |
967 | + g_free(global_path); |
968 | } |
969 | - g_free(globalPath); |
970 | + g_free(path); |
971 | |
972 | auto home_path = libertine_container_home_path(container_id); |
973 | - auto local_path = g_build_filename(home_path, LOCAL_APPLICATIONS, nullptr); |
974 | + if (home_path != nullptr) |
975 | + { |
976 | + auto local_path = g_build_filename(home_path, LOCAL_APPLICATIONS, nullptr); |
977 | + |
978 | + error = list_apps_from_path(local_path, container_id, apps); |
979 | + if (error != nullptr) |
980 | + { |
981 | + g_error_free(error); // free error, but return previously found apps |
982 | + } |
983 | + g_free(local_path); |
984 | + } |
985 | g_free(home_path); |
986 | |
987 | - error = list_apps_from_path(local_path, container_id, apps); |
988 | - if (error != nullptr) |
989 | - { |
990 | - g_error_free(error); // free error, but return previously found apps |
991 | - } |
992 | - g_free(local_path); |
993 | - |
994 | - g_free(path); |
995 | return (gchar**)g_array_free(apps, FALSE); |
996 | } |
997 | |
998 | @@ -130,10 +137,10 @@ |
999 | gchar * |
1000 | libertine_container_path(const gchar * container_id) |
1001 | { |
1002 | - gchar * path = NULL; |
1003 | - g_return_val_if_fail(container_id != NULL, NULL); |
1004 | + gchar * path = nullptr; |
1005 | + g_return_val_if_fail(container_id != nullptr, nullptr); |
1006 | |
1007 | - path = g_build_filename(g_get_user_cache_dir(), "libertine-container", container_id, "rootfs", NULL); |
1008 | + path = g_build_filename(g_get_user_cache_dir(), "libertine-container", container_id, "rootfs", nullptr); |
1009 | |
1010 | if (g_file_test(path, G_FILE_TEST_EXISTS)) |
1011 | { |
1012 | @@ -142,7 +149,7 @@ |
1013 | else |
1014 | { |
1015 | g_free(path); |
1016 | - return NULL; |
1017 | + return nullptr; |
1018 | } |
1019 | } |
1020 | |
1021 | @@ -150,10 +157,10 @@ |
1022 | gchar * |
1023 | libertine_container_home_path(const gchar * container_id) |
1024 | { |
1025 | - gchar * path = NULL; |
1026 | - g_return_val_if_fail(container_id != NULL, NULL); |
1027 | + gchar * path = nullptr; |
1028 | + g_return_val_if_fail(container_id != nullptr, nullptr); |
1029 | |
1030 | - path = g_build_filename(g_get_user_data_dir(), "libertine-container", "user-data", container_id, NULL); |
1031 | + path = g_build_filename(g_get_user_data_dir(), "libertine-container", "user-data", container_id, nullptr); |
1032 | |
1033 | if (g_file_test(path, G_FILE_TEST_EXISTS)) |
1034 | { |
1035 | @@ -162,7 +169,7 @@ |
1036 | else |
1037 | { |
1038 | g_free(path); |
1039 | - return NULL; |
1040 | + return nullptr; |
1041 | } |
1042 | |
1043 | } |
1044 | @@ -173,7 +180,7 @@ |
1045 | { |
1046 | guint container_count; |
1047 | guint i; |
1048 | - gchar * container_name = NULL; |
1049 | + gchar * container_name = nullptr; |
1050 | LibertineConfig config; |
1051 | ContainerConfigList container_list(&config); |
1052 | QVariant id; |
1053 | |
1054 | === modified file 'python/libertine/ChrootContainer.py' |
1055 | --- python/libertine/ChrootContainer.py 2016-09-30 18:55:22 +0000 |
1056 | +++ python/libertine/ChrootContainer.py 2016-11-21 19:42:42 +0000 |
1057 | @@ -199,26 +199,29 @@ |
1058 | |
1059 | return proot_cmd |
1060 | |
1061 | - def launch_application(self, app_exec_line): |
1062 | + def start_application(self, app_exec_line, environ): |
1063 | # FIXME: Disabling seccomp is a temporary measure until we fully understand why we need |
1064 | # it or figure out when we need it. |
1065 | - os.environ['PROOT_NO_SECCOMP'] = '1' |
1066 | + environ['PROOT_NO_SECCOMP'] = '1' |
1067 | |
1068 | # Workaround issue where a custom dconf profile is on the machine |
1069 | - if 'DCONF_PROFILE' in os.environ: |
1070 | - del os.environ['DCONF_PROFILE'] |
1071 | + if 'DCONF_PROFILE' in environ: |
1072 | + del environ['DCONF_PROFILE'] |
1073 | |
1074 | proot_cmd = self._build_proot_command() |
1075 | |
1076 | args = shlex.split(proot_cmd) |
1077 | args.extend(utils.setup_window_manager(self.container_id, enable_toolbars=True)) |
1078 | - window_manager = psutil.Popen(args) |
1079 | + window_manager = psutil.Popen(args, env=environ) |
1080 | |
1081 | args = shlex.split(proot_cmd) |
1082 | args.extend(app_exec_line) |
1083 | - psutil.Popen(args).wait() |
1084 | + app = psutil.Popen(args, env=environ) |
1085 | + return app |
1086 | |
1087 | + def finish_application(self, app): |
1088 | utils.terminate_window_manager(window_manager) |
1089 | + app.wait() |
1090 | |
1091 | def _run_ldconfig(self, verbosity=1): |
1092 | if verbosity == 1: |
1093 | @@ -227,4 +230,8 @@ |
1094 | command_line = self._build_privileged_proot_cmd() + " ldconfig.REAL" |
1095 | |
1096 | args = shlex.split(command_line) |
1097 | - subprocess.Popen(args).wait() |
1098 | + app = subprocess.Popen(args) |
1099 | + return app |
1100 | + |
1101 | + def finish_application(self, app): |
1102 | + app.wait() |
1103 | |
1104 | === modified file 'python/libertine/ContainersConfig.py' |
1105 | --- python/libertine/ContainersConfig.py 2016-08-25 11:55:05 +0000 |
1106 | +++ python/libertine/ContainersConfig.py 2016-11-21 19:42:42 +0000 |
1107 | @@ -36,6 +36,10 @@ |
1108 | def write_container_config_file(container_list): |
1109 | container_config_file = libertine.utils.get_libertine_database_file_path() |
1110 | |
1111 | + # Add a warning to adventurous users advising against mucking with this file |
1112 | + if container_list is not None: |
1113 | + container_list["_warning"] = "This file is automatically generated by Libertine and should not be manually edited." |
1114 | + |
1115 | with open(container_config_file, 'w') as fd: |
1116 | fcntl.lockf(fd, fcntl.LOCK_EX) |
1117 | json.dump(container_list, fd, sort_keys=True, indent=4) |
1118 | @@ -322,6 +326,17 @@ |
1119 | package_name) |
1120 | |
1121 | """ |
1122 | + Operations for running apps in a Libertine container. |
1123 | + """ |
1124 | + def add_running_app(self, container_id, app_exec_name, pid=0): |
1125 | + app_obj = {'appExecName': app_exec_name, 'pid': pid} |
1126 | + self._set_value_by_key(container_id, 'runningApps', app_obj) |
1127 | + |
1128 | + def delete_running_app(self, container_id, app_exec_name): |
1129 | + self._delete_array_object_by_key_value(container_id, 'runningApps', |
1130 | + 'appExecName', app_exec_name) |
1131 | + |
1132 | + """ |
1133 | Fetcher functions for various configuration information. |
1134 | """ |
1135 | def get_container_distro(self, container_id): |
1136 | |
1137 | === modified file 'python/libertine/Libertine.py' |
1138 | --- python/libertine/Libertine.py 2016-09-30 18:55:22 +0000 |
1139 | +++ python/libertine/Libertine.py 2016-11-21 19:42:42 +0000 |
1140 | @@ -14,19 +14,11 @@ |
1141 | |
1142 | from .AppDiscovery import AppLauncherCache |
1143 | from gi.repository import Libertine |
1144 | -from multiprocessing import Process, active_children |
1145 | -from socket import * |
1146 | import abc |
1147 | import contextlib |
1148 | import libertine.utils |
1149 | import os |
1150 | -import psutil |
1151 | -import select |
1152 | import shutil |
1153 | -import shlex |
1154 | -import signal |
1155 | -import sys |
1156 | -import time |
1157 | |
1158 | from libertine.ContainersConfig import ContainersConfig |
1159 | from libertine.HostInfo import HostInfo |
1160 | @@ -56,6 +48,32 @@ |
1161 | return False |
1162 | |
1163 | |
1164 | +class NoContainer(object): |
1165 | + """ |
1166 | + A containerless class used for launching apps with libertine-launch. |
1167 | + """ |
1168 | + def connect(self): |
1169 | + """ |
1170 | + A no-op function used by the Session class. |
1171 | + """ |
1172 | + pass |
1173 | + |
1174 | + def disconnect(self): |
1175 | + """ |
1176 | + A no-op function used by the Session class. |
1177 | + """ |
1178 | + pass |
1179 | + |
1180 | + def start_application(self, app_exec_line, environ): |
1181 | + import psutil |
1182 | + |
1183 | + app = psutil.Popen(app_exec_line, env=environ) |
1184 | + return app |
1185 | + |
1186 | + def finish_application(self, app): |
1187 | + app.wait() |
1188 | + |
1189 | + |
1190 | class BaseContainer(metaclass=abc.ABCMeta): |
1191 | """ |
1192 | An abstract base container to provide common functionality for all |
1193 | @@ -249,11 +267,14 @@ |
1194 | def run_in_container(self, command_string): |
1195 | return 0 |
1196 | |
1197 | - def launch_application(self, app_exec_line): |
1198 | + def start_application(self, app_exec_line, environ): |
1199 | import subprocess |
1200 | |
1201 | - cmd = subprocess.Popen(app_exec_line) |
1202 | - cmd.wait() |
1203 | + app = subprocess.Popen(app_exec_line, env=environ) |
1204 | + return app |
1205 | + |
1206 | + def finish_application(self, app): |
1207 | + app.wait() |
1208 | |
1209 | |
1210 | class ContainerRunning(contextlib.ExitStack): |
1211 | @@ -265,9 +286,8 @@ |
1212 | """ |
1213 | def __init__(self, container): |
1214 | super().__init__() |
1215 | - if not container.is_running(): |
1216 | - container.start_container() |
1217 | - self.callback(lambda: container.stop_container()) |
1218 | + container.start_container() |
1219 | + self.callback(lambda: container.stop_container()) |
1220 | |
1221 | |
1222 | class LibertineContainer(object): |
1223 | @@ -382,21 +402,34 @@ |
1224 | except RuntimeError as e: |
1225 | return handle_runtime_error(e) |
1226 | |
1227 | - def launch_application(self, app_exec_line): |
1228 | + def connect(self): |
1229 | + """ |
1230 | + Connects to the container in preparation to launch an application. May |
1231 | + do something like start up daemons or bind-mount directories, I dunno, |
1232 | + it's up to the concrete container class. Maybe it does nothing. |
1233 | + """ |
1234 | + pass |
1235 | + |
1236 | + def disconnect(self): |
1237 | + """ |
1238 | + The inverse of connect() above. |
1239 | + """ |
1240 | + pass |
1241 | + |
1242 | + def start_application(self, app_exec_line, environ): |
1243 | """ |
1244 | Launches an application in the container. |
1245 | |
1246 | :param app_exec_line: the application exec line as passed in by |
1247 | ubuntu-app-launch |
1248 | """ |
1249 | - if self.containers_config.container_exists(self.container.container_id): |
1250 | - # Update $PATH as necessary |
1251 | - if '/usr/games' not in os.environ['PATH']: |
1252 | - os.environ['PATH'] = os.environ['PATH'] + ":/usr/games" |
1253 | + return self.container.start_application(app_exec_line, environ) |
1254 | |
1255 | - self.container.launch_application(app_exec_line) |
1256 | - else: |
1257 | - raise RuntimeError("Container with id %s does not exist." % self.container.container_id) |
1258 | + def finish_application(self, app): |
1259 | + """ |
1260 | + Finishes the currently running application in the container. |
1261 | + """ |
1262 | + self.container.finish_application(app) |
1263 | |
1264 | def list_app_launchers(self, use_json=False): |
1265 | """ |
1266 | @@ -450,262 +483,3 @@ |
1267 | return self.container.configure_remove_archive(archive, verbosity) |
1268 | except RuntimeError as e: |
1269 | return handle_runtime_error(e) |
1270 | - |
1271 | - |
1272 | -class Socket(object): |
1273 | - """ |
1274 | - A simple socket wrapper class. This will wrap a socket |
1275 | - |
1276 | - :param socket: A python socket to be wrapped |
1277 | - """ |
1278 | - def __init__(self, sock): |
1279 | - if not isinstance(sock, socket): |
1280 | - raise TypeError("expected socket to be a python socket class instead found: '{}'".format(sock.__class__)) |
1281 | - |
1282 | - self.socket = sock |
1283 | - |
1284 | - """ |
1285 | - Implement equality checking for other instances of this class or ints only. |
1286 | - |
1287 | - :param other: Either a Socket class or an int |
1288 | - """ |
1289 | - def __eq__(self, other): |
1290 | - if isinstance(other, Socket): |
1291 | - return self.socket == other.socket |
1292 | - elif isinstance(other, socket): |
1293 | - return self.socket == other |
1294 | - elif isinstance(other, int): |
1295 | - return self.socket.fileno() == other |
1296 | - else: |
1297 | - raise TypeError("unsupported operand type(s) for ==: '{}' and '{}'".format(self.__class__, type(other))) |
1298 | - |
1299 | - def __ne__(self, other): |
1300 | - return not self == other |
1301 | - |
1302 | - def __hash__(self): |
1303 | - return self.socket.fileno() |
1304 | - |
1305 | - def socket(self): |
1306 | - return self.socket |
1307 | - |
1308 | -class SessionSocket(Socket): |
1309 | - """ |
1310 | - Creates a AF_UNIX socket from a path to be used as the session socket. |
1311 | - This is used for RAII and to take ownership of the socket |
1312 | - |
1313 | - :param path: The path the socket will be binded with |
1314 | - """ |
1315 | - def __init__(self, session_path): |
1316 | - try: |
1317 | - sock = socket(AF_UNIX, SOCK_STREAM) |
1318 | - except: |
1319 | - sock = None |
1320 | - raise |
1321 | - else: |
1322 | - try: |
1323 | - sock.bind(session_path) |
1324 | - sock.listen(5) |
1325 | - except: |
1326 | - sock.close() |
1327 | - sock = None |
1328 | - raise |
1329 | - else: |
1330 | - super().__init__(sock) |
1331 | - self.session_path = session_path |
1332 | - |
1333 | - def __del__(self): |
1334 | - self.socket.shutdown(SHUT_RDWR) |
1335 | - self.socket.close() |
1336 | - os.remove(self.session_path) |
1337 | - |
1338 | - |
1339 | -class HostSessionSocketPair(): |
1340 | - def __init__(self, host_socket_path, session_socket_path): |
1341 | - self.host_socket_path = host_socket_path |
1342 | - self.session_socket_path = session_socket_path |
1343 | - |
1344 | - |
1345 | -class LibertineSessionBridge(object): |
1346 | - """ |
1347 | - Creates a session bridge which will pair host and session sockets to proxy the info |
1348 | - from the session to the host and vice versa. |
1349 | - |
1350 | - :param host_session_socket_paths: A list of pairs container valid sockets to proxy {host:session} |
1351 | - """ |
1352 | - def __init__(self, host_session_socket_paths): |
1353 | - self.descriptors = [] |
1354 | - self.host_session_socket_path_map = {} |
1355 | - self.socket_pairs = {} |
1356 | - |
1357 | - for host_session_socket_pair in host_session_socket_paths: |
1358 | - host_path = host_session_socket_pair.host_socket_path |
1359 | - session_path = host_session_socket_pair.session_socket_path |
1360 | - |
1361 | - socket = SessionSocket(session_path) |
1362 | - |
1363 | - self.descriptors.append(socket) |
1364 | - self.host_session_socket_path_map.update({socket:host_path}) |
1365 | - |
1366 | - """ |
1367 | - If we end up going out of scope/error let's make sure we clean up sockets and paths. |
1368 | - """ |
1369 | - def __del__(self): |
1370 | - self.socket_pairs = None |
1371 | - self.descriptors = None |
1372 | - self.host_session_socket_path_map = None |
1373 | - |
1374 | - """ |
1375 | - When a new connection is made on one of the main sockets we have to create a new |
1376 | - socket pairing with the container socket. |
1377 | - |
1378 | - :param host_path: The raw path to the container socket |
1379 | - :param container_sock: The socket which received a new connection |
1380 | - """ |
1381 | - def accept_new_connection(self, host_path, container_sock): |
1382 | - newconn = Socket(container_sock.accept()[0]) |
1383 | - self.descriptors.append(newconn) |
1384 | - |
1385 | - host_sock = Socket(socket(AF_UNIX, SOCK_STREAM)) |
1386 | - host_sock.socket.connect(host_path) |
1387 | - self.descriptors.append(host_sock) |
1388 | - |
1389 | - self.socket_pairs.update({newconn:host_sock}) |
1390 | - self.socket_pairs.update({host_sock:newconn}) |
1391 | - |
1392 | - """ |
1393 | - Cleans up a socket that has had its connection closed. |
1394 | - |
1395 | - :param socket_to_remove: The socket that had its connection closed |
1396 | - """ |
1397 | - def close_connections(self, socket_to_remove): |
1398 | - partner_socket = self.socket_pairs[socket_to_remove.fileno()] |
1399 | - |
1400 | - self.socket_pairs.pop(socket_to_remove.fileno()) |
1401 | - self.socket_pairs.pop(partner_socket.socket.fileno()) |
1402 | - |
1403 | - self.descriptors.remove(socket_to_remove) |
1404 | - self.descriptors.remove(partner_socket) |
1405 | - |
1406 | - if socket_to_remove in self.host_session_socket_path_map: |
1407 | - self.host_session_socket_path_map.pop(socket_to_remove) |
1408 | - |
1409 | - """ |
1410 | - The main loop which uses select to block until one of the sockets we are listening to becomes readable. |
1411 | - It is advised this be started in its own process or thread, as this function blocks! |
1412 | - """ |
1413 | - def main_loop(self): |
1414 | - while 1: |
1415 | - try: |
1416 | - raw_sockets = list(map(lambda x : x.socket, self.descriptors)) |
1417 | - rlist, wlist, elist = select.select(raw_sockets, [], []) |
1418 | - except InterruptedError as e: |
1419 | - continue |
1420 | - except Exception as e: |
1421 | - libertine.utils.get_logger().exception(e) |
1422 | - break |
1423 | - |
1424 | - for sock in rlist: |
1425 | - if sock.fileno() == -1: |
1426 | - continue |
1427 | - |
1428 | - # Its possible to have multiple socket reads that are pairs. If this happens and we remove a pair we |
1429 | - # need to ignore the other pair since it no longer has a complete pair |
1430 | - if sock.fileno() not in self.host_session_socket_path_map and sock.fileno() not in self.socket_pairs: |
1431 | - continue |
1432 | - |
1433 | - if sock.fileno() in self.host_session_socket_path_map: |
1434 | - self.accept_new_connection(self.host_session_socket_path_map[sock.fileno()], sock) |
1435 | - |
1436 | - else: |
1437 | - try: |
1438 | - data = sock.recv(4096) |
1439 | - except Exception as e: |
1440 | - libertine.utils.get_logger().exception(e) |
1441 | - self.close_connections(sock) |
1442 | - continue |
1443 | - else: |
1444 | - if len(data) == 0: |
1445 | - self.close_connections(sock) |
1446 | - continue |
1447 | - |
1448 | - send_sock = self.socket_pairs[sock.fileno()].socket |
1449 | - |
1450 | - if send_sock.fileno() < 0: |
1451 | - continue |
1452 | - |
1453 | - totalsent = 0 |
1454 | - while totalsent < len(data): |
1455 | - try: |
1456 | - sent = send_sock.send(data) |
1457 | - except BrokenPipeError as e: |
1458 | - libertine.utils.get_logger().exception(e) |
1459 | - self.close_connections(sock) |
1460 | - break |
1461 | - else: |
1462 | - if sent == 0: |
1463 | - close_connections(sock) |
1464 | - break |
1465 | - totalsent = totalsent + sent |
1466 | - |
1467 | - |
1468 | -class LibertineApplication(object): |
1469 | - """ |
1470 | - Launches a libertine container with a session bridge for sockets such as dbus |
1471 | - |
1472 | - :param container_id: The container id. |
1473 | - :param app_exec_line: The exec line used to start the app in the container. |
1474 | - """ |
1475 | - def __init__(self, container_id, app_exec_line): |
1476 | - signal.signal(signal.SIGTERM, self.cleanup_lsb) |
1477 | - signal.signal(signal.SIGINT, self.cleanup_lsb) |
1478 | - |
1479 | - self.container_id = container_id |
1480 | - self.app_exec_line = app_exec_line |
1481 | - self.lsb = None |
1482 | - self.pasted = None |
1483 | - |
1484 | - def cleanup_lsb(self, signum, frame): |
1485 | - self.close_lsb() |
1486 | - |
1487 | - def close_lsb(self): |
1488 | - if self.lsb is not None: |
1489 | - self.lsb_process.terminate() |
1490 | - |
1491 | - while active_children(): |
1492 | - time.sleep(1) |
1493 | - |
1494 | - """ |
1495 | - Launches the libertine session bridge. This creates a proxy socket to read to and from |
1496 | - for abstract sockets such as dbus. |
1497 | - |
1498 | - :param session_socket_paths: A list of socket paths the session will create. |
1499 | - """ |
1500 | - def launch_session_bridge(self, session_socket_paths): |
1501 | - self.lsb = LibertineSessionBridge(session_socket_paths) |
1502 | - self.lsb_process = Process(target=self.lsb.main_loop) |
1503 | - self.lsb_process.start() |
1504 | - |
1505 | - """ |
1506 | - Launches the pasted process for allowing copy and paste to work with X apps and |
1507 | - content-hub. |
1508 | - """ |
1509 | - def launch_pasted(self): |
1510 | - self.pasted = psutil.Popen("pasted") |
1511 | - |
1512 | - """ |
1513 | - Launches the container from the id and attempts to run the application exec. |
1514 | - """ |
1515 | - def launch_application(self): |
1516 | - if not ContainersConfig().container_exists(self.container_id): |
1517 | - raise RuntimeError("Container ID %s does not exist." % self.container_id) |
1518 | - |
1519 | - container = LibertineContainer(self.container_id) |
1520 | - try: |
1521 | - container.launch_application(self.app_exec_line) |
1522 | - except: |
1523 | - raise |
1524 | - finally: |
1525 | - self.close_lsb() |
1526 | - |
1527 | - if self.pasted is not None: |
1528 | - self.pasted.terminate() |
1529 | |
1530 | === modified file 'python/libertine/LxcContainer.py' |
1531 | --- python/libertine/LxcContainer.py 2016-09-20 13:58:52 +0000 |
1532 | +++ python/libertine/LxcContainer.py 2016-11-21 19:42:42 +0000 |
1533 | @@ -56,6 +56,14 @@ |
1534 | return os.path.join(home_path, '.config', 'lxc') |
1535 | |
1536 | |
1537 | +def get_lxc_manager_dbus_name(): |
1538 | + return LIBERTINE_LXC_MANAGER_NAME |
1539 | + |
1540 | + |
1541 | +def get_lxc_manager_dbus_path(): |
1542 | + return LIBERTINE_LXC_MANAGER_PATH |
1543 | + |
1544 | + |
1545 | def lxc_container(container_id): |
1546 | config_path = utils.get_libertine_containers_dir_path() |
1547 | if not os.path.exists(config_path): |
1548 | @@ -66,6 +74,28 @@ |
1549 | return container |
1550 | |
1551 | |
1552 | +def lxc_start(container, lxc_log_file): |
1553 | + container.append_config_item("lxc.logfile", lxc_log_file) |
1554 | + container.append_config_item("lxc.logpriority", "3") |
1555 | + |
1556 | + if not container.start(): |
1557 | + return (False, "Container failed to start") |
1558 | + |
1559 | + if not container.wait("RUNNING", 10): |
1560 | + return (False, "Container failed to enter the RUNNING state") |
1561 | + |
1562 | + if not container.get_ips(timeout=30): |
1563 | + lxc_stop(container) |
1564 | + return (False, "Not able to connect to the network.") |
1565 | + |
1566 | + return (True, "") |
1567 | + |
1568 | + |
1569 | +def lxc_stop(container): |
1570 | + if container.running: |
1571 | + container.stop() |
1572 | + |
1573 | + |
1574 | def get_host_timezone(): |
1575 | with open(os.path.join('/', 'etc', 'timezone'), 'r') as fd: |
1576 | host_timezone = fd.read().strip('\n') |
1577 | @@ -107,6 +137,18 @@ |
1578 | super().__init__(container_id) |
1579 | self.container_type = "lxc" |
1580 | self.container = lxc_container(container_id) |
1581 | + self._set_lxc_log() |
1582 | + self.lxc_manager_interface = None |
1583 | + self.window_manager = None |
1584 | + |
1585 | + utils.set_session_dbus_env_var() |
1586 | + |
1587 | + try: |
1588 | + bus = dbus.SessionBus() |
1589 | + lxc_mgr_service = bus.get_object(get_lxc_manager_dbus_name(), get_lxc_manager_dbus_path()) |
1590 | + self.lxc_manager_interface = dbus.Interface(lxc_mgr_service, get_lxc_manager_dbus_name()) |
1591 | + except dbus.exceptions.DBusException: |
1592 | + pass |
1593 | |
1594 | def is_running(self): |
1595 | return self.container.running |
1596 | @@ -122,40 +164,26 @@ |
1597 | else: |
1598 | return True |
1599 | |
1600 | - def _dynamic_bind_mounts(self): |
1601 | - for user_dir in utils.get_common_xdg_user_directories(): |
1602 | - xdg_user_dir_entry = ( |
1603 | - "%s %s/%s none bind,create=dir,optional" |
1604 | - % (user_dir[0], home_path.strip('/'), user_dir[1]) |
1605 | - ) |
1606 | - self.container.append_config_item("lxc.mount.entry", xdg_user_dir_entry) |
1607 | - |
1608 | - def start_container(self, launchable = False): |
1609 | - if not self.container.defined: |
1610 | - raise RuntimeError("Container %s is not valid" % self.container_id) |
1611 | - |
1612 | - if not self.container.running: |
1613 | - self._set_lxc_log() |
1614 | - self._dynamic_bind_mounts() |
1615 | - if not self.container.start(): |
1616 | - self._dump_lxc_log() |
1617 | - raise RuntimeError("Container failed to start") |
1618 | - if not self.container.wait("RUNNING", 10): |
1619 | - self._dump_lxc_log() |
1620 | - raise RuntimeError("Container failed to enter the RUNNING state") |
1621 | - |
1622 | - if not self.container.get_ips(timeout=30): |
1623 | - self.stop_container() |
1624 | - raise RuntimeError("Not able to connect to the network.") |
1625 | - |
1626 | - if not launchable: |
1627 | - if self.run_in_container("mountpoint -q /tmp/.X11-unix") == 0: |
1628 | - self.run_in_container("umount /tmp/.X11-unix") |
1629 | - if self.run_in_container("mountpoint -q /usr/lib/locale") == 0: |
1630 | - self.run_in_container("umount -l /usr/lib/locale") |
1631 | + def start_container(self): |
1632 | + if self.lxc_manager_interface: |
1633 | + (result, error) = self.lxc_manager_interface.operation_start(self.container_id, self.lxc_log_file) |
1634 | + else: |
1635 | + (result, error) = lxc_start(self.container, self.lxc_log_file) |
1636 | + |
1637 | + if not result: |
1638 | + self._dump_lxc_log() |
1639 | + raise RuntimeError(error) |
1640 | + |
1641 | + if self.run_in_container("mountpoint -q /tmp/.X11-unix") == 0: |
1642 | + self.run_in_container("umount /tmp/.X11-unix") |
1643 | + if self.run_in_container("mountpoint -q /usr/lib/locale") == 0: |
1644 | + self.run_in_container("umount -l /usr/lib/locale") |
1645 | |
1646 | def stop_container(self): |
1647 | - self.container.stop() |
1648 | + if self.lxc_manager_interface: |
1649 | + self.lxc_manager_interface.operation_stop(self.container_id) |
1650 | + else: |
1651 | + lxc_stop(self.container) |
1652 | |
1653 | def run_in_container(self, command_string): |
1654 | cmd_args = shlex.split(command_string) |
1655 | @@ -282,49 +310,49 @@ |
1656 | # Dump it all to disk |
1657 | self.container.save_config() |
1658 | |
1659 | - def launch_application(self, app_exec_line): |
1660 | - bus = dbus.SessionBus() |
1661 | - lxc_mgr_service = bus.get_object(LIBERTINE_LXC_MANAGER_NAME, LIBERTINE_LXC_MANAGER_PATH) |
1662 | - lxc_manager_interface = dbus.Interface(lxc_mgr_service, LIBERTINE_LXC_MANAGER_NAME) |
1663 | - |
1664 | - if lxc_manager_interface.app_start(self.container_id): |
1665 | - if not self.container.wait("RUNNING", 10): |
1666 | - print("Container failed to enter the RUNNING state") |
1667 | - return |
1668 | - |
1669 | - if not self.container.get_ips(timeout=30): |
1670 | - print("Not able to connect to the network.") |
1671 | - return |
1672 | - |
1673 | - else: |
1674 | - print("Failure detected from libertine-lxc-manager") |
1675 | - return |
1676 | - |
1677 | - window_manager = self.container.attach(lxc.attach_run_command, |
1678 | - utils.setup_window_manager(self.container_id)) |
1679 | + def start_application(self, app_exec_line, environ): |
1680 | + if self.lxc_manager_interface == None: |
1681 | + print("No interface to libertine-lxc-manager. Failing application launch.") |
1682 | + return |
1683 | + |
1684 | + os.environ.clear() |
1685 | + os.environ.update(environ) |
1686 | + |
1687 | + (result, error) = self.lxc_manager_interface.app_start(self.container_id, self.lxc_log_file) |
1688 | + |
1689 | + if not result: |
1690 | + self._dump_lxc_log() |
1691 | + print("%s" % error) |
1692 | + return |
1693 | + |
1694 | + self.window_manager = self.container.attach(lxc.attach_run_command, |
1695 | + utils.setup_window_manager(self.container_id)) |
1696 | |
1697 | # Setup pulse to work inside the container |
1698 | os.environ['PULSE_SERVER'] = utils.get_libertine_lxc_pulse_socket_path() |
1699 | |
1700 | app_launch_cmd = "sudo -E -u " + os.environ['USER'] + " env PATH=" + os.environ['PATH'] |
1701 | cmd = shlex.split(app_launch_cmd) |
1702 | - self.container.attach_wait(lxc.attach_run_command, |
1703 | - cmd + app_exec_line) |
1704 | - |
1705 | - utils.terminate_window_manager(psutil.Process(window_manager)) |
1706 | + app = self.container.attach(lxc.attach_run_command, |
1707 | + cmd + app_exec_line) |
1708 | + return psutil.Process(app) |
1709 | + |
1710 | + def finish_application(self, app): |
1711 | + os.waitpid(app.pid, 0) |
1712 | + |
1713 | + utils.terminate_window_manager(psutil.Process(self.window_manager)) |
1714 | |
1715 | # Tell libertine-lxc-manager that the app has stopped. |
1716 | - lxc_manager_interface.app_stop(self.container_id) |
1717 | + self.lxc_manager_interface.app_stop(self.container_id) |
1718 | |
1719 | def _set_lxc_log(self): |
1720 | self.lxc_log_file = os.path.join(tempfile.mkdtemp(), 'lxc-start.log') |
1721 | - self.container.append_config_item("lxc.logfile", self.lxc_log_file) |
1722 | - self.container.append_config_item("lxc.logpriority", "3") |
1723 | |
1724 | def _dump_lxc_log(self): |
1725 | - try: |
1726 | - with open(self.lxc_log_file, 'r') as fd: |
1727 | - for line in fd: |
1728 | - print(line.lstrip()) |
1729 | - except Exception as ex: |
1730 | - print("Could not open LXC log file: %s" % ex, file=sys.stderr) |
1731 | + if os.path.exists(self.lxc_log_file): |
1732 | + try: |
1733 | + with open(self.lxc_log_file, 'r') as fd: |
1734 | + for line in fd: |
1735 | + print(line.lstrip()) |
1736 | + except Exception as ex: |
1737 | + print("Could not open LXC log file: %s" % ex, file=sys.stderr) |
1738 | |
1739 | === modified file 'python/libertine/__init__.py' |
1740 | --- python/libertine/__init__.py 2016-08-04 23:21:52 +0000 |
1741 | +++ python/libertine/__init__.py 2016-11-21 19:42:42 +0000 |
1742 | @@ -22,21 +22,12 @@ |
1743 | |
1744 | __all__ = [ |
1745 | # from Libertine |
1746 | - 'LibertineApplication', |
1747 | 'LibertineContainer', |
1748 | - 'LibertineSessionBridge', |
1749 | - 'LibertineSessionBridge', |
1750 | - 'HostSessionSocketPair', |
1751 | - 'SessionSocket', |
1752 | - 'Socket', |
1753 | + 'NoContainer', |
1754 | 'utils', |
1755 | ] |
1756 | |
1757 | __docformat__ = "restructuredtext en" |
1758 | |
1759 | -from libertine.Libertine import LibertineApplication |
1760 | from libertine.Libertine import LibertineContainer |
1761 | -from libertine.Libertine import LibertineSessionBridge |
1762 | -from libertine.Libertine import HostSessionSocketPair |
1763 | -from libertine.Libertine import SessionSocket |
1764 | -from libertine.Libertine import Socket |
1765 | +from libertine.Libertine import NoContainer |
1766 | |
1767 | === added directory 'python/libertine/launcher' |
1768 | === added file 'python/libertine/launcher/__init__.py' |
1769 | --- python/libertine/launcher/__init__.py 1970-01-01 00:00:00 +0000 |
1770 | +++ python/libertine/launcher/__init__.py 2016-11-21 19:42:42 +0000 |
1771 | @@ -0,0 +1,28 @@ |
1772 | +# Copyright 2016 Canonical Ltd. |
1773 | +# |
1774 | +# This program is free software: you can redistribute it and/or modify it |
1775 | +# under the terms of the GNU General Public License version 3, as published |
1776 | +# by the Free Software Foundation. |
1777 | +# |
1778 | +# This program is distributed in the hope that it will be useful, but |
1779 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
1780 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1781 | +# PURPOSE. See the GNU General Public License for more details. |
1782 | +# |
1783 | +# You should have received a copy of the GNU General Public License along |
1784 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
1785 | + |
1786 | +"""Provides the Libertine launcher functionality. |
1787 | + |
1788 | +All the things used specifically for launching and running an application under |
1789 | +a Libertine aegis are in this subpackage. It is the principal guts of the |
1790 | +libertine-launch tool and associated test suites. |
1791 | + |
1792 | +This is the public interface of the Libertine launcher package. |
1793 | +""" |
1794 | + |
1795 | +from .config import Config, SocketBridge |
1796 | +from .session import Session, translate_to_real_address |
1797 | +from .task import LaunchServiceTask, TaskConfig, TaskType |
1798 | + |
1799 | +__all__ = ('Config', 'Session') |
1800 | |
1801 | === added file 'python/libertine/launcher/config.py' |
1802 | --- python/libertine/launcher/config.py 1970-01-01 00:00:00 +0000 |
1803 | +++ python/libertine/launcher/config.py 2016-11-21 19:42:42 +0000 |
1804 | @@ -0,0 +1,297 @@ |
1805 | +# Copyright 2016 Canonical Ltd. |
1806 | +# |
1807 | +# This program is free software: you can redistribute it and/or modify it |
1808 | +# under the terms of the GNU General Public License version 3, as published |
1809 | +# by the Free Software Foundation. |
1810 | +# |
1811 | +# This program is distributed in the hope that it will be useful, but |
1812 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
1813 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1814 | +# PURPOSE. See the GNU General Public License for more details. |
1815 | +# |
1816 | +# You should have received a copy of the GNU General Public License along |
1817 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
1818 | + |
1819 | +"""Configure the libertine launcher.""" |
1820 | + |
1821 | +import argparse |
1822 | +import dbus |
1823 | +import os |
1824 | +import random |
1825 | +import string |
1826 | +import sys |
1827 | +from .task import TaskType, TaskConfig |
1828 | +from .. import utils |
1829 | + |
1830 | +import gettext |
1831 | +gettext.textdomain('libertine') |
1832 | +_ = gettext.gettext |
1833 | + |
1834 | +log = utils.get_logger() |
1835 | + |
1836 | + |
1837 | +def _generate_unique_id(): |
1838 | + """Generate a (hopefully) unique identifier string.""" |
1839 | + return ''.join(random.choice(string.ascii_lowercase + string.digits) for i in range(8)) |
1840 | + |
1841 | + |
1842 | +class SocketBridge(object): |
1843 | + """Configuration for a single socket bridge entry. |
1844 | + |
1845 | + This is a 3-tuple of a label, the address of a Unix-domain socket in the |
1846 | + host environment, and the address of a Unix-domain socket in the session |
1847 | + environment. |
1848 | + |
1849 | + .. data:: env_var |
1850 | + |
1851 | + The label of the socket bridge. This is used as the environment variable |
1852 | + in the session environment. It should follow the naming rules for |
1853 | + environment variables. |
1854 | + |
1855 | + .. data:: host_address |
1856 | + |
1857 | + The address of a Unix-domain socket in the host environment. |
1858 | + |
1859 | + .. data:: session_address |
1860 | + |
1861 | + The address of a Unix-domain socket in the session environment. |
1862 | + |
1863 | + """ |
1864 | + |
1865 | + def __init__(self, env_var, host_address, session_address): |
1866 | + """Initialize the socket bridge address pair. |
1867 | + |
1868 | + :param env_var: The socket bridge label. |
1869 | + :param host_address: The host socket address. |
1870 | + :param session_address: The session socket address. |
1871 | + |
1872 | + """ |
1873 | + self.env_var = env_var |
1874 | + self.host_address = host_address |
1875 | + self.session_address = session_address |
1876 | + |
1877 | + def __repr__(self): |
1878 | + """Get a human-readable string representation.""" |
1879 | + return '{{{}:{}->{}}}'.format(self.env_var, self.host_address, self.session_address) |
1880 | + |
1881 | + |
1882 | +def _get_maliit_address_from_dbus(): |
1883 | + """Query the session D-Bus for the address of the Maliit server. |
1884 | + |
1885 | + If there is no response from the D-Bus (let's just say there is no maliit |
1886 | + server running) None is returned. |
1887 | + """ |
1888 | + try: |
1889 | + address_bus_name = 'org.maliit.server' |
1890 | + address_object_path = '/org/maliit/server/address' |
1891 | + address_interface = 'org.maliit.Server.Address' |
1892 | + address_property = 'address' |
1893 | + |
1894 | + session_bus = dbus.SessionBus() |
1895 | + maliit_object = session_bus.get_object('org.maliit.server', '/org/maliit/server/address') |
1896 | + interface = dbus.Interface(maliit_object, dbus.PROPERTIES_IFACE) |
1897 | + return interface.Get('org.maliit.Server.Address', 'address') |
1898 | + except Exception as ex: |
1899 | + log.warning(ex) |
1900 | + return None |
1901 | + |
1902 | + |
1903 | +class Config(object): |
1904 | + """Configuration for the libertine application launcher. |
1905 | + |
1906 | + The main configuration items are the container ID and the application |
1907 | + command line. Additional configuration items include and environment |
1908 | + variables to be passed in to the application session and any sockets to be |
1909 | + bridged between the session and the host. |
1910 | + |
1911 | + The following members can be read from objects of this class. |
1912 | + |
1913 | + ================== ================== |
1914 | + Attribute Description |
1915 | + ------------------ ------------------ |
1916 | + **id** A unique session identifier. A random string of letters and numbers. |
1917 | + **container_id** The ID of the container in which the session will run. |
1918 | + **exec_line** The program and arguments to execute. |
1919 | + **host_environ** A sanitized dictionary of environment variables to |
1920 | + export in host operations. |
1921 | + **session_environ** A sanitized, remapped, and extended dictionary of environment variables to |
1922 | + export in the session environment. Effectively a copy of the host |
1923 | + environment but with some changes. |
1924 | + **socket_bridges** A collection of SocketBridge objects to implement. |
1925 | + **prelaunch_tasks** A collection of tasks to perform before launching the application. |
1926 | + ================== ================== |
1927 | + |
1928 | + """ |
1929 | + |
1930 | + def __init__(self, argv=sys.argv[1:]): |
1931 | + """ |
1932 | + :param argv: Command-line arguments used to configure the launcher. Default is ``sys.argv``. |
1933 | + |
1934 | + """ |
1935 | + log.debug('Config.__init__() begins') |
1936 | + arg_parser = argparse.ArgumentParser(description=_('Launch an application natively or in a Libertine container')) |
1937 | + arg_parser.add_argument('-i', '--id', |
1938 | + help=_('Container identifier when launching containerized apps')) |
1939 | + arg_parser.add_argument('-E', '--env', |
1940 | + default=[], |
1941 | + dest='environ', |
1942 | + action='append', |
1943 | + help=_('Set an environment variable')) |
1944 | + arg_parser.add_argument('app_exec_line', |
1945 | + nargs=argparse.REMAINDER, |
1946 | + help=_('exec line')) |
1947 | + options = arg_parser.parse_args(args=argv) |
1948 | + |
1949 | + if not options.app_exec_line: |
1950 | + arg_parser.error('Must specify an exec line') |
1951 | + |
1952 | + if options.id: |
1953 | + self.container_id = options.id |
1954 | + else: |
1955 | + self.container_id = None |
1956 | + |
1957 | + self.id = _generate_unique_id() |
1958 | + self.exec_line = options.app_exec_line |
1959 | + self.host_environ = self._sanitize_host_environment(options) |
1960 | + self.socket_bridges = self._create_socket_bridges() |
1961 | + self.prelaunch_tasks = self._add_prelaunch_tasks() |
1962 | + self.session_environ = self._generate_session_environment() |
1963 | + |
1964 | + log.debug('id = "{}"'.format(self.id)) |
1965 | + log.debug('container_id = "{}"'.format(self.container_id)) |
1966 | + log.debug('exec_line = "{}"'.format(self.exec_line)) |
1967 | + log.debug('session_environ = {}'.format(self.session_environ)) |
1968 | + for bridge in self.socket_bridges: |
1969 | + log.debug('socket bridge: {}'.format(bridge)) |
1970 | + log.debug('Config.__init__() ends') |
1971 | + |
1972 | + def _sanitize_host_environment(self, options): |
1973 | + """Create a dictionary of sanitized environment variables. |
1974 | + |
1975 | + The environment available in the Libertine aegis are effectively those |
1976 | + of the launcher, but santized to remove any unwanted or dangerous stuff |
1977 | + and with and changes requested on the command line. |
1978 | + |
1979 | + :param options: A collection of option values including a dictionary |
1980 | + named 'environ'. |
1981 | + :param type: An argparse namespace. |
1982 | + |
1983 | + :returns: A sanitized host environment dictionary. |
1984 | + """ |
1985 | + # inherit from the host |
1986 | + environ = os.environ.copy() |
1987 | + |
1988 | + # remove problematic environment variables |
1989 | + for e in ['QT_QPA_PLATFORM', 'LD_LIBRARY_PATH', 'FAKECHROOT_BASE', 'FAKECHROOT_CMD_SUBST']: |
1990 | + if e in environ: |
1991 | + del environ[e] |
1992 | + |
1993 | + # add or replace CLI-speicifed variables |
1994 | + for e in options.environ: |
1995 | + k, v = e.split(sep='=', maxsplit=2) |
1996 | + environ[k] = v |
1997 | + |
1998 | + return environ |
1999 | + |
2000 | + def _generate_session_environment(self): |
2001 | + """Generate the session environment.""" |
2002 | + |
2003 | + # Start with the host environment. |
2004 | + environ = self.host_environ.copy() |
2005 | + |
2006 | + # Fudge some values. |
2007 | + path = environ.get('PATH', '/usr/bin') |
2008 | + if '/usr/games' not in path.split(sep=':'): |
2009 | + environ['PATH'] = path + os.pathsep + '/usr/games' |
2010 | + |
2011 | + # Add the remapped socket bridge variables. |
2012 | + for bridge in self.socket_bridges: |
2013 | + environ[bridge.env_var] = 'unix:path=' + bridge.session_address |
2014 | + |
2015 | + return environ |
2016 | + |
2017 | + def _add_prelaunch_tasks(self): |
2018 | + """Create a collection of pre-launch tasks.""" |
2019 | + tasks = [] |
2020 | + tasks.append(TaskConfig(TaskType.LAUNCH_SERVICE, ["pasted"])) |
2021 | + |
2022 | + if self.container_id is None: |
2023 | + tasks.append(TaskConfig(TaskType.LAUNCH_SERVICE, ['matchbox-window-manager', '-use_titlebar', 'no'])) |
2024 | + |
2025 | + return tasks |
2026 | + |
2027 | + def _create_socket_bridges(self): |
2028 | + """Create the required socket bridge configs.""" |
2029 | + bridges = [] |
2030 | + |
2031 | + if self.container_id: |
2032 | + maliit_host_bridge = self._create_maliit_host_bridge() |
2033 | + if maliit_host_bridge: |
2034 | + bridges.append(maliit_host_bridge) |
2035 | + dbus_host_bridge = self._create_dbus_host_bridge() |
2036 | + if dbus_host_bridge: |
2037 | + bridges.append(dbus_host_bridge) |
2038 | + |
2039 | + return bridges |
2040 | + |
2041 | + def _generate_session_socket_name(self, socket_target): |
2042 | + """Generate a socket name for the target. |
2043 | + |
2044 | + :param target: A string used as a base for the socket address. |
2045 | + |
2046 | + If there is a seession ID, the session ID is appended to the socket name |
2047 | + to make it (hopefully) unique. |
2048 | + """ |
2049 | + socket_name = os.path.join(utils.get_libertine_runtime_dir(), socket_target) |
2050 | + if self.id: |
2051 | + socket_name += ('-' + self.id) |
2052 | + return socket_name |
2053 | + |
2054 | + def _get_dbus_host_address(self): |
2055 | + """Get (or try to get) the D-Bus server socket address. |
2056 | + |
2057 | + If the D-Bus server socket address environment variable is set, the |
2058 | + value of that environment variable is used, otherwise None is returned. |
2059 | + """ |
2060 | + return self.host_environ.get('DBUS_SESSION_BUS_ADDRESS', None) |
2061 | + |
2062 | + def _create_dbus_host_bridge(self): |
2063 | + """Create a socket bridge for the D-Bus session. |
2064 | + |
2065 | + Creates a socket bridge configuration for the D-Bus session, if a socket |
2066 | + can be found for the host, otherwise returns None. |
2067 | + """ |
2068 | + socket_bridge = None |
2069 | + server_address = self._get_dbus_host_address() |
2070 | + if server_address: |
2071 | + socket_bridge = SocketBridge('DBUS_SESSION_BUS_ADDRESS', |
2072 | + server_address, |
2073 | + self._generate_session_socket_name('dbus')) |
2074 | + return socket_bridge |
2075 | + |
2076 | + def _get_maliit_host_address(self): |
2077 | + """Get (or try to get) the Maliit server socket address. |
2078 | + |
2079 | + If the Maliit server socket address environment variable is set, the |
2080 | + value of that environment variable is used, otherwise an attempt is made |
2081 | + to query the session D-Bus. If neither works, Maliit is probably not |
2082 | + there. |
2083 | + """ |
2084 | + env_var = self.host_environ.get('MALIIT_SERVER_ADDRESS', None) |
2085 | + if env_var: |
2086 | + return env_var |
2087 | + return _get_maliit_address_from_dbus() |
2088 | + |
2089 | + def _create_maliit_host_bridge(self): |
2090 | + """Create a socket bridge for the Maliit server. |
2091 | + |
2092 | + Creates a socket bridge configuration for the Maliit server, if a socket |
2093 | + can be found for the host, otherwise returns None. |
2094 | + """ |
2095 | + socket_bridge = None |
2096 | + server_address = self._get_maliit_host_address() |
2097 | + if server_address: |
2098 | + socket_bridge = SocketBridge('MALIIT_SERVER_ADDRESS', |
2099 | + server_address, |
2100 | + self._generate_session_socket_name('maliit')) |
2101 | + return socket_bridge |
2102 | |
2103 | === added file 'python/libertine/launcher/session.py' |
2104 | --- python/libertine/launcher/session.py 1970-01-01 00:00:00 +0000 |
2105 | +++ python/libertine/launcher/session.py 2016-11-21 19:42:42 +0000 |
2106 | @@ -0,0 +1,358 @@ |
2107 | +# Copyright 2016 Canonical Ltd. |
2108 | +# |
2109 | +# This program is free software: you can redistribute it and/or modify it |
2110 | +# under the terms of the GNU General Public License version 3, as published |
2111 | +# by the Free Software Foundation. |
2112 | +# |
2113 | +# This program is distributed in the hope that it will be useful, but |
2114 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
2115 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
2116 | +# PURPOSE. See the GNU General Public License for more details. |
2117 | +# |
2118 | +# You should have received a copy of the GNU General Public License along |
2119 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
2120 | + |
2121 | +"""High-level interface for starting and running an application in Libertine.""" |
2122 | + |
2123 | +import os |
2124 | +import selectors |
2125 | +import signal |
2126 | +import struct |
2127 | +import sys |
2128 | + |
2129 | +from .config import Config |
2130 | +from .. import utils |
2131 | +from contextlib import ExitStack, suppress |
2132 | +from libertine.ContainersConfig import ContainersConfig |
2133 | +from psutil import STATUS_ZOMBIE |
2134 | +from socket import socket, AF_UNIX, SOCK_STREAM, SHUT_RDWR |
2135 | +from .task import LaunchServiceTask, TaskType |
2136 | + |
2137 | + |
2138 | +log = utils.get_logger() |
2139 | + |
2140 | + |
2141 | +def translate_to_real_address(abstract_address): |
2142 | + """Translate the notional text address to a real UNIX-domain address string. |
2143 | + |
2144 | + :param abstract_address: The human-readable abstract socket address string. |
2145 | + |
2146 | + Trims off any suffix starting with a comma and replaces any leading string of |
2147 | + 'unix:abstract=' with a zero byte. |
2148 | + """ |
2149 | + addr = abstract_address.split(',')[0] |
2150 | + if addr.startswith('unix:abstract='): |
2151 | + return "\0" + ' '.join(addr.split('=')[1:]) |
2152 | + elif addr.startswith('unix:path='): |
2153 | + return ' '.join(addr.split('=')[1:]) |
2154 | + |
2155 | + return addr |
2156 | + |
2157 | + |
2158 | +class BridgePair(object): |
2159 | + """A pair of sockets that make up a bridge between host and session.""" |
2160 | + |
2161 | + def __init__(self, session_socket, host_address): |
2162 | + """Create a pair of sockets bridging host and session. |
2163 | + |
2164 | + A socket bridge pair takes an (already-opened) session socket and the |
2165 | + address of the host socket and opens a connection to that. |
2166 | + """ |
2167 | + self.session_socket = session_socket |
2168 | + self.session_socket.setblocking(False) |
2169 | + |
2170 | + self.host_socket = socket(AF_UNIX, SOCK_STREAM) |
2171 | + self.host_socket.connect(translate_to_real_address(host_address)) |
2172 | + self.host_socket.setblocking(False) |
2173 | + |
2174 | + def handle_read_fd(self, fd, session): |
2175 | + """Handle read-available events on one of the sockets. |
2176 | + |
2177 | + :param fd: A file descriptor on which a read is available. |
2178 | + :type fd: int -- valid file descriptor. |
2179 | + :param session: A libertine application session object. |
2180 | + :type session: libertine.launcher.Session |
2181 | + |
2182 | + Callback to handle a read-available event on one of the bridge pair |
2183 | + socket fds. |
2184 | + """ |
2185 | + if fd == self.session_socket.fileno(): |
2186 | + if self._copy_data(self.session_socket, self.host_socket) == 0: |
2187 | + self._close_up_shop(session) |
2188 | + elif fd == self.host_socket.fileno(): |
2189 | + if self._copy_data(self.host_socket, self.session_socket) == 0: |
2190 | + self._close_up_shop(session) |
2191 | + |
2192 | + def _copy_data(self, from_socket, to_socket): |
2193 | + """Copy data between the sockets. |
2194 | + |
2195 | + :param from_socket: A socket to be read from. |
2196 | + :type from_socket: socket-object |
2197 | + :param to_socket: A socket to be written to. |
2198 | + :type to_socket: socket-object |
2199 | + |
2200 | + Note that this chunks the data 4 kilobytes at a time (which maybe should |
2201 | + be a tuneable parameter). |
2202 | + Also, it's a non-blocking write and that may affect things. Honestly, it |
2203 | + should be a non-blocking write and logic should be added to track |
2204 | + write-available events and unsent bytes and all that stuff but not today. |
2205 | + """ |
2206 | + try: |
2207 | + b = from_socket.recv(4096) |
2208 | + if len(b) > 0: |
2209 | + to_socket.sendall(b) |
2210 | + log.debug('copied {} bytes from fd to {}'.format(len(b), from_socket, to_socket)) |
2211 | + else: |
2212 | + log.info('close detected on {}'.format(from_socket)) |
2213 | + return len(b) |
2214 | + except Exception as e: |
2215 | + log.debug(e) |
2216 | + return 0 |
2217 | + |
2218 | + def _close_up_shop(self, session): |
2219 | + """Clean up. |
2220 | + |
2221 | + :param session: A libertine application session object. |
2222 | + :type session: libertine.launcher.Session |
2223 | + |
2224 | + Closes both sockets in the pair and calls back the session to remove |
2225 | + this object from its watch list. |
2226 | + """ |
2227 | + session.remove_bridge_pair(self) |
2228 | + self.session_socket.shutdown(SHUT_RDWR) |
2229 | + self.session_socket.close() |
2230 | + |
2231 | + self.host_socket.shutdown(SHUT_RDWR) |
2232 | + self.host_socket.close() |
2233 | + |
2234 | + |
2235 | +class Session(ExitStack): |
2236 | + """Encapsulation of a running application under a Libertine aegis. |
2237 | + |
2238 | + A session includes the following. |
2239 | + |
2240 | + * A cleansed and remapped set of environment variables. |
2241 | + * A collection of redirected sockets. |
2242 | + * Zero or more running auxiliary programs. |
2243 | + * An executable command line. |
2244 | + |
2245 | + A session must be associated with a single aegis (a container, a snap, or |
2246 | + the host itself, depending). The associated aegis is termed the *container* |
2247 | + for historical reasons. |
2248 | + |
2249 | + Each application run under a Libertine aegis must have its own session. |
2250 | + |
2251 | + A session is constructed in a 'stopped' state. It needs to be started and |
2252 | + the main loop entered, and once the main loop exits it gets torn down and |
2253 | + returned to a stopped state. |
2254 | + """ |
2255 | + |
2256 | + def __init__(self, config, container): |
2257 | + """Construct a libertine application session for a container. |
2258 | + |
2259 | + :param config: A session configuration object. |
2260 | + :param container: The container in which the application will be run. |
2261 | + """ |
2262 | + super().__init__() |
2263 | + self._app = None |
2264 | + self._config = config |
2265 | + self._container = container |
2266 | + self._bridge_pairs = [] |
2267 | + self._child_processes = [] |
2268 | + self._selector = selectors.DefaultSelector() |
2269 | + self._set_signal_handlers() |
2270 | + self.callback(self._shutdown) |
2271 | + |
2272 | + self._ensure_paths_exist() |
2273 | + |
2274 | + with suppress(AttributeError): |
2275 | + for bridge_config in self._config.socket_bridges: |
2276 | + self._create_bridge_listener(bridge_config) |
2277 | + |
2278 | + with suppress(AttributeError): |
2279 | + for task_config in self._config.prelaunch_tasks: |
2280 | + if task_config.task_type == TaskType.LAUNCH_SERVICE: |
2281 | + log.info("launching {}".format(task_config.datum[0])) |
2282 | + task = LaunchServiceTask(task_config) |
2283 | + self._child_processes.append(task) |
2284 | + task.start(self._config.host_environ) |
2285 | + |
2286 | + @property |
2287 | + def id(self): |
2288 | + """A unique string identifying this session.""" |
2289 | + return self._config.id |
2290 | + |
2291 | + def _add_read_fd_handler(self, fd, handler, datum): |
2292 | + """Add a handler to be called when a read event is received on fd. |
2293 | + |
2294 | + :param fd: A file descriptor to watch for read events. |
2295 | + :type fd: int -- valid file descriptor. |
2296 | + :param handler: A function to be called when a read on fd becomes available. |
2297 | + :param datum: Data to be passed to handler when called. |
2298 | + """ |
2299 | + self._selector.register(fd, selectors.EVENT_READ, (handler, datum)) |
2300 | + |
2301 | + def _remove_read_fd_handler(self, fd): |
2302 | + """Remove a handler used for reading events on an fd. |
2303 | + |
2304 | + :param fd: A file descriptor to be removed from watching read events. |
2305 | + """ |
2306 | + self._selector.unregister(fd) |
2307 | + |
2308 | + def add_bridge_pair(self, bridge_pair): |
2309 | + """Add a bridge pair to the list of those being monitored. |
2310 | + |
2311 | + :param bridge_pair: an active BridgePair object to add to the watch list. |
2312 | + """ |
2313 | + self._add_read_fd_handler(bridge_pair.session_socket.fileno(), |
2314 | + bridge_pair.handle_read_fd, |
2315 | + self) |
2316 | + self._add_read_fd_handler(bridge_pair.host_socket.fileno(), |
2317 | + bridge_pair.handle_read_fd, |
2318 | + self) |
2319 | + self._bridge_pairs.append(bridge_pair) |
2320 | + |
2321 | + def remove_bridge_pair(self, bridge_pair): |
2322 | + """Remove a bridge pair from the list of those being monitored. |
2323 | + |
2324 | + :param bridge_pair: an active BridgePair object to remove from the watch list. |
2325 | + """ |
2326 | + self._remove_read_fd_handler(bridge_pair.session_socket.fileno()) |
2327 | + self._remove_read_fd_handler(bridge_pair.host_socket.fileno()) |
2328 | + self._bridge_pairs.remove(bridge_pair) |
2329 | + |
2330 | + def run(self): |
2331 | + """Run the main event loop of the Session. |
2332 | + |
2333 | + The main loop monitors the various socket bridges and the contained |
2334 | + child process(es) and dispatches events appropriately. |
2335 | + |
2336 | + The event loop is generally terminated by the receipt of a StopIteration |
2337 | + exception. |
2338 | + """ |
2339 | + with suppress(StopIteration): |
2340 | + while True: |
2341 | + events = self._selector.select() |
2342 | + for key, mask in events: |
2343 | + handler, datum = key.data |
2344 | + handler(key.fd, datum) |
2345 | + self._container.finish_application(self._app) |
2346 | + self._stop_services() |
2347 | + |
2348 | + def start_application(self): |
2349 | + """Connect to the container and start the application running.""" |
2350 | + self._container.connect() |
2351 | + self.callback(self._container.disconnect) |
2352 | + self._add_running_app() |
2353 | + self._app = self._container.start_application(self._config.exec_line, |
2354 | + self._config.session_environ) |
2355 | + |
2356 | + def _add_running_app(self): |
2357 | + """Add a running app entry to ContainersConfig.json.""" |
2358 | + if self._config.container_id: |
2359 | + ContainersConfig().add_running_app(self._config.container_id, self._config.exec_line[0]) |
2360 | + |
2361 | + def _remove_running_app(self): |
2362 | + """Remove a running app entry from ContainersConfig.json.""" |
2363 | + if self._config.container_id: |
2364 | + ContainersConfig().delete_running_app(self._config.container_id, self._config.exec_line[0]) |
2365 | + |
2366 | + def _create_bridge_listener(self, bridge_config): |
2367 | + """Create a socket bridge listener for a socket bridge configuration. |
2368 | + |
2369 | + :param bridge_config: A socket bridge configuration object. |
2370 | + :type bridge_config: libertine.launcher.config.SocketBridge |
2371 | + |
2372 | + The purpose of the listener is to listen on the session socket and |
2373 | + create a socket bridge to the host when a connection from the session is |
2374 | + made. |
2375 | + """ |
2376 | + log.debug('creating bridge listener for {} on {}'. |
2377 | + format(bridge_config.env_var, bridge_config.session_address)) |
2378 | + sock = socket(AF_UNIX, SOCK_STREAM) |
2379 | + sock.bind(translate_to_real_address(bridge_config.session_address)) |
2380 | + sock.listen(5) |
2381 | + self._add_read_fd_handler(sock.fileno(), |
2382 | + self._accept_bridge_connection, |
2383 | + (bridge_config, sock)) |
2384 | + |
2385 | + def _accept_bridge_connection(self, fd, datum): |
2386 | + """Handle a connection on a session socket. |
2387 | + |
2388 | + :param fd: A file descriptor with an active connect operation in |
2389 | + progress. |
2390 | + :type fd: int -- valid file descriptor. |
2391 | + :param datum: Data passed to handler. |
2392 | + """ |
2393 | + (bridge_config, sock) = datum |
2394 | + conn = sock.accept() |
2395 | + log.debug('connection of session socket {} accepted'.format(bridge_config.session_address)) |
2396 | + self.add_bridge_pair(BridgePair(conn[0], bridge_config.host_address)) |
2397 | + |
2398 | + def _ensure_paths_exist(self): |
2399 | + """Ensure the required paths all exist for supporting the session.""" |
2400 | + directory = utils.get_libertine_runtime_dir() |
2401 | + if not os.path.exists(directory): |
2402 | + os.makedirs(directory) |
2403 | + |
2404 | + def _handle_child_died(self): |
2405 | + """Take action when a SIGCHILD has been raised.""" |
2406 | + for child in self._child_processes: |
2407 | + if child.wait(): |
2408 | + return True |
2409 | + |
2410 | + if self._app == None or self._app.status() == STATUS_ZOMBIE: |
2411 | + return True |
2412 | + |
2413 | + return False |
2414 | + |
2415 | + def _handle_sig_fd(self, fd, dummy): |
2416 | + """Handle read events for captured signals. |
2417 | + |
2418 | + :param fd: A file descriptor on which signal information will be sent. |
2419 | + :type fd: int -- valid file descriptor. |
2420 | + :param dummy: A dummy parameter. Required for magic. |
2421 | + """ |
2422 | + data = os.read(fd, 4) |
2423 | + sig = struct.unpack('%uB' % len(data), data) |
2424 | + if sig[0] == signal.SIGCHLD: |
2425 | + log.info('SIGCHLD received') |
2426 | + if self._handle_child_died(): |
2427 | + raise StopIteration('launched program exited') |
2428 | + elif sig[0] == signal.SIGINT: |
2429 | + log.info('SIGINT received') |
2430 | + raise StopIteration('keyboard interrupt') |
2431 | + elif sig[0] == signal.SIGTERM: |
2432 | + log.info('SIGTERM received') |
2433 | + raise StopIteration('terminate') |
2434 | + else: |
2435 | + log.warning('unknown signal {} received'.format(sig[0])) |
2436 | + |
2437 | + def _set_signal_handlers(self): |
2438 | + """Set the signal handlers.""" |
2439 | + def noopSignalHandler(*args): |
2440 | + pass |
2441 | + self._sigchld_handler = signal.signal(signal.SIGCHLD, noopSignalHandler) |
2442 | + self._sigint_handler = signal.signal(signal.SIGINT, noopSignalHandler) |
2443 | + self._sigterm_handler = signal.signal(signal.SIGTERM, noopSignalHandler) |
2444 | + |
2445 | + sig_r_fd, sig_w_fd = os.pipe2(os.O_NONBLOCK | os.O_CLOEXEC) |
2446 | + signal.set_wakeup_fd(sig_w_fd) |
2447 | + self._add_read_fd_handler(sig_r_fd, self._handle_sig_fd, None) |
2448 | + |
2449 | + def _shutdown(self): |
2450 | + """Restore the previous state to the world when the Session is torn down.""" |
2451 | + signal.signal(signal.SIGCHLD, self._sigchld_handler) |
2452 | + signal.signal(signal.SIGINT, self._sigint_handler) |
2453 | + signal.signal(signal.SIGTERM, self._sigterm_handler) |
2454 | + |
2455 | + if self._config.container_id: |
2456 | + self._remove_running_app() |
2457 | + |
2458 | + for bridge_pair in self._config.socket_bridges: |
2459 | + os.remove(translate_to_real_address(bridge_pair.session_address)) |
2460 | + |
2461 | + def _stop_services(self): |
2462 | + """Ask any started services to stop.""" |
2463 | + for service in self._child_processes: |
2464 | + service.stop() |
2465 | |
2466 | === added file 'python/libertine/launcher/task.py' |
2467 | --- python/libertine/launcher/task.py 1970-01-01 00:00:00 +0000 |
2468 | +++ python/libertine/launcher/task.py 2016-11-21 19:42:42 +0000 |
2469 | @@ -0,0 +1,75 @@ |
2470 | +# Copyright 2016 Canonical Ltd. |
2471 | +# |
2472 | +# This program is free software: you can redistribute it and/or modify it |
2473 | +# under the terms of the GNU General Public License version 3, as published |
2474 | +# by the Free Software Foundation. |
2475 | +# |
2476 | +# This program is distributed in the hope that it will be useful, but |
2477 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
2478 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
2479 | +# PURPOSE. See the GNU General Public License for more details. |
2480 | +# |
2481 | +# You should have received a copy of the GNU General Public License along |
2482 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
2483 | + |
2484 | +"""Pre- and post-launch tasks surrounding the Libertine launch of an application.""" |
2485 | + |
2486 | + |
2487 | +from os import waitpid, WNOHANG |
2488 | +from subprocess import Popen |
2489 | + |
2490 | + |
2491 | +class TaskType: |
2492 | + """Namespace used for task type enumeration.""" |
2493 | + |
2494 | + LAUNCH_SERVICE = 1 |
2495 | + |
2496 | + |
2497 | +class TaskConfig(object): |
2498 | + """Encapsulation of the configuration of a single launch task.""" |
2499 | + |
2500 | + def __init__(self, task_type, datum): |
2501 | + """ |
2502 | + :param task_type: The type of launch task. |
2503 | + :tyoe task_type: a TaskType value |
2504 | + :param datum: The data associated with the launch task. |
2505 | + :type datum: anything -- usually a collection of data |
2506 | + """ |
2507 | + self.task_type = task_type |
2508 | + self.datum = datum |
2509 | + |
2510 | + |
2511 | +class LaunchServiceTask(object): |
2512 | + """A task that launches a service.""" |
2513 | + |
2514 | + def __init__(self, config): |
2515 | + """ |
2516 | + :param config: The task configuraiton. |
2517 | + :type config: TaskConfig |
2518 | + |
2519 | + The constructor unpacks the service commandline from the config datum. |
2520 | + """ |
2521 | + self._command_line = config.datum |
2522 | + self._process = None |
2523 | + |
2524 | + def start(self, environ=None): |
2525 | + """Start the service. |
2526 | + |
2527 | + :param env: An alternate environment dictionary. |
2528 | + """ |
2529 | + self._process = Popen(self._command_line, env=environ) |
2530 | + |
2531 | + def stop(self): |
2532 | + """Shuts the service down. """ |
2533 | + try: |
2534 | + self._process.terminate() |
2535 | + except ProcessLookupError: |
2536 | + pass |
2537 | + |
2538 | + def wait(self): |
2539 | + """Wait for service shutdown to complete. |
2540 | + :return: True if the service process was successfully waited for, False |
2541 | + otherwise (which implies the service is still running). |
2542 | + """ |
2543 | + (pid, status) = waitpid(self._process.pid, WNOHANG) |
2544 | + return pid == self._process.pid |
2545 | |
2546 | === modified file 'python/libertine/utils.py' |
2547 | --- python/libertine/utils.py 2016-09-06 12:36:47 +0000 |
2548 | +++ python/libertine/utils.py 2016-11-21 19:42:42 +0000 |
2549 | @@ -1,4 +1,3 @@ |
2550 | -#!/usr/bin/python3 |
2551 | # -*- coding: utf-8 -*- |
2552 | |
2553 | # Copyright (C) 2015-2016 Canonical Ltd. |
2554 | @@ -31,24 +30,19 @@ |
2555 | |
2556 | # If someone else sets a handler before this, we wont run this! |
2557 | if not logger.hasHandlers(): |
2558 | - logger.setLevel(logging.DEBUG) |
2559 | - logger.disabled = True |
2560 | - |
2561 | stream_handler = logging.StreamHandler() |
2562 | - stream_handler.setLevel(logging.DEBUG) |
2563 | - |
2564 | formatter = logging.Formatter('%(filename)s:' |
2565 | '%(lineno)d: ' |
2566 | '%(levelname)s: ' |
2567 | '%(funcName)s():\t' |
2568 | '%(message)s') |
2569 | - |
2570 | stream_handler.setFormatter(formatter) |
2571 | logger.addHandler(stream_handler) |
2572 | |
2573 | - # Only enable the logger if we set this |
2574 | - if os.getenv('LIBERTINE_DEBUG') is '1': |
2575 | - logger.disabled = False |
2576 | + if 'LIBERTINE_DEBUG' in os.environ: |
2577 | + logger.setLevel(logging.DEBUG) |
2578 | + else: |
2579 | + logger.setLevel(logging.WARNING) |
2580 | |
2581 | return logger |
2582 | |
2583 | @@ -173,3 +167,20 @@ |
2584 | (scopes_object_path, invalidate_signal, libertine_scope_id)) |
2585 | |
2586 | subprocess.Popen(shlex.split(gdbus_cmd), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) |
2587 | + |
2588 | + |
2589 | +def set_session_dbus_env_var(): |
2590 | + if not 'DBUS_SESSION_BUS_ADDRESS' in os.environ: |
2591 | + dbus_session_path = os.path.join('/', 'run', 'user', str(os.getuid()), 'dbus-session') |
2592 | + |
2593 | + if os.path.exists(dbus_session_path): |
2594 | + with open(dbus_session_path, 'r') as fd: |
2595 | + dbus_session_str = fd.read() |
2596 | + |
2597 | + os.environ['DBUS_SESSION_BUS_ADDRESS'] = dbus_session_str.partition('DBUS_SESSION_BUS_ADDRESS=')[2].rstrip('\n') |
2598 | + |
2599 | + return True |
2600 | + else: |
2601 | + return False |
2602 | + |
2603 | + return True |
2604 | |
2605 | === added directory 'qml' |
2606 | === added file 'qml/CMakeLists.txt' |
2607 | --- qml/CMakeLists.txt 1970-01-01 00:00:00 +0000 |
2608 | +++ qml/CMakeLists.txt 2016-11-21 19:42:42 +0000 |
2609 | @@ -0,0 +1,3 @@ |
2610 | +install(DIRECTORY common DESTINATION ${LIBERTINE_QML_PATH}) |
2611 | +install(DIRECTORY gui DESTINATION ${LIBERTINE_QML_PATH}) |
2612 | +install(DIRECTORY plugin DESTINATION ${LIBERTINE_QML_PATH}) |
2613 | |
2614 | === added directory 'qml/common' |
2615 | === renamed file 'libertine/qml/AddExtraArchiveView.qml' => 'qml/common/AddExtraArchiveView.qml' |
2616 | --- libertine/qml/AddExtraArchiveView.qml 2016-07-13 19:48:48 +0000 |
2617 | +++ qml/common/AddExtraArchiveView.qml 2016-11-21 19:42:42 +0000 |
2618 | @@ -28,6 +28,8 @@ |
2619 | } |
2620 | property string currentContainer: "" |
2621 | |
2622 | + signal error(string short_description, string details) |
2623 | + |
2624 | Column { |
2625 | spacing: units.gu(2) |
2626 | |
2627 | @@ -96,9 +98,9 @@ |
2628 | } |
2629 | |
2630 | function addArchive() { |
2631 | - var worker = Qt.createComponent("ContainerManager.qml").createObject(mainView) |
2632 | + var worker = Qt.createComponent("ContainerManager.qml").createObject(parent) |
2633 | worker.finishedConfigure.connect(finishedConfigure) |
2634 | - worker.error.connect(mainView.error) |
2635 | + worker.error.connect(addExtraArchiveView.error) |
2636 | worker.addArchive(currentContainer, containerConfigList.getContainerName(currentContainer), |
2637 | extraArchiveString.text, publicSigningKey.text.trim()) |
2638 | |
2639 | |
2640 | === renamed file 'libertine/qml/HomeView.qml' => 'qml/common/ContainerEditView.qml' |
2641 | --- libertine/qml/HomeView.qml 2016-07-06 14:17:46 +0000 |
2642 | +++ qml/common/ContainerEditView.qml 2016-11-21 19:42:42 +0000 |
2643 | @@ -26,7 +26,7 @@ |
2644 | id: homeView |
2645 | header: PageHeader { |
2646 | id: pageHeader |
2647 | - title: i18n.tr("Classic Apps - %1").arg(containerConfigList.getContainerName(currentContainer)) |
2648 | + title: i18n.tr("%1 - All Apps").arg(containerConfigList.getContainerName(currentContainer)) |
2649 | trailingActionBar.actions: [ |
2650 | Action { |
2651 | id: settingsButton |
2652 | @@ -153,7 +153,7 @@ |
2653 | width: parent.width |
2654 | onClicked: { |
2655 | PopupUtils.close(addAppsDialog) |
2656 | - PopupUtils.open(Qt.resolvedUrl("SearchPackagesDialog.qml")) |
2657 | + PopupUtils.open(Qt.resolvedUrl("SearchPackagesDialog.qml"), {currentContainer: currentContainer}) |
2658 | } |
2659 | } |
2660 | } |
2661 | @@ -249,12 +249,11 @@ |
2662 | } |
2663 | |
2664 | function operationSetup() { |
2665 | - var comp = Qt.createComponent("ContainerManager.qml") |
2666 | - var worker = comp.createObject(mainView) |
2667 | - worker.error.connect(mainView.error) |
2668 | - worker.updateOperationDetails.connect(mainView.updateOperationDetails) |
2669 | - mainView.packageOperationInteraction.connect(worker.packageOperationInteraction) |
2670 | - worker.operationFinished.connect(mainView.resetOperationDetails) |
2671 | + var worker = Qt.createComponent("ContainerManager.qml").createObject(parent) |
2672 | + worker.error.connect(packageOperationDetails.error) |
2673 | + worker.updateOperationDetails.connect(packageOperationDetails.update) |
2674 | + packageOperationDetails.send.connect(worker.packageOperationInteraction) |
2675 | + worker.operationFinished.connect(packageOperationDetails.clear) |
2676 | return worker |
2677 | } |
2678 | |
2679 | |
2680 | === renamed file 'libertine/qml/ContainerInfoView.qml' => 'qml/common/ContainerInfoView.qml' |
2681 | --- libertine/qml/ContainerInfoView.qml 2016-06-09 18:20:19 +0000 |
2682 | +++ qml/common/ContainerInfoView.qml 2016-11-21 19:42:42 +0000 |
2683 | @@ -27,7 +27,7 @@ |
2684 | id: containerInfoView |
2685 | header: PageHeader { |
2686 | id: pageHeader |
2687 | - title: i18n.tr("Container information for %1").arg(containerConfigList.getContainerName(currentContainer)) |
2688 | + title: i18n.tr("Container Info: %1").arg(containerConfigList.getContainerName(currentContainer)) |
2689 | } |
2690 | |
2691 | property string currentContainer: null |
2692 | @@ -114,19 +114,17 @@ |
2693 | Component.onCompleted: { |
2694 | containerConfigList.configChanged.connect(reloadStatus) |
2695 | |
2696 | - var worker = Qt.createComponent("ContainerManager.qml").createObject(mainView) |
2697 | + var worker = Qt.createComponent("ContainerManager.qml").createObject(parent) |
2698 | + operationDetails = packageOperationDetails.details(currentContainer, "") |
2699 | + packageOperationDetails.updated.connect(updateDetails) |
2700 | |
2701 | - operationDetails = mainView.getOperationDetails(currentContainer) |
2702 | operationDetailsView.cursorPosition = operationDetailsView.length |
2703 | - if (operationDetails != "") { |
2704 | + if (operationDetails !== "") { |
2705 | showDetails = !showDetails |
2706 | } |
2707 | - |
2708 | - mainView.operationDetailsUpdated.connect(updateDetails) |
2709 | } |
2710 | |
2711 | Component.onDestruction: { |
2712 | - mainView.operationDetailsUpdated.disconnect(updateDetails) |
2713 | containerConfigList.configChanged.disconnect(reloadStatus) |
2714 | } |
2715 | |
2716 | |
2717 | === renamed file 'libertine/qml/ContainerManager.qml' => 'qml/common/ContainerManager.qml' |
2718 | === renamed file 'libertine/qml/ContainerOptionsDialog.qml' => 'qml/common/ContainerOptionsDialog.qml' |
2719 | --- libertine/qml/ContainerOptionsDialog.qml 2016-09-02 18:26:25 +0000 |
2720 | +++ qml/common/ContainerOptionsDialog.qml 2016-11-21 19:42:42 +0000 |
2721 | @@ -30,7 +30,7 @@ |
2722 | signal onCreateInitialized() |
2723 | |
2724 | Row { |
2725 | - visible: containerConfigList.getHostArchitecture() == 'x86_64' ? true : false |
2726 | + visible: containerConfigList.getHostArchitecture() === 'x86_64' ? true : false |
2727 | spacing: units.gu(1) |
2728 | CheckBox { |
2729 | id: enableMultiarchCheckbox |
2730 | @@ -97,11 +97,11 @@ |
2731 | |
2732 | function createContainer() { |
2733 | var container_id = containerConfigList.addNewContainer("lxc", containerNameInput.text) |
2734 | - var worker = Qt.createComponent("ContainerManager.qml").createObject(mainView) |
2735 | + var worker = Qt.createComponent("ContainerManager.qml").createObject(parent) |
2736 | |
2737 | - worker.updateOperationDetails.connect(mainView.updateOperationDetails) |
2738 | - worker.operationFinished.connect(mainView.resetOperationDetails) |
2739 | - worker.error.connect(mainView.error) |
2740 | + worker.updateOperationDetails.connect(packageOperationDetails.update) |
2741 | + worker.operationFinished.connect(packageOperationDetails.clear) |
2742 | + worker.error.connect(packageOperationDetails.error) |
2743 | |
2744 | worker.createContainer(container_id, |
2745 | containerConfigList.getContainerName(container_id), |
2746 | |
2747 | === added file 'qml/common/ContainersList.qml' |
2748 | --- qml/common/ContainersList.qml 1970-01-01 00:00:00 +0000 |
2749 | +++ qml/common/ContainersList.qml 2016-11-21 19:42:42 +0000 |
2750 | @@ -0,0 +1,131 @@ |
2751 | +/** |
2752 | + * @file ContainersListView.qml |
2753 | + * @brief Libertine containers view |
2754 | + */ |
2755 | +/* |
2756 | + * Copyright 2016 Canonical Ltd |
2757 | + * |
2758 | + * Libertine is free software: you can redistribute it and/or modify it under |
2759 | + * the terms of the GNU General Public License, version 3, as published by the |
2760 | + * Free Software Foundation. |
2761 | + * |
2762 | + * Libertine is distributed in the hope that it will be useful, but WITHOUT ANY |
2763 | + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR |
2764 | + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
2765 | + * |
2766 | + * You should have received a copy of the GNU General Public License |
2767 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
2768 | + */ |
2769 | +import Libertine 1.0 |
2770 | +import QtQuick 2.4 |
2771 | +import Ubuntu.Components 1.3 |
2772 | +import Ubuntu.Components.Popups 1.3 |
2773 | + |
2774 | + |
2775 | +/** |
2776 | + * Component providing a list of available containers and their (possibly animated) |
2777 | + * states. |
2778 | + */ |
2779 | +Item { |
2780 | + property string currentContainer |
2781 | + |
2782 | + UbuntuListView { |
2783 | + id: containersList |
2784 | + anchors { |
2785 | + fill: parent |
2786 | + } |
2787 | + model: containerConfigList |
2788 | + |
2789 | + function edit(id, status) { |
2790 | + if (status === "removing") { |
2791 | + packageOperationDetails.error(i18n.tr("Container Unavailable"), i18n.tr("Container is being destroyed and is no longer editable.")) |
2792 | + return |
2793 | + } |
2794 | + currentContainer = id |
2795 | + containerAppsList.setContainerApps(currentContainer) |
2796 | + pageStack.push(Qt.resolvedUrl("../common/ContainerEditView.qml"), {currentContainer: currentContainer}) |
2797 | + } |
2798 | + |
2799 | + delegate: ListItem { |
2800 | + Label { |
2801 | + text: name |
2802 | + anchors { |
2803 | + verticalCenter: parent.verticalCenter |
2804 | + left: parent.left |
2805 | + leftMargin: units.gu(2) |
2806 | + } |
2807 | + } |
2808 | + ActivityIndicator { |
2809 | + id: containerActivity |
2810 | + anchors { |
2811 | + verticalCenter: parent.verticalCenter |
2812 | + right: parent.right |
2813 | + rightMargin: units.gu(2) |
2814 | + } |
2815 | + visible: installStatus === i18n.tr("installing") || |
2816 | + installStatus === i18n.tr("removing") |
2817 | + running: containerActivity.visible |
2818 | + } |
2819 | + |
2820 | + onClicked: { containersList.edit(containerId, installStatus) } |
2821 | + |
2822 | + leadingActions: ListItemActions { |
2823 | + actions: [ |
2824 | + Action { |
2825 | + iconName: "delete" |
2826 | + text: i18n.tr("delete") |
2827 | + description: i18n.tr("Delete Container") |
2828 | + onTriggered: { |
2829 | + var worker = Qt.createComponent("../common/ContainerManager.qml").createObject(parent) |
2830 | + worker.error.connect(packageOperationDetails.error) |
2831 | + worker.destroyContainer(containerId) |
2832 | + } |
2833 | + } |
2834 | + ] |
2835 | + } |
2836 | + |
2837 | + trailingActions: ListItemActions { |
2838 | + actions: [ |
2839 | + Action { |
2840 | + iconName: "info" |
2841 | + text: i18n.tr("info") |
2842 | + description: i18n.tr("Container Info") |
2843 | + onTriggered: { |
2844 | + currentContainer = containerId |
2845 | + pageStack.push(Qt.resolvedUrl("../common/ContainerInfoView.qml"), {currentContainer: containerId}) |
2846 | + } |
2847 | + }, |
2848 | + Action { |
2849 | + iconName: "edit" |
2850 | + text: i18n.tr("edit") |
2851 | + description: i18n.tr("Container Apps") |
2852 | + visible: installStatus === i18n.tr("ready") || |
2853 | + installStatus === i18n.tr("updating") |
2854 | + onTriggered: { |
2855 | + containersList.edit(containerId, installStatus) |
2856 | + } |
2857 | + } |
2858 | + ] |
2859 | + } |
2860 | + } |
2861 | + } |
2862 | + |
2863 | + Component.onCompleted: { |
2864 | + containerConfigList.configChanged.connect(updateContainerList) |
2865 | + } |
2866 | + |
2867 | + Component.onDestruction: { |
2868 | + containerConfigList.configChanged.disconnect(updateContainerList) |
2869 | + } |
2870 | + |
2871 | + function updateContainerList() { |
2872 | + containerConfigList.reloadContainerList() |
2873 | + |
2874 | + if (currentContainer && !containerConfigList.getContainerStatus(currentContainer) && pageStack.currentPage !== containersView) { |
2875 | + pageStack.pop() |
2876 | + currentContainer = "" |
2877 | + packageOperationDetails.error(i18n.tr("Container Unavailable"), |
2878 | + i18n.tr("This container has been destroyed and is no longer valid. You have been returned to the containers overview.")) |
2879 | + } |
2880 | + } |
2881 | +} |
2882 | |
2883 | === renamed file 'libertine/qml/DebianPackagePicker.qml' => 'qml/common/DebianPackagePicker.qml' |
2884 | === renamed file 'libertine/qml/ExtraArchivesView.qml' => 'qml/common/ExtraArchivesView.qml' |
2885 | --- libertine/qml/ExtraArchivesView.qml 2016-09-29 15:40:02 +0000 |
2886 | +++ qml/common/ExtraArchivesView.qml 2016-11-21 19:42:42 +0000 |
2887 | @@ -96,7 +96,7 @@ |
2888 | function deleteArchive(archive) { |
2889 | var worker = Qt.createComponent("ContainerManager.qml").createObject(mainView) |
2890 | worker.finishedConfigure.connect(finishedConfigure) |
2891 | - worker.error.connect(mainView.error) |
2892 | + worker.error.connect(packageOperationDetails.error) |
2893 | worker.configureContainer(currentContainer, containerConfigList.getContainerName(currentContainer), ["--archive", "remove", "--archive-name", "\"" + archive + "\""]) |
2894 | } |
2895 | |
2896 | |
2897 | === renamed file 'libertine/qml/GenericErrorDialog.qml' => 'qml/common/GenericErrorDialog.qml' |
2898 | === renamed file 'libertine/qml/ManageContainer.qml' => 'qml/common/ManageContainer.qml' |
2899 | --- libertine/qml/ManageContainer.qml 2016-07-11 14:35:11 +0000 |
2900 | +++ qml/common/ManageContainer.qml 2016-11-21 19:42:42 +0000 |
2901 | @@ -46,14 +46,14 @@ |
2902 | anchors.right: parent.right |
2903 | |
2904 | ListItem.Standard { |
2905 | - visible: containerConfigList.getHostArchitecture() == 'x86_64' ? true : false |
2906 | + visible: containerConfigList.getHostArchitecture() === 'x86_64' ? true : false |
2907 | control: CheckBox { |
2908 | checked: isMultiarchEnabled |
2909 | onClicked: { |
2910 | - var worker = Qt.createComponent("ContainerManager.qml").createObject(mainView) |
2911 | + var worker = Qt.createComponent("ContainerManager.qml").createObject(parent) |
2912 | |
2913 | - worker.updateOperationDetails.connect(mainView.updateOperationDetails) |
2914 | - worker.operationFinished.connect(mainView.resetOperationDetails) |
2915 | + worker.updateOperationDetails.connect(packageOperationDetails.update) |
2916 | + worker.operationFinished.connect(packageOperationDetails.clear) |
2917 | |
2918 | if (checked) { |
2919 | worker.configureContainer(currentContainer, |
2920 | @@ -105,12 +105,14 @@ |
2921 | control: CheckBox { |
2922 | checked: isDefaultContainer |
2923 | onClicked: { |
2924 | - var worker = Qt.createComponent("ContainerManager.qml").createObject(mainView) |
2925 | - worker.error.connect(mainView.error) |
2926 | + var worker = Qt.createComponent("ContainerManager.qml").createObject(parent) |
2927 | + worker.error.connect(packageOperationDetails.error) |
2928 | + |
2929 | var fallback = checked |
2930 | worker.error.connect(function() { |
2931 | checked = fallback |
2932 | }) |
2933 | + |
2934 | worker.setDefaultContainer(currentContainer, !checked) |
2935 | } |
2936 | } |
2937 | @@ -129,11 +131,11 @@ |
2938 | } |
2939 | |
2940 | function updateContainer() { |
2941 | - var worker = Qt.createComponent("ContainerManager.qml").createObject(mainView) |
2942 | - worker.error.connect(mainView.error); |
2943 | + var worker = Qt.createComponent("ContainerManager.qml").createObject(parent) |
2944 | + worker.error.connect(packageOperationDetails.error); |
2945 | |
2946 | - worker.updateOperationDetails.connect(mainView.updateOperationDetails) |
2947 | - worker.operationFinished.connect(mainView.resetOperationDetails) |
2948 | + worker.updateOperationDetails.connect(packageOperationDetails.update) |
2949 | + worker.operationFinished.connect(packageOperationDetails.clear) |
2950 | |
2951 | worker.updateContainer(currentContainer, containerConfigList.getContainerName(currentContainer)) |
2952 | } |
2953 | |
2954 | === renamed file 'libertine/qml/PackageExistsDialog.qml' => 'qml/common/PackageExistsDialog.qml' |
2955 | === renamed file 'libertine/qml/PackageInfoView.qml' => 'qml/common/PackageInfoView.qml' |
2956 | --- libertine/qml/PackageInfoView.qml 2016-06-10 13:22:16 +0000 |
2957 | +++ qml/common/PackageInfoView.qml 2016-11-21 19:42:42 +0000 |
2958 | @@ -27,19 +27,18 @@ |
2959 | id: packageInfoView |
2960 | header: PageHeader { |
2961 | id: pageHeader |
2962 | - title: i18n.tr("Information for the %1 package").arg(currentPackage) |
2963 | + title: i18n.tr("%1 - %2").arg(currentContainer).arg(currentPackage) |
2964 | } |
2965 | property string currentContainer: null |
2966 | property var currentPackage: null |
2967 | property var statusText: containerConfigList.getAppStatus(currentContainer, currentPackage) |
2968 | property var packageVersionText: i18n.tr("Obtaining package version…") |
2969 | - property string packageOperationDetails: "" |
2970 | + property string currentDetails: "" |
2971 | property var worker: null |
2972 | property bool showDetails: false |
2973 | |
2974 | signal sendOperationInteraction(string text) |
2975 | |
2976 | - |
2977 | Flickable { |
2978 | anchors { |
2979 | topMargin: pageHeader.height |
2980 | @@ -77,7 +76,7 @@ |
2981 | text: enabled ? |
2982 | showDetails ? i18n.tr('Hide') : i18n.tr('Show') |
2983 | : i18n.tr('None') |
2984 | - enabled: packageOperationDetails != "" |
2985 | + enabled: currentDetails != "" |
2986 | onClicked: { |
2987 | showDetails = !showDetails |
2988 | } |
2989 | @@ -92,7 +91,7 @@ |
2990 | anchors.right: parent.right |
2991 | height: Math.max(packageInfoView.height - pageHeader.height - packageListItem.height - showDetailsView.height - statusListItem.height - 35, units.gu(35)) |
2992 | readOnly: true |
2993 | - text: packageOperationDetails |
2994 | + text: currentDetails |
2995 | } |
2996 | |
2997 | TextField { |
2998 | @@ -112,31 +111,32 @@ |
2999 | Component.onCompleted: { |
3000 | containerConfigList.configChanged.connect(reloadStatus) |
3001 | var command = "apt-cache policy " + currentPackage |
3002 | - var worker = Qt.createComponent("ContainerManager.qml").createObject(mainView) |
3003 | + var worker = Qt.createComponent("ContainerManager.qml").createObject(parent) |
3004 | worker.finishedCommand.connect(getPackageVersion) |
3005 | |
3006 | - packageOperationDetails = mainView.getOperationDetails(currentContainer, currentPackage) |
3007 | + currentDetails = packageOperationDetails.details(currentContainer, currentPackage) |
3008 | packageDetailsView.cursorPosition = packageDetailsView.length |
3009 | - if (packageOperationDetails != "") { |
3010 | + if (currentDetails != "") { |
3011 | showDetails = !showDetails |
3012 | } |
3013 | |
3014 | - mainView.operationDetailsUpdated.connect(updatePackageDetails) |
3015 | - sendOperationInteraction.connect(mainView.packageOperationInteraction) |
3016 | + packageOperationDetails.updated.connect(updatePackageDetails) |
3017 | + sendOperationInteraction.connect(packageOperationDetails.send) |
3018 | |
3019 | worker.error.connect(onError) |
3020 | + worker.error.connect(packageOperationDetails.error) |
3021 | worker.runCommand(currentContainer, containerConfigList.getContainerName(currentContainer), command) |
3022 | } |
3023 | |
3024 | Component.onDestruction: { |
3025 | containerConfigList.configChanged.disconnect(reloadStatus) |
3026 | - mainView.operationDetailsUpdated.disconnect(updatePackageDetails) |
3027 | - sendOperationInteraction.disconnect(mainView.packageOperationInteraction) |
3028 | + packageOperationDetails.updated.disconnect(updatePackageDetails) |
3029 | + sendOperationInteraction.disconnect(packageOperationDetails.send) |
3030 | } |
3031 | |
3032 | function updatePackageDetails(container_id, package_name, details) { |
3033 | if (container_id === currentContainer && package_name === currentPackage) { |
3034 | - packageOperationDetails += details |
3035 | + currentDetails += details |
3036 | packageDetailsView.cursorPosition = packageDetailsView.length |
3037 | } |
3038 | } |
3039 | |
3040 | === renamed file 'libertine/qml/SearchPackagesDialog.qml' => 'qml/common/SearchPackagesDialog.qml' |
3041 | --- libertine/qml/SearchPackagesDialog.qml 2016-05-06 21:13:01 +0000 |
3042 | +++ qml/common/SearchPackagesDialog.qml 2016-11-21 19:42:42 +0000 |
3043 | @@ -25,6 +25,7 @@ |
3044 | id: searchPackageDialog |
3045 | title: i18n.tr("Search for packages") |
3046 | text: i18n.tr("Search archives for packages") |
3047 | + property string currentContainer: null |
3048 | |
3049 | TextField { |
3050 | id: searchPackageInput |
3051 | @@ -42,11 +43,11 @@ |
3052 | onClicked: { |
3053 | if (searchPackageInput.text != "") { |
3054 | PopupUtils.close(searchPackageDialog) |
3055 | - if (pageStack.currentPage.objectName == "searchResultsView") { |
3056 | + if (pageStack.currentPage.objectName === "searchResultsView") { |
3057 | pageStack.currentPage.searchForPackages(searchPackageInput.text) |
3058 | } |
3059 | else { |
3060 | - pageStack.push(Qt.resolvedUrl("SearchResultsView.qml"), {search_string : searchPackageInput.text}) |
3061 | + pageStack.push(Qt.resolvedUrl("SearchResultsView.qml"), {search_string : searchPackageInput.text, currentContainer: currentContainer}) |
3062 | } |
3063 | } |
3064 | } |
3065 | |
3066 | === renamed file 'libertine/qml/SearchResults.qml' => 'qml/common/SearchResults.qml' |
3067 | --- libertine/qml/SearchResults.qml 2016-05-10 19:26:22 +0000 |
3068 | +++ qml/common/SearchResults.qml 2016-11-21 19:42:42 +0000 |
3069 | @@ -28,8 +28,10 @@ |
3070 | fill: parent |
3071 | } |
3072 | |
3073 | + property var currentContainer: null |
3074 | + |
3075 | function install(packageName) { |
3076 | - if (!containerConfigList.isAppInstalled(mainView.currentContainer, packageName)) { |
3077 | + if (!containerConfigList.isAppInstalled(currentContainer, packageName)) { |
3078 | pageStack.pop() |
3079 | pageStack.currentPage.installPackage(packageName) |
3080 | } |
3081 | |
3082 | === renamed file 'libertine/qml/SearchResultsView.qml' => 'qml/common/SearchResultsView.qml' |
3083 | --- libertine/qml/SearchResultsView.qml 2016-05-19 17:56:43 +0000 |
3084 | +++ qml/common/SearchResultsView.qml 2016-11-21 19:42:42 +0000 |
3085 | @@ -43,6 +43,8 @@ |
3086 | property var search_string: null |
3087 | property var search_comp: null |
3088 | property var search_obj: null |
3089 | + property var currentContainer: null |
3090 | + |
3091 | signal doSearch |
3092 | |
3093 | Component { |
3094 | @@ -50,7 +52,7 @@ |
3095 | Dialog { |
3096 | id: noResultsDialog |
3097 | title: i18n.tr("No Search Results Found") |
3098 | - property var returnHome: false |
3099 | + property bool returnHome: false |
3100 | |
3101 | Button { |
3102 | id: searchAgain |
3103 | @@ -58,7 +60,7 @@ |
3104 | color: UbuntuColors.green |
3105 | onClicked: { |
3106 | PopupUtils.close(noResultsDialog) |
3107 | - PopupUtils.open(Qt.resolvedUrl("SearchPackagesDialog.qml")) |
3108 | + PopupUtils.open(Qt.resolvedUrl("SearchPackagesDialog.qml"), {currentContainer: currentContainer}) |
3109 | } |
3110 | } |
3111 | |
3112 | @@ -118,11 +120,11 @@ |
3113 | } |
3114 | packageListModel.clear() |
3115 | |
3116 | - var comp = Qt.createComponent("ContainerManager.qml") |
3117 | - var worker = comp.createObject() |
3118 | + var worker = Qt.createComponent("ContainerManager.qml").createObject(parent) |
3119 | worker.finishedSearch.connect(finishedSearch) |
3120 | - worker.error.connect(mainView.error) |
3121 | - worker.searchPackageCache(mainView.currentContainer, search_string) |
3122 | + worker.error.connect(packageOperationDetails.error) |
3123 | + |
3124 | + worker.searchPackageCache(currentContainer, search_string) |
3125 | } |
3126 | |
3127 | function finishedSearch(packageList) { |
3128 | @@ -133,14 +135,14 @@ |
3129 | packageListModel.append({"package_desc": packageList[i], "package_name": packageList[i].split(' ')[0]}) |
3130 | } |
3131 | if (!search_comp) { |
3132 | - search_comp = Qt.createComponent("SearchResults.qml") |
3133 | + search_comp = Qt.createComponent("SearchResults.qml", {currentContainer: currentContainer}) |
3134 | } |
3135 | - search_obj = search_comp.createObject(searchResultsView, {"model": packageListModel}) |
3136 | + search_obj = search_comp.createObject(parent, {"model": packageListModel}) |
3137 | } |
3138 | else { |
3139 | PopupUtils.open(noResultsPopup) |
3140 | } |
3141 | } |
3142 | |
3143 | - onDoSearch: PopupUtils.open(Qt.resolvedUrl("SearchPackagesDialog.qml")) |
3144 | + onDoSearch: PopupUtils.open(Qt.resolvedUrl("SearchPackagesDialog.qml"), {currentContainer: currentContainer}) |
3145 | } |
3146 | |
3147 | === added directory 'qml/gui' |
3148 | === renamed file 'libertine/qml/ContainersView.qml' => 'qml/gui/ContainersView.qml' |
3149 | --- libertine/qml/ContainersView.qml 2016-08-22 18:20:43 +0000 |
3150 | +++ qml/gui/ContainersView.qml 2016-11-21 19:42:42 +0000 |
3151 | @@ -20,6 +20,7 @@ |
3152 | import QtQuick 2.4 |
3153 | import Ubuntu.Components 1.3 |
3154 | import Ubuntu.Components.Popups 1.3 |
3155 | +import "../common" |
3156 | |
3157 | |
3158 | /** |
3159 | @@ -35,115 +36,19 @@ |
3160 | Action { |
3161 | iconName: "add" |
3162 | onTriggered: { |
3163 | - PopupUtils.open(Qt.resolvedUrl("ContainerOptionsDialog.qml")) |
3164 | + PopupUtils.open(Qt.resolvedUrl("../common/ContainerOptionsDialog.qml")) |
3165 | } |
3166 | } |
3167 | ] |
3168 | - leadingActionBar.actions: [ |
3169 | - Action { |
3170 | - iconName: "back" |
3171 | - visible: false |
3172 | - } |
3173 | - ] |
3174 | } |
3175 | + property string currentContainer |
3176 | |
3177 | - UbuntuListView { |
3178 | - id: containersList |
3179 | + ContainersList { |
3180 | anchors { |
3181 | topMargin: pageHeader.height |
3182 | - fill: parent |
3183 | - } |
3184 | - model: containerConfigList |
3185 | - |
3186 | - function edit(id, status) { |
3187 | - if (status === "removing") { |
3188 | - mainView.error(i18n.tr("Container Unavailable"), i18n.tr("Container is being destroyed and is no longer editable.")) |
3189 | - return |
3190 | - } |
3191 | - mainView.currentContainer = id |
3192 | - containerAppsList.setContainerApps(mainView.currentContainer) |
3193 | - pageStack.push(Qt.resolvedUrl("HomeView.qml"), {"currentContainer": mainView.currentContainer}) |
3194 | - } |
3195 | - |
3196 | - delegate: ListItem { |
3197 | - Label { |
3198 | - text: name |
3199 | - anchors { |
3200 | - verticalCenter: parent.verticalCenter |
3201 | - left: parent.left |
3202 | - leftMargin: units.gu(2) |
3203 | - } |
3204 | - } |
3205 | - ActivityIndicator { |
3206 | - id: containerActivity |
3207 | - anchors { |
3208 | - verticalCenter: parent.verticalCenter |
3209 | - right: parent.right |
3210 | - rightMargin: units.gu(2) |
3211 | - } |
3212 | - visible: (installStatus === i18n.tr("installing") || |
3213 | - installStatus === i18n.tr("removing")) ? true : false |
3214 | - running: containerActivity.visible |
3215 | - } |
3216 | - |
3217 | - onClicked: { containersList.edit(containerId, installStatus) } |
3218 | - |
3219 | - leadingActions: ListItemActions { |
3220 | - actions: [ |
3221 | - Action { |
3222 | - iconName: "delete" |
3223 | - text: i18n.tr("delete") |
3224 | - description: i18n.tr("Delete Container") |
3225 | - onTriggered: { |
3226 | - var worker = Qt.createComponent("ContainerManager.qml").createObject(mainView) |
3227 | - worker.error.connect(mainView.error) |
3228 | - worker.destroyContainer(containerId) |
3229 | - } |
3230 | - } |
3231 | - ] |
3232 | - } |
3233 | - |
3234 | - trailingActions: ListItemActions { |
3235 | - actions: [ |
3236 | - Action { |
3237 | - iconName: "info" |
3238 | - text: i18n.tr("info") |
3239 | - description: i18n.tr("Container Info") |
3240 | - onTriggered: { |
3241 | - mainView.currentContainer = containerId |
3242 | - pageStack.push(Qt.resolvedUrl("ContainerInfoView.qml"), {currentContainer: containerId}) |
3243 | - } |
3244 | - }, |
3245 | - Action { |
3246 | - iconName: "edit" |
3247 | - text: i18n.tr("edit") |
3248 | - description: i18n.tr("Container Apps") |
3249 | - visible: (installStatus === i18n.tr("ready") || |
3250 | - installStatus === i18n.tr("updating")) ? true : false |
3251 | - onTriggered: { |
3252 | - containersList.edit(containerId, installStatus) |
3253 | - } |
3254 | - } |
3255 | - ] |
3256 | - } |
3257 | - } |
3258 | - } |
3259 | - |
3260 | - Component.onCompleted: { |
3261 | - containerConfigList.configChanged.connect(updateContainerList) |
3262 | - } |
3263 | - |
3264 | - Component.onDestruction: { |
3265 | - containerConfigList.configChanged.disconnect(updateContainerList) |
3266 | - } |
3267 | - |
3268 | - function updateContainerList() { |
3269 | - containerConfigList.reloadContainerList() |
3270 | - |
3271 | - if (mainView.currentContainer && !containerConfigList.getContainerStatus(mainView.currentContainer) && pageStack.currentPage !== containersView) { |
3272 | - pageStack.pop() |
3273 | - mainView.currentContainer = "" |
3274 | - mainView.error(i18n.tr("Container Unavailable"), i18n.tr("This container has been destroyed and is no longer valid. You have been returned to the containers overview.")) |
3275 | - } |
3276 | + fill: containersView |
3277 | + } |
3278 | + |
3279 | + currentContainer: containersView.currentContainer |
3280 | } |
3281 | } |
3282 | |
3283 | === renamed file 'libertine/qml/WelcomeView.qml' => 'qml/gui/WelcomeView.qml' |
3284 | --- libertine/qml/WelcomeView.qml 2016-09-07 20:43:08 +0000 |
3285 | +++ qml/gui/WelcomeView.qml 2016-11-21 19:42:42 +0000 |
3286 | @@ -49,6 +49,7 @@ |
3287 | |
3288 | text: i18n.tr("Welcome to the Ubuntu Legacy Application Support Manager.") |
3289 | } |
3290 | + |
3291 | Label { |
3292 | id: warningMessage |
3293 | Layout.fillWidth: true |
3294 | @@ -67,13 +68,13 @@ |
3295 | color: UbuntuColors.green |
3296 | |
3297 | onClicked: { |
3298 | - var dialog = PopupUtils.open(Qt.resolvedUrl("ContainerOptionsDialog.qml")) |
3299 | + var dialog = PopupUtils.open(Qt.resolvedUrl("../common/ContainerOptionsDialog.qml")) |
3300 | dialog.onCreateInitialized.connect(createInitialized) |
3301 | } |
3302 | } |
3303 | } |
3304 | |
3305 | function createInitialized() { |
3306 | - pageStack.push(Qt.resolvedUrl("ContainersView.qml")) |
3307 | + pageStack.push(Qt.resolvedUrl("ContainersView.qml"), {currentContainer: ""}) |
3308 | } |
3309 | } |
3310 | |
3311 | === renamed file 'libertine/qml/libertine.qml' => 'qml/gui/libertine.qml' |
3312 | --- libertine/qml/libertine.qml 2016-06-09 18:20:19 +0000 |
3313 | +++ qml/gui/libertine.qml 2016-11-21 19:42:42 +0000 |
3314 | @@ -28,27 +28,25 @@ |
3315 | applicationName: "libertine" |
3316 | width: units.gu(90) |
3317 | height: units.gu(75) |
3318 | - property var currentContainer: undefined |
3319 | - property var operationDetails: undefined |
3320 | |
3321 | signal error(string short_description, string details) |
3322 | - signal operationDetailsUpdated(string container_id, string package_name, string details) |
3323 | - signal packageOperationInteraction(string data) |
3324 | |
3325 | PageStack { |
3326 | id: pageStack |
3327 | } |
3328 | |
3329 | Component.onCompleted: { |
3330 | - Qt.createComponent("ContainerManager.qml").createObject(mainView).fixIntegrity() |
3331 | - |
3332 | - mainView.currentContainer = containerConfigList.defaultContainerId |
3333 | + packageOperationDetails.error.connect(error) |
3334 | + |
3335 | + Qt.createComponent("../common/ContainerManager.qml").createObject(mainView).fixIntegrity() |
3336 | + |
3337 | + var currentContainer = containerConfigList.defaultContainerId |
3338 | |
3339 | if (!containerConfigList.empty()) { |
3340 | - pageStack.push(Qt.resolvedUrl("ContainersView.qml")) |
3341 | - if (mainView.currentContainer) { |
3342 | - containerAppsList.setContainerApps(mainView.currentContainer) |
3343 | - pageStack.push(Qt.resolvedUrl("HomeView.qml"), {currentContainer: mainView.currentContainer}) |
3344 | + pageStack.push(Qt.resolvedUrl("ContainersView.qml"), {currentContainer: currentContainer}) |
3345 | + if (currentContainer) { |
3346 | + containerAppsList.setContainerApps(currentContainer) |
3347 | + pageStack.push(Qt.resolvedUrl("../common/ContainerEditView.qml"), {currentContainer: currentContainer}) |
3348 | } |
3349 | } |
3350 | else { |
3351 | @@ -57,63 +55,7 @@ |
3352 | } |
3353 | |
3354 | onError: { |
3355 | - PopupUtils.open(Qt.resolvedUrl("GenericErrorDialog.qml"), null, |
3356 | + PopupUtils.open(Qt.resolvedUrl("../common/GenericErrorDialog.qml"), null, |
3357 | {"short_description": short_description, "details": details}) |
3358 | } |
3359 | - |
3360 | - function initializeDetails(container_id, package_name) { |
3361 | - if (!operationDetails) { |
3362 | - operationDetails = {} |
3363 | - } |
3364 | - if (!operationDetails[container_id]) { |
3365 | - operationDetails[container_id] = {packages: {}, details: ""} |
3366 | - } |
3367 | - if (!operationDetails[container_id].details) { |
3368 | - operationDetails[container_id].details = "" |
3369 | - } |
3370 | - if (package_name && !operationDetails[container_id].packages[package_name]) { |
3371 | - operationDetails[container_id].packages[package_name] = "" |
3372 | - } |
3373 | - } |
3374 | - |
3375 | - function updateOperationDetails(container_id, package_name, details) { |
3376 | - if (!mainView) { |
3377 | - return |
3378 | - } |
3379 | - |
3380 | - initializeDetails(container_id, package_name) |
3381 | - |
3382 | - if (package_name) { |
3383 | - operationDetails[container_id].packages[package_name] += details |
3384 | - } else { |
3385 | - operationDetails[container_id].details += details |
3386 | - } |
3387 | - |
3388 | - operationDetailsUpdated(container_id, package_name, details) |
3389 | - } |
3390 | - |
3391 | - function resetOperationDetails(container_id, package_name) { |
3392 | - if (mainView && operationDetails && operationDetails[container_id]) { |
3393 | - if (package_name) { |
3394 | - if (operationDetails[container_id].packages[package_name]) { |
3395 | - delete operationDetails[container_id].packages[package_name] |
3396 | - } |
3397 | - } else { |
3398 | - delete operationDetails[container_id].details |
3399 | - } |
3400 | - } |
3401 | - } |
3402 | - |
3403 | - function getOperationDetails(container_id, package_name) { |
3404 | - if (operationDetails && operationDetails[container_id]) { |
3405 | - if (package_name) { |
3406 | - if (operationDetails[container_id].packages[package_name]) { |
3407 | - return operationDetails[container_id].packages[package_name] |
3408 | - } |
3409 | - } else if (operationDetails[container_id].details) { |
3410 | - return operationDetails[container_id].details |
3411 | - } |
3412 | - } |
3413 | - return "" |
3414 | - } |
3415 | } |
3416 | |
3417 | === added directory 'qml/plugin' |
3418 | === added file 'qml/plugin/MainSettingsPage.qml' |
3419 | --- qml/plugin/MainSettingsPage.qml 1970-01-01 00:00:00 +0000 |
3420 | +++ qml/plugin/MainSettingsPage.qml 2016-11-21 19:42:42 +0000 |
3421 | @@ -0,0 +1,58 @@ |
3422 | +/* |
3423 | + * Copyright (C) 2016 Canonical Ltd. |
3424 | + * |
3425 | + * This program is free software: you can redistribute it and/or modify it |
3426 | + * under the terms of the GNU General Public License version 3, as published |
3427 | + * by the Free Software Foundation. |
3428 | + * |
3429 | + * This program is distributed in the hope that it will be useful, but |
3430 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
3431 | + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
3432 | + * PURPOSE. See the GNU General Public License for more details. |
3433 | + * |
3434 | + * You should have received a copy of the GNU General Public License along |
3435 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
3436 | + */ |
3437 | + |
3438 | +import QtQuick 2.4 |
3439 | +import Ubuntu.Components 1.3 |
3440 | +import Ubuntu.Components.Popups 1.3 |
3441 | +import SystemSettings 1.0 |
3442 | +import Libertine 1.0 |
3443 | +import "../common" |
3444 | + |
3445 | +ItemPage { |
3446 | + id: mainView |
3447 | + |
3448 | + header: PageHeader { |
3449 | + id: pageHeader |
3450 | + title: i18n.tr("Manage Libertine Containers") |
3451 | + trailingActionBar.actions: [ |
3452 | + Action { |
3453 | + iconName: "add" |
3454 | + onTriggered: { |
3455 | + PopupUtils.open(Qt.resolvedUrl("../common/ContainerOptionsDialog.qml")) |
3456 | + } |
3457 | + } |
3458 | + ] |
3459 | + } |
3460 | + |
3461 | + property string currentContainer |
3462 | + property var operationDetails |
3463 | + |
3464 | + signal error(string short_description, string details) |
3465 | + |
3466 | + ContainersList { |
3467 | + anchors { |
3468 | + topMargin: pageHeader.height |
3469 | + fill: mainView |
3470 | + } |
3471 | + |
3472 | + currentContainer: mainView.currentContainer |
3473 | + } |
3474 | + |
3475 | + onError: { |
3476 | + PopupUtils.open(Qt.resolvedUrl("../common/GenericErrorDialog.qml"), null, |
3477 | + {"short_description": short_description, "details": details}) |
3478 | + } |
3479 | +} |
3480 | |
3481 | === added directory 'system-settings-plugin' |
3482 | === added file 'system-settings-plugin/CMakeLists.txt' |
3483 | --- system-settings-plugin/CMakeLists.txt 1970-01-01 00:00:00 +0000 |
3484 | +++ system-settings-plugin/CMakeLists.txt 2016-11-21 19:42:42 +0000 |
3485 | @@ -0,0 +1,38 @@ |
3486 | +set(PLUGIN_MANIFEST_DIR_BASE share/ubuntu/settings/system) |
3487 | +set(PLUGIN_MODULE_DIR_BASE ubuntu-system-settings) |
3488 | + |
3489 | +set(PLUGIN_MANIFEST_DIR "${CMAKE_INSTALL_PREFIX}/${PLUGIN_MANIFEST_DIR_BASE}") |
3490 | +set(PLUGIN_MODULE_DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/${PLUGIN_MODULE_DIR_BASE}") |
3491 | +set(PLUGIN_QML_DIR "${CMAKE_INSTALL_PREFIX}/share/ubuntu/settings/system/qml-plugins/libertine") |
3492 | + |
3493 | +SET(CMAKE_INSTALL_RPATH "${PLUGIN_MODULE_DIR}") |
3494 | + |
3495 | +configure_file(config.h.in config.h) |
3496 | + |
3497 | +set(LIBERTINE_PLUGIN_ICON_PATH "${PLUGIN_MANIFEST_DIR}/icons") |
3498 | +set(PLUGIN_SETTINGS_TARGET libertine.settings) |
3499 | +configure_file(${PLUGIN_SETTINGS_TARGET}.in ${PLUGIN_SETTINGS_TARGET}) |
3500 | + |
3501 | +set (CMAKE_INCLUDE_CURRENT_DIR ON) |
3502 | +set (CMAKE_AUTOMOC ON) |
3503 | + |
3504 | +# For some reason, building on vivid seems to require that we manually |
3505 | +# add /usr/include to the include directories when using automoc here. |
3506 | +set (CMAKE_AUTOMOC_MOC_OPTIONS -I/usr/include) |
3507 | + |
3508 | +pkg_check_modules(SYSTEM_SETTINGS REQUIRED SystemSettings) |
3509 | + |
3510 | +file(GLOB_RECURSE PLUGIN_QML_SRC |
3511 | + ${CMAKE_SOURCE_DIR}/qml/common/*.qml |
3512 | + ${CMAKE_SOURCE_DIR}/qml/plugin/*.qml |
3513 | +) |
3514 | + |
3515 | +add_library(libertine-plugin MODULE plugin.cpp plugin.h ${PLUGIN_QML_SRC}) |
3516 | +qt5_use_modules(libertine-plugin Qml Quick) |
3517 | + |
3518 | +include_directories(${SYSTEM_SETTINGS_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/libertine) |
3519 | +target_link_libraries(libertine-plugin ${LIBERTINE_COMMON} ${SYSTEM_SETTINGS_LIBRARIES}) |
3520 | + |
3521 | +install(TARGETS libertine-plugin LIBRARY DESTINATION ${PLUGIN_MODULE_DIR} NAMELINK_SKIP) |
3522 | +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${PLUGIN_SETTINGS_TARGET} DESTINATION ${PLUGIN_MANIFEST_DIR}) |
3523 | +install(FILES libertine-plugin.png DESTINATION ${LIBERTINE_PLUGIN_ICON_PATH}) |
3524 | |
3525 | === added file 'system-settings-plugin/config.h.in' |
3526 | --- system-settings-plugin/config.h.in 1970-01-01 00:00:00 +0000 |
3527 | +++ system-settings-plugin/config.h.in 2016-11-21 19:42:42 +0000 |
3528 | @@ -0,0 +1,16 @@ |
3529 | +/* |
3530 | + * Copyright 2016 Canonical Ltd |
3531 | + * |
3532 | + * Libertine is free software: you can redistribute it and/or modify it under |
3533 | + * the terms of the GNU General Public License, version 3, as published by the |
3534 | + * Free Software Foundation. |
3535 | + * |
3536 | + * Libertine is distributed in the hope that it will be useful, but WITHOUT ANY |
3537 | + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR |
3538 | + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
3539 | + * |
3540 | + * You should have received a copy of the GNU General Public License |
3541 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
3542 | + */ |
3543 | + |
3544 | +#define LIBERTINE_PLUGIN_QML_DIR "@LIBERTINE_QML_PATH@/plugin" |
3545 | |
3546 | === added file 'system-settings-plugin/libertine-plugin.png' |
3547 | Binary files system-settings-plugin/libertine-plugin.png 1970-01-01 00:00:00 +0000 and system-settings-plugin/libertine-plugin.png 2016-11-21 19:42:42 +0000 differ |
3548 | === added file 'system-settings-plugin/libertine.settings.in' |
3549 | --- system-settings-plugin/libertine.settings.in 1970-01-01 00:00:00 +0000 |
3550 | +++ system-settings-plugin/libertine.settings.in 2016-11-21 19:42:42 +0000 |
3551 | @@ -0,0 +1,14 @@ |
3552 | +{ |
3553 | + "name": "Libertine", |
3554 | + "icon": "${LIBERTINE_PLUGIN_ICON_PATH}/libertine-plugin.png", |
3555 | + "translations": "libertine", |
3556 | + "category": "system", |
3557 | + "keywords": [ |
3558 | + "libertine", |
3559 | + "legacy", |
3560 | + "x11" |
3561 | + ], |
3562 | + "has-dynamic-keywords": false, |
3563 | + "has-dynamic-visibility": false, |
3564 | + "plugin": "libertine-plugin" |
3565 | +} |
3566 | |
3567 | === added file 'system-settings-plugin/plugin.cpp' |
3568 | --- system-settings-plugin/plugin.cpp 1970-01-01 00:00:00 +0000 |
3569 | +++ system-settings-plugin/plugin.cpp 2016-11-21 19:42:42 +0000 |
3570 | @@ -0,0 +1,105 @@ |
3571 | +/* |
3572 | + * Copyright (C) 2016 Canonical Ltd. |
3573 | + * |
3574 | + * This program is free software: you can redistribute it and/or modify it |
3575 | + * under the terms of the GNU General Public License version 3, as published |
3576 | + * by the Free Software Foundation. |
3577 | + * |
3578 | + * This program is distributed in the hope that it will be useful, but |
3579 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
3580 | + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
3581 | + * PURPOSE. See the GNU General Public License for more details. |
3582 | + * |
3583 | + * You should have received a copy of the GNU General Public License along |
3584 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
3585 | + */ |
3586 | + |
3587 | +#include "plugin.h" |
3588 | + |
3589 | +#include "config.h" |
3590 | +#include "common/ContainerManager.h" |
3591 | +#include "common/ContainerAppsList.h" |
3592 | +#include "common/ContainerArchivesList.h" |
3593 | +#include "common/ContainerConfigList.h" |
3594 | +#include "common/LibertineConfig.h" |
3595 | +#include "common/ContainerConfig.h" |
3596 | +#include "common/PackageOperationDetails.h" |
3597 | +#include <memory> |
3598 | +#include <QQmlEngine> |
3599 | +#include <QQmlContext> |
3600 | +#include <QFileSystemWatcher> |
3601 | +#include <SystemSettings/ItemBase> |
3602 | + |
3603 | +using namespace SystemSettings; |
3604 | + |
3605 | +class LibertineItem: public ItemBase |
3606 | +{ |
3607 | + Q_OBJECT |
3608 | + |
3609 | +public: |
3610 | + explicit LibertineItem(const QVariantMap &staticData, QObject *parent = 0); |
3611 | + virtual ~LibertineItem() = default; |
3612 | + |
3613 | + virtual QQmlComponent* pageComponent(QQmlEngine *engine, |
3614 | + QObject *parent = 0) override; |
3615 | + |
3616 | +private: |
3617 | + std::unique_ptr<LibertineConfig> config_; |
3618 | + ContainerConfigList* containers_; |
3619 | + ContainerAppsList* container_apps_; |
3620 | + ContainerArchivesList* container_archives_; |
3621 | + PackageOperationDetails* package_operation_details_; |
3622 | + QFileSystemWatcher watcher_; |
3623 | + |
3624 | +private slots: |
3625 | + void reload_config(QString const&); |
3626 | +}; |
3627 | + |
3628 | +LibertineItem:: |
3629 | +LibertineItem(const QVariantMap &staticData, QObject *parent) |
3630 | + : ItemBase(staticData, parent) |
3631 | + , config_(new LibertineConfig()) |
3632 | + , containers_(new ContainerConfigList(config_.get(), this)) |
3633 | + , container_apps_(new ContainerAppsList(containers_, this)) |
3634 | + , container_archives_(new ContainerArchivesList(containers_, this)) |
3635 | + , package_operation_details_(new PackageOperationDetails(this)) |
3636 | + , watcher_({config_->containers_config_file_name()}) |
3637 | +{ |
3638 | + qmlRegisterType<ContainerConfig>("Libertine", 1, 0, "ContainerConfig"); |
3639 | + qmlRegisterType<ContainerManagerWorker>("Libertine", 1, 0, "ContainerManagerWorker"); |
3640 | + qmlRegisterType<PackageOperationDetails>("Libertine", 1, 0, "PackageOperationDetails"); |
3641 | + |
3642 | + connect(&watcher_, &QFileSystemWatcher::fileChanged, this, &LibertineItem::reload_config); |
3643 | +} |
3644 | + |
3645 | +QQmlComponent *LibertineItem:: |
3646 | +pageComponent(QQmlEngine *engine, QObject *parent) |
3647 | +{ |
3648 | + auto ctxt = engine->rootContext(); |
3649 | + ctxt->setContextProperty("containerConfigList", containers_); |
3650 | + ctxt->setContextProperty("containerAppsList", container_apps_); |
3651 | + ctxt->setContextProperty("containerArchivesList", container_archives_); |
3652 | + ctxt->setContextProperty("packageOperationDetails", package_operation_details_); |
3653 | + |
3654 | + auto component = new QQmlComponent(engine, |
3655 | + QUrl(LIBERTINE_PLUGIN_QML_DIR "/MainSettingsPage.qml"), |
3656 | + parent); |
3657 | + return component; |
3658 | +} |
3659 | + |
3660 | + |
3661 | +void LibertineItem:: |
3662 | +reload_config(QString const&) |
3663 | +{ |
3664 | + containers_->reloadConfigs(); |
3665 | +} |
3666 | + |
3667 | + |
3668 | +ItemBase *LibertinePlugin:: |
3669 | +createItem(const QVariantMap &staticData, |
3670 | + QObject *parent) |
3671 | +{ |
3672 | + return new LibertineItem(staticData, parent); |
3673 | +} |
3674 | + |
3675 | +#include "plugin.moc" |
3676 | |
3677 | === added file 'system-settings-plugin/plugin.h' |
3678 | --- system-settings-plugin/plugin.h 1970-01-01 00:00:00 +0000 |
3679 | +++ system-settings-plugin/plugin.h 2016-11-21 19:42:42 +0000 |
3680 | @@ -0,0 +1,34 @@ |
3681 | +/* |
3682 | + * Copyright (C) 2016 Canonical Ltd. |
3683 | + * |
3684 | + * This program is free software: you can redistribute it and/or modify it |
3685 | + * under the terms of the GNU General Public License version 3, as published |
3686 | + * by the Free Software Foundation. |
3687 | + * |
3688 | + * This program is distributed in the hope that it will be useful, but |
3689 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
3690 | + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
3691 | + * PURPOSE. See the GNU General Public License for more details. |
3692 | + * |
3693 | + * You should have received a copy of the GNU General Public License along |
3694 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
3695 | + */ |
3696 | +#pragma once |
3697 | + |
3698 | +#include <QObject> |
3699 | +#include <SystemSettings/PluginInterface> |
3700 | + |
3701 | +class LibertinePlugin: |
3702 | + public QObject, |
3703 | + public SystemSettings::PluginInterface2 |
3704 | +{ |
3705 | + Q_OBJECT |
3706 | + Q_PLUGIN_METADATA(IID "com.ubuntu.SystemSettings.PluginInterface/2.0") |
3707 | + Q_INTERFACES(SystemSettings::PluginInterface2) |
3708 | + |
3709 | +public: |
3710 | + explicit LibertinePlugin() = default; |
3711 | + |
3712 | + SystemSettings::ItemBase *createItem(const QVariantMap &staticData, |
3713 | + QObject *parent = 0); |
3714 | +}; |
3715 | |
3716 | === modified file 'tests/unit/CMakeLists.txt' |
3717 | --- tests/unit/CMakeLists.txt 2016-08-09 22:37:08 +0000 |
3718 | +++ tests/unit/CMakeLists.txt 2016-11-21 19:42:42 +0000 |
3719 | @@ -19,59 +19,14 @@ |
3720 | test_container_config |
3721 | ) |
3722 | |
3723 | -add_test(test_libertine_public_gir |
3724 | - "/usr/bin/python3" "-m" "testtools.run" "libertine_public_gir_tests" |
3725 | -) |
3726 | -set_tests_properties(test_libertine_public_gir |
3727 | - PROPERTIES |
3728 | - ENVIRONMENT |
3729 | - "GI_TYPELIB_PATH=${CMAKE_BINARY_DIR}/liblibertine;LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/liblibertine:${LD_LIBRARY_PATH};CMAKE_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR};PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}") |
3730 | -set_tests_properties(test_libertine_public_gir |
3731 | - PROPERTIES |
3732 | - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") |
3733 | - |
3734 | -add_test(test_libertine "/usr/bin/python3" "-m" "testtools.run" "discover" "-s" "${CMAKE_CURRENT_SOURCE_DIR}") |
3735 | +add_test(test_libertine "py.test-3" "${CMAKE_CURRENT_SOURCE_DIR}" "-v" "--junit-xml=test_libertine.xml" "--ignore=${CMAKE_CURRENT_SOURCE_DIR}/test_launcher_with_dbus.py") |
3736 | set_tests_properties(test_libertine |
3737 | - PROPERTIES |
3738 | - ENVIRONMENT |
3739 | - "GI_TYPELIB_PATH=${CMAKE_BINARY_DIR}/liblibertine;LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/liblibertine:${LD_LIBRARY_PATH};PYTHONPATH=${CMAKE_SOURCE_DIR}/python") |
3740 | - |
3741 | -add_test(test_libertine_container_manager |
3742 | - "/usr/bin/python3" "-m" "testtools.run" "libertine_container_manager_tests" |
3743 | -) |
3744 | -set_tests_properties(test_libertine_container_manager |
3745 | - PROPERTIES |
3746 | - ENVIRONMENT |
3747 | - "GI_TYPELIB_PATH=${CMAKE_BINARY_DIR}/liblibertine;LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/liblibertine:${LD_LIBRARY_PATH};CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR};PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}:${CMAKE_SOURCE_DIR}/python;CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR}") |
3748 | - |
3749 | -add_test(test_libertine_launch |
3750 | - "/usr/bin/python3" "-m" "testtools.run" "libertine_launch_tests" |
3751 | -) |
3752 | -set_tests_properties(test_libertine_launch |
3753 | - PROPERTIES |
3754 | - ENVIRONMENT |
3755 | - "GI_TYPELIB_PATH=${CMAKE_BINARY_DIR}/liblibertine;LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/liblibertine:${LD_LIBRARY_PATH};CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR};PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}:${CMAKE_SOURCE_DIR}/python;CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR}") |
3756 | - |
3757 | -add_test(test_libertine_session_bridge |
3758 | - "/usr/bin/python3" "-m" "testtools.run" "libertine_session_bridge_tests" |
3759 | -) |
3760 | -set_tests_properties(test_libertine_session_bridge |
3761 | - PROPERTIES |
3762 | - ENVIRONMENT |
3763 | - "GI_TYPELIB_PATH=${CMAKE_BINARY_DIR}/liblibertine;LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/liblibertine:${LD_LIBRARY_PATH};CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR};PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}:${CMAKE_SOURCE_DIR}/python;CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR}") |
3764 | - |
3765 | -add_test(test_libertine_socket |
3766 | - "/usr/bin/python3" "-m" "testtools.run" "libertine_socket_tests" |
3767 | -) |
3768 | -set_tests_properties(test_libertine_socket |
3769 | - PROPERTIES |
3770 | - ENVIRONMENT |
3771 | - "GI_TYPELIB_PATH=${CMAKE_BINARY_DIR}/liblibertine;LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/liblibertine:${LD_LIBRARY_PATH};CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR};PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}:${CMAKE_SOURCE_DIR}/python;CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR}") |
3772 | - |
3773 | -add_test(test_logger |
3774 | - "/usr/bin/python3" "-m" "testtools.run" "libertine_logger_tests" |
3775 | -) |
3776 | -set_tests_properties(test_logger |
3777 | - PROPERTIES |
3778 | - ENVIRONMENT |
3779 | - "GI_TYPELIB_PATH=${CMAKE_BINARY_DIR}/liblibertine;LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/liblibertine:${LD_LIBRARY_PATH};CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR};PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}:${CMAKE_SOURCE_DIR}/python;CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR}") |
3780 | + PROPERTIES ENVIRONMENT |
3781 | + "GI_TYPELIB_PATH=${CMAKE_BINARY_DIR}/liblibertine;LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/liblibertine:${LD_LIBRARY_PATH};CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR};PYTHONPATH=${CMAKE_SOURCE_DIR}/python;CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR};LIBERTINE_DATA_DIR=${CMAKE_CURRENT_SOURCE_DIR}") |
3782 | + |
3783 | +add_test(test_libertine_with_dbus "py.test-3" "${CMAKE_CURRENT_SOURCE_DIR}/test_launcher_with_dbus.py" "-v" "--junit-xml=test_libertine_with_dbus.xml" ) |
3784 | +set_tests_properties(test_libertine_with_dbus |
3785 | + PROPERTIES |
3786 | + ENVIRONMENT |
3787 | + "GI_TYPELIB_PATH=${CMAKE_BINARY_DIR}/liblibertine;LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/liblibertine:${LD_LIBRARY_PATH};CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR};PYTHONPATH=${CMAKE_SOURCE_DIR}/python;CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR}") |
3788 | + |
3789 | |
3790 | === modified file 'tests/unit/ContainerConfigListTests.cpp' |
3791 | --- tests/unit/ContainerConfigListTests.cpp 2015-09-15 15:03:38 +0000 |
3792 | +++ tests/unit/ContainerConfigListTests.cpp 2016-11-21 19:42:42 +0000 |
3793 | @@ -19,7 +19,7 @@ |
3794 | */ |
3795 | #include <gtest/gtest.h> |
3796 | |
3797 | -#include "libertine/ContainerConfigList.h" |
3798 | +#include "common/ContainerConfigList.h" |
3799 | #include <QtCore/QByteArray> |
3800 | #include <QtCore/QJsonDocument> |
3801 | #include <QtCore/QJsonParseError> |
3802 | |
3803 | === modified file 'tests/unit/ContainerConfigTests.cpp' |
3804 | --- tests/unit/ContainerConfigTests.cpp 2016-01-21 21:42:28 +0000 |
3805 | +++ tests/unit/ContainerConfigTests.cpp 2016-11-21 19:42:42 +0000 |
3806 | @@ -19,7 +19,7 @@ |
3807 | */ |
3808 | #include <gtest/gtest.h> |
3809 | |
3810 | -#include "libertine/ContainerConfig.h" |
3811 | +#include "common/ContainerConfig.h" |
3812 | #include <QtCore/QByteArray> |
3813 | #include <QtCore/QJsonDocument> |
3814 | #include <QtCore/QJsonParseError> |
3815 | |
3816 | === removed file 'tests/unit/libertine_public_gir_tests.py' |
3817 | --- tests/unit/libertine_public_gir_tests.py 2015-10-08 14:49:00 +0000 |
3818 | +++ tests/unit/libertine_public_gir_tests.py 1970-01-01 00:00:00 +0000 |
3819 | @@ -1,63 +0,0 @@ |
3820 | -# Copyright 2015 Canonical Ltd. |
3821 | -# |
3822 | -# This program is free software: you can redistribute it and/or modify it |
3823 | -# under the terms of the GNU General Public License version 3, as published |
3824 | -# by the Free Software Foundation. |
3825 | -# |
3826 | -# This program is distributed in the hope that it will be useful, but |
3827 | -# WITHOUT ANY WARRANTY; without even the implied warranties of |
3828 | -# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
3829 | -# PURPOSE. See the GNU General Public License for more details. |
3830 | -# |
3831 | -# You should have received a copy of the GNU General Public License along |
3832 | -# with this program. If not, see <http://www.gnu.org/licenses/>. |
3833 | - |
3834 | -import os |
3835 | -from testtools import TestCase |
3836 | -from testtools.matchers import Equals |
3837 | -from gi.repository import Libertine |
3838 | - |
3839 | -class TestLibertineGir(TestCase): |
3840 | - |
3841 | - def setUp(self): |
3842 | - super(TestLibertineGir, self).setUp() |
3843 | - self.cmake_source_dir = os.environ['CMAKE_SOURCE_DIR'] |
3844 | - |
3845 | - def test_list_containers(self): |
3846 | - os.environ['XDG_DATA_HOME'] = self.cmake_source_dir + '/libertine-config' |
3847 | - |
3848 | - containers = Libertine.list_containers() |
3849 | - |
3850 | - self.assertThat(containers[0], Equals('wily')) |
3851 | - self.assertThat(containers[1], Equals('wily-2')) |
3852 | - |
3853 | - def test_container_path(self): |
3854 | - container_id = 'wily' |
3855 | - os.environ['XDG_CACHE_HOME'] = self.cmake_source_dir + '/libertine-data' |
3856 | - |
3857 | - container_path = Libertine.container_path(container_id) |
3858 | - self.assertThat(container_path, Equals(self.cmake_source_dir + '/libertine-data/libertine-container/wily/rootfs')) |
3859 | - |
3860 | - def test_container_home_path(self): |
3861 | - container_id = 'wily' |
3862 | - os.environ['XDG_DATA_HOME'] = self.cmake_source_dir + '/libertine-home' |
3863 | - |
3864 | - container_home_path = Libertine.container_home_path(container_id) |
3865 | - self.assertThat(container_home_path, Equals(self.cmake_source_dir + '/libertine-home/libertine-container/user-data/wily')) |
3866 | - |
3867 | - def test_container_name(self): |
3868 | - container_id = 'wily' |
3869 | - os.environ['XDG_DATA_HOME'] = self.cmake_source_dir + '/libertine-config' |
3870 | - |
3871 | - container_name = Libertine.container_name(container_id) |
3872 | - self.assertThat(container_name, Equals("Ubuntu 'Wily Werewolf'")) |
3873 | - |
3874 | - container_id = 'wily-2' |
3875 | - container_name = Libertine.container_name(container_id) |
3876 | - self.assertThat(container_name, Equals("Ubuntu 'Wily Werewolf' (2)")) |
3877 | - |
3878 | - def test_list_apps_for_container(self): |
3879 | - os.environ['XDG_DATA_HOME'] = self.cmake_source_dir + '/libertine-config' |
3880 | - |
3881 | - apps = Libertine.list_apps_for_container('wily') |
3882 | - self.assertThat(len(apps), Equals(0)) |
3883 | |
3884 | === removed file 'tests/unit/libertine_session_bridge_tests.py' |
3885 | --- tests/unit/libertine_session_bridge_tests.py 2016-08-09 18:18:14 +0000 |
3886 | +++ tests/unit/libertine_session_bridge_tests.py 1970-01-01 00:00:00 +0000 |
3887 | @@ -1,106 +0,0 @@ |
3888 | -# Copyright 2016 Canonical Ltd. |
3889 | -# |
3890 | -# This program is free software: you can redistribute it and/or modify it |
3891 | -# under the terms of the GNU General Public License version 3, as published |
3892 | -# by the Free Software Foundation. |
3893 | -# |
3894 | -# This program is distributed in the hope that it will be useful, but |
3895 | -# WITHOUT ANY WARRANTY; without even the implied warranties of |
3896 | -# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
3897 | -# PURPOSE. See the GNU General Public License for more details. |
3898 | -# |
3899 | -# You should have received a copy of the GNU General Public License along |
3900 | -# with this program. If not, see <http://www.gnu.org/licenses/>. |
3901 | - |
3902 | -import os |
3903 | -import tempfile |
3904 | -from socket import * |
3905 | -import shutil |
3906 | - |
3907 | -from libertine import LibertineSessionBridge, HostSessionSocketPair, SessionSocket, LibertineApplication |
3908 | -from libertine import Socket |
3909 | -from multiprocessing import Process |
3910 | - |
3911 | -from testtools import TestCase |
3912 | -from testtools.matchers import Equals, NotEquals |
3913 | - |
3914 | -import time |
3915 | -import threading |
3916 | - |
3917 | -class TestLibertineSessionBridge(TestCase): |
3918 | - def setUp(self): |
3919 | - super(TestLibertineSessionBridge, self).setUp() |
3920 | - |
3921 | - xdg_runtime_path = tempfile.mkdtemp() |
3922 | - |
3923 | - self.addCleanup(self.cleanup) |
3924 | - |
3925 | - # Set necessary enviroment variables |
3926 | - os.environ['XDG_RUNTIME_DIR'] = xdg_runtime_path |
3927 | - |
3928 | - # Create r/w fake temp sockets, the session will be created by the lsb |
3929 | - self.host_path = os.path.join(xdg_runtime_path, 'HOST') |
3930 | - self.session_path = os.path.join(xdg_runtime_path, 'SESSION') |
3931 | - |
3932 | - self.host_socket = SessionSocket(self.host_path) |
3933 | - self.assertTrue(os.path.exists(self.host_path)) |
3934 | - |
3935 | - """ |
3936 | - Make sure we assert out socket is cleaned up. If we are failing here RAII |
3937 | - is broken. |
3938 | - """ |
3939 | - def cleanup(self): |
3940 | - self.host_socket = None |
3941 | - |
3942 | - self.assertFalse(os.path.exists(self.session_path)) |
3943 | - self.assertFalse(os.path.exists(self.host_path)) |
3944 | - shutil.rmtree(os.environ['XDG_RUNTIME_DIR']) |
3945 | - |
3946 | - """ |
3947 | - A function used to just read from the host socket. In a different thread |
3948 | - """ |
3949 | - def host_read(self, expected_bytes): |
3950 | - conn, addr = self.host_socket.socket.accept() |
3951 | - data = conn.recv(1024) |
3952 | - conn.close() |
3953 | - self.assertThat(data, Equals(expected_bytes)) |
3954 | - |
3955 | - def test_creates_socket_file(self): |
3956 | - """ |
3957 | - We assert when we create a lsb we create the session socket |
3958 | - """ |
3959 | - lsb = LibertineSessionBridge([HostSessionSocketPair(self.host_path, self.session_path)]) |
3960 | - self.assertTrue(os.path.exists(self.session_path)) |
3961 | - |
3962 | - |
3963 | - def test_data_read_from_host_to_session(self): |
3964 | - """ |
3965 | - This test shows we are able to proxy data from a host socket to a session client. |
3966 | - We do this by: |
3967 | - 1) Create a valid host socket |
3968 | - 2) Start a thread which waits to asserts out host socket recv the expected data |
3969 | - 3) Start the lsb thread to create a proxy session socket |
3970 | - 4) We create a fake session client (socket) and connect to the session path |
3971 | - 5) We send the expected bytes to the fake session client socket |
3972 | - 6) We join on the host sock loop, if we never get the expected bytes in the host socket loop we fail |
3973 | - 7) Clean up, and assert all our threads are not alive and have been cleaned up |
3974 | - """ |
3975 | - expected_bytes = b'Five exclamation marks, the sure sign of an insane mind.' |
3976 | - host_sock_loop = threading.Thread(target=self.host_read, args=(expected_bytes,)) |
3977 | - host_sock_loop.start() |
3978 | - |
3979 | - lsb = LibertineSessionBridge([HostSessionSocketPair(self.host_path, self.session_path)]) |
3980 | - lsb_process = Process(target=lsb.main_loop) |
3981 | - lsb_process.start() |
3982 | - |
3983 | - fake_session_client = socket(AF_UNIX, SOCK_STREAM) |
3984 | - fake_session_client.connect(self.session_path) |
3985 | - fake_session_client.sendall(expected_bytes) |
3986 | - |
3987 | - host_sock_loop.join(timeout=1) |
3988 | - fake_session_client.close() |
3989 | - lsb_process.terminate() |
3990 | - lsb_process.join(timeout=1) |
3991 | - |
3992 | - self.assertFalse(host_sock_loop.is_alive()) |
3993 | - self.assertFalse(lsb_process.is_alive()) |
3994 | |
3995 | === removed file 'tests/unit/libertine_socket_tests.py' |
3996 | --- tests/unit/libertine_socket_tests.py 2016-07-19 20:25:02 +0000 |
3997 | +++ tests/unit/libertine_socket_tests.py 1970-01-01 00:00:00 +0000 |
3998 | @@ -1,72 +0,0 @@ |
3999 | -# Copyright 2016 Canonical Ltd. |
4000 | -# |
4001 | -# This program is free software: you can redistribute it and/or modify it |
4002 | -# under the terms of the GNU General Public License version 3, as published |
4003 | -# by the Free Software Foundation. |
4004 | -# |
4005 | -# This program is distributed in the hope that it will be useful, but |
4006 | -# WITHOUT ANY WARRANTY; without even the implied warranties of |
4007 | -# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
4008 | -# PURPOSE. See the GNU General Public License for more details. |
4009 | -# |
4010 | -# You should have received a copy of the GNU General Public License along |
4011 | -# with this program. If not, see <http://www.gnu.org/licenses/>. |
4012 | - |
4013 | -from libertine import Socket |
4014 | - |
4015 | -from socket import socket |
4016 | - |
4017 | -from testtools import TestCase |
4018 | -from testtools.matchers import Equals, NotEquals |
4019 | - |
4020 | -class FakeSocket(Socket): |
4021 | - """ |
4022 | - We are making things up here... soo lets not attempt to remove anything! |
4023 | - """ |
4024 | - def __del__(self): |
4025 | - pass |
4026 | - |
4027 | - |
4028 | -class TestSocket(TestCase): |
4029 | - def setUp(self): |
4030 | - super(TestSocket, self).setUp() |
4031 | - self.socket1 = socket() |
4032 | - self.socket2 = socket() |
4033 | - |
4034 | - """ |
4035 | - Test we can wrap a python socket.socket in our Socket class |
4036 | - """ |
4037 | - def test_socket_warp(self): |
4038 | - s = FakeSocket(self.socket1) |
4039 | - self.assertThat(s, Equals(self.socket1)) |
4040 | - |
4041 | - """ |
4042 | - Test our equivalence operator works on three types: |
4043 | - 1: Other Socket classes |
4044 | - 2: Python socket classes |
4045 | - 3: The fd/socket raw Int value |
4046 | - """ |
4047 | - def test_socket_eq_op(self): |
4048 | - s1 = FakeSocket(self.socket1) |
4049 | - s2 = FakeSocket(self.socket1) |
4050 | - self.assertThat(s1, Equals(s2)) |
4051 | - self.assertThat(s2, Equals(self.socket1)) |
4052 | - self.assertThat(s2, Equals(self.socket1.fileno())) |
4053 | - |
4054 | - """ |
4055 | - Test our not equivalence works on the same three types |
4056 | - """ |
4057 | - def test_socket_not_eq_op(self): |
4058 | - s1 = FakeSocket(self.socket1) |
4059 | - s2 = FakeSocket(self.socket2) |
4060 | - self.assertThat(s1, NotEquals(s2)) |
4061 | - self.assertThat(s2, NotEquals(self.socket1)) |
4062 | - self.assertThat(s2, NotEquals(self.socket1.fileno())) |
4063 | - |
4064 | - """ |
4065 | - Test our Socket wrapper class is also hashable |
4066 | - """ |
4067 | - def test_socket_is_hashable(self): |
4068 | - s = FakeSocket(self.socket1) |
4069 | - dic = {s: 17} |
4070 | - self.assertThat(dic[s], Equals(17)) |
4071 | |
4072 | === renamed file 'tests/unit/libertine_launch_tests.py' => 'tests/unit/test_launcher.py' |
4073 | --- tests/unit/libertine_launch_tests.py 2016-06-30 20:45:38 +0000 |
4074 | +++ tests/unit/test_launcher.py 2016-11-21 19:42:42 +0000 |
4075 | @@ -1,4 +1,4 @@ |
4076 | -# Copyright 2015 Canonical Ltd. |
4077 | +# Copyright 2015-2016 Canonical Ltd. |
4078 | # |
4079 | # This program is free software: you can redistribute it and/or modify it |
4080 | # under the terms of the GNU General Public License version 3, as published |
4081 | @@ -13,64 +13,575 @@ |
4082 | # with this program. If not, see <http://www.gnu.org/licenses/>. |
4083 | |
4084 | import os |
4085 | -import shlex |
4086 | +import random |
4087 | import shutil |
4088 | -import subprocess |
4089 | +import signal |
4090 | +import string |
4091 | import tempfile |
4092 | |
4093 | -from libertine import LibertineApplication |
4094 | - |
4095 | -from testtools import TestCase |
4096 | -from testtools.matchers import Equals, NotEquals |
4097 | - |
4098 | -class TestLibertineLaunch(TestCase): |
4099 | +from libertine import LibertineContainer |
4100 | +from libertine import launcher |
4101 | + |
4102 | +from contextlib import suppress |
4103 | +from io import StringIO |
4104 | +from selectors import DefaultSelector, EVENT_READ |
4105 | +from socket import socket, AF_UNIX, SOCK_STREAM |
4106 | +from testtools import TestCase, ExpectedException |
4107 | +from testtools.matchers import Equals, Not, Contains, MatchesPredicate |
4108 | +from threading import Thread, Barrier, BrokenBarrierError |
4109 | +from time import sleep |
4110 | +from unittest.mock import call, patch, MagicMock |
4111 | + |
4112 | + |
4113 | +def _generate_unique_string(prefix=''): |
4114 | + """Generates a (hopefully) unique string.""" |
4115 | + return prefix + ''.join(random.choice(string.ascii_lowercase + string.digits) for i in range(8)) |
4116 | + |
4117 | +class TestLauncher(TestCase): |
4118 | def setUp(self): |
4119 | - super(TestLibertineLaunch, self).setUp() |
4120 | - self.cmake_source_dir = os.environ['CMAKE_SOURCE_DIR'] |
4121 | - self.cmake_binary_dir = os.environ['CMAKE_BINARY_DIR'] |
4122 | - |
4123 | - container_config_path = os.path.join(self.cmake_binary_dir, 'tests', 'unit', 'libertine-config') |
4124 | - |
4125 | - # Set necessary enviroment variables |
4126 | - os.environ['XDG_DATA_HOME'] = container_config_path |
4127 | + super(TestLauncher, self).setUp() |
4128 | os.environ['XDG_RUNTIME_DIR'] = tempfile.mkdtemp() |
4129 | - os.environ['PATH'] = (self.cmake_source_dir + '/tests/mocks:' + |
4130 | - self.cmake_source_dir + '/tools:' + os.environ['PATH']) |
4131 | - |
4132 | self.addCleanup(self.cleanup) |
4133 | |
4134 | - # Lets figure out how to really mock this.... |
4135 | - cli_cmd = self.cmake_source_dir + '/tools/libertine-container-manager create -i test -n Test -t mock' |
4136 | - args = shlex.split(cli_cmd) |
4137 | - subprocess.Popen(args).wait() |
4138 | - |
4139 | def cleanup(self): |
4140 | shutil.rmtree(os.environ['XDG_RUNTIME_DIR']) |
4141 | |
4142 | - def test_launch_app_existing_container(self): |
4143 | - ''' |
4144 | - Base line test to ensure launching an app in an existing container works. |
4145 | - ''' |
4146 | - la = LibertineApplication('test', 'true') |
4147 | - la.launch_application() |
4148 | - |
4149 | - def test_launch_app_nonexistent_container(self): |
4150 | - ''' |
4151 | - Test to make sure that things gracefully handle a non-existing container. |
4152 | - ''' |
4153 | - la = LibertineApplication('test1', 'true') |
4154 | - self.assertRaises(RuntimeError, la.launch_application) |
4155 | - |
4156 | - def test_launch_good_app(self): |
4157 | - ''' |
4158 | - Test to make sure that launching an app actually works. |
4159 | - ''' |
4160 | - la = LibertineApplication('test', 'mock_app') |
4161 | - la.launch_application() |
4162 | - |
4163 | - def test_launch_bad_app(self): |
4164 | - ''' |
4165 | - Test to make sure launching an app that doesn't exist doesn't break things |
4166 | - ''' |
4167 | - la = LibertineApplication('test', 'foo') |
4168 | - self.assertRaises(FileNotFoundError, la.launch_application) |
4169 | +class TestLauncherTaskConfig(TestLauncher): |
4170 | + """Unit tests for libertine.launcher.task module.""" |
4171 | + |
4172 | + def test_task_config_ctor(self): |
4173 | + """Verify Task constructor sets the required attributes.""" |
4174 | + fake_datum = [self.getUniqueString()] |
4175 | + |
4176 | + task = launcher.TaskConfig(launcher.TaskType.LAUNCH_SERVICE, fake_datum) |
4177 | + |
4178 | + self.assertThat(task.task_type, Equals(launcher.TaskType.LAUNCH_SERVICE)) |
4179 | + self.assertThat(task.datum, Equals(fake_datum)) |
4180 | + |
4181 | + |
4182 | +class TestLauncherConfig(TestLauncher): |
4183 | + """ |
4184 | + Verifies the defined behaviour of the Libertine Launcher Config class. |
4185 | + """ |
4186 | + |
4187 | + # a standard set of command-line arguments for when we don't care |
4188 | + id_arg = ['-i', 'container-id'] |
4189 | + exec_args = ['exec-line', 'one', 'two', 'three'] |
4190 | + basic_args = id_arg + exec_args |
4191 | + |
4192 | + def test_unqiue_id_generation(self): |
4193 | + """Ensure each unique configuration has a unique identifier.""" |
4194 | + config1 = launcher.Config(TestLauncherConfig.basic_args[:]) |
4195 | + config2 = launcher.Config(TestLauncherConfig.basic_args[:]) |
4196 | + self.assertThat(config1.id, Not(Equals(config2.id))) |
4197 | + |
4198 | + def test_mandatory_args(self): |
4199 | + """Verify that the mandatory CLI args get put where they're supposed to get put.""" |
4200 | + config = launcher.Config(TestLauncherConfig.basic_args[:]) |
4201 | + self.assertEquals(config.exec_line, TestLauncherConfig.exec_args) |
4202 | + |
4203 | + @patch('sys.stderr', new_callable=StringIO) |
4204 | + def test_missing_mandatory_args(self, stderr): |
4205 | + """Make sure missing mandatory CLI args are handled gracefully. |
4206 | + |
4207 | + Gracefully means a usage message is printed and the application exists. |
4208 | + """ |
4209 | + with ExpectedException(SystemExit): |
4210 | + config = launcher.Config(TestLauncherConfig.id_arg) |
4211 | + message = stderr.getvalue().strip() |
4212 | + self.assertThat(message, Contains("usage:")) |
4213 | + |
4214 | + def test_optional_id_arg(self): |
4215 | + """Make sure optional container id is handled.""" |
4216 | + config = launcher.Config(TestLauncherConfig.basic_args[:]) |
4217 | + self.assertEquals(config.container_id, 'container-id') |
4218 | + |
4219 | + def test_missing_optional_id_arg(self): |
4220 | + """Ensure command parsing works fine when container id is left off""" |
4221 | + config = launcher.Config(TestLauncherConfig.exec_args) |
4222 | + self.assertEquals(config.container_id, None) |
4223 | + |
4224 | + def test_one_explicit_environment_arg(self): |
4225 | + """Verify that one -E arg gets put into the environment.""" |
4226 | + argv = ['-EMY_ENV=something'] + TestLauncherConfig.basic_args[:] |
4227 | + config = launcher.Config(argv) |
4228 | + self.assertIn('MY_ENV', config.session_environ) |
4229 | + |
4230 | + def test_two_explicit_environment_args(self): |
4231 | + """Verify that multiple -E args gets put into the environment.""" |
4232 | + argv = ['-EMY_ENV=something', '-E', 'SOME_OTHER_ENV=somethingelse'] + TestLauncherConfig.basic_args[:] |
4233 | + config = launcher.Config(argv) |
4234 | + self.assertEquals(config.session_environ.get('MY_ENV', 'nothing'), 'something') |
4235 | + self.assertEquals(config.session_environ.get('SOME_OTHER_ENV', 'nothing'), 'somethingelse') |
4236 | + |
4237 | + def test_usr_games_is_in_path(self): |
4238 | + """Makes sure that /usr/games is in $PATH. |
4239 | + |
4240 | + This was a problem encountered early on. |
4241 | + """ |
4242 | + with patch.dict('os.environ', {'PATH': '/usr/local/bin:/bin:/usr/bin'}): |
4243 | + self.assertNotIn('/usr/games', os.environ['PATH'].split(sep=':')) |
4244 | + config = launcher.Config(TestLauncherConfig.basic_args[:]) |
4245 | + self.assertIn('/usr/games', config.session_environ['PATH'].split(sep=':')) |
4246 | + |
4247 | + def test_maliit_socket_bridge_from_env(self): |
4248 | + """Make sure the Maliit socket bridge gets configured. |
4249 | + |
4250 | + The Maliit socket address is normally found in an environment variable. |
4251 | + """ |
4252 | + env_key = 'MALIIT_SERVER_ADDRESS' |
4253 | + bogus_host_address = 'unix:abstract=/tmp/maliit-host-socket' |
4254 | + maliit_bridge = None |
4255 | + |
4256 | + with patch.dict('os.environ', {env_key: bogus_host_address}): |
4257 | + config = launcher.Config(TestLauncherConfig.basic_args[:]) |
4258 | + for bridge in config.socket_bridges: |
4259 | + if bridge.env_var == env_key: |
4260 | + maliit_bridge = bridge |
4261 | + |
4262 | + self.assertIsNotNone(maliit_bridge) |
4263 | + self.assertThat(maliit_bridge.host_address, Equals(bogus_host_address)) |
4264 | + self.assertThat(maliit_bridge.session_address, Contains('maliit')) |
4265 | + self.assertThat(config.session_environ.get(env_key, None), Not(Equals(None))) |
4266 | + self.assertThat(config.session_environ.get(env_key, bogus_host_address), |
4267 | + Not(Equals(bogus_host_address))) |
4268 | + |
4269 | + def test_dbus_socket_bridge_from_env(self): |
4270 | + """Make sure the D-Bus socket bridge gets configured. |
4271 | + |
4272 | + The D-Bus socket address is normally found in an environment variable. |
4273 | + """ |
4274 | + env_key = 'DBUS_SESSION_BUS_ADDRESS' |
4275 | + bogus_host_address = 'unix:abstract=/tmp/dbus-host-socket' |
4276 | + dbus_bridge = None |
4277 | + |
4278 | + with patch.dict('os.environ', {env_key: bogus_host_address}): |
4279 | + config = launcher.Config(TestLauncherConfig.basic_args[:]) |
4280 | + for bridge in config.socket_bridges: |
4281 | + if bridge.env_var == env_key: |
4282 | + dbus_bridge = bridge |
4283 | + |
4284 | + self.assertIsNotNone(dbus_bridge) |
4285 | + self.assertThat(dbus_bridge.host_address, Equals(bogus_host_address)) |
4286 | + self.assertThat(dbus_bridge.session_address, Contains('dbus')) |
4287 | + self.assertThat(config.session_environ.get(env_key, None), Not(Equals(None))) |
4288 | + self.assertThat(config.session_environ.get(env_key, bogus_host_address), |
4289 | + Not(Equals(bogus_host_address))) |
4290 | + |
4291 | + def test_default_prelaunch_tasks(self): |
4292 | + """Ensure 'pasted' is in the default pre-launch task list.""" |
4293 | + def pasted_is_in_list(task_list): |
4294 | + for t in task_list: |
4295 | + if t.task_type == launcher.TaskType.LAUNCH_SERVICE and t.datum[0] == "pasted": |
4296 | + return True |
4297 | + return False |
4298 | + |
4299 | + config = launcher.Config(TestLauncherConfig.basic_args[:]) |
4300 | + self.assertThat(config.prelaunch_tasks, |
4301 | + MatchesPredicate(pasted_is_in_list, "pasted is not in task list")) |
4302 | + |
4303 | + |
4304 | +class TestLauncherSession(TestLauncher): |
4305 | + """ |
4306 | + Verifies the defined bahaviour of a Libertine Lunacher session. |
4307 | + |
4308 | + This set of tests does the alien probe dance on things that can be verified |
4309 | + without actually requiring the launcher event loop be runing -- except for |
4310 | + the basic check that an event loop runs and can be terminated. |
4311 | + """ |
4312 | + |
4313 | + def setUp(self): |
4314 | + """Set up a mock config and a mock container.""" |
4315 | + super().setUp() |
4316 | + |
4317 | + fake_session_socket = 'unix:path=/tmp/garbage' |
4318 | + fake_bridge_config = launcher.SocketBridge('FAKE_SOCKET', 'dummy', fake_session_socket) |
4319 | + self._mock_config = MagicMock(spec=launcher.Config, |
4320 | + socket_bridges=[fake_bridge_config], |
4321 | + container_id=None) |
4322 | + |
4323 | + self._fake_session_address = launcher.translate_to_real_address(fake_session_socket) |
4324 | + |
4325 | + self._mock_container = MagicMock(spec=LibertineContainer) |
4326 | + |
4327 | + def test_basic_ctor(self): |
4328 | + """Just test the basic no-frill construction of a session.""" |
4329 | + self._mock_config.id = 'blah' |
4330 | + session = launcher.Session(self._mock_config, self._mock_container) |
4331 | + self.assertThat(session.id, Equals('blah')) |
4332 | + |
4333 | + def test_signals_are_restored(self): |
4334 | + """Verify that a Session object, when used correctly, cleans up any signal |
4335 | + handlers it has replaced. |
4336 | + |
4337 | + This test just makes sure the test environment is left in a clean state |
4338 | + after testing so that it does not corrupt the results of other unit |
4339 | + tests. |
4340 | + """ |
4341 | + old_sigchld_handler = signal.getsignal(signal.SIGCHLD) |
4342 | + old_sigint_handler = signal.getsignal(signal.SIGINT) |
4343 | + old_sigterm_handler = signal.getsignal(signal.SIGTERM) |
4344 | + |
4345 | + with launcher.Session(self._mock_config, self._mock_container) as session: |
4346 | + pass |
4347 | + |
4348 | + self.assertThat(signal.getsignal(signal.SIGCHLD), Equals(old_sigchld_handler)) |
4349 | + self.assertThat(signal.getsignal(signal.SIGINT), Equals(old_sigint_handler)) |
4350 | + self.assertThat(signal.getsignal(signal.SIGTERM), Equals(old_sigterm_handler)) |
4351 | + |
4352 | + @patch('libertine.launcher.session.socket.listen') |
4353 | + @patch('libertine.launcher.session.socket.bind') |
4354 | + def test_bridge_listener_is_created(self, mock_bind, mock_listen): |
4355 | + """Verify that a socket connection listener is set up for a socket bridge config passed in.""" |
4356 | + with launcher.Session(self._mock_config, self._mock_container) as session: |
4357 | + self.assertThat(mock_bind.call_args, Equals(call(self._fake_session_address))) |
4358 | + self.assertThat(mock_listen.called, Equals(True)) |
4359 | + |
4360 | + def test_sigchld_exits_run(self): |
4361 | + """Verify that the run() method returns on receipt of SIGCHLD. """ |
4362 | + with launcher.Session(self._mock_config, self._mock_container) as session: |
4363 | + def run_session_event_loop(): |
4364 | + session.run() |
4365 | + |
4366 | + session_event_loop = Thread(target=run_session_event_loop) |
4367 | + session_event_loop.start() |
4368 | + |
4369 | + os.kill(os.getpid(), signal.SIGCHLD) |
4370 | + |
4371 | + session_event_loop.join(timeout=0.5) |
4372 | + self.assertThat(session_event_loop.is_alive(), Equals(False)) |
4373 | + |
4374 | + def test_sigint_exits_run(self): |
4375 | + """Verify that the run() method returns on receipt of SIGINT. """ |
4376 | + with launcher.Session(self._mock_config, self._mock_container) as session: |
4377 | + def run_session_event_loop(): |
4378 | + session.run() |
4379 | + |
4380 | + session_event_loop = Thread(target=run_session_event_loop) |
4381 | + session_event_loop.start() |
4382 | + |
4383 | + os.kill(os.getpid(), signal.SIGINT) |
4384 | + |
4385 | + session_event_loop.join(timeout=0.5) |
4386 | + self.assertThat(session_event_loop.is_alive(), Equals(False)) |
4387 | + |
4388 | + def test_sigterm_exits_run(self): |
4389 | + """Verify that the run() method returns on receipt of SIGTERM.""" |
4390 | + with launcher.Session(self._mock_config, self._mock_container) as session: |
4391 | + def run_session_event_loop(): |
4392 | + session.run() |
4393 | + |
4394 | + session_event_loop = Thread(target=run_session_event_loop) |
4395 | + session_event_loop.start() |
4396 | + |
4397 | + os.kill(os.getpid(), signal.SIGTERM) |
4398 | + |
4399 | + session_event_loop.join(timeout=0.5) |
4400 | + self.assertThat(session_event_loop.is_alive(), Equals(False)) |
4401 | + |
4402 | + |
4403 | +class TestLauncherServiceTask(TestLauncher): |
4404 | + """Verify the expected bahaviour of launch tasks.""" |
4405 | + |
4406 | + def setUp(self): |
4407 | + """Set up an even loop fixture to watch for signals from spawned tasks. """ |
4408 | + super().setUp() |
4409 | + self._sigchld_caught = False |
4410 | + |
4411 | + def noopSignalHandler(*args): |
4412 | + pass |
4413 | + original_sigchld_handler = signal.signal(signal.SIGCHLD, noopSignalHandler) |
4414 | + self.addCleanup(lambda: signal.signal(signal.SIGCHLD, original_sigchld_handler)) |
4415 | + |
4416 | + sig_r_fd, sig_w_fd = os.pipe2(os.O_NONBLOCK | os.O_CLOEXEC) |
4417 | + signal.set_wakeup_fd(sig_w_fd) |
4418 | + |
4419 | + pre_loop_barrier = Barrier(2) |
4420 | + self._post_loop_barrier = Barrier(2) |
4421 | + |
4422 | + def loop(): |
4423 | + pre_loop_barrier.wait(1) |
4424 | + selector = DefaultSelector() |
4425 | + selector.register(sig_r_fd, EVENT_READ) |
4426 | + with suppress(StopIteration): |
4427 | + while True: |
4428 | + events = selector.select(timeout=5) |
4429 | + if len(events) == 0: |
4430 | + raise StopIteration |
4431 | + for key, mask in events: |
4432 | + if key.fd == sig_r_fd: |
4433 | + data = os.read(sig_r_fd, 4) |
4434 | + self._sigchld_caught = True |
4435 | + raise StopIteration |
4436 | + self._post_loop_barrier.wait(1) |
4437 | + |
4438 | + test_loop = Thread(target=loop) |
4439 | + self.addCleanup(lambda: test_loop.join(1)) |
4440 | + test_loop.start() |
4441 | + pre_loop_barrier.wait(1) |
4442 | + |
4443 | + def test_service_sends_SIGCHLD_on_exit(self): |
4444 | + """Verify a SIGCHLD signal gets raised when the service exits. |
4445 | + |
4446 | + Runs a fast-exiting 'service' (which should finish on its own in a |
4447 | + reasonable time) and verifies that a SIGCHLD is raised. |
4448 | + """ |
4449 | + config = launcher.TaskConfig(launcher.TaskType.LAUNCH_SERVICE, ("/bin/true")) |
4450 | + task = launcher.LaunchServiceTask(config) |
4451 | + task.start() |
4452 | + self._post_loop_barrier.wait(1) |
4453 | + self.assertThat(self._sigchld_caught, Equals(True)) |
4454 | + |
4455 | + def test_service_stop_exits_program(self): |
4456 | + """Verify the service exists when stopped. |
4457 | + |
4458 | + Runs a long-running service (which should not normally finish on its |
4459 | + own) and verifies that a SIGCHLD is received after calling stop() on the |
4460 | + task. |
4461 | + """ |
4462 | + config = launcher.TaskConfig(launcher.TaskType.LAUNCH_SERVICE, ("/usr/bin/yes")) |
4463 | + task = launcher.LaunchServiceTask(config) |
4464 | + task.start() |
4465 | + sleep(0.05) |
4466 | + task.stop() |
4467 | + self._post_loop_barrier.wait(1) |
4468 | + self.assertThat(self._sigchld_caught, Equals(True)) |
4469 | + |
4470 | + |
4471 | +class SessionEventLoopRunning(Thread): |
4472 | + """Provide a running, stoppable session event loop as a context manager thread.""" |
4473 | + |
4474 | + def __init__(self, session): |
4475 | + super().__init__() |
4476 | + self._session = session |
4477 | + |
4478 | + def __enter__(self): |
4479 | + """Start the session event loop thread running on context entry.""" |
4480 | + self.start() |
4481 | + return self |
4482 | + |
4483 | + def __exit__(self, *exc): |
4484 | + """Shut down the session event loop thread.""" |
4485 | + join_attempt_count = 0 |
4486 | + max_join_attempts = 5 |
4487 | + while self.is_alive() and join_attempt_count < max_join_attempts: |
4488 | + self.stop() |
4489 | + self.join(timeout=0.5) |
4490 | + join_attempt_count += 1 |
4491 | + assert join_attempt_count < max_join_attempts |
4492 | + return False |
4493 | + |
4494 | + def run(self): |
4495 | + with suppress(BrokenBarrierError): |
4496 | + self._session.run() |
4497 | + |
4498 | + def stop(self): |
4499 | + """Stop the session event loop. |
4500 | + |
4501 | + May be called multiple times without harm. |
4502 | + |
4503 | + Has a little nap after raising the TERM signal so the GIL gets |
4504 | + relinquished and the loop thread has a chance to process its signals. |
4505 | + """ |
4506 | + os.kill(os.getpid(), signal.SIGTERM) |
4507 | + sleep(0.0001) |
4508 | + |
4509 | + |
4510 | +class EchoServer(Thread): |
4511 | + """A test fixture providing some kind of service running on the host at a known address.""" |
4512 | + |
4513 | + socket_address = 'unix:path=/tmp/echo-host-' + _generate_unique_string() |
4514 | + |
4515 | + def __init__(self): |
4516 | + super().__init__() |
4517 | + self.daemon = True |
4518 | + |
4519 | + real_socket_address = launcher.translate_to_real_address(EchoServer.socket_address) |
4520 | + |
4521 | + self._socket = socket(AF_UNIX, SOCK_STREAM) |
4522 | + self._socket.bind(real_socket_address) |
4523 | + self._socket.listen(5) |
4524 | + self.start() |
4525 | + |
4526 | + def run(self): |
4527 | + while True: |
4528 | + (s, a) = self._socket.accept() |
4529 | + b = s.recv(512) |
4530 | + r = bytes("echo<<{}>>".format(b), 'ascii') |
4531 | + with suppress(BrokenPipeError): |
4532 | + s.sendall(r) |
4533 | + s.close() |
4534 | + |
4535 | + |
4536 | +class TestLauncherSessionSocketBridge(TestLauncher): |
4537 | + """Verify the Launcher Session socket bridge functionality.""" |
4538 | + |
4539 | + _fake_session_socket = 'unix:path=/tmp/session-' + _generate_unique_string() |
4540 | + |
4541 | + @patch('test_launcher.LibertineContainer') |
4542 | + def setUp(self, mock_container): |
4543 | + """Construct a test fixture with a single socket bridge configuration.""" |
4544 | + super().setUp() |
4545 | + |
4546 | + real_socket_address = launcher.translate_to_real_address(self._fake_session_socket) |
4547 | + with suppress(OSError): |
4548 | + os.remove(real_socket_address) |
4549 | + |
4550 | + config = MagicMock(spec=launcher.Config, |
4551 | + socket_bridges=[launcher.SocketBridge('FAKE_SOCKET', |
4552 | + host_address=EchoServer.socket_address, |
4553 | + session_address=self._fake_session_socket)], |
4554 | + container_id=None) |
4555 | + self._session = launcher.Session(config, mock_container) |
4556 | + |
4557 | + def test_abstract_socket_is_used(self): |
4558 | + bogus_abstract_path = '/tmp/dbus-host-socket' |
4559 | + bogus_host_address = 'unix:abstract=' + bogus_abstract_path |
4560 | + |
4561 | + bogus_host_socket = launcher.translate_to_real_address(bogus_host_address) |
4562 | + |
4563 | + self.assertThat(bogus_host_socket, Equals('\0' + bogus_abstract_path)) |
4564 | + |
4565 | + def test_connection_acceptance(self): |
4566 | + """Verify that when at attempt is made to connect to a session bridge it is accepted. |
4567 | + |
4568 | + This test has to perform some ear-wiggling and hop-dart behaviour to |
4569 | + make sure it waits until the event loop has called the accept() method |
4570 | + before it tries to check to see if the accept() method has been called |
4571 | + (hence the thread barrier). Also, because the real socket accept |
4572 | + operation does not get performed, the mock accept() call can be |
4573 | + executed several times by the server before we get around to actually |
4574 | + checking up on it and we only care that it's called at least once, hence |
4575 | + the 'called_once' flag. |
4576 | + """ |
4577 | + barrier = Barrier(2, timeout=0.5) |
4578 | + called_once = False |
4579 | + real_session_address = launcher.translate_to_real_address(self._fake_session_socket) |
4580 | + |
4581 | + def fake_accept(*args, called_once=called_once): |
4582 | + if not called_once: |
4583 | + called_once = True |
4584 | + barrier.wait() |
4585 | + sock = socket(AF_UNIX, SOCK_STREAM) |
4586 | + return (sock, 'xyzzy') |
4587 | + |
4588 | + with patch('libertine.launcher.session.socket.accept', side_effect=fake_accept) as mock_accept: |
4589 | + with SessionEventLoopRunning(self._session): |
4590 | + sock = socket(AF_UNIX, SOCK_STREAM) |
4591 | + sock.connect(real_session_address) |
4592 | + with suppress(BrokenBarrierError): |
4593 | + barrier.wait() |
4594 | + self.assertThat(mock_accept.called, Equals(True), "accept() not called") |
4595 | + |
4596 | + def test_bridge_socket_relay(self): |
4597 | + """Test that the whole socket bridge relay functionality works. |
4598 | + |
4599 | + Sure, this is not really a unit test but a black-box functional test. |
4600 | + """ |
4601 | + echo_server = EchoServer() |
4602 | + real_session_address = launcher.translate_to_real_address(self._fake_session_socket) |
4603 | + |
4604 | + with SessionEventLoopRunning(self._session): |
4605 | + sock = socket(AF_UNIX, SOCK_STREAM) |
4606 | + sock.connect(real_session_address) |
4607 | + sock.sendall(bytes('ping', 'ascii')) |
4608 | + response = str(sock.recv(1024), 'ascii') |
4609 | + self.assertThat(response, Contains('ping')) |
4610 | + |
4611 | + |
4612 | +class TestLauncherSessionTask(TestLauncher): |
4613 | + """Verify how a Session handles Tasks.""" |
4614 | + |
4615 | + @patch('test_launcher.LibertineContainer') |
4616 | + def setUp(self, mock_container): |
4617 | + super().setUp() |
4618 | + |
4619 | + # Monkey-patch the task object created by the LaunchServiceTask class. |
4620 | + # This is a little awkward by it's the way Python works. |
4621 | + p = patch('libertine.launcher.session.LaunchServiceTask') |
4622 | + mock_service_task_class = p.start() |
4623 | + self._mock_service_task = mock_service_task_class.return_value |
4624 | + self._mock_service_task.wait = MagicMock(return_value=True) |
4625 | + self.addCleanup(p.stop) |
4626 | + |
4627 | + fake_datum = [self.getUniqueString()] |
4628 | + task = launcher.TaskConfig(launcher.TaskType.LAUNCH_SERVICE, fake_datum) |
4629 | + |
4630 | + config = MagicMock(spec=launcher.Config, |
4631 | + socket_bridges=[], |
4632 | + prelaunch_tasks=[task], |
4633 | + host_environ={}, |
4634 | + container_id=None) |
4635 | + self._session = launcher.Session(config, mock_container) |
4636 | + |
4637 | + def test_session_starts_prelaunch_task(self): |
4638 | + """Test that a session starts a pre-launch task.""" |
4639 | + self.assertThat(self._mock_service_task.start.called, |
4640 | + Equals(True), |
4641 | + message="task.start() not called") |
4642 | + |
4643 | + def test_session_stops_prelaunch_task(self): |
4644 | + """Test that a session starts a pre-launch task.""" |
4645 | + with SessionEventLoopRunning(self._session) as event_loop: |
4646 | + event_loop.stop() |
4647 | + self.assertThat(self._mock_service_task.stop.called, Equals(True)) |
4648 | + |
4649 | + def test_session_handles_dying_prelaunch_task(self): |
4650 | + """Test that a session starts a pre-launch task.""" |
4651 | + with SessionEventLoopRunning(self._session) as event_loop: |
4652 | + os.kill(os.getpid(), signal.SIGCHLD) |
4653 | + event_loop.stop() |
4654 | + self.assertThat(self._mock_service_task.wait.called, Equals(True)) |
4655 | + |
4656 | + |
4657 | +class TestLauncherContainerBehavior(TestLauncher): |
4658 | + """Verify some expected behaviour when it comes to running the contained application.""" |
4659 | + |
4660 | + def setUp(self): |
4661 | + super().setUp() |
4662 | + |
4663 | + self._mock_config = MagicMock(spec=launcher.Config, |
4664 | + socket_bridges=[], |
4665 | + session_environ={}, |
4666 | + container_id=None) |
4667 | + |
4668 | + # Need to fake the ContainersConfig used internally by |
4669 | + # LibertineContainer... that whole outfit needs to be refactored for |
4670 | + # better testing next. |
4671 | + mock_containers_config = MagicMock() |
4672 | + mock_containers_config.get_container_type = MagicMock(return_value='mock') |
4673 | + |
4674 | + self._container_proxy = LibertineContainer(container_id='xyzzy', |
4675 | + containers_config=mock_containers_config) |
4676 | + |
4677 | + libertine_mock_patcher = patch.object(self._container_proxy, "container", autospec=True) |
4678 | + self._mock_container = libertine_mock_patcher.start() |
4679 | + self.addCleanup(libertine_mock_patcher.stop) |
4680 | + |
4681 | + def test_start_application(self): |
4682 | + """Test the start_application() function of the session API.""" |
4683 | + self._mock_config.exec_line = ["/bin/echo", "sis", "boom", "bah"] |
4684 | + |
4685 | + with launcher.Session(self._mock_config, self._container_proxy) as session: |
4686 | + with SessionEventLoopRunning(session): |
4687 | + session.start_application() |
4688 | + |
4689 | + #self.assertThat(self._mock_container.connect.called, Equals(True)) |
4690 | + #self.assertThat(self._mock_container.disconnect.called, Equals(True)) |
4691 | + self.assertThat(self._mock_container.start_application.called, Equals(True)) |
4692 | + self.assertThat(self._mock_container.start_application.call_args[0][0], |
4693 | + Equals(self._mock_config.exec_line)) |
4694 | + self.assertThat(self._mock_container.finish_application.called, Equals(True)) |
4695 | + |
4696 | + def test_environment_gets_set(self): |
4697 | + """Verify that the configured variables are set in the contained execution environments.""" |
4698 | + fake_value = _generate_unique_string() |
4699 | + fake_var = _generate_unique_string(prefix='V') |
4700 | + self._mock_config.session_environ[fake_var] = fake_value |
4701 | + self._mock_config.exec_line = ["/dev/null"] |
4702 | + |
4703 | + with launcher.Session(self._mock_config, self._container_proxy) as session: |
4704 | + with SessionEventLoopRunning(session): |
4705 | + session.start_application() |
4706 | + |
4707 | + self.assertThat(self._mock_container.start_application.call_args[0][1], Contains(fake_var)) |
4708 | |
4709 | === added file 'tests/unit/test_launcher_with_dbus.py' |
4710 | --- tests/unit/test_launcher_with_dbus.py 1970-01-01 00:00:00 +0000 |
4711 | +++ tests/unit/test_launcher_with_dbus.py 2016-11-21 19:42:42 +0000 |
4712 | @@ -0,0 +1,103 @@ |
4713 | +# Copyright 2016 Canonical Ltd. |
4714 | +# |
4715 | +# This program is free software: you can redistribute it and/or modify it |
4716 | +# under the terms of the GNU General Public License version 3, as published |
4717 | +# by the Free Software Foundation. |
4718 | +# |
4719 | +# This program is distributed in the hope that it will be useful, but |
4720 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
4721 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
4722 | +# PURPOSE. See the GNU General Public License for more details. |
4723 | +# |
4724 | +# You should have received a copy of the GNU General Public License along |
4725 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
4726 | + |
4727 | +import dbus |
4728 | +import dbusmock |
4729 | +import os |
4730 | +import subprocess |
4731 | + |
4732 | +from libertine import launcher |
4733 | + |
4734 | +from testtools import TestCase |
4735 | +from testtools.matchers import Equals, Contains |
4736 | +from unittest.mock import patch |
4737 | + |
4738 | + |
4739 | + |
4740 | +class MockDBusServer(object): |
4741 | + """ |
4742 | + A mock object that proxies a mock D-Bus server, for testing things that do |
4743 | + D-Bus operations in the background. |
4744 | + """ |
4745 | + |
4746 | + def __init__(self, maliit_server_address): |
4747 | + dbusmock.DBusTestCase.start_session_bus() |
4748 | + self.maliit_server_address = maliit_server_address |
4749 | + self._dbus_connection = dbusmock.DBusTestCase.get_dbus(system_bus=False) |
4750 | + |
4751 | + def setUp(self): |
4752 | + self._dbus_mock_server = dbusmock.DBusTestCase.spawn_server( |
4753 | + 'org.maliit.server', |
4754 | + '/org/maliit/server/address', |
4755 | + 'org.maliit.Server.Address', |
4756 | + system_bus=False, |
4757 | + stdout=subprocess.PIPE) |
4758 | + self._mock_interface = self._get_mock_interface() |
4759 | + self._mock_interface.AddProperty('', 'address', self.maliit_server_address) |
4760 | + |
4761 | + def cleanUp(self): |
4762 | + self._dbus_mock_server.terminate() |
4763 | + self._dbus_mock_server.wait() |
4764 | + |
4765 | + def getDetails(self): |
4766 | + return { } |
4767 | + |
4768 | + def _get_mock_interface(self): |
4769 | + return dbus.Interface( |
4770 | + self._dbus_connection.get_object( |
4771 | + 'org.maliit.server', |
4772 | + '/org/maliit/server/address', |
4773 | + 'org.maliit.Server.Address'), |
4774 | + dbusmock.MOCK_IFACE) |
4775 | + |
4776 | + |
4777 | +class TestLauncherConfigUsingDBus(TestCase): |
4778 | + """ |
4779 | + Verifies the defined behaviour of the Libertine Launcher Config class with |
4780 | + reference to a D-Bus server. |
4781 | + """ |
4782 | + def setUp(self): |
4783 | + """ |
4784 | + Need to save and restore the os.environ for each test in case they |
4785 | + change things. That's a global that propagates between tests. |
4786 | + """ |
4787 | + super().setUp() |
4788 | + fake_maliit_host_address = 'unix:abstract=/tmp/maliit-host-socket' |
4789 | + self._dbus_server = self.useFixture(MockDBusServer(maliit_server_address=fake_maliit_host_address)) |
4790 | + self._cli = [ '-i', self.getUniqueString(), self.getUniqueString() ] |
4791 | + |
4792 | + def test_maliit_socket_bridge_from_dbus(self): |
4793 | + """ |
4794 | + Make sure the Maliit socket bridge gets configured. |
4795 | + |
4796 | + The Maliit service advertizes it's socket on the D-Bus, although the |
4797 | + environment variable, if present, overrides that so to test the D-Bus |
4798 | + advert the environment variable must be clear. |
4799 | + """ |
4800 | + maliit_bridge = None |
4801 | + with patch.dict('os.environ'): |
4802 | + if 'MALIIT_SERVER_ADDRESS' in os.environ: |
4803 | + del os.environ['MALIIT_SERVER_ADDRESS'] |
4804 | + |
4805 | + config = launcher.Config(self._cli[:]) |
4806 | + |
4807 | + for bridge in config.socket_bridges: |
4808 | + if bridge.env_var == 'MALIIT_SERVER_ADDRESS': |
4809 | + maliit_bridge = bridge |
4810 | + |
4811 | + self.assertIsNotNone(maliit_bridge) |
4812 | + self.assertThat(maliit_bridge.host_address, Equals(self._dbus_server.maliit_server_address)) |
4813 | + self.assertThat(maliit_bridge.session_address, Contains('maliit')) |
4814 | + self.assertThat(maliit_bridge.session_address, Contains(config.id)) |
4815 | + |
4816 | |
4817 | === renamed file 'tests/unit/libertine_container_manager_tests.py' => 'tests/unit/test_libertine_container_manager.py' |
4818 | === added file 'tests/unit/test_libertine_gir.py' |
4819 | --- tests/unit/test_libertine_gir.py 1970-01-01 00:00:00 +0000 |
4820 | +++ tests/unit/test_libertine_gir.py 2016-11-21 19:42:42 +0000 |
4821 | @@ -0,0 +1,67 @@ |
4822 | +# Copyright 2015 Canonical Ltd. |
4823 | +# |
4824 | +# This program is free software: you can redistribute it and/or modify it |
4825 | +# under the terms of the GNU General Public License version 3, as published |
4826 | +# by the Free Software Foundation. |
4827 | +# |
4828 | +# This program is distributed in the hope that it will be useful, but |
4829 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
4830 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
4831 | +# PURPOSE. See the GNU General Public License for more details. |
4832 | +# |
4833 | +# You should have received a copy of the GNU General Public License along |
4834 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
4835 | + |
4836 | +import os |
4837 | +from testtools import TestCase |
4838 | +from testtools.matchers import Equals |
4839 | +from gi.repository import Libertine |
4840 | +from unittest import skip |
4841 | +from unittest.mock import patch |
4842 | + |
4843 | + |
4844 | +class TestLibertineGir(TestCase): |
4845 | + |
4846 | + def setUp(self): |
4847 | + super(TestLibertineGir, self).setUp() |
4848 | + self.cmake_source_dir = os.environ['LIBERTINE_DATA_DIR'] |
4849 | + |
4850 | + def test_list_containers(self): |
4851 | + with patch.dict('os.environ', {'XDG_DATA_HOME': self.cmake_source_dir + '/libertine-config'}): |
4852 | + containers = Libertine.list_containers() |
4853 | + |
4854 | + self.assertThat(containers[0], Equals('wily')) |
4855 | + self.assertThat(containers[1], Equals('wily-2')) |
4856 | + |
4857 | + @skip("need to work around cached globals in glib") |
4858 | + def test_container_path(self): |
4859 | + container_id = 'wily' |
4860 | + with patch.dict('os.environ', {'XDG_CACHE_HOME': self.cmake_source_dir + '/libertine-data'}): |
4861 | + container_path = Libertine.container_path(container_id) |
4862 | + |
4863 | + self.assertThat(container_path, Equals(self.cmake_source_dir + '/libertine-data/libertine-container/wily/rootfs')) |
4864 | + |
4865 | + def test_container_home_path(self): |
4866 | + container_id = 'wily' |
4867 | + with patch.dict('os.environ', {'XDG_DATA_HOME': self.cmake_source_dir + '/libertine-home'}): |
4868 | + container_home_path = Libertine.container_home_path(container_id) |
4869 | + |
4870 | + self.assertThat(container_home_path, Equals(self.cmake_source_dir + '/libertine-home/libertine-container/user-data/wily')) |
4871 | + |
4872 | + def test_container_name(self): |
4873 | + container_id = 'wily' |
4874 | + with patch.dict('os.environ', {'XDG_DATA_HOME': self.cmake_source_dir + '/libertine-config'}): |
4875 | + container_name = Libertine.container_name(container_id) |
4876 | + |
4877 | + self.assertThat(container_name, Equals("Ubuntu 'Wily Werewolf'")) |
4878 | + |
4879 | + container_id = 'wily-2' |
4880 | + container_name = Libertine.container_name(container_id) |
4881 | + |
4882 | + self.assertThat(container_name, Equals("Ubuntu 'Wily Werewolf' (2)")) |
4883 | + |
4884 | + def test_list_apps_for_container(self): |
4885 | + with patch.dict('os.environ', {'XDG_DATA_HOME': self.cmake_source_dir + '/libertine-config'}): |
4886 | + apps = Libertine.list_apps_for_container('wily') |
4887 | + |
4888 | + self.assertThat(len(apps), Equals(0)) |
4889 | |
4890 | === renamed file 'tests/unit/libertine_logger_tests.py' => 'tests/unit/test_logger.py' |
4891 | --- tests/unit/libertine_logger_tests.py 2016-08-09 23:34:22 +0000 |
4892 | +++ tests/unit/test_logger.py 2016-11-21 19:42:42 +0000 |
4893 | @@ -12,38 +12,34 @@ |
4894 | # You should have received a copy of the GNU General Public License along |
4895 | # with this program. If not, see <http://www.gnu.org/licenses/>. |
4896 | |
4897 | +import logging |
4898 | import os |
4899 | -import shutil |
4900 | |
4901 | import libertine.utils |
4902 | |
4903 | from testtools import TestCase |
4904 | from testtools.matchers import Equals, NotEquals |
4905 | +from unittest.mock import patch |
4906 | + |
4907 | |
4908 | class TestLogger(TestCase): |
4909 | - def setUp(self): |
4910 | - super(TestLogger, self).setUp() |
4911 | - os.environ['LIBERTINE_DEBUG'] = '1' |
4912 | - self.addCleanup(self.cleanup) |
4913 | - |
4914 | - def cleanup(self): |
4915 | - if os.getenv('LIBERTINE_DEBUG', None) is not None: |
4916 | - del os.environ['LIBERTINE_DEBUG'] |
4917 | |
4918 | def test_logger_off_by_default(self): |
4919 | - # Need to turn off for this test only! |
4920 | - self.cleanup() |
4921 | - os.unsetenv('LIBERTINE_DEBUG') |
4922 | - l = libertine.utils.get_logger() |
4923 | - self.assertTrue(l.disabled) |
4924 | + with patch.dict('os.environ'): |
4925 | + if 'LIBERTINE_DEBUG' in os.environ: |
4926 | + del os.environ['LIBERTINE_DEBUG'] |
4927 | + l = libertine.utils.get_logger() |
4928 | + self.assertThat(l.getEffectiveLevel(), Equals(logging.WARNING)) |
4929 | |
4930 | def test_logger_on_with_env_var(self): |
4931 | - l = libertine.utils.get_logger() |
4932 | - self.assertFalse(l.disabled) |
4933 | + with patch.dict('os.environ', {'LIBERTINE_DEBUG': '1'}): |
4934 | + l = libertine.utils.get_logger() |
4935 | + self.assertThat(l.getEffectiveLevel(), Equals(logging.DEBUG)) |
4936 | |
4937 | def test_logger_only_inits_once(self): |
4938 | - l1 = libertine.utils.get_logger() |
4939 | - l2 = libertine.utils.get_logger() |
4940 | - l3 = libertine.utils.get_logger() |
4941 | - self.assertThat(len(l3.handlers), Equals(1)) |
4942 | + with patch.dict('os.environ', {'LIBERTINE_DEBUG': '1'}): |
4943 | + l1 = libertine.utils.get_logger() |
4944 | + l2 = libertine.utils.get_logger() |
4945 | + l3 = libertine.utils.get_logger() |
4946 | + self.assertThat(len(l3.handlers), Equals(1)) |
4947 | |
4948 | |
4949 | === modified file 'tools/libertine-container-manager' |
4950 | --- tools/libertine-container-manager 2016-10-03 15:38:18 +0000 |
4951 | +++ tools/libertine-container-manager 2016-11-21 19:42:42 +0000 |
4952 | @@ -239,6 +239,10 @@ |
4953 | self.containers_config.update_container_multiarch_support(container_id, multiarch) |
4954 | |
4955 | elif args.archive is not None: |
4956 | + if args.archive_name is None: |
4957 | + print("Configure archive called with no archive name. See configure --help for usage.") |
4958 | + sys.exit(1) |
4959 | + |
4960 | archive_name = args.archive_name.strip("\'\"") |
4961 | archive_name_esc = "\"" + archive_name + "\"" |
4962 | |
4963 | |
4964 | === modified file 'tools/libertine-launch' |
4965 | --- tools/libertine-launch 2016-10-03 19:51:28 +0000 |
4966 | +++ tools/libertine-launch 2016-11-21 19:42:42 +0000 |
4967 | @@ -1,7 +1,7 @@ |
4968 | #!/usr/bin/python3 |
4969 | # -*- coding: utf-8 -*- |
4970 | |
4971 | -# Copyright (C) 2015 Canonical Ltd. |
4972 | +# Copyright (C) 2015-2016 Canonical Ltd. |
4973 | # Author: Christopher Townsend <christopher.townsend@canonical.com> |
4974 | |
4975 | # This program is free software: you can redistribute it and/or modify |
4976 | @@ -16,144 +16,25 @@ |
4977 | # You should have received a copy of the GNU General Public License |
4978 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
4979 | |
4980 | -import argparse |
4981 | -import dbus |
4982 | import os |
4983 | -import random |
4984 | -import string |
4985 | -import libertine.utils |
4986 | -import shlex |
4987 | -import time |
4988 | -from libertine import LibertineApplication, HostSessionSocketPair |
4989 | - |
4990 | - |
4991 | -def get_host_maliit_socket(): |
4992 | - address_bus_name = 'org.maliit.server' |
4993 | - address_object_path = '/org/maliit/server/address' |
4994 | - address_interface = 'org.maliit.Server.Address' |
4995 | - address_property = 'address' |
4996 | - address = '' |
4997 | - |
4998 | - try: |
4999 | - session_bus = dbus.SessionBus() |
5000 | - maliit_object = session_bus.get_object('org.maliit.server', '/org/maliit/server/address') |
lgtm!