From 09b20902aae83a096067a77d33068b995901229d Mon Sep 17 00:00:00 2001 From: "Hong Jen Yee (PCMan)" Date: Mon, 28 Nov 2016 00:52:02 +0800 Subject: [PATCH] Do not try to close browser window from within the chewing config tool. (It's not supported by some browsers, such as Firefox, due to security reasons.) Refactor the config_tool.py to avoid using unnecessary global variables. Keep the chewing config tool server alive when there is a request. --- python/input_methods/chewing/config_tool.html | 3 +- python/input_methods/chewing/config_tool.py | 176 +++++++++--------- python/input_methods/chewing/js/config.js | 24 +-- .../input_methods/chewing/js/user_phrase.js | 14 +- 4 files changed, 98 insertions(+), 119 deletions(-) diff --git a/python/input_methods/chewing/config_tool.html b/python/input_methods/chewing/config_tool.html index f282b4a51..79742d382 100644 --- a/python/input_methods/chewing/config_tool.html +++ b/python/input_methods/chewing/config_tool.html @@ -123,8 +123,7 @@
- - +
diff --git a/python/input_methods/chewing/config_tool.py b/python/input_methods/chewing/config_tool.py index d289f91da..08be2c0a4 100644 --- a/python/input_methods/chewing/config_tool.py +++ b/python/input_methods/chewing/config_tool.py @@ -28,49 +28,40 @@ from ctypes import c_uint, byref, create_string_buffer -configDir = os.path.join(os.path.expandvars("%APPDATA%"), "PIME", "chewing") -currentDir = os.path.dirname(__file__) -dataDir = os.path.join(currentDir, "data") -localDataDir = os.path.join(os.path.expandvars("%LOCALAPPDATA%"), "PIME", "chewing") +config_dir = os.path.join(os.path.expandvars("%APPDATA%"), "PIME", "chewing") +current_dir = os.path.dirname(__file__) +data_dir = os.path.join(current_dir, "data") +localdata_dir = os.path.join(os.path.expandvars("%LOCALAPPDATA%"), "PIME", "chewing") COOKIE_ID = "chewing_config_token" SERVER_TIMEOUT = 120 -timeout_handler = None # syspath 參數可包含多個路徑,用 ; 分隔 # 此處把 user 設定檔目錄插入到 system-wide 資料檔路徑前 # 如此使用者變更設定後,可以比系統預設值有優先權 -search_paths = ";".join((chewingConfig.getConfigDir(), dataDir)).encode("UTF-8") +search_paths = ";".join((chewingConfig.getConfigDir(), data_dir)).encode("UTF-8") user_phrase = chewingConfig.getUserPhrase().encode("UTF-8") -print(search_paths, user_phrase) +# print(search_paths, user_phrase) chewing_ctx = ChewingContext(syspath = search_paths, userpath = user_phrase) # new libchewing context -def quit_server(): - # terminate the server process - tornado.ioloop.IOLoop.current().close() - sys.exit(0) - - class BaseHandler(tornado.web.RequestHandler): def get_current_user(self): # override the login check return self.get_cookie(COOKIE_ID) + def prepare(self): # called before every request + self.application.reset_timeout() # reset the quit server timeout -class KeepAliveHandler(BaseHandler): +class KeepAliveHandler(BaseHandler): @tornado.web.authenticated def get(self): - global timeout_handler - loop = tornado.ioloop.IOLoop.current() - if timeout_handler: - loop.remove_timeout(timeout_handler) - timeout_handler = loop.call_later(SERVER_TIMEOUT, quit_server) - self.write("ok") + # the actual keep-alive is done inside BaseHandler.prepare() + self.write('{"return":true}') class ConfigHandler(BaseHandler): @@ -92,7 +83,7 @@ def post(self): # save config data = tornado.escape.json_decode(self.request.body) print(data) # ensure the config dir exists - os.makedirs(configDir, exist_ok=True) + os.makedirs(config_dir, exist_ok=True) # write the config to files config = data.get("config", None) if config: @@ -103,18 +94,12 @@ def post(self): # save config swkb = data.get("swkb", None) if swkb: self.save_file("swkb.dat", swkb) - self.write("ok") - - @tornado.web.authenticated - def delete(self): # quit - loop = tornado.ioloop.IOLoop.current() - loop.call_later(0, quit_server) - self.write("ok") + self.write('{"return":true}') def load_config(self): config = chewingConfig.toJson() # the default settings try: - with open(os.path.join(configDir, "config.json"), "r", encoding="UTF-8") as f: + with open(os.path.join(config_dir, "config.json"), "r", encoding="UTF-8") as f: # override default values with user config config.update(json.load(f)) except Exception as e: @@ -123,18 +108,18 @@ def load_config(self): def load_data(self, name): try: - userFile = os.path.join(configDir, name) + userFile = os.path.join(config_dir, name) with open(userFile, "r", encoding="UTF-8") as f: return f.read() except FileNotFoundError: - with open(os.path.join(dataDir, name), "r", encoding="UTF-8") as f: + with open(os.path.join(data_dir, name), "r", encoding="UTF-8") as f: return f.read() except Exception: return "" def save_file(self, filename, data): try: - with open(os.path.join(configDir, filename), "w", encoding="UTF-8") as f: + with open(os.path.join(config_dir, filename), "w", encoding="UTF-8") as f: f.write(data) if file == "symbols": f.write("\n") @@ -193,65 +178,84 @@ def post(self, page_name): self.redirect("/{}.html".format(page_name)) -def launch_browser(port, page_name, access_token): - user_html = """ -
- -
- -""".format(PORT=port, PAGE_NAME=page_name, TOKEN=access_token) - # use a local html file to send access token to our service via http POST for authentication. - os.makedirs(localDataDir, exist_ok=True) - filename = os.path.join(localDataDir, "launch_{}.html".format(page_name)) - with open(filename, "w") as f: - f.write(user_html) - os.startfile(filename) +class ConfigApp(tornado.web.Application): -def main(): - # generate a new auth token using UUID - access_token = uuid.uuid4().hex - settings = { - "access_token": access_token, # our custom setting - "login_url": "/version", - "debug": True - } - - app = tornado.web.Application([ - (r"/(.*\.html)", tornado.web.StaticFileHandler, {"path": currentDir}), - (r"/((css|images|js)/.*)", tornado.web.StaticFileHandler, {"path": currentDir}), - (r"/(version.txt)", tornado.web.StaticFileHandler, {"path": os.path.relpath("../../../", currentDir)}), - (r"/config", ConfigHandler), # main configuration handler - (r"/user_phrases", UserPhraseHandler), # user phrase editor - (r"/keep_alive", KeepAliveHandler), # keep the api server alive - (r"/login/(.*)", LoginHandler), # authentication - ], **settings) - - # find a port number that's available - random.seed() - while True: - port = random.randint(1025, 65535) - try: - app.listen(port, "127.0.0.1") - break - except OSError: # it's possible that the port we want to use is already in use - continue + def __init__(self): + # generate a new auth token using UUID + self.access_token = uuid.uuid4().hex + settings = { + "access_token": self.access_token, # our custom setting + "login_url": "/version", + "debug": True + } + handlers = [ + (r"/(.*\.html)", tornado.web.StaticFileHandler, {"path": current_dir}), + (r"/((css|images|js)/.*)", tornado.web.StaticFileHandler, {"path": current_dir}), + (r"/(version.txt)", tornado.web.StaticFileHandler, {"path": os.path.relpath("../../../", current_dir)}), + (r"/config", ConfigHandler), # main configuration handler + (r"/user_phrases", UserPhraseHandler), # user phrase editor + (r"/keep_alive", KeepAliveHandler), # keep the api server alive + (r"/login/(.*)", LoginHandler), # authentication + ] + super().__init__(handlers, **settings) + self.timeout_handler = None + self.port = 0 + + def launch_browser(self, tool_name): + user_html = """ +
+ +
+ + """.format(PORT=self.port, PAGE_NAME=tool_name, TOKEN=self.access_token) + # use a local html file to send access token to our service via http POST for authentication. + os.makedirs(localdata_dir, exist_ok=True) + filename = os.path.join(localdata_dir, "launch_{}.html".format(tool_name)) + with open(filename, "w") as f: + f.write(user_html) + os.startfile(filename) + + def run(self, tool_name): + # find a port number that's available + random.seed() + while True: + port = random.randint(1025, 65535) + try: + self.listen(port, "127.0.0.1") + break + except OSError: # it's possible that the port we want to use is already in use + continue + self.port = port + + self.launch_browser(tool_name) + + # setup the main event loop + loop = tornado.ioloop.IOLoop.current() + self.timeout_handler = loop.call_later(SERVER_TIMEOUT, self.quit) + loop.start() - # launch web browser - if len(sys.argv) >= 2 and sys.argv[1] == "user_phrase_editor": - page_name = "user_phrase_editor" - else: - page_name = "config_tool" + def reset_timeout(self): + loop = tornado.ioloop.IOLoop.current() + if self.timeout_handler: + loop.remove_timeout(self.timeout_handler) + self.timeout_handler = loop.call_later(SERVER_TIMEOUT, self.quit) - launch_browser(port, page_name, access_token) + def quit(self): + # terminate the server process + tornado.ioloop.IOLoop.current().close() + sys.exit(0) - # setup the main event loop - loop = tornado.ioloop.IOLoop.current() - global timeout_handler - timeout_handler = loop.call_later(SERVER_TIMEOUT, quit_server) - loop.start() + +def main(): + app = ConfigApp() + if len(sys.argv) >= 2 and sys.argv[1] == "user_phrase_editor": + tool_name = "user_phrase_editor" + else: + tool_name = "config_tool" + app.run(tool_name) if __name__ == "__main__": diff --git a/python/input_methods/chewing/js/config.js b/python/input_methods/chewing/js/config.js index c9b600554..af3a449b1 100644 --- a/python/input_methods/chewing/js/config.js +++ b/python/input_methods/chewing/js/config.js @@ -167,16 +167,6 @@ function initializeUI() { }); } -function closeWindow() { - $.ajax({ - url: "/config", - type: "DELETE", - success: function() { - window.close(); - } - }); -} - // jQuery ready $(function() { // workaround the same origin policy of IE. @@ -208,24 +198,20 @@ $(function() { // OK button $("#ok").click(function() { updateConfig(); // update the config based on the state of UI elements - saveConfig(closeWindow); - return false; - }); - - // Cancel button - $("#cancel").click(function() { - closeWindow(); + saveConfig(function() { + alert("設定已儲存!"); + }); return false; }); // load configurations and update the UI accordingly loadConfig(); - // keep the server alive every 30 second + // keep the server alive every 20 second window.setInterval(function() { $.ajax({ url: "/keep_alive", cache: false // needs to turn off cache. otherwise the server will be requested only once. }); - }, 5 * 1000); + }, 20 * 1000); }); diff --git a/python/input_methods/chewing/js/user_phrase.js b/python/input_methods/chewing/js/user_phrase.js index c1d53d418..bb9470fd6 100644 --- a/python/input_methods/chewing/js/user_phrase.js +++ b/python/input_methods/chewing/js/user_phrase.js @@ -14,16 +14,6 @@ function loadUserPhrases() { }, "json"); } -function closeWindow() { - $.ajax({ - url: "/config", - type: "DELETE", - success: function() { - window.close(); - } - }); -} - // called when the OK button of the "add phrase" dialog is clicked function onAddPhrase() { var phrase = $("#phrase_input").val(); @@ -114,11 +104,11 @@ $(function() { loadUserPhrases(); - // keep the server alive every 30 second + // keep the server alive every 20 second window.setInterval(function() { $.ajax({ url: "/keep_alive", cache: false // needs to turn off cache. otherwise the server will be requested only once. }); - }, 5 * 1000); + }, 20 * 1000); });