From 51680797995cc45c420d7eda9cdc7f31e1c20ad3 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Fri, 11 Feb 2022 00:29:50 -0600 Subject: [PATCH] overhaul on import logic, and private fields --- src/Example.py | 16 +- src/shellfm/__init__.py | 1 - src/shellfm/windows/Window.py | 66 ----- src/shellfm/windows/WindowController.py | 170 ------------ src/shellfm/windows/__init__.py | 2 - src/shellfm/windows/controller.py | 185 +++++++++++++ src/shellfm/windows/view/View.py | 211 --------------- src/shellfm/windows/view/__init__.py | 5 - src/shellfm/windows/view/icons/__init__.py | 4 - .../windows/view/icons/mixins/__init__.py | 4 - src/shellfm/windows/view/utils/__init__.py | 2 - src/shellfm/windows/views/__init__.py | 0 src/shellfm/windows/views/icons/__init__.py | 0 .../icons/Icon.py => views/icons/icon.py} | 24 +- .../windows/views/icons/mixins/__init__.py | 0 .../icons/mixins/desktopiconmixin.py} | 25 -- .../icons/mixins/videoiconmixin.py} | 0 .../icons/mixins/xdg/BaseDirectory.py | 0 .../icons/mixins/xdg/Config.py | 0 .../icons/mixins/xdg/DesktopEntry.py | 0 .../icons/mixins/xdg/Exceptions.py | 0 .../icons/mixins/xdg/IconTheme.py | 0 .../icons/mixins/xdg/IniFile.py | 0 .../icons/mixins/xdg/Locale.py | 0 .../{view => views}/icons/mixins/xdg/Menu.py | 0 .../icons/mixins/xdg/MenuEditor.py | 0 .../{view => views}/icons/mixins/xdg/Mime.py | 0 .../icons/mixins/xdg/RecentFiles.py | 0 .../icons/mixins/xdg/__init__.py | 0 .../{view => views}/icons/mixins/xdg/util.py | 0 .../windows/{view/Path.py => views/path.py} | 15 +- src/shellfm/windows/views/utils/__init__.py | 0 .../utils/filehandler.py} | 10 +- .../Launcher.py => views/utils/launcher.py} | 29 ++- .../Settings.py => views/utils/settings.py} | 36 +-- src/shellfm/windows/views/view.py | 242 ++++++++++++++++++ src/shellfm/windows/window.py | 89 +++++++ 37 files changed, 605 insertions(+), 531 deletions(-) delete mode 100644 src/shellfm/windows/Window.py delete mode 100644 src/shellfm/windows/WindowController.py create mode 100644 src/shellfm/windows/controller.py delete mode 100644 src/shellfm/windows/view/View.py delete mode 100644 src/shellfm/windows/view/__init__.py delete mode 100644 src/shellfm/windows/view/icons/__init__.py delete mode 100644 src/shellfm/windows/view/icons/mixins/__init__.py delete mode 100644 src/shellfm/windows/view/utils/__init__.py create mode 100644 src/shellfm/windows/views/__init__.py create mode 100644 src/shellfm/windows/views/icons/__init__.py rename src/shellfm/windows/{view/icons/Icon.py => views/icons/icon.py} (70%) create mode 100644 src/shellfm/windows/views/icons/mixins/__init__.py rename src/shellfm/windows/{view/icons/mixins/DesktopIconMixin.py => views/icons/mixins/desktopiconmixin.py} (68%) rename src/shellfm/windows/{view/icons/mixins/VideoIconMixin.py => views/icons/mixins/videoiconmixin.py} (100%) rename src/shellfm/windows/{view => views}/icons/mixins/xdg/BaseDirectory.py (100%) rename src/shellfm/windows/{view => views}/icons/mixins/xdg/Config.py (100%) rename src/shellfm/windows/{view => views}/icons/mixins/xdg/DesktopEntry.py (100%) rename src/shellfm/windows/{view => views}/icons/mixins/xdg/Exceptions.py (100%) rename src/shellfm/windows/{view => views}/icons/mixins/xdg/IconTheme.py (100%) rename src/shellfm/windows/{view => views}/icons/mixins/xdg/IniFile.py (100%) rename src/shellfm/windows/{view => views}/icons/mixins/xdg/Locale.py (100%) rename src/shellfm/windows/{view => views}/icons/mixins/xdg/Menu.py (100%) rename src/shellfm/windows/{view => views}/icons/mixins/xdg/MenuEditor.py (100%) rename src/shellfm/windows/{view => views}/icons/mixins/xdg/Mime.py (100%) rename src/shellfm/windows/{view => views}/icons/mixins/xdg/RecentFiles.py (100%) rename src/shellfm/windows/{view => views}/icons/mixins/xdg/__init__.py (100%) rename src/shellfm/windows/{view => views}/icons/mixins/xdg/util.py (100%) rename src/shellfm/windows/{view/Path.py => views/path.py} (77%) create mode 100644 src/shellfm/windows/views/utils/__init__.py rename src/shellfm/windows/{view/utils/FileHandler.py => views/utils/filehandler.py} (94%) rename src/shellfm/windows/{view/utils/Launcher.py => views/utils/launcher.py} (73%) rename src/shellfm/windows/{view/utils/Settings.py => views/utils/settings.py} (71%) create mode 100644 src/shellfm/windows/views/view.py create mode 100644 src/shellfm/windows/window.py diff --git a/src/Example.py b/src/Example.py index 9cb363f..34b7346 100644 --- a/src/Example.py +++ b/src/Example.py @@ -1,21 +1,27 @@ -from shellfm import WindowController +from shellfm.windows.controller import WindowController def main(): + print("\n\n-------------------------------------------\n\n") window_controller = WindowController() # Create "File Window" 1 window = window_controller.create_window() - window.nickname = "Win1" - window_controller.add_view_for_window_by_nickname(window.nickname) + window.set_nickname("Win1") + window_controller.add_view_for_window_by_nickname(window.get_nickname()) # Create "File Window" 2 window2 = window_controller.create_window() - window2.nickname = "Win2" - window_controller.add_view_for_window_by_nickname(window2.nickname) + window2.set_nickname("Win2") + window_controller.add_view_for_window_by_nickname(window2.get_nickname()) window_controller.list_windows() + print("\n\n-------------------------------------------\n\n") + window2.set_is_hidden(True) + window_controller.list_windows() + + if __name__ == '__main__': main() diff --git a/src/shellfm/__init__.py b/src/shellfm/__init__.py index 0c8b591..e69de29 100644 --- a/src/shellfm/__init__.py +++ b/src/shellfm/__init__.py @@ -1 +0,0 @@ -from .windows import WindowController diff --git a/src/shellfm/windows/Window.py b/src/shellfm/windows/Window.py deleted file mode 100644 index 78c5241..0000000 --- a/src/shellfm/windows/Window.py +++ /dev/null @@ -1,66 +0,0 @@ -# Python imports -from random import randint - - -# Lib imports - - -# Application imports -from .view import View - - -class Window: - def __init__(self): - self.id_length = 10 - self.id = "" - self.name = "" - self.nickname = "" - self.isHidden = False - self.views = [] - - self.generate_id() - - - def random_with_N_digits(self, n): - range_start = 10**(n-1) - range_end = (10**n)-1 - return randint(range_start, range_end) - - def generate_id(self): - self.id = str(self.random_with_N_digits(self.id_length)) - - def get_window_id(self): - return self.id - - def create_view(self): - view = View() - self.views.append(view) - return view - - def pop_view(self): - self.views.pop() - - def delete_view_by_id(self, vid): - for view in self.views: - if view.id == vid: - self.views.remove(view) - break - - - def get_view_by_id(self, vid): - for view in self.views: - if view.id == vid: - return view - - def get_view_by_index(self, index): - return self.views[index] - - def get_views_count(self): - return len(self.views) - - def get_all_views(self): - return self.views - - def list_files_from_views(self): - for view in self.views: - print(view.files) diff --git a/src/shellfm/windows/WindowController.py b/src/shellfm/windows/WindowController.py deleted file mode 100644 index 6cc5bfa..0000000 --- a/src/shellfm/windows/WindowController.py +++ /dev/null @@ -1,170 +0,0 @@ -# Python imports -import threading, subprocess, time, json -from os import path - -# Lib imports - -# Application imports -from . import Window - - -class WindowController: - def __init__(self): - USER_HOME = path.expanduser('~') - CONFIG_PATH = USER_HOME + "/.config/shellfm" - self.session_file = CONFIG_PATH + "/session.json" - - self.active_window_id = "" - self.active_tab_id = "" - self.windows = [] - - - def set_active_data(self, wid, tid): - self.active_window_id = str(wid) - self.active_tab_id = str(tid) - - def get_active_data(self): - return self.active_window_id, self.active_tab_id - - def create_window(self): - window = Window() - window.name = "window_" + window.id - window.nickname = "window_" + str(len(self.windows) + 1) - - self.windows.append(window) - return window - - - def add_view_for_window(self, win_id): - for window in self.windows: - if window.id == win_id: - return window.create_view() - - def add_view_for_window_by_name(self, name): - for window in self.windows: - if window.name == name: - return window.create_view() - - def add_view_for_window_by_nickname(self, nickname): - for window in self.windows: - if window.nickname == nickname: - return window.create_view() - - def pop_window(self): - self.windows.pop() - - def delete_window_by_id(self, win_id): - for window in self.windows: - if window.id == win_id: - self.windows.remove(window) - break - - def delete_window_by_name(self, name): - for window in self.windows: - if window.name == name: - self.windows.remove(window) - break - - def delete_window_by_nickname(self, nickname): - for window in self.windows: - if window.nickname == nickname: - self.windows.remove(window) - break - - def get_window_by_id(self, win_id): - for window in self.windows: - if window.id == win_id: - return window - - raise(f"No Window by ID {win_id} found!") - - def get_window_by_name(self, name): - for window in self.windows: - if window.name == name: - return window - - raise(f"No Window by Name {name} found!") - - def get_window_by_nickname(self, nickname): - for window in self.windows: - if window.nickname == nickname: - return window - - raise(f"No Window by Nickname {nickname} found!") - - def get_window_by_index(self, index): - return self.windows[index] - - def get_all_windows(self): - return self.windows - - def set_window_nickname(self, win_id = None, nickname = ""): - for window in self.windows: - if window.id == win_id: - window.nickname = nickname - - def list_windows(self): - print("\n[ ---- Windows ---- ]\n") - for window in self.windows: - print(f"\nID: {window.id}") - print(f"Name: {window.name}") - print(f"Nickname: {window.nickname}") - print(f"Is Hidden: {window.isHidden}") - print(f"View Count: {window.get_views_count()}") - print("\n-------------------------\n") - - - - def list_files_from_views_of_window(self, win_id): - for window in self.windows: - if window.id == win_id: - window.list_files_from_views() - break - - def get_views_count(self, win_id): - for window in self.windows: - if window.id == win_id: - return window.get_views_count() - - def get_views_from_window(self, win_id): - for window in self.windows: - if window.id == win_id: - return window.get_all_views() - - - - - def save_state(self, session_file = None): - if not session_file: - session_file = self.session_file - - windows = [] - for window in self.windows: - views = [] - for view in window.views: - views.append(view.get_current_directory()) - - windows.append( - [ - { - 'window':{ - "ID": window.id, - "Name": window.name, - "Nickname": window.nickname, - "isHidden": window.isHidden, - 'views': views - } - } - ] - ) - - with open(session_file, 'w') as outfile: - json.dump(windows, outfile, separators=(',', ':'), indent=4) - - def load_state(self, session_file = None): - if not session_file: - session_file = self.session_file - - if path.isfile(session_file): - with open(session_file) as infile: - return json.load(infile) diff --git a/src/shellfm/windows/__init__.py b/src/shellfm/windows/__init__.py index cd9f6ce..e69de29 100644 --- a/src/shellfm/windows/__init__.py +++ b/src/shellfm/windows/__init__.py @@ -1,2 +0,0 @@ -from .Window import Window -from .WindowController import WindowController diff --git a/src/shellfm/windows/controller.py b/src/shellfm/windows/controller.py new file mode 100644 index 0000000..48ab097 --- /dev/null +++ b/src/shellfm/windows/controller.py @@ -0,0 +1,185 @@ +# Python imports +import threading, subprocess, time, json +from os import path + +# Lib imports + +# Application imports +from .window import Window + + +def threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() + return wrapper + + +class WindowController: + def __init__(self): + USER_HOME = path.expanduser('~') + CONFIG_PATH = USER_HOME + "/.config/solarfm" + self._session_file = CONFIG_PATH + "/session.json" + + self._event_sleep_time = 1 + self._active_window_id = "" + self._active_tab_id = "" + self._windows = [] + + + def set_active_data(self, wid, tid): + self._active_window_id = str(wid) + self._active_tab_id = str(tid) + + def get_active_wid_and_tid(self): + return self._active_window_id, self._active_tab_id + + def create_window(self): + window = Window() + window.set_nickname(f"window_{str(len(self._windows) + 1)}") + self._windows.append(window) + return window + + + def add_view_for_window(self, win_id): + for window in self._windows: + if window.get_id() == win_id: + return window.create_view() + + def add_view_for_window_by_name(self, name): + for window in self._windows: + if window.get_name() == name: + return window.create_view() + + def add_view_for_window_by_nickname(self, nickname): + for window in self._windows: + if window.get_nickname() == nickname: + return window.create_view() + + def pop_window(self): + self._windows.pop() + + def delete_window_by_id(self, win_id): + for window in self._windows: + if window.get_id() == win_id: + self._windows.remove(window) + break + + def delete_window_by_name(self, name): + for window in self._windows: + if window.get_name() == name: + self._windows.remove(window) + break + + def delete_window_by_nickname(self, nickname): + for window in self._windows: + if window.get_nickname() == nickname: + self._windows.remove(window) + break + + def get_window_by_id(self, win_id): + for window in self._windows: + if window.get_id() == win_id: + return window + + raise(f"No Window by ID {win_id} found!") + + def get_window_by_name(self, name): + for window in self._windows: + if window.get_name() == name: + return window + + raise(f"No Window by Name {name} found!") + + def get_window_by_nickname(self, nickname): + for window in self._windows: + if window.get_nickname() == nickname: + return window + + raise(f"No Window by Nickname {nickname} found!") + + def get_window_by_index(self, index): + return self._windows[index] + + def get_all_windows(self): + return self._windows + + + def set_window_nickname(self, win_id = None, nickname = ""): + for window in self._windows: + if window.get_id() == win_id: + window.set_nickname(nickname) + + def list_windows(self): + print("\n[ ---- Windows ---- ]\n") + for window in self._windows: + print(f"\nID: {window.get_id()}") + print(f"Name: {window.get_name()}") + print(f"Nickname: {window.get_nickname()}") + print(f"Is Hidden: {window.is_hidden()}") + print(f"View Count: {window.get_views_count()}") + print("\n-------------------------\n") + + + + def list_files_from_views_of_window(self, win_id): + for window in self._windows: + if window.get_id() == win_id: + window.list_files_from_views() + break + + def get_views_count(self, win_id): + for window in self._windows: + if window.get_id() == win_id: + return window.get_views_count() + + def get_views_from_window(self, win_id): + for window in self._windows: + if window.get_id() == win_id: + return window.get_all_views() + + + + + def unload_views_and_windows(self): + for window in self._windows: + window.get_all_views().clear() + + self._windows.clear() + + def save_state(self, session_file = None): + if not session_file: + session_file = self._session_file + + if len(self._windows) > 0: + windows = [] + for window in self._windows: + views = [] + for view in window.get_all_views(): + views.append(view.get_current_directory()) + + windows.append( + [ + { + 'window':{ + "ID": window.get_id(), + "Name": window.get_name(), + "Nickname": window.get_nickname(), + "isHidden": f"{window.is_hidden()}", + 'views': views + } + } + ] + ) + + with open(session_file, 'w') as outfile: + json.dump(windows, outfile, separators=(',', ':'), indent=4) + else: + raise Exception("Window dara corrupted! Can not save session!") + + def load_state(self, session_file = None): + if not session_file: + session_file = self._session_file + + if path.isfile(session_file): + with open(session_file) as infile: + return json.load(infile) diff --git a/src/shellfm/windows/view/View.py b/src/shellfm/windows/view/View.py deleted file mode 100644 index ae0bbb8..0000000 --- a/src/shellfm/windows/view/View.py +++ /dev/null @@ -1,211 +0,0 @@ -# Python imports -import hashlib -import os -from os import listdir -from os.path import isdir, isfile, join - -from random import randint - - -# Lib imports - - -# Application imports -from .utils import Settings, Launcher, FileHandler -from .icons import Icon -from . import Path - - -class View(Settings, FileHandler, Launcher, Icon, Path): - def __init__(self): - self. logger = None - self.id_length = 10 - - self.id = "" - self.wid = None - self.dir_watcher = None - self.hide_hidden = self.HIDE_HIDDEN_FILES - self.files = [] - self.dirs = [] - self.vids = [] - self.images = [] - self.desktop = [] - self.ungrouped = [] - - self.generate_id() - self.set_to_home() - - - def random_with_N_digits(self, n): - range_start = 10**(n-1) - range_end = (10**n)-1 - return randint(range_start, range_end) - - def generate_id(self): - self.id = str(self.random_with_N_digits(self.id_length)) - - def get_tab_id(self): - return self.id - - def set_wid(self, _wid): - self.wid = _wid - - def get_wid(self): - return self.wid - - def set_dir_watcher(self, watcher): - self.dir_watcher = watcher - - def get_dir_watcher(self): - return self.dir_watcher - - def load_directory(self): - path = self.get_path() - self.dirs = [".", ".."] - self.vids = [] - self.images = [] - self.desktop = [] - self.ungrouped = [] - self.files = [] - - if not isdir(path): - self.set_to_home() - return "" - - for f in listdir(path): - file = join(path, f) - if self.hide_hidden: - if f.startswith('.'): - continue - - if isfile(file): - lowerName = file.lower() - if lowerName.endswith(self.fvideos): - self.vids.append(f) - elif lowerName.endswith(self.fimages): - self.images.append(f) - elif lowerName.endswith((".desktop",)): - self.desktop.append(f) - else: - self.ungrouped.append(f) - else: - self.dirs.append(f) - - self.dirs.sort() - self.vids.sort() - self.images.sort() - self.desktop.sort() - self.ungrouped.sort() - - self.files = self.dirs + self.vids + self.images + self.desktop + self.ungrouped - - def hash_text(self, text): - return hashlib.sha256(str.encode(text)).hexdigest()[:18] - - def hash_set(self, arry): - data = [] - for arr in arry: - data.append([arr, self.hash_text(arr)]) - return data - - def is_folder_locked(self, hash): - if self.lock_folder: - path_parts = self.get_path().split('/') - file = self.get_path_part_from_hash(hash) - - # Insure chilren folders are locked too. - lockedFolderInPath = False - for folder in self.locked_folders: - if folder in path_parts: - lockedFolderInPath = True - break - - return (file in self.locked_folders or lockedFolderInPath) - else: - return False - - - - - def get_path_part_from_hash(self, hash): - files = self.get_files() - file = None - - for f in files: - if hash == f[1]: - file = f[0] - break - - return file - - def get_files_formatted(self): - files = self.hash_set(self.files), - dirs = self.hash_set(self.dirs), - videos = self.get_videos(), - images = self.hash_set(self.images), - desktops = self.hash_set(self.desktop), - ungrouped = self.hash_set(self.ungrouped) - - return { - 'path_head': self.get_path(), - 'list': { - 'files': files, - 'dirs': dirs, - 'videos': videos, - 'images': images, - 'desktops': desktops, - 'ungrouped': ungrouped - } - } - - def get_pixbuf_icon_str_combo(self): - data = [] - dir = self.get_current_directory() - for file in self.files: - icon = self.create_icon(dir, file).get_pixbuf() - data.append([icon, file]) - - return data - - def get_gtk_icon_str_combo(self): - data = [] - dir = self.get_current_directory() - for file in self.files: - icon = self.create_icon(dir, file) - data.append([icon, file[0]]) - - return data - - def get_current_directory(self): - return self.get_path() - - def get_current_sub_path(self): - path = self.get_path() - home = self.get_home() + "/" - return path.replace(home, "") - - def get_end_of_path(self): - parts = self.get_current_directory().split("/") - size = len(parts) - return parts[size - 1] - - def get_dot_dots(self): - return self.hash_set(['.', '..']) - - def get_files(self): - return self.hash_set(self.files) - - def get_dirs(self): - return self.hash_set(self.dirs) - - def get_videos(self): - return self.hash_set(self.vids) - - def get_images(self): - return self.hash_set(self.images) - - def get_desktops(self): - return self.hash_set(self.desktop) - - def get_ungrouped(self): - return self.hash_set(self.ungrouped) diff --git a/src/shellfm/windows/view/__init__.py b/src/shellfm/windows/view/__init__.py deleted file mode 100644 index 07d9ad7..0000000 --- a/src/shellfm/windows/view/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .utils import * -from .icons import * - -from .Path import Path -from .View import View diff --git a/src/shellfm/windows/view/icons/__init__.py b/src/shellfm/windows/view/icons/__init__.py deleted file mode 100644 index b946d44..0000000 --- a/src/shellfm/windows/view/icons/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .mixins import DesktopIconMixin -from .mixins import VideoIconMixin - -from .Icon import Icon diff --git a/src/shellfm/windows/view/icons/mixins/__init__.py b/src/shellfm/windows/view/icons/mixins/__init__.py deleted file mode 100644 index 54bfe39..0000000 --- a/src/shellfm/windows/view/icons/mixins/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from . import xdg - -from .VideoIconMixin import VideoIconMixin -from .DesktopIconMixin import DesktopIconMixin diff --git a/src/shellfm/windows/view/utils/__init__.py b/src/shellfm/windows/view/utils/__init__.py deleted file mode 100644 index 3c05646..0000000 --- a/src/shellfm/windows/view/utils/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .Settings import Settings -from .Launcher import Launcher diff --git a/src/shellfm/windows/views/__init__.py b/src/shellfm/windows/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/shellfm/windows/views/icons/__init__.py b/src/shellfm/windows/views/icons/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/shellfm/windows/view/icons/Icon.py b/src/shellfm/windows/views/icons/icon.py similarity index 70% rename from src/shellfm/windows/view/icons/Icon.py rename to src/shellfm/windows/views/icons/icon.py index f551ee6..6afa0e5 100644 --- a/src/shellfm/windows/view/icons/Icon.py +++ b/src/shellfm/windows/views/icons/icon.py @@ -3,10 +3,13 @@ import os, subprocess, threading, hashlib from os.path import isfile # Gtk imports +import gi +gi.require_version('GdkPixbuf', '2.0') from gi.repository import GdkPixbuf # Application imports -from .mixins import * +from .mixins.desktopiconmixin import DesktopIconMixin +from .mixins.videoiconmixin import VideoIconMixin def threaded(fn): @@ -17,7 +20,7 @@ def threaded(fn): class Icon(DesktopIconMixin, VideoIconMixin): def create_icon(self, dir, file): - full_path = dir + "/" + file + full_path = f"{dir}/{file}" return self.get_icon_image(dir, file, full_path) def get_icon_image(self, dir, file, full_path): @@ -36,29 +39,32 @@ class Icon(DesktopIconMixin, VideoIconMixin): return None def create_thumbnail(self, dir, file): - full_path = dir + "/" + file + full_path = f"{dir}/{file}" try: file_hash = hashlib.sha256(str.encode(full_path)).hexdigest() - hash_img_pth = self.ABS_THUMBS_PTH + "/" + file_hash + ".jpg" + hash_img_pth = f"{self.ABS_THUMBS_PTH}/{file_hash}.jpg" if isfile(hash_img_pth) == False: self.generate_video_thumbnail(full_path, hash_img_pth) thumbnl = self.create_scaled_image(hash_img_pth, self.VIDEO_ICON_WH) if thumbnl == None: # If no icon whatsoever, return internal default - thumbnl = GdkPixbuf.Pixbuf.new_from_file(self.DEFAULT_ICONS + "/video.png") + thumbnl = GdkPixbuf.Pixbuf.new_from_file(f"{self.DEFAULT_ICONS}/video.png") return thumbnl except Exception as e: print("Thumbnail generation issue:") print( repr(e) ) - return GdkPixbuf.Pixbuf.new_from_file(self.DEFAULT_ICONS + "/video.png") + return GdkPixbuf.Pixbuf.new_from_file(f"{self.DEFAULT_ICONS}/video.png") def create_scaled_image(self, path, wxh): try: - pixbuf = GdkPixbuf.Pixbuf.new_from_file(path) - scaled_pixbuf = pixbuf.scale_simple(wxh[0], wxh[1], 2) # 2 = BILINEAR and is best by default - return scaled_pixbuf + if path.lower().endswith(".gif"): + return GdkPixbuf.PixbufAnimation.new_from_file(path) \ + .get_static_image() \ + .scale_simple(wxh[0], wxh[1], GdkPixbuf.InterpType.BILINEAR) + else: + return GdkPixbuf.Pixbuf.new_from_file_at_scale(path, wxh[0], wxh[1], True) except Exception as e: print("Image Scaling Issue:") print( repr(e) ) diff --git a/src/shellfm/windows/views/icons/mixins/__init__.py b/src/shellfm/windows/views/icons/mixins/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/shellfm/windows/view/icons/mixins/DesktopIconMixin.py b/src/shellfm/windows/views/icons/mixins/desktopiconmixin.py similarity index 68% rename from src/shellfm/windows/view/icons/mixins/DesktopIconMixin.py rename to src/shellfm/windows/views/icons/mixins/desktopiconmixin.py index 34573cb..2d3c30b 100644 --- a/src/shellfm/windows/view/icons/mixins/DesktopIconMixin.py +++ b/src/shellfm/windows/views/icons/mixins/desktopiconmixin.py @@ -5,8 +5,6 @@ from os.path import isfile # Gtk imports import gi gi.require_version('Gtk', '3.0') - -from gi.repository import Gio from gi.repository import Gtk # Application imports @@ -14,29 +12,6 @@ from .xdg.DesktopEntry import DesktopEntry class DesktopIconMixin: - # NOTE: !!!IMPORTANT!!! This method is NOT thread safe and causes seg faults - # when ran from another thread! It took me a while to hunt this info down. - # Gio is the culprit. Pull this into a main Gtk thread if actually needed. - def get_system_thumbnail(self, filename, size): - try: - if os.path.exists(filename): - gioFile = Gio.File.new_for_path(filename) - info = gioFile.query_info('standard::icon' , 0, Gio.Cancellable()) - icon = info.get_icon().get_names()[0] - iconTheme = Gtk.IconTheme.get_default() - iconData = iconTheme.lookup_icon(icon , size , 0) - if iconData: - iconPath = iconData.get_filename() - return Gtk.Image.new_from_file(iconPath) - else: - return None - else: - return None - except Exception as e: - print("system icon generation issue:") - print( repr(e) ) - return None - def parse_desktop_files(self, full_path): try: xdgObj = DesktopEntry(full_path) diff --git a/src/shellfm/windows/view/icons/mixins/VideoIconMixin.py b/src/shellfm/windows/views/icons/mixins/videoiconmixin.py similarity index 100% rename from src/shellfm/windows/view/icons/mixins/VideoIconMixin.py rename to src/shellfm/windows/views/icons/mixins/videoiconmixin.py diff --git a/src/shellfm/windows/view/icons/mixins/xdg/BaseDirectory.py b/src/shellfm/windows/views/icons/mixins/xdg/BaseDirectory.py similarity index 100% rename from src/shellfm/windows/view/icons/mixins/xdg/BaseDirectory.py rename to src/shellfm/windows/views/icons/mixins/xdg/BaseDirectory.py diff --git a/src/shellfm/windows/view/icons/mixins/xdg/Config.py b/src/shellfm/windows/views/icons/mixins/xdg/Config.py similarity index 100% rename from src/shellfm/windows/view/icons/mixins/xdg/Config.py rename to src/shellfm/windows/views/icons/mixins/xdg/Config.py diff --git a/src/shellfm/windows/view/icons/mixins/xdg/DesktopEntry.py b/src/shellfm/windows/views/icons/mixins/xdg/DesktopEntry.py similarity index 100% rename from src/shellfm/windows/view/icons/mixins/xdg/DesktopEntry.py rename to src/shellfm/windows/views/icons/mixins/xdg/DesktopEntry.py diff --git a/src/shellfm/windows/view/icons/mixins/xdg/Exceptions.py b/src/shellfm/windows/views/icons/mixins/xdg/Exceptions.py similarity index 100% rename from src/shellfm/windows/view/icons/mixins/xdg/Exceptions.py rename to src/shellfm/windows/views/icons/mixins/xdg/Exceptions.py diff --git a/src/shellfm/windows/view/icons/mixins/xdg/IconTheme.py b/src/shellfm/windows/views/icons/mixins/xdg/IconTheme.py similarity index 100% rename from src/shellfm/windows/view/icons/mixins/xdg/IconTheme.py rename to src/shellfm/windows/views/icons/mixins/xdg/IconTheme.py diff --git a/src/shellfm/windows/view/icons/mixins/xdg/IniFile.py b/src/shellfm/windows/views/icons/mixins/xdg/IniFile.py similarity index 100% rename from src/shellfm/windows/view/icons/mixins/xdg/IniFile.py rename to src/shellfm/windows/views/icons/mixins/xdg/IniFile.py diff --git a/src/shellfm/windows/view/icons/mixins/xdg/Locale.py b/src/shellfm/windows/views/icons/mixins/xdg/Locale.py similarity index 100% rename from src/shellfm/windows/view/icons/mixins/xdg/Locale.py rename to src/shellfm/windows/views/icons/mixins/xdg/Locale.py diff --git a/src/shellfm/windows/view/icons/mixins/xdg/Menu.py b/src/shellfm/windows/views/icons/mixins/xdg/Menu.py similarity index 100% rename from src/shellfm/windows/view/icons/mixins/xdg/Menu.py rename to src/shellfm/windows/views/icons/mixins/xdg/Menu.py diff --git a/src/shellfm/windows/view/icons/mixins/xdg/MenuEditor.py b/src/shellfm/windows/views/icons/mixins/xdg/MenuEditor.py similarity index 100% rename from src/shellfm/windows/view/icons/mixins/xdg/MenuEditor.py rename to src/shellfm/windows/views/icons/mixins/xdg/MenuEditor.py diff --git a/src/shellfm/windows/view/icons/mixins/xdg/Mime.py b/src/shellfm/windows/views/icons/mixins/xdg/Mime.py similarity index 100% rename from src/shellfm/windows/view/icons/mixins/xdg/Mime.py rename to src/shellfm/windows/views/icons/mixins/xdg/Mime.py diff --git a/src/shellfm/windows/view/icons/mixins/xdg/RecentFiles.py b/src/shellfm/windows/views/icons/mixins/xdg/RecentFiles.py similarity index 100% rename from src/shellfm/windows/view/icons/mixins/xdg/RecentFiles.py rename to src/shellfm/windows/views/icons/mixins/xdg/RecentFiles.py diff --git a/src/shellfm/windows/view/icons/mixins/xdg/__init__.py b/src/shellfm/windows/views/icons/mixins/xdg/__init__.py similarity index 100% rename from src/shellfm/windows/view/icons/mixins/xdg/__init__.py rename to src/shellfm/windows/views/icons/mixins/xdg/__init__.py diff --git a/src/shellfm/windows/view/icons/mixins/xdg/util.py b/src/shellfm/windows/views/icons/mixins/xdg/util.py similarity index 100% rename from src/shellfm/windows/view/icons/mixins/xdg/util.py rename to src/shellfm/windows/views/icons/mixins/xdg/util.py diff --git a/src/shellfm/windows/view/Path.py b/src/shellfm/windows/views/path.py similarity index 77% rename from src/shellfm/windows/view/Path.py rename to src/shellfm/windows/views/path.py index 91787b0..ecce282 100644 --- a/src/shellfm/windows/view/Path.py +++ b/src/shellfm/windows/views/path.py @@ -11,7 +11,7 @@ class Path: return os.path.expanduser("~") + self.subpath def get_path(self): - return "/" + "/".join(self.path) + return f"/{'/'.join(self.path)}" if self.path else f"/{''.join(self.path)}" def get_path_list(self): return self.path @@ -21,13 +21,16 @@ class Path: self.load_directory() def pop_from_path(self): - self.path.pop() + try: + self.path.pop() - if not self.go_past_home: - if self.get_home() not in self.get_path(): - self.set_to_home() + if not self.go_past_home: + if self.get_home() not in self.get_path(): + self.set_to_home() - self.load_directory() + self.load_directory() + except Exception as e: + pass def set_path(self, path): if path == self.get_path(): diff --git a/src/shellfm/windows/views/utils/__init__.py b/src/shellfm/windows/views/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/shellfm/windows/view/utils/FileHandler.py b/src/shellfm/windows/views/utils/filehandler.py similarity index 94% rename from src/shellfm/windows/view/utils/FileHandler.py rename to src/shellfm/windows/views/utils/filehandler.py index 57c69e3..d0f7396 100644 --- a/src/shellfm/windows/view/utils/FileHandler.py +++ b/src/shellfm/windows/views/utils/filehandler.py @@ -1,6 +1,12 @@ - +# Python imports import os, shutil, subprocess, threading +# Lib imports + +# Application imports + + + class FileHandler: def create_file(self, nFile, type): @@ -51,7 +57,7 @@ class FileHandler: def move_file(self, fFile, tFile): try: print(f"Moving: {fFile} --> {tFile}") - if os.path.exists(fFile) and os.path.exists(tFile): + if os.path.exists(fFile) and not os.path.exists(tFile): if not tFile.endswith("/"): tFile += "/" diff --git a/src/shellfm/windows/view/utils/Launcher.py b/src/shellfm/windows/views/utils/launcher.py similarity index 73% rename from src/shellfm/windows/view/utils/Launcher.py rename to src/shellfm/windows/views/utils/launcher.py index ba50e47..22a14e3 100644 --- a/src/shellfm/windows/view/utils/Launcher.py +++ b/src/shellfm/windows/views/utils/launcher.py @@ -1,5 +1,5 @@ # System import -import os, subprocess, threading +import os, threading, subprocess # Lib imports @@ -8,6 +8,12 @@ import os, subprocess, threading # Apoplication imports +def threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs).start() + return wrapper + + class Launcher: def open_file_locally(self, file): lowerName = file.lower() @@ -30,13 +36,28 @@ class Launcher: command = [self.text_app, file] elif lowerName.endswith(self.fpdf): command = [self.pdf_app, file] - else: + elif lowerName.endswith("placeholder-until-i-can-get-a-use-pref-fm-flag"): command = [self.file_manager_app, file] + else: + command = ["xdg-open", file] - self.logger.debug(command) + self.execute(command, use_shell=False) + + + def execute(self, command, start_dir=os.getenv("HOME"), use_os_system=None, use_shell=True): + self.logger.debug(command) + if use_os_system: + os.system(command) + else: + subprocess.Popen(command, cwd=start_dir, shell=use_shell, start_new_session=True, stdout=None, stderr=None, close_fds=True) + + def execute_and_return_thread_handler(self, command, start_dir=os.getenv("HOME"), use_shell=True): DEVNULL = open(os.devnull, 'w') - subprocess.Popen(command, start_new_session=True, stdout=DEVNULL, stderr=DEVNULL, close_fds=True) + return subprocess.Popen(command, cwd=start_dir, shell=use_shell, start_new_session=True, stdout=DEVNULL, stderr=DEVNULL, close_fds=True) + @threaded + def app_chooser_exec(self, app_info, uris): + app_info.launch_uris_async(uris) def remux_video(self, hash, file): remux_vid_pth = self.REMUX_FOLDER + "/" + hash + ".mp4" diff --git a/src/shellfm/windows/view/utils/Settings.py b/src/shellfm/windows/views/utils/settings.py similarity index 71% rename from src/shellfm/windows/view/utils/Settings.py rename to src/shellfm/windows/views/utils/settings.py index 35e51ae..e4d9323 100644 --- a/src/shellfm/windows/view/utils/Settings.py +++ b/src/shellfm/windows/views/utils/settings.py @@ -13,22 +13,23 @@ from os import path class Settings: logger = None + USR_SOLARFM = "/usr/share/solarfm" USER_HOME = path.expanduser('~') - CONFIG_PATH = USER_HOME + "/.config/shellfm" - CONFIG_FILE = CONFIG_PATH + "/settings.json" + CONFIG_PATH = f"{USER_HOME}/.config/solarfm" + CONFIG_FILE = f"{CONFIG_PATH}/settings.json" HIDE_HIDDEN_FILES = True GTK_ORIENTATION = 1 # HORIZONTAL (0) VERTICAL (1) - DEFAULT_ICONS = CONFIG_PATH + "/icons" - DEFAULT_ICON = DEFAULT_ICONS + "/text.png" - FFMPG_THUMBNLR = CONFIG_PATH + "/ffmpegthumbnailer" # Thumbnail generator binary - REMUX_FOLDER = USER_HOME + "/.remuxs" # Remuxed files folder + DEFAULT_ICONS = f"{CONFIG_PATH}/icons" + DEFAULT_ICON = f"{DEFAULT_ICONS}/text.png" + FFMPG_THUMBNLR = f"{CONFIG_PATH}/ffmpegthumbnailer" # Thumbnail generator binary + REMUX_FOLDER = f"{USER_HOME}/.remuxs" # Remuxed files folder STEAM_BASE_URL = "https://steamcdn-a.akamaihd.net/steam/apps/" - ICON_DIRS = ["/usr/share/pixmaps", "/usr/share/icons", USER_HOME + "/.icons" ,] - BASE_THUMBS_PTH = USER_HOME + "/.thumbnails" # Used for thumbnail generation - ABS_THUMBS_PTH = BASE_THUMBS_PTH + "/normal" # Used for thumbnail generation - STEAM_ICONS_PTH = BASE_THUMBS_PTH + "/steam_icons" + ICON_DIRS = ["/usr/share/pixmaps", "/usr/share/icons", f"{USER_HOME}/.icons" ,] + BASE_THUMBS_PTH = f"{USER_HOME}/.thumbnails" # Used for thumbnail generation + ABS_THUMBS_PTH = f"{BASE_THUMBS_PTH}/normal" # Used for thumbnail generation + STEAM_ICONS_PTH = f"{BASE_THUMBS_PTH}/steam_icons" CONTAINER_ICON_WH = [128, 128] VIDEO_ICON_WH = [128, 64] SYS_ICON_WH = [56, 56] @@ -58,7 +59,7 @@ class Settings: subpath = settings["base_of_home"] HIDE_HIDDEN_FILES = True if settings["hide_hidden_files"] == "true" else False FFMPG_THUMBNLR = FFMPG_THUMBNLR if settings["thumbnailer_path"] == "" else settings["thumbnailer_path"] - go_past_home = True if settings["go_past_home"] == "true" else False + go_past_home = True if settings["go_past_home"] == "" else settings["go_past_home"] lock_folder = True if settings["lock_folder"] == "true" else False locked_folders = settings["locked_folders"].split("::::") mplayer_options = settings["mplayer_options"].split() @@ -69,6 +70,7 @@ class Settings: pdf_app = settings["pdf_app"] text_app = settings["text_app"] file_manager_app = settings["file_manager_app"] + terminal_app = settings["terminal_app"] remux_folder_max_disk_usage = settings["remux_folder_max_disk_usage"] # Filters @@ -81,14 +83,18 @@ class Settings: # Dir structure check - if path.isdir(REMUX_FOLDER) == False: + if not path.isdir(REMUX_FOLDER): os.mkdir(REMUX_FOLDER) - if path.isdir(BASE_THUMBS_PTH) == False: + if not path.isdir(BASE_THUMBS_PTH): os.mkdir(BASE_THUMBS_PTH) - if path.isdir(ABS_THUMBS_PTH) == False: + if not path.isdir(ABS_THUMBS_PTH): os.mkdir(ABS_THUMBS_PTH) - if path.isdir(STEAM_ICONS_PTH) == False: + if not path.isdir(STEAM_ICONS_PTH): os.mkdir(STEAM_ICONS_PTH) + + if not os.path.exists(DEFAULT_ICONS): + DEFAULT_ICONS = f"{USR_SOLARFM}/icons" + DEFAULT_ICON = f"{DEFAULT_ICONS}/text.png" diff --git a/src/shellfm/windows/views/view.py b/src/shellfm/windows/views/view.py new file mode 100644 index 0000000..41bacab --- /dev/null +++ b/src/shellfm/windows/views/view.py @@ -0,0 +1,242 @@ +# Python imports +import os, hashlib, re +from os import listdir +from os.path import isdir, isfile, join + +from random import randint + + +# Lib imports + + +# Application imports +from .utils.settings import Settings +from .utils.launcher import Launcher +from .utils.filehandler import FileHandler + +from .icons.icon import Icon +from .path import Path + + +class View(Settings, FileHandler, Launcher, Icon, Path): + def __init__(self): + self.logger = None + self._id_length = 10 + + self._id = "" + self._wid = None + self._dir_watcher = None + self._hide_hidden = self.HIDE_HIDDEN_FILES + self._files = [] + self._dirs = [] + self._vids = [] + self._images = [] + self._desktop = [] + self._ungrouped = [] + self._hidden = [] + + self._generate_id() + self.set_to_home() + + def load_directory(self): + path = self.get_path() + self._dirs = [] + self._vids = [] + self._images = [] + self._desktop = [] + self._ungrouped = [] + self._hidden = [] + self._files = [] + + if not isdir(path): + self.set_to_home() + return "" + + for f in listdir(path): + file = join(path, f) + if self._hide_hidden: + if f.startswith('.'): + self._hidden.append(f) + continue + + if isfile(file): + lowerName = file.lower() + if lowerName.endswith(self.fvideos): + self._vids.append(f) + elif lowerName.endswith(self.fimages): + self._images.append(f) + elif lowerName.endswith((".desktop",)): + self._desktop.append(f) + else: + self._ungrouped.append(f) + else: + self._dirs.append(f) + + self._dirs.sort(key=self._natural_keys) + self._vids.sort(key=self._natural_keys) + self._images.sort(key=self._natural_keys) + self._desktop.sort(key=self._natural_keys) + self._ungrouped.sort(key=self._natural_keys) + + self._files = self._dirs + self._vids + self._images + self._desktop + self._ungrouped + + def is_folder_locked(self, hash): + if self.lock_folder: + path_parts = self.get_path().split('/') + file = self.get_path_part_from_hash(hash) + + # Insure chilren folders are locked too. + lockedFolderInPath = False + for folder in self.locked_folders: + if folder in path_parts: + lockedFolderInPath = True + break + + return (file in self.locked_folders or lockedFolderInPath) + else: + return False + + + def get_not_hidden_count(self): + return len(self._files) + \ + len(self._dirs) + \ + len(self._vids) + \ + len(self._images) + \ + len(self._desktop) + \ + len(self._ungrouped) + + def get_hidden_count(self): + return len(self._hidden) + + def get_files_count(self): + return len(self._files) + + def get_path_part_from_hash(self, hash): + files = self.get_files() + file = None + + for f in files: + if hash == f[1]: + file = f[0] + break + + return file + + def get_files_formatted(self): + files = self._hash_set(self._files), + dirs = self._hash_set(self._dirs), + videos = self.get_videos(), + images = self._hash_set(self._images), + desktops = self._hash_set(self._desktop), + ungrouped = self._hash_set(self._ungrouped) + hidden = self._hash_set(self._hidden) + + return { + 'path_head': self.get_path(), + 'list': { + 'files': files, + 'dirs': dirs, + 'videos': videos, + 'images': images, + 'desktops': desktops, + 'ungrouped': ungrouped, + 'hidden': hidden + } + } + + def get_pixbuf_icon_str_combo(self): + data = [] + dir = self.get_current_directory() + for file in self._files: + icon = self.create_icon(dir, file).get_pixbuf() + data.append([icon, file]) + + return data + + + def get_gtk_icon_str_combo(self): + data = [] + dir = self.get_current_directory() + for file in self._files: + icon = self.create_icon(dir, file) + data.append([icon, file[0]]) + + return data + + def get_current_directory(self): + return self.get_path() + + def get_current_sub_path(self): + path = self.get_path() + home = f"{self.get_home()}/" + return path.replace(home, "") + + def get_end_of_path(self): + parts = self.get_current_directory().split("/") + size = len(parts) + return parts[size - 1] + + def is_hidden(self): + return self._dir_watcher + + def set_is_hidden(self, state): + self._hide_hidden = state + + def get_dot_dots(self): + return self._hash_set(['.', '..']) + + def get_files(self): + return self._hash_set(self._files) + + def get_dirs(self): + return self._hash_set(self._dirs) + + def get_videos(self): + return self._hash_set(self._vids) + + def get_images(self): + return self._hash_set(self._images) + + def get_desktops(self): + return self._hash_set(self._desktop) + + def get_ungrouped(self): + return self._hash_set(self._ungrouped) + + def get_id(self): + return self._id + + def set_wid(self, _wid): + self._wid = _wid + + def get_wid(self): + return self._wid + + def set_dir_watcher(self, watcher): + self._dir_watcher = watcher + + def get_dir_watcher(self): + return self._dir_watcher + + def _atoi(self, text): + return int(text) if text.isdigit() else text + + def _natural_keys(self, text): + return [ self._atoi(c) for c in re.split('(\d+)',text) ] + + def _hash_text(self, text): + return hashlib.sha256(str.encode(text)).hexdigest()[:18] + + def _hash_set(self, arry): + data = [] + for arr in arry: + data.append([arr, self._hash_text(arr)]) + return data + + def _random_with_N_digits(self, n): + range_start = 10**(n-1) + range_end = (10**n)-1 + return randint(range_start, range_end) + + def _generate_id(self): + self._id = str(self._random_with_N_digits(self._id_length)) diff --git a/src/shellfm/windows/window.py b/src/shellfm/windows/window.py new file mode 100644 index 0000000..e390804 --- /dev/null +++ b/src/shellfm/windows/window.py @@ -0,0 +1,89 @@ +# Python imports +from random import randint + + +# Lib imports + + +# Application imports +from .views.view import View + + +class Window: + def __init__(self): + self._id_length = 10 + self._id = "" + self._name = "" + self._nickname = "" + self._isHidden = False + self._views = [] + + self._generate_id() + self._set_name() + + + def create_view(self): + view = View() + self._views.append(view) + return view + + def pop_view(self): + self._views.pop() + + def delete_view_by_id(self, vid): + for view in self._views: + if view.get_id() == vid: + self._views.remove(view) + break + + + def get_view_by_id(self, vid): + for view in self._views: + if view.get_id() == vid: + return view + + def get_view_by_index(self, index): + return self._views[index] + + def get_views_count(self): + return len(self._views) + + def get_all_views(self): + return self._views + + def list_files_from_views(self): + for view in self._views: + print(view.files) + + + def get_id(self): + return self._id + + def get_name(self): + return self._name + + def get_nickname(self): + return self._nickname + + def is_hidden(self): + return self._isHidden + + + + + def set_nickname(self, nickname): + self._nickname = f"{nickname}" + + def set_is_hidden(self, state): + self._isHidden = f"{state}" + + def _set_name(self): + self._name = "window_" + self.get_id() + + def _random_with_N_digits(self, n): + range_start = 10**(n-1) + range_end = (10**n)-1 + return randint(range_start, range_end) + + def _generate_id(self): + self._id = str(self._random_with_N_digits(self._id_length))