Merge branch 'master' of https://github.com/imransobir/qutebrowser into imransobir-master

This commit is contained in:
Florian Bruhin 2017-02-11 17:05:57 +01:00
commit c3153273f5
8 changed files with 356 additions and 0 deletions

View File

@ -19,6 +19,7 @@ parse-type==0.3.4
py==1.4.32 py==1.4.32
pytest==3.0.6 pytest==3.0.6
pytest-bdd==2.18.1 pytest-bdd==2.18.1
pytest-benchmark==3.0.0
pytest-catchlog==1.2.2 pytest-catchlog==1.2.2
pytest-cov==2.4.0 pytest-cov==2.4.0
pytest-faulthandler==1.3.1 pytest-faulthandler==1.3.1

View File

@ -6,6 +6,7 @@ httpbin
hypothesis hypothesis
pytest pytest
pytest-bdd pytest-bdd
pytest-benchmark
pytest-catchlog pytest-catchlog
pytest-cov pytest-cov
pytest-faulthandler pytest-faulthandler

View File

@ -1408,6 +1408,19 @@ class CommandDispatcher:
tab.dump_async(callback, plain=plain) tab.dump_async(callback, plain=plain)
@cmdutils.register(instance='command-dispatcher', name='history',
scope='window')
def show_history(self, tab=True, bg=False, window=False):
r"""Show browsing history
Args:
tab: Open in a new tab.
bg: Open in a background tab.
window: Open in a new window.
"""
url = QUrl('qute://history/')
self._open(url, tab, bg, window)
@cmdutils.register(instance='command-dispatcher', name='help', @cmdutils.register(instance='command-dispatcher', name='help',
scope='window') scope='window')
@cmdutils.argument('topic', completion=usertypes.Completion.helptopic) @cmdutils.argument('topic', completion=usertypes.Completion.helptopic)

View File

@ -24,8 +24,13 @@ Module attributes:
_HANDLERS: The handlers registered via decorators. _HANDLERS: The handlers registered via decorators.
""" """
import sys
import time
import datetime
import urllib.parse import urllib.parse
from PyQt5.QtCore import QUrlQuery
import qutebrowser import qutebrowser
from qutebrowser.utils import (version, utils, jinja, log, message, docutils, from qutebrowser.utils import (version, utils, jinja, log, message, docutils,
objreg) objreg)
@ -158,6 +163,86 @@ def qute_bookmarks(_url):
return 'text/html', html return 'text/html', html
@add_handler('history')
def qute_history(url):
"""Handler for qute:history. Display history."""
# Get current date from query parameter, if not given choose today.
curr_date = datetime.date.today()
try:
query_date = QUrlQuery(url).queryItemValue("date")
if query_date:
curr_date = datetime.datetime.strptime(query_date, "%Y-%m-%d")
curr_date = curr_date.date()
except ValueError:
log.misc.debug("Invalid date passed to qute:history: " + query_date)
one_day = datetime.timedelta(days=1)
next_date = curr_date + one_day
prev_date = curr_date - one_day
def history_iter(reverse):
today_timestamp = time.mktime(datetime.date.today().timetuple())
history = objreg.get('web-history').history_dict.values()
if reverse:
history = reversed(history)
for item in history:
# If we can't apply the reverse performance trick below,
# at least continue as early as possible with old items.
# This gets us down from 550ms to 123ms with 500k old items on my
# machine.
if item.atime < today_timestamp and not reverse:
continue
# Convert timestamp
try:
item_atime = datetime.datetime.fromtimestamp(item.atime)
except (ValueError, OSError, OverflowError):
log.misc.debug("Invalid timestamp {}.".format(item.atime))
continue
if reverse and item_atime.date() < curr_date:
# If we could reverse the history in-place, and this entry is
# older than today, only older entries will follow, so we can
# abort here.
return
# Skip items not on curr_date
# Skip redirects
# Skip qute:// links
is_internal = item.url.scheme() == 'qute'
is_not_today = item_atime.date() != curr_date
if item.redirect or is_internal or is_not_today:
continue
# Use item's url as title if there's no title.
item_url = item.url.toDisplayString()
item_title = item.title if item.title else item_url
display_atime = item_atime.strftime("%X")
yield (item_url, item_title, display_atime)
if sys.hexversion >= 0x03050000:
# On Python >= 3.5 we can reverse the ordereddict in-place and thus
# apply an additional performance improvement in history_iter.
# On my machine, this gets us down from 550ms to 72us with 500k old
# items.
history = list(history_iter(reverse=True))
else:
# On Python 3.4, we can't do that, so we'd need to copy the entire
# history to a list. There, filter first and then reverse it here.
history = reversed(list(history_iter(reverse=False)))
html = jinja.render('history.html',
title='History',
history=history,
curr_date=curr_date,
next_date=next_date,
prev_date=prev_date,
today=datetime.date.today())
return 'text/html', html
@add_handler('pyeval') @add_handler('pyeval')
def qute_pyeval(_url): def qute_pyeval(_url):
"""Handler for qute:pyeval.""" """Handler for qute:pyeval."""

View File

@ -0,0 +1,92 @@
{% extends "base.html" %}
{% block style %}
body {
background: #fefefe;
font-family: sans-serif;
margin: 0 auto;
max-width: 1440px;
padding-left: 20px;
padding-right: 20px;
}
h1 {
color: #444;
font-weight: normal;
}
table {
border-collapse: collapse;
width: 100%;
}
td {
max-width: 50%;
padding: 2px 5px;
text-align: left;
}
td.title {
word-break: break-all;
}
td.time {
color: #555;
text-align: right;
white-space: nowrap;
}
a {
text-decoration: none;
color: #2562dc
}
a:hover {
text-decoration: underline;
}
tr:nth-child(odd) {
background-color: #f8f8f8;
}
.date {
color: #888;
font-size: 14pt;
padding-left: 25px;
}
.pagination-link {
display: inline-block;
margin-bottom: 10px;
margin-top: 10px;
padding-right: 10px;
}
.pagination-link > a {
color: #333;
font-weight: bold;
}
{% endblock %}
{% block content %}
<h1>Browsing history <span class="date">{{curr_date.strftime("%a, %d %B %Y")}}</span></h1>
<table>
<tbody>
{% for url, title, time in history %}
<tr>
<td class="title"><a href="{{url}}">{{title}}</a></td>
<td class="time">{{time}}</td>
</tr>
{% endfor %}
</tbody>
</table>
<span class="pagination-link"><a href="qute://history/?date={{prev_date.strftime("%Y-%m-%d")}}" rel="prev">Previous</a></span>
{% if today >= next_date %}
<span class="pagination-link"><a href="qute://history/?date={{next_date.strftime("%Y-%m-%d")}}" rel="next">Next</a></span>
{% endif %}
{% endblock %}

View File

@ -74,6 +74,13 @@ Feature: Page history
http://localhost:(port)/data/hints/html/simple.html Simple link http://localhost:(port)/data/hints/html/simple.html Simple link
http://localhost:(port)/data/hello.txt http://localhost:(port)/data/hello.txt
Scenario: Listing history
When I open data/numbers/3.txt
And I open data/numbers/4.txt
And I open qute:history
Then the page should contain the plaintext "3.txt"
Then the page should contain the plaintext "4.txt"
## Bugs ## Bugs
@qtwebengine_skip @qtwebkit_ng_skip @qtwebengine_skip @qtwebkit_ng_skip

View File

@ -290,6 +290,24 @@ Feature: Various utility commands.
- about:blank - about:blank
- qute://help/index.html (active) - qute://help/index.html (active)
# :history
Scenario: :history without arguments
When I run :tab-only
And I run :history
And I wait until qute://history/ is loaded
Then the following tabs should be open:
- qute://history/ (active)
Scenario: :history with -t
When I open about:blank
And I run :tab-only
And I run :history -t
And I wait until qute://history/ is loaded
Then the following tabs should be open:
- about:blank
- qute://history/ (active)
# :home # :home
Scenario: :home with single page Scenario: :home with single page

View File

@ -0,0 +1,139 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2017 Imran Sobir
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
import datetime
import collections
from PyQt5.QtCore import QUrl
import pytest
from qutebrowser.browser import history, qutescheme
from qutebrowser.utils import objreg
Dates = collections.namedtuple('Dates', ['yesterday', 'today', 'tomorrow'])
class TestHistoryHandler:
"""Test the qute://history endpoint."""
@pytest.fixture
def dates(self):
one_day = datetime.timedelta(days=1)
today = datetime.datetime.today()
tomorrow = today + one_day
yesterday = today - one_day
return Dates(yesterday, today, tomorrow)
@pytest.fixture
def entries(self, dates):
today = history.Entry(atime=str(dates.today.timestamp()),
url=QUrl('www.today.com'), title='today')
tomorrow = history.Entry(atime=str(dates.tomorrow.timestamp()),
url=QUrl('www.tomorrow.com'), title='tomorrow')
yesterday = history.Entry(atime=str(dates.yesterday.timestamp()),
url=QUrl('www.yesterday.com'), title='yesterday')
return Dates(yesterday, today, tomorrow)
@pytest.fixture
def fake_web_history(self, fake_save_manager, tmpdir):
"""Create a fake web-history and register it into objreg."""
web_history = history.WebHistory(tmpdir.dirname, 'fake-history')
objreg.register('web-history', web_history)
yield web_history
objreg.delete('web-history')
@pytest.fixture(autouse=True)
def fake_history(self, fake_web_history, entries):
"""Create fake history for three different days."""
fake_web_history._add_entry(entries.yesterday)
fake_web_history._add_entry(entries.today)
fake_web_history._add_entry(entries.tomorrow)
fake_web_history.save()
def test_history_without_query(self):
"""Ensure qute://history shows today's history without any query."""
_mimetype, data = qutescheme.qute_history(QUrl("qute://history"))
key = "<span class=\"date\">{}</span>".format(
datetime.date.today().strftime("%a, %d %B %Y"))
assert key in data
def test_history_with_bad_query(self):
"""Ensure qute://history shows today's history with bad query."""
url = QUrl("qute://history?date=204-blaah")
_mimetype, data = qutescheme.qute_history(url)
key = "<span class=\"date\">{}</span>".format(
datetime.date.today().strftime("%a, %d %B %Y"))
assert key in data
def test_history_today(self):
"""Ensure qute://history shows history for today."""
url = QUrl("qute://history")
_mimetype, data = qutescheme.qute_history(url)
assert "today" in data
assert "tomorrow" not in data
assert "yesterday" not in data
def test_history_yesterday(self, dates):
"""Ensure qute://history shows history for yesterday."""
url = QUrl("qute://history?date=" +
dates.yesterday.strftime("%Y-%m-%d"))
_mimetype, data = qutescheme.qute_history(url)
assert "today" not in data
assert "tomorrow" not in data
assert "yesterday" in data
def test_history_tomorrow(self, dates):
"""Ensure qute://history shows history for tomorrow."""
url = QUrl("qute://history?date=" +
dates.tomorrow.strftime("%Y-%m-%d"))
_mimetype, data = qutescheme.qute_history(url)
assert "today" not in data
assert "tomorrow" in data
assert "yesterday" not in data
def test_no_next_link_to_future(self, dates):
"""Ensure there's no next link pointing to the future."""
url = QUrl("qute://history")
_mimetype, data = qutescheme.qute_history(url)
assert "Next" not in data
url = QUrl("qute://history?date=" +
dates.tomorrow.strftime("%Y-%m-%d"))
_mimetype, data = qutescheme.qute_history(url)
assert "Next" not in data
def test_qute_history_benchmark(self, dates, entries, fake_web_history,
benchmark):
for i in range(100000):
entry = history.Entry(
atime=str(dates.yesterday.timestamp()),
url=QUrl('www.yesterday.com/{}'.format(i)),
title='yesterday')
fake_web_history._add_entry(entry)
fake_web_history._add_entry(entries.today)
fake_web_history._add_entry(entries.tomorrow)
url = QUrl("qute://history")
_mimetype, data = benchmark(qutescheme.qute_history, url)
assert "today" in data
assert "tomorrow" not in data
assert "yesterday" not in data