From bf50dfdc6425c1bc5bb2650d4c554191b9d207b9 Mon Sep 17 00:00:00 2001 From: Bilal Elmoussaoui Date: Tue, 23 May 2017 17:20:17 +0200 Subject: [PATCH] fixes #306 & #358 --- src/app.py | 15 ++- src/decorators.py | 22 ++-- src/modules/applications/application.py | 78 ++++++------- src/modules/applications/binary.py | 40 +++---- src/modules/applications/electron.py | 123 ++++++++++++-------- src/modules/applications/extract.py | 15 ++- src/modules/applications/pak.py | 35 ++++-- src/modules/applications/zip.py | 8 +- src/modules/backup.py | 142 ++++++++++++++++++++++++ src/modules/icon.py | 12 +- src/modules/log.py | 14 ++- src/modules/parser.py | 27 +++-- src/modules/path.py | 21 ++-- src/modules/svg/svg.py | 7 +- src/utils.py | 103 +---------------- 15 files changed, 384 insertions(+), 278 deletions(-) create mode 100644 src/modules/backup.py diff --git a/src/app.py b/src/app.py index e6aa6bed..6fb19aef 100644 --- a/src/app.py +++ b/src/app.py @@ -24,7 +24,7 @@ from json import load from os import path from gi.repository import Gio - +from time import time from .const import DESKTOP_ENV, CONFIG_FILE, DB_FOLDER from .enum import Action, ConversionTools from .utils import progress, get_scaling_factor, replace_to_6hex @@ -104,26 +104,30 @@ def execute(action): """ apps = App.get_supported_apps() done = [] + total_time = 0 if len(apps) != 0: cnt = 0 counter_total = sum(app.parser.total_icons for app in apps) for i, app in enumerate(apps): app_name = app.name + start_time = time() if action == Action.APPLY: app.install() elif action == Action.REVERT: app.reinstall() elif action == Action.CLEAR_CACHE: app.clear_cache() + delta = time() - start_time + total_time += delta if app.is_done: cnt += app.parser.total_icons if app_name not in done: - progress(cnt, counter_total, app_name) + progress(cnt, counter_total, delta, app_name) done.append(app_name) else: - counter_total -= app.data.supported_icons_cnt - if i == len(apps) - 1: - progress(cnt, counter_total) + counter_total -= app.parser.total_icons + print("Failed to fix {0}".format(app_name)) + print("Took {0}s to finish the tasks".format(round(total_time, 2))) else: if action == Action.APPLY: exit("No apps to fix! Please report on GitHub if this is not the case") @@ -148,6 +152,7 @@ def config(): @staticmethod def svg(): if App._svgtopng is None: + conversion_tool = None if App.args().conversion_tool: conversion_tool = App.args().conversion_tool elif App.config().get("conversion-tool"): diff --git a/src/decorators.py b/src/decorators.py index 367a3408..6dbb989b 100644 --- a/src/decorators.py +++ b/src/decorators.py @@ -20,7 +20,7 @@ You should have received a copy of the GNU General Public License along with Hardcode-Tray. If not, see . """ -from .utils import symlink_file, show_select_backup, create_backup_dir +from .utils import symlink_file def symlinks_installer(func): @@ -42,22 +42,22 @@ def wrapper(application, icon, icon_path): def install_wrapper(func): def wrapper(app): - app.backup_dir = create_backup_dir(app.name) + app.backup.create_backup_dir() app.install_symlinks() func(app) return wrapper def revert_wrapper(func): - def wrapper(application): - if application.BACKUP_IGNORE: - application.remove_symlinks() - func(application) + def wrapper(app): + if app.BACKUP_IGNORE: + app.remove_symlinks() + func(app) else: - application.selected_backup = show_select_backup(application.name) - if application.selected_backup: - application.remove_symlinks() - func(application) + app.backup.select() + if app.backup.selected_backup: + app.remove_symlinks() + func(app) else: - application.is_done = False + app.is_done = False return wrapper diff --git a/src/modules/applications/application.py b/src/modules/applications/application.py index deb815c9..e8f13be9 100644 --- a/src/modules/applications/application.py +++ b/src/modules/applications/application.py @@ -20,12 +20,14 @@ You should have received a copy of the GNU General Public License along with Hardcode-Tray. If not, see . """ +from concurrent.futures import ThreadPoolExecutor as Executor from os import path from shutil import rmtree from src.const import BACKUP_FOLDER -from src.utils import backup, revert, symlink_file, mchown +from src.enum import Action +from src.utils import symlink_file, mchown from src.decorators import symlinks_installer, revert_wrapper, install_wrapper - +from src.modules.backup import Backup class Application: """Application class.""" @@ -43,6 +45,7 @@ def __init__(self, parser): self.parser = parser self._selected_backup = None self._back_dir = None + self.backup = Backup(self) @property def name(self): @@ -59,32 +62,16 @@ def app_path(self): """Return the application installation paths.""" return self.parser.app_path - @property - def backup_dir(self): - """Return the backup directory of the current application.""" - return self._back_dir - - @backup_dir.setter - def backup_dir(self, backup_dir): - self._back_dir = backup_dir - - @property - def selected_backup(self): - """Return the selected backup directory during the revert process.""" - return self._selected_backup - - @selected_backup.setter - def selected_backup(self, selected_backup): - if path.exists(selected_backup): - self._selected_backup = selected_backup - else: - raise FileNotFoundError - @property def icons_path(self): """Return the application installation paths.""" return self.parser.icons_path + @property + def backup_ignore(self): + """Return either the backup files should be created or not.""" + return self.parser.backup_ignore + @property def symlinks(self): """Return application symlinks.""" @@ -102,7 +89,7 @@ def install_symlinks(self): for directory in self.app_path: root = symlinks[syml]["root"] dest = directory.append(symlinks[syml]["dest"]) - backup(self.backup_dir, dest) + self.backup.create(dest) symlink_file(root, dest) def remove_symlinks(self): @@ -111,8 +98,7 @@ def remove_symlinks(self): symlinks = self.symlinks for syml in symlinks: for directory in self.app_path: - revert(self.name, self.selected_backup, - directory.append(symlinks[syml]["dest"])) + self.backup.remove(directory.append(symlinks[syml]["dest"])) def clear_cache(self): """Clear Backup cache.""" @@ -122,35 +108,25 @@ def clear_cache(self): return True return False - def get_output_icons(self): - """Return a list of output icons.""" - icons = [] - for icon in self.icons: - for icon_path in self.icons_path: - output_icon = icon_path.append(icon.original) - icons.append({ - "output_icon": output_icon, - "data": icon, - "path": icon_path - }) - return icons + def execute(self, action): + """Execute actions: Apply/Revert.""" + for icon_path in self.icons_path: + with Executor(max_workers=4) as exe: + for icon in self.icons: + if action == Action.APPLY: + exe.submit(self.install_icon, icon, icon_path) + elif action == Action.REVERT: + exe.submit(self.revert_icon, icon, icon_path) @install_wrapper def install(self): """Install the application icons.""" - for icon in self.get_output_icons(): - if not self.parser.backup_ignore: - backup(self.backup_dir, icon["output_icon"]) - self.install_icon(icon["data"], icon["path"]) + self.execute(Action.APPLY) @revert_wrapper def reinstall(self): """Reinstall the application icons and remove symlinks.""" - for icon in self.get_output_icons(): - if not self.parser.backup_ignore: - revert(self.name, - self.selected_backup, - icon["output_icon"]) + self.execute(Action.REVERT) @symlinks_installer def install_icon(self, icon, icon_path): @@ -160,9 +136,17 @@ def install_icon(self, icon, icon_path): ext_theme = icon.theme_ext icon_size = icon.icon_size output_icon = icon_path.append(icon.original) + if not self.backup_ignore: + self.backup.create(output_icon) if ext_theme == ext_orig: symlink_file(theme_icon, output_icon) elif ext_theme == "svg" and ext_orig == "png": from src.app import App App.svg().to_png(theme_icon, output_icon, icon_size) mchown(output_icon) + + def revert_icon(self, icon, icon_path): + """Revert to the original icon.""" + output_icon = icon_path.append(icon.original) + if not self.backup_ignore: + self.backup.remove(output_icon) diff --git a/src/modules/applications/binary.py b/src/modules/applications/binary.py index e5f89aae..135a2f7d 100644 --- a/src/modules/applications/binary.py +++ b/src/modules/applications/binary.py @@ -20,42 +20,38 @@ You should have received a copy of the GNU General Public License along with Hardcode-Tray. If not, see . """ -from src.decorators import revert_wrapper, install_wrapper -from src.utils import backup, revert +from src.enum import Action from .application import Application - class BinaryApplication(Application): """Pak Application class, based on data_pak file.""" def __init__(self, parser): """Init method.""" Application.__init__(self, parser) - - def backup_binary(self, icon_path): - """Backup binary file before modification.""" - backup(self.backup_dir, icon_path.append(self.binary)) - - def revert_binary(self, icon_path): - """Restore the backed up binary file.""" - revert(self.name, self.selected_backup, - icon_path + self.binary) + self.is_corrupted = False @property def binary(self): """Return the binary file if exists.""" return self.parser.binary - @revert_wrapper - def reinstall(self): - """Reinstall the old icons.""" - for icon_path in self.icons_path: - self.revert_binary(icon_path) + def get_backup_file(self, icon_name): + """Return the binary content of a backup file.""" + backup_file = self.backup.get_backup_file(icon_name) + if backup_file: + with open(backup_file, 'rb') as binary_obj: + pngbytes = binary_obj.read() + return pngbytes + return None - @install_wrapper - def install(self): - """Install the application icons.""" + def execute(self, action): for icon_path in self.icons_path: - self.backup_binary(icon_path) for icon in self.icons: - self.install_icon(icon, icon_path) + if self.is_corrupted: + break + if action == Action.APPLY: + self.install_icon(icon, icon_path) + elif action == Action.REVERT: + self.revert_icon(icon, icon_path) + self.is_done = not self.is_corrupted diff --git a/src/modules/applications/electron.py b/src/modules/applications/electron.py index 2e9ac74f..3b54da9f 100644 --- a/src/modules/applications/electron.py +++ b/src/modules/applications/electron.py @@ -20,11 +20,12 @@ You should have received a copy of the GNU General Public License along with Hardcode-Tray. If not, see . """ -from struct import unpack, pack +from struct import unpack, pack, error as StructError from json import loads, dumps from src.utils import (get_from_dict, change_dict_vals, set_in_dict, get_pngbytes) from .binary import BinaryApplication +from ..log import Logger class ElectronApplication(BinaryApplication): @@ -36,57 +37,85 @@ def __init__(self, parser): def install_icon(self, icon, icon_path): """Install the icon.""" - filename = icon_path.append(self.binary) - icon_to_repl = "files/{0!s}".format( - "/files/".join(icon.original.split("/"))) - asarfile = open(filename, 'rb') - asarfile.seek(4) - - # header size is stored in byte 12:16 - len1 = unpack('I', asarfile.read(4))[0] - len2 = unpack('I', asarfile.read(4))[0] - len3 = unpack('I', asarfile.read(4))[0] - header_size = len3 - zeros_padding = (len2 - 4 - len3) - - header = asarfile.read(header_size).decode('utf-8') - files = loads(header) - originaloffset = asarfile.tell() + zeros_padding - asarfile.close() - - keys = icon_to_repl.split('/') - - fileinfo = get_from_dict(files, keys) - if isinstance(fileinfo, dict) and "offset" in fileinfo.keys(): - offset0 = int(fileinfo['offset']) - offset = offset0 + originaloffset - size = int(fileinfo['size']) - - with open(filename, 'rb') as asarfile: - bytearr = asarfile.read() + pngbytes = get_pngbytes(icon) + if pngbytes: + self.set_icon(icon.original, icon_path, pngbytes, True) + else: + Logger.error("PNG file was not found.") + + @staticmethod + def get_real_path(icon_name, delimiter="/"): + return "files/{0}".format( + "/files/".join(icon_name.split(delimiter))) + + def revert_icon(self, icon, icon_path): + """Revert to the original icon.""" + backup_file = "|".join(ElectronApplication.get_real_path(icon.original).split("/")) + pngbytes = self.get_backup_file(backup_file) + if pngbytes: + self.set_icon(icon.original, icon_path, pngbytes) + else: + Logger.error("Backup file of {0} was not found".format(self.name)) + + def set_icon(self, icon_to_repl, binary_path, pngbytes, backup=False): + """Set the icon into the electron binary file.""" + icon_to_repl = ElectronApplication.get_real_path(icon_to_repl) + binary_file = binary_path.append(self.binary) + + asarfile = open(binary_file, 'rb') + try: + asarfile.seek(4) + + # header size is stored in byte 12:16 + len1 = unpack('I', asarfile.read(4))[0] + len2 = unpack('I', asarfile.read(4))[0] + len3 = unpack('I', asarfile.read(4))[0] + header_size = len3 + zeros_padding = (len2 - 4 - len3) + + header = asarfile.read(header_size).decode('utf-8') + files = loads(header) + originaloffset = asarfile.tell() + zeros_padding asarfile.close() - pngbytes = get_pngbytes(icon) + keys = icon_to_repl.split("/") - if pngbytes: - set_in_dict(files, keys + ['size'], len(pngbytes)) + fileinfo = get_from_dict(files, keys) + if isinstance(fileinfo, dict) and "offset" in fileinfo.keys(): + offset0 = int(fileinfo['offset']) + offset = offset0 + originaloffset + size = int(fileinfo['size']) - newbytearr = pngbytes.join( - [bytearr[:offset], bytearr[offset + size:]]) + with open(binary_file, 'rb') as asarfile: + bytearr = asarfile.read() - sizediff = len(pngbytes) - size + if pngbytes: + set_in_dict(files, keys + ['size'], len(pngbytes)) - newfiles = change_dict_vals(files, sizediff, offset0) - newheader = dumps(newfiles).encode('utf-8') - newheaderlen = len(newheader) + if backup: + backup_file = "|".join(keys) + self.backup.file(backup_file, bytearr[offset:offset+size]) - bytearr2 = b''.join([bytearr[:4], - pack('I', newheaderlen + (len1 - len3)), - pack('I', newheaderlen + (len2 - len3)), - pack('I', newheaderlen), newheader, - b'\x00' * zeros_padding, - newbytearr[originaloffset:]]) + newbytearr = pngbytes.join( + [bytearr[:offset], bytearr[offset + size:]]) - asarfile = open(filename, 'wb') - asarfile.write(bytearr2) - asarfile.close() + sizediff = len(pngbytes) - size + + newfiles = change_dict_vals(files, sizediff, offset0) + newheader = dumps(newfiles).encode('utf-8') + newheaderlen = len(newheader) + + bytearr2 = b''.join([bytearr[:4], + pack('I', newheaderlen + (len1 - len3)), + pack('I', newheaderlen + (len2 - len3)), + pack('I', newheaderlen), newheader, + b'\x00' * zeros_padding, + newbytearr[originaloffset:]]) + + asarfile = open(binary_file, 'wb') + asarfile.write(bytearr2) + asarfile.close() + except StructError: + Logger.error( + "The asar file of {0} seems to be corrupted".format(self.name)) + self.is_corrupted = True diff --git a/src/modules/applications/extract.py b/src/modules/applications/extract.py index 7b276c8a..4463c8d0 100644 --- a/src/modules/applications/extract.py +++ b/src/modules/applications/extract.py @@ -20,7 +20,7 @@ You should have received a copy of the GNU General Public License along with Hardcode-Tray. If not, see . """ -from src.decorators import install_wrapper +from src.enum import Action from .binary import BinaryApplication @@ -30,15 +30,18 @@ def __init__(self, parser): BinaryApplication.__init__(self, parser) self.tmp_data = None - @install_wrapper - def install(self): - """Install the application icons.""" + def execute(self, action): for icon_path in self.icons_path: - self.backup_binary(icon_path) + if self.is_corrupted: + break self.extract(icon_path) for icon in self.icons: - self.install_icon(icon, self.tmp_data) + if action == Action.APPLY: + self.install_icon(icon, self.tmp_data) + elif action == Action.REVERT: + self.revert_icon(icon, self.tmp_data) self.pack(icon_path) + self.is_done = not self.is_corrupted def extract(self, icon_path): pass diff --git a/src/modules/applications/pak.py b/src/modules/applications/pak.py index c5ec6d0d..a562d799 100644 --- a/src/modules/applications/pak.py +++ b/src/modules/applications/pak.py @@ -24,6 +24,7 @@ from imp import load_source from src.utils import get_pngbytes from .binary import BinaryApplication +from ..log import Logger absolute_path = path.split(path.abspath(__file__))[0] data_pack_path = path.join(absolute_path, "pak", "data_pack.py") @@ -36,13 +37,33 @@ class PakApplication(BinaryApplication): def __init__(self, parser): """Init method.""" BinaryApplication.__init__(self, parser) + self.binary_file = None + self.pak = None + + def set_binary_file(self, binary_file): + """Set pak file and create a new instance of it.""" + if binary_file != self.binary_file: + self.binary_file = binary_file + self.pak = data_pack.read_data_pack(binary_file) + + def set_icon(self, icon, icon_path, pngbytes, backup=False): + """Update the icon bytes with the new one.""" + self.set_binary_file(icon_path.append(self.binary)) + icon_name = int(icon.original) + if pngbytes: + if backup: + self.backup.file(str(icon_name), self.pak.resources[icon_name]) + self.pak.resources[icon_name] = pngbytes + data_pack.write_data_pack(self.pak.resources, self.binary_file, 0) + else: + Logger.error("Couldn't find a PNG file.") def install_icon(self, icon, icon_path): - """Install the icon.""" - filename = icon_path.append(self.binary) - icon_to_repl = int(icon.original) + """Install the new icon.""" pngbytes = get_pngbytes(icon) - if pngbytes: - _data_pack = data_pack.read_data_pack(filename) - _data_pack.resources[icon_to_repl] = pngbytes - data_pack.write_data_pack(_data_pack.resources, filename, 0) + self.set_icon(icon, icon_path, pngbytes, True) + + def revert_icon(self, icon, icon_path): + """Revert to the original icon.""" + pngbytes = self.get_backup_file(icon.original) + self.set_icon(icon, icon_path, pngbytes) diff --git a/src/modules/applications/zip.py b/src/modules/applications/zip.py index cffa0da0..4e5b8422 100644 --- a/src/modules/applications/zip.py +++ b/src/modules/applications/zip.py @@ -21,8 +21,8 @@ along with Hardcode-Tray. If not, see . """ from os import path, remove, makedirs -from zipfile import ZipFile from shutil import make_archive, rmtree +from zipfile import ZipFile from src.utils import execute from .extract import ExtractApplication @@ -47,12 +47,12 @@ def extract(self, icon_path): rmtree(self.tmp_path) makedirs(self.tmp_path, exist_ok=True) execute(["chmod", "0777", self.tmp_path]) - with ZipFile(icon_path + self.binary) as zf: - zf.extractall(self.tmp_path) + with ZipFile(icon_path + self.binary) as zip_object: + zip_object.extractall(self.tmp_path) def pack(self, icon_path): """Recreate the zip file from the tmp directory.""" - zip_file = icon_path + self.binary + zip_file = icon_path.append(self.binary) if path.isfile(zip_file): remove(zip_file) make_archive(zip_file.replace(".zip", ""), 'zip', self.tmp_path) diff --git a/src/modules/backup.py b/src/modules/backup.py new file mode 100644 index 00000000..7b20e10b --- /dev/null +++ b/src/modules/backup.py @@ -0,0 +1,142 @@ +from os import path, listdir, remove +from shutil import rmtree, move +from time import strftime +from src.const import BACKUP_EXTENSION, BACKUP_FILE_FORMAT, BACKUP_FOLDER +from src.utils import create_dir, copy_file, mchown +from .log import Logger + + +class Backup: + + def __init__(self, application): + self._app = application + self._backup_dir = None + self._selected_backup = None + + @property + def app(self): + """Return the instance of Application object.""" + return self._app + + @property + def backup_dir(self): + """Return the backup directory of the current application.""" + return self._backup_dir + + @backup_dir.setter + def backup_dir(self, backup_dir): + self._backup_dir = backup_dir + + @property + def selected_backup(self): + """Return the selected backup directory during the revert process.""" + return self._selected_backup + + @selected_backup.setter + def selected_backup(self, selected_backup): + self._selected_backup = selected_backup + + def create_backup_dir(self): + """Create a backup directory for an application (application_name).""" + current_time_folder = strftime(BACKUP_FILE_FORMAT) + back_dir = path.join(BACKUP_FOLDER, self.app.name, + current_time_folder, "") + exists = True + new_back_dir = back_dir + i = 1 + while exists: + if path.exists(new_back_dir): + new_back_dir = back_dir + "_" + str(i) + if not path.exists(new_back_dir): + create_dir(new_back_dir) + exists = False + i += 1 + self._backup_dir = new_back_dir + + def create(self, file_name): + """Backup functions.""" + from src.app import App + if not App.config().get("backup-ignore", False): + back_file = path.join(self.backup_dir, path.basename( + file_name) + BACKUP_EXTENSION) + if path.exists(file_name): + Logger.debug("Backup current file {0} to{1}".format( + file_name, back_file)) + copy_file(file_name, back_file) + mchown(back_file) + try: + cache_files = listdir(self.backup_dir) + if len(cache_files) == 0: + rmtree(self.backup_dir) + except FileNotFoundError: + pass + + def file(self, filename, binary): + """Backup a binary content as a file.""" + tempfile = "/" + path.join("tmp", path.basename(filename)) + with open(tempfile, 'wb') as fobj: + fobj.write(binary) + self.create(tempfile) + remove(tempfile) + + def get_backup_file(self, filename): + """Return the backup file path.""" + backup_folder = path.join(BACKUP_FOLDER, self.app.name, self.selected_backup) + backup_file = path.join(backup_folder, filename + BACKUP_EXTENSION) + if path.exists(backup_file): + return backup_file + return None + + def get_backup_folders(self): + """Get a list of backup folders of a sepecific application.""" + return listdir(path.join(BACKUP_FOLDER, self.app.name)) + + def select(self): + """Show a select option for the backup of each application.""" + backup_folders = self.get_backup_folders() + max_i = len(backup_folders) + if max_i != 0: + backup_folders.sort() + i = 1 + for backup_folder in backup_folders: + print("{0}) {1}/{2} ".format(str(i), + self.app.name, backup_folder)) + i += 1 + print("(Q)uit to not revert to any version") + have_chosen = False + stopped = False + while not have_chosen and not stopped: + try: + selected_backup = input( + "Select a restore date : ").strip().lower() + if selected_backup in ["q", "quit", "exit"]: + stopped = True + selected_backup = int(selected_backup) + if 1 <= selected_backup <= max_i: + have_chosen = True + self._selected_backup = backup_folders[selected_backup - 1] + except ValueError: + pass + except KeyboardInterrupt: + exit() + if stopped: + Logger.debug("The user stopped the reversion for {0}".format( + self.app.name)) + else: + Logger.debug("No backup folder found " + "for the application {0}".format( + self.app.name)) + + def remove(self, file_name): + """ + Backup functions, enables reverting. + + Args: + icon(str) : the original icon name + revert(bool) : True: revert, False: only backup + """ + back_dir = path.join(BACKUP_FOLDER, self.app.name, self.selected_backup, "") + if path.exists(back_dir): + back_file = path.join(back_dir, path.basename(file_name) + BACKUP_EXTENSION) + if path.isfile(back_file): + move(back_file, file_name) diff --git a/src/modules/icon.py b/src/modules/icon.py index adbd9080..6cd25c90 100644 --- a/src/modules/icon.py +++ b/src/modules/icon.py @@ -21,7 +21,8 @@ along with Hardcode-Tray. If not, see . """ from os import path -from src.utils import get_iterated_icons, get_extension +from src.utils import get_extension, get_iterated_icons + class Icon: """ @@ -34,12 +35,13 @@ class Icon: def __init__(self, icon_dic): """Init function.""" self.icon_data = icon_dic - self._found = False + self._exists = False self._read() @property - def found(self): - return self._found + def exists(self): + """Return wether the icon exists or not.""" + return self._exists @staticmethod def get_theme(icon_name): @@ -73,7 +75,7 @@ def _read(self): self.theme_ext = get_extension(self.theme) self.orig_ext = ext_orig self.icon_size = self.get_icon_size(App.icon_size()) - self._found = True + self._exists = True if (not isinstance(self.icon_data, str) and self.icon_data.get("symlinks")): diff --git a/src/modules/log.py b/src/modules/log.py index 38fd6986..18362eb5 100644 --- a/src/modules/log.py +++ b/src/modules/log.py @@ -20,18 +20,21 @@ You should have received a copy of the GNU General Public License along with Hardcode-Tray. If not, see . """ -from os import path, makedirs import logging +from os import makedirs, path from time import strftime + class Logger: """ Logger class, logs error and other messages on /tmp/Hardcode-Tray. """ _log = None + @staticmethod def get_default(): + """Return default instance of Logger.""" if Logger._log is None: from src.const import LOG_FILE_FORMAT logger = logging.getLogger('hardcode-tray') @@ -40,9 +43,8 @@ def get_default(): if not path.exists(path.dirname(tmp_file)): makedirs(path.dirname(tmp_file)) if not path.exists(tmp_file): - f = open(tmp_file, 'w') - f.write('') - f.close() + with open(tmp_file, 'w') as tmp_obj: + tmp_obj.write('') handler = logging.FileHandler(tmp_file) formater = logging.Formatter( '[%(levelname)s] - %(asctime)s - %(message)s') @@ -54,16 +56,20 @@ def get_default(): @staticmethod def warning(msg): + """Log warning message.""" Logger.get_default().warning(msg) @staticmethod def debug(msg): + """Log debug message.""" Logger.get_default().debug(msg) @staticmethod def info(msg): + """Log info message.""" Logger.get_default().info(msg) @staticmethod def error(msg): + """Log error message.""" Logger.get_default().error(msg) diff --git a/src/modules/parser.py b/src/modules/parser.py index c14dfcdc..e32d231b 100644 --- a/src/modules/parser.py +++ b/src/modules/parser.py @@ -21,8 +21,8 @@ along with Hardcode-Tray. If not, see . """ import json -from src.utils import get_iterated_icons from src.enum import ApplicationType +from src.utils import get_iterated_icons from .icon import Icon from .path import Path from .applications import * @@ -56,9 +56,11 @@ def get_type(self): return "normal" def is_installed(self): + """Return wether the application is installed or not.""" return not self.dont_install def get_application(self): + """Application factory, return an instance of Application.""" application = ApplicationType.choices()[self.get_type()] return globals()[application](self) @@ -66,26 +68,27 @@ def _read(self): """ Read the json file and parse it. """ - with open(self._db_file, 'r') as f: - data = json.load(f) - do_later = ["app_path", "icons_path", "icons"] + do_later = ["app_path", "icons_path", "icons"] + with open(self._db_file, 'r') as db_obj: + data = json.load(db_obj) for key, value in data.items(): if key not in do_later: setattr(self, key, value) - f.close() + self._parse_paths(data["app_path"], "app_path") self._parse_paths(data["icons_path"], "icons_path") self._parse_icons(data["icons"]) - self.dont_install = not (len(self.icons) > 0 - and len(self.app_path) > 0 - and len(self.icons_path) > 0 - ) + self.dont_install = not ( + len(self.icons) > 0 + and len(self.app_path) > 0 + and len(self.icons_path) > 0 + ) - def _parse_paths(self, paths, key="app_path"): + def _parse_paths(self, paths, key): for path in paths: path = Path(path, self.exec_path_script) - if path.found: # If path exists + if path.exists: # If path exists getattr(self, key).append(path) def _parse_icons(self, icons): @@ -96,6 +99,6 @@ def _parse_icons(self, icons): icon = Icon(icon) else: icon = Icon(icons[icon]) - if icon.found: # If icon found on current Gtk Icon theme + if icon.exists: # If icon found on current Gtk Icon theme self.icons.append(icon) self.total_icons += 1 diff --git a/src/modules/path.py b/src/modules/path.py index 6ec416c3..87ca9360 100644 --- a/src/modules/path.py +++ b/src/modules/path.py @@ -21,10 +21,11 @@ along with Hardcode-Tray. If not, see . """ from os import path -from src.const import ARCH, USERHOME, PATH_SCRIPTS_FOLDER +from src.const import ARCH, PATH_SCRIPTS_FOLDER, USERHOME from src.utils import create_dir, execute from .log import Logger + class Path: """ Path class: @@ -32,22 +33,25 @@ class Path: """ DB_VARIABLES = { - "{userhome}" : USERHOME, - "{size}" : 22, - "{arch}" : ARCH + "{userhome}": USERHOME, + "{size}": 22, + "{arch}": ARCH } + def __init__(self, absolute_path, exec_path_script=None, force_create=False): self._path = absolute_path self._path_script = exec_path_script self._force_create = force_create - self._found = True + self._exists = True self._validate() def append(self, filename): + """Append a file name to the path.""" return path.join(self.path, filename) @property def path(self): + """Return the path.""" return self._path @path.setter @@ -55,8 +59,9 @@ def path(self, value): self._path = value @property - def found(self): - return self._found + def exists(self): + """Return wether the path exists or not.""" + return self._exists def _validate(self): """ @@ -77,4 +82,4 @@ def _validate(self): if self._force_create: create_dir(self.path) if not path.exists(self.path): - self._found = False + self._exists = False diff --git a/src/modules/svg/svg.py b/src/modules/svg/svg.py index c33feb2b..ad9d53f3 100644 --- a/src/modules/svg/svg.py +++ b/src/modules/svg/svg.py @@ -27,11 +27,11 @@ class SVG: """SVG Interface used by other class's.""" - _svg = None def __init__(self, colors): """Init function.""" self.colors = colors + self.cmd = None def to_png(self, input_file, output_file, width=None, height=None): """Convert svg to png and save it in a destination.""" @@ -55,10 +55,13 @@ def to_bin(self, input_file, width=None, height=None): self.to_png(input_file, outfile, width, height) with open(outfile, 'rb') as temppng: binary = temppng.read() - temppng.close() remove(outfile) return binary + def convert_to_png(self, input_file, output_file, width, height): + """Convert from svg to png. Override the method by childs.""" + pass + def is_installed(self): """Check if the tool is installed.""" return is_installed(self.cmd) diff --git a/src/utils.py b/src/utils.py index 6c98206f..bbe2866f 100644 --- a/src/utils.py +++ b/src/utils.py @@ -20,13 +20,13 @@ You should have received a copy of the GNU General Public License along with Hardcode-Tray. If not, see . """ - from functools import reduce from os import chown, makedirs, path, remove, symlink, listdir from re import findall, match, sub from shutil import copyfile, move, rmtree from subprocess import PIPE, Popen, call from sys import stdout +from tempfile import NamedTemporaryFile from time import strftime from gi.repository import Gio from .modules.log import Logger @@ -34,10 +34,11 @@ BACKUP_EXTENSION, BACKUP_FOLDER, BACKUP_FILE_FORMAT) -def progress(count, count_max, app_name=""): +def progress(count, count_max, time, app_name=""): """Used to draw a progress bar.""" bar_len = 40 space = 20 + time = round(time, 2) filled_len = int(round(bar_len * count / float(count_max))) percents = round(100.0 * count / float(count_max), 1) @@ -45,8 +46,8 @@ def progress(count, count_max, app_name=""): stdout.write("\r{0!s}{1!s}".format( app_name, " " * (abs(len(app_name) - space)))) - stdout.write('[{0!s}] {1:d}/{2:d} {3!s}{4!s}\r'.format(progress_bar, - count, count_max, percents, '%')) + stdout.write('[{0}] {1}/{2} {3}% {4}s\r'.format(progress_bar, + count, count_max, percents, time)) print("") stdout.flush() @@ -184,100 +185,6 @@ def is_installed(binary): return bool(ink_flag == 0) -def create_backup_dir(application_name): - """Create a backup directory for an application (application_name).""" - current_time_folder = strftime(BACKUP_FILE_FORMAT) - back_dir = path.join(BACKUP_FOLDER, application_name, - current_time_folder, "") - exists = True - new_back_dir = back_dir - i = 1 - while exists: - if path.exists(new_back_dir): - new_back_dir = back_dir + "_" + str(i) - if not path.exists(new_back_dir): - create_dir(new_back_dir) - exists = False - i += 1 - return new_back_dir - - -def backup(back_dir, file_name): - """Backup functions.""" - from src.app import App - if App.config().get("backup-ignore", False): - back_file = path.join(back_dir, path.basename( - file_name) + BACKUP_EXTENSION) - if path.exists(file_name): - Logger.debug("Backup current file %s to %s", file_name, back_file) - copy_file(file_name, back_file) - mchown(back_file) - try: - cache_files = listdir(back_dir) - if len(cache_files) == 0: - rmtree(back_dir) - except FileNotFoundError: - pass - - -def get_backup_folders(application_name): - """Get a list of backup folders of a sepecific application.""" - return listdir(path.join(BACKUP_FOLDER, application_name)) - - -def show_select_backup(application_name): - """Show a select option for the backup of each application.""" - backup_folders = get_backup_folders(application_name) - max_i = len(backup_folders) - if max_i != 0: - backup_folders.sort() - i = 1 - for backup_folder in backup_folders: - print("{0}) {1}/{2} ".format(str(i), application_name, - backup_folder)) - i += 1 - print("(Q)uit to not revert to any version") - have_chosen = False - stopped = False - while not have_chosen and not stopped: - try: - selected_backup = input( - "Select a restore date : ").strip().lower() - if selected_backup in ["q", "quit", "exit"]: - stopped = True - selected_backup = int(selected_backup) - if 1 <= selected_backup <= max_i: - have_chosen = True - backup_folder = backup_folders[selected_backup - 1] - return backup_folder - except ValueError: - pass - except KeyboardInterrupt: - exit() - if stopped: - Logger.debug("The user stopped the reversion for %s", - application_name) - else: - Logger.debug("No backup folder found " - "for the application %s" % application_name) - return None - - -def revert(application_name, selected_backup, file_name): - """ - Backup functions, enables reverting. - - Args: - icon(str) : the original icon name - revert(bool) : True: revert, False: only backup - """ - back_dir = path.join(BACKUP_FOLDER, application_name, selected_backup, "") - if not path.exists(back_dir): - back_file = path.join(back_dir, path.basename( - file_name) + BACKUP_EXTENSION) - if path.isfile(back_file): - move(back_file, file_name) - def get_iterated_icons(icons): """Used to avoid multiple icons names, like for telegram."""