parent
19d369377e
commit
157c25bb13
@ -467,6 +467,7 @@ class Application(QApplication):
|
|||||||
self.lastWindowClosed.connect(self.on_last_window_closed)
|
self.lastWindowClosed.connect(self.on_last_window_closed)
|
||||||
config_obj.style_changed.connect(style.get_stylesheet.cache_clear)
|
config_obj.style_changed.connect(style.get_stylesheet.cache_clear)
|
||||||
self.focusChanged.connect(self.on_focus_changed)
|
self.focusChanged.connect(self.on_focus_changed)
|
||||||
|
self.focusChanged.connect(message.on_focus_changed)
|
||||||
|
|
||||||
def _get_widgets(self):
|
def _get_widgets(self):
|
||||||
"""Get a string list of all widgets."""
|
"""Get a string list of all widgets."""
|
||||||
|
@ -25,8 +25,6 @@ import functools
|
|||||||
import posixpath
|
import posixpath
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
from PyQt5.QtCore import QTimer
|
|
||||||
|
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
from qutebrowser.utils import objreg, standarddir, log, message
|
from qutebrowser.utils import objreg, standarddir, log, message
|
||||||
from qutebrowser.commands import cmdutils
|
from qutebrowser.commands import cmdutils
|
||||||
@ -107,9 +105,8 @@ class HostBlocker:
|
|||||||
log.misc.exception("Failed to read host blocklist!")
|
log.misc.exception("Failed to read host blocklist!")
|
||||||
else:
|
else:
|
||||||
if config.get('content', 'host-block-lists') is not None:
|
if config.get('content', 'host-block-lists') is not None:
|
||||||
QTimer.singleShot(500, functools.partial(
|
message.info('current',
|
||||||
message.info, 'last-focused',
|
"Run :adblock-update to get adblock lists.")
|
||||||
"Run :adblock-update to get adblock lists."))
|
|
||||||
|
|
||||||
@cmdutils.register(instance='host-blocker')
|
@cmdutils.register(instance='host-blocker')
|
||||||
def adblock_update(self, win_id: {'special': 'win_id'}):
|
def adblock_update(self, win_id: {'special': 'win_id'}):
|
||||||
@ -156,9 +153,8 @@ class HostBlocker:
|
|||||||
f = get_fileobj(byte_io)
|
f = get_fileobj(byte_io)
|
||||||
except (OSError, UnicodeDecodeError, zipfile.BadZipFile,
|
except (OSError, UnicodeDecodeError, zipfile.BadZipFile,
|
||||||
zipfile.LargeZipFile) as e:
|
zipfile.LargeZipFile) as e:
|
||||||
message.error('last-focused', "adblock: Error while reading {}: "
|
message.error('current', "adblock: Error while reading {}: {} - "
|
||||||
"{} - {}".format(
|
"{}".format(byte_io.name, e.__class__.__name__, e))
|
||||||
byte_io.name, e.__class__.__name__, e))
|
|
||||||
return
|
return
|
||||||
for line in f:
|
for line in f:
|
||||||
line_count += 1
|
line_count += 1
|
||||||
@ -186,17 +182,16 @@ class HostBlocker:
|
|||||||
self.blocked_hosts.add(host)
|
self.blocked_hosts.add(host)
|
||||||
log.misc.debug("{}: read {} lines".format(byte_io.name, line_count))
|
log.misc.debug("{}: read {} lines".format(byte_io.name, line_count))
|
||||||
if error_count > 0:
|
if error_count > 0:
|
||||||
message.error('last-focused', "adblock: {} read errors for "
|
message.error('current', "adblock: {} read errors for {}".format(
|
||||||
"{}".format(error_count, byte_io.name))
|
error_count, byte_io.name))
|
||||||
|
|
||||||
def on_lists_downloaded(self):
|
def on_lists_downloaded(self):
|
||||||
"""Install block lists after files have been downloaded."""
|
"""Install block lists after files have been downloaded."""
|
||||||
with open(self._hosts_file, 'w', encoding='utf-8') as f:
|
with open(self._hosts_file, 'w', encoding='utf-8') as f:
|
||||||
for host in sorted(self.blocked_hosts):
|
for host in sorted(self.blocked_hosts):
|
||||||
f.write(host + '\n')
|
f.write(host + '\n')
|
||||||
message.info('last-focused', "adblock: Read {} hosts from {} "
|
message.info('current', "adblock: Read {} hosts from {} sources."
|
||||||
"sources.".format(len(self.blocked_hosts),
|
.format(len(self.blocked_hosts), self._done_count))
|
||||||
self._done_count))
|
|
||||||
|
|
||||||
@config.change_filter('content', 'host-block-lists')
|
@config.change_filter('content', 'host-block-lists')
|
||||||
def on_config_changed(self):
|
def on_config_changed(self):
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
import sys
|
import sys
|
||||||
import enum
|
import enum
|
||||||
import unittest
|
import unittest
|
||||||
|
import datetime
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
@ -192,6 +193,37 @@ class FormatSecondsTests(unittest.TestCase):
|
|||||||
self.assertEqual(utils.format_seconds(seconds), out)
|
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):
|
class FormatSizeTests(unittest.TestCase):
|
||||||
|
|
||||||
"""Tests for format_size.
|
"""Tests for format_size.
|
||||||
|
@ -19,16 +19,73 @@
|
|||||||
|
|
||||||
"""Message singleton so we don't have to define unneeded signals."""
|
"""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
|
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):
|
def _get_bridge(win_id):
|
||||||
"""Get the correct MessageBridge instance for a window."""
|
"""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)
|
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):
|
def error(win_id, message, immediately=False):
|
||||||
"""Convienience function to display an error message in the statusbar.
|
"""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.
|
win_id: The ID of the window which is calling this function.
|
||||||
others: See MessageBridge.error.
|
others: See MessageBridge.error.
|
||||||
"""
|
"""
|
||||||
QTimer.singleShot(
|
_wrapper(win_id, 'error', message, immediately)
|
||||||
0, lambda: _get_bridge(win_id).error(message, immediately))
|
|
||||||
|
|
||||||
|
|
||||||
def warning(win_id, message, immediately=False):
|
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.
|
win_id: The ID of the window which is calling this function.
|
||||||
others: See MessageBridge.warning.
|
others: See MessageBridge.warning.
|
||||||
"""
|
"""
|
||||||
QTimer.singleShot(
|
_wrapper(win_id, 'warning', message, immediately)
|
||||||
0, lambda: _get_bridge(win_id).warning(message, immediately))
|
|
||||||
|
|
||||||
|
|
||||||
def info(win_id, message, immediately=True):
|
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.
|
win_id: The ID of the window which is calling this function.
|
||||||
others: See MessageBridge.info.
|
others: See MessageBridge.info.
|
||||||
"""
|
"""
|
||||||
QTimer.singleShot(
|
_wrapper(win_id, 'info', message, immediately)
|
||||||
0, lambda: _get_bridge(win_id).info(message, immediately))
|
|
||||||
|
|
||||||
|
|
||||||
def set_cmd_text(win_id, txt):
|
def set_cmd_text(win_id, txt):
|
||||||
"""Convienience function to Set the statusbar command line to a text."""
|
"""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):
|
def ask(win_id, message, mode, default=None):
|
||||||
@ -98,7 +152,7 @@ def alert(win_id, message):
|
|||||||
q = usertypes.Question()
|
q = usertypes.Question()
|
||||||
q.text = message
|
q.text = message
|
||||||
q.mode = usertypes.PromptMode.alert
|
q.mode = usertypes.PromptMode.alert
|
||||||
_get_bridge(win_id).ask(q, blocking=True)
|
_wrapper(win_id, 'ask', q, blocking=True)
|
||||||
q.deleteLater()
|
q.deleteLater()
|
||||||
|
|
||||||
|
|
||||||
@ -114,14 +168,13 @@ def ask_async(win_id, message, mode, handler, default=None):
|
|||||||
"""
|
"""
|
||||||
if not isinstance(mode, usertypes.PromptMode):
|
if not isinstance(mode, usertypes.PromptMode):
|
||||||
raise TypeError("Mode {} is no PromptMode member!".format(mode))
|
raise TypeError("Mode {} is no PromptMode member!".format(mode))
|
||||||
bridge = _get_bridge(win_id)
|
q = usertypes.Question()
|
||||||
q = usertypes.Question(bridge)
|
|
||||||
q.text = message
|
q.text = message
|
||||||
q.mode = mode
|
q.mode = mode
|
||||||
q.default = default
|
q.default = default
|
||||||
q.answered.connect(handler)
|
q.answered.connect(handler)
|
||||||
q.completed.connect(q.deleteLater)
|
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):
|
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.
|
no_action: Callable to be called when the user answered no.
|
||||||
default: True/False to set a default value, or None.
|
default: True/False to set a default value, or None.
|
||||||
"""
|
"""
|
||||||
bridge = _get_bridge(win_id)
|
q = usertypes.Question()
|
||||||
q = usertypes.Question(bridge)
|
|
||||||
q.text = message
|
q.text = message
|
||||||
q.mode = usertypes.PromptMode.yesno
|
q.mode = usertypes.PromptMode.yesno
|
||||||
q.default = default
|
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:
|
if no_action is not None:
|
||||||
q.answered_no.connect(no_action)
|
q.answered_no.connect(no_action)
|
||||||
q.completed.connect(q.deleteLater)
|
q.completed.connect(q.deleteLater)
|
||||||
bridge.ask(q, blocking=False)
|
_wrapper(win_id, 'ask', q, blocking=False)
|
||||||
|
|
||||||
|
|
||||||
class MessageBridge(QObject):
|
class MessageBridge(QObject):
|
||||||
@ -185,19 +237,6 @@ class MessageBridge(QObject):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return utils.get_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):
|
def error(self, msg, immediately=False):
|
||||||
"""Display an error in the statusbar.
|
"""Display an error in the statusbar.
|
||||||
|
|
||||||
@ -211,7 +250,7 @@ class MessageBridge(QObject):
|
|||||||
"""
|
"""
|
||||||
msg = str(msg)
|
msg = str(msg)
|
||||||
log.misc.error(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):
|
def warning(self, msg, immediately=False):
|
||||||
"""Display an warning in the statusbar.
|
"""Display an warning in the statusbar.
|
||||||
@ -226,7 +265,7 @@ class MessageBridge(QObject):
|
|||||||
"""
|
"""
|
||||||
msg = str(msg)
|
msg = str(msg)
|
||||||
log.misc.warning(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):
|
def info(self, msg, immediately=True):
|
||||||
"""Display an info text in the statusbar.
|
"""Display an info text in the statusbar.
|
||||||
@ -237,7 +276,7 @@ class MessageBridge(QObject):
|
|||||||
"""
|
"""
|
||||||
msg = str(msg)
|
msg = str(msg)
|
||||||
log.misc.info(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):
|
def set_cmd_text(self, text):
|
||||||
"""Set the command text of the statusbar.
|
"""Set the command text of the statusbar.
|
||||||
@ -247,7 +286,7 @@ class MessageBridge(QObject):
|
|||||||
"""
|
"""
|
||||||
text = str(text)
|
text = str(text)
|
||||||
log.misc.debug(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):
|
def set_text(self, text):
|
||||||
"""Set the normal text of the statusbar.
|
"""Set the normal text of the statusbar.
|
||||||
@ -257,7 +296,7 @@ class MessageBridge(QObject):
|
|||||||
"""
|
"""
|
||||||
text = str(text)
|
text = str(text)
|
||||||
log.misc.debug(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):
|
def maybe_reset_text(self, text):
|
||||||
"""Reset the text in the statusbar if it matches an expected text.
|
"""Reset the text in the statusbar if it matches an expected text.
|
||||||
@ -265,7 +304,7 @@ class MessageBridge(QObject):
|
|||||||
Args:
|
Args:
|
||||||
text: The expected text.
|
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):
|
def ask(self, question, blocking):
|
||||||
"""Ask a question to the user.
|
"""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
|
Note this method doesn't return the answer, it only blocks. The caller
|
||||||
needs to construct a Question object and get the answer.
|
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:
|
Args:
|
||||||
question: A Question object.
|
question: A Question object.
|
||||||
blocking: Whether to return immediately or wait until the
|
blocking: Whether to return immediately or wait until the
|
||||||
|
@ -201,6 +201,21 @@ def format_seconds(total_seconds):
|
|||||||
return prefix + ':'.join(chunks)
|
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=''):
|
def format_size(size, base=1024, suffix=''):
|
||||||
"""Format a byte size so it's human readable.
|
"""Format a byte size so it's human readable.
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user