diff --git a/DONE b/DONE deleted file mode 100644 index c87cd0a..0000000 --- a/DONE +++ /dev/null @@ -1,11 +0,0 @@ -bug fix freedesktop linux path -fixed iquail_update permission bug -fixed PATH option linux -override tmp dir -fix bug for josso - -Override side img -Logging -Setup methods for differential update -Option to verify solution (conf ignore & iquail update) -Validation of the side image diff --git a/TODO b/TODO index 53c7a24..8e4e387 100644 --- a/TODO +++ b/TODO @@ -1,10 +1,5 @@ -dont mkdtemp - -KeyboardInterrupt err -tmp size? - - -key signature +Option to verify solution (conf ignore & iquail update) +Logging!!! Change solution[] path diff --git a/examples/allum1.py b/examples/allum1.py deleted file mode 100644 index b451225..0000000 --- a/examples/allum1.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/python3 - -import iquail - -iquail.run( - solution=iquail.SolutionPacked(path='Allum1'), - installer=iquail.Installer( - publisher="alies", - name='Allum1', - icon='icon.jpeg', - binary='allum1', - console=True, - launch_with_quail=True - ), - builder=iquail.builder.Builder( - iquail.builder.CmdNoconsole() - ), - controller=iquail.ControllerTkinter() -) diff --git a/examples/cmder_gui.py b/examples/cmder_gui.py index 183f4ac..54d1abe 100644 --- a/examples/cmder_gui.py +++ b/examples/cmder_gui.py @@ -1,13 +1,6 @@ #!/usr/bin/python3 -import os -import iquail -import logging - -logging.basicConfig( - level=logging.DEBUG, - filename=os.path.join(os.path.dirname(__file__), '..', 'iquail.log'), -) +import iquail if not iquail.helper.OS_WINDOWS: raise AssertionError("This test solution is windows only") @@ -28,9 +21,7 @@ def next_pressed(self): iquail.run( - solution=iquail.SolutionGitHub( - iquail.ConfVar("zip_url", default_value="cmder_mini.zip"), - "https://github.com/cmderdev/cmder"), + solution=iquail.SolutionGitHub(iquail.ConfVar("zip_url"), "https://github.com/cmderdev/cmder"), installer=iquail.Installer( publisher='cmderdev', name='Cmder', @@ -43,7 +34,6 @@ def next_pressed(self): iquail.builder.CmdIcon('icon.ico'), iquail.builder.CmdNoconsole() ), - controller=iquail.ControllerTkinter( - install_custom_frame=FrameSelectMiniOrFull), + controller=iquail.ControllerTkinter(install_custom_frame=FrameSelectMiniOrFull), conf_ignore=["config/*"] ) diff --git a/examples/cmder_simple_gui.py b/examples/cmder_simple_gui.py deleted file mode 100644 index 8a384de..0000000 --- a/examples/cmder_simple_gui.py +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/python3 - -import iquail - -iquail.run( - solution=iquail.SolutionGitHub("cmder_mini.zip", "https://github.com/cmderdev/cmder"), - installer=iquail.Installer( - publisher='cmderdev', - name='Cmder', - icon='Cmder.exe', - binary='Cmder.exe', - console=False, - launch_with_quail=True, - ), - builder=iquail.builder.Builder( - iquail.builder.CmdIcon('icon.ico'), - iquail.builder.CmdNoconsole() - ), - controller=iquail.ControllerTkinter() -) diff --git a/examples/indie_gui.py b/examples/indie_gui.py index 3d7f8b4..37412d6 100755 --- a/examples/indie_gui.py +++ b/examples/indie_gui.py @@ -16,20 +16,18 @@ def register(self): iquail.run( - solution=iquail.SolutionGitHub( - "indie.zip", "https://github.com/QuailTeam/cpp_indie_studio"), + solution=iquail.SolutionGitHub("indie.zip", "https://github.com/QuailTeam/cpp_indie_studio"), installer=MyInstaller( publisher='tek', name='Indie', icon='icon.png', binary='indie_studio', console=False, - launch_with_quail=False + launch_with_quail=True ), builder=iquail.builder.Builder( iquail.builder.CmdIcon('icon.png'), - iquail.builder.CmdNoconsole(), - side_img_override="./side_img.gif" + iquail.builder.CmdNoconsole() ), controller=iquail.ControllerTkinter(ask_for_update=True, eula="TEST") diff --git a/examples/old/cmder_local.py b/examples/old/cmder_local.py deleted file mode 100644 index 880828d..0000000 --- a/examples/old/cmder_local.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/python3 - -import iquail - -iquail.run( - solution=iquail.SolutionPacked("cmder_mini.zip"), - installer=iquail.Installer( - publisher='cmderdev', - name='Cmder', - icon='Cmder.exe', - binary='Cmder.exe', - console=False, - launch_with_quail=True, - ), - builder=iquail.builder.Builder( - iquail.builder.CmdIcon('icon.ico'), - iquail.builder.CmdNoconsole() - ), - controller=iquail.ControllerTkinter(), - conf_ignore=["config/*"] -) diff --git a/examples/openhardwaremonitor.py b/examples/openhardwaremonitor.py index bb26579..5f85980 100644 --- a/examples/openhardwaremonitor.py +++ b/examples/openhardwaremonitor.py @@ -14,8 +14,7 @@ ), builder=iquail.builder.Builder( iquail.builder.CmdIcon('icon.ico'), - iquail.builder.CmdNoconsole(), - side_img_override="side_img.gif", + iquail.builder.CmdNoconsole() ), controller=iquail.ControllerTkinter() ) diff --git a/examples/side_img.gif b/examples/side_img.gif deleted file mode 100644 index 11fdbf9..0000000 Binary files a/examples/side_img.gif and /dev/null differ diff --git a/examples/old/test_github.py b/examples/test_github.py old mode 100644 new mode 100755 similarity index 100% rename from examples/old/test_github.py rename to examples/test_github.py diff --git a/examples/old/test_github_gui.py b/examples/test_github_gui.py similarity index 100% rename from examples/old/test_github_gui.py rename to examples/test_github_gui.py diff --git a/examples/old/test_local.py b/examples/test_local.py old mode 100644 new mode 100755 similarity index 100% rename from examples/old/test_local.py rename to examples/test_local.py diff --git a/examples/old/test_packed.py b/examples/test_packed.py old mode 100644 new mode 100755 similarity index 100% rename from examples/old/test_packed.py rename to examples/test_packed.py diff --git a/examples/old/test_packed_gui.py b/examples/test_packed_gui.py old mode 100644 new mode 100755 similarity index 100% rename from examples/old/test_packed_gui.py rename to examples/test_packed_gui.py diff --git a/iquail/builder/builder.py b/iquail/builder/builder.py index d73eeaa..267f99d 100644 --- a/iquail/builder/builder.py +++ b/iquail/builder/builder.py @@ -1,29 +1,15 @@ import os -import logging -from ..constants import Constants from .. import helper -logger = logging.getLogger(__name__) - class Builder: """Build executable using PyInstaller Takes BuildCmd as argument """ - def __init__(self, *build_cmds, side_img_override=None): - self._side_img = helper.get_side_img_path() - if side_img_override is not None: - if os.path.basename(side_img_override) != Constants.SIDE_IMG_NAME: - raise AssertionError( - "side_img must of named: %s" % Constants.SIDE_IMG_NAME) - self._side_img = side_img_override + def __init__(self, *build_cmds): self._build_cmds = list(build_cmds) - @property - def side_img(self): - return self._side_img - def register(self, builder_action): """see builder_action for more information""" self._build_cmds.extend(builder_action.builder_cmds()) @@ -31,9 +17,8 @@ def register(self, builder_action): def default_build_params(self): params = [helper.get_script(), "--onefile", - '--add-data', self._side_img + os.path.pathsep + "iquail", - "--exclude-module", "PyInstaller", - "--exclude-module", "PIL"] + '--add-data', helper.get_side_img_path() + os.path.pathsep + "iquail", + "--exclude-module", "PyInstaller"] if helper.OS_WINDOWS: # upx slows down the launch time params.append("--noupx") diff --git a/iquail/builder/cmd_zip.py b/iquail/builder/cmd_zip.py index 04616ba..afe433e 100644 --- a/iquail/builder/cmd_zip.py +++ b/iquail/builder/cmd_zip.py @@ -1,16 +1,12 @@ import os import zipfile -import logging from .. import helper from .cmd_base import CmdBase -logger = logging.getLogger(__name__) - class CmdZip(CmdBase): """ Zip a folder and add it to the executable """ - def __init__(self, path, zip_name, zip_clean=True): super().__init__() if isinstance(path, list): @@ -28,8 +24,7 @@ def pre_build(self): file_path = os.path.join(root, file) zipf.write(file_path, arcname=os.path.relpath(file_path, self._path)) - logger.info("Adding file to zip: " + - os.path.relpath(file_path, self._path)) + print(os.path.relpath(file_path, self._path)) zipf.close() def post_build(self): diff --git a/iquail/constants.py b/iquail/constants.py index d5858e9..1fc3ff7 100644 --- a/iquail/constants.py +++ b/iquail/constants.py @@ -1,8 +1,7 @@ class Constants: ARGUMENT_PATH = "--iquail_path" - IQUAIL_TO_UPDATE = "iquail_update.exe" # TODO doc - SIDE_IMG_NAME = "side_img.gif" # TODO doc + IQUAIL_TO_UPDATE = "iquail.update.exe" IQUAIL_ROOT_NAME = ".iquail" ARGUMENT_UNINSTALL = "--iquail_uninstall" ARGUMENT_BUILD = "--iquail_build" @@ -10,10 +9,9 @@ class Constants: ARGUMENT_REPLACE = "--iquail_replace" ARGUMENT_INSTALL_POLKIT = "--iquail_install_polkit" ARGUMENT_RUN = "--iquail_run" - ARGUMENT_VALIDATE = "--iquail_validate" CHECKSUMS_FILE = ".integrity.json" INTEGRITY_IGNORE_FILE = ".integrity_ignore" VERSION_FILE = "solution_version.txt" - CONF_IGNORE = ".conf_ignore" # TODO doc + CONF_IGNORE = ".conf_ignore" #TODO doc CONFIG_FILE = "config.ini" PATH_SEP = "::" diff --git a/iquail/controller/controller_base.py b/iquail/controller/controller_base.py index b09cd54..0f28d74 100644 --- a/iquail/controller/controller_base.py +++ b/iquail/controller/controller_base.py @@ -1,13 +1,9 @@ from abc import ABC, abstractmethod import sys -import logging from ..errors import SolutionUnreachableError, SolutionNotRemovableError from ..helper.traceback_info import ExceptionInfo -logger = logging.getLogger(__name__) - - class ControllerBase(ABC): __manager = None @@ -29,19 +25,15 @@ def setup(self, manager): @property def manager(self): if self.__manager is None: - raise AssertionError( - "manager is None, You must call Controller.setup() first") + raise AssertionError("manager is None, You must call Controller.setup() first") return self.__manager def excepthook(self, exctype, value, tb): exc_info = ExceptionInfo(exctype, value, tb) - logger.error(exc_info.traceback_str) if isinstance(value, SolutionNotRemovableError): self.callback_solution_not_removable_error(exc_info) - elif isinstance(value, SolutionUnreachableError): + if isinstance(value, SolutionUnreachableError): self.callback_solution_unreachable_error(exc_info) - elif isinstance(value, KeyboardInterrupt): - sys.__excepthook__(exctype, value, tb) else: self._excepthook(exc_info) diff --git a/iquail/controller/controller_tkinter/controller.py b/iquail/controller/controller_tkinter/controller.py index b3e1ff3..1d9b5bb 100644 --- a/iquail/controller/controller_tkinter/controller.py +++ b/iquail/controller/controller_tkinter/controller.py @@ -166,8 +166,7 @@ def __init__(self, self.title_font = None self._ask_for_update = ask_for_update tk.Tk.report_callback_exception = self.excepthook - assert install_custom_frame is None or issubclass( - install_custom_frame, FrameBase) + assert install_custom_frame is None or issubclass(install_custom_frame, FrameBase) self.install_custom_frame = install_custom_frame @property @@ -190,8 +189,7 @@ def _start_tk(self, frame, title): self.root_frame.pack(side="top", fill="both", expand=True) self.root_frame.grid_rowconfigure(0, weight=1) self.root_frame.grid_columnconfigure(0, weight=1) - self.title_font = Font(family='Helvetica', size=15, - weight="bold", slant="italic") + self.title_font = Font(family='Helvetica', size=15, weight="bold", slant="italic") self.medium_font = Font(family='Helvetica', size=11) # Select frame self.switch_frame(frame) @@ -213,8 +211,7 @@ def switch_frame(self, frame_class, **kwargs): self._frame.tkraise() def _excepthook(self, exception_info): - reporter = ErrorReporter( - "Automatic bug report", exception_info.traceback_str) + reporter = ErrorReporter("Automatic bug report", exception_info.traceback_str) reporter.show() self.quit_tk() diff --git a/iquail/controller/controller_tkinter/frames.py b/iquail/controller/controller_tkinter/frames.py index de518f7..dbf490f 100644 --- a/iquail/controller/controller_tkinter/frames.py +++ b/iquail/controller/controller_tkinter/frames.py @@ -114,8 +114,7 @@ def choice2_selected(self): class FrameBaseInProgress(FrameBase): def __init__(self, parent, controller, label_str): super().__init__(parent, controller) - self._label = tk.Label(self, text=label_str, - font=controller.title_font) + self._label = tk.Label(self, text=label_str, font=controller.title_font) self._label.pack(side="top", fill="x", pady=10) self.progress_var = tk.IntVar() self._progress_bar = ttk.Progressbar(self, diff --git a/iquail/helper/configuration.py b/iquail/helper/configuration.py index b3df8f5..71bfeb7 100644 --- a/iquail/helper/configuration.py +++ b/iquail/helper/configuration.py @@ -1,19 +1,15 @@ -import logging import configparser from pathlib import Path -logger = logging.getLogger(__name__) - class ConfVar: - def __init__(self, key, cast=str, default_value=None): + def __init__(self, key, cast=str): """ Configuration variable It will be replaced by its configuration value when Configuration.apply is called :param key: key which will be found in the configuration :param cast: cast function (by default all configuration variables are strings) """ self.key = key - self.default_value = default_value self.cast = cast @@ -56,9 +52,7 @@ def apply(self, *instances): instance_vars = instance.__dict__ for var_name, var_value in instance_vars.items(): if isinstance(var_value, ConfVar): - value = var_value.cast(self.get(var_value.key, - default=var_value.default_value)) + value = var_value.cast(self.get(var_value.key)) if value is not None: - logger.info("Applying conf var: %s = %s" % - (var_name, str(value))) setattr(instance, var_name, value) + diff --git a/iquail/helper/file_ignore.py b/iquail/helper/file_ignore.py index c4603e0..501bcfb 100644 --- a/iquail/helper/file_ignore.py +++ b/iquail/helper/file_ignore.py @@ -1,10 +1,6 @@ from fnmatch import fnmatch import os import shutil -import logging - -logger = logging.getLogger(__name__) - def accept_path(path, ignore_list): """ Check if path should be ignored according to ignore_list @@ -20,45 +16,35 @@ def not_pattern(ignore_pattern): return ignore_pattern[1:] return '!' + ignore_pattern - # TODO option to keep old config if new config is provided by solution accept = True for ignore in ignore_list: if ignore != "": not_ignore = not_pattern(ignore) if fnmatch(path, ignore) ^ fnmatch(path, not_ignore): accept = fnmatch(path, not_ignore) - logger.debug("Accepted: %s : %s" % (path, not_ignore)) - return accept -def _format_lines(line): - line = line.split("#")[0] # remove comments - line = line.replace(" ", "") # remove all spaces - return line - - class FileIgnore: def __init__(self, file_path): try: with open(file_path, 'r') as f: - lines = f.read().splitlines() + self._ignore_list = f.read().splitlines() except FileNotFoundError: - lines = [] - self._ignore_list = list(map(_format_lines, lines)) + self._ignore_list = [] def accept(self, path): return accept_path(path, self._ignore_list) def copy_ignored(self, src, dest): """Move all ignored files""" - for root, _, files in os.walk(src): + for root, dirs, files in os.walk(src): for f in files: rel_root = os.path.relpath(root, src) file_path = os.path.join(root, f) dest_path = os.path.join(dest, rel_root) if not self.accept(os.path.join(rel_root, f)): - logger.debug("Copying %s to %s" % - (file_path, os.path.join(dest_path, f))) os.makedirs(dest_path, 0o777, True) shutil.copy(file_path, os.path.join(dest_path, f)) + + diff --git a/iquail/helper/misc.py b/iquail/helper/misc.py index bf7948a..137fb04 100644 --- a/iquail/helper/misc.py +++ b/iquail/helper/misc.py @@ -6,35 +6,13 @@ import ctypes import platform import tempfile -import logging from iquail.helper.linux_polkit_file import polkit_check from ..constants import Constants -logger = logging.getLogger(__name__) - OS_LINUX = platform.system() == 'Linux' OS_WINDOWS = platform.system() == 'Windows' -_OVERRIDE_TMP_DIR = None - - -def set_override_tmpdir(path): - global _OVERRIDE_TMP_DIR - if os.path.exists(path): - shutil.rmtree(path, ignore_errors=True) - os.makedirs(path, 0o777, exist_ok=True) - _OVERRIDE_TMP_DIR = path - - -def my_mkdtemp(): - global _OVERRIDE_TMP_DIR - if _OVERRIDE_TMP_DIR is not None and os.path.isdir(_OVERRIDE_TMP_DIR): - d = tempfile.mkdtemp(dir=_OVERRIDE_TMP_DIR) - else: - d = tempfile.mkdtemp() - return d - def cache_result(func): """ Decorator to save the return value of a function @@ -58,7 +36,7 @@ def wrapper(self, *args, **kwargs): def get_side_img_path(): - return get_module_path(Constants.SIDE_IMG_NAME) + return get_module_path("side_img.gif") def get_module_path(*args): @@ -66,10 +44,7 @@ def get_module_path(*args): def get_script(): - called = sys.argv[0] - if not os.path.isfile(called) and shutil.which(called): - called = shutil.which(called) - return os.path.realpath(called) + return os.path.realpath(sys.argv[0]) def get_script_name(): @@ -83,7 +58,6 @@ def get_script_path(): def running_from_installed_binary(): # TODO: unittest split_script_path = os.path.normpath(get_script_path()).split(os.path.sep) - logger.debug("split_script_path = " + str(split_script_path)) # split script path should look like [..,".iquail", "project_name"] if len(split_script_path) < 2: return False @@ -106,7 +80,7 @@ def _delete_atexit(path_to_delete): assert os.path.isdir(path_to_delete) def _delete_from_tmp(): - tmpdir = my_mkdtemp() + tmpdir = tempfile.mkdtemp() newscript = shutil.copy2(get_script(), tmpdir) args = (newscript, Constants.ARGUMENT_RM, path_to_delete) if running_from_script(): @@ -126,10 +100,9 @@ def exit_and_replace(dest, src, run=False): assert os.path.isfile(src) assert not os.path.isdir(dest) - tmpdir = my_mkdtemp() + tmpdir = tempfile.mkdtemp() newscript = shutil.copy2(get_script(), tmpdir) - args = [newscript, Constants.ARGUMENT_REPLACE, - dest + Constants.PATH_SEP + src] + args = [newscript, Constants.ARGUMENT_REPLACE, dest + Constants.PATH_SEP + src] if run: args.append(Constants.ARGUMENT_RUN) if running_from_script(): @@ -166,13 +139,13 @@ def rerun_as_admin(graphical, uid=None): sys.executable, ' '.join(sys.argv), None, 1) - # TODO MACOS + ##TODO MACOS def move_folder_content(src, dest, ignore_errors=False): """Move folder content to another folder""" for f in os.listdir(src): - logger.debug("moved %s >> %s" % (os.path.join(src, f), dest)) + # print("moved %s >> %s" % (os.path.join(src, f), dest)) try: shutil.move(os.path.join(src, f), dest) except: @@ -180,14 +153,12 @@ def move_folder_content(src, dest, ignore_errors=False): raise -def safe_move_folder_content(src, ignore=None, remove=False): - """Move or remove folder content +def safe_remove_folder_content(src, ignore=None): + """Remove folder content If an error happens while removing the content will be untouched - If remove=False then src will be moved to a tmp dir - tmp dir is returned """ - tmp_dir = safe_mkdtemp() + tmp_dir = tempfile.mkdtemp() try: move_folder_content(src, tmp_dir) if ignore is not None: @@ -199,47 +170,25 @@ def safe_move_folder_content(src, ignore=None, remove=False): raise finally: # TODO chmod -R +w ? - if remove: - shutil.rmtree(tmp_dir) - return None - return tmp_dir + shutil.rmtree(tmp_dir) -def safe_mkdtemp(): +def safe_mkdtemp(debug=False): """Same as mkdtemp but removes the directory when quail exit """ - tmp_dir = my_mkdtemp() - logger.info("Created: " + tmp_dir) + tmp_dir = tempfile.mkdtemp() + if debug: + print("Created: " + tmp_dir, file=sys.stderr) def delete_tmp_dir(): if not os.path.isdir(tmp_dir): return try: shutil.rmtree(tmp_dir) - logger.info("Removed: " + tmp_dir) + if debug: + print("Removed: " + tmp_dir, file=sys.stderr) except Exception as e: - logger.error("Can't remove: " + tmp_dir + - " : " + str(e)) + print("Can't remove: " + tmp_dir + " : " + str(e), file=sys.stderr) atexit.register(delete_tmp_dir) return tmp_dir - - -def filter_iquail_args(args): - composed_arg_list = [ - # list of args composed of a variable - Constants.ARGUMENT_REPLACE, - Constants.ARGUMENT_VALIDATE, - Constants.ARGUMENT_RM, - ] - ignore_next = False - res = [] - for arg in args: - if ignore_next: - ignore_next = False - continue - if "--iquail" not in arg: - res.append(arg) - if arg in composed_arg_list: - ignore_next = True - return res diff --git a/iquail/installer/installer_base.py b/iquail/installer/installer_base.py index 974df92..da1beb7 100644 --- a/iquail/installer/installer_base.py +++ b/iquail/installer/installer_base.py @@ -25,8 +25,7 @@ def __init__(self, binary_options='', install_path=None, requires_root=False, - launch_with_quail=True, - is_large_solution=False): + launch_with_quail=True): self._install_systemwide = install_systemwide self._launch_with_quail = launch_with_quail self._binary_name = binary @@ -36,29 +35,17 @@ def __init__(self, self._icon = icon self._publisher = publisher self._console = console - self._install_path = self.build_install_path( - ) if install_path is None else install_path + self._install_path = self.build_install_path() if install_path is None else install_path self._solution_path = os.path.join(self._install_path, 'solution') self._launcher_name = "iquail_launcher" if self.requires_root: self._launcher_name = "setup_" + self._launcher_name - if is_large_solution: - # override tmp dir if the solution is too large - misc.set_override_tmpdir( - os.path.join(self.build_root_path(), "tmp-" + self.uid)) + def get_solution_icon(self): """Get solution's icon""" return self.get_solution_path(self._icon) - @property - def binary_name(self): - return self._binary_name - - @property - def icon(self): - return self._icon - @property def install_systemwide(self): return self._install_systemwide diff --git a/iquail/installer/installer_linux.py b/iquail/installer/installer_linux.py index 97a0124..b531e6e 100644 --- a/iquail/installer/installer_linux.py +++ b/iquail/installer/installer_linux.py @@ -17,7 +17,6 @@ def __init__(self, linux_desktop_conf=None, linux_exec_flags='', 'Icon': self.get_solution_icon(), 'Terminal': 'true' if self.console else 'false', 'Type': 'Application', - 'Path': self.get_solution_path(), 'Exec': self.launch_command + ' ' + linux_exec_flags} if linux_desktop_conf: @@ -76,7 +75,8 @@ def _register(self): Icon=self.get_solution_icon(), Terminal='true' if self.console else 'false') if self._add_to_path: - self.add_to_path(self.iquail_binary, self._binary_name) + # TODO launch with self.launcher_binary? + self.add_to_path(self.binary, self._binary_name) def _unregister(self): self.delete_shortcut(self._launch_shortcut) diff --git a/iquail/manager.py b/iquail/manager.py index 11f9213..e66bf28 100644 --- a/iquail/manager.py +++ b/iquail/manager.py @@ -1,16 +1,11 @@ import os import stat import sys -import logging +from .helper import misc from . import helper -from .helper import misc from .constants import Constants from .solution.solutioner import Solutioner -from .validate import Validate - - -logger = logging.getLogger(__name__) class Manager: @@ -20,7 +15,6 @@ def __init__(self, installer, solution, builder, graphical, *, conf_ignore=None) self._solution = solution self._builder = builder self._solutioner = Solutioner(self._solution, - self, self._installer.get_solution_path(), conf_ignore=conf_ignore) self._config = helper.Configuration( @@ -83,9 +77,7 @@ def build(self): def get_solution_version(self): """Get version from solution""" - version = self._solution.get_version_string() - logger.info("Manager got solution version: " + str(version)) - return version + return self._solution.get_version_string() def get_installed_version(self): """Get installed version""" @@ -113,7 +105,6 @@ def install_part_register(self): self._installer.register() self._chmod_binary() self.config.save() - logger.info("Saved config") def install(self): """Installation process was split in multiple parts @@ -128,7 +119,6 @@ def install(self): def update(self): """Update process""" # TODO: kill solution here - logger.info("Updating...") self._solutioner.update() self._set_solution_installed_version() @@ -142,39 +132,21 @@ def is_installed(self): """Check if solution is installed""" return self._solutioner.installed() # and self._installer.registered() - def validate_solution(self, path): - v = Validate(os.path.realpath( - path), self._installer, self._builder) - return v.run() - - def restart_quail(self): - os.chdir(self._installer.get_solution_path()) - binary = self._installer.launcher_binary - binary_args = [binary] + misc.filter_iquail_args(sys.argv[1:]) - logger.info("Restarting quail: %s with args: %s" % - (binary, str(binary_args))) - os.execl(binary, *binary_args) - def run(self): """Run solution""" # TODO: self.config.save() config could be used for "don't ask me again to update" feature binary = self._installer.binary if self.solutioner.get_iquail_update() is not None: - # TODO: verify binary (run validations) - misc.exit_and_replace( - misc.get_script(), self.solutioner.get_iquail_update(), run=True) + # TODO: verify binary + misc.exit_and_replace(misc.get_script(), self.solutioner.get_iquail_update(), run=True) self._chmod_binary() - args = misc.filter_iquail_args(sys.argv[1:]) + args = list(filter(lambda x: "--iquail" not in x, sys.argv[1:])) binary_args = [os.path.basename(binary)] + args os.chdir(self._installer.get_solution_path()) - logger.info("Running: %s with args: %s" % (binary, str(binary_args))) os.execl(binary, *binary_args) def check_permissions(self, uid): if self._installer.install_systemwide and os.geteuid() != 0: - # TODO fix os.geteuid doesn't exist on windows if self._graphical is False: - logger.error( - 'Root access is required for further action, relaunching as root') - logger.info("Re-running as admin with UID %s" % str(uid)) + print('Root access is required for further action, relaunching as root') misc.rerun_as_admin(self._graphical, uid) diff --git a/iquail/run.py b/iquail/run.py index 7681417..408694f 100644 --- a/iquail/run.py +++ b/iquail/run.py @@ -2,8 +2,6 @@ import argparse import shutil import os -import logging - from contextlib import suppress from .constants import Constants from . import helper @@ -12,8 +10,6 @@ from .controller import ControllerConsole from .helper import misc -logger = logging.getLogger(__name__) - def parse_args(): parser = argparse.ArgumentParser(add_help=helper.running_from_script()) @@ -43,9 +39,6 @@ def parse_args(): action="store_true", help="Tells iQuail to install a polkit authorization file in /usr/bin/polkit-1/actions and " "then rerun itself with pkexec (Linux only)") - parser.add_argument(Constants.ARGUMENT_VALIDATE, - type=str, - help="Validate a solution") return parser.parse_known_args() @@ -63,22 +56,12 @@ def run(solution, installer, builder=None, controller=None, conf_ignore=None): controller = ControllerConsole() manager = Manager(installer, solution, builder, controller.is_graphical(), conf_ignore=conf_ignore) - if args.iquail_validate: - success, _ = manager.validate_solution(args.iquail_validate) - if not success: - print("VALIDATION FAILED", file=sys.stderr) - exit(1) - print("VALIDATION PASSED", file=sys.stderr) - exit(0) controller.setup(manager) if args.iquail_rm: shutil.rmtree(args.iquail_rm) elif args.iquail_replace: dest, src = args.iquail_replace.split(Constants.PATH_SEP) os.replace(src, dest) - if args.iquail_run: - # launch new quail version - manager.restart_quail() elif args.iquail_build: manager.build() elif args.iquail_uninstall: @@ -88,7 +71,7 @@ def run(solution, installer, builder=None, controller=None, conf_ignore=None): controller.start_run_or_update() else: if manager.is_installed(): - logger.info(misc.get_script_path()) + print(misc.get_script_path()) # program is installed but we are not launched from the installed folder # TODO: ask repair/uninstall controller.start_uninstall() @@ -96,3 +79,4 @@ def run(solution, installer, builder=None, controller=None, conf_ignore=None): controller.start_install() if args.iquail_run: controller.start_run_or_update() + diff --git a/iquail/solution/solution_base.py b/iquail/solution/solution_base.py index 82fef86..e20e1a0 100644 --- a/iquail/solution/solution_base.py +++ b/iquail/solution/solution_base.py @@ -9,7 +9,6 @@ class SolutionProgress: """Class returned in callback to notify progress (see SolutionBase._update_progress """ - def __init__(self, percent, status, log): self.percent = percent self.status = status @@ -27,8 +26,6 @@ class SolutionBase(ABC, builder.BuilderAction): def __init__(self): self._progress_hook = None - self.__solutioner = None - self.__manager = None def __enter__(self): self.open() @@ -49,30 +46,6 @@ def _update_progress(self, percent, status="loading", log=""): if self._progress_hook: self._progress_hook(SolutionProgress(percent, status, log)) - """Setup manager and installer - """ - - def setup(self, solutioner, manager): - self.__solutioner = solutioner - self.__manager = manager - - """Get installed version of the solution, if there is one""" - - def get_installed_version(self): - return self.__manager.get_installed_version() - - """Get current installed file with relative path - If the file exists then return the absolute path - else return None - """ - - def retrieve_current_file(self, *relpath): - res = self.__solutioner.backup_dest(*relpath) - if res is not None and os.path.isfile(res): - return res - # TODO copy res in tmp before returning - return None - def get_version_string(self): """ return version string of a solution This function is meant to be overridden diff --git a/iquail/solution/solutioner.py b/iquail/solution/solutioner.py index ebd28a5..ecd0fdb 100644 --- a/iquail/solution/solutioner.py +++ b/iquail/solution/solutioner.py @@ -1,82 +1,47 @@ import shutil import os -import sys -import logging - from ..errors import SolutionNotRemovableError from ..helper import misc from ..helper import FileIgnore from ..constants import Constants -logger = logging.getLogger(__name__) - - class Solutioner: - def __init__(self, solution, manager, dest, *, conf_ignore=None): + def __init__(self, solution, dest, *, conf_ignore=None): if conf_ignore is not None: assert isinstance(conf_ignore, list) self.conf_ignore = conf_ignore self._dest = dest self._solution = solution - # _backup_dir: during the update the solution content is moved to a tmp dir - # this variable keeps track of this folder - self._backup_dir = None - solution.setup(self, manager) - - def _remove_moved_solution(self): - """Remove self._backup_dir""" - try: - if self._backup_dir is not None: - misc.safe_move_folder_content(self._backup_dir, remove=True) - self._backup_dir = None - except: - logger.log("Can't remove _backup_dir") - pass - - def _move_solution(self, ignore=None): - """Move solution to self._backup_dir""" - try: - result_dir = misc.safe_move_folder_content( - self.dest(), ignore=ignore, remove=False) - self._backup_dir = result_dir - except Exception as e: - raise SolutionNotRemovableError( - "Can't remove %s" % self.dest()) from e def _remove_solution(self, ignore=None): + """Remove solution + If the root solution isn't removable it will be ignored with this function + def onerror(func, path, exc_info): + if self.dest() != path: + raise OSError("Cannot delete " + path) + shutil.rmtree(self.dest(), onerror=onerror) + """ try: - misc.safe_move_folder_content( - self.dest(), ignore=ignore, remove=True) + misc.safe_remove_folder_content(self.dest(), ignore=ignore) except Exception as e: - raise SolutionNotRemovableError( - "Can't remove %s" % self.dest()) from e - - def backup_dest(self, *args): - if self._backup_dir is None: - return None - return os.path.realpath(os.path.join(self._backup_dir, *args)) + raise SolutionNotRemovableError("Can't remove %s" % self.dest()) from e def dest(self, *args): return os.path.realpath(os.path.join(self._dest, *args)) def _retrieve_file(self, relpath): - logger.debug("Solutioner: retrieving file: " + relpath) - if os.path.exists(self.dest(relpath)): - logger.warning("Solutioner: ignored file: " + relpath) - return tmpfile = self._solution.retrieve_file(relpath) shutil.move(tmpfile, self.dest(os.path.dirname(relpath))) - def install(self, ignore=None): + def install(self): """ Download solution to dest folder (open & setup the solution before using download) """ self._solution.open() try: if os.path.exists(self.dest()): - logger.info("Solutioner destionation already exists...") - self._remove_solution(ignore) + self._remove_solution() os.makedirs(self.dest(), 0o777, True) for root, dirs, files in self._solution.walk(): for _dir in dirs: @@ -95,7 +60,6 @@ def install(self, ignore=None): def get_iquail_update(self): path = self.dest(Constants.IQUAIL_TO_UPDATE) if os.path.isfile(path): - os.chmod(path, 0o777) return path return None @@ -104,13 +68,12 @@ def installed(self): def update(self): # TODO: uninstall will be a waste of time on future solution types - ignore = None if self.installed(): + ignore = None if os.path.isfile(self.dest(Constants.CONF_IGNORE)): ignore = FileIgnore(self.dest(Constants.CONF_IGNORE)) - self._move_solution(ignore=ignore) - self.install(ignore) - self._remove_moved_solution() + self._remove_solution(ignore=ignore) + self.install() def uninstall(self): if self.installed(): diff --git a/iquail/validate/__init__.py b/iquail/validate/__init__.py deleted file mode 100644 index 0e26f03..0000000 --- a/iquail/validate/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .validate import Validate diff --git a/iquail/validate/validate.py b/iquail/validate/validate.py deleted file mode 100644 index c4cea0f..0000000 --- a/iquail/validate/validate.py +++ /dev/null @@ -1,115 +0,0 @@ - -import os -from abc import ABC, abstractmethod -import logging -from ..constants import Constants -from ..helper.traceback_info import ExceptionInfo - -logger = logging.getLogger(__name__) - -TEST_PREFIX = "test_" - - -def _validate_side_img(path): - # TODO unittest side img validation - try: - from PIL import Image - with Image.open(path) as img: - width, height = img.size - if height != 250: - logger.warn("Side image not valid: height should be 250px") - return False - - if width > 250: - logger.warn("Side image not valid: width should be <= 250px") - return False - else: - logger.info("Side image is valid") - return True - except ImportError: - logger.warn("Cannot check side image: please install Pillow module") - return False - - -class TestResult: - def __init__(self, name, success, is_critical, exception_info=None): - assert isinstance(name, str) - assert isinstance(success, bool) - assert isinstance(is_critical, bool) - if exception_info is not None: - assert isinstance(exception_info, ExceptionInfo) - # TODO add help string as param - self.name = name - self.success = success - self.is_critical = is_critical - self.exception_info = exception_info - - def log(self): - success_str = "SUCCESS" if self.success else "FAIL" - res = "test result: %s: %s" % (self.name, success_str) - # TODO log traceback - if self.success: - logger.info(res) - else: - if self.is_critical: - logger.error(res) - else: - logger.warn(res) - - def get_dict(self): - return { - "name": self.name, - "success": self.success, - "is_critical": self.is_critical, - "exception_info": self.exception_info - } - - -class Validate: - def __init__(self, path, installer, builder): - assert os.path.exists(path) - self._path = path - self._installer = installer - self._builder = builder - - def path(self, *args): - return os.path.join(self._path, *args) - - def isfile(self, *args): - return os.path.isfile(self.path(*args)) - - def test_binary(self): - success = self.isfile(self._installer.binary_name) - return TestResult("binary", success, is_critical=True) - - def test_icon(self): - success = self.isfile(self._installer.icon) - return TestResult("icon", success, is_critical=True) - - def test_iquail_update(self): - success = self.isfile(Constants.IQUAIL_TO_UPDATE) - return TestResult("iquail_update", success, is_critical=False) - - def test_conf_ignore(self): - success = self.isfile(Constants.CONF_IGNORE) - # TODO add tests - return TestResult("conf_ignore", success, is_critical=False) - - def test_side_img(self): - success = _validate_side_img(self._builder.side_img) - return TestResult("side_img", success, is_critical=False) - - def run(self): - success = True - res = [] - tests = list(filter(lambda x: x.startswith(TEST_PREFIX), dir(self))) - logger.info("Running tests...") - for test in tests: - name = test[len(TEST_PREFIX):] - f = getattr(self, test) - test_result = f() - test_result.log() - if not test_result.success and test_result.is_critical: - success = False - res.append(test_result.get_dict()) - return success, res diff --git a/new_features.txt b/new_features.txt new file mode 100644 index 0000000..cc4b4be --- /dev/null +++ b/new_features.txt @@ -0,0 +1,7 @@ +- not running from installed_binary = ask uninstall (instead of launching) +- delete tmp dir at exit +- fix bug packed --iquail_rm +- fixed name for iquail launcher +- fixed traceback >> traceback_str +- UID for publisher +- require pypiwin32 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 4541cd4..0da3389 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,2 @@ pyinstaller pypiwin32; sys_platform == 'win32' -Pillow diff --git a/setup.py b/setup.py index 4668c98..71cf2ee 100755 --- a/setup.py +++ b/setup.py @@ -12,15 +12,14 @@ setup( name='iquail', packages=packages, - version='2.0', + version='1.9', description='iQuail cross-platform installer', author='Quail team', long_description=long_description, long_description_content_type="text/markdown", author_email='quail_2020@labeip.epitech.eu', url='https://github.com/QuailTeam/iQuail', - keywords=['tool', 'deploy', 'installer', - 'wizard', 'install', 'update', 'quail'], + keywords=['tool', 'deploy', 'installer', 'wizard', 'install', 'update', 'quail'], classifiers=['Intended Audience :: Developers', 'Development Status :: 4 - Beta', 'Programming Language :: Python :: 3 :: Only', diff --git a/examples/old/xono_linux.py b/test.py similarity index 100% rename from examples/old/xono_linux.py rename to test.py diff --git a/tests/test_helper_ignore.py b/tests/test_helper_ignore.py index b585abb..40ea051 100644 --- a/tests/test_helper_ignore.py +++ b/tests/test_helper_ignore.py @@ -26,3 +26,7 @@ def test_copy_ignored(self): self.assertFalse(os.path.exists(self.tmp("allum1"))) self.assertTrue(os.path.exists(self.tmp("test.conf"))) self.assertTrue(os.path.exists(self.tmp("conf", "test.txt"))) + + + + diff --git a/tests/test_misc.py b/tests/test_misc.py index 9036f25..4832b36 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -17,30 +17,30 @@ def setUp(self): self.verifier = iquail.helper.IntegrityVerifier(self.test_dir) self.verifier.dump() - def test_safe_move_folder_content(self): - misc.safe_move_folder_content(self.test_dir) + def test_safe_remove_folder_content(self): + misc.safe_remove_folder_content(self.test_dir) self.assertListEqual(os.listdir(self.test_dir), []) - def test_safe_move_folder_content_error(self): + def test_safe_remove_folder_content_error(self): subfolder = os.path.join(self.test_dir, "subfolder") os.chmod(subfolder, 0o555) try: with open(os.path.join(subfolder, "testfile.txt"), "a") as f: - self.assertRaises( - Exception, misc.safe_move_folder_content, self.test_dir) + self.assertRaises(Exception, misc.safe_remove_folder_content, self.test_dir) self.assertListEqual(self.verifier.verify_all(), []) finally: os.chmod(subfolder, 0o777) + def test_running_from_installed_binary(self): misc.get_script_path = lambda: "/test/test/test/test" assert misc.running_from_installed_binary() is False misc.get_script_path = lambda: "/test/" assert misc.running_from_installed_binary() is False - misc.get_script_path = lambda: os.path.join( - "/test/test/", Constants.IQUAIL_ROOT_NAME, "test") + misc.get_script_path = lambda: os.path.join("/test/test/", Constants.IQUAIL_ROOT_NAME, "test") assert misc.running_from_installed_binary() is True + def test_rerun_as_admin(self): global mock if misc.OS_LINUX: @@ -56,3 +56,4 @@ def test_rerun_as_admin(self): sys.argv = [os.path.abspath('./bin')] misc.rerun_as_admin(True, './bin') mock.assert_called_with('pkexec', ['pkexec', './dir/bin', '--iquail_install_polkit']) + diff --git a/tests/testdata/Ignore/ignore_basic b/tests/testdata/Ignore/ignore_basic index 33ae616..6f4a417 100644 --- a/tests/testdata/Ignore/ignore_basic +++ b/tests/testdata/Ignore/ignore_basic @@ -1,3 +1,3 @@ conf_file -*.conf # this is a test comment +*.conf !not_conf.conf diff --git a/tests/testdata/Ignore/ignore_path b/tests/testdata/Ignore/ignore_path index 5b4aba7..4716e83 100644 --- a/tests/testdata/Ignore/ignore_path +++ b/tests/testdata/Ignore/ignore_path @@ -1,2 +1,2 @@ -./conf/* # ignore all files in conf +./conf/* !./conf/not_conf \ No newline at end of file