diff --git a/README.md b/README.md index 536c467..a5943e6 100644 --- a/README.md +++ b/README.md @@ -190,7 +190,6 @@ Alternatively, you can delete images from this folder and it will not break the $ sudo apt-get update $ sudo apt install terminology ``` -* If you get the error `39:46: syntax error: Expected end of line but found identifier. (-2741)`: Locate the file `ITerm.py` in `pokemonterminal/terminal/adapters` and on line 9, change `iTerm` to `iTerm2`. If you still experience the error, try changing it to `iTerm 2`. ## Saving diff --git a/pokemonterminal/__init__.py b/pokemonterminal/__init__.py index 9c9adc5..1dfdde4 100644 --- a/pokemonterminal/__init__.py +++ b/pokemonterminal/__init__.py @@ -9,5 +9,5 @@ """ -__version__ = "1.1.0" +__version__ = "1.2.0" __author__ = "LazoCoder" diff --git a/pokemonterminal/main.py b/pokemonterminal/main.py index fa99fbb..ca720e9 100644 --- a/pokemonterminal/main.py +++ b/pokemonterminal/main.py @@ -80,6 +80,9 @@ def main(argv=None): return if is_slideshow and options.id <= 0 and size > 1: + if options.slideshow <= 0: + print("Time has to be greater than 0. You can use decimal values.") + return if event_exists: print("One or more slideshows is already running.\n") while True: @@ -97,11 +100,12 @@ def main(argv=None): return else: print("Not a valid option!\n") - if options.slideshow <= 0: - print("Time has to be greater then 0. You can use decimal values.") - return target_func = scripter.change_wallpaper if options.wallpaper else scripter.change_terminal - slideshow.start(Filter.filtered_list, options.slideshow, target_func, event_name) + print(f"Starting slideshow with {len(Filter.filtered_list)} Pokemons and a delay of {options.slideshow} minutes.") + pid = slideshow.start(Filter.filtered_list, options.slideshow, target_func, event_name) + print(f"Forked process to background with PID {pid}.") + print("You can stop it with 'pokemon {}'.".format('-c -w' if options.wallpaper else '-c')) + return if options.wallpaper: scripter.change_wallpaper(target.get_path()) diff --git a/pokemonterminal/platform/named_event/posix.py b/pokemonterminal/platform/named_event/posix.py index 9cd1536..b789492 100644 --- a/pokemonterminal/platform/named_event/posix.py +++ b/pokemonterminal/platform/named_event/posix.py @@ -1,35 +1,58 @@ +import errno import os +import psutil +import stat import sys import time from . import NamedEvent +from unittest.mock import patch from pathlib import PosixPath +def _isfifo_strict(path): + # https://github.com/giampaolo/psutil/blob/release-5.4.6/psutil/_common.py#L362 + try: + st = os.stat(path) + except OSError as err: + if err.errno in (errno.EPERM, errno.EACCES): + raise + return False + else: + return stat.S_ISFIFO(st.st_mode) + class PosixNamedEvent(NamedEvent): """ A wrapper for named events using a FIFO (named pipe) """ - __self_references = ['self', str(os.getpid()), 'thread-self'] - @staticmethod def __build_fifo_path(name: str) -> PosixPath: - return PosixPath('/') / 'run' / 'user' / str(os.getuid()) / 'pokemon-terminal' / name + return PosixPath(f'/tmp/{name}/{os.getuid()}') @staticmethod - def __generate_handle_list() -> [PosixPath]: - for p in PosixPath('/proc').glob('*/fd/*'): - if not any(p.parts[2] == s for s in PosixNamedEvent.__self_references): - yield p.resolve() + def __has_open_file_handles_real(path: PosixPath) -> bool: + for proc in psutil.process_iter(): + try: + if proc.pid != os.getpid(): + for file in proc.open_files(): + if PosixPath(file.path).samefile(path): + return True + except psutil.Error: + continue + return False @staticmethod def __has_open_file_handles(path: PosixPath) -> bool: - if sys.platform != 'darwin': - realpath = path.resolve() - return any(realpath == p for p in PosixNamedEvent.__generate_handle_list()) - else: - # TODO - raise NotImplementedError("macOS doesn't have /proc") + # HACK psutil explicitely filters out FIFOs from open_files() + # HACK patch the function responsible of it so it does the reverse instead + # HACK (only enumerate FIFOs in open_files) + try: + with patch("psutil._psplatform.isfile_strict", _isfifo_strict): + return PosixNamedEvent.__has_open_file_handles_real(path) + except: + # Something happened(tm), or the platform doesn't uses isfile_strict (ex: BSD). + # Do a best effort. + return PosixNamedEvent.__has_open_file_handles_real(path) def exists(name: str) -> bool: p = PosixNamedEvent.__build_fifo_path(name) diff --git a/pokemonterminal/scripter.py b/pokemonterminal/scripter.py index 3c7de73..27244bc 100644 --- a/pokemonterminal/scripter.py +++ b/pokemonterminal/scripter.py @@ -16,7 +16,7 @@ def __init_terminal_provider(): if len(providers) > 1: # All this if is really not supposed to happen at all whatsoever # really what kind of person has 2 simultaneous T.E??? - print("Multiple providers found select the appropriate one:") + print("Multiple providers found, please select the appropriate one.") for i, x in enumerate(providers): print(f'{i}. {x.__str__()}') print("If some of these make no sense or are irrelevant please file " + @@ -46,7 +46,7 @@ def __init_wallpaper_provider(): if len(providers) > 1: # All this if is really not supposed to happen at all whatsoever # really what kind of person has 2 simultaneous D.E??? - print("Multiple providers found select the appropriate one:") + print("Multiple providers found, please select the appropriate one.") for i, x in enumerate(providers): print(f'{i}. {x.__str__()}') print("If some of these make no sense or are irrelevant please file " + diff --git a/pokemonterminal/slideshow.py b/pokemonterminal/slideshow.py index 2376341..e0c295e 100644 --- a/pokemonterminal/slideshow.py +++ b/pokemonterminal/slideshow.py @@ -6,11 +6,6 @@ from .platform import PlatformNamedEvent from threading import Thread -def __print_fork(pid, length, delay): - print(f"Starting slideshow with {length} Pokemons and a delay of {delay} minutes.") - print(f"Forked process to background with PID {pid}.") - print("You can stop it with 'pokemon -c'. (add '-w' if this is a wallpaper slideshow)") - def __event_listener(event): event.wait() @@ -36,10 +31,9 @@ def __slideshow_worker(filtered, delay, changer_func, event_name): def start(filtered, delay, changer_func, event_name): p = multiprocessing.Process(target=__slideshow_worker, args=(filtered, delay, changer_func, event_name, ), daemon=True) p.start() - __print_fork(p.pid, len(filtered), delay) # HACK remove multiprocessing's exit handler to prevent it killing our child. atexit.unregister(multiprocessing.util._exit_function) - sys.exit(0) + return p.pid def stop(event_name): with PlatformNamedEvent(event_name) as e: diff --git a/pokemonterminal/terminal/__init__.py b/pokemonterminal/terminal/__init__.py index 1b6c41a..5fc94a5 100644 --- a/pokemonterminal/terminal/__init__.py +++ b/pokemonterminal/terminal/__init__.py @@ -1,6 +1,7 @@ -import os import importlib import inspect +import pathlib + from .adapters import TerminalProvider @@ -16,17 +17,12 @@ def _get_adapter_classes() -> [TerminalProvider]: all the implementing wallpaper adapter classes thanks for/adapted from https://github.com/cclauss/adapter_pattern/ """ - dirname = os.path.join(os.path.dirname( - os.path.abspath(__file__)), 'adapters') - adapter_classes = [] - for file_name in sorted(os.listdir(dirname)): - root, ext = os.path.splitext(file_name) - if ext.lower() == '.py' and not root.startswith('__'): - module = importlib.import_module( - '.' + root, 'pokemonterminal.terminal.adapters') + adapter_dir = pathlib.Path(__file__).resolve().parent / 'adapters' + for file in adapter_dir.iterdir(): + if file.suffix.lower() == '.py' and not file.name.startswith('__'): + module = importlib.import_module('.' + file.name.split('.')[0], 'pokemonterminal.terminal.adapters') for _, c in inspect.getmembers(module, _is_adapter): - adapter_classes.append(c) - return adapter_classes + yield c def get_current_terminal_adapters() -> [TerminalProvider]: diff --git a/pokemonterminal/wallpaper/__init__.py b/pokemonterminal/wallpaper/__init__.py index 17da7fc..a0f6f13 100644 --- a/pokemonterminal/wallpaper/__init__.py +++ b/pokemonterminal/wallpaper/__init__.py @@ -1,6 +1,7 @@ -import os import importlib import inspect +import pathlib + from .adapters import WallpaperProvider @@ -16,17 +17,12 @@ def _get_adapter_classes() -> [WallpaperProvider]: all the implementing wallpaper adapter classes thanks for/adapted from https://github.com/cclauss/adapter_pattern/ """ - dirname = os.path.join(os.path.dirname( - os.path.abspath(__file__)), 'adapters') - adapter_classes = [] - for file_name in sorted(os.listdir(dirname)): - root, ext = os.path.splitext(file_name) - if ext.lower() == '.py' and not root.startswith('__'): - module = importlib.import_module( - '.' + root, 'pokemonterminal.wallpaper.adapters') + adapter_dir = pathlib.Path(__file__).resolve().parent / 'adapters' + for file in adapter_dir.iterdir(): + if file.suffix.lower() == '.py' and not file.name.startswith('__'): + module = importlib.import_module('.' + file.name.split('.')[0], 'pokemonterminal.wallpaper.adapters') for _, c in inspect.getmembers(module, _is_adapter): - adapter_classes.append(c) - return adapter_classes + yield c def get_current_wallpaper_adapters() -> [WallpaperProvider]: diff --git a/setup.py b/setup.py index 6ee127f..8272d73 100755 --- a/setup.py +++ b/setup.py @@ -73,5 +73,6 @@ def package_data(relpath, folders): "Programming Language :: Python :: 3.6" ], - python_requires=">=3.6" + python_requires=">=3.6", + install_requires=(["psutil"] if sys.platform != "win32" else None) ) diff --git a/tests/test_terminal.py b/tests/test_terminal.py index 95fb98f..45dc449 100644 --- a/tests/test_terminal.py +++ b/tests/test_terminal.py @@ -4,7 +4,7 @@ def test_terminal_adapter_classes(): - all_adapter = _get_adapter_classes() + all_adapter = list(_get_adapter_classes()) files = {__inspct.getfile(x) for x in all_adapter} print('all adapter classes:\n', files) assert len(all_adapter) >= len(files), \ diff --git a/tests/test_wallpaper.py b/tests/test_wallpaper.py index e26072b..9386fc8 100644 --- a/tests/test_wallpaper.py +++ b/tests/test_wallpaper.py @@ -4,7 +4,7 @@ def test_wallpaper_adapter_classes(): - all_adapter = _get_adapter_classes() + all_adapter = list(_get_adapter_classes()) files = {__inspct.getfile(x) for x in all_adapter} print('all adapter classes:\n', files) assert len(all_adapter) >= len(files), \