parent
19d369377e
commit
157c25bb13
@ -467,6 +467,7 @@ class Application(QApplication):
|
||||
self.lastWindowClosed.connect(self.on_last_window_closed)
|
||||
config_obj.style_changed.connect(style.get_stylesheet.cache_clear)
|
||||
self.focusChanged.connect(self.on_focus_changed)
|
||||
self.focusChanged.connect(message.on_focus_changed)
|
||||
|
||||
def _get_widgets(self):
|
||||
"""Get a string list of all widgets."""
|
||||
|
@ -25,8 +25,6 @@ import functools
|
||||
import posixpath
|
||||
import zipfile
|
||||
|
||||
from PyQt5.QtCore import QTimer
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import objreg, standarddir, log, message
|
||||
from qutebrowser.commands import cmdutils
|
||||
@ -107,9 +105,8 @@ class HostBlocker:
|
||||
log.misc.exception("Failed to read host blocklist!")
|
||||
else:
|
||||
if config.get('content', 'host-block-lists') is not None:
|
||||
QTimer.singleShot(500, functools.partial(
|
||||
message.info, 'last-focused',
|
||||
"Run :adblock-update to get adblock lists."))
|
||||
message.info('current',
|
||||
"Run :adblock-update to get adblock lists.")
|
||||
|
||||
@cmdutils.register(instance='host-blocker')
|
||||
def adblock_update(self, win_id: {'special': 'win_id'}):
|
||||
@ -156,9 +153,8 @@ class HostBlocker:
|
||||
f = get_fileobj(byte_io)
|
||||
except (OSError, UnicodeDecodeError, zipfile.BadZipFile,
|
||||
zipfile.LargeZipFile) as e:
|
||||
message.error('last-focused', "adblock: Error while reading {}: "
|
||||
"{} - {}".format(
|
||||
byte_io.name, e.__class__.__name__, e))
|
||||
message.error('current', "adblock: Error while reading {}: {} - "
|
||||
"{}".format(byte_io.name, e.__class__.__name__, e))
|
||||
return
|
||||
for line in f:
|
||||
line_count += 1
|
||||
@ -186,17 +182,16 @@ class HostBlocker:
|
||||
self.blocked_hosts.add(host)
|
||||
log.misc.debug("{}: read {} lines".format(byte_io.name, line_count))
|
||||
if error_count > 0:
|
||||
message.error('last-focused', "adblock: {} read errors for "
|
||||
"{}".format(error_count, byte_io.name))
|
||||
message.error('current', "adblock: {} read errors for {}".format(
|
||||
error_count, byte_io.name))
|
||||
|
||||
def on_lists_downloaded(self):
|
||||
"""Install block lists after files have been downloaded."""
|
||||
with open(self._hosts_file, 'w', encoding='utf-8') as f:
|
||||
for host in sorted(self.blocked_hosts):
|
||||
f.write(host + '\n')
|
||||
message.info('last-focused', "adblock: Read {} hosts from {} "
|
||||
"sources.".format(len(self.blocked_hosts),
|
||||
self._done_count))
|
||||
message.info('current', "adblock: Read {} hosts from {} sources."
|
||||
.format(len(self.blocked_hosts), self._done_count))
|
||||
|
||||
@config.change_filter('content', 'host-block-lists')
|
||||
def on_config_changed(self):
|
||||
|
@ -22,6 +22,7 @@
|
||||
import sys
|
||||
import enum
|
||||
import unittest
|
||||
import datetime
|
||||
import os.path
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
@ -192,6 +193,37 @@ class FormatSecondsTests(unittest.TestCase):
|
||||
self.assertEqual(utils.format_seconds(seconds), out)
|
||||
|
||||
|
||||
class FormatTimedeltaTests(unittest.TestCase):
|
||||
|
||||
"""Tests for format_timedelta.
|
||||
|
||||
Class attributes:
|
||||
TESTS: A list of (input, output) tuples.
|
||||
"""
|
||||
|
||||
TESTS = [
|
||||
(datetime.timedelta(seconds=-1), '-1s'),
|
||||
(datetime.timedelta(seconds=0), '0s'),
|
||||
(datetime.timedelta(seconds=59), '59s'),
|
||||
(datetime.timedelta(seconds=120), '2m'),
|
||||
(datetime.timedelta(seconds=60.4), '1m'),
|
||||
(datetime.timedelta(seconds=63), '1m 3s'),
|
||||
(datetime.timedelta(seconds=-64), '-1m 4s'),
|
||||
(datetime.timedelta(seconds=3599), '59m 59s'),
|
||||
(datetime.timedelta(seconds=3600), '1h'),
|
||||
(datetime.timedelta(seconds=3605), '1h 5s'),
|
||||
(datetime.timedelta(seconds=3723), '1h 2m 3s'),
|
||||
(datetime.timedelta(seconds=3780), '1h 3m'),
|
||||
(datetime.timedelta(seconds=36000), '10h'),
|
||||
]
|
||||
|
||||
def test_format_seconds(self):
|
||||
"""Test format_seconds with several tests."""
|
||||
for td, out in self.TESTS:
|
||||
with self.subTest(td=td):
|
||||
self.assertEqual(utils.format_timedelta(td), out)
|
||||
|
||||
|
||||
class FormatSizeTests(unittest.TestCase):
|
||||
|
||||
"""Tests for format_size.
|
||||
|
@ -19,16 +19,73 @@
|
||||
|
||||
"""Message singleton so we don't have to define unneeded signals."""
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, QObject, QTimer
|
||||
import datetime
|
||||
import collections
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject
|
||||
|
||||
from qutebrowser.utils import usertypes, log, objreg, utils
|
||||
|
||||
|
||||
_QUEUED = []
|
||||
QueuedMsg = collections.namedtuple(
|
||||
'QueuedMsg', ['time', 'win_id', 'method_name', 'text', 'args', 'kwargs'])
|
||||
|
||||
|
||||
def _wrapper(win_id, method_name, text, *args, **kwargs):
|
||||
"""A wrapper which executes the action if possible, and queues it if not.
|
||||
|
||||
It tries to get the message bridge for the given window, and if it's
|
||||
unavailable, it queues it.
|
||||
|
||||
Args:
|
||||
win_id: The window ID to execute the action in,.
|
||||
method_name: The name of the MessageBridge method to call.
|
||||
text: The text do display.
|
||||
*args/**kwargs: Arguments to pass to the method.
|
||||
"""
|
||||
try:
|
||||
bridge = _get_bridge(win_id)
|
||||
except objreg.RegistryUnavailableError:
|
||||
if win_id == 'current':
|
||||
log.misc.debug("Queueing {} for window {}".format(method_name,
|
||||
win_id))
|
||||
msg = QueuedMsg(time=datetime.datetime.now(), win_id=win_id,
|
||||
method_name=method_name, text=text, args=args,
|
||||
kwargs=kwargs)
|
||||
_QUEUED.append(msg)
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
getattr(bridge, method_name)(text, *args, **kwargs)
|
||||
|
||||
|
||||
def _get_bridge(win_id):
|
||||
"""Get the correct MessageBridge instance for a window."""
|
||||
try:
|
||||
int(win_id)
|
||||
except ValueError:
|
||||
if win_id == 'current':
|
||||
pass
|
||||
else:
|
||||
raise ValueError("Invalid window id {} - needs to be 'current' or "
|
||||
"a valid integer!".format(win_id))
|
||||
return objreg.get('message-bridge', scope='window', window=win_id)
|
||||
|
||||
|
||||
@pyqtSlot()
|
||||
def on_focus_changed():
|
||||
"""Gets called when a new window has been focused."""
|
||||
while _QUEUED:
|
||||
msg = _QUEUED.pop()
|
||||
delta = datetime.datetime.now() - msg.time
|
||||
log.misc.debug("Handling queued {} for window {}, delta {}".format(
|
||||
msg.method_name, msg.win_id, delta))
|
||||
bridge = _get_bridge(msg.win_id)
|
||||
text = '[{} ago] {}'.format(utils.format_timedelta(delta), msg.text)
|
||||
getattr(bridge, msg.method_name)(text, *msg.args, **msg.kwargs)
|
||||
|
||||
|
||||
def error(win_id, message, immediately=False):
|
||||
"""Convienience function to display an error message in the statusbar.
|
||||
|
||||
@ -36,8 +93,7 @@ def error(win_id, message, immediately=False):
|
||||
win_id: The ID of the window which is calling this function.
|
||||
others: See MessageBridge.error.
|
||||
"""
|
||||
QTimer.singleShot(
|
||||
0, lambda: _get_bridge(win_id).error(message, immediately))
|
||||
_wrapper(win_id, 'error', message, immediately)
|
||||
|
||||
|
||||
def warning(win_id, message, immediately=False):
|
||||
@ -47,8 +103,7 @@ def warning(win_id, message, immediately=False):
|
||||
win_id: The ID of the window which is calling this function.
|
||||
others: See MessageBridge.warning.
|
||||
"""
|
||||
QTimer.singleShot(
|
||||
0, lambda: _get_bridge(win_id).warning(message, immediately))
|
||||
_wrapper(win_id, 'warning', message, immediately)
|
||||
|
||||
|
||||
def info(win_id, message, immediately=True):
|
||||
@ -58,13 +113,12 @@ def info(win_id, message, immediately=True):
|
||||
win_id: The ID of the window which is calling this function.
|
||||
others: See MessageBridge.info.
|
||||
"""
|
||||
QTimer.singleShot(
|
||||
0, lambda: _get_bridge(win_id).info(message, immediately))
|
||||
_wrapper(win_id, 'info', message, immediately)
|
||||
|
||||
|
||||
def set_cmd_text(win_id, txt):
|
||||
"""Convienience function to Set the statusbar command line to a text."""
|
||||
_get_bridge(win_id).set_cmd_text(txt)
|
||||
_wrapper(win_id, 'set_cmd_text', txt)
|
||||
|
||||
|
||||
def ask(win_id, message, mode, default=None):
|
||||
@ -98,7 +152,7 @@ def alert(win_id, message):
|
||||
q = usertypes.Question()
|
||||
q.text = message
|
||||
q.mode = usertypes.PromptMode.alert
|
||||
_get_bridge(win_id).ask(q, blocking=True)
|
||||
_wrapper(win_id, 'ask', q, blocking=True)
|
||||
q.deleteLater()
|
||||
|
||||
|
||||
@ -114,14 +168,13 @@ def ask_async(win_id, message, mode, handler, default=None):
|
||||
"""
|
||||
if not isinstance(mode, usertypes.PromptMode):
|
||||
raise TypeError("Mode {} is no PromptMode member!".format(mode))
|
||||
bridge = _get_bridge(win_id)
|
||||
q = usertypes.Question(bridge)
|
||||
q = usertypes.Question()
|
||||
q.text = message
|
||||
q.mode = mode
|
||||
q.default = default
|
||||
q.answered.connect(handler)
|
||||
q.completed.connect(q.deleteLater)
|
||||
bridge.ask(q, blocking=False)
|
||||
_wrapper(win_id, 'ask', q, blocking=False)
|
||||
|
||||
|
||||
def confirm_async(win_id, message, yes_action, no_action=None, default=None):
|
||||
@ -134,8 +187,7 @@ def confirm_async(win_id, message, yes_action, no_action=None, default=None):
|
||||
no_action: Callable to be called when the user answered no.
|
||||
default: True/False to set a default value, or None.
|
||||
"""
|
||||
bridge = _get_bridge(win_id)
|
||||
q = usertypes.Question(bridge)
|
||||
q = usertypes.Question()
|
||||
q.text = message
|
||||
q.mode = usertypes.PromptMode.yesno
|
||||
q.default = default
|
||||
@ -143,7 +195,7 @@ def confirm_async(win_id, message, yes_action, no_action=None, default=None):
|
||||
if no_action is not None:
|
||||
q.answered_no.connect(no_action)
|
||||
q.completed.connect(q.deleteLater)
|
||||
bridge.ask(q, blocking=False)
|
||||
_wrapper(win_id, 'ask', q, blocking=False)
|
||||
|
||||
|
||||
class MessageBridge(QObject):
|
||||
@ -185,19 +237,6 @@ class MessageBridge(QObject):
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self)
|
||||
|
||||
def _emit_later(self, signal, *args):
|
||||
"""Emit a message later when the mainloop is not busy anymore.
|
||||
|
||||
This is especially useful when messages are sent during init, before
|
||||
the messagebridge signals are connected - messages would get lost if we
|
||||
did normally emit them.
|
||||
|
||||
Args:
|
||||
signal: The signal to be emitted.
|
||||
*args: Args to be passed to the signal.
|
||||
"""
|
||||
QTimer.singleShot(0, lambda: signal.emit(*args))
|
||||
|
||||
def error(self, msg, immediately=False):
|
||||
"""Display an error in the statusbar.
|
||||
|
||||
@ -211,7 +250,7 @@ class MessageBridge(QObject):
|
||||
"""
|
||||
msg = str(msg)
|
||||
log.misc.error(msg)
|
||||
self._emit_later(self.s_error, msg, immediately)
|
||||
self.s_error.emit(msg, immediately)
|
||||
|
||||
def warning(self, msg, immediately=False):
|
||||
"""Display an warning in the statusbar.
|
||||
@ -226,7 +265,7 @@ class MessageBridge(QObject):
|
||||
"""
|
||||
msg = str(msg)
|
||||
log.misc.warning(msg)
|
||||
self._emit_later(self.s_warning, msg, immediately)
|
||||
self.s_warning.emit(msg, immediately)
|
||||
|
||||
def info(self, msg, immediately=True):
|
||||
"""Display an info text in the statusbar.
|
||||
@ -237,7 +276,7 @@ class MessageBridge(QObject):
|
||||
"""
|
||||
msg = str(msg)
|
||||
log.misc.info(msg)
|
||||
self._emit_later(self.s_info, msg, immediately)
|
||||
self.s_info.emit(msg, immediately)
|
||||
|
||||
def set_cmd_text(self, text):
|
||||
"""Set the command text of the statusbar.
|
||||
@ -247,7 +286,7 @@ class MessageBridge(QObject):
|
||||
"""
|
||||
text = str(text)
|
||||
log.misc.debug(text)
|
||||
self._emit_later(self.s_set_cmd_text, text)
|
||||
self.s_set_cmd_text.emit(text)
|
||||
|
||||
def set_text(self, text):
|
||||
"""Set the normal text of the statusbar.
|
||||
@ -257,7 +296,7 @@ class MessageBridge(QObject):
|
||||
"""
|
||||
text = str(text)
|
||||
log.misc.debug(text)
|
||||
self._emit_later(self.s_set_text, text)
|
||||
self.s_set_text.emit(text)
|
||||
|
||||
def maybe_reset_text(self, text):
|
||||
"""Reset the text in the statusbar if it matches an expected text.
|
||||
@ -265,7 +304,7 @@ class MessageBridge(QObject):
|
||||
Args:
|
||||
text: The expected text.
|
||||
"""
|
||||
self._emit_later(self.s_maybe_reset_text, str(text))
|
||||
self.s_maybe_reset_text.emit(str(text))
|
||||
|
||||
def ask(self, question, blocking):
|
||||
"""Ask a question to the user.
|
||||
@ -273,9 +312,6 @@ class MessageBridge(QObject):
|
||||
Note this method doesn't return the answer, it only blocks. The caller
|
||||
needs to construct a Question object and get the answer.
|
||||
|
||||
We don't use _emit_later here as this makes no sense with a blocking
|
||||
question.
|
||||
|
||||
Args:
|
||||
question: A Question object.
|
||||
blocking: Whether to return immediately or wait until the
|
||||
|
@ -201,6 +201,21 @@ def format_seconds(total_seconds):
|
||||
return prefix + ':'.join(chunks)
|
||||
|
||||
|
||||
def format_timedelta(td):
|
||||
"""Format a timedelta to get a "1h 5m 1s" string."""
|
||||
prefix = '-' if td.total_seconds() < 0 else ''
|
||||
hours, rem = divmod(abs(round(td.total_seconds())), 3600)
|
||||
minutes, seconds = divmod(rem, 60)
|
||||
chunks = []
|
||||
if hours:
|
||||
chunks.append('{}h'.format(hours))
|
||||
if minutes:
|
||||
chunks.append('{}m'.format(minutes))
|
||||
if seconds or not chunks:
|
||||
chunks.append('{}s'.format(seconds))
|
||||
return prefix + ' '.join(chunks)
|
||||
|
||||
|
||||
def format_size(size, base=1024, suffix=''):
|
||||
"""Format a byte size so it's human readable.
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user