Use SQL for quickmark/bookmark storage.

Store quickmarks and bookmarks in an in-memory sql database instead of a
python dict. Long-term storage is not affected, bookmarks and
quickmarks are still persisted in a text file.

The added and deleted signals were removed, as once sql completion
models are used the models will no longer need to update themselves.

This will set the stage for SQL-based history completion.
See #1765.
This commit is contained in:
Ryan Roden-Corrent 2017-01-22 07:14:42 -05:00
parent 9477a2eeb2
commit 93d81d96ce
2 changed files with 42 additions and 84 deletions

View File

@ -1260,13 +1260,15 @@ class CommandDispatcher:
if name is None: if name is None:
url = self._current_url() url = self._current_url()
try: try:
name = quickmark_manager.get_by_qurl(url) quickmark_manager.delete_by_qurl(url)
except urlmarks.DoesNotExistError as e: except KeyError as e:
raise cmdexc.CommandError(str(e)) raise cmdexc.CommandError(str(e))
try: else:
quickmark_manager.delete(name) try:
except KeyError: quickmark_manager.delete(name)
raise cmdexc.CommandError("Quickmark '{}' not found!".format(name)) except KeyError:
raise cmdexc.CommandError(
"Quickmark '{}' not found!".format(name))
@cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.register(instance='command-dispatcher', scope='window')
def bookmark_add(self, url=None, title=None, toggle=False): def bookmark_add(self, url=None, title=None, toggle=False):

View File

@ -31,12 +31,12 @@ import os.path
import functools import functools
import collections import collections
from PyQt5.QtCore import pyqtSignal, QUrl, QObject from PyQt5.QtCore import QUrl, QObject
from qutebrowser.utils import (message, usertypes, qtutils, urlutils, from qutebrowser.utils import (message, usertypes, qtutils, urlutils,
standarddir, objreg, log) standarddir, objreg, log)
from qutebrowser.commands import cmdutils from qutebrowser.commands import cmdutils
from qutebrowser.misc import lineparser from qutebrowser.misc import lineparser, sql
class Error(Exception): class Error(Exception):
@ -53,13 +53,6 @@ class InvalidUrlError(Error):
pass pass
class DoesNotExistError(Error):
"""Exception emitted when a given URL does not exist."""
pass
class AlreadyExistsError(Error): class AlreadyExistsError(Error):
"""Exception emitted when a given URL does already exist.""" """Exception emitted when a given URL does already exist."""
@ -67,29 +60,18 @@ class AlreadyExistsError(Error):
pass pass
class UrlMarkManager(QObject): class UrlMarkManager(sql.SqlTable):
"""Base class for BookmarkManager and QuickmarkManager. """Base class for BookmarkManager and QuickmarkManager.
Attributes: Attributes:
marks: An OrderedDict of all quickmarks/bookmarks. marks: An OrderedDict of all quickmarks/bookmarks.
_lineparser: The LineParser used for the marks _lineparser: The LineParser used for the marks
Signals:
changed: Emitted when anything changed.
added: Emitted when a new quickmark/bookmark was added.
removed: Emitted when an existing quickmark/bookmark was removed.
""" """
changed = pyqtSignal() def __init__(self, name, fields, primary_key, parent=None):
added = pyqtSignal(str, str) """Initialize and read marks."""
removed = pyqtSignal(str) super().__init__(name, fields, primary_key, parent)
def __init__(self, parent=None):
"""Initialize and read quickmarks."""
super().__init__(parent)
self.marks = collections.OrderedDict()
self._init_lineparser() self._init_lineparser()
for line in self._lineparser: for line in self._lineparser:
@ -110,32 +92,21 @@ class UrlMarkManager(QObject):
def save(self): def save(self):
"""Save the marks to disk.""" """Save the marks to disk."""
self._lineparser.data = [' '.join(tpl) for tpl in self.marks.items()] self._lineparser.data = [' '.join(tpl) for tpl in self]
self._lineparser.save() self._lineparser.save()
def delete(self, key):
"""Delete a quickmark/bookmark.
Args:
key: The key to delete (name for quickmarks, URL for bookmarks.)
"""
del self.marks[key]
self.changed.emit()
self.removed.emit(key)
class QuickmarkManager(UrlMarkManager): class QuickmarkManager(UrlMarkManager):
"""Manager for quickmarks. """Manager for quickmarks.
The primary key for quickmarks is their *name*, this means: The primary key for quickmarks is their *name*.
- self.marks maps names to URLs.
- changed gets emitted with the name as first argument and the URL as
second argument.
- removed gets emitted with the name as argument.
""" """
def __init__(self, parent=None):
super().__init__('Quickmarks', ['name', 'url'], primary_key='name',
parent=parent)
def _init_lineparser(self): def _init_lineparser(self):
self._lineparser = lineparser.LineParser( self._lineparser = lineparser.LineParser(
standarddir.config(), 'quickmarks', parent=self) standarddir.config(), 'quickmarks', parent=self)
@ -151,7 +122,7 @@ class QuickmarkManager(UrlMarkManager):
except ValueError: except ValueError:
message.error("Invalid quickmark '{}'".format(line)) message.error("Invalid quickmark '{}'".format(line))
else: else:
self.marks[key] = url self.insert(key, url)
def prompt_save(self, url): def prompt_save(self, url):
"""Prompt for a new quickmark name to be added and add it. """Prompt for a new quickmark name to be added and add it.
@ -191,41 +162,30 @@ class QuickmarkManager(UrlMarkManager):
def set_mark(): def set_mark():
"""Really set the quickmark.""" """Really set the quickmark."""
self.marks[name] = url self.insert(name, url)
self.changed.emit()
self.added.emit(name, url)
log.misc.debug("Added quickmark {} for {}".format(name, url)) log.misc.debug("Added quickmark {} for {}".format(name, url))
if name in self.marks: if name in self:
message.confirm_async( message.confirm_async(
title="Override existing quickmark?", title="Override existing quickmark?",
yes_action=set_mark, default=True) yes_action=set_mark, default=True)
else: else:
set_mark() set_mark()
def get_by_qurl(self, url): def delete_by_qurl(self, url):
"""Look up a quickmark by QUrl, returning its name. """Look up a quickmark by QUrl, returning its name."""
Takes O(n) time, where n is the number of quickmarks.
Use a name instead where possible.
"""
qtutils.ensure_valid(url) qtutils.ensure_valid(url)
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded) urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
try: try:
index = list(self.marks.values()).index(urlstr) self.delete(urlstr, field='url')
key = list(self.marks.keys())[index] except KeyError:
except ValueError: raise KeyError("Quickmark for '{}' not found!".format(urlstr))
raise DoesNotExistError(
"Quickmark for '{}' not found!".format(urlstr))
return key
def get(self, name): def get(self, name):
"""Get the URL of the quickmark named name as a QUrl.""" """Get the URL of the quickmark named name as a QUrl."""
if name not in self.marks: if name not in self:
raise DoesNotExistError( raise KeyError("Quickmark '{}' does not exist!".format(name))
"Quickmark '{}' does not exist!".format(name)) urlstr = self[name]
urlstr = self.marks[name]
try: try:
url = urlutils.fuzzy_url(urlstr, do_search=False) url = urlutils.fuzzy_url(urlstr, do_search=False)
except urlutils.InvalidUrlError as e: except urlutils.InvalidUrlError as e:
@ -238,14 +198,13 @@ class BookmarkManager(UrlMarkManager):
"""Manager for bookmarks. """Manager for bookmarks.
The primary key for bookmarks is their *url*, this means: The primary key for bookmarks is their *url*.
- self.marks maps URLs to titles.
- changed gets emitted with the URL as first argument and the title as
second argument.
- removed gets emitted with the URL as argument.
""" """
def __init__(self, parent=None):
super().__init__('Bookmarks', ['url', 'title'], primary_key='url',
parent=parent)
def _init_lineparser(self): def _init_lineparser(self):
bookmarks_directory = os.path.join(standarddir.config(), 'bookmarks') bookmarks_directory = os.path.join(standarddir.config(), 'bookmarks')
if not os.path.isdir(bookmarks_directory): if not os.path.isdir(bookmarks_directory):
@ -262,10 +221,9 @@ class BookmarkManager(UrlMarkManager):
def _parse_line(self, line): def _parse_line(self, line):
parts = line.split(maxsplit=1) parts = line.split(maxsplit=1)
if len(parts) == 2: urlstr = parts[0]
self.marks[parts[0]] = parts[1] title = parts[1] if len(parts) == 2 else ''
elif len(parts) == 1: self.insert(urlstr, title)
self.marks[parts[0]] = ''
def add(self, url, title, *, toggle=False): def add(self, url, title, *, toggle=False):
"""Add a new bookmark. """Add a new bookmark.
@ -286,14 +244,12 @@ class BookmarkManager(UrlMarkManager):
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded) urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
if urlstr in self.marks: if urlstr in self:
if toggle: if toggle:
del self.marks[urlstr] self.delete(urlstr)
return False return False
else: else:
raise AlreadyExistsError("Bookmark already exists!") raise AlreadyExistsError("Bookmark already exists!")
else: else:
self.marks[urlstr] = title self.insert(urlstr, title)
self.changed.emit()
self.added.emit(title, urlstr)
return True return True