Example #1
0
class Cli(cmd2.Cmd):
    def __init__(self):
        super().__init__()

        # Show this as the prompt when asking for input
        self.prompt = 'fcreplay> '

        # Used as prompt for multiline commands after the first line
        self.continuation_prompt = '... '

        self.db = Database()

    delete_failed_parser = cmd2.Cmd2ArgumentParser(
        description='Delete a failed replay')
    delete_failed_parser.add_argument('challenge_id',
                                      help='Challenge id of replay')

    delete_all_failed_parser = cmd2.Cmd2ArgumentParser(
        description='Delete all failed replays')
    delete_all_failed_parser.add_argument('-y',
                                          '--yes',
                                          action='store_true',
                                          help='Force yes')

    delete_pending_parser = cmd2.Cmd2ArgumentParser(
        description='Delete a pending replay')
    delete_pending_parser.add_argument('challenge_id',
                                       help='Challenge id of the replay')

    delete_all_pending_parser = cmd2.Cmd2ArgumentParser(
        description='Delete all pending replays')
    delete_all_pending_parser.add_argument('-y',
                                           '--yes',
                                           action='store_true',
                                           help='Force yes')

    retry_replay_parser = cmd2.Cmd2ArgumentParser(
        description='Mark a replay to be re-encoded')
    retry_replay_parser.add_argument('challenge_id',
                                     help='Challenge id of replay')

    retry_all_failed_replays_parser = cmd2.Cmd2ArgumentParser(
        description='Mark all failed replays to be re-encoded')
    retry_all_failed_replays_parser.add_argument('-y',
                                                 '--yes',
                                                 action='store_true',
                                                 help='Force yes')

    list_replays_parser = cmd2.Cmd2ArgumentParser(description='List replays')
    list_replays_parser.add_argument('type',
                                     type=str,
                                     nargs=1,
                                     choices=['failed', 'finished', 'pending'],
                                     help='Type of replays to return')
    list_replays_parser.add_argument('-l',
                                     '--limit',
                                     default=10,
                                     type=int,
                                     help='Limit number of results')

    count_parser = cmd2.Cmd2ArgumentParser(description='List replays')
    count_parser.add_argument('type',
                              type=str,
                              nargs=1,
                              choices=['failed', 'finished', 'pending', 'all'],
                              help='Type of replays to count')

    def yes_or_no(self, question):
        while "the answer is invalid":
            reply = str(input(question + ' continue? (y/n): ')).lower().strip()
            if reply[:1] == 'y':
                return True
            if reply[:1] == 'n':
                return False

    @cmd2.with_argparser(delete_failed_parser)
    def do_delete_failed(self, args):
        if self.yes_or_no(
                f"This will delete failed replay: {args.challenge_id},"):
            replay = self.db.get_single_replay(args.challenge_id)

            if replay is not None:
                if replay.failed is True:
                    self.db.delete_replay(args.challenge_id)
                    print(f"Deleated replay {args.challenge_id}")
                else:
                    print(f"Replay {args.challenge_id} isn't a faild replay")
                    return
            else:
                print(f"Replay {args.challenge_id} doesn't exist")
                return

    @cmd2.with_argparser(delete_all_failed_parser)
    def do_delete_all_failed(self, args):
        if not args.yes:
            if not self.yes_or_no("This will delete all failed replays,"):
                return

        failed_replays = self.db.get_all_failed_replays(limit=9999)

        if failed_replays is not None:
            for r in failed_replays:
                self.db.delete_replay(r.id)
                print(f"Removed replay: {r.id}")
        else:
            print("No failed replays")
            return

    @cmd2.with_argparser(delete_pending_parser)
    def do_delete_pending(self, args):
        if self.yes_or_no(
                f"This will delete the pending replay: {args.challenge_id},"):
            replay = self.db.get_single_replay(args.challenge_id)

            if replay is not None:
                if replay.failed is not True and replay.finished is not True:
                    self.db.delete_replay(replay.id)
                else:
                    print("Replay isn't a pending replay")
                    return
            else:
                print("No replay found")
                return

    @cmd2.with_argparser(delete_all_pending_parser)
    def do_delete_all_pending(self, args):
        if not args.yes:
            if not self.yes_or_no("This will delete all pending replays,"):
                return

        pending_replays = self.db.get_all_queued_replays(limit=9999)

        if pending_replays is not None:
            for r in pending_replays:
                self.db.delete_replay(r.id)
                print(f"Removed replay: {r.id}")
        else:
            print("No pending replays")
            return

    @cmd2.with_argparser(retry_replay_parser)
    def do_retry_replay(self, args):
        replay = self.db.get_single_replay(args.challenge_id)

        if replay is not None:
            self.db.rerecord_replay(args.challenge_id)
            print(f"Marked replay {args.challenge_id} to be re-encoded")
        else:
            print(f"Replay {args.challenge_id} doesn't exist")

    @cmd2.with_argparser(retry_all_failed_replays_parser)
    def do_retry_all_failed_replays(self, args):
        if not args.yes:
            if not self.yes_or_no("This will retry all failed replays,"):
                return

        failed_replays = self.db.get_all_failed_replays()
        if failed_replays is None:
            print("No failed replays to retry")
        else:
            for r in failed_replays:
                self.db.rerecord_replay(r.id)
                print(f"Marked failed replay {r.id} to be re-encoded")

    @cmd2.with_argparser(list_replays_parser)
    def do_ls(self, args):
        replays = None

        if 'failed' in args.type:
            replays = self.db.get_all_failed_replays(limit=args.limit)
        elif 'finished' in args.type:
            replays = self.db.get_all_finished_replays(limit=args.limit)
        elif 'pending' in args.type:
            replays = self.db.get_all_queued_replays(limit=args.limit)
        else:
            return

        if replays is not None:
            pp = pprint.PrettyPrinter()
            for r in replays:
                pp.pprint(r.__dict__)
        else:
            print(f"No replays found for query: {args}")

    @cmd2.with_argparser(count_parser)
    def do_count(self, args):
        replay_count = None

        if 'failed' in args.type:
            replay_count = self.db.get_failed_count()
        elif 'finished' in args.type:
            replay_count = self.db.get_finished_count()
        elif 'pending' in args.type:
            replay_count = self.db.get_pending_count()
        elif 'all' in args.type:
            replay_count = self.db.get_all_count()

        if replay_count is None:
            print("0")
        else:
            print(replay_count)
Example #2
0
class Tasker:
    def __init__(self):
        self.started_instances = {}
        self.db = Database()
        self.max_instances = 1
        self.max_fails = 5

    def check_for_replay(self):
        if self.number_of_instances() >= self.max_instances:
            print(
                f"Maximum number of instances ({self.max_instances}) reached")
            return False

        print("Looking for replay")
        player_replay = self.db.get_oldest_player_replay()
        if player_replay is not None:
            print("Found player replay")
            self.launch_fcreplay()
            return True

        replay = self.db.get_oldest_replay()
        if replay is not None:
            print("Found replay")
            self.launch_fcreplay()
            return True

        print("No replays")
        return False

    def number_of_instances(self):
        d_client = docker.from_env()
        containers = d_client.containers.list()

        instance_count = 0
        for container in containers:
            if 'fcreplay-instance-' in container.name:
                instance_count += 1

        return instance_count

    def running_instance(self, instance_hostname):
        d_client = docker.from_env()
        for i in d_client.containers.list():
            if instance_hostname in i.attrs['Config']['Hostname']:
                return True

        return False

    def remove_temp_dirs(self):
        remove_instances = []

        for docker_hostname in self.started_instances:
            if not self.running_instance(docker_hostname):
                print(
                    f"Removing '/avi_storage_temp/{self.started_instances[docker_hostname]}'"
                )
                shutil.rmtree(
                    f"/avi_storage_temp/{self.started_instances[docker_hostname]}"
                )
                remove_instances.append(docker_hostname)

        for i in remove_instances:
            del self.started_instances[i]

    def retry_failed_videos(self):
        print('Setting failed videos to retry')
        failed_replays = self.db.get_all_failed_replays(limit=1000)

        for r in failed_replays:
            if r.fail_count < self.max_fails:
                self.db.rerecord_replay(r.id)
                print(f"Marked failed replay {r.id} to be re-encoded")

    def delete_failed_videos(self):
        """Delete replays that have failed 5 times to record
        """
        failed_replays = self.db.get_all_failed_replays(limit=1000)
        for r in failed_replays:
            if r.fail_count >= self.max_fails:
                self.db.delete_replay(r.id)

    def launch_fcreplay(self):
        print("Getting docker env")
        d_client = docker.from_env()

        instance_uuid = str(uuid.uuid4().hex)

        if 'FCREPLAY_NETWORK' not in os.environ:
            os.environ['FCREPLAY_NETWORK'] = 'bridge'

        # Get fcreplay network list
        networks = os.environ['FCREPLAY_NETWORK'].split(',')

        print(
            f"Starting new instance with temp dir: '{os.environ['AVI_TEMP_DIR']}/{instance_uuid}'"
        )
        c_instance = d_client.containers.run(
            'fcreplay/image:latest',
            command='fcrecord',
            cpu_count=int(os.environ['CPUS']),
            detach=True,
            mem_limit=str(os.environ['MEMORY']),
            network=networks[0],
            remove=True,
            name=f"fcreplay-instance-{instance_uuid}",
            volumes={
                str(os.environ['CLIENT_SECRETS']): {
                    'bind': '/root/.client_secrets.json',
                    'mode': 'ro'
                },
                str(os.environ['CONFIG']): {
                    'bind': '/root/config.json',
                    'mode': 'ro'
                },
                str(os.environ['DESCRIPTION_APPEND']): {
                    'bind': '/root/description_append.txt',
                    'mode': 'ro'
                },
                str(os.environ['IA']): {
                    'bind': '/root/.ia',
                    'mode': 'ro'
                },
                str(os.environ['ROMS']): {
                    'bind': '/Fightcade/emulator/fbneo/ROMs',
                    'mode': 'ro'
                },
                str(os.environ['YOUTUBE_UPLOAD_CREDENTIALS']): {
                    'bind': '/root/.youtube-upload-credentials.json',
                    'mode': 'ro'
                },
                f"{os.environ['AVI_TEMP_DIR']}/{instance_uuid}": {
                    'bind': '/Fightcade/emulator/fbneo/avi',
                    'mode': 'rw'
                }
            })

        if len(networks) > 1:
            for n in networks[1:]:
                print(f"Adding container to network {n}")
                d_net = d_client.networks.get(n)
                d_net.connect(c_instance)

        print("Getting instance uuid")
        self.started_instances[c_instance.attrs['Config']
                               ['Hostname']] = instance_uuid

    def check_for_docker_network(self):
        d_client = docker.from_env()
        d_net = d_client.networks.list()
        networks = os.environ['FCREPLAY_NETWORK'].split(',')

        if set(networks) <= set([i.name for i in d_net]) is False:
            print(
                f"The folling networks don't exist: {set(networks) - set([i.name for i in d_net])}"
            )
            return False

        return True

    def update_video_status(self):
        """Update the status for videos uploaded to archive.org
        """
        print("Checking status for completed videos")

        # Get all replays that are completed, where video_processed is false
        to_check = self.db.get_unprocessed_replays()

        for replay in to_check:
            # Check if replay has embeded video link. Easy way to do this is to check
            # if a thumbnail is created
            print(f"Checking: {replay.id}")
            if replay.video_youtube_uploaded:
                print(
                    f"Checking url: http://img.youtube.com/vi/{replay.video_youtube_id}/0.jpg"
                )
                try:
                    r = requests.get(
                        f"http://img.youtube.com/vi/{replay.video_youtube_id}/0.jpg"
                    )
                except Exception as e:
                    print(f"Caught exception: {e}, when checking {replay.id}")
                    continue
            else:
                print(
                    f"Checking url: https://archive.org/download/{replay.id.replace('@', '-')}/__ia_thumb.jpg"
                )
                try:
                    r = requests.get(
                        f"https://archive.org/download/{replay.id.replace('@', '-')}/__ia_thumb.jpg"
                    )
                except Exception as e:
                    print(f"Caught exception: {e}, when checking {replay.id}")
                    continue

            print(f"ID: {replay.id}, Status: {r.status_code}")
            if r.status_code == 200:
                self.db.set_replay_processed(challenge_id=replay.id)

    def recorder(self, max_instances=1):
        if self.check_for_docker_network() is False:
            return False

        schedule.every(10).to(30).seconds.do(self.remove_temp_dirs)
        schedule.every(30).to(60).seconds.do(self.check_for_replay)

        self.max_instances = max_instances

        if 'MAX_INSTANCES' in os.environ:
            self.max_instances = int(os.environ['MAX_INSTANCES'])

        self.check_for_replay()

        while True:
            schedule.run_pending()
            time.sleep(1)

    def check_top_weekly(self):
        g = Getreplay()
        schedule.every(1).hour.do(g.get_top_weekly)

        g.get_top_weekly()
        while True:
            schedule.run_pending()
            time.sleep(1)

    def check_video_status(self):
        self.update_video_status()
        schedule.every(1).hour.do(self.update_video_status)
        while True:
            schedule.run_pending()
            time.sleep(1)

    def schedule_retry_failed_replays(self):
        self.retry_failed_videos()
        schedule.every(1).hour.do(self.retry_failed_videos)
        while True:
            schedule.run_pending()
            time.sleep(1)

    def schedule_delete_failed_replays(self):
        self.delete_failed_videos()
        schedule.every(1).hour.do(self.delete_failed_videos)
        while True:
            schedule.run_pending()
            time.sleep(1)