Add a CompletionHistory instead of HistoryVisits table
This commit is contained in:
parent
c64b7d00e6
commit
57d96a4512
@ -72,13 +72,13 @@ class Entry:
|
|||||||
return self.url.toString(QUrl.FullyEncoded | QUrl.RemovePassword)
|
return self.url.toString(QUrl.FullyEncoded | QUrl.RemovePassword)
|
||||||
|
|
||||||
|
|
||||||
class HistoryVisits(sql.SqlTable):
|
class CompletionHistory(sql.SqlTable):
|
||||||
|
|
||||||
"""Secondary table with visited URLs and timestamps."""
|
"""History which only has the newest entry for each URL."""
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__("Visits", ['url', 'atime'],
|
super().__init__("CompletionHistory", ['url', 'title', 'last_atime'],
|
||||||
fkeys={'url': 'History(url)'})
|
constraints={'url': 'PRIMARY KEY'}, parent=parent)
|
||||||
|
|
||||||
|
|
||||||
class WebHistory(sql.SqlTable):
|
class WebHistory(sql.SqlTable):
|
||||||
@ -86,14 +86,11 @@ class WebHistory(sql.SqlTable):
|
|||||||
"""The global history of visited pages."""
|
"""The global history of visited pages."""
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__("History",
|
super().__init__("History", ['url', 'title', 'atime', 'redirect'],
|
||||||
['url', 'title', 'last_atime', 'redirect'],
|
|
||||||
constraints={'url': 'PRIMARY KEY'},
|
|
||||||
parent=parent)
|
parent=parent)
|
||||||
self.visits = HistoryVisits(parent=self)
|
self.completion = CompletionHistory(parent=self)
|
||||||
self.create_index('HistoryIndex', 'url')
|
self.create_index('HistoryIndex', 'url')
|
||||||
self._contains_query = self.contains_query('url')
|
self._contains_query = self.contains_query('url')
|
||||||
# FIXME
|
|
||||||
self._between_query = sql.Query('SELECT * FROM History '
|
self._between_query = sql.Query('SELECT * FROM History '
|
||||||
'where not redirect '
|
'where not redirect '
|
||||||
'and not url like "qute://%" '
|
'and not url like "qute://%" '
|
||||||
@ -117,8 +114,10 @@ class WebHistory(sql.SqlTable):
|
|||||||
def _add_entry(self, entry):
|
def _add_entry(self, entry):
|
||||||
"""Add an entry to the in-memory database."""
|
"""Add an entry to the in-memory database."""
|
||||||
self.insert([entry.url_str(), entry.title, int(entry.atime),
|
self.insert([entry.url_str(), entry.title, int(entry.atime),
|
||||||
entry.redirect], replace=True)
|
entry.redirect])
|
||||||
self.visits.insert([entry.url_str(), int(entry.atime)])
|
if not entry.redirect:
|
||||||
|
self.completion.insert([entry.url_str(), entry.title,
|
||||||
|
int(entry.atime)], replace=True)
|
||||||
|
|
||||||
def get_recent(self):
|
def get_recent(self):
|
||||||
"""Get the most recent history entries."""
|
"""Get the most recent history entries."""
|
||||||
@ -229,9 +228,10 @@ class WebHistory(sql.SqlTable):
|
|||||||
raise ValueError("Invalid flags {!r}".format(flags))
|
raise ValueError("Invalid flags {!r}".format(flags))
|
||||||
|
|
||||||
redirect = 'r' in flags
|
redirect = 'r' in flags
|
||||||
|
row = (url, title, float(atime), redirect)
|
||||||
|
completion_row = None if redirect else (url, title, float(atime))
|
||||||
|
|
||||||
return ((url, float(atime)),
|
return (row, completion_row)
|
||||||
(url, title, float(atime), bool(redirect)))
|
|
||||||
|
|
||||||
def import_txt(self):
|
def import_txt(self):
|
||||||
"""Import a history text file into sqlite if it exists.
|
"""Import a history text file into sqlite if it exists.
|
||||||
@ -258,20 +258,21 @@ class WebHistory(sql.SqlTable):
|
|||||||
"""Import a text file into the sql database."""
|
"""Import a text file into the sql database."""
|
||||||
with open(path, 'r', encoding='utf-8') as f:
|
with open(path, 'r', encoding='utf-8') as f:
|
||||||
rows = []
|
rows = []
|
||||||
visit_rows = []
|
completion_rows = []
|
||||||
for (i, line) in enumerate(f):
|
for (i, line) in enumerate(f):
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if not line:
|
if not line:
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
visit_row, row = self._parse_entry(line.strip())
|
row, completion_row = self._parse_entry(line.strip())
|
||||||
rows.append(row)
|
rows.append(row)
|
||||||
visit_rows.append(visit_row)
|
if completion_row is not None:
|
||||||
|
completion_rows.append(completion_row)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise Exception('Failed to parse line #{} of {}: "{}"'
|
raise Exception('Failed to parse line #{} of {}: "{}"'
|
||||||
.format(i, path, line))
|
.format(i, path, line))
|
||||||
self.insert_batch(rows, replace=True)
|
self.insert_batch(rows)
|
||||||
self.visits.insert_batch(visit_rows)
|
self.completion.insert_batch(completion_rows, replace=True)
|
||||||
|
|
||||||
@cmdutils.register(instance='web-history', debug=True)
|
@cmdutils.register(instance='web-history', debug=True)
|
||||||
def debug_dump_history(self, dest):
|
def debug_dump_history(self, dest):
|
||||||
@ -280,7 +281,6 @@ class WebHistory(sql.SqlTable):
|
|||||||
Args:
|
Args:
|
||||||
dest: Where to write the file to.
|
dest: Where to write the file to.
|
||||||
"""
|
"""
|
||||||
# FIXME
|
|
||||||
dest = os.path.expanduser(dest)
|
dest = os.path.expanduser(dest)
|
||||||
|
|
||||||
lines = ('{}{} {} {}'
|
lines = ('{}{} {} {}'
|
||||||
|
@ -30,7 +30,7 @@ class SqlCategory(QSqlQueryModel):
|
|||||||
"""Wraps a SqlQuery for use as a completion category."""
|
"""Wraps a SqlQuery for use as a completion category."""
|
||||||
|
|
||||||
def __init__(self, name, *, filter_fields, sort_by=None, sort_order=None,
|
def __init__(self, name, *, filter_fields, sort_by=None, sort_order=None,
|
||||||
select='*', where=None, group_by=None, suffix=None, parent=None):
|
select='*', where=None, group_by=None, parent=None):
|
||||||
"""Create a new completion category backed by a sql table.
|
"""Create a new completion category backed by a sql table.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -77,8 +77,8 @@ def url():
|
|||||||
timefmt = config.get('completion', 'timestamp-format')
|
timefmt = config.get('completion', 'timestamp-format')
|
||||||
select_time = "strftime('{}', last_atime, 'unixepoch')".format(timefmt)
|
select_time = "strftime('{}', last_atime, 'unixepoch')".format(timefmt)
|
||||||
hist_cat = sqlcategory.SqlCategory(
|
hist_cat = sqlcategory.SqlCategory(
|
||||||
'History', sort_order='desc', sort_by='last_atime',
|
'CompletionHistory', sort_order='desc', sort_by='last_atime',
|
||||||
filter_fields=['url', 'title'],
|
filter_fields=['url', 'title'],
|
||||||
select='url, title, {}'.format(select_time), where='not redirect')
|
select='url, title, {}'.format(select_time))
|
||||||
model.add_category(hist_cat)
|
model.add_category(hist_cat)
|
||||||
return model
|
return model
|
||||||
|
@ -106,8 +106,7 @@ class SqlTable(QObject):
|
|||||||
|
|
||||||
changed = pyqtSignal()
|
changed = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, name, fields, constraints=None, fkeys=None,
|
def __init__(self, name, fields, constraints=None, parent=None):
|
||||||
parent=None):
|
|
||||||
"""Create a new table in the sql database.
|
"""Create a new table in the sql database.
|
||||||
|
|
||||||
Raises SqlException if the table already exists.
|
Raises SqlException if the table already exists.
|
||||||
@ -116,22 +115,16 @@ class SqlTable(QObject):
|
|||||||
name: Name of the table.
|
name: Name of the table.
|
||||||
fields: A list of field names.
|
fields: A list of field names.
|
||||||
constraints: A dict mapping field names to constraint strings.
|
constraints: A dict mapping field names to constraint strings.
|
||||||
fkeys: A dict mapping field names to foreign keys.
|
|
||||||
"""
|
"""
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._name = name
|
self._name = name
|
||||||
|
|
||||||
constraints = constraints or {}
|
constraints = constraints or {}
|
||||||
fkeys = fkeys or {}
|
|
||||||
|
|
||||||
column_defs = ['{} {}'.format(field, constraints.get(field, ''))
|
column_defs = ['{} {}'.format(field, constraints.get(field, ''))
|
||||||
for field in fields]
|
for field in fields]
|
||||||
for field, fkey in sorted(fkeys.items()):
|
|
||||||
column_defs.append('FOREIGN KEY({}) REFERENCES {}'.format(
|
|
||||||
field, fkey))
|
|
||||||
|
|
||||||
q = Query("CREATE TABLE IF NOT EXISTS {} ({})"
|
q = Query("CREATE TABLE IF NOT EXISTS {} ({})"
|
||||||
.format(name, ','.join(column_defs)))
|
.format(name, ','.join(column_defs)))
|
||||||
|
|
||||||
q.run()
|
q.run()
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
self.Entry = collections.namedtuple(name + '_Entry', fields)
|
self.Entry = collections.namedtuple(name + '_Entry', fields)
|
||||||
|
@ -155,24 +155,22 @@ def bookmarks(bookmark_manager_stub):
|
|||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def web_history_stub(stubs, init_sql):
|
def web_history_stub(stubs, init_sql):
|
||||||
return sql.SqlTable("History", ['url', 'title', 'atime', 'redirect'])
|
return sql.SqlTable("CompletionHistory", ['url', 'title', 'last_atime'])
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def web_history(web_history_stub, init_sql):
|
def web_history(web_history_stub, init_sql):
|
||||||
"""Pre-populate the web-history database."""
|
"""Pre-populate the web-history database."""
|
||||||
web_history_stub.insert(['http://some-redirect.example.com', 'redirect',
|
|
||||||
datetime(2016, 9, 5).timestamp(), True])
|
|
||||||
web_history_stub.insert(['http://qutebrowser.org', 'qutebrowser',
|
web_history_stub.insert(['http://qutebrowser.org', 'qutebrowser',
|
||||||
datetime(2015, 9, 5).timestamp(), False])
|
datetime(2015, 9, 5).timestamp()])
|
||||||
web_history_stub.insert(['https://python.org', 'Welcome to Python.org',
|
web_history_stub.insert(['https://python.org', 'Welcome to Python.org',
|
||||||
datetime(2016, 2, 8).timestamp(), False])
|
datetime(2016, 2, 8).timestamp()])
|
||||||
web_history_stub.insert(['https://python.org', 'Welcome to Python.org',
|
web_history_stub.insert(['https://python.org', 'Welcome to Python.org',
|
||||||
datetime(2016, 3, 8).timestamp(), False])
|
datetime(2016, 3, 8).timestamp()])
|
||||||
web_history_stub.insert(['https://python.org', 'Welcome to Python.org',
|
web_history_stub.insert(['https://python.org', 'Welcome to Python.org',
|
||||||
datetime(2014, 3, 8).timestamp(), False])
|
datetime(2014, 3, 8).timestamp()])
|
||||||
web_history_stub.insert(['https://github.com', 'https://github.com',
|
web_history_stub.insert(['https://github.com', 'https://github.com',
|
||||||
datetime(2016, 5, 1).timestamp(), False])
|
datetime(2016, 5, 1).timestamp()])
|
||||||
return web_history_stub
|
return web_history_stub
|
||||||
|
|
||||||
|
|
||||||
@ -336,7 +334,7 @@ def test_url_completion_pattern(config_stub, web_history_stub,
|
|||||||
url, title, pattern, rowcount):
|
url, title, pattern, rowcount):
|
||||||
"""Test that url completion filters by url and title."""
|
"""Test that url completion filters by url and title."""
|
||||||
config_stub.data['completion'] = {'timestamp-format': '%Y-%m-%d'}
|
config_stub.data['completion'] = {'timestamp-format': '%Y-%m-%d'}
|
||||||
web_history_stub.insert([url, title, 0, False])
|
web_history_stub.insert([url, title, 0])
|
||||||
model = urlmodel.url()
|
model = urlmodel.url()
|
||||||
model.set_pattern(pattern)
|
model.set_pattern(pattern)
|
||||||
# 2, 0 is History
|
# 2, 0 is History
|
||||||
@ -582,10 +580,9 @@ def test_url_completion_benchmark(benchmark, config_stub,
|
|||||||
'web-history-max-items': 1000}
|
'web-history-max-items': 1000}
|
||||||
|
|
||||||
entries = [web_history_stub.Entry(
|
entries = [web_history_stub.Entry(
|
||||||
atime=i,
|
last_atime=i,
|
||||||
url='http://example.com/{}'.format(i),
|
url='http://example.com/{}'.format(i),
|
||||||
title='title{}'.format(i),
|
title='title{}'.format(i))
|
||||||
redirect=False)
|
|
||||||
for i in range(100000)]
|
for i in range(100000)]
|
||||||
|
|
||||||
web_history_stub.insert_batch(entries)
|
web_history_stub.insert_batch(entries)
|
||||||
|
Loading…
Reference in New Issue
Block a user