Beispiel #1
0
class SyncWorker:
    """
       Manage sync with mozilla server, will start syncing on init
    """
    def __init__(self):
        """
            Init worker
        """
        self.__syncing = False
        self.__sync_cancellable = Gio.Cancellable()
        self.__username = ""
        self.__password = ""
        self.__mtimes = {"bookmarks": 0.1, "history": 0.1}
        self.__mozilla_sync = None
        self.__state_lock = True
        self.__session = None
        self.__helper = PasswordsHelper()
        self.__helper.get_sync(self.__set_credentials)

    def login(self, attributes, password):
        """
            Login to service
            @param attributes as {}
            @param password as str
            @raise exceptions
        """
        if attributes is None:
            return
        from base64 import b64encode
        if self.__mozilla_sync is None:
            self.__mozilla_sync = MozillaSync()
        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 = 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, self.on_password_stored, True)

    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 self.syncing or\
                not Gio.NetworkMonitor.get_default().get_network_available():
            return
        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():
            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():
            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():
            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():
            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():
            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()

    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
        """
        if El().sync_worker is not None:
            self.__helper.get_sync(self.__set_credentials)
            # Wait for credentials
            if sync:
                GLib.timeout_add(10, El().sync_worker.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.__mozilla_sync is None:
                self.__mozilla_sync = MozillaSync()
            self.__get_session_bulk_keys()
            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.__mozilla_sync is None:
            self.__mozilla_sync = MozillaSync()
        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_LOCAL_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_LOCAL_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.__helper.get_sync(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["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)
            # Keep folder only for firefox compatiblity
            if "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"], [], 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"], 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)
                    El().bookmarks.set_mtime(bookmark_id, record["modified"],
                                             False)
            # Deleted bookmark
            elif "deleted" in bookmark.keys():
                El().bookmarks.remove(bookmark_id)
            # 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")
Beispiel #2
0
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)
Beispiel #3
0
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)