def _on_sync_button_clicked(self, button): """ Start sync @param button as Gtk.Button """ helper = PasswordsHelper() helper.get_sync(self.__on_get_sync) button.hide()
class SyncWorker: """ Manage sync with mozilla server, will start syncing on init """ def check_modules(): """ True if deps are installed @return bool """ from importlib import util fxa = util.find_spec("fxa") crypto = util.find_spec("Crypto") return fxa is not None and crypto is not None def __init__(self): """ Init worker """ self.__syncing = False self.__sync_cancellable = Gio.Cancellable() self.__username = "" self.__password = "" self.__token = "" self.__uid = "" self.__keyB = b"" self.__mtimes = {"bookmarks": 0.1, "history": 0.1} # We do not create this here because it's slow down Eolie startup # See __mozilla_sync property self.__mz = None self.__state_lock = True self.__session = None self.__helper = PasswordsHelper() self.set_credentials() def login(self, attributes, password): """ Login to service @param attributes as {} @param password as str @raise exceptions """ self.__username = "" self.__password = "" self.__uid = "" self.__token = "" self.__keyB = b"" if attributes is None or not attributes["login"] or not password: print("SyncWorker::login():", attributes) return from base64 import b64encode session = None self.__username = attributes["login"] self.__password = password # Connect to mozilla sync try: session = self.__mozilla_sync.login(self.__username, password) bid_assertion, key = self.__mozilla_sync.\ get_browserid_assertion(session) self.__token = session.token self.__uid = session.uid self.__keyB = session.keys[1] keyB_encoded = b64encode(self.__keyB).decode("utf-8") self.__helper.clear_sync(self.__helper.store_sync, self.__username, password, self.__uid, self.__token, keyB_encoded, self.on_password_stored, True) except Exception as e: self.__helper.clear_sync(self.__helper.store_sync, attributes["login"], password, "", "", "") raise e def new_session(self): """ Start a new session """ # Just reset session, will be set by get_session_bulk_keys() self.__session = None def set_credentials(self): """ Set credentials using Secret """ self.__helper.get_sync(self.__set_credentials) def sync(self, loop=False, first_sync=False): """ Start syncing, you need to check sync_status property @param loop as bool -> for GLib.timeout_add() @param first_sync as bool """ if Gio.NetworkMonitor.get_default().get_network_available() and\ self.__username and self.__password and not self.syncing: task_helper = TaskHelper() task_helper.run(self.__sync, first_sync) return loop def push_history(self, history_ids): """ Push history ids @param history_ids as [int] """ if Gio.NetworkMonitor.get_default().get_network_available() and\ self.__username and self.__password: task_helper = TaskHelper() task_helper.run(self.__push_history, history_ids) def push_password(self, user_form_name, user_form_value, pass_form_name, pass_form_value, uri, form_uri, uuid): """ Push password @param user_form_name as str @param user_form_value as str @param pass_form_name as str @param pass_form_value as str @param uri as str @param form_uri as str @param uuid as str """ if Gio.NetworkMonitor.get_default().get_network_available() and\ self.__username and self.__password: task_helper = TaskHelper() task_helper.run(self.__push_password, user_form_name, user_form_value, pass_form_name, pass_form_value, uri, form_uri, uuid) def remove_from_history(self, guid): """ Remove history guid from remote history @param guid as str """ if Gio.NetworkMonitor.get_default().get_network_available() and\ self.__username and self.__password: task_helper = TaskHelper() task_helper.run(self.__remove_from_history, guid) def remove_from_bookmarks(self, guid): """ Remove bookmark guid from remote bookmarks @param guid as str """ if Gio.NetworkMonitor.get_default().get_network_available() and\ self.__username and self.__password: task_helper = TaskHelper() task_helper.run(self.__remove_from_bookmarks, guid) def remove_from_passwords(self, uuid): """ Remove password from passwords collection @param uuid as str """ if Gio.NetworkMonitor.get_default().get_network_available() and\ self.__username and self.__password: task_helper = TaskHelper() task_helper.run(self.__remove_from_passwords, uuid) def delete_secret(self): """ Delete sync secret """ self.__username = "" self.__password = "" self.__session = None self.__sync_cancellable.reset() self.__helper.clear_sync(None) def stop(self, force=False): """ Stop update, if force, kill session too @param force as bool """ self.__sync_cancellable.cancel() if force: self.__session = None def on_password_stored(self, secret, result, sync): """ Update credentials @param secret as Secret @param result as Gio.AsyncResult @param sync as bool """ self.set_credentials() if sync: # Wait for credentials (server side) GLib.timeout_add(10, self.sync, True) @property def mtimes(self): """ Sync engine modification times @return {} """ return self.__mtimes @property def syncing(self): """ True if sync is running @return bool """ return self.__syncing @property def status(self): """ True if sync is working @return bool """ try: if self.__username: self.__get_session_bulk_keys() self.__mozilla_sync.client.info_collections() return True except Exception as e: print("SyncWorker::status(): %s" % e) @property def username(self): """ Get username @return str """ return self.__username ####################### # PRIVATE # ####################### @property def __mozilla_sync(self): """ Get mozilla sync, create if None """ if self.__mz is None: self.__mz = MozillaSync() return self.__mz def __get_session_bulk_keys(self): """ Get session decrypt keys @return keys as (b"", b"") """ if self.__session is None: from fxa.core import Session as FxASession from fxa.crypto import quick_stretch_password self.__session = FxASession(self.__mozilla_sync.fxa_client, self.__username, quick_stretch_password( self.__username, self.__password), self.__uid, self.__token) self.__session.keys = [b"", self.__keyB] self.__session.check_session_status() bid_assertion, key = self.__mozilla_sync.get_browserid_assertion( self.__session) bulk_keys = self.__mozilla_sync.connect(bid_assertion, key) return bulk_keys def __update_state(self): """ Update state file """ try: # If syncing, state will be written by self.__sync() if not self.syncing: f = open(EOLIE_DATA_PATH + "/mozilla_sync.bin", "wb") # Lock file flock(f, LOCK_EX | LOCK_NB) self.__mtimes = self.__mozilla_sync.client.info_collections() dump(self.__mtimes, f) # Unlock file flock(f, LOCK_UN) except Exception as e: debug("SyncWorker::__update_state(): %s" % e) def __push_history(self, history_ids): """ Push history ids if atime is available, else, ask to remove @param history ids as [int] """ try: bulk_keys = self.__get_session_bulk_keys() for history_id in history_ids: self.__check_worker() sleep(0.01) record = {} atimes = El().history.get_atimes(history_id) guid = El().history.get_guid(history_id) if atimes: record["histUri"] = El().history.get_uri(history_id) record["id"] = guid record["title"] = El().history.get_title(history_id) record["visits"] = [] for atime in atimes: record["visits"].append({"date": atime*1000000, "type": 1}) debug("pushing %s" % record) self.__mozilla_sync.add(record, "history", bulk_keys) else: record["id"] = guid record["type"] = "item" record["deleted"] = True debug("deleting %s" % record) self.__mozilla_sync.add(record, "history", bulk_keys) self.__update_state() except Exception as e: debug("SyncWorker::__push_history(): %s" % e) def __push_password(self, user_form_name, user_form_value, pass_form_name, pass_form_value, uri, form_uri, uuid): """ Push password @param user_form_name as str @param user_form_value as str @param pass_form_name as str @param pass_form_value as str @param uri as str @param uuid as str """ try: self.__check_worker() bulk_keys = self.__get_session_bulk_keys() record = {} record["id"] = "{%s}" % uuid record["hostname"] = uri record["formSubmitURL"] = form_uri record["httpRealm"] = None record["username"] = user_form_value record["password"] = pass_form_value record["usernameField"] = user_form_name record["passwordField"] = pass_form_name mtime = int(time()*1000) record["timeCreated"] = mtime record["timePasswordChanged"] = mtime debug("pushing %s" % record) self.__mozilla_sync.add(record, "passwords", bulk_keys) self.__update_state() except Exception as e: print("SyncWorker::__push_password():", e) def __remove_from_history(self, guid): """ Remove from history @param guid as str """ try: self.__check_worker() bulk_keys = self.__get_session_bulk_keys() record = {} record["id"] = guid record["type"] = "item" record["deleted"] = True debug("deleting %s" % record) self.__mozilla_sync.add(record, "history", bulk_keys) self.__update_state() except Exception as e: debug("SyncWorker::__remove_from_history(): %s" % e) def __remove_from_bookmarks(self, guid): """ Remove from history @param guid as str """ try: self.__check_worker() bulk_keys = self.__get_session_bulk_keys() record = {} record["id"] = guid record["type"] = "bookmark" record["deleted"] = True debug("deleting %s" % record) self.__mozilla_sync.add(record, "bookmark", bulk_keys) self.__update_state() except Exception as e: debug("SyncWorker::__remove_from_bookmarks(): %s" % e) def __remove_from_passwords(self, uuid): """ Remove password from passwords collection @param uuid as str """ try: self.__check_worker() bulk_keys = self.__get_session_bulk_keys() record = {} record["id"] = uuid record["deleted"] = True debug("deleting %s" % record) self.__mozilla_sync.add(record, "passwords", bulk_keys) self.__update_state() except Exception as e: debug("SyncWorker::__remove_from_passwords(): %s" % e) def __sync(self, first_sync): """ Sync Eolie objects (bookmarks, history, ...) with Mozilla Sync @param first_sync as bool """ debug("Start syncing") self.__syncing = True self.__sync_cancellable.reset() try: self.__mtimes = load(open(EOLIE_DATA_PATH + "/mozilla_sync.bin", "rb")) except: self.__mtimes = {"bookmarks": 0.1, "history": 0.1, "passwords": 0.1} try: self.__check_worker() bulk_keys = self.__get_session_bulk_keys() new_mtimes = self.__mozilla_sync.client.info_collections() self.__check_worker() ######################## # Passwords Management # ######################## try: debug("local passwords: %s, remote passwords: %s" % ( self.__mtimes["passwords"], new_mtimes["passwords"])) # Only pull if something new available if self.__mtimes["passwords"] != new_mtimes["passwords"]: self.__pull_passwords(bulk_keys) except: pass # No passwords in sync self.__check_worker() ###################### # History Management # ###################### try: debug("local history: %s, remote history: %s" % ( self.__mtimes["history"], new_mtimes["history"])) # Only pull if something new available if self.__mtimes["history"] != new_mtimes["history"]: self.__pull_history(bulk_keys) except: pass # No history in sync self.__check_worker() ######################## # Bookmarks Management # ######################## try: debug("local bookmarks: %s, remote bookmarks: %s" % ( self.__mtimes["bookmarks"], new_mtimes["bookmarks"])) # Push new bookmarks self.__push_bookmarks(bulk_keys) except: pass # No bookmarks in sync self.__check_worker() # Only pull if something new available if self.__mtimes["bookmarks"] != new_mtimes["bookmarks"]: self.__pull_bookmarks(bulk_keys, first_sync) # Update last sync mtime self.__syncing = False self.__update_state() debug("Stop syncing") except Exception as e: debug("SyncWorker::__sync(): %s" % e) if str(e) == "The authentication token could not be found": self.set_credentials() self.__syncing = False def __push_bookmarks(self, bulk_keys): """ Push to bookmarks @param bulk keys as KeyBundle @param start time as float @raise StopIteration """ debug("push bookmarks") parents = [] for bookmark_id in El().bookmarks.get_ids_for_mtime( self.__mtimes["bookmarks"]): self.__check_worker() sleep(0.01) parent_guid = El().bookmarks.get_parent_guid(bookmark_id) # No parent, move it to unfiled if parent_guid is None: parent_guid = "unfiled" parent_id = El().bookmarks.get_id_by_guid(parent_guid) if parent_id not in parents: parents.append(parent_id) record = {} record["bmkUri"] = El().bookmarks.get_uri(bookmark_id) record["id"] = El().bookmarks.get_guid(bookmark_id) record["title"] = El().bookmarks.get_title(bookmark_id) record["tags"] = El().bookmarks.get_tags(bookmark_id) record["parentid"] = parent_guid record["parentName"] = El().bookmarks.get_parent_name(bookmark_id) record["type"] = "bookmark" debug("pushing %s" % record) self.__mozilla_sync.add(record, "bookmarks", bulk_keys) # Del old bookmarks for bookmark_id in El().bookmarks.get_deleted_ids(): self.__check_worker() sleep(0.01) parent_guid = El().bookmarks.get_parent_guid(bookmark_id) parent_id = El().bookmarks.get_id_by_guid(parent_guid) if parent_id not in parents: parents.append(parent_id) record = {} record["id"] = El().bookmarks.get_guid(bookmark_id) record["type"] = "bookmark" record["deleted"] = True debug("deleting %s" % record) self.__mozilla_sync.add(record, "bookmarks", bulk_keys) El().bookmarks.remove(bookmark_id) # Push parents in this order, parents near root are handled later # Otherwise, order will be broken by new children updates while parents: parent_id = parents.pop(0) parent_guid = El().bookmarks.get_guid(parent_id) parent_name = El().bookmarks.get_title(parent_id) children = El().bookmarks.get_children(parent_guid) # So search if children in parents found = False for child_guid in children: child_id = El().bookmarks.get_id_by_guid(child_guid) if child_id in parents: found = True break # Handle children first if found: parents.append(parent_id) debug("later: %s" % parent_name) continue record = {} record["id"] = parent_guid record["type"] = "folder" # A parent with parent as unfiled needs to be moved to places # Firefox internal grand_parent_guid = El().bookmarks.get_parent_guid(parent_id) if grand_parent_guid == "unfiled": grand_parent_guid = "places" record["parentid"] = grand_parent_guid record["parentName"] = El().bookmarks.get_parent_name(parent_id) record["title"] = parent_name record["children"] = children debug("pushing parent %s" % record) self.__mozilla_sync.add(record, "bookmarks", bulk_keys) El().bookmarks.clean_tags() def __pull_bookmarks(self, bulk_keys, first_sync): """ Pull from bookmarks @param bulk_keys as KeyBundle @param first_sync as bool @raise StopIteration """ debug("pull bookmarks") SqlCursor.add(El().bookmarks) records = self.__mozilla_sync.get_records("bookmarks", bulk_keys) children_array = [] for record in records: self.__check_worker() if record["modified"] < self.__mtimes["bookmarks"]: continue sleep(0.01) bookmark = record["payload"] bookmark_id = El().bookmarks.get_id_by_guid(bookmark["id"]) # Nothing to apply, continue if El().bookmarks.get_mtime(bookmark_id) >= record["modified"]: continue debug("pulling %s" % record) # Deleted bookmark if "deleted" in bookmark.keys(): El().bookmarks.remove(bookmark_id) # Keep folder only for firefox compatiblity elif "type" in bookmark.keys() and bookmark["type"] == "folder"\ and bookmark["id"] is not None\ and bookmark["title"]: if bookmark_id is None: bookmark_id = El().bookmarks.add(bookmark["title"], bookmark["id"], bookmark["id"], [], 0, False) # Will calculate position later if "children" in bookmark.keys(): children_array.append(bookmark["children"]) # We have a bookmark, add it elif "type" in bookmark.keys() and bookmark["type"] == "bookmark"\ and bookmark["id"] is not None\ and bookmark["title"]: # Add a new bookmark if bookmark_id is None: # Use parent name if no bookmarks tags if "tags" not in bookmark.keys() or\ not bookmark["tags"]: if "parentName" in bookmark.keys() and\ bookmark["parentName"]: bookmark["tags"] = [bookmark["parentName"]] else: bookmark["tags"] = [] bookmark_id = El().bookmarks.add(bookmark["title"], bookmark["bmkUri"], bookmark["id"], bookmark["tags"], 0, False) # Update bookmark else: El().bookmarks.set_title(bookmark_id, bookmark["title"], False) El().bookmarks.set_uri(bookmark_id, bookmark["bmkUri"], False) # Update tags current_tags = El().bookmarks.get_tags(bookmark_id) for tag in El().bookmarks.get_tags(bookmark_id): if "tags" in bookmark.keys() and\ tag not in bookmark["tags"]: tag_id = El().bookmarks.get_tag_id(tag) current_tags.remove(tag) El().bookmarks.del_tag_from(tag_id, bookmark_id, False) if "tags" in bookmark.keys(): for tag in bookmark["tags"]: # Tag already associated if tag in current_tags: continue tag_id = El().bookmarks.get_tag_id(tag) if tag_id is None: tag_id = El().bookmarks.add_tag(tag, False) El().bookmarks.add_tag_to(tag_id, bookmark_id, False) # Update parent name if available if bookmark_id is not None and "parentName" in bookmark.keys(): El().bookmarks.set_parent(bookmark_id, bookmark["parentid"], bookmark["parentName"], False) El().bookmarks.set_mtime(bookmark_id, record["modified"], False) # Update bookmark position for children in children_array: position = 0 for child in children: bid = El().bookmarks.get_id_by_guid(child) El().bookmarks.set_position(bid, position, False) position += 1 El().bookmarks.clean_tags() # Will commit SqlCursor.remove(El().bookmarks) def __pull_passwords(self, bulk_keys): """ Pull from passwords @param bulk_keys as KeyBundle @raise StopIteration """ debug("pull passwords") records = self.__mozilla_sync.get_records("passwords", bulk_keys) for record in records: self.__check_worker() if record["modified"] < self.__mtimes["passwords"]: continue sleep(0.01) debug("pulling %s" % record) password = record["payload"] password_id = password["id"].strip("{}") if "formSubmitURL" in password.keys(): self.__helper.clear(password_id, self.__helper.store, password["usernameField"], password["username"], password["passwordField"], password["password"], password["hostname"], password["formSubmitURL"], password_id, None) elif "deleted" in password.keys(): # We assume True self.__helper.clear(password_id) def __pull_history(self, bulk_keys): """ Pull from history @param bulk_keys as KeyBundle @raise StopIteration """ debug("pull history") records = self.__mozilla_sync.get_records("history", bulk_keys) for record in records: self.__check_worker() if record["modified"] < self.__mtimes["history"]: continue sleep(0.01) El().history.thread_lock.acquire() history = record["payload"] keys = history.keys() history_id = El().history.get_id_by_guid(history["id"]) # Check we have a valid history item if "histUri" in keys and\ "title" in keys and\ history["title"] and\ El().history.get_mtime(history_id) < record["modified"]: # Try to get visit date atimes = [] try: for visit in history["visits"]: atimes.append(round(int(visit["date"]) / 1000000, 2)) except: El().history.thread_lock.release() continue debug("pulling %s" % record) title = history["title"].rstrip().lstrip() history_id = El().history.add(title, history["histUri"], record["modified"], history["id"], atimes, True) elif "deleted" in keys: history_id = El().history.get_id_by_guid(history_id) El().history.remove(history_id) El().history.thread_lock.release() def __set_credentials(self, attributes, password, uri, index, count): """ Set credentials @param attributes as {} @param password as str @param uri as None @param index as int @param count as int """ if attributes is None: return from base64 import b64decode try: self.__username = attributes["login"] self.__password = password self.__token = attributes["token"] self.__uid = attributes["uid"] self.__keyB = b64decode(attributes["keyB"]) # Force login if no token if not self.__token: self.login(attributes, password) except Exception as e: debug("SyncWorker::__set_credentials(): %s" % e) def __check_worker(self): """ Raise an exception if worker should not be syncing: error&cancel """ if self.__sync_cancellable.is_cancelled(): raise StopIteration("SyncWorker: cancelled") elif not self.__username: raise StopIteration("SyncWorker: missing username") elif not self.__password: raise StopIteration("SyncWorker: missing password") elif not self.__token: raise StopIteration("SyncWorker: missing token")
class SettingsDialog: """ Dialog showing eolie options """ def __init__(self, window): """ Init dialog @param window as Window """ self.__helper = PasswordsHelper() builder = Gtk.Builder() builder.add_from_resource("/org/gnome/Eolie/SettingsDialog.ui") self.__settings_dialog = builder.get_object("settings_dialog") self.__settings_dialog.set_transient_for(window) # self.__settings_dialog.connect("destroy", self.__on_destroy) if False: self.__settings_dialog.set_title(_("Preferences")) else: headerbar = builder.get_object("header_bar") headerbar.set_title(_("Preferences")) self.__settings_dialog.set_titlebar(headerbar) download_chooser = builder.get_object("download_chooser") dir_uri = El().settings.get_value("download-uri").get_string() if not dir_uri: directory = GLib.get_user_special_dir( GLib.UserDirectory.DIRECTORY_DOWNLOAD) if directory is not None: dir_uri = GLib.filename_to_uri(directory, None) if dir_uri: download_chooser.set_uri(dir_uri) else: download_chooser.set_uri("file://" + GLib.getenv("HOME")) open_downloads = builder.get_object("open_downloads_check") open_downloads.set_active( El().settings.get_value("open-downloads")) self.__start_page_uri = builder.get_object("start_page_uri") combo_start = builder.get_object("combo_start") start_page = El().settings.get_value("start-page").get_string() if start_page.startswith("http"): combo_start.set_active_id("address") self.__start_page_uri.set_text(start_page) self.__start_page_uri.show() else: combo_start.set_active_id(start_page) remember_session = builder.get_object("remember_sessions_check") remember_session.set_active( El().settings.get_value("remember-session")) enable_plugins = builder.get_object("plugins_check") enable_plugins.set_active( El().settings.get_value("enable-plugins")) self.__fonts_grid = builder.get_object("fonts_grid") use_system_fonts = builder.get_object("system_fonts_check") use_system_fonts.set_active( El().settings.get_value("use-system-fonts")) self.__fonts_grid.set_sensitive( not El().settings.get_value("use-system-fonts")) sans_serif_button = builder.get_object("sans_serif_button") sans_serif_button.set_font_name( El().settings.get_value("font-sans-serif").get_string()) serif_button = builder.get_object("serif_button") serif_button.set_font_name( El().settings.get_value("font-serif").get_string()) monospace_button = builder.get_object("monospace_button") monospace_button.set_font_name( El().settings.get_value("font-monospace").get_string()) min_font_size_spin = builder.get_object("min_font_size_spin") min_font_size_spin.set_value( El().settings.get_value("min-font-size").get_int32()) monitor_model = get_current_monitor_model(window) zoom_levels = El().settings.get_value("default-zoom-level") wanted_zoom_level = 1.0 try: for zoom_level in zoom_levels: zoom_splited = zoom_level.split('@') if zoom_splited[0] == monitor_model: wanted_zoom_level = float(zoom_splited[1]) except: pass default_zoom_level = builder.get_object("default_zoom_level") default_zoom_level.set_value(float(wanted_zoom_level)) cookies_combo = builder.get_object("cookies_combo") storage = El().settings.get_enum("cookie-storage") cookies_combo.set_active_id(str(storage)) history_combo = builder.get_object("history_combo") storage = El().settings.get_enum("history-storage") history_combo.set_active_id(str(storage)) self.__populars_count = builder.get_object("populars_count") if start_page == "popular": self.__populars_count.show() max_popular_items = El().settings.get_value( "max-popular-items").get_int32() builder.get_object("popular_spin_button").set_value(max_popular_items) remember_passwords = builder.get_object("remember_passwords_check") remember_passwords.set_active( El().settings.get_value("remember-passwords")) tracking_check = builder.get_object("tracking_check") tracking_check.set_active( El().settings.get_value("do-not-track")) self.__result_label = builder.get_object("result_label") self.__sync_button = builder.get_object("sync_button") self.__login_entry = builder.get_object("login_entry") self.__password_entry = builder.get_object("password_entry") self.__result_image = builder.get_object("result_image") builder.connect_signals(self) self.__helper.get_sync(self.__on_get_sync) thread = Thread(target=self.__get_sync_status) thread.daemon = True thread.start() def show(self): """ Show dialog """ self.__settings_dialog.show() ####################### # PROTECTED # ####################### def _on_popular_spin_value_changed(self, button): """ Save value @param button as Gtk.SpinButton """ value = GLib.Variant("i", button.get_value()) El().settings.set_value("max-popular-items", value) def _on_configure_engines_clicked(self, button): """ Show Web engines configurator @param button as Gtk.Button """ from eolie.dialog_search_engine import SearchEngineDialog dialog = SearchEngineDialog(self.__settings_dialog) dialog.run() def _on_clear_personnal_data_clicked(self, button): """ Show clear personnal data dialog @param button as Gtk.button """ from eolie.dialog_clear_data import ClearDataDialog dialog = ClearDataDialog(self.__settings_dialog) dialog.run() def _on_manage_cookies_clicked(self, button): """ Show cookies popover @param button as Gtk.button """ from eolie.popover_cookies import CookiesPopover popover = CookiesPopover() popover.populate() popover.set_relative_to(button) popover.popup() def _on_manage_passwords_clicked(self, button): """ Launch searhorse @param button as Gtk.Button """ from eolie.popover_passwords import PasswordsPopover popover = PasswordsPopover() popover.populate() popover.set_relative_to(button) popover.popup() def _on_tracking_toggled(self, button): """ Save state @param button as Gtk.ToggleButton """ El().settings.set_value("do-not-track", GLib.Variant("b", button.get_active())) def _on_cookies_changed(self, combo): """ Save cookies setting @param combo as Gtk.ComboBoxText """ El().settings.set_enum("cookie-storage", int(combo.get_active_id())) for window in El().windows: for view in window.container.views: context = view.webview.get_context() cookie_manager = context.get_cookie_manager() cookie_manager.set_accept_policy( El().settings.get_enum("cookie-storage")) def _on_history_changed(self, combo): """ Save history keep setting @param combo as Gtk.ComboBoxText """ El().settings.set_enum("history-storage", int(combo.get_active_id())) def _on_default_zoom_changed(self, button): """ Save size @param button as Gtk.SpinButton """ monitor_model = get_current_monitor_model( self.__settings_dialog.get_transient_for()) try: # Add current value less monitor model zoom_levels = [] for zoom_level in El().settings.get_value("default-zoom-level"): zoom_splited = zoom_level.split('@') if zoom_splited[0] == monitor_model: continue else: zoom_levels.append("%s@%s" % (zoom_splited[0], zoom_splited[1])) # Add new zoom value for monitor model zoom_levels.append("%s@%s" % (monitor_model, button.get_value())) El().settings.set_value("default-zoom-level", GLib.Variant("as", zoom_levels)) for window in El().windows: window.update_zoom_level(True) except Exception as e: print("SettingsDialog::_on_default_zoom_changed():", e) def _on_min_font_size_changed(self, button): """ Save size @param button as Gtk.SpinButton """ value = GLib.Variant("i", button.get_value()) El().settings.set_value("min-font-size", value) El().set_setting("minimum-font-size", button.get_value()) def _on_system_fonts_toggled(self, button): """ Save state @param button as Gtk.ToggleButton """ self.__fonts_grid.set_sensitive(not button.get_active()) El().settings.set_value("use-system-fonts", GLib.Variant("b", button.get_active())) def _on_font_sans_serif_set(self, fontbutton): """ Save font setting @param fontchooser as Gtk.FontButton """ value = GLib.Variant("s", fontbutton.get_font_name()) El().settings.set_value("font-sans-serif", value) El().set_setting("sans-serif-font-family", fontbutton.get_font_name()) def _on_font_serif_set(self, fontbutton): """ Save font setting @param fontchooser as Gtk.FontButton """ value = GLib.Variant("s", fontbutton.get_font_name()) El().settings.set_value("font-serif", value) El().set_setting("serif-font-family", fontbutton.get_font_name()) def _on_font_monospace_set(self, fontbutton): """ Save font setting @param fontchooser as Gtk.FontButton """ value = GLib.Variant("s", fontbutton.get_font_name()) El().settings.set_value("font-monospace", value) El().set_setting("monospace-font-family", fontbutton.get_font_name()) def _on_plugins_toggled(self, button): """ Save state @param button as Gtk.ToggleButton """ value = GLib.Variant("b", button.get_active()) El().settings.set_value("enable-plugins", value) El().set_setting("enable-plugins", button.get_active()) def _on_remember_passwords_toggled(self, button): """ Save state @param button as Gtk.ToggleButton """ El().settings.set_value("remember-passwords", GLib.Variant("b", button.get_active())) def _on_remember_sessions_toggled(self, button): """ Save state @param button as Gtk.ToggleButton """ El().settings.set_value("remember-session", GLib.Variant("b", button.get_active())) def _on_start_page_uri_changed(self, entry): """ Save startup page @param entry as Gtk.Entry """ El().settings.set_value("start-page", GLib.Variant("s", entry.get_text())) def _on_start_changed(self, combo): """ Save startup page @param combo as Gtk.ComboBoxText """ self.__start_page_uri.hide() self.__populars_count.hide() if combo.get_active_id() == "address": self.__start_page_uri.show() elif combo.get_active_id() == "popular": self.__populars_count.show() El().settings.set_value("start-page", GLib.Variant("s", combo.get_active_id())) def _on_engine_changed(self, combo): """ Save engine @param combo as Gtk.ComboBoxText """ El().settings.set_value("search-engine", GLib.Variant("s", combo.get_active_id())) El().search.update_default_engine() def _on_open_downloads_toggled(self, button): """ Save state @param button as Gtk.ToggleButton """ El().settings.set_value("open-downloads", GLib.Variant("b", button.get_active())) def _on_selection_changed(self, chooser): """ Save uri @chooser as Gtk.FileChooserButton """ uri = chooser.get_uri() if uri is None: uri = "" El().settings.set_value("download-uri", GLib.Variant("s", uri)) def _on_key_release_event(self, widget, event): """ Destroy window if Esc @param widget as Gtk.Widget @param event as Gdk.event """ if event.keyval == Gdk.KEY_Escape: self.__settings_dialog.destroy() def _on_sync_button_clicked(self, button): """ Connect to Mozilla Sync to get tokens @param button as Gtk.Button """ icon_name = self.__result_image.get_icon_name()[0] if icon_name == "network-transmit-receive-symbolic": El().sync_worker.stop(True) El().sync_worker.delete_secret() self.__setup_sync_button(False) else: El().sync_worker.delete_secret() self.__result_label.set_text(_("Connecting…")) button.set_sensitive(False) self.__result_image.set_from_icon_name("content-loading-symbolic", Gtk.IconSize.MENU) thread = Thread(target=self.__connect_mozilla_sync, args=(self.__login_entry.get_text(), self.__password_entry.get_text())) thread.daemon = True thread.start() ####################### # PRIVATE # ####################### def __get_sync_status(self): """ Get sync status @thread safe """ if El().sync_worker is not None: status = El().sync_worker.status GLib.idle_add(self.__setup_sync_button, status) else: GLib.idle_add(self.__missing_fxa) def __setup_sync_button(self, status): """ Setup sync button based on current sync status @param status as bool """ self.__sync_button.set_sensitive(True) self.__sync_button.get_style_context().remove_class( "destructive-action") self.__sync_button.get_style_context().remove_class( "suggested-action") if status: self.__result_label.set_text(_("Synchronization is working")) self.__result_image.set_from_icon_name( "network-transmit-receive-symbolic", Gtk.IconSize.MENU) self.__sync_button.get_style_context().add_class( "destructive-action") self.__sync_button.set_label(_("Cancel synchronization")) else: self.__result_label.set_text( _("Synchronization is not working")) self.__result_image.set_from_icon_name( "computer-fail-symbolic", Gtk.IconSize.MENU) self.__sync_button.get_style_context().add_class( "suggested-action") self.__sync_button.set_label(_("Allow synchronization")) def __missing_fxa(self): """ Show a message about missing fxa module """ try: from eolie.mozilla_sync import SyncWorker SyncWorker # Just make PEP8 happy except Exception as e: self.__result_label.set_text( _("Synchronization is not available" " on your computer:\n %s") % e) self.__sync_button.set_sensitive(False) def __connect_mozilla_sync(self, username, password): """ Connect to mozilla sync @param username as str @param password as str @thread safe """ try: El().sync_worker.login({"login": username}, password) GLib.idle_add(self.__setup_sync_button, True) except Exception as e: print("SettingsDialog::__connect_mozilla_sync():", e) GLib.idle_add(self.__sync_button.set_sensitive, True) if str(e) == "Unverified account": GLib.timeout_add(1000, self.__settings_dialog.destroy) # Try to go to webmail split = username.split("@") GLib.idle_add(El().active_window.container.add_webview, "https://%s" % split[1], Gdk.WindowType.CHILD) GLib.idle_add( El().active_window.toolbar.title.show_message, El().active_window.container.current.webview, _("You've received an email" " to validate synchronization")) self.__helper.store_sync(username, password, "", "", "", El().sync_worker.on_password_stored, False) else: GLib.idle_add(self.__result_label.set_text, str(e)) GLib.idle_add(self.__result_image.set_from_icon_name, "computer-fail-symbolic", Gtk.IconSize.MENU) def __on_get_sync(self, attributes, password, uri, index, count): """ Set username and password @param attributes as {} @param password as str @param uri as None @param index as int @param count as int """ if attributes is None: return try: self.__login_entry.set_text(attributes["login"]) self.__password_entry.set_text(password) except Exception as e: print("SettingsDialog::__on_get_sync()", e)
class SyncWorker(GObject.GObject): """ Manage sync with mozilla server, will start syncing on init """ __gsignals__ = {"sync-finished": (GObject.SignalFlags.RUN_FIRST, None, ())} def __init__(self): """ Init worker """ GObject.GObject.__init__(self) self.__stop = True self.__username = "" self.__password = "" self.__mtimes = {"bookmarks": 0.1, "history": 0.1} self.__mozilla_sync = MozillaSync() self.__session = None self.__helper = PasswordsHelper() def login(self, attributes, password, uri): """ Login to service @param attributes as {} @param password as str @param uri as None @raise exceptions """ if attributes is None: return keyB = "" session = None # Connect to mozilla sync session = self.__mozilla_sync.login(attributes["login"], password) bid_assertion, key = self.__mozilla_sync.get_browserid_assertion( session) keyB = base64.b64encode(session.keys[1]).decode("utf-8") # Store credentials if session is None: uid = "" token = "" else: uid = session.uid token = session.token self.__helper.store_sync(attributes["login"], password, uid, token, keyB, lambda x, y: self.sync(True)) def sync(self, first_sync=False): """ Start syncing, you need to check sync_status property @param first_sync as bool """ if self.syncing or\ not Gio.NetworkMonitor.get_default().get_network_available(): return self.__username = "" self.__password = "" self.__stop = False # We force session reset to user last stored token if first_sync: self.__session = None self.__helper.get_sync(self.__start_sync, first_sync) def push_history(self, history_ids): """ Push history ids @param history_ids as [int] """ if Gio.NetworkMonitor.get_default().get_network_available(): thread = Thread(target=self.__push_history, args=(history_ids, )) thread.daemon = True thread.start() def push_password(self, username, userform, password, passform, uri, uuid): """ Push password @param username as str @param userform as str @param password as str @param passform as str @param uri as str @param uuid as str """ if Gio.NetworkMonitor.get_default().get_network_available(): thread = Thread(target=self.__push_password, args=(username, userform, password, passform, uri, uuid)) thread.daemon = True thread.start() def remove_from_history(self, guid): """ Remove history id from remote history A first call to sync() is needed to populate secrets @param guid as str """ if Gio.NetworkMonitor.get_default().get_network_available(): thread = Thread(target=self.__remove_from_history, args=(guid, )) thread.daemon = True thread.start() def remove_from_passwords(self, uri): """ Remove password from passwords collection @param uri as str """ if Gio.NetworkMonitor.get_default().get_network_available(): self.__helper.get(uri, self.__remove_from_passwords_thread) def delete_secret(self): """ Delete sync secret """ self.__username = "" self.__password = "" self.__session = None self.__stop = True self.__helper.clear_sync() def stop(self): """ Stop update """ self.__stop = True @property def mtimes(self): """ Sync engine modification times @return {} """ return self.__mtimes @property def syncing(self): """ True if sync is running @return bool """ return not self.__stop @property def status(self): """ True if sync is working @return bool """ try: self.__mozilla_sync.client.info_collections() return True except: return False @property def username(self): """ Get username @return str """ return self.__username ####################### # PRIVATE # ####################### def __get_session_bulk_keys(self): """ Get session decrypt keys @return keys as (b"", b"") """ if self.__session is None: self.__session = FxASession( self.__mozilla_sync.fxa_client, self.__username, quick_stretch_password(self.__username, self.__password), self.__uid, self.__token) self.__session.keys = [b"", self.__keyB] self.__session.check_session_status() bid_assertion, key = self.__mozilla_sync.get_browserid_assertion( self.__session) bulk_keys = self.__mozilla_sync.connect(bid_assertion, key) return bulk_keys def __push_history(self, history_ids): """ Push history ids if atime is available, else, ask to remove @param history ids as [int] """ if not self.__username or not self.__password: return try: bulk_keys = self.__get_session_bulk_keys() for history_id in history_ids: sleep(0.01) record = {} atimes = El().history.get_atimes(history_id) guid = El().history.get_guid(history_id) if atimes: record["histUri"] = El().history.get_uri(history_id) record["id"] = guid record["title"] = El().history.get_title(history_id) record["visits"] = [] for atime in atimes: record["visits"].append({ "date": atime * 1000000, "type": 1 }) debug("pushing %s" % record) self.__mozilla_sync.add(record, "history", bulk_keys) else: record["id"] = guid record["type"] = "item" record["deleted"] = True debug("deleting %s" % record) self.__mozilla_sync.add(record, "history", bulk_keys) self.__mtimes = self.__mozilla_sync.client.info_collections() dump(self.__mtimes, open(LOCAL_PATH + "/mozilla_sync.bin", "wb")) except Exception as e: print("SyncWorker::__push_history():", e) def __push_password(self, username, userform, password, passform, uri, uuid): """ Push password @param username as str @param userform as str @param password as str @param passform as str @param uri as str @param uuid as str """ if not self.__username or not self.__password: return try: bulk_keys = self.__get_session_bulk_keys() record = {} record["id"] = "{%s}" % uuid record["hostname"] = uri record["formSubmitURL"] = uri record["httpRealm"] = None record["username"] = username record["password"] = password record["usernameField"] = userform record["passwordField"] = passform mtime = int(time() * 1000) record["timeCreated"] = mtime record["timePasswordChanged"] = mtime debug("pushing %s" % record) self.__mozilla_sync.add(record, "passwords", bulk_keys) except Exception as e: print("SyncWorker::__push_password():", e) def __remove_from_history(self, guid): """ Remove from history @param guid as str """ if not self.__username or not self.__password: return try: bulk_keys = self.__get_session_bulk_keys() record = {} record["id"] = guid record["type"] = "item" record["deleted"] = True debug("deleting %s" % record) self.__mozilla_sync.add(record, "history", bulk_keys) except Exception as e: print("SyncWorker::__remove_from_history():", e) def __remove_from_passwords(self, attributes, password, uri): """ Remove password from passwords collection @param attributes as {} @param password as str @param uri as str """ if not self.__username or not self.__password: return try: bulk_keys = self.__get_session_bulk_keys() record = {} record["id"] = attributes["uuid"] record["deleted"] = True debug("deleting %s" % record) self.__mozilla_sync.add(record, "passwords", bulk_keys) self.__helper.clear(uri) except Exception as e: print("SyncWorker::__remove_from_passwords():", e) def __remove_from_passwords_thread(self, attributes, password, uri): """ Remove password from passwords collection @param attributes as {} @param password as str @param uri as str """ thread = Thread(target=self.__remove_from_passwords, args=(attributes, password, uri)) thread.daemon = True thread.start() def __sync(self, first_sync): """ Sync Eolie objects (bookmarks, history, ...) with Mozilla Sync @param first_sync as bool """ debug("Start syncing") if not self.__username or not self.__password: self.__stop = True return try: self.__mtimes = load(open(LOCAL_PATH + "/mozilla_sync.bin", "rb")) except: self.__mtimes = { "bookmarks": 0.1, "history": 0.1, "passwords": 0.1 } try: bulk_keys = self.__get_session_bulk_keys() new_mtimes = self.__mozilla_sync.client.info_collections() if self.__stop: return ###################### # Passwords Management # ###################### debug("local passwords: %s, remote passwords: %s" % (self.__mtimes["passwords"], new_mtimes["passwords"])) # Only pull if something new available if self.__mtimes["passwords"] != new_mtimes["passwords"]: self.__pull_passwords(bulk_keys) if self.__stop: return ###################### # History Management # ###################### debug("local history: %s, remote history: %s" % (self.__mtimes["history"], new_mtimes["history"])) # Only pull if something new available if self.__mtimes["history"] != new_mtimes["history"]: self.__pull_history(bulk_keys) if self.__stop: return ######################## # Bookmarks Management # ######################## debug("local bookmarks: %s, remote bookmarks: %s" % (self.__mtimes["bookmarks"], new_mtimes["bookmarks"])) # Push new bookmarks self.__push_bookmarks(bulk_keys) if self.__stop: return # Only pull if something new available if self.__mtimes["bookmarks"] != new_mtimes["bookmarks"]: self.__pull_bookmarks(bulk_keys, first_sync) # Update last sync mtime self.__mtimes = self.__mozilla_sync.client.info_collections() dump(self.__mtimes, open(LOCAL_PATH + "/mozilla_sync.bin", "wb")) debug("Stop syncing") GLib.idle_add(self.emit, "sync-finished") except Exception as e: print("SyncWorker::__sync():", e) if str(e) == "The authentication token could not be found": self.__helper.get_sync(self.login) self.__stop = True def __push_bookmarks(self, bulk_keys): """ Push to bookmarks @param bulk keys as KeyBundle @param start time as float @raise StopIteration """ debug("push bookmarks") parents = [] for bookmark_id in El().bookmarks.get_ids_for_mtime( self.__mtimes["bookmarks"]): if self.__stop: raise StopIteration("Cancelled") sleep(0.01) parent_guid = El().bookmarks.get_parent_guid(bookmark_id) # No parent, move it to unfiled if parent_guid is None: parent_guid = "unfiled" parent_id = El().bookmarks.get_id_by_guid(parent_guid) if parent_id not in parents: parents.append(parent_id) record = {} record["bmkUri"] = El().bookmarks.get_uri(bookmark_id) record["id"] = El().bookmarks.get_guid(bookmark_id) record["title"] = El().bookmarks.get_title(bookmark_id) record["tags"] = El().bookmarks.get_tags(bookmark_id) record["parentid"] = parent_guid record["type"] = "bookmark" debug("pushing %s" % record) self.__mozilla_sync.add(record, "bookmarks", bulk_keys) # Del old bookmarks for bookmark_id in El().bookmarks.get_deleted_ids(): if self.__stop: raise StopIteration("Cancelled") sleep(0.01) parent_guid = El().bookmarks.get_parent_guid(bookmark_id) parent_id = El().bookmarks.get_id_by_guid(parent_guid) if parent_id not in parents: parents.append(parent_id) record = {} record["id"] = El().bookmarks.get_guid(bookmark_id) record["type"] = "item" record["deleted"] = True debug("deleting %s" % record) self.__mozilla_sync.add(record, "bookmarks", bulk_keys) El().bookmarks.remove(bookmark_id) # Push parents in this order, parents near root are handled later # Otherwise, order will be broken by new children updates while parents: parent_id = parents.pop(0) parent_guid = El().bookmarks.get_guid(parent_id) parent_name = El().bookmarks.get_title(parent_id) children = El().bookmarks.get_children(parent_guid) # So search if children in parents found = False for child_guid in children: child_id = El().bookmarks.get_id_by_guid(child_guid) if child_id in parents: found = True break # Handle children first if found: parents.append(parent_id) debug("later: %s" % parent_name) continue record = {} record["id"] = parent_guid record["type"] = "folder" # A parent with parent as unfiled needs to be moved to places # Firefox internal grand_parent_guid = El().bookmarks.get_parent_guid(parent_id) if grand_parent_guid == "unfiled": grand_parent_guid = "places" record["parentid"] = grand_parent_guid record["parentName"] = El().bookmarks.get_parent_name(parent_id) record["title"] = parent_name record["children"] = children debug("pushing parent %s" % record) self.__mozilla_sync.add(record, "bookmarks", bulk_keys) El().bookmarks.clean_tags() def __pull_bookmarks(self, bulk_keys, first_sync): """ Pull from bookmarks @param bulk_keys as KeyBundle @param first_sync as bool @raise StopIteration """ debug("pull bookmarks") SqlCursor.add(El().bookmarks) records = self.__mozilla_sync.get_records("bookmarks", bulk_keys) # We get all guids here and remove them while sync # At the end, we have deleted records # On fist sync, keep all if first_sync: to_delete = [] else: to_delete = El().bookmarks.get_guids() for record in records: if self.__stop: raise StopIteration("Cancelled") sleep(0.01) bookmark = record["payload"] if "type" not in bookmark.keys() or\ bookmark["type"] not in ["folder", "bookmark"]: continue bookmark_id = El().bookmarks.get_id_by_guid(bookmark["id"]) # This bookmark exists, remove from to delete if bookmark["id"] in to_delete: to_delete.remove(bookmark["id"]) # Nothing to apply, continue if El().bookmarks.get_mtime(bookmark_id) >= record["modified"]: continue debug("pulling %s" % record) if bookmark_id is None: if "bmkUri" in bookmark.keys(): # Use parent name if no bookmarks tags if "tags" not in bookmark.keys() or\ not bookmark["tags"]: if "parentName" in bookmark.keys() and\ bookmark["parentName"]: bookmark["tags"] = [bookmark["parentName"]] else: bookmark["tags"] = [] bookmark_id = El().bookmarks.add(bookmark["title"], bookmark["bmkUri"], bookmark["id"], bookmark["tags"], False) else: bookmark["tags"] = [] bookmark_id = El().bookmarks.add(bookmark["title"], bookmark["id"], bookmark["id"], bookmark["tags"], False) else: El().bookmarks.set_title(bookmark_id, bookmark["title"], False) if "bmkUri" in bookmark.keys(): El().bookmarks.set_uri(bookmark_id, bookmark["bmkUri"], False) elif "children" in bookmark.keys(): position = 0 for child in bookmark["children"]: bid = El().bookmarks.get_id_by_guid(child) El().bookmarks.set_position(bid, position, False) position += 1 # Remove previous tags current_tags = El().bookmarks.get_tags(bookmark_id) for tag in El().bookmarks.get_tags(bookmark_id): if "tags" in bookmark.keys() and\ tag not in bookmark["tags"]: tag_id = El().bookmarks.get_tag_id(tag) current_tags.remove(tag) El().bookmarks.del_tag_from(tag_id, bookmark_id, False) if "tags" in bookmark.keys(): for tag in bookmark["tags"]: # Tag already associated if tag in current_tags: continue tag_id = El().bookmarks.get_tag_id(tag) if tag_id is None: tag_id = El().bookmarks.add_tag(tag, False) El().bookmarks.add_tag_to(tag_id, bookmark_id, False) El().bookmarks.set_mtime(bookmark_id, record["modified"], False) if "parentName" in bookmark.keys(): El().bookmarks.set_parent(bookmark_id, bookmark["parentid"], bookmark["parentName"], False) for guid in to_delete: if self.__stop: raise StopIteration("Cancelled") debug("deleting: %s" % guid) bookmark_id = El().bookmarks.get_id_by_guid(guid) if bookmark_id is not None: El().bookmarks.remove(bookmark_id, False) El().bookmarks.clean_tags() # Will commit SqlCursor.remove(El().bookmarks) def __pull_passwords(self, bulk_keys): """ Pull from passwords @param bulk_keys as KeyBundle @raise StopIteration """ debug("pull passwords") records = self.__mozilla_sync.get_records("passwords", bulk_keys) for record in records: if self.__stop: raise StopIteration("Cancelled") sleep(0.01) debug("pulling %s" % record) password = record["payload"] if "formSubmitURL" in password.keys(): self.__helper.clear(password["formSubmitURL"]) self.__helper.store(password["username"], password["password"], password["formSubmitURL"], password["id"], None) elif "deleted" in password.keys(): # We assume True self.__helper.clear(password["id"]) def __pull_history(self, bulk_keys): """ Pull from history @param bulk_keys as KeyBundle @raise StopIteration """ debug("pull history") records = self.__mozilla_sync.get_records("history", bulk_keys) for record in records: if self.__stop: raise StopIteration("Cancelled") sleep(0.01) El().history.thread_lock.acquire() history = record["payload"] keys = history.keys() # Ignore pages without a title if "title" not in keys or not history["title"]: El().history.thread_lock.release() continue # Ignore pages without an uri (deleted) if "histUri" not in keys: El().history.thread_lock.release() continue history_id = El().history.get_id_by_guid(history["id"]) # Nothing to apply, continue if El().history.get_mtime(history_id) >= record["modified"]: El().history.thread_lock.release() continue # Try to get visit date atimes = [] try: for visit in history["visits"]: atimes.append(round(int(visit["date"]) / 1000000, 2)) except: El().history.thread_lock.release() continue debug("pulling %s" % record) title = history["title"].rstrip().lstrip() history_id = El().history.add(title, history["histUri"], record["modified"], history["id"], atimes, True) El().history.thread_lock.release() def __start_sync(self, attributes, password, uri, first_sync): """ Set params and start sync @param attributes as {} @param password as str @param uri as None @param first_sync as bool """ if attributes is None: return try: self.__username = attributes["login"] self.__password = password self.__token = attributes["token"] self.__uid = attributes["uid"] self.__keyB = base64.b64decode(attributes["keyB"]) if Gio.NetworkMonitor.get_default().get_network_available(): thread = Thread(target=self.__sync, args=(first_sync, )) thread.daemon = True thread.start() except Exception as e: print("SyncWorker::__start_sync()", e)