diff --git a/usr/share/blackbox/blackbox.py b/usr/share/blackbox/blackbox.py new file mode 100644 index 0000000..6ae8484 --- /dev/null +++ b/usr/share/blackbox/blackbox.py @@ -0,0 +1,1290 @@ +#!/usr/bin/env python3 + +import gi +import os + +from requests.packages import package + +import Functions as fn +import signal + +import subprocess +from Functions import os +from queue import Queue +from time import sleep +import sys +import time + +# UI modules +from ui.GUI import GUI +from ui.SplashScreen import SplashScreen +from ui.ProgressBarWindow import ProgressBarWindow +from ui.AppFrameGUI import AppFrameGUI +from ui.AboutDialog import AboutDialog +from ui.MessageDialog import MessageDialog +from ui.PacmanLogWindow import PacmanLogWindow +from ui.PackageListDialog import PackageListDialog +from ui.ProgressDialog import ProgressDialog +from ui.ISOPackagesWindow import ISOPackagesWindow +from ui.PackageSearchWindow import PackageSearchWindow +from ui.PackagesImportDialog import PackagesImportDialog + +# Configuration module +from Settings import Settings + +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk, Gdk, GdkPixbuf, Pango, GLib + +# #============================================================ +# #= Authors: Erik Dubois - Cameron Percival - Fennec = +# #============================================================ + +# Folder structure + +# cache contains descriptions - inside we have corrections for manual intervention +# + installed applications list +# yaml is the folder that is used to create the application +# yaml-awesome is a copy/paste from Calamares to meld manually - not used in the app + +base_dir = os.path.dirname(os.path.realpath(__file__)) + + +class Main(Gtk.Window): + # Create a queue, for worker communication (Multithreading - used in GUI layer) + queue = Queue() + + # Create a queue to handle package install/removal + pkg_queue = Queue() + + # Create a queue for storing search results + search_queue = Queue() + + # Create a queue for storing Pacman log file contents + pacmanlog_queue = Queue() + + # Create a queue for storing packages waiting behind an in-progress pacman install transaction + pkg_holding_queue = Queue() + + def __init__(self): + try: + super(Main, self).__init__(title="Sofirem") + + self.set_border_width(10) + self.connect("delete-event", self.on_close) + self.set_position(Gtk.WindowPosition.CENTER) + self.set_icon_from_file(os.path.join(base_dir, "images/sofirem.png")) + self.set_default_size(1100, 900) + + # ctrl+f give focus to search entry + self.connect("key-press-event", self.on_keypress_event) + + # used for notifications + self.timeout_id = None + + # default: displaying versions are disabled + self.display_versions = False + + # initial app load search_activated is set to False + self.search_activated = False + + # initial app load show the progress dialog window when a package is installed/uninstalled + self.display_package_progress = False + + print( + "---------------------------------------------------------------------------" + ) + print("If you have errors, report it on the discord channel of ArcoLinux") + print( + "---------------------------------------------------------------------------" + ) + print("You can receive support on https://discord.gg/stBhS4taje") + print( + "---------------------------------------------------------------------------" + ) + print( + "Many applications are coming from the Arch Linux repos and can be installed" + ) + print( + "without any issues. Other applications are available from third party repos" + ) + print("like Chaotic repo, ArcoLinux repo and others.") + print( + "---------------------------------------------------------------------------" + ) + print("We do NOT build packages from AUR.") + print( + "---------------------------------------------------------------------------" + ) + print("Some packages are only available on the ArcoLinux repos.") + print( + "---------------------------------------------------------------------------" + ) + + if os.path.exists(fn.sofirem_lockfile): + running = fn.check_if_process_running("sofirem") + if running is True: + fn.logger.error( + "Sofirem lock file found in %s" % fn.sofirem_lockfile + ) + fn.logger.error("Is there another Sofirem instance running ?") + + sys.exit(1) + + else: + splash_screen = SplashScreen() + + while Gtk.events_pending(): + Gtk.main_iteration() + + sleep(1.5) + splash_screen.destroy() + + # test there is no pacman lock file on the system + if fn.check_pacman_lockfile(): + message_dialog = MessageDialog( + "Error", + "Sofirem cannot proceed pacman lockfile found", + "Pacman cannot lock the db, a lockfile is found inside %s" + % fn.pacman_lockfile, + "Is there another Pacman process running ?", + "error", + False, + ) + message_dialog.show_all() + message_dialog.run() + message_dialog.hide() + + sys.exit(1) + + fn.logger.info("pkgver = pkgversion") + fn.logger.info("pkgrel = pkgrelease") + print( + "---------------------------------------------------------------------------" + ) + fn.logger.info("Distro = " + fn.distr) + print( + "---------------------------------------------------------------------------" + ) + + # start making sure sofirem starts next time with dark or light theme + if os.path.isdir(fn.home + "/.config/gtk-3.0"): + try: + if not os.path.islink("/root/.config/gtk-3.0"): + if os.path.exists("/root/.config/gtk-3.0"): + fn.shutil.rmtree("/root/.config/gtk-3.0") + + fn.shutil.copytree( + fn.home + "/.config/gtk-3.0", "/root/.config/gtk-3.0" + ) + except Exception as e: + fn.logger.warning("GTK config: %s" % e) + + if os.path.isdir("/root/.config/xsettingsd/xsettingsd.conf"): + try: + if not os.path.islink("/root/.config/xsettingsd/"): + if os.path.exists("/root/.config/xsettingsd/"): + fn.shutil.rmtree("/root/.config/xsettingsd/") + if fn.path.isdir(fn.home + "/.config/xsettingsd/"): + fn.shutil.copytree( + fn.home + "/.config/xsettingsd/", + "/root/.config/xsettingsd/", + ) + except Exception as e: + fn.logger.warning("xsettingsd config: %s" % e) + + # store package information into memory, and use the dictionary returned to search in for quicker retrieval + fn.logger.info("Storing package metadata started") + + self.packages = fn.store_packages() + fn.logger.info("Storing package metadata completed") + + fn.logger.info("Categories = %s" % len(self.packages.keys())) + + total_packages = 0 + + for category in self.packages: + total_packages += len(self.packages[category]) + + fn.logger.info("Total packages = %s" % total_packages) + + fn.logger.info("Setting up GUI") + + GUI.setup_gui( + self, + Gtk, + Gdk, + GdkPixbuf, + base_dir, + os, + Pango, + fn.settings_config, + ) + + # Create installed.lst file for first time + + fn.get_current_installed() + installed_lst_file = "%s/cache/installed.lst" % base_dir + packages_app_start_file = "%s/%s-packages.txt" % ( + fn.log_dir, + fn.datetime.now().strftime("%Y-%m-%d-%H-%M-%S"), + ) + if os.path.exists(installed_lst_file): + fn.logger.info("Created installed.lst") + # Keep log of installed packages before the app makes changes + # fn.shutil.copy(installed_lst_file, packages_app_start_file) + + # pacman sync db and also tests network connectivity + + thread_pacman_sync_db = fn.threading.Thread( + name="thread_pacman_sync_db", + target=self.pacman_db_sync, + daemon=True, + ) + thread_pacman_sync_db.start() + # if self.pacman_db_sync() is False: + # sys.exit(1) + + except Exception as e: + fn.logger.error("Exception in Main() : %s" % e) + + # ===================================================== + # PACMAN DB SYNC + # ===================================================== + + def pacman_db_sync(self): + sync_err = fn.sync_package_db() + + if sync_err is not None: + fn.logger.error("Pacman db synchronization failed") + + print( + "---------------------------------------------------------------------------" + ) + + GLib.idle_add( + self.show_sync_db_message_dialog, + sync_err, + priority=GLib.PRIORITY_DEFAULT, + ) + + else: + fn.logger.info("Pacman db synchronization completed") + + return True + + def show_sync_db_message_dialog(self, sync_err): + message_dialog = MessageDialog( + "Error", + "Pacman db synchronization failed", + "Failed to run command = pacman -Sy\nPacman db synchronization failed\nCheck the synchronization logs, and verify you can connect to the appropriate mirrors\n\n", + sync_err, + "error", + True, + ) + + message_dialog.show_all() + message_dialog.run() + message_dialog.hide() + + # ===================================================== + # WINDOW KEY EVENT CTRL + F + # ===================================================== + + # sets focus on the search entry + def on_keypress_event(self, widget, event): + shortcut = Gtk.accelerator_get_label(event.keyval, event.state) + + if shortcut in ("Ctrl+F", "Ctrl+Mod2+F"): + # set focus on text entry, select all text if any + self.searchentry.grab_focus() + + if shortcut in ("Ctrl+I", "Ctrl+Mod2+I"): + fn.show_package_info(self) + + # ===================================================== + # SEARCH ENTRY + # ===================================================== + + def on_search_activated(self, searchentry): + if searchentry.get_text_length() == 0 and self.search_activated: + GUI.setup_gui( + self, + Gtk, + Gdk, + GdkPixbuf, + base_dir, + os, + Pango, + None, + ) + self.search_activated = False + + if searchentry.get_text_length() == 0: + self.search_activated = False + + search_term = searchentry.get_text() + # if the string is completely whitespace ignore searching + if not search_term.isspace(): + try: + if len(search_term.rstrip().lstrip()) > 0: + # test if the string entered by the user is in the package name + # results is a dictionary, which holds a list of packages + # results[category]=pkg_list + + # searching is processed inside a thread + + th_search = fn.threading.Thread( + name="thread_search", + target=fn.search, + args=( + self, + search_term.rstrip().lstrip(), + ), + ) + fn.logger.info("Starting search") + + th_search.start() + + # get the search_results from the queue + results = self.search_queue.get() + + if results is not None: + fn.logger.info("Search complete") + + if len(results) > 0: + total = 0 + for val in results.values(): + total += len(val) + + fn.logger.info("Search found %s results" % total) + # make sure the gui search only displays the pkgs inside the results + + GUI.setup_gui_search( + self, + Gtk, + Gdk, + GdkPixbuf, + base_dir, + os, + Pango, + results, + search_term, + None, + ) + + self.search_activated = True + else: + fn.logger.info("Search found %s results" % 0) + self.searchentry.grab_focus() + + message_dialog = MessageDialog( + "Info", + "Search returned 0 results", + "Failed to find search term inside the package name or description.", + "Try to search again using another term", + "info", + False, + ) + + message_dialog.show_all() + message_dialog.run() + message_dialog.hide() + + elif self.search_activated == True: + GUI.setup_gui( + self, + Gtk, + Gdk, + GdkPixbuf, + base_dir, + os, + Pango, + None, + ) + self.search_activated = False + except Exception as err: + fn.logger.error("Exception in on_search_activated(): %s" % err) + + finally: + if self.search_activated == True: + self.search_queue.task_done() + + def on_search_cleared(self, searchentry, icon_pos, event): + if self.search_activated: + GUI.setup_gui( + self, + Gtk, + Gdk, + GdkPixbuf, + base_dir, + os, + Pango, + None, + ) + + self.searchentry.set_placeholder_text("Search...") + + self.search_activated = False + + # ===================================================== + # RESTART/QUIT BUTTON + # ===================================================== + + def on_close(self, widget, data): + # to preserve settings, save current options to conf file inside $HOME/.config/sofirem/sofirem.yaml + + settings = Settings(self.display_versions, self.display_package_progress) + settings.write_config_file() + + # make a final installed packages file inside /var/log/sofirem/ + # this allows a before/after comparison + # fn.on_close_create_packages_file() + + if os.path.exists(fn.sofirem_lockfile): + os.unlink(fn.sofirem_lockfile) + + if os.path.exists(fn.sofirem_pidfile): + os.unlink(fn.sofirem_pidfile) + + # see the comment in fn.terminate_pacman() + fn.terminate_pacman() + + Gtk.main_quit() + print( + "---------------------------------------------------------------------------" + ) + print("Thanks for using Sofirem") + print("Report issues to make it even better") + print( + "---------------------------------------------------------------------------" + ) + print("You can report issues on https://discord.gg/stBhS4taje") + print( + "---------------------------------------------------------------------------" + ) + + # ==================================================================== + # Button Functions + # ==================================================================== + # Given what this function does, it might be worth considering making it a + # thread so that the app doesn't block while installing/uninstalling is happening. + + def app_toggle(self, widget, active, package): + # switch widget is currently toggled off + + if widget.get_state() == False and widget.get_active() == True: + if len(package.name) > 0: + inst_str = [ + "pacman", + "-S", + package.name, + "--needed", + "--noconfirm", + ] + + if self.display_package_progress is True: + if fn.check_pacman_lockfile(): + widget.set_state(False) + widget.set_active(False) + proc = fn.get_pacman_process() + + message_dialog = MessageDialog( + "Warning", + "Sofirem cannot proceed pacman lockfile found", + "Pacman cannot lock the db, a lockfile is found inside %s" + % fn.pacman_lockfile, + "Pacman is running: %s" % proc, + "warning", + False, + ) + + message_dialog.show_all() + message_dialog.run() + message_dialog.hide() + return True + else: + package_metadata = fn.get_package_information(package.name) + + if ( + type(package_metadata) is str + and package_metadata.strip() + == "error: package '%s' was not found" % package.name + ): + self.package_found = False + fn.logger.warning( + "The package %s was not found in any configured Pacman repositories" + % package.name + ) + fn.logger.warning("Package install cannot continue") + + message_dialog = MessageDialog( + "Error", + "Pacman repository error: package '%s' was not found" + % package.name, + "Sofirem cannot process the request", + "Are the correct pacman mirrorlists configured ?", + "error", + False, + ) + message_dialog.show_all() + message_dialog.run() + message_dialog.hide() + + widget.set_state(False) + widget.set_active(False) + + return True + else: + widget.set_state(True) + widget.set_active(True) + + progress_dialog = ProgressDialog( + "install", + package, + " ".join(inst_str), + package_metadata, + ) + + progress_dialog.show_all() + + self.pkg_queue.put( + ( + package, + "install", + widget, + inst_str, + progress_dialog, + ), + ) + + th = fn.threading.Thread( + name="thread_pkginst", + target=fn.install, + args=(self,), + daemon=True, + ) + + th.start() + fn.logger.debug("Package-install thread started") + + else: + progress_dialog = None + widget.set_sensitive(False) + + widget.set_active(True) + widget.set_state(True) + + fn.logger.info("Package to install : %s" % package.name) + + # another pacman transaction is running, add items to the holding queue + if ( + fn.check_pacman_lockfile() is True + and self.display_package_progress is False + ): + self.pkg_holding_queue.put( + ( + package, + "install", + widget, + inst_str, + progress_dialog, + ), + ) + + if fn.is_thread_alive("thread_check_holding_queue") is False: + th = fn.threading.Thread( + target=fn.check_holding_queue, + name="thread_check_holding_queue", + daemon=True, + args=(self,), + ) + + th.start() + fn.logger.debug("Check-holding-queue thread started") + elif self.display_package_progress is False: + self.pkg_queue.put( + ( + package, + "install", + widget, + inst_str, + progress_dialog, + ), + ) + + th = fn.threading.Thread( + name="thread_pkginst", + target=fn.install, + args=(self,), + daemon=True, + ) + + th.start() + fn.logger.debug("Package-install thread started") + + # switch widget is currently toggled on + if widget.get_state() == True and widget.get_active() == False: + # Uninstall the package + + if len(package.name) > 0: + uninst_str = ["pacman", "-Rs", package.name, "--noconfirm"] + + fn.logger.info("Package to remove : %s" % package.name) + + if fn.check_pacman_lockfile(): + widget.set_state(True) + widget.set_active(True) + + fn.logger.info("Pacman lockfile found, uninstall aborted") + + GLib.idle_add( + self.show_lockfile_message_dialog, + priority=GLib.PRIORITY_DEFAULT, + ) + + return True + + if self.display_package_progress is True: + package_metadata = fn.get_package_information(package.name) + + progress_dialog = ProgressDialog( + "uninstall", + package, + " ".join(uninst_str), + package_metadata, + ) + + progress_dialog.show_all() + else: + progress_dialog = None + + widget.set_active(False) + widget.set_state(False) + + self.pkg_queue.put( + ( + package, + "uninstall", + widget, + uninst_str, + progress_dialog, + ), + ) + + th = fn.threading.Thread( + name="thread_pkgrem", + target=fn.uninstall, + args=(self,), + daemon=True, + ) + + th.start() + fn.logger.debug("Package-uninstall thread started") + + # fn.print_running_threads() + + # return True to prevent the default handler from running + return True + + # App_Frame_GUI.GUI(self, Gtk, vboxStack1, fn, category, package_file) + # widget.get_parent().get_parent().get_parent().get_parent().get_parent().get_parent().get_parent().queue_redraw() + # self.gui.hide() + # self.gui.queue_redraw() + # self.gui.show_all() + + def show_lockfile_message_dialog(self): + proc = fn.get_pacman_process() + message_dialog = MessageDialog( + "Warning", + "Sofirem cannot proceed pacman lockfile found", + "Pacman cannot lock the db, a lockfile is found inside %s" + % fn.pacman_lockfile, + "Process running = %s" % proc, + "warning", + False, + ) + + message_dialog.show_all() + message_dialog.run() + message_dialog.hide() + + message_dialog.destroy() + + def recache_clicked(self, widget): + # Check if cache is out of date. If so, run the re-cache, if not, don't. + # pb = ProgressBarWindow() + # pb.show_all() + # pb.set_text("Updating Cache") + # pb.reset_timer() + + fn.logger.info("Recache applications - start") + + fn.cache_btn() + + # ================================================================ + # SETTINGS + # ================================================================ + + def on_package_search_clicked(self, widget): + fn.logger.debug("Showing Package Search window") + self.toggle_popover() + + package_search_win = PackageSearchWindow() + package_search_win.show_all() + + def on_arcolinux_iso_packages_clicked(self, widget): + fn.logger.debug("Showing ArcoLinux ISO Packages window") + arcolinux_iso_packages_window = ISOPackagesWindow() + arcolinux_iso_packages_window.show() + + def on_about_app_clicked(self, widget): + fn.logger.debug("Showing About dialog") + self.toggle_popover() + + about = AboutDialog() + about.run() + about.hide() + about.destroy() + + def on_packages_export_clicked(self, widget): + self.toggle_popover() + + dialog_packagelist = PackageListDialog() + dialog_packagelist.show_all() + + def on_packages_import_clicked(self, widget): + self.toggle_popover() + try: + if not os.path.exists(fn.pacman_lockfile): + package_file = "%s/packages-x86_64.txt" % (fn.export_dir,) + package_import_logfile = "%spackages-install-status-%s-%s.log" % ( + fn.log_dir, + fn.datetime.today().date(), + fn.datetime.today().time().strftime("%H-%M-%S"), + ) + + if os.path.exists(package_file): + # check we have a valid file + lines = None + with open(package_file, encoding="utf-8", mode="r") as f: + lines = f.readlines() + + if lines is not None: + if ( + "# This file was auto-generated by the ArchLinux Tweak Tool on" + in lines[0] + or "# This file was auto-generated by Sofirem on" + in lines[0] + ): + fn.logger.info("Package list file is valid") + packages_list = [] + for line in lines: + if not line.startswith("#"): + packages_list.append(line.strip()) + + if len(packages_list) > 0: + dialog_package_import = PackagesImportDialog( + package_file, + packages_list, + package_import_logfile, + ) + dialog_package_import.show_all() + + else: + message_dialog = MessageDialog( + "Error", + "Package file is not valid %s" % package_file, + "Export a list of packages first using the Show Installed Packages button", + "", + "error", + False, + ) + + message_dialog.show_all() + message_dialog.run() + message_dialog.hide() + else: + message_dialog = MessageDialog( + "Warning", + "Cannot locate export package file %s" % package_file, + "Export a list of packages first using the Show Installed Packages button", + "", + "warning", + False, + ) + + message_dialog.show_all() + message_dialog.run() + message_dialog.hide() + else: + message_dialog = MessageDialog( + "Error", + "Pacman lock file found %s" % fn.pacman_lockfile, + "Cannot proceed, another pacman process is running", + "", + "error", + False, + ) + + message_dialog.show_all() + message_dialog.run() + message_dialog.hide() + except Exception as e: + fn.logger.error("Exception in on_packages_import_clicked(): %s" % e) + + # show/hide popover + def toggle_popover(self): + if self.popover.get_visible(): + self.popover.hide() + else: + self.popover.show_all() + + def on_settings_clicked(self, widget): + self.toggle_popover() + + # ArcoLinux keys, mirrors setup + + def arco_keyring_toggle(self, widget, data): + # toggle is currently off, add keyring + if widget.get_state() == False and widget.get_active() == True: + fn.logger.info("Installing ArcoLinux keyring") + install_keyring = fn.install_arco_keyring() + + if install_keyring == 0: + fn.logger.info("Installation of ArcoLinux keyring = OK") + rc = fn.add_arco_repos() + if rc == 0: + fn.logger.info("ArcoLinux repos added into %s" % fn.pacman_conf) + widget.set_active(True) + else: + message_dialog = MessageDialog( + "Error", + "Failed to update pacman conf", + "Errors occurred during update of the pacman config file", + rc, + "error", + True, + ) + + message_dialog.show_all() + message_dialog.run() + message_dialog.hide() + + widget.set_active(False) + widget.set_state(False) + + return True + + else: + message_dialog = MessageDialog( + "Error", + "Failed to install ArcoLinux keyring", + "Errors occurred during install of the ArcoLinux keyring", + "Command run = %s\n\n Error = %s" + % (install_keyring["cmd_str"], install_keyring["output"]), + "error", + True, + ) + + message_dialog.show_all() + message_dialog.run() + message_dialog.hide() + + widget.set_active(False) + widget.set_state(False) + + return True + # toggle is currently on + if widget.get_state() == True and widget.get_active() == False: + remove_keyring = fn.remove_arco_keyring() + + if remove_keyring == 0: + fn.logger.info("Removing ArcoLinux keyring OK") + + rc = fn.remove_arco_repos() + if rc == 0: + fn.logger.info("ArcoLinux repos removed from %s" % fn.pacman_conf) + widget.set_active(False) + else: + message_dialog = MessageDialog( + "Error", + "Failed to update pacman conf", + "Errors occurred during update of the pacman config file", + rc, + "error", + True, + ) + + message_dialog.show_all() + message_dialog.run() + message_dialog.hide() + + widget.set_active(True) + widget.set_state(True) + + return True + else: + fn.logger.error("Failed to remove ArcoLinux keyring") + + message_dialog = MessageDialog( + "Error", + "Failed to remove ArcoLinux keyring", + "Errors occurred during removal of the ArcoLinux keyring", + "Command run = %s\n\n Error = %s" + % (remove_keyring["cmd_str"], remove_keyring["output"]), + "error", + True, + ) + + message_dialog.show_all() + message_dialog.run() + message_dialog.hide() + + widget.set_active(False) + widget.set_state(False) + + return True + + def arco_mirrorlist_toggle(self, widget, data): + # self.toggle_popover() + + # toggle is currently off + + if widget.get_state() == False and widget.get_active() == True: + widget.set_active(True) + widget.set_state(True) + + # before installing the mirrorlist make sure the pacman.conf file does not have any references to /etc/pacman.d/arcolinux-mirrorlist + # otherwise the mirrorlist package will not install + rc_remove = fn.remove_arco_repos() + if rc_remove == 0: + install_mirrorlist = fn.install_arco_mirrorlist() + + if install_mirrorlist == 0: + fn.logger.info("Installation of ArcoLinux mirrorlist = OK") + + rc_add = fn.add_arco_repos() + if rc_add == 0: + fn.logger.info("ArcoLinux repos added into %s" % fn.pacman_conf) + self.pacman_db_sync() + + else: + message_dialog = MessageDialog( + "Error", + "Failed to update pacman conf", + "Errors occurred during update of the pacman config file", + rc_add, + "error", + True, + ) + + message_dialog.show_all() + message_dialog.run() + message_dialog.hide() + + widget.set_active(False) + widget.set_state(False) + + return True + + else: + fn.logger.error("Failed to install ArcoLinux mirrorlist") + + message_dialog = MessageDialog( + "Error", + "Failed to install ArcoLinux mirrorlist", + "Errors occurred during install of the ArcoLinux mirrorlist", + "Command run = %s\n\n Error = %s" + % (install_mirrorlist["cmd_str"], install_mirrorlist["output"]), + "error", + True, + ) + message_dialog.show_all() + message_dialog.run() + message_dialog.hide() + + widget.set_active(False) + widget.set_state(False) + + return True + else: + message_dialog = MessageDialog( + "Error", + "Failed to update pacman conf", + "Errors occurred during update of the pacman config file", + rc, + "error", + True, + ) + + message_dialog.show_all() + message_dialog.run() + message_dialog.hide() + + widget.set_active(False) + widget.set_state(False) + + return True + # toggle is currently on + if widget.get_state() == True and widget.get_active() == False: + widget.set_active(False) + widget.set_state(False) + + fn.logger.info("Removing ArcoLinux mirrorlist") + + remove_mirrorlist = fn.remove_arco_mirrorlist() + + if remove_mirrorlist == 0: + fn.logger.info("Removing ArcoLinux mirrorlist OK") + + rc = fn.remove_arco_repos() + if rc == 0: + fn.logger.info("ArcoLinux repos removed from %s" % fn.pacman_conf) + widget.set_active(False) + else: + message_dialog = MessageDialog( + "Error", + "Failed to update pacman conf", + "Errors occurred during update of the pacman config file", + rc, + "error", + True, + ) + + message_dialog.show_all() + message_dialog.run() + message_dialog.hide() + + widget.set_active(True) + widget.set_state(True) + + return True + else: + fn.logger.error("Failed to remove ArcoLinux mirrorlist") + + message_dialog = MessageDialog( + "Error", + "Failed to remove ArcoLinux mirrorlist", + "Errors occurred during removal of the ArcoLinux mirrorlist", + "Command run = %s\n\n Error = %s" + % (remove_mirrorlist["cmd_str"], remove_mirrorlist["output"]), + "error", + True, + ) + + message_dialog.show_all() + message_dialog.run() + message_dialog.hide() + + widget.set_active(True) + widget.set_state(True) + + return True + + return True + + def version_toggle(self, widget, data): + if widget.get_active() == True: + fn.logger.debug("Showing package versions") + + self.display_versions = True + GLib.idle_add( + self.refresh_main_gui, + priority=GLib.PRIORITY_DEFAULT, + ) + else: + fn.logger.debug("Hiding package versions") + self.display_versions = False + GLib.idle_add( + self.refresh_main_gui, + priority=GLib.PRIORITY_DEFAULT, + ) + + def refresh_main_gui(self): + self.remove(self.vbox) + GUI.setup_gui(self, Gtk, Gdk, GdkPixbuf, base_dir, os, Pango, None) + self.show_all() + + def on_pacman_log_clicked(self, widget): + try: + self.toggle_popover() + + thread_addlog = "thread_addPacmanLogQueue" + self.thread_add_pacmanlog_alive = fn.is_thread_alive(thread_addlog) + + if self.thread_add_pacmanlog_alive == False: + fn.logger.info("Starting thread to monitor Pacman Log file") + + th_add_pacmanlog_queue = fn.threading.Thread( + name=thread_addlog, + target=fn.add_pacmanlog_queue, + args=(self,), + daemon=True, + ) + th_add_pacmanlog_queue.start() + + if self.thread_add_pacmanlog_alive is True: + # need to recreate the textview, can't use existing reference as it throws a seg fault + + self.textview_pacmanlog = Gtk.TextView() + self.textview_pacmanlog.set_property("editable", False) + self.textview_pacmanlog.set_property("monospace", True) + self.textview_pacmanlog.set_border_width(10) + self.textview_pacmanlog.set_vexpand(True) + self.textview_pacmanlog.set_hexpand(True) + + # use the reference to the text buffer initialized before the logtimer thread started + self.textview_pacmanlog.set_buffer(self.textbuffer_pacmanlog) + + window_pacmanlog = PacmanLogWindow( + self.textview_pacmanlog, + self.modelbtn_pacmanlog, + ) + window_pacmanlog.show_all() + + self.start_logtimer = window_pacmanlog.start_logtimer + + else: + # keep a handle on the textbuffer, this is needed again later, if the pacman log file dialog is closed + # since the textbuffer will already hold textdata at that point + + # textview is used inside another thread to update as the pacmanlog file is read into memory + self.textbuffer_pacmanlog = Gtk.TextBuffer() + + self.textview_pacmanlog = Gtk.TextView() + self.textview_pacmanlog.set_property("editable", False) + self.textview_pacmanlog.set_property("monospace", True) + self.textview_pacmanlog.set_border_width(10) + self.textview_pacmanlog.set_vexpand(True) + self.textview_pacmanlog.set_hexpand(True) + + self.textview_pacmanlog.set_buffer(self.textbuffer_pacmanlog) + + window_pacmanlog = PacmanLogWindow( + self.textview_pacmanlog, + self.modelbtn_pacmanlog, + ) + window_pacmanlog.show_all() + + thread_logtimer = "thread_startLogTimer" + thread_logtimer_alive = False + + thread_logtimer_alive = fn.is_thread_alive(thread_logtimer) + + # a flag to indicate that the textview will need updating, used inside fn.start_log_timer + self.start_logtimer = True + + if thread_logtimer_alive == False: + th_logtimer = fn.threading.Thread( + name=thread_logtimer, + target=fn.start_log_timer, + args=(self, window_pacmanlog), + daemon=True, + ) + th_logtimer.start() + + self.thread_add_pacmanlog_alive = True + self.modelbtn_pacmanlog.set_sensitive(False) + + except Exception as e: + fn.logger.error("Exception in on_pacman_log_clicked() : %s" % e) + + def package_progress_toggle(self, widget, data): + if widget.get_active() is True: + self.display_package_progress = True + if widget.get_active() is False: + self.display_package_progress = False + + +# ==================================================================== +# MAIN +# ==================================================================== + + +def signal_handler(sig, frame): + fn.logger.info("Sofirem is closing.") + if os.path.exists("/tmp/sofirem.lock"): + os.unlink("/tmp/sofirem.lock") + + if os.path.exists("/tmp/sofirem.pid"): + os.unlink("/tmp/sofirem.pid") + Gtk.main_quit(0) + + +# These should be kept as it ensures that multiple installation instances can't be run concurrently. +if __name__ == "__main__": + try: + signal.signal(signal.SIGINT, signal_handler) + + if not os.path.isfile("/tmp/sofirem.lock"): + with open("/tmp/sofirem.pid", "w") as f: + f.write(str(os.getpid())) + + style_provider = Gtk.CssProvider() + style_provider.load_from_path(base_dir + "/sofirem.css") + + Gtk.StyleContext.add_provider_for_screen( + Gdk.Screen.get_default(), + style_provider, + Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION, + ) + w = Main() + w.show_all() + + fn.logger.info("App Started") + + Gtk.main() + else: + fn.logger.info("Sofirem lock file found") + + md = Gtk.MessageDialog( + parent=Main(), + flags=0, + message_type=Gtk.MessageType.INFO, + buttons=Gtk.ButtonsType.YES_NO, + text="Sofirem Lock File Found", + ) + md.format_secondary_markup( + "A Sofirem lock file has been found. This indicates there is already an instance of Sofirem running.\n\ + Click 'Yes' to remove the lock file and try running again" + ) # noqa + + result = md.run() + md.destroy() + + if result in (Gtk.ResponseType.OK, Gtk.ResponseType.YES): + pid = "" + if os.path.exists(fn.sofirem_pidfile): + with open("/tmp/sofirem.pid", "r") as f: + line = f.read() + pid = line.rstrip().lstrip() + + if fn.check_if_process_running(int(pid)): + # needs to be fixed - todo + + # md2 = Gtk.MessageDialog( + # parent=Main, + # flags=0, + # message_type=Gtk.MessageType.INFO, + # buttons=Gtk.ButtonsType.OK, + # title="Application Running!", + # text="You first need to close the existing application", + # ) + # md2.format_secondary_markup( + # "You first need to close the existing application" + # ) + # md2.run() + fn.logger.info( + "You first need to close the existing application" + ) + else: + os.unlink("/tmp/sofirem.lock") + sys.exit(1) + else: + # in the rare event that the lock file is present, but the pid isn't + os.unlink("/tmp/sofirem.lock") + sys.exit(1) + else: + sys.exit(1) + except Exception as e: + fn.logger.error("Exception in __main__: %s" % e) \ No newline at end of file