from platform import system from pathlib import Path from more_itertools import only from sqlalchemy.ext.automap import automap_base from sqlalchemy.orm import Session from sqlalchemy import create_engine, MetaData from . import Bookmarks, Bookmark, Folder class FirefoxBookmarks(Bookmarks): _locations = { "Windows": Path.home()/"AppData/Roaming/Mozilla/Firefox/Profiles", "Linux": Path.home()/".mozilla/firefox", "Darwin": Path.home()/"Library/Application Support/Firefox/Profiles" } def read_data(self): try: self.bookmarks_file = only(self._locations[system()].glob( "*.default-release"))/"places.sqlite" except KeyError: # Assume everthing that isn't Windows or MacOS works like Linux self.bookmarks_file = only(self._locations["Linux"].glob( "*.default-release"))/"places.sqlite" if not self.bookmarks_file.exists(): raise RuntimeError( "Firefox profile cannot be found or does not exist") else: self._engine = create_engine(f"sqlite:///{self.bookmarks_file}") self._base = automap_base() self._base.metadata = MetaData(bind=self._engine) self._base.prepare(self._engine, reflect=True, classname_for_table=lambda base, name, table: name.replace("moz_", "")) self._session = Session(self._engine) @property def _all_elements(self): return (self._session.query(self._base.classes.bookmarks, self._base.classes.places) .outerjoin(self._base.classes.places, self._base.classes.bookmarks.fk == self._base.classes.places.id) .order_by(self._base.classes.moz_bookmarks.parent, self._base.classes.moz_bookmarks.position) ) @property def folders(self): return self._all_elements.filter(self._base.classes.bookmarks.type == 2) @property def bookmarks(self): return self._all_elements.filter(self._base.classes.bookmarks.type == 1) @property def root_folder(self): return only(self.folders.filter(self._base.classes.bookmarks.id == 1)) @property def toolbar_folder(self): return only(self.folders .filter(self._base.classes.bookmarks.id < 7) .filter(self._base.classes.bookmarks.title == "toolbar") )