def __init__(self): """ Create database tables or manage update if needed @param suffix as str """ self.thread_lock = Lock() new_version = len(self.__UPGRADES) self.__DB_PATH = "%s/settings2.db" % EOLIE_DATA_PATH if not GLib.file_test(self.__DB_PATH, GLib.FileTest.IS_REGULAR): try: if not GLib.file_test(EOLIE_DATA_PATH, GLib.FileTest.IS_DIR): GLib.mkdir_with_parents(EOLIE_DATA_PATH, 0o0750) # Create db schema with SqlCursor(self) as sql: sql.execute(self.__create_settings) sql.execute("PRAGMA user_version=%s" % new_version) except Exception as e: Logger.error("DatabaseSettings::__init__(): %s", e) # DB upgrade, TODO Make it generic between class version = 0 with SqlCursor(self) as sql: result = sql.execute("PRAGMA user_version") v = result.fetchone() if v is not None: version = v[0] if version < new_version: for i in range(version + 1, new_version + 1): try: sql.execute(self.__UPGRADES[i]) except: Logger.error("Settings DB upgrade %s failed", i) sql.execute("PRAGMA user_version=%s" % new_version)
def __init__(self): """ Create database tables or manage update if needed @param suffix as str """ new_version = len(self.__UPGRADES) self.__DB_PATH = "%s/settings.db" % EOLIE_LOCAL_PATH f = Gio.File.new_for_path(self.__DB_PATH) if not f.query_exists(): try: d = Gio.File.new_for_path(EOLIE_LOCAL_PATH) if not d.query_exists(): d.make_directory_with_parents() # Create db schema with SqlCursor(self) as sql: sql.execute(self.__create_settings) sql.execute("PRAGMA user_version=%s" % new_version) sql.commit() except Exception as e: print("DatabaseSettings::__init__(): %s" % e) # DB upgrade, TODO Make it generic between class version = 0 with SqlCursor(self) as sql: result = sql.execute("PRAGMA user_version") v = result.fetchone() if v is not None: version = v[0] if version < new_version: for i in range(version+1, new_version + 1): try: sql.execute(self.__UPGRADES[i]) except: print("Settings DB upgrade %s failed" % i) sql.execute("PRAGMA user_version=%s" % new_version) sql.commit()
def __save_abp_rules(self, rules): """ Save rules to db @param rules as bytes """ SqlCursor.add(self) result = rules.decode("utf-8") count = 0 for line in result.split('\n'): SqlCursor.allow_thread_execution(self) if self.__cancellable.is_cancelled(): SqlCursor.remove(self) raise Exception("Cancelled") if "-abp-" in line or "$" in line or "!" in line or "[" in line: continue elif line.startswith("##"): self.__save_css_default_rule(line) elif "#@#" in line: self.__save_css_exception(line) elif "##" in line: self.__save_css_domain_rule(line) elif line.startswith("@@"): self.__save_abp_rule(line[2:], True) else: self.__save_abp_rule(line, False) Logger.debug("Add abp filter: %s", line) count += 1 if count == 1000: SqlCursor.commit(self) # Do not flood sqlite, this allow webkit extension to run sleep(0.1) count = 0 SqlCursor.remove(self)
def __vacuum(self): """ VACUUM DB @thread safe """ try: with SqlCursor(self.bookmarks) as sql: sql.isolation_level = None sql.execute("VACUUM") sql.isolation_level = "" with SqlCursor(self.history) as sql: sql.isolation_level = None sql.execute("VACUUM") sql.isolation_level = "" with SqlCursor(self.adblock) as sql: sql.isolation_level = None sql.execute("VACUUM") sql.isolation_level = "" with SqlCursor(self.phishing) as sql: sql.isolation_level = None sql.execute("VACUUM") sql.isolation_level = "" except Exception as e: print("Application::__vacuum(): ", e) self.art.vacuum()
def __save_rules(self, rules): """ Save rules to db @param rules bytes """ SqlCursor.add(self) result = rules.decode('utf-8') count = 0 for line in result.split('\n'): SqlCursor.allow_thread_execution(self) if self.__cancellable.is_cancelled(): SqlCursor.remove(self) raise Exception("Cancelled") if line.startswith('#'): continue array = line.replace( ' ', '\t', 1).replace('\t', '@', 1).split('@') if len(array) <= 1: continue netloc = array[1].replace( ' ', '').replace('\r', '').split('#')[0] # Update entry if exists, create else if netloc != "localhost": Logger.debug("Add filter: %s", netloc) self.__add_netloc(netloc) count += 1 if count == 1000: SqlCursor.commit(self) # Do not flood sqlite, this allow webkit extension to run sleep(0.1) count = 0 SqlCursor.remove(self)
def import_chromium(self, chrome): """ Chromium/Chrome importer As Eolie doesn't sync with Chromium, we do not handle parent guid and just import parents as tags @param chrome as bool """ try: self.thread_lock.acquire() SqlCursor.add(self) import json homedir = GLib.get_home_dir() if chrome: path = homedir + "/.config/chrome/Default/Bookmarks" else: path = homedir + "/.config/chromium/Default/Bookmarks" f = Gio.File.new_for_path(path) if not f.query_exists(): return (status, content, tag) = f.load_contents(None) if status: data = content.decode("utf-8") j = json.loads(data) parents = [] # Setup initial parents for root in j["roots"]: parents.append(("", j["roots"][root]["children"])) # Walk parents and children while parents: (parent_name, children) = parents.pop(0) bookmarks = [] for child in children: if child["type"] == "folder": parents.append((child["name"], child["children"])) elif child["type"] == "url": bookmarks.append((child["name"], child["url"])) position = 0 for bookmark in bookmarks: tags = [parent_name] title = bookmark[0] uri = bookmark[1] if not uri.startswith('http') or not title: continue uri = uri.rstrip('/') rowid = self.get_id(uri) if rowid is None: # Add bookmark bookmark_id = self.add(title, uri, None, tags, 0, False) # Set position self.set_position(bookmark_id, position, False) position += 1 with SqlCursor(self) as sql: sql.commit() SqlCursor.remove(self) except Exception as e: print("DatabaseBookmarks::import_chromium:", e) finally: self.thread_lock.release()
def init(self): """ Init main application """ self.__is_fs = False if Gtk.get_minor_version() > 18: cssProviderFile = Gio.File.new_for_uri( 'resource:///org/gnome/Eolie/application.css') else: cssProviderFile = Gio.File.new_for_uri( 'resource:///org/gnome/Eolie/application-legacy.css') cssProvider = Gtk.CssProvider() cssProvider.load_from_file(cssProviderFile) screen = Gdk.Screen.get_default() styleContext = Gtk.StyleContext() styleContext.add_provider_for_screen(screen, cssProvider, Gtk.STYLE_PROVIDER_PRIORITY_USER) self.settings = Settings.new() self.history = DatabaseHistory() self.bookmarks = DatabaseBookmarks() # We store cursors for main thread SqlCursor.add(self.history) SqlCursor.add(self.bookmarks) self.bookmarks.import_firefox() adblock = DatabaseAdblock() adblock.update() self.art = Art() self.search = Search() self.downloads_manager = DownloadsManager() shortcut_action = Gio.SimpleAction.new('shortcut', GLib.VariantType.new('s')) shortcut_action.connect('activate', self.__on_shortcut_action) self.add_action(shortcut_action) self.set_accels_for_action("app.shortcut::uri", ["<Control>l"]) self.set_accels_for_action("app.shortcut::new_page", ["<Control>t"]) self.set_accels_for_action("app.shortcut::close_page", ["<Control>w"]) # Set some WebKit defaults context = WebKit2.WebContext.get_default() GLib.setenv('PYTHONPATH', self.__extension_dir, True) context.set_web_extensions_directory(self.__extension_dir) data_manager = WebKit2.WebsiteDataManager() context.new_with_website_data_manager(data_manager) context.set_process_model( WebKit2.ProcessModel.MULTIPLE_SECONDARY_PROCESSES) context.set_cache_model(WebKit2.CacheModel.WEB_BROWSER) d = Gio.File.new_for_path(self.__FAVICONS_PATH) if not d.query_exists(): d.make_directory_with_parents() context.set_favicon_database_directory(self.__FAVICONS_PATH) cookie_manager = context.get_cookie_manager() cookie_manager.set_accept_policy( WebKit2.CookieAcceptPolicy.NO_THIRD_PARTY) cookie_manager.set_persistent_storage( self.__COOKIES_PATH, WebKit2.CookiePersistentStorage.SQLITE)
def import_html(self, path): """ Import html bookmarks @param path as str """ try: self.thread_lock.acquire() from bs4 import BeautifulSoup SqlCursor.add(self) f = Gio.File.new_for_path(path) if not f.query_exists(): return (status, content, tag) = f.load_contents(None) if status: data = content.decode("utf-8") soup = BeautifulSoup(data, "html.parser") parent_name = "" position = 0 for dt in soup.findAll("dt"): h3 = dt.find("h3") if h3 is not None: parent_name = h3.contents[0] continue else: a = dt.find("a") uri = a.get("href") if a.get("tags") is None: tags = [parent_name] else: tags = [a.get("tags")] title = a.contents[0] if uri is None: parent_name = title continue elif not uri.startswith('http') or not title: continue uri = uri.rstrip('/') rowid = self.get_id(uri) if rowid is None: if not tags: tags = [parent_name] # Add bookmark bookmark_id = self.add(title, uri, None, tags, 0, False) # Set position self.set_position(bookmark_id, position, False) position += 1 with SqlCursor(self) as sql: sql.commit() SqlCursor.remove(self) except Exception as e: print("DatabaseBookmarks::import_html:", e) finally: self.thread_lock.release()
def __update(self): """ Update database """ result = "" try: for uri in self.__URIS: session = Soup.Session.new() request = session.request(uri) stream = request.send(self.__cancellable) bytes = bytearray(0) buf = stream.read_bytes(1024, self.__cancellable).get_data() while buf: bytes += buf buf = stream.read_bytes(1024, self.__cancellable).get_data() result = bytes.decode('utf-8') for line in result.split('\n'): if self.__cancellable.is_cancelled(): raise IOError("Cancelled") sleep(self.__sleep) if line.startswith('#'): continue array = line.replace(' ', '\t', 1).replace('\t', '@', 1).split('@') if len(array) <= 1: continue dns = array[1].replace(' ', '').replace('\r', '').split('#')[0] # Update entry if exists, create else with SqlCursor(self) as sql: result = sql.execute( "SELECT mtime FROM adblock\ WHERE dns=?", (dns, )) v = result.fetchone() if v is not None: sql.execute( "UPDATE adblock set mtime=?\ WHERE dns=?", (self.__mtime, dns)) else: sql.execute( "INSERT INTO adblock\ (dns, mtime)\ VALUES (?, ?)", (dns, self.__mtime)) sql.commit() # Delete removed entries with SqlCursor(self) as sql: sql.execute( "DELETE FROM adblock\ WHERE mtime!=?", (self.__mtime, )) except Exception as e: print("DatabaseAdlbock:__update():", e)
def add_uri_for_url(self, uri, url): """ Add an uri related to url @param uri as str @param url as str """ try: with SqlCursor(self) as sql: result = sql.execute( "SELECT rowid FROM filechooser\ WHERE url=?", (url, )) v = result.fetchone() if v is not None: sql.execute( "UPDATE filechooser\ SET uri=?\ WHERE url=?", (uri, url)) else: sql.execute( "INSERT INTO filechooser\ (url, uri)\ VALUES (?, ?)", (url, uri)) sql.commit() except Exception as e: print("DatabaseFilechooser::add_uri_for_url():", e)
def __save_css_domain_rule(self, line): """ Save domain rule to db @param line as str """ whitelist = [] blacklist = [] (domains, rule) = line.split("##") for domain in domains.split(","): if domain.startswith("~"): blacklist.append(domain[1:]) else: whitelist.append(domain) str_whitelist = ",".join(whitelist) str_blacklist = ",".join(blacklist) with SqlCursor(self) as sql: result = sql.execute("SELECT mtime FROM adblock_css\ WHERE blacklist=? AND whitelist=?\ AND rule=?", (str_blacklist, str_whitelist, rule)) v = result.fetchone() if v is None: sql.execute("INSERT INTO adblock_css\ (rule, whitelist, blacklist, mtime)\ VALUES (?, ?, ?, ?)", (rule, str_whitelist, str_blacklist, self.__adblock_mtime)) else: sql.execute("UPDATE adblock_css SET mtime=?\ WHERE rule=? and blacklist=? and whitelist=?", (self.__adblock_mtime, rule, str_blacklist, str_whitelist))
def set_chooser_uri(self, chooseruri, uri): """ Add an uri related to uri @param chooseruri as str @param uri as str """ parsed = urlparse(uri) if parsed.scheme not in ["http", "https"]: return try: with SqlCursor(self) as sql: result = sql.execute( "SELECT rowid FROM settings\ WHERE uri=?", (parsed.netloc, )) v = result.fetchone() if v is not None: sql.execute( "UPDATE settings\ SET chooseruri=?\ WHERE uri=?", (chooseruri, parsed.netloc)) else: sql.execute( "INSERT INTO settings\ (uri, chooseruri)\ VALUES (?, ?)", (parsed.netloc, chooseruri)) except Exception as e: Logger.error("DatabaseSettings::set_chooser_uri(): %s", e)
def __on_save_rules(self, result, uris=[]): """ Load next uri, if finished, load CSS rules @param result (unused) @param uris as [str] """ if self.__cancellable.is_cancelled(): return if uris: uri = uris.pop(0) self.__task_helper.load_uri_content(uri, self.__cancellable, self.__on_load_uri_content, uris) else: with SqlCursor(self) as sql: sql.execute("DELETE FROM adblock\ WHERE mtime!=?", (self.__adblock_mtime,)) sql.execute("DELETE FROM adblock_re\ WHERE mtime!=?", (self.__adblock_mtime,)) sql.execute("DELETE FROM adblock_re_domain\ WHERE mtime!=?", (self.__adblock_mtime,)) sql.execute("DELETE FROM adblock_re_domain_ex\ WHERE mtime!=?", (self.__adblock_mtime,)) sql.execute("DELETE FROM adblock_css\ WHERE mtime!=?", (self.__adblock_mtime,)) sql.execute("DELETE FROM adblock_cache") try: dump(self.__adblock_mtime, open(EOLIE_DATA_PATH + "/adblock.bin", "wb")) except Exception as e: Logger.error("DatabaseAdblock::__on_save_rules(): %s", e)
def add(self, title, uri, mtime=None): """ Add a new entry to history, if exists, update it @param title as str @param uri as str @param mtime as int """ if not uri: return if title is None: title = "" if mtime is None: mtime = int(time()) with SqlCursor(self) as sql: result = sql.execute( "SELECT popularity FROM history\ WHERE uri=?", (uri, )) v = result.fetchone() if v is not None: sql.execute( "UPDATE history set mtime=?, popularity=?\ WHERE uri=?", (int(time()), v[0] + 1, uri)) else: sql.execute( "INSERT INTO history\ (title, uri, mtime, popularity)\ VALUES (?, ?, ?, ?)", (title, uri, mtime, 0)) sql.commit()
def set_profile(self, profile, uri): """ Set profile for uri @param user_agent as str @param uri as str """ parsed = urlparse(uri) if parsed.scheme not in ["http", "https"]: return try: with SqlCursor(self) as sql: result = sql.execute( "SELECT rowid FROM settings\ WHERE uri=?", (parsed.netloc, )) v = result.fetchone() if v is not None: sql.execute( "UPDATE settings\ SET profile=?\ WHERE uri=?", (profile, parsed.netloc)) else: sql.execute( "INSERT INTO settings\ (uri, profile)\ VALUES (?, ?)", (parsed.netloc, profile)) except Exception as e: Logger.error("DatabaseSettings::set_profile(): %s", e)
def set_zoom(self, zoom, uri): """ Set zoom for uri @param zoom as int @param uri as str """ parsed = urlparse(uri) if parsed.scheme not in ["http", "https"]: return try: with SqlCursor(self) as sql: result = sql.execute( "SELECT rowid FROM settings\ WHERE uri=?", (parsed.netloc, )) v = result.fetchone() if v is not None: sql.execute( "UPDATE settings\ SET zoom=?\ WHERE uri=?", (zoom, parsed.netloc)) else: sql.execute( "INSERT INTO settings\ (uri, zoom)\ VALUES (?, ?)", (parsed.netloc, zoom)) except Exception as e: Logger.error("DatabaseSettings::set_zoom(): %s", e)
def set_accept_tls(self, uri, accept): """ Accept TLS for uri @param uri as str @param accept as bool """ parsed = urlparse(uri) if parsed.scheme != "https": return try: with SqlCursor(self) as sql: result = sql.execute( "SELECT rowid FROM settings\ WHERE uri=?", (parsed.netloc, )) v = result.fetchone() if v is not None: sql.execute( "UPDATE settings\ SET accept_tls=?\ WHERE uri=?", (accept, parsed.netloc)) else: sql.execute( "INSERT INTO settings\ (uri, accept_tls)\ VALUES (?, ?)", (parsed.netloc, accept)) except Exception as e: Logger.error("DatabaseSettings::set_accept_tls(): %s", e)
def allow_geolocation(self, uri, b): """ Allow geolocation for uri @param uri as str @param b as bool """ parsed = urlparse(uri) if parsed.scheme not in ["http", "https"]: return try: with SqlCursor(self) as sql: result = sql.execute( "SELECT rowid FROM settings\ WHERE uri=?", (parsed.netloc, )) v = result.fetchone() if v is not None: sql.execute( "UPDATE settings\ SET geolocation=?\ WHERE uri=?", (b, parsed.netloc)) else: sql.execute( "INSERT INTO settings\ (uri, geolocation)\ VALUES (?, ?)", (b, parsed.netloc)) except Exception as e: Logger.error("DatabaseSettings::allow_geolocation(): %s", e)
def __on_save_rules(self, result=None, uris=[]): """ Load next uri, if finished, load CSS rules @param result as None @param uris as [str] """ if uris: uri = uris.pop(0) self.__task_helper.load_uri_content(uri, self.__cancellable, self.__on_load_uri_content, uris) else: # Check entries in DB, do we need to update? mtime = 0 with SqlCursor(self) as sql: result = sql.execute("SELECT mtime FROM adblock_css\ ORDER BY mtime LIMIT 1") v = result.fetchone() if v is not None: mtime = v[0] # We ignore update value from rules file if self.__adblock_mtime - mtime < self.__UPDATE: return locales = GLib.get_language_names() user_locale = locales[0].split("_")[0] try: uris = [self.__CSS_LOCALIZED_URIS[user_locale]] except: uris = [] uris += list(self.__CSS_URIS) uri = uris.pop(0) self.__task_helper.load_uri_content(uri, self.__cancellable, self.__on_load_uri_css_content, uris)
def set_user_agent(self, user_agent, url): """ Set user agent for url @param user_agent as str @param url as str """ parsed = urlparse(url) if parsed.scheme not in ["http", "https"]: return try: with SqlCursor(self) as sql: result = sql.execute("SELECT rowid FROM settings\ WHERE url=?", (parsed.netloc,)) v = result.fetchone() if v is not None: sql.execute("UPDATE settings\ SET user_agent=?\ WHERE url=?", (user_agent, parsed.netloc)) else: sql.execute("INSERT INTO settings\ (url, user_agent)\ VALUES (?, ?)", (parsed.netloc, user_agent)) sql.commit() except Exception as e: print("DatabaseSettings::set_user_agent():", e)
def add(self, title, uri, guid, tags, atime=0): """ Add a new bookmark @param title as str @param uri as str @param guid as str @param tags as [str] @param parent_guid as str @param ctime as int @return bookmark id as int """ # Find an uniq guid while guid is None: guid = get_random_string(12) if self.exists_guid(guid): guid = None with SqlCursor(self) as sql: result = sql.execute( "INSERT INTO bookmarks\ (title, uri, popularity, guid, atime, mtime)\ VALUES (?, ?, ?, ?, ?, ?)", (title, uri.rstrip('/'), 0, guid, atime, 0)) bookmarks_id = result.lastrowid for tag in tags: if not tag: continue tag_id = self.get_tag_id(tag) if tag_id is None: tag_id = self.add_tag(tag) sql.execute( "INSERT INTO bookmarks_tags\ (bookmark_id, tag_id) VALUES (?, ?)", (bookmarks_id, tag_id)) return bookmarks_id
def add(self, title, uri, tags): """ Add a new bookmark @param title as str @param uri as str @param tags as [str] """ if not uri or not title: return with SqlCursor(self) as sql: result = sql.execute( "INSERT INTO bookmarks\ (title, uri)\ VALUES (?, ?)", (title, uri)) bookmarks_id = result.lastrowid for tag in tags: if not tag: continue tag_id = self.get_tag_id(tag) if tag_id is None: result = sql.execute( "INSERT INTO tags\ (title) VALUES (?)", (tag, )) tag_id = result.lastrowid sql.execute( "INSERT INTO bookmarks_tags\ (bookmark_id, tag_id) VALUES (?, ?)", (bookmarks_id, tag_id)) sql.commit() # We need this as current db is attached to history El().history.add(title, uri, 0)
def add_language(self, code, uri): """ Add language for uri @param code as str @param uri as str """ parsed = urlparse(uri) if parsed.scheme not in ["http", "https"]: return try: with SqlCursor(self) as sql: codes = self.get_languages(uri) if codes is not None: if code not in codes: codes.append(code) sql.execute( "UPDATE settings\ SET languages=?\ WHERE uri=?", (";".join(codes), parsed.netloc)) else: sql.execute( "INSERT INTO settings\ (uri, languages)\ VALUES (?, ?)", (parsed.netloc, code)) except Exception as e: Logger.error("DatabaseSettings::add_language(): %s", e)
def set_parent(self, bookmark_id, parent_guid, parent_name): """ Set parent id for bookmark @param bookmark_id as int @param parent_guid as str @param parent_name as str """ with SqlCursor(self) as sql: result = sql.execute( "SELECT parent_guid\ FROM parents\ WHERE bookmark_id=?", (bookmark_id, )) v = result.fetchone() if v is None or v[0] is None: sql.execute( "INSERT INTO parents\ (bookmark_id, parent_guid, parent_name)\ VALUES (?, ?, ?)", (bookmark_id, parent_guid, parent_name)) else: sql.execute( "UPDATE parents\ SET parent_guid=?, parent_name=?\ WHERE bookmark_id=?", (parent_guid, parent_name, bookmark_id))
def add(self, title, uri, mtime, guid=None, atimes=[], commit=True): """ Add a new entry to history, if exists, update it @param title as str @param uri as str @param mtime as int @parma guid as str @param atime as [int] @param commit as bool @return history id as int """ if not uri: return uri = uri.rstrip('/') if title is None: title = "" # No guid provided, first search in bookmarks # Then in history. Db may be broken and contains multiple guid # for same uri if guid is None: bookmark_id = El().bookmarks.get_id(uri) if bookmark_id is not None: guid = El().bookmarks.get_guid(bookmark_id) else: history_id = El().history.get_id(uri) guid = El().history.get_guid(history_id) # Find an uniq guid if none exists in db while guid is None: guid = get_random_string(12) if self.exists_guid(guid): guid = None with SqlCursor(self) as sql: result = sql.execute("SELECT rowid, popularity FROM history\ WHERE guid=?", (guid,)) v = result.fetchone() if v is not None: history_id = v[0] sql.execute("UPDATE history\ SET uri=?, mtime=?, title=?, popularity=?\ WHERE rowid=?", (uri, mtime, title, v[1]+1, history_id)) else: result = sql.execute("INSERT INTO history\ (title, uri, mtime, popularity, guid)\ VALUES (?, ?, ?, ?, ?)", (title, uri, mtime, 0, guid)) history_id = result.lastrowid # Only add new atimes to db if not atimes: atimes = [mtime] current_atimes = self.get_atimes(history_id) for atime in atimes: if atime not in current_atimes: sql.execute("INSERT INTO history_atime\ (history_id, atime)\ VALUES (?, ?)", (history_id, atime)) if commit: sql.commit() return history_id
def reset_popularity(self, uri): """ Reset popularity for uri @param uri as str """ with SqlCursor(self) as sql: sql.execute("UPDATE bookmarks SET popularity=0 WHERE uri=?", (uri, ))
def get_guids(self): """ Get all guids @return guids as [str] """ with SqlCursor(self) as sql: result = sql.execute("SELECT guid FROM bookmarks") return list(itertools.chain(*result))
def rename_tag(self, old, new): """ Rename tag @param old as str @param new as str """ with SqlCursor(self) as sql: sql.execute("UPDATE tags set title=? WHERE title=?", (new, old))
def set_tag_title(self, tag_id, title): """ Set tag id title @param tag id as int @parma title as str """ with SqlCursor(self) as sql: sql.execute("UPDATE tags SET title=? WHERE id=?", (title, tag_id,)) sql.commit()
def remove(self, history_id): """ Remove item from history @param history id as int """ with SqlCursor(self) as sql: sql.execute("DELETE from history\ WHERE rowid=?", (history_id,)) sql.commit()