diff --git a/src/shellfm/__init__.py b/src/shellfm/__init__.py new file mode 100644 index 0000000..0c8b591 --- /dev/null +++ b/src/shellfm/__init__.py @@ -0,0 +1 @@ +from .windows import WindowController diff --git a/src/shellfm/windows/Launcher.py b/src/shellfm/windows/Launcher.py new file mode 100644 index 0000000..6e3dcac --- /dev/null +++ b/src/shellfm/windows/Launcher.py @@ -0,0 +1,102 @@ +# System import +import os, subprocess, threading + + +# Lib imports + + +# Apoplication imports + + +class Launcher: + def openFilelocally(self, file): + lowerName = file.lower() + command = [] + + if lowerName.endswith(self.fvideos): + command = [self.media_app] + + if "mplayer" in self.media_app: + command += self.mplayer_options + + command += [file] + elif lowerName.endswith(self.fimages): + command = [self.image_app, file] + elif lowerName.endswith(self.fmusic): + command = [self.music_app, file] + elif lowerName.endswith(self.foffice): + command = [self.office_app, file] + elif lowerName.endswith(self.ftext): + command = [self.text_app, file] + elif lowerName.endswith(self.fpdf): + command = [self.pdf_app, file] + else: + command = [self.file_manager_app, file] + + self.logger.debug(command) + DEVNULL = open(os.devnull, 'w') + subprocess.Popen(command, start_new_session=True, stdout=DEVNULL, stderr=DEVNULL, close_fds=True) + + + def remuxVideo(self, hash, file): + remux_vid_pth = self.REMUX_FOLDER + "/" + hash + ".mp4" + self.logger.debug(remux_vid_pth) + + if not os.path.isfile(remux_vid_pth): + self.check_remux_space() + + command = ["ffmpeg", "-i", file, "-hide_banner", "-movflags", "+faststart"] + if file.endswith("mkv"): + command += ["-codec", "copy", "-strict", "-2"] + if file.endswith("avi"): + command += ["-c:v", "libx264", "-crf", "21", "-c:a", "aac", "-b:a", "192k", "-ac", "2"] + if file.endswith("wmv"): + command += ["-c:v", "libx264", "-crf", "23", "-c:a", "aac", "-strict", "-2", "-q:a", "100"] + if file.endswith("f4v") or file.endswith("flv"): + command += ["-vcodec", "copy"] + + command += [remux_vid_pth] + try: + proc = subprocess.Popen(command) + proc.wait() + except Exception as e: + self.logger.debug(message) + self.logger.debug(e) + return False + + return True + + + def generateVideoThumbnail(self, fullPath, hashImgPth): + try: + proc = subprocess.Popen([self.FFMPG_THUMBNLR, "-t", "65%", "-s", "300", "-c", "jpg", "-i", fullPath, "-o", hashImgPth]) + proc.wait() + except Exception as e: + self.logger.debug(repr(e)) + + + def check_remux_space(self): + limit = self.remux_folder_max_disk_usage + try: + limit = int(limit) + except Exception as e: + self.logger.debug(e) + return + + usage = self.getRemuxFolderUsage(self.REMUX_FOLDER) + if usage > limit: + files = os.listdir(self.REMUX_FOLDER) + for file in files: + fp = os.path.join(self.REMUX_FOLDER, file) + os.unlink(fp) + + + def getRemuxFolderUsage(self, start_path = "."): + total_size = 0 + for dirpath, dirnames, filenames in os.walk(start_path): + for f in filenames: + fp = os.path.join(dirpath, f) + if not os.path.islink(fp): # Skip if it is symbolic link + total_size += os.path.getsize(fp) + + return total_size diff --git a/src/shellfm/windows/Path.py b/src/shellfm/windows/Path.py new file mode 100644 index 0000000..5d46fbd --- /dev/null +++ b/src/shellfm/windows/Path.py @@ -0,0 +1,46 @@ +# Python imports +import os + +# Lib imports + +# Application imports + + +class Path: + def get_path(self): + return "/" + "/".join(self.path) + + def get_path_list(self): + return self.path + + def push_to_path(self, dir): + self.path.append(dir) + self.load_directory() + + def pop_from_path(self): + self.path.pop() + + if not self.go_past_home: + if self.get_home() not in self.get_path(): + self.set_to_home() + + self.load_directory() + + + def set_path(self, path): + self.path = list( filter(None, path.replace("\\", "/").split('/')) ) + self.load_directory() + + def set_path_with_sub_path(self, sub_path): + path = os.path.join(self.get_home(), sub_path) + self.path = list( filter(None, path.replace("\\", "/").split('/')) ) + self.load_directory() + + def set_to_home(self): + home = os.path.expanduser("~") + self.subpath + path = list( filter(None, home.replace("\\", "/").split('/')) ) + self.path = path + self.load_directory() + + def get_home(self): + return os.path.expanduser("~") + self.subpath diff --git a/src/shellfm/windows/Settings.py b/src/shellfm/windows/Settings.py new file mode 100644 index 0000000..41a4663 --- /dev/null +++ b/src/shellfm/windows/Settings.py @@ -0,0 +1,39 @@ +# System import +from os import path + + +# Lib imports + + +# Apoplication imports + + + +class Settings: + logger = None + ABS_THUMBS_PTH = None # Used for thumbnail generation and is set by passing in + REMUX_FOLDER = None # Used for Remuxed files and is set by passing in + FFMPG_THUMBNLR = None # Used for thumbnail generator binary and is set by passing in + HIDE_HIDDEN_FILES = True + lock_folder = True + go_past_home = False + + subpath = "/LazyShare" # modify 'home' folder path + locked_folders = "Synced Backup::::venv::::flasks".split("::::") + mplayer_options = "-quiet -really-quiet -xy 1600 -geometry 50%:50%".split() + music_app = "/opt/deadbeef/bin/deadbeef" + media_app = "mpv" + image_app = "mirage" + office_app = "libreoffice" + pdf_app = "evince" + text_app = "leafpad" + file_manager_app = "spacefm" + remux_folder_max_disk_usage = "8589934592" + + + fvideos = ('.mkv', '.avi', '.flv', '.mov', '.m4v', '.mpg', '.wmv', '.mpeg', '.mp4', '.webm') + foffice = ('.doc', '.docx', '.xls', '.xlsx', '.xlt', '.xltx', '.xlm', '.ppt', 'pptx', '.pps', '.ppsx', '.odt', '.rtf') + fimages = ('.png', '.jpg', '.jpeg', '.gif', '.ico', '.tga') + ftext = ('.txt', '.text', '.sh', '.cfg', '.conf') + fmusic = ('.psf', '.mp3', '.ogg', '.flac', '.m4a') + fpdf = ('.pdf') diff --git a/src/shellfm/windows/View.py b/src/shellfm/windows/View.py new file mode 100644 index 0000000..9ed9b7d --- /dev/null +++ b/src/shellfm/windows/View.py @@ -0,0 +1,159 @@ +# Python imports +import hashlib +import os +from os import listdir +from os.path import isdir, isfile, join + + +# Lib imports + + +# Application imports +from . import Path, Settings, Launcher + + +class View(Settings, Launcher, Path): + def __init__(self): + self.files = [] + self.dirs = [] + self.vids = [] + self.images = [] + self.desktop = [] + self.ungrouped = [] + + self.set_to_home() + + 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_FILES: + 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 hashText(self, text): + return hashlib.sha256(str.encode(text)).hexdigest()[:18] + + def hashSet(self, arry): + data = [] + for arr in arry: + data.append([arr, self.hashText(arr)]) + return data + + 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.hashSet(self.files), + dirs = self.hashSet(self.dirs), + videos = self.get_videos(), + images = self.hashSet(self.images), + desktops = self.hashSet(self.desktop), + ungrouped = self.hashSet(self.ungrouped) + + return { + 'path_head': self.get_path(), + 'list': { + 'files': files, + 'dirs': dirs, + 'videos': videos, + 'images': images, + 'desktops': desktops, + 'ungrouped': 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_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_dot_dots(self): + return self.hashSet(['.', '..']) + + def get_files(self): + return self.hashSet(self.files) + + def get_dirs(self): + return self.hashSet(self.dirs) + + def get_videos(self): + videos_set = self.hashSet(self.vids) + current_directory = self.get_current_directory() + for video in videos_set: + hashImgPth = join(self.ABS_THUMBS_PTH, video[1]) + ".jpg" + if not os.path.exists(hashImgPth) : + fullPath = join(current_directory, video[0]) + self.logger.debug(f"Hash Path: {hashImgPth}\nFile Path: {fullPath}") + self.generateVideoThumbnail(fullPath, hashImgPth) + + return videos_set + + def get_images(self): + return self.hashSet(self.images) + + def get_desktops(self): + return self.hashSet(self.desktop) + + def get_ungrouped(self): + return self.hashSet(self.ungrouped) diff --git a/src/shellfm/windows/Window.py b/src/shellfm/windows/Window.py new file mode 100644 index 0000000..089dc66 --- /dev/null +++ b/src/shellfm/windows/Window.py @@ -0,0 +1,22 @@ +from .import View + + +class Window: + def __init__(self): + self.name = "" + self.nickname = "" + self.id = 0 + self.views = [] + + def create_view(self): + view = View() + self.views.append(view) + + def pop_view(self): + self.views.pop() + + def delete_view(self, index): + del self.views[index] + + def get_view(self, index): + return self.views[index] diff --git a/src/shellfm/windows/WindowController.py b/src/shellfm/windows/WindowController.py new file mode 100644 index 0000000..30cc30d --- /dev/null +++ b/src/shellfm/windows/WindowController.py @@ -0,0 +1,67 @@ +from . import Window + + +class WindowController: + def __init__(self): + self.windows = [] + self.add_window() + + def get_window(self, win_id): + for window in self.windows: + if window.id == win_id: + return window + + raise("No Window by ID {} found!".format(win_id)) + + def get_windows(self): + return self.windows + + def add_window(self): + window = Window() + window.id = len(self.windows) + 1 + window.name = "window_" + str(window.id) + window.create_view() + self.windows.append(window) + + def add_view_for_window(self, win_id): + for window in self.windows: + if window.id == win_id: + window.create_view() + break + + def pop_window(self): + self.windows.pop() + + def delete_window_by_id(self, win_id): + i = 0 + for window in self.windows: + if window.id == win_id: + self.window.remove(win_id) + break + i += 1 + + 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): + for window in self.windows: + print("\n[ Window ]") + print("ID: " + str(window.id)) + print("Name: " + window.name) + print("Nickname: " + window.nickname) + print("View Count: " + str( len(window.views) )) + + + def list_views_from_window(self, win_id): + for window in self.windows: + if window.id == win_id: + for view in window.views: + print(view.files) + break + + def return_views_from_window(self, win_id): + for window in self.windows: + if window.id == win_id: + return window.views diff --git a/src/shellfm/windows/__init__.py b/src/shellfm/windows/__init__.py new file mode 100644 index 0000000..4d45198 --- /dev/null +++ b/src/shellfm/windows/__init__.py @@ -0,0 +1,6 @@ +from .Settings import Settings +from .Launcher import Launcher +from .Path import Path +from .View import View +from .Window import Window +from .WindowController import WindowController