From 9fb794656b1c2538177726ea0d58a87f118e3f03 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 31 Aug 2018 22:01:45 +0200 Subject: [PATCH] Make it possible to force a history rebuild This adds a new CompletionMetaInfo table which is a simple key/value store. Thanks to Python/sqlite duck typing, we can use that to store values of any type, even new ones in the future. Currently, the only allowed key is force_rebuild, which forces a rebuild of the CompletionHistory table. This will be needed for a future change. --- qutebrowser/browser/history.py | 41 +++++++++++++++++++++++++++ tests/unit/browser/test_history.py | 45 ++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/qutebrowser/browser/history.py b/qutebrowser/browser/history.py index 7c0db112a..52d6e6cf5 100644 --- a/qutebrowser/browser/history.py +++ b/qutebrowser/browser/history.py @@ -35,6 +35,40 @@ from qutebrowser.misc import objects, sql _USER_VERSION = 2 +class CompletionMetaInfo(sql.SqlTable): + + """Table containing meta-information for the completion.""" + + KEYS = { + 'force_rebuild': False, + } + + def __init__(self, parent=None): + super().__init__("CompletionMetaInfo", ['key', 'value'], + constraints={'key': 'PRIMARY KEY'}) + for key, default in self.KEYS.items(): + if key not in self: + self[key] = default + + def __contains__(self, key): + if key not in self.KEYS: + raise KeyError(key) + query = self.contains_query('key') + return query.run(val=key).value() + + def __getitem__(self, key): + if key not in self.KEYS: + raise KeyError(key) + query = sql.Query('SELECT value FROM CompletionMetaInfo ' + 'WHERE key = :key') + return query.run(key=key).value() + + def __setitem__(self, key, value): + if key not in self.KEYS: + raise KeyError(key) + self.insert({'key': key, 'value': value}, replace=True) + + class CompletionHistory(sql.SqlTable): """History which only has the newest entry for each URL.""" @@ -65,11 +99,18 @@ class WebHistory(sql.SqlTable): 'redirect': 'NOT NULL'}, parent=parent) self.completion = CompletionHistory(parent=self) + self.metainfo = CompletionMetaInfo(parent=self) + if sql.Query('pragma user_version').run().value() < _USER_VERSION: self.completion.delete_all() + if self.metainfo['force_rebuild']: + self.completion.delete_all() + self.metainfo['force_rebuild'] = False + if not self.completion: # either the table is out-of-date or the user wiped it manually self._rebuild_completion() + self.create_index('HistoryIndex', 'url') self.create_index('HistoryAtimeIndex', 'atime') self._contains_query = self.contains_query('url') diff --git a/tests/unit/browser/test_history.py b/tests/unit/browser/test_history.py index 43476d7e0..74b4d3a88 100644 --- a/tests/unit/browser/test_history.py +++ b/tests/unit/browser/test_history.py @@ -444,3 +444,48 @@ def test_user_version(hist, monkeypatch): ('example.com/1', '', 1), ('example.com/2', '', 2), ] + + +def test_force_rebuild(hist): + """Ensure that completion is regenerated if we force a rebuild.""" + hist.add_url(QUrl('example.com/1'), redirect=False, atime=1) + hist.add_url(QUrl('example.com/2'), redirect=False, atime=2) + hist.completion.delete('url', 'example.com/2') + + hist2 = history.WebHistory() + assert list(hist2.completion) == [('example.com/1', '', 1)] + hist2.metainfo['force_rebuild'] = True + + hist3 = history.WebHistory() + assert list(hist3.completion) == [ + ('example.com/1', '', 1), + ('example.com/2', '', 2), + ] + assert not hist3.metainfo['force_rebuild'] + + +class TestCompletionMetaInfo: + + @pytest.fixture + def metainfo(self): + return history.CompletionMetaInfo() + + def test_contains_keyerror(self, metainfo): + with pytest.raises(KeyError): + 'does_not_exist' in metainfo # pylint: disable=pointless-statement + + def test_getitem_keyerror(self, metainfo): + with pytest.raises(KeyError): + metainfo['does_not_exist'] # pylint: disable=pointless-statement + + def test_setitem_keyerror(self, metainfo): + with pytest.raises(KeyError): + metainfo['does_not_exist'] = 42 + + def test_contains(self, metainfo): + assert 'force_rebuild' in metainfo + + def test_modify(self, metainfo): + assert not metainfo['force_rebuild'] + metainfo['force_rebuild'] = True + assert metainfo['force_rebuild']