From 913e6bc842ee7980401439868bd01d4ec091c054 Mon Sep 17 00:00:00 2001 From: Arjix <53124886+ArjixWasTaken@users.noreply.github.com> Date: Sat, 16 Oct 2021 14:03:07 +0300 Subject: [PATCH] Prevents modifying the config if its already being modified. Co-authored-by: Arjix <53124886+ArjixGamer@users.noreply.github.com> --- anime_downloader/commands/config.py | 18 ++++--- anime_downloader/commands/dl.py | 11 ++-- anime_downloader/commands/ezdl.py | 39 +++++++++----- anime_downloader/commands/watch.py | 30 ++++++++--- anime_downloader/util.py | 81 +++++++++++++++++++++++++++++ 5 files changed, 149 insertions(+), 30 deletions(-) diff --git a/anime_downloader/commands/config.py b/anime_downloader/commands/config.py index 9e12faf..bfc41a3 100644 --- a/anime_downloader/commands/config.py +++ b/anime_downloader/commands/config.py @@ -1,7 +1,11 @@ -import click +from anime_downloader.util import is_running +if is_running(regex=r'python|anime|config', expected_matches=3): + raise Exception('Another instance of "anime config" is already running!') + import os +import click from tabulate import tabulate -from anime_downloader.config import APP_DIR, Config +from anime_downloader.config import APP_DIR, Config # noqa data = Config._CONFIG @@ -20,7 +24,7 @@ def traverse_json(data, previous=''): click.echo(create_table(keys, previous)) val = click.prompt("Select Option", type=int, default=1) - 1 - if type(data[keys[val]]) == dict: + if isinstance(data[keys[val]], dict): traverse_json(data[keys[val]], keys[val]) else: click.echo(f"Current value: {data[keys[val]]}") @@ -32,8 +36,9 @@ def traverse_json(data, previous=''): except (SyntaxError, NameError) as e: pass - if type(newVal) != type(data[keys[val]]): - choice = click.confirm(f"{newVal} appears to be of an incorrect type. Continue") + if not isinstance(newVal, type(data[keys[val]])): + choice = click.confirm( + f"{newVal} appears to be of an incorrect type. Continue") if not choice: exit() @@ -41,7 +46,8 @@ def traverse_json(data, previous=''): try: newVal = type(data[keys[val]])(newVal) except TypeError: - click.echo(f"'{newVal}' could not be converted to the correct type") + click.echo( + f"'{newVal}' could not be converted to the correct type") exit() data[keys[val]] = newVal diff --git a/anime_downloader/commands/dl.py b/anime_downloader/commands/dl.py index ca831f2..d6e5eab 100644 --- a/anime_downloader/commands/dl.py +++ b/anime_downloader/commands/dl.py @@ -73,7 +73,8 @@ sitenames = [v[1] for v in ALL_ANIME_SITES] '--choice', '-c', type=int, help='Choice to start downloading given anime number ' ) -@click.option("--skip-fillers", is_flag=True, help="Skip downloading of fillers.") +@click.option("--skip-fillers", is_flag=True, + help="Skip downloading of fillers.") @click.option( "--speed-limit", type=str, @@ -149,16 +150,18 @@ def command(ctx, anime_url, episode_range, url, player, skip_download, quality, if skip_fillers and fillers: if episode.ep_no in fillers: logger.info( - "Skipping episode {} because it is a filler.".format(episode.ep_no)) + "Skipping episode {} because it is a filler.".format( + episode.ep_no)) continue if url: util.print_episodeurl(episode) if player: - episode_range = f"0:{len(animes)}" if not episode_range else episode_range util.play_episode( - episode, player=player, title=f'{anime.title} - Episode {episode.ep_no}', episodes=episode_range) + episode, + player=player, + title=f'{anime.title} - Episode {episode.ep_no}') if not skip_download: if external_downloader: diff --git a/anime_downloader/commands/ezdl.py b/anime_downloader/commands/ezdl.py index a95ec56..613b848 100644 --- a/anime_downloader/commands/ezdl.py +++ b/anime_downloader/commands/ezdl.py @@ -50,7 +50,8 @@ sitenames = [v[1] for v in ALL_ANIME_SITES] @click.option( '--download-metadata', '-dm', is_flag=True, help='Download additional metadata') -@click.option("--skip-fillers", is_flag=True, help="Skip downloading of fillers.") +@click.option("--skip-fillers", is_flag=True, + help="Skip downloading of fillers.") @click.pass_context def command(ctx, anime_url, episode_range, player, force_download, provider, @@ -94,11 +95,13 @@ def command(ctx, anime_url, episode_range, player, raise exceptions.NotFoundError('No episodes found within index.') # Stores the choices for each provider, to prevent re-prompting search. - # As the current setup runs episode wise without this a 12 episode series would give 12+ prompts. + # As the current setup runs episode wise without this a 12 episode series + # would give 12+ prompts. choice_dict = {} # Doesn't work on nyaa since it only returns one episode. - for episode_range in range(int(episode_range_split[0]), int(episode_range_split[-1]) + 1): + for episode_range in range(int(episode_range_split[0]), int( + episode_range_split[-1]) + 1): # Exits if all providers are skipped. if [choice_dict[i] for i in choice_dict] == [0] * len(providers): logger.info('All providers skipped, exiting') @@ -115,7 +118,8 @@ def command(ctx, anime_url, episode_range, player, # To make the downloads use the correct name if URL:s are used. real_provider = cls.sitename if cls else provider - # This will allow for animeinfo metadata in filename and one filename for multiple providers. + # This will allow for animeinfo metadata in filename and one + # filename for multiple providers. rep_dict = { 'animeinfo_anime_title': util.slugify(info.title), 'provider': util.slugify(real_provider), @@ -128,11 +132,13 @@ def command(ctx, anime_url, episode_range, player, disable_ssl = False session.get_session().verify = not disable_ssl - # This is just to make choices in providers presistent between searches. + # This is just to make choices in providers presistent between + # searches. choice_provider = choice_dict.get(provider) if not cls: - _anime_url, choice_provider = util.search(anime_url, provider, val=choice_provider, season_info=info, ratio=ratio) + _anime_url, choice_provider = util.search( + anime_url, provider, val=choice_provider, season_info=info, ratio=ratio) choice_dict[provider] = choice_provider if choice_provider == 0 or not _anime_url: logger.info('Skipped') @@ -145,7 +151,7 @@ def command(ctx, anime_url, episode_range, player, fallback_qualities=fallback_qualities) # I have yet to investigate all errors this can output # No sources found gives error which exits the script - except: + except BaseException: continue logger.debug('Found anime: {}'.format(anime.title)) @@ -153,7 +159,8 @@ def command(ctx, anime_url, episode_range, player, try: animes = util.parse_ep_str(anime, str(episode_range)) except RuntimeError: - logger.error('No episode found with index {}'.format(episode_range)) + logger.error( + 'No episode found with index {}'.format(episode_range)) continue except: logger.error('Unknown provider error') @@ -167,23 +174,31 @@ def command(ctx, anime_url, episode_range, player, skip_download = True if download_dir and not skip_download: - logger.info('Downloading to {}'.format(os.path.abspath(download_dir))) + logger.info( + 'Downloading to {}'.format( + os.path.abspath(download_dir))) if skip_fillers: fillers = util.get_filler_episodes(query) for episode in animes: if skip_fillers and fillers: if episode.ep_no in fillers: - logger.info("Skipping episode {} because it is a filler.".format(episode.ep_no)) + logger.info( + "Skipping episode {} because it is a filler.".format( + episode.ep_no)) continue if download_metadata: - util.download_metadata(fixed_file_format, info.metadata, episode) + util.download_metadata( + fixed_file_format, info.metadata, episode) if url: util.print_episodeurl(episode) if player: - util.play_episode(episode, player=player, title=f'{anime.title} - Episode {episode.ep_no}') + util.play_episode( + episode, + player=player, + title=f'{anime.title} - Episode {episode.ep_no}') if not skip_download: if external_downloader: diff --git a/anime_downloader/commands/watch.py b/anime_downloader/commands/watch.py index 9ca7bc8..04fc616 100644 --- a/anime_downloader/commands/watch.py +++ b/anime_downloader/commands/watch.py @@ -1,3 +1,8 @@ +from anime_downloader.util import is_running +if is_running(regex=r'python|anime|watch', expected_matches=3): + raise Exception('Another instance of "anime watch" is already running!') + + import click import logging import sys @@ -90,17 +95,21 @@ def command(anime_name, new, update_all, _list, quality, remove, watcher.update_anime(anime) if mal_import: - PATH = anime_name # Hack, but needed to prompt the user. Uses the anime name as parameter. + # Hack, but needed to prompt the user. Uses the anime name as + # parameter. + PATH = anime_name if PATH: query = PATH else: - query = click.prompt('Enter the file path for the MAL .xml file', type=str) + query = click.prompt( + 'Enter the file path for the MAL .xml file', type=str) if query.endswith('.xml'): watcher._import_from_MAL(query) sys.exit(0) else: - logging.error("Either the file selected was not an .xml or no file was selected.") + logging.error( + "Either the file selected was not an .xml or no file was selected.") sys.exit(1) # Defaults the command to anime watch -l all. @@ -128,13 +137,15 @@ def command(anime_name, new, update_all, _list, quality, remove, def command_parser(command): # Returns a list of the commands - # new "no neverland" --provider vidstream > ['new', '--provider', 'no neverland', 'vidstream'] + # new "no neverland" --provider vidstream > ['new', '--provider', 'no + # neverland', 'vidstream'] # Better than split(' ') because it accounts for quoutes. # Group 3 for quoted command command_regex = r'(("|\')(.*?)("|\')|.*?\s)' matches = re.findall(command_regex, command + " ") - commands = [i[0].strip('"').strip("'").strip() for i in matches if i[0].strip()] + commands = [i[0].strip('"').strip("'").strip() + for i in matches if i[0].strip()] return commands @@ -159,8 +170,10 @@ def list_animes(watcher, quality, download_dir, imp=None, _filter=None): watcher.new(url) if key == 'swap': - if vals[0] in ['all', 'watching', 'completed', 'planned', 'dropped', 'hold']: - return list_animes(watcher, quality, download_dir, imp=imp, _filter=vals[0]) + if vals[0] in ['all', 'watching', 'completed', + 'planned', 'dropped', 'hold']: + return list_animes( + watcher, quality, download_dir, imp=imp, _filter=vals[0]) return list_animes(watcher, quality, download_dir, imp=imp) else: @@ -272,7 +285,8 @@ def list_animes(watcher, quality, download_dir, imp=None, _filter=None): watcher.update(anime) elif key == 'watch_status': - if val in ['watching', 'completed', 'dropped', 'planned', 'all']: + if val in ['watching', 'completed', + 'dropped', 'planned', 'all']: colours = { 'watching': 'cyan', 'completed': 'green', diff --git a/anime_downloader/util.py b/anime_downloader/util.py index d60e932..4a3a49c 100644 --- a/anime_downloader/util.py +++ b/anime_downloader/util.py @@ -493,3 +493,84 @@ class ClickListOption(click.Option): return ast.literal_eval(value) except: raise click.BadParameter(value) + + +class Process: + def __init__(self, name, cmdline, pid): + self.name = name + self.pid = pid + self.cmdline = cmdline + + def __str__(self): + return str({ + 'name': self.name, + 'pid': self.pid, + 'cmdline': self.cmdline + }) + + +def getAllProcesses_Win32(): + placeholder = list() + out = os.popen('WMIC path win32_process get Caption,Processid,Commandline').read( + ).split('\n')[::2][1:] + for line in out: + f = line.split() + if f: + if len(f) > 2: + placeholder.append( + Process(name=f[0], cmdline=f[1:-1], pid=int(f[-1]))) + else: + placeholder.append( + Process(name=f[0], cmdline=None, pid=int(f[-1]))) + return placeholder + + +def getAllProcesses_unix(): + if sys.platform.startswith('darwin'): + cmd = 'ps -Ao user,pid,%cpu,%mem,vsz,rss,tt,stat,start,time,command' + return [] + elif sys.startswith('linux'): + cmd = 'ps aux' + out = os.popen(cmd).read() + out = out.split('\n')[1:] + placeholder = list() + for line in out: + try: + line_list = line.lower().split() + PID = line_list[1] + NAME = line_list[10:][0] + CMD = line_list[10:] + placeholder.append(Process(name=NAME, cmdline=CMD, pid=PID)) + except IndexError: + continue + return placeholder + + +def get_all_processes(): + if sys.platform.startswith('win'): + return getAllProcesses_Win32() + else: + return getAllProcesses_unix() + + +def is_running(regex, expected_matches): + """ + Iterates through all the processes that are running + and returns a boolean if a process matches the regex passed + and the groups matched are equal to or more than the expected_matches. + """ + + already_running = False + dict_pids = { + p.pid: [p.name, p.cmdline] + for p in get_all_processes() + } + + if os.getpid() in dict_pids: + del dict_pids[os.getpid()] + for key, value in dict_pids.items(): + if value[1]: + list_of_matches = re.findall(regex, ' '.join(value[1])) + if list_of_matches and len(list_of_matches) >= expected_matches: + already_running = True + return already_running