commit 1b288197ecdf4c523dd7789b7e8505993c207497 Author: Hans P Möller Date: Wed May 29 21:21:52 2019 -0400 initial commit diff --git a/data/upg-notifier-autostart.desktop b/data/upg-notifier-autostart.desktop new file mode 100644 index 0000000..2c1474a --- /dev/null +++ b/data/upg-notifier-autostart.desktop @@ -0,0 +1,7 @@ +[Desktop Entry] +Exec=/usr/lib/lubuntu-update-notifier/upg-notifier.sh +NoDisplay=true +Name=upgNotifier +Icon=system-software-update +Type=Application +Version=0.1 diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..9d8c61b --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +lubuntu-update-notifier (0.1) UNRELEASED; urgency=medium + + * Initial release. (Closes: #XXXXXX) + + -- Hans P Möller Tue, 28 May 2019 16:42:14 -0400 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..f599e28 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +10 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..16097dc --- /dev/null +++ b/debian/control @@ -0,0 +1,20 @@ +Source: lubuntu-update-notifier +Section: admin +Priority: optional +Maintainer: Hans P. Möller +Build-Depends: debhelper (>=11~), + dh-python, + python3-apt, + python3-all, + python3-setuptools, +Standards-Version: 4.0.0 + +Package: lubuntu-update-notifier +Architecture: all +Depends: ${shlibs:Depends}, ${python3:Depends}, ${misc:Depends}, + update-notifier-common, aptdaemon, debconf-kde-helper, python3-pyqt5, + python3, python3-aptdaemon (>= 0.6.20ubuntu16) +Description: Daemon which notifies about package updates + Opens a window when package updates are available. + It also allows to do a full upgrade, aka dist-upgrade. + diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..e69de29 diff --git a/debian/lubuntu-update-notifier.install b/debian/lubuntu-update-notifier.install new file mode 100644 index 0000000..5aef360 --- /dev/null +++ b/debian/lubuntu-update-notifier.install @@ -0,0 +1,5 @@ +data/upg-notifier-autostart.desktop etc/xdg/autostart +#debian/lubuntu-update-notifier/usr/lib/python3*/*-packages/lubuntu-update-notifier/*.py +#debian/lubuntu-update-notifier/usr/bin/upg-notifier.sh +#debian/tmp/usr/share/applications/upg-notifier-autostart.desktop +#debian/lubuntu-update-notifier/etc/xdg/autostart/upg-notifier-autostart.desktop diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..481abc5 --- /dev/null +++ b/debian/rules @@ -0,0 +1,7 @@ +#!/usr/bin/make -f +#export DH_VERBOSE=1 +#export PYBUILD_NAME=lubuntu-update-notifier +export PYBUILD_INTERPRETERS=python3 + +%: + dh $@ --with python3 --buildsystem=pybuild diff --git a/lubuntu-update-notifier/__init__.py b/lubuntu-update-notifier/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/notifier.py b/notifier.py new file mode 100755 index 0000000..1938b39 --- /dev/null +++ b/notifier.py @@ -0,0 +1,150 @@ +#!/usr/bin/python3 + +import sys +from PyQt5.QtWidgets import (QWidget, QApplication, QLabel, QPushButton, + QHBoxLayout, QVBoxLayout) +from PyQt5.QtCore import (Qt, QProcess) +from PyQt5.QtGui import QIcon +from optparse import OptionParser + +from pathlib import Path + +import subprocess +#from upgrader import DialogUpg #still dont know how to ask for sudo when calling a function + +class Dialog(QWidget): + def __init__(self, upgrades, security_upgrades, reboot_required, upg_path): + QWidget.__init__(self) + self.upgrades = upgrades + self.security_upgrades = security_upgrades + self.upg_path = upg_path + + self.initUI() + self.upgradeBtn.clicked.connect(self.call_upgrade) + self.closeBtn.clicked.connect(self.call_reject) + + def initUI(self): + self.label = QLabel() + self.label.setAlignment(Qt.AlignHCenter) + self.upgradeBtn = QPushButton("Upgrade") + self.closeBtn = QPushButton("Close") + text = "" + + hbox=QHBoxLayout() + hbox.addStretch(1) + hbox.addWidget(self.upgradeBtn) + hbox.addWidget(self.closeBtn) + hbox.addStretch(1) + + vbox=QVBoxLayout() + vbox.addWidget(self.label) + vbox.addLayout(hbox) + + if self.upg_path == None: + self.upgradeBtn.setVisible(False) + + self.setLayout(vbox) + self.setGeometry(300, 300, 500, 150) + self.setWindowTitle("Update Notifier") + self.center() + + if self.upgrades > 0: + '''text = "There are(is) %s upgrade(s) available and %s security update(s) available\n" % (self.upgrades, self.security_upgrades) + text = text + "Do you want to do a system upgrade?\nThis will upgrade, install and remove packages"''' + text = "There are upgrades available. Do you want to do a system upgrade?\nThis will upgrade, install and remove packages" + + + if reboot_required: + if text == "": + text = "Reboot is needed" + self.upgradeBtn.setVisible(False) + else: + text = text + "\nReboot is needed" + + self.label.setText(text) + + def center(self): + frameGm = self.frameGeometry() + screen = QApplication.desktop().screenNumber(QApplication.desktop().cursor().pos()) + centerPoint = QApplication.desktop().screenGeometry(screen).center() + frameGm.moveCenter(centerPoint) + self.move(frameGm.topLeft()) + + def call_reject(self): + app.quit() + + def call_upgrade(self): + self.label.setText("Upgrading....") + + #TODO maybe open another thread so notifier won't freeze + if self.upg_path == "terminal": + #cmd = ['qterminal', '-e', 'sudo', 'apt', 'dist-upgrade'] + cmd = ['qterminal', '-e', './upg.sh'] + else: + cmd = ['lxqt-sudo', self.upg_path,'--full-upgrade'] + #process = subprocess.Popen(self.upg_path) + #process = subprocess.Popen(cmd, shell=True) + self.upgradeBtn.setVisible(False) + self.upgradeBtn.setEnabled(False) + process = subprocess.Popen(cmd) + process.wait() + + '''options.fullUpgrade = 1 + dialogUpg = DialogUpg(optionss, pkg=self.packages) + dialogUpg.show()''' + + if self.upg_path == "terminal": + text = "Upgrade finished" + + reboot_required_path = Path("/var/run/reboot-required") + if reboot_required_path.exists(): + text = text + "\n" + "Restart required" + self.label.setText(text) + self.closeBtn.setVisible(True) + self.closeBtn.setEnabled(True) + + else: + app.quit() + +class App(QApplication): + def __init__(self, upgrades, security_upgrades, reboot_required, upg_path, + *args): + QApplication.__init__(self, *args) + self.dialog = Dialog(upgrades, security_upgrades, reboot_required, + upg_path) + self.dialog.show() + +def main(args, upgrades, security_upgrades, reboot_required, upg_path): + global app + app = App(upgrades, security_upgrades, reboot_required, upg_path, args) + app.setWindowIcon(QIcon.fromTheme("system-software-update")) + app.exec_() + +if __name__ == "__main__": + parser = OptionParser() + parser.add_option("-p", + "--upgrader-sw", + dest="upg_path", + help="Define software/app to open for upgrade", + metavar="APP") + parser.add_option("-u", + "--upgrades", + dest="upgrades", + help="How many upgrades are available", + metavar="APP") + parser.add_option("-s", + "--security-upg", + dest="security_upgrades", + help="How many security upgrades are available", + metavar="APP") + + (options, args) = parser.parse_args() + + reboot_required_path = Path("/var/run/reboot-required") + if reboot_required_path.exists(): + reboot_required = True + else: + reboot_required = False + + if int(options.upgrades) > 0 or reboot_required: + main(sys.argv, int(options.upgrades), int(options.security_upgrades), reboot_required, options.upg_path) diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..ee3c6e9 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[sdist] +formats = bztar diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..ae60532 --- /dev/null +++ b/setup.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 + +from setuptools import setup +#from distutils.core import setup +#from DistUtilsExtra.command import * + +setup( + name="lubuntu-update-notifier", + version="0.1", + packages=['lubuntu-update-notifier'], + scripts=['upgrader.py'], + data_files=[ + ('lib/lubuntu-update-notifier/', + ['upg-notifier.sh','notifier.py'])] +) diff --git a/upg-notifier.sh b/upg-notifier.sh new file mode 100755 index 0000000..611be6e --- /dev/null +++ b/upg-notifier.sh @@ -0,0 +1,21 @@ +#!/bin/sh +while true; + do + OUT=`/usr/lib/update-notifier/apt-check 2>&1` + #echo $OUT + oldIFS=$IFS + IFS=';' + j=0 + for STRING in $OUT; do + case $j in + 0) + UPG=$STRING;; + 1) + SEC=$STRING;; + esac + j=`expr $j + 1` + done + IFS=$oldIFS + /usr/lib/lubuntu-update-notifier/notifier.py -u $UPG -s $SEC -p /usr/bin/upgrader.py + sleep 3600 +done; diff --git a/upgrader.py b/upgrader.py new file mode 100755 index 0000000..27d0dd6 --- /dev/null +++ b/upgrader.py @@ -0,0 +1,335 @@ +#!/usr/bin/python3 +# deppend on +# -aptdaemon +# -debconf-kde-helper +import sys +import os + +#import pty +#import subprocess + +from PyQt5.QtWidgets import (QWidget, QApplication, QLabel, QPushButton, + QHBoxLayout, QVBoxLayout, QProgressBar, QTreeView, + QPlainTextEdit, QMessageBox) +from PyQt5 import uic +from PyQt5.QtCore import (Qt, QProcess) +from PyQt5.QtGui import (QStandardItemModel, QIcon, QTextCursor, QPalette) +from optparse import OptionParser +from aptdaemon import client +from aptdaemon.errors import NotAuthorizedError, TransactionFailed +from aptdaemon.enums import (EXIT_SUCCESS, + EXIT_FAILED, + STATUS_COMMITTING, + get_error_description_from_enum, + get_error_string_from_enum, + get_status_string_from_enum) +from pathlib import Path + +class DialogUpg(QWidget): + def __init__(self, options=None): + QWidget.__init__(self) + + self.initUI() + self.closeBtn.clicked.connect(self.call_reject) + self.apt_client = client.AptClient() + self.downloadText = "" + self.detailText = "" + self.old_short_desc="" + self.details="" + self.status="" + self.errors = [] + #TODO make a terminal work to see more info + #self.master, self.slave = pty.openpty() + '''proc = subprocess.Popen(['qterminal'], + stdin=self.slave, + #stdout=subprocess.PIPE, + stdout=self.slave, + #stderr=subprocess.PIPE + stderr=self.slave)''' + + + if options.fullUpgrade: + self.trans2 = self.apt_client.upgrade_system(safe_mode=False) + self.setWindowTitle('Full Upgrade') + else: + self.trans2 = self.apt_client.upgrade_system(safe_mode=True) + + if options.cacheUpdate: + self.trans1 = self.apt_client.update_cache() + self.update_cache() + else: + self.upgrade() + + def initUI(self): + self.label = QLabel() + self.label.setAlignment(Qt.AlignHCenter) + self.closeBtn = QPushButton("Close") + self.progressBar = QProgressBar() + self.plainTextEdit = QPlainTextEdit() + palette = self.plainTextEdit.palette() + palette.setColor(QPalette.Base, Qt.black) + palette.setColor(QPalette.Text, Qt.gray) + self.plainTextEdit.setPalette(palette) + + hbox=QHBoxLayout() + hbox.addStretch(1) + hbox.addWidget(self.closeBtn) + hbox.addStretch(1) + + vbox=QVBoxLayout() + vbox.addWidget(self.label) + vbox.addWidget(self.progressBar) + vbox.addWidget(self.plainTextEdit) + vbox.addLayout(hbox) + + self.setLayout(vbox) + self.setGeometry(300, 300, 500, 150) + self.setWindowTitle('Upgrade') + self.progressBar.setVisible(False) + self.plainTextEdit.setReadOnly(True) + self.plainTextEdit.setVisible(False) + self.center() + + def center(self): + frameGm = self.frameGeometry() + screen = QApplication.desktop().screenNumber(QApplication.desktop().cursor().pos()) + centerPoint = QApplication.desktop().screenGeometry(screen).center() + frameGm.moveCenter(centerPoint) + self.move(frameGm.topLeft()) + + def upgrade_progress(self, transaction, progress): + self.progressBar.setVisible(True) + self.progressBar.setValue(progress) + + def update_progress(self, transaction, progress): + self.progressBar.setVisible(True) + self.progressBar.setValue(progress) + self.label.setText("Updating cache...") + + def update_progress_download(self, transaction, uri, status, short_desc, + total_size, current_size, msg): + self.plainTextEdit.setVisible(True) + if self.old_short_desc == short_desc: #if it's the same file we update the line, don't append new line + self.plainTextEdit.moveCursor(QTextCursor.End) + cursor = self.plainTextEdit.textCursor() + cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor) + cursor.select(QTextCursor.LineUnderCursor) + cursor.removeSelectedText() + self.plainTextEdit.insertPlainText(str(current_size) + "/" + str(total_size) + " " + msg) + cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor) + else: + self.plainTextEdit.moveCursor(QTextCursor.End) + self.plainTextEdit.appendPlainText(status + " " + short_desc + "\n") + self.plainTextEdit.insertPlainText(str(current_size) + "/" + str(total_size) + " " + msg) + self.plainTextEdit.moveCursor(QTextCursor.End) + self.old_short_desc = short_desc + + def upgrade_progress_download(self, transaction, uri, status, short_desc, + total_size, current_size, msg): + self.plainTextEdit.setVisible(True) + if self.status == "status-downloading":#TODO it prints the last line after installation is complete, need to manage this. + if self.old_short_desc == short_desc: #if it's the same file we update the line, don't append new line + self.plainTextEdit.moveCursor(QTextCursor.End) + cursor = self.plainTextEdit.textCursor() + cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor) + cursor.select(QTextCursor.LineUnderCursor) + cursor.removeSelectedText() + self.plainTextEdit.insertPlainText(str(current_size) + "/" + str(total_size) + " " + msg) + cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor) + else: + self.plainTextEdit.moveCursor(QTextCursor.End) + self.plainTextEdit.appendPlainText(status + " " + short_desc + "\n") + self.plainTextEdit.insertPlainText(str(current_size) + "/" + str(total_size) + " " + msg) + self.plainTextEdit.moveCursor(QTextCursor.End) + self.old_short_desc = short_desc + + def update_progress_detail(self, transaction, current_items, total_items, + current_bytes, total_bytes, current_cps, eta): + if total_items > 0: + self.plainTextEdit.setVisible(True) + if self.detailText != "Fetching " + str(current_items) + " of " + str(total_items): + self.detailText = "Fetching " + str(current_items) + " of " + str(total_items) + self.label.setText(self.detailText + "\n" + self.downloadText) + + + def upgrade_progress_detail(self, transaction, current_items, total_items, + current_bytes, total_bytes, current_cps, eta): + #self.detailText = "Upgrading..." + #self.label.setText(self.detailText) + if total_items > 0: + self.plainTextEdit.setVisible(True) + if self.detailText != "Downloaded " + str(current_items) + " of " + str(total_items): + self.detailText = "Downloaded " + str(current_items) + " of " + str(total_items) + self.label.setText(self.detailText + "\n" + self.downloadText) + + def upgrade_finish(self, transaction, exit_state): + if exit_state == EXIT_FAILED: + error_string = get_error_string_from_enum(transaction.error.code) + error_desc = get_error_description_from_enum(transaction.error.code) + + text = "Upgrade finished" + + reboot_required_path = Path("/var/run/reboot-required") + if reboot_required_path.exists(): + text = text + "\n" + "Restart required" + self.progressBar.setVisible(False) + + if(len(self.errors)>0): + text = text + "\n With some Errors" + self.plainTextEdit.appendPlainText("Error Resume:\n") + for error in self.errors: + self.plainTextEdit.setEnabled(False) + self.plainTextEdit.insertPlainText(error + "\n") + self.plainTextEdit.insertPlainText(error_string + "\n") + self.plainTextEdit.insertPlainText(error_desc + "\n") + self.plainTextEdit.moveCursor(QTextCursor.End) + + self.label.setText(text) + self.closeBtn.setVisible(True) + self.closeBtn.setEnabled(True) + self.plainTextEdit.setEnabled(True) + + def upgrade_error(self, transaction, error_code, error_details): + self.plainTextEdit.setVisible(True) + self.errors.append("Eror Code: " + str(error_code)) + self.errors.append("Error Detail: " + error_details) + #for error in self.errors: + #self.plainTextEdit.appendPlainText(error) + self.plainTextEdit.setVisible(True) + self.closeBtn.setEnabled(True) + print("ECode: " + str(error_code) + "\n") + print("EDetail: " + error_details + "\n") + + def upgrade_cancellable_changed(self, transaction, cancellable): + self.closeBtn.setEnabled(cancellable) + + def update_cache(self): + self.closeBtn.setVisible(False) + #self.label.setText("Updating cache...") + try: + self.trans1.connect('finished', self.update_finish) + + self.trans1.connect('progress-changed', self.update_progress) + self.trans1.connect('progress-details-changed', + self.update_progress_detail) + self.trans1.connect('progress-download-changed', + self.update_progress_download) + self.trans1.connect('error', self.upgrade_error) + #status-details-changed is not needed, progress_download already hast this info when downloading + #self.trans1.connect("status-details-changed", self.status_details_changed) + self.trans1.connect("status-changed", self.status_changed) + #TODO make a terminal work to see more info + #self.trans1.set_terminal(os.ttyname(self.slave)) + + self.trans1.run() + #print(self.trans1) + + except (NotAuthorizedError, TransactionFailed) as e: + print("Warning: install transaction not completed successfully:" + + "{}".format(e)) + + def update_finish(self, transaction, exit_state): + self.label.setText("Update Cache Finished") + if exit_state == EXIT_FAILED: + error_string = get_error_string_from_enum(transaction.error.code) + error_desc = get_error_description_from_enum(transaction.error.code) + self.plainTextEdit.setEnabled(False) + self.plainTextEdit.moveCursor(QTextCursor.End) + self.plainTextEdit.insertPlainText(error_string + "\n") + self.plainTextEdit.insertPlainText(error_desc + "\n") + self.plainTextEdit.moveCursor(QTextCursor.End) + self.plainTextEdit.setEnabled(True) + + self.upgrade() + + def status_changed(self, transaction, status): + self.status = status + self.label.setText("Status:" + get_status_string_from_enum(status)) + print("Status:" + get_status_string_from_enum(status) + " " + status + "\n") + + def status_details_changed(self, transaction, details): + self.plainTextEdit.setVisible(True) + if self.details != details: + self.details = details + + if self.status != "status-downloading": #if "Downloading xxxxx" is handled by "upgrade_progress_download" in short_desc + self.plainTextEdit.appendPlainText(details) + self.plainTextEdit.moveCursor(QTextCursor.End) + self.label.setText(details) + else: + self.label.setText(self.detailText + "\n" + details) #if is downloading put the "Downloaded x of y" text + #print("PTY:" + str(self.slave)) + + print("Status Details:" + details) + + def upgrade(self): + try: + self.trans2.connect('progress-changed', self.upgrade_progress) + self.trans2.connect('cancellable-changed', + self.upgrade_cancellable_changed) + self.trans2.connect('progress-details-changed', + self.upgrade_progress_detail) + self.trans2.connect('progress-download-changed', + self.upgrade_progress_download) + self.trans2.connect('finished', self.upgrade_finish) + self.trans2.connect('error', self.upgrade_error) + self.trans2.connect("status-details-changed", self.status_details_changed) + self.trans2.connect("status-changed", self.status_changed) + + #TODO make a terminal work to see more info + #self.trans2.set_terminal(os.ttyname(self.slave)) + + ''' + #TODO implement this + self.trans2.connect("medium-required", self._on_medium_required) + self.trans2.connect("config-file-conflict", self._on_config_file_conflict) + remove_obsoleted_depends + ''' + self.trans2.set_debconf_frontend('kde') + self.trans2.run() + + except (NotAuthorizedError, TransactionFailed) as e: + print("Warning: install transaction not completed successfully:" + + "{}".format(e)) + + def call_reject(self): + app.quit() + +class App(QApplication): + def __init__(self, options, *args): + QApplication.__init__(self, *args) + self.dialog = DialogUpg(options) + self.dialog.show() + + +def main(args, options): + global app + app = App(options, args) + app.setWindowIcon(QIcon.fromTheme("system-software-update")) + + # Check for root permissions + if os.geteuid() != 0: + text = "Please run this software with administrative rights. To do so, run this program with lxqt-sudo." + title = "Need administrative powers" + msgbox = QMessageBox.critical(None, title, text) + sys.exit(1) + else: + app.exec_() + +if __name__ == "__main__": + # check arguments + parser = OptionParser() + parser.add_option("", + "--cache-update", + action="store_true", + dest="cacheUpdate", + help="Update Cache Before Upgrade") + parser.add_option("", + "--full-upgrade", + action="store_true", + dest="fullUpgrade", + help="Full upgrade same as dist-upgrade") + (options, args) = parser.parse_args() + + #run it + main(sys.argv, options)