589 lines
23 KiB
Python
589 lines
23 KiB
Python
# -*- coding: utf-8; Mode: Python; indent-tabs-mode: nil; tab-width: 4 -*-
|
|
|
|
# Copyright (C) 2013 Canonical Ltd.
|
|
# Written by Evan Dandrea <evan.dandrea@canonical.com>
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
import glob
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import syslog
|
|
|
|
from ubiquity import i18n, misc, osextras, plugin, upower
|
|
from ubiquity.install_misc import (archdetect, is_secure_boot,
|
|
minimal_install_rlist_path)
|
|
|
|
NAME = 'prepare'
|
|
AFTER = 'wireless'
|
|
WEIGHT = 11
|
|
OEM = False
|
|
|
|
|
|
# TODO: This cannot be a non-debconf plugin after all as OEMs may want to
|
|
# preseed the 'install updates' and 'install non-free software' options. So?
|
|
# Just db_get them. No need for any other overhead, surely. Actually, you
|
|
# need the dbfilter for that get.
|
|
|
|
class PreparePageBase(plugin.PluginUI):
|
|
plugin_title = 'ubiquity/text/prepare_heading_label'
|
|
download_updates = True
|
|
download_updates_enabled = True
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
plugin.PluginUI.__init__(self)
|
|
|
|
def plugin_set_online_state(self, state):
|
|
# if we're disabling, remember what it was before so we can re-enable
|
|
# properly if network comes back
|
|
if self.prepare_network_connection.get_state() and not state:
|
|
self.download_updates = self.get_download_updates()
|
|
self.prepare_network_connection.set_state(state)
|
|
self.enable_download_updates(state)
|
|
self.download_updates_enabled = state
|
|
if not state:
|
|
self.set_download_updates(False)
|
|
else:
|
|
self.set_download_updates(self.download_updates)
|
|
|
|
def set_sufficient_space(self, state, required, free):
|
|
if not state:
|
|
# There's either no drives present, or not enough free space.
|
|
# Either way, we cannot continue.
|
|
self.show_insufficient_space_page(required, free)
|
|
self.controller.allow_go_forward(False)
|
|
self.prepare_sufficient_space.set_state(state)
|
|
|
|
def plugin_translate(self, lang):
|
|
self.rst_title_text = i18n.get_string('rst_header', lang)
|
|
return
|
|
|
|
def show_ubuntu_drivers_spinner(self):
|
|
return
|
|
|
|
def hide_ubuntu_drivers_spinner(self):
|
|
return
|
|
|
|
|
|
class PageGtk(PreparePageBase):
|
|
restricted_package_name = 'ubuntu-restricted-addons'
|
|
|
|
def __init__(self, controller, *args, **kwargs):
|
|
self.controller = controller
|
|
from ubiquity.gtkwidgets import Builder
|
|
builder = Builder()
|
|
self.controller.add_builder(builder)
|
|
builder.add_from_file(os.path.join(
|
|
os.environ['UBIQUITY_GLADE'], 'stepPrepare.ui'))
|
|
builder.connect_signals(self)
|
|
|
|
# Get all objects + add internal child(s)
|
|
all_widgets = builder.get_object_ids()
|
|
for wdg in all_widgets:
|
|
setattr(self, wdg, builder.get_object(wdg))
|
|
|
|
self.password_strength_pages = {
|
|
'empty': 0,
|
|
'too_short': 1,
|
|
'good': 2,
|
|
}
|
|
self.password_match_pages = {
|
|
'empty': 0,
|
|
'mismatch': 1,
|
|
'ok': 2,
|
|
}
|
|
|
|
if upower.has_battery():
|
|
upower.setup_power_watch(self.prepare_power_source)
|
|
else:
|
|
self.prepare_power_source.hide()
|
|
self.prepare_network_connection = builder.get_object(
|
|
'prepare_network_connection')
|
|
|
|
self.using_secureboot = False
|
|
|
|
self.secureboot_box.set_sensitive(False)
|
|
self.password_grid.set_sensitive(False)
|
|
|
|
self.minimal_install_vbox.set_visible(
|
|
os.path.exists(minimal_install_rlist_path))
|
|
|
|
self.prepare_page = builder.get_object('stepPrepare')
|
|
self.insufficient_space_page = builder.get_object('stepNoSpace')
|
|
self.rst_page = builder.get_object('stepRST')
|
|
self.rst_label = builder.get_object('label_using_rst')
|
|
self.rst_label.connect('activate-link', self.on_link_clicked)
|
|
self.current_page = self.prepare_page
|
|
self.plugin_widgets = self.prepare_page
|
|
self.plugin_optional_widgets = [self.insufficient_space_page,
|
|
self.rst_page]
|
|
|
|
def on_link_clicked(self, widget, uri):
|
|
misc.launch_uri(uri)
|
|
|
|
def plugin_get_current_page(self):
|
|
return self.current_page
|
|
|
|
def show_insufficient_space_page(self, required, free):
|
|
self.current_page = self.insufficient_space_page
|
|
|
|
self.label_required_space.set_label(required)
|
|
self.label_free_space.set_label(free)
|
|
|
|
self.controller.go_to_page(self.current_page)
|
|
|
|
def show_rst_page(self):
|
|
for page in self.controller._wizard.pages:
|
|
if page.module.NAME == NAME:
|
|
page.title = 'ubiquity/text/rst_header'
|
|
break
|
|
self.current_page = self.rst_page
|
|
self.controller.go_to_page(self.current_page)
|
|
return True
|
|
|
|
def show_ubuntu_drivers_spinner(self):
|
|
frontend = self.controller._wizard
|
|
frontend.status_spinner.show()
|
|
frontend.status_spinner.start()
|
|
frontend.status_label.show()
|
|
|
|
def hide_ubuntu_drivers_spinner(self):
|
|
frontend = self.controller._wizard
|
|
frontend.status_spinner.hide()
|
|
frontend.status_spinner.stop()
|
|
frontend.status_label.hide()
|
|
|
|
def plugin_on_next_clicked(self):
|
|
if self.current_page != self.rst_page:
|
|
if self.get_use_nonfree():
|
|
self.show_ubuntu_drivers_spinner()
|
|
return
|
|
|
|
self.controller._wizard.do_reboot()
|
|
return True
|
|
|
|
def set_using_secureboot(self, secureboot):
|
|
self.using_secureboot = secureboot
|
|
self.secureboot_box.set_visible(secureboot)
|
|
self.disable_secureboot.set_active(True)
|
|
self.on_nonfree_toggled(None)
|
|
self.info_loop(None)
|
|
|
|
def enable_download_updates(self, val):
|
|
if (val):
|
|
template = 'ubiquity/text/label_download_updates'
|
|
else:
|
|
template = 'ubiquity/text/label_download_updates_na'
|
|
self.label_download_updates.set_label(
|
|
self.controller.get_string(template))
|
|
self.prepare_download_updates.set_sensitive(val)
|
|
|
|
def set_download_updates(self, val):
|
|
self.prepare_download_updates.set_active(val)
|
|
|
|
def get_download_updates(self):
|
|
return self.prepare_download_updates.get_active()
|
|
|
|
def set_minimal_install(self, val):
|
|
self.prepare_minimal_install.set_active(val)
|
|
|
|
def get_minimal_install(self):
|
|
return self.prepare_minimal_install.get_active()
|
|
|
|
def set_allow_nonfree(self, allow):
|
|
if not allow:
|
|
self.prepare_nonfree_software.set_active(False)
|
|
self.nonfree_vbox.set_property('visible', False)
|
|
|
|
def set_use_nonfree(self, val):
|
|
if osextras.find_on_path('ubuntu-drivers'):
|
|
self.prepare_nonfree_software.set_active(val)
|
|
else:
|
|
self.debug('Could not find ubuntu-drivers on the executable path.')
|
|
self.set_allow_nonfree(False)
|
|
|
|
def get_use_nonfree(self):
|
|
return self.prepare_nonfree_software.get_active()
|
|
|
|
def get_disable_secureboot(self):
|
|
return self.disable_secureboot.get_active()
|
|
|
|
def plugin_translate(self, lang):
|
|
PreparePageBase.plugin_translate(self, lang)
|
|
|
|
frontend = self.controller._wizard
|
|
frontend.status_label.set_text(self.controller.get_string(
|
|
'ubiquity/text/preparing_ud_label', lang))
|
|
|
|
release = misc.get_release()
|
|
|
|
from gi.repository import Gtk
|
|
for widget in [self.prepare_download_updates,
|
|
self.label_required_space,
|
|
self.label_free_space]:
|
|
text = i18n.get_string(Gtk.Buildable.get_name(widget), lang)
|
|
text = text.replace('${RELEASE}', release.name)
|
|
widget.set_label(text)
|
|
|
|
def on_nonfree_toggled(self, widget):
|
|
enabled = self.get_use_nonfree()
|
|
self.secureboot_box.set_sensitive(enabled)
|
|
self.info_loop(None)
|
|
|
|
def on_secureboot_toggled(self, widget):
|
|
enabled = self.get_disable_secureboot()
|
|
self.password_grid.set_sensitive(enabled)
|
|
self.info_loop(None)
|
|
|
|
def info_loop(self, unused_widget):
|
|
if not self.get_use_nonfree() \
|
|
or not self.password_grid.get_sensitive():
|
|
self.controller.allow_go_forward(True)
|
|
return True
|
|
|
|
complete = False
|
|
passw = self.password.get_text()
|
|
vpassw = self.verified_password.get_text()
|
|
|
|
if len(passw) == 0:
|
|
self.password_strength.set_current_page(
|
|
self.password_strength_pages['empty'])
|
|
elif len(passw) >= 8:
|
|
self.password_strength.set_current_page(
|
|
self.password_strength_pages['good'])
|
|
else:
|
|
self.password_strength.set_current_page(
|
|
self.password_strength_pages['too_short'])
|
|
|
|
if len(passw) == 0 or len(vpassw) == 0:
|
|
self.password_match.set_current_page(
|
|
self.password_match_pages['empty'])
|
|
elif passw != vpassw or (passw and len(passw) < 8):
|
|
self.password_match.set_current_page(
|
|
self.password_match_pages['empty'])
|
|
if len(passw) >= 8 and (not passw.startswith(vpassw) or
|
|
len(vpassw) / len(passw) > 0.6):
|
|
self.password_match.set_current_page(
|
|
self.password_match_pages['mismatch'])
|
|
else:
|
|
complete = True
|
|
self.password_match.set_current_page(
|
|
self.password_match_pages['ok'])
|
|
|
|
self.controller.allow_go_forward(complete)
|
|
return complete
|
|
|
|
def get_secureboot_key(self):
|
|
return self.password.get_text()
|
|
|
|
def show_learn_more(self, unused):
|
|
from gi.repository import Gtk
|
|
|
|
sb_title_template = 'ubiquity/text/efi_secureboot'
|
|
sb_info_template = 'ubiquity/text/efi_secureboot_info'
|
|
secureboot_title = self.controller.get_string(sb_title_template)
|
|
secureboot_msg = self.controller.get_string(sb_info_template)
|
|
|
|
dialog = Gtk.MessageDialog(
|
|
self.current_page.get_toplevel(), Gtk.DialogFlags.MODAL,
|
|
Gtk.MessageType.INFO, Gtk.ButtonsType.CLOSE, None)
|
|
dialog.set_title(secureboot_title)
|
|
dialog.set_markup(secureboot_msg)
|
|
dialog.run()
|
|
dialog.destroy()
|
|
|
|
|
|
class PageKde(PreparePageBase):
|
|
plugin_breadcrumb = 'ubiquity/text/breadcrumb_prepare'
|
|
restricted_package_name = 'kubuntu-restricted-addons'
|
|
|
|
def __init__(self, controller, *args, **kwargs):
|
|
from ubiquity.qtwidgets import StateBox
|
|
self.controller = controller
|
|
try:
|
|
from PyQt5 import uic
|
|
from PyQt5 import QtGui, QtWidgets
|
|
# No worries, this has nothing to do with NM, we just want the
|
|
# generic progress indicator from there
|
|
from ubiquity.frontend.kde_components.nmwidgets import ProgressIndicator
|
|
self.page = uic.loadUi('/usr/share/ubiquity/qt/stepPrepare.ui')
|
|
self.prepare_minimal_install = self.page.prepare_minimal_install
|
|
self.qt_label_minimal_install = self.page.qt_label_minimal_install
|
|
self.prepare_download_updates = self.page.prepare_download_updates
|
|
self.prepare_nonfree_software = self.page.prepare_nonfree_software
|
|
self.prepare_foss_disclaimer = self.page.prepare_foss_disclaimer
|
|
self.prepare_sufficient_space = StateBox(self.page)
|
|
self.secureboot_label = self.page.secureboot_label
|
|
self.disable_secureboot = self.page.disable_secureboot
|
|
self.password = self.page.password
|
|
self.verified_password = self.page.verified_password
|
|
self.password_extra_label = self.page.password_extra_label
|
|
self.badPassword = self.page.badPassword
|
|
self.badPassword.setPixmap(QtGui.QPixmap(
|
|
"/usr/share/icons/oxygen/16x16/status/dialog-warning.png"))
|
|
self.progress_indicator = ProgressIndicator()
|
|
self.progress_indicator.hide()
|
|
layout = QtWidgets.QHBoxLayout(self.page.progress_container)
|
|
layout.addWidget(self.progress_indicator)
|
|
# TODO we should set these up and tear them down while on this
|
|
# page.
|
|
try:
|
|
self.prepare_power_source = StateBox(self.page)
|
|
if upower.has_battery():
|
|
upower.setup_power_watch(self.prepare_power_source)
|
|
else:
|
|
self.prepare_power_source.hide()
|
|
except Exception as e:
|
|
# TODO use an inconsistent state?
|
|
print('unable to set up power source watch:', e)
|
|
if not os.path.exists(minimal_install_rlist_path):
|
|
self.qt_label_minimal_install.hide()
|
|
self.prepare_minimal_install.hide()
|
|
try:
|
|
self.prepare_network_connection = StateBox(self.page)
|
|
except Exception as e:
|
|
print('unable to set up network connection watch:', e)
|
|
except Exception as e:
|
|
print("Could not create prepare page:", str(e), file=sys.stderr)
|
|
self.debug('Could not create prepare page: %s', e)
|
|
self.page = None
|
|
self.set_using_secureboot(False)
|
|
self.plugin_widgets = self.page
|
|
|
|
def show_rst_page(self):
|
|
return False
|
|
|
|
def show_ubuntu_drivers_spinner(self):
|
|
self.progress_indicator.show()
|
|
self.progress_indicator.setSpinnerVisible(True)
|
|
|
|
def hide_ubuntu_drivers_spinner(self):
|
|
self.progress_indicator.setSpinnerVisible(False)
|
|
self.progress_indicator.hide()
|
|
|
|
def plugin_on_next_clicked(self):
|
|
if self.get_use_nonfree():
|
|
self.show_ubuntu_drivers_spinner()
|
|
|
|
def show_insufficient_space_page(self, required, free):
|
|
from PyQt5 import QtWidgets
|
|
QtWidgets.QMessageBox.critical(self.page,
|
|
free,
|
|
required)
|
|
sys.exit(1)
|
|
return
|
|
|
|
def set_using_secureboot(self, secureboot):
|
|
self.using_secureboot = secureboot
|
|
self.secureboot_label.setVisible(secureboot)
|
|
self.disable_secureboot.setVisible(secureboot)
|
|
self.password.setVisible(secureboot)
|
|
self.verified_password.setVisible(secureboot)
|
|
self.password_extra_label.setVisible(secureboot)
|
|
self.badPassword.hide()
|
|
if (secureboot):
|
|
self.password.textChanged.connect(self.verify_password)
|
|
self.verified_password.textChanged.connect(self.verify_password)
|
|
|
|
# show warning if passwords do not match
|
|
def verify_password(self):
|
|
complete = False
|
|
|
|
if self.password.text() == self.verified_password.text():
|
|
self.badPassword.hide()
|
|
complete = True
|
|
else:
|
|
self.badPassword.show()
|
|
|
|
if not self.password.text():
|
|
complete = False
|
|
|
|
self.controller.allow_go_forward(complete)
|
|
|
|
def get_secureboot_key(self):
|
|
return str(self.page.password.text())
|
|
|
|
def enable_download_updates(self, val):
|
|
self.prepare_download_updates.setEnabled(val)
|
|
|
|
def set_download_updates(self, val):
|
|
self.prepare_download_updates.setChecked(val)
|
|
|
|
def get_download_updates(self):
|
|
from PyQt5.QtCore import Qt
|
|
return self.prepare_download_updates.checkState() == Qt.Checked
|
|
|
|
def set_minimal_install(self, val):
|
|
self.prepare_minimal_install.setChecked(val)
|
|
|
|
def get_minimal_install(self):
|
|
if self.prepare_minimal_install.isChecked():
|
|
return True
|
|
return False
|
|
|
|
def set_allow_nonfree(self, allow):
|
|
if not allow:
|
|
self.prepare_nonfree_software.setChecked(False)
|
|
self.prepare_nonfree_software.setVisible(False)
|
|
self.prepare_foss_disclaimer.setVisible(False)
|
|
|
|
def set_use_nonfree(self, val):
|
|
if osextras.find_on_path('ubuntu-drivers'):
|
|
self.prepare_nonfree_software.setChecked(val)
|
|
else:
|
|
self.debug('Could not find ubuntu-drivers on the executable path.')
|
|
self.set_allow_nonfree(False)
|
|
|
|
def get_use_nonfree(self):
|
|
from PyQt5.QtCore import Qt
|
|
return self.prepare_nonfree_software.checkState() == Qt.Checked
|
|
|
|
def plugin_translate(self, lang):
|
|
PreparePageBase.plugin_translate(self, lang)
|
|
# Translate the progress label
|
|
self.progress_indicator.setText(self.controller.get_string(
|
|
'ubiquity/text/preparing_ud_label', lang))
|
|
# gtk does the ${RELEASE} replace for the title in gtk_ui but we do
|
|
# it per plugin because our title widget is per plugin
|
|
release = misc.get_release()
|
|
widgets = (
|
|
self.page.prepare_heading_label,
|
|
self.page.prepare_download_updates,
|
|
)
|
|
for widget in widgets:
|
|
text = widget.text()
|
|
text = text.replace('${RELEASE}', release.name)
|
|
text = text.replace('Ubuntu', 'Kubuntu')
|
|
widget.setText(text)
|
|
|
|
|
|
class Page(plugin.Plugin):
|
|
def prepare(self):
|
|
if (self.db.get('apt-setup/restricted') == 'false' or
|
|
self.db.get('apt-setup/multiverse') == 'false'):
|
|
self.ui.set_allow_nonfree(False)
|
|
else:
|
|
use_nonfree = self.db.get('ubiquity/use_nonfree') == 'true'
|
|
self.ui.set_use_nonfree(use_nonfree)
|
|
|
|
arch, subarch = archdetect()
|
|
if 'efi' in subarch:
|
|
if is_secure_boot():
|
|
self.ui.set_using_secureboot(True)
|
|
|
|
self.ui.download_updates = self.db.get('ubiquity/download_updates') == 'true'
|
|
if self.ui.download_updates_enabled:
|
|
self.ui.set_download_updates(self.ui.download_updates)
|
|
minimal_install = self.db.get('ubiquity/minimal_install') == 'true'
|
|
self.ui.set_minimal_install(minimal_install)
|
|
self.apply_debconf_branding()
|
|
|
|
# wait for it to finish
|
|
if self.frontend.ubuntu_drivers:
|
|
self.frontend.ubuntu_drivers.communicate()
|
|
self.frontend.ubuntu_drivers = None
|
|
|
|
# output whether there are OEM packages for this system
|
|
try:
|
|
with open('/run/ubuntu-drivers-oem.autoinstall', 'r') as f:
|
|
syslog.syslog(F'ubuntu-drivers list-oem finished with: "{" ".join(f.read().splitlines())}"')
|
|
except FileNotFoundError:
|
|
syslog.syslog("ubuntu-drivers list-oem finished with no available packages. Maybe we need to apt update? "
|
|
"Doing that and trying again.")
|
|
# We only do this when we really have to since it could be slow: apt update & re-run of ubuntu-drivers
|
|
self.frontend.save_oem_metapackages_list(wait_finished=True)
|
|
try:
|
|
with open('/run/ubuntu-drivers-oem.autoinstall', 'r') as f:
|
|
syslog.syslog(F'ubuntu-drivers list-oem finished with: "{" ".join(f.read().splitlines())}"')
|
|
except FileNotFoundError:
|
|
syslog.syslog("No, we didn't find any OEM packages again.")
|
|
|
|
if self.should_show_rst_page():
|
|
if not self.ui.show_rst_page():
|
|
self.setup_sufficient_space()
|
|
else:
|
|
self.ui.plugin_is_restart = True
|
|
else:
|
|
self.setup_sufficient_space()
|
|
command = ['/usr/share/ubiquity/simple-plugins', 'prepare']
|
|
questions = ['ubiquity/use_nonfree']
|
|
return command, questions
|
|
|
|
def apply_debconf_branding(self):
|
|
release = misc.get_release()
|
|
for template in ['ubiquity/text/required_space',
|
|
'ubiquity/text/free_space']:
|
|
self.db.subst(template, 'RELEASE', release.name)
|
|
|
|
def should_show_rst_page(self):
|
|
search = '/sys/module/ahci/drivers/pci:ahci/*/remapped_nvme'
|
|
for remapped_nvme in glob.glob(search):
|
|
with open(remapped_nvme, 'r') as f:
|
|
if int(f.read()) > 0:
|
|
return True
|
|
return os.environ.get('SHOW_RST_UI', '0') == '1'
|
|
|
|
def setup_sufficient_space(self):
|
|
# TODO move into prepare.
|
|
size = misc.install_size()
|
|
self.db.subst(
|
|
'ubiquity/text/required_space', 'SIZE',
|
|
misc.format_size(size))
|
|
free = self.free_space()
|
|
self.db.subst(
|
|
'ubiquity/text/free_space', 'SIZE',
|
|
misc.format_size(free))
|
|
required_text = self.description('ubiquity/text/required_space')
|
|
free_text = self.description('ubiquity/text/free_space')
|
|
self.ui.set_sufficient_space(size < free, required_text, free_text)
|
|
|
|
def free_space(self):
|
|
biggest = 0
|
|
with misc.raised_privileges():
|
|
proc = subprocess.Popen(
|
|
['parted_devices'],
|
|
stdout=subprocess.PIPE, universal_newlines=True)
|
|
devices = proc.communicate()[0].rstrip('\n').split('\n')
|
|
for device in devices:
|
|
if device and int(device.split('\t')[1]) > biggest:
|
|
biggest = int(device.split('\t')[1])
|
|
return biggest
|
|
|
|
def ok_handler(self):
|
|
download_updates = self.ui.get_download_updates()
|
|
minimal_install = self.ui.get_minimal_install()
|
|
use_nonfree = self.ui.get_use_nonfree()
|
|
secureboot_key = self.ui.get_secureboot_key()
|
|
self.preseed_bool('ubiquity/use_nonfree', use_nonfree)
|
|
self.preseed_bool('ubiquity/download_updates', download_updates)
|
|
self.preseed_bool('ubiquity/minimal_install', minimal_install)
|
|
if self.ui.using_secureboot and secureboot_key:
|
|
self.preseed('ubiquity/secureboot_key', secureboot_key, seen=True)
|
|
if use_nonfree:
|
|
with misc.raised_privileges():
|
|
# Install ubuntu-restricted-addons.
|
|
self.preseed_bool('apt-setup/universe', True)
|
|
self.preseed_bool('apt-setup/multiverse', True)
|
|
if self.db.fget('ubiquity/nonfree_package', 'seen') != 'true':
|
|
self.preseed(
|
|
'ubiquity/nonfree_package',
|
|
self.ui.restricted_package_name)
|
|
plugin.Plugin.ok_handler(self)
|
|
|
|
def cleanup(self):
|
|
self.ui.hide_ubuntu_drivers_spinner()
|
|
plugin.Plugin.cleanup(self)
|