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()
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()
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()
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()