Full redirect support for history
When a redirect occurs, the item is saved in history with a -r suffix now. When opening qutebrowser that's picked up and the item is hidden from completion.
This commit is contained in:
parent
66aae2e5ce
commit
66938ed44b
@ -38,23 +38,26 @@ class Entry:
|
|||||||
Attributes:
|
Attributes:
|
||||||
atime: The time the page was accessed.
|
atime: The time the page was accessed.
|
||||||
url: The URL which was accessed as QUrl.
|
url: The URL which was accessed as QUrl.
|
||||||
hidden: If True, don't save this entry to disk
|
redirect: If True, don't save this entry to disk
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, atime, url, title, hidden=False):
|
def __init__(self, atime, url, title, redirect=False):
|
||||||
self.atime = float(atime)
|
self.atime = float(atime)
|
||||||
self.url = url
|
self.url = url
|
||||||
self.title = title
|
self.title = title
|
||||||
self.hidden = hidden
|
self.redirect = redirect
|
||||||
qtutils.ensure_valid(url)
|
qtutils.ensure_valid(url)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return utils.get_repr(self, constructor=True, atime=self.atime,
|
return utils.get_repr(self, constructor=True, atime=self.atime,
|
||||||
url=self.url_str(), title=self.title,
|
url=self.url_str(), title=self.title,
|
||||||
hidden=self.hidden)
|
redirect=self.redirect)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
elems = [str(int(self.atime)), self.url_str()]
|
atime = str(int(self.atime))
|
||||||
|
if self.redirect:
|
||||||
|
atime += '-r' # redirect flag
|
||||||
|
elems = [atime, self.url_str()]
|
||||||
if self.title:
|
if self.title:
|
||||||
elems.append(self.title)
|
elems.append(self.title)
|
||||||
return ' '.join(elems)
|
return ' '.join(elems)
|
||||||
@ -63,7 +66,7 @@ class Entry:
|
|||||||
return (self.atime == other.atime and
|
return (self.atime == other.atime and
|
||||||
self.title == other.title and
|
self.title == other.title and
|
||||||
self.url == other.url and
|
self.url == other.url and
|
||||||
self.hidden == other.hidden)
|
self.redirect == other.redirect)
|
||||||
|
|
||||||
def url_str(self):
|
def url_str(self):
|
||||||
"""Get the URL as a lossless string."""
|
"""Get the URL as a lossless string."""
|
||||||
@ -91,7 +94,18 @@ class Entry:
|
|||||||
"https://github.com/The-Compiler/qutebrowser/issues/"
|
"https://github.com/The-Compiler/qutebrowser/issues/"
|
||||||
"670".format(data))
|
"670".format(data))
|
||||||
atime = atime.lstrip('\0')
|
atime = atime.lstrip('\0')
|
||||||
return cls(atime, url, title)
|
|
||||||
|
if '-' in atime:
|
||||||
|
atime, flags = atime.split('-')
|
||||||
|
else:
|
||||||
|
flags = ''
|
||||||
|
|
||||||
|
if not set(flags).issubset('r'):
|
||||||
|
raise ValueError("Invalid flags {!r}".format(flags))
|
||||||
|
|
||||||
|
redirect = 'r' in flags
|
||||||
|
|
||||||
|
return cls(atime, url, title, redirect=redirect)
|
||||||
|
|
||||||
|
|
||||||
class WebHistoryInterface(QWebHistoryInterface):
|
class WebHistoryInterface(QWebHistoryInterface):
|
||||||
@ -230,8 +244,8 @@ class WebHistory(QObject):
|
|||||||
|
|
||||||
for entry in self._temp_history.values():
|
for entry in self._temp_history.values():
|
||||||
self._add_entry(entry)
|
self._add_entry(entry)
|
||||||
if not entry.hidden:
|
self._new_history.append(entry)
|
||||||
self._new_history.append(entry)
|
if not entry.redirect:
|
||||||
self.add_completion_item.emit(entry)
|
self.add_completion_item.emit(entry)
|
||||||
self._temp_history.clear()
|
self._temp_history.clear()
|
||||||
|
|
||||||
@ -270,25 +284,26 @@ class WebHistory(QObject):
|
|||||||
self._saved_count = 0
|
self._saved_count = 0
|
||||||
self.cleared.emit()
|
self.cleared.emit()
|
||||||
|
|
||||||
def add_url(self, url, title="", *, hidden=False, atime=None):
|
def add_url(self, url, title="", *, redirect=False, atime=None):
|
||||||
"""Called by WebKit when an URL should be added to the history.
|
"""Called by WebKit when an URL should be added to the history.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
url: An url (as QUrl) to add to the history.
|
url: An url (as QUrl) to add to the history.
|
||||||
hidden: Whether to hide the entry from the on-disk history
|
redirect: Whether the entry was redirected to another URL
|
||||||
|
(hidden in completion)
|
||||||
atime: Override the atime used to add the entry
|
atime: Override the atime used to add the entry
|
||||||
"""
|
"""
|
||||||
if config.get('general', 'private-browsing'):
|
if config.get('general', 'private-browsing'):
|
||||||
return
|
return
|
||||||
if atime is None:
|
if atime is None:
|
||||||
atime = time.time()
|
atime = time.time()
|
||||||
entry = Entry(atime, url, title, hidden=hidden)
|
entry = Entry(atime, url, title, redirect=redirect)
|
||||||
if self._initial_read_done:
|
if self._initial_read_done:
|
||||||
self._add_entry(entry)
|
self._add_entry(entry)
|
||||||
if not entry.hidden:
|
self._new_history.append(entry)
|
||||||
|
self.item_added.emit(entry)
|
||||||
|
if not entry.redirect:
|
||||||
self.add_completion_item.emit(entry)
|
self.add_completion_item.emit(entry)
|
||||||
self._new_history.append(entry)
|
|
||||||
self.item_added.emit(entry)
|
|
||||||
else:
|
else:
|
||||||
self._add_entry(entry, target=self._temp_history)
|
self._add_entry(entry, target=self._temp_history)
|
||||||
|
|
||||||
|
@ -155,7 +155,7 @@ class WebView(QWebView):
|
|||||||
not self._orig_url.matches(self.cur_url, no_formatting)):
|
not self._orig_url.matches(self.cur_url, no_formatting)):
|
||||||
# If the url of the page is different than the url of the link
|
# If the url of the page is different than the url of the link
|
||||||
# originally clicked, save them both.
|
# originally clicked, save them both.
|
||||||
history.add_url(self._orig_url, self.title(), hidden=True)
|
history.add_url(self._orig_url, self.title(), redirect=True)
|
||||||
history.add_url(self.cur_url, self.title())
|
history.add_url(self.cur_url, self.title())
|
||||||
|
|
||||||
def _init_page(self):
|
def _init_page(self):
|
||||||
|
@ -74,7 +74,8 @@ class UrlCompletionModel(base.BaseCompletionModel):
|
|||||||
self._max_history = config.get('completion', 'web-history-max-items')
|
self._max_history = config.get('completion', 'web-history-max-items')
|
||||||
history = utils.newest_slice(self._history, self._max_history)
|
history = utils.newest_slice(self._history, self._max_history)
|
||||||
for entry in history:
|
for entry in history:
|
||||||
self._add_history_entry(entry)
|
if not entry.redirect:
|
||||||
|
self._add_history_entry(entry)
|
||||||
self._history.add_completion_item.connect(self.on_history_item_added)
|
self._history.add_completion_item.connect(self.on_history_item_added)
|
||||||
self._history.cleared.connect(self.on_history_cleared)
|
self._history.cleared.connect(self.on_history_cleared)
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ Feature: Page history
|
|||||||
When I open redirect-to?url=data/title.html without waiting
|
When I open redirect-to?url=data/title.html without waiting
|
||||||
And I wait until data/title.html is loaded
|
And I wait until data/title.html is loaded
|
||||||
Then the history file should contain:
|
Then the history file should contain:
|
||||||
|
r http://localhost:(port)/redirect-to?url=data/title.html Test title
|
||||||
http://localhost:(port)/data/title.html Test title
|
http://localhost:(port)/data/title.html Test title
|
||||||
|
|
||||||
Scenario: History item with spaces in URL
|
Scenario: History item with spaces in URL
|
||||||
|
@ -37,8 +37,11 @@ def check_history(quteproc, httpbin, expected):
|
|||||||
if not line.strip():
|
if not line.strip():
|
||||||
continue
|
continue
|
||||||
print('history line: ' + line)
|
print('history line: ' + line)
|
||||||
line = line.split(' ', maxsplit=1)[1] # Strip off timestamp
|
atime, line = line.split(' ', maxsplit=1)
|
||||||
line = line.rstrip()
|
line = line.rstrip()
|
||||||
|
if '-' in atime:
|
||||||
|
flags = atime.split('-')[1]
|
||||||
|
line = '{} {}'.format(flags, line)
|
||||||
lines.append(line)
|
lines.append(line)
|
||||||
|
|
||||||
assert lines == expected
|
assert lines == expected
|
||||||
|
@ -5,8 +5,10 @@
|
|||||||
<title>Visited/Unvisited links</title>
|
<title>Visited/Unvisited links</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<p>After visiting a link, it should turn purple.</p>
|
<ul>
|
||||||
<p>When closing/reopening qutebrowser, the link still should be purple.</p>
|
<li>After visiting a link, it should turn purple.</li>
|
||||||
|
<li>When closing/reopening qutebrowser, the link still should be purple.</li>
|
||||||
|
<li>The httpbin.org link (before redirect) should not be in the completion.</li>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="http://www.example.com/">normal link</a></li>
|
<li><a href="http://www.example.com/">normal link</a></li>
|
||||||
<li><a href="http://httpbin.org/redirect-to?url=http://www.example.com/">link with redirect</a></li>
|
<li><a href="http://httpbin.org/redirect-to?url=http://www.example.com/">link with redirect</a></li>
|
||||||
|
@ -73,16 +73,16 @@ def test_async_read_no_datadir(qtbot, config_stub, fake_save_manager):
|
|||||||
list(hist.async_read())
|
list(hist.async_read())
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('hidden', [True, False])
|
@pytest.mark.parametrize('redirect', [True, False])
|
||||||
def test_adding_item_during_async_read(qtbot, hist, hidden):
|
def test_adding_item_during_async_read(qtbot, hist, redirect):
|
||||||
"""Check what happens when adding URL while reading the history."""
|
"""Check what happens when adding URL while reading the history."""
|
||||||
url = QUrl('http://www.example.com/')
|
url = QUrl('http://www.example.com/')
|
||||||
|
|
||||||
with qtbot.assertNotEmitted(hist.add_completion_item), \
|
with qtbot.assertNotEmitted(hist.add_completion_item), \
|
||||||
qtbot.assertNotEmitted(hist.item_added):
|
qtbot.assertNotEmitted(hist.item_added):
|
||||||
hist.add_url(url, hidden=hidden, atime=12345)
|
hist.add_url(url, redirect=redirect, atime=12345)
|
||||||
|
|
||||||
if hidden:
|
if redirect:
|
||||||
with qtbot.assertNotEmitted(hist.add_completion_item):
|
with qtbot.assertNotEmitted(hist.add_completion_item):
|
||||||
with qtbot.waitSignal(hist.async_read_done):
|
with qtbot.waitSignal(hist.async_read_done):
|
||||||
list(hist.async_read())
|
list(hist.async_read())
|
||||||
@ -93,7 +93,7 @@ def test_adding_item_during_async_read(qtbot, hist, hidden):
|
|||||||
|
|
||||||
assert not hist._temp_history
|
assert not hist._temp_history
|
||||||
|
|
||||||
expected = history.Entry(url=url, atime=12345, hidden=hidden, title="")
|
expected = history.Entry(url=url, atime=12345, redirect=redirect, title="")
|
||||||
assert list(hist.history_dict.values()) == [expected]
|
assert list(hist.history_dict.values()) == [expected]
|
||||||
|
|
||||||
|
|
||||||
@ -131,7 +131,7 @@ def test_iter(hist):
|
|||||||
url = QUrl('http://www.example.com/')
|
url = QUrl('http://www.example.com/')
|
||||||
hist.add_url(url, atime=12345)
|
hist.add_url(url, atime=12345)
|
||||||
|
|
||||||
entry = history.Entry(url=url, atime=12345, hidden=False, title="")
|
entry = history.Entry(url=url, atime=12345, redirect=False, title="")
|
||||||
assert list(hist) == [entry]
|
assert list(hist) == [entry]
|
||||||
|
|
||||||
|
|
||||||
@ -249,19 +249,36 @@ def test_add_item(qtbot, hist):
|
|||||||
with qtbot.waitSignals([hist.add_completion_item, hist.item_added]):
|
with qtbot.waitSignals([hist.add_completion_item, hist.item_added]):
|
||||||
hist.add_url(QUrl(url), atime=12345, title="the title")
|
hist.add_url(QUrl(url), atime=12345, title="the title")
|
||||||
|
|
||||||
entry = history.Entry(url=QUrl(url), hidden=False, atime=12345,
|
entry = history.Entry(url=QUrl(url), redirect=False, atime=12345,
|
||||||
title="the title")
|
title="the title")
|
||||||
assert hist.history_dict[url] == entry
|
assert hist.history_dict[url] == entry
|
||||||
|
|
||||||
|
|
||||||
def test_add_item_hidden(qtbot, hist):
|
def test_add_item_redirect(qtbot, hist):
|
||||||
list(hist.async_read())
|
list(hist.async_read())
|
||||||
url = 'http://www.example.com/'
|
url = 'http://www.example.com/'
|
||||||
with qtbot.assertNotEmitted(hist.add_completion_item), \
|
with qtbot.assertNotEmitted(hist.add_completion_item):
|
||||||
qtbot.assertNotEmitted(hist.item_added):
|
with qtbot.waitSignal(hist.item_added):
|
||||||
hist.add_url(QUrl(url), hidden=True, atime=12345)
|
hist.add_url(QUrl(url), redirect=True, atime=12345)
|
||||||
|
|
||||||
entry = history.Entry(url=QUrl(url), hidden=True, atime=12345, title="")
|
entry = history.Entry(url=QUrl(url), redirect=True, atime=12345, title="")
|
||||||
|
assert hist.history_dict[url] == entry
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_item_redirect_update(qtbot, tmpdir):
|
||||||
|
"""A redirect update added should override a non-redirect one."""
|
||||||
|
url = 'http://www.example.com/'
|
||||||
|
|
||||||
|
hist_file = tmpdir / 'filled-history'
|
||||||
|
hist_file.write('12345 {}\n'.format(url))
|
||||||
|
hist = history.WebHistory(hist_dir=str(tmpdir), hist_name='filled-history')
|
||||||
|
list(hist.async_read())
|
||||||
|
|
||||||
|
with qtbot.assertNotEmitted(hist.add_completion_item):
|
||||||
|
with qtbot.waitSignal(hist.item_added):
|
||||||
|
hist.add_url(QUrl(url), redirect=True, atime=67890)
|
||||||
|
|
||||||
|
entry = history.Entry(url=QUrl(url), redirect=True, atime=67890, title="")
|
||||||
assert hist.history_dict[url] == entry
|
assert hist.history_dict[url] == entry
|
||||||
|
|
||||||
|
|
||||||
@ -287,6 +304,12 @@ def test_add_item_hidden(qtbot, hist):
|
|||||||
'\x0012345 http://example.com/',
|
'\x0012345 http://example.com/',
|
||||||
history.Entry(atime=12345, url=QUrl('http://example.com/'), title=''),
|
history.Entry(atime=12345, url=QUrl('http://example.com/'), title=''),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
# redirect flag
|
||||||
|
'12345-r http://example.com/ this is a title',
|
||||||
|
history.Entry(atime=12345, url=QUrl('http://example.com/'),
|
||||||
|
title='this is a title', redirect=True)
|
||||||
|
),
|
||||||
])
|
])
|
||||||
def test_entry_parse_valid(line, expected):
|
def test_entry_parse_valid(line, expected):
|
||||||
entry = history.Entry.from_str(line)
|
entry = history.Entry.from_str(line)
|
||||||
@ -297,6 +320,8 @@ def test_entry_parse_valid(line, expected):
|
|||||||
'12345', # one field
|
'12345', # one field
|
||||||
'12345 ::', # invalid URL
|
'12345 ::', # invalid URL
|
||||||
'xyz http://www.example.com/', # invalid timestamp
|
'xyz http://www.example.com/', # invalid timestamp
|
||||||
|
'12345-x http://www.example.com/', # invalid flags
|
||||||
|
'12345-r-r http://www.example.com/', # double flags
|
||||||
])
|
])
|
||||||
def test_entry_parse_invalid(line):
|
def test_entry_parse_invalid(line):
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
@ -328,6 +353,12 @@ def test_entry_parse_hypothesis(text):
|
|||||||
history.Entry(12345.678, QUrl('http://example.com/'), ""),
|
history.Entry(12345.678, QUrl('http://example.com/'), ""),
|
||||||
"12345 http://example.com/",
|
"12345 http://example.com/",
|
||||||
),
|
),
|
||||||
|
# redirect flag
|
||||||
|
(
|
||||||
|
history.Entry(12345.678, QUrl('http://example.com/'), "",
|
||||||
|
redirect=True),
|
||||||
|
"12345-r http://example.com/",
|
||||||
|
),
|
||||||
])
|
])
|
||||||
def test_entry_str(entry, expected):
|
def test_entry_str(entry, expected):
|
||||||
assert str(entry) == expected
|
assert str(entry) == expected
|
||||||
|
Loading…
Reference in New Issue
Block a user