Merge lp:~townsend/libertine/1.4.3-release into lp:libertine/trunk

Proposed by Christopher Townsend
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
Reviewer Review Type Date Requested Status
Larry Price Approve
Review via email: mp+309700@code.launchpad.net

Commit message

* Use libertine-lxc-manager when starting/stopping a container for libertine-container-manager operations if the session DBus is available. If not, fallback to l-c-m starting/stopping the container. (LP: #1628587)
* 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 ContainersConfig.json advising users not to edit the file.
* 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.

To post a comment you must log in.
lp:~townsend/libertine/1.4.3-release updated
173. By Christopher Townsend

Merge in lp:libertine for fixes.

174. By Christopher Townsend

Set cmake project version to 1.4.3.

Revision history for this message
Larry Price (larryprice) wrote :

lgtm!

review: Approve
lp:~townsend/libertine/1.4.3-release updated
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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'
3547Binary 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')
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches