Esempio n. 1
0
File: browser.py Progetto: kcaa/kcaa
def setup_kcaa_browser(args, root_url, to_exit):
    monitor = None
    try:
        logenv.setup_logger(args.debug, args.log_file, args.log_level, args.keep_timestamped_logs)
        kcaa_browser = open_kcaa_browser(args, root_url)
        if not kcaa_browser:
            return
        monitor = BrowserMonitor("KCAA", kcaa_browser, 3)
        while True:
            time.sleep(1.0)
            if to_exit.wait(0.0):
                logger.info("Browser KCAA got an exit signal. Shutting down.")
                break
            if not monitor.is_alive():
                # KCAA window is not vital for playing the game -- it is not
                # necessarily a signal for exiting. Rather, I would restart it
                # again, assuming that was an accident.
                monitor = BrowserMonitor("KCAA", open_kcaa_browser(args, root_url), 3)
    except (KeyboardInterrupt, SystemExit):
        logger.info("SIGINT received in the KCAA browser process. Exiting...")
    except:
        logger.error(traceback.format_exc())
    to_exit.set()
    if monitor:
        monitor.quit()
Esempio n. 2
0
File: server.py Progetto: kcaa/kcaa
def handle_server(args, controller_queue_in, controller_queue_out,
                  object_queue, to_exit):
    httpd = None
    try:
        logenv.setup_logger(args.debug, args.log_file, args.log_level,
                            args.keep_timestamped_logs)
        logger = logging.getLogger('kcaa.server')
        KCAAHTTPRequestHandler.extensions_map.update({
            '.dart': 'application/dart',
        })
        httpd, root_url = setup(args, logger)
        httpd.new_objects = set()
        httpd.objects = {}
        httpd.controller_queue_in = controller_queue_in
        httpd.controller_queue_out = controller_queue_out
        httpd.timeout = args.backend_update_interval
        controller_queue_out.put(root_url)
        try:
            assert controller_queue_in.get(timeout=300.0)
        except Queue.Empty:
            raise Exception('Controller did not initialize in 5 minutes.')
        while True:
            httpd.handle_request()
            if to_exit.wait(0.0):
                logger.info('Server got an exit signal. Shutting down.')
                break
            while not object_queue.empty():
                to_update, object_type, data = object_queue.get()
                if to_update:
                    httpd.new_objects.add(object_type)
                httpd.objects[object_type] = data
    except (KeyboardInterrupt, SystemExit):
        logger.info('SIGINT received in the server process. Exiting...')
    except:
        logger.error(traceback.format_exc())
    finally:
        controller_queue_in.close()
        controller_queue_out.close()
        if httpd:
            httpd.server_close()
    to_exit.set()
Esempio n. 3
0
File: browser.py Progetto: kcaa/kcaa
def setup_kancolle_browser(args, controller_queue_in, controller_queue_out, to_exit, browser_broken):
    monitor = None
    try:
        logenv.setup_logger(args.debug, args.log_file, args.log_level, args.keep_timestamped_logs)
        monitor = BrowserMonitor("Kancolle", open_kancolle_browser(args), 3)
        # Signals the browser is ready.
        controller_queue_out.put(True)
        game_frame, dx, dy, game_area_rect = None, None, None, None
        covered = False
        while True:
            browser = monitor.browser
            if to_exit.wait(0.0):
                logger.info("Browser Kancolle got an exit signal. Shutting " "down.")
                break
            if not monitor.is_alive():
                # If a user closes the Kancolle browser, it should be a signal
                # that the user wants to exit the game.
                break
            if game_frame:
                try:
                    command_type, command_args = controller_queue_in.get(timeout=1.0)
                    if command_type == COMMAND_CLICK:
                        x, y = command_args
                        x += dx
                        y += dy
                        actions = action_chains.ActionChains(browser)
                        actions.move_to_element_with_offset(game_frame, x, y)
                        actions.click(None)
                        if covered:
                            show_game_frame_cover(browser, False)
                            time.sleep(0.1)
                        perform_actions(actions)
                        if covered:
                            time.sleep(0.1)
                            show_game_frame_cover(browser, True)
                    elif command_type == COMMAND_CLICK_HOLD:
                        logger.debug("click hold!")
                        x, y = command_args
                        x += dx
                        y += dy
                        actions = action_chains.ActionChains(browser)
                        actions.move_to_element_with_offset(game_frame, x, y)
                        actions.click_and_hold(None)
                        if covered:
                            show_game_frame_cover(browser, False)
                            time.sleep(0.1)
                        perform_actions(actions)
                    elif command_type == COMMAND_CLICK_RELEASE:
                        logger.debug("click release!")
                        x, y = command_args
                        x += dx
                        y += dy
                        actions = action_chains.ActionChains(browser)
                        actions.move_to_element_with_offset(game_frame, x, y)
                        actions.release(None)
                        perform_actions(actions)
                        if covered:
                            time.sleep(0.1)
                            show_game_frame_cover(browser, True)
                    elif command_type == COMMAND_MOVE_MOUSE:
                        logger.debug("mouse move!")
                        x, y = command_args
                        x += dx
                        y += dy
                        actions = action_chains.ActionChains(browser)
                        actions.move_to_element_with_offset(game_frame, x, y)
                        perform_actions(actions)
                    elif command_type == COMMAND_COVER:
                        is_shown = command_args[0]
                        if is_shown != covered:
                            show_game_frame_cover(browser, is_shown)
                            covered = is_shown
                    elif command_type == COMMAND_TAKE_SCREENSHOT:
                        format, quality, width, height = command_args
                        im_buffer = None
                        response = ""
                        try:
                            im_buffer = cStringIO.StringIO(browser.get_screenshot_as_png())
                            im = Image.open(im_buffer)
                            im.load()
                            im_buffer.close()
                            im = im.crop(game_area_rect)
                            if width != 0 and height != 0:
                                im.thumbnail((width, height), Image.NEAREST)
                            im_buffer = cStringIO.StringIO()
                            if format == "jpeg":
                                im.save(im_buffer, format, quality=quality)
                            else:
                                im.save(im_buffer, format)
                            response = im_buffer.getvalue()
                        except exceptions.UnexpectedAlertPresentException as e:
                            logger.error("Unexpected alert: {}".format(e.alert_text))
                            logger.debug(str(e))
                        finally:
                            controller_queue_out.put(response)
                            if im_buffer:
                                im_buffer.close()
                    else:
                        raise ValueError(
                            "Unknown browser command: type = {}, args = {}".format(command_type, command_args)
                        )
                except Queue.Empty:
                    pass
            else:
                game_frame, dx, dy, game_area_rect = get_game_frame(browser, args.debug)
                time.sleep(1.0)
    except (KeyboardInterrupt, SystemExit):
        logger.info("SIGINT received in the Kancolle browser process. " "Exiting...")
    except exceptions.NoSuchWindowException:
        logger.error("Kancolle window seems to have been killed.")
        browser_broken.set()
        return
    except:
        logger.error(traceback.format_exc())
    finally:
        controller_queue_in.close()
        controller_queue_out.close()
        if monitor:
            monitor.quit()
    to_exit.set()
Esempio n. 4
0
def control(args, to_exit):
    # It seems that uncaught exceptions are silently buffered after creating
    # another multiprocessing.Process.
    logenv.setup_logger(args.debug, args.log_file, args.log_level,
                        args.keep_timestamped_logs)
    logger = logging.getLogger('kcaa.controller')
    state = ControllerState(logger, args, to_exit)
    try:
        state.setup()
        preferences, kcsapi_handler, manipulator_manager = (
            state.preferences, state.kcsapi_handler, state.manipulator_manager)
        last_restart = 0
        restart_wait_sec = DEFAULT_RESTART_WAIT_SEC
        while True:
            to_restart = False
            time.sleep(args.backend_update_interval)
            if to_exit.wait(0.0):
                logger.info('Controller got an exit signal. Shutting down.')
                break
            try:
                while True:
                    command_type, command_args = state.server_queue_in.get(
                        block=False)
                    # TODO: Refactor this block out to a function.
                    # Too much nested.
                    if command_type == COMMAND_REQUEST_OBJECT:
                        try:
                            requestable = kcsapi_handler.request(command_args)
                            if requestable:
                                if isinstance(requestable, str):
                                    state.server_queue_out.put(requestable)
                                else:
                                    state.server_queue_out.put(
                                        requestable.json())
                            else:
                                state.server_queue_out.put(None)
                        except:
                            logger.error(traceback.format_exc())
                            state.server_queue_out.put(None)
                    elif command_type == COMMAND_CLICK:
                        # This command is currently dead. If there is a
                        # reasonable means to get the clicked position in the
                        # client, this is supposed to feed that information to
                        # the fake client owned by the controller to better
                        # guess the current screen.
                        state.browser_queue_out.put(
                            (browser.COMMAND_CLICK, command_args))
                    elif command_type == COMMAND_RELOAD_KCSAPI:
                        kcsapi_handler.save_journals(args.journal_basedir)
                        kcsapi_handler.save_states(args.state_basedir)
                        serialized_objects = kcsapi_handler.serialize_objects()
                        reload(kcsapi_util)
                        kcsapi_util.reload_modules()
                        kcsapi_handler = kcsapi_util.KCSAPIHandler(
                            har_manager, args.journal_basedir,
                            args.state_basedir, args.debug)
                        kcsapi_handler.deserialize_objects(serialized_objects)
                        manipulator_manager.reset_objects(
                            kcsapi_handler.objects,
                            kcsapi_handler.loaded_states)
                        # TODO: Refactor!
                        preferences = kcsapi_handler.objects['Preferences']
                        manipulator_manager.preferences = preferences
                    elif command_type == COMMAND_RELOAD_MANIPULATORS:
                        reload(manipulator_util)
                        manipulator_util.reload_modules()
                        manipulator_manager = (
                            manipulator_util.ManipulatorManager(
                                state.browser_queue_out,
                                kcsapi_handler.objects,
                                kcsapi_handler.loaded_states, preferences,
                                time.time()))
                    elif command_type == COMMAND_MANIPULATE:
                        try:
                            manipulator_manager.dispatch(command_args)
                        except:
                            logger.error(traceback.format_exc())
                    elif command_type == COMMAND_SET_PREFERENCES:
                        preferences = kcsapi.prefs.Preferences.parse_text(
                            command_args[0])
                        save_preferences(args, logger, preferences)
                        # TODO: Refactor this part. Generalize the framework to
                        # update objects.
                        kcsapi_handler.update_preferences(preferences)
                        state.object_queue.put(
                            (False, 'Preferences', preferences.json()))
                        manipulator_manager.set_auto_manipulator_preferences(
                            kcsapi.prefs.AutoManipulatorPreferences(
                                enabled=preferences.automan_prefs.enabled,
                                schedules=[kcsapi.prefs.ScheduleFragment(
                                    start=sf.start, end=sf.end) for sf
                                    in preferences.automan_prefs.schedules]))
                        # TODO: Refactor this as well. Setting Preferences
                        # object should be a single operation on
                        # ManipulatorManager.
                        manipulator_manager.preferences = preferences
                    elif command_type == COMMAND_TAKE_SCREENSHOT:
                        format, quality, width, height = command_args
                        try:
                            state.browser_queue_out.put(
                                (browser.COMMAND_TAKE_SCREENSHOT,
                                 (format, quality, width, height)))
                            screenshot = state.browser_queue_in.get(
                                timeout=BROWSER_QUEUE_TIMEOUT_SEC)
                            state.server_queue_out.put(screenshot)
                        except Queue.Empty:
                            to_restart = True
                            break
                    else:
                        raise ValueError(
                            'Unknown controller command: type = {}, args = {}'
                            .format(command_type, command_args))
            except Queue.Empty:
                pass
            try:
                for obj in kcsapi_handler.get_updated_objects():
                    state.object_queue.put((True, obj.object_type, obj.json()))
                for obj in manipulator_manager.update(time.time()):
                    state.object_queue.put((True, obj.object_type, obj.json()))
            except kcsapi_util.NoResponseError:
                logger.error('Detected broken KCSAPI response. Will restart.')
                to_restart = True
            except:
                # Permit an exception in KCSAPI handler or manipulators -- it's
                # very likely a bug in how a raw response is read, or how they
                # are implemented.
                # Do not reload modules automatically because the bug should be
                # still there. You can always reload modules explicitly with
                # the reload button in the KCAA control window.
                logger.error(traceback.format_exc())
            if state.browser_broken.wait(0.0):
                logger.error('Detected broken browser. Will restart.')
                to_restart = True
            if to_restart:
                # Some unrecoverable error happened. Retry after some wait.
                # TODO: Maybe restore the currently scheduled manipulators as
                # much as possible, but it should not be critical.
                state.teardown()
                now = time.time()
                if (now - last_restart <
                        HEALTHY_DURATION_FOR_RESTART_SUCCESS_SEC):
                    logger.error('Last restart seems to have failed.')
                    restart_wait_sec *= 2
                else:
                    logger.info('Last restart seems to have succeeded.')
                    restart_wait_sec = DEFAULT_RESTART_WAIT_SEC
                logger.info(
                    'Waiting for {} seconds before restarting...'.format(
                        restart_wait_sec))
                time.sleep(restart_wait_sec)
                logger.info('Restarting the browsers.')
                state.setup()
                preferences, kcsapi_handler, manipulator_manager = (
                    state.preferences, state.kcsapi_handler,
                    state.manipulator_manager)
                last_restart = time.time()
    except (KeyboardInterrupt, SystemExit):
        logger.info('SIGINT received in the controller process. Exiting...')
    except:
        logger.error(traceback.format_exc())
    state.teardown()