1# 2# sync_server.py <Peter.Bienstman@UGent.be> 3# 4 5import os 6import http.client 7import threading 8import mnemosyne.version 9 10from openSM2sync.ui import UI 11from openSM2sync.server import Server 12from mnemosyne.libmnemosyne.component import Component 13from mnemosyne.libmnemosyne.utils import expand_path, localhost_IP 14 15 16class SyncServer(Component, Server): 17 18 """libmnemosyne-specific parts of the openSM2sync server.""" 19 20 program_name = "Mnemosyne" 21 program_version = mnemosyne.version.version 22 23 def __init__(self, **kwds): 24 config = kwds["component_manager"].current("config") 25 if "server_only" in kwds: 26 self.server_only = kwds["server_only"] 27 del kwds["server_only"] 28 else: 29 self.server_only = False 30 super().__init__(machine_id=config.machine_id(), 31 port=config["sync_server_port"], **kwds) 32 self.check_for_edited_local_media_files = \ 33 self.config()["check_for_edited_local_media_files"] 34 35 def authorization_set_up(self) -> bool: 36 auth_username = self.config()["remote_access_username"] 37 auth_password = self.config()["remote_access_password"] 38 return auth_password != "" and auth_username != "" 39 40 def authorise(self, username, password): 41 # We should not be running if authorization is not set up, 42 # but check just in case. 43 return self.authorization_set_up() and \ 44 username == self.config()["remote_access_username"] and \ 45 password == self.config()["remote_access_password"] 46 47 def load_database(self, database_name): 48 if self.server_only: 49 # First see if web server needs to release database. 50 try: 51 con = http.client.HTTPConnection("127.0.0.1", 52 self.config()["web_server_port"]) 53 con.request("GET", "/release_database") 54 response = con.getresponse() 55 except: 56 pass 57 if not os.path.exists(expand_path(database_name, 58 self.config().data_dir)): 59 self.database().new(database_name) 60 else: 61 self.database().load(database_name) 62 else: 63 self.previous_database = self.config()["last_database"] 64 if self.previous_database != database_name: 65 if not os.path.exists(expand_path(database_name, 66 self.config().data_dir)): 67 self.database().new(database_name) 68 else: 69 self.database().load(database_name) 70 return self.database() 71 72 def unload_database(self, database): 73 if self.server_only: 74 self.database().unload() 75 else: 76 self.database().load(self.previous_database) 77 self.log().loaded_database() 78 self.review_controller().reset_but_try_to_keep_current_card() 79 self.review_controller().update_dialog(redraw_all=True) 80 81 def flush(self): 82 83 """If there are still dangling sessions (i.e. those waiting in vain 84 for more client input) in the sync server, we should flush them and 85 make sure they restore from backup before doing anything that could 86 change the database (e.g. adding a card). Otherwise, if these 87 sessions close later during program shutdown, their backup 88 restoration will override the changes. 89 90 """ 91 92 self.expire_old_sessions() 93 94 95class SyncServerThread(threading.Thread, SyncServer): 96 97 """Basic threading implementation of the sync server, suitable for text- 98 based UIs. A GUI-based client will want to override several functions 99 in SyncServer and SyncServerThread in view of the interaction between 100 multiple threads and the GUI event loop. 101 102 """ 103 104 def __init__(self, component_manager): 105 threading.Thread.__init__(self) 106 SyncServer.__init__(self, component_manager=component_manager, 107 ui=UI(), server_only=True) 108 109 def run(self): 110 if not self.authorization_set_up(): 111 print("""Error: Authorization not set up. 112If on a headless server, you may use the following commands in the sqlite3 console on config.db to configure authorization: 113 update config set value="'<username>'" where key = "remote_access_username"; 114 update config set value="'<password>'" where key = "remote_access_password";""") 115 return 116 117 # Start server. 118 print(("Sync server listening on " + localhost_IP() + ":" + \ 119 str(self.config()["sync_server_port"]))) 120 self.serve_until_stopped() 121 server_hanging = (len(self.sessions) != 0) 122 if server_hanging: 123 self.terminate_all_sessions() 124 self.database().release_connection() 125