Add --datadir/--cachedir arguments. Closes #136.

This commit is contained in:
Florian Bruhin 2015-05-16 22:12:27 +02:00
parent 9b372de4a9
commit c762340a0c
14 changed files with 213 additions and 80 deletions

View File

@ -41,6 +41,12 @@ show it.
*-c* 'CONFDIR', *--confdir* 'CONFDIR'::
Set config directory (empty for no config storage).
*--datadir* 'DATADIR'::
Set data directory (empty for no data storage).
*--cachedir* 'CACHEDIR'::
Set cache directory (empty for no cache storage).
*-V*, *--version*::
Show version and quit.

View File

@ -27,7 +27,7 @@ import zipfile
from qutebrowser.config import config
from qutebrowser.utils import objreg, standarddir, log, message
from qutebrowser.commands import cmdutils
from qutebrowser.commands import cmdutils, cmdexc
def guess_zip_filename(zf):
@ -90,12 +90,18 @@ class HostBlocker:
self.blocked_hosts = set()
self._in_progress = []
self._done_count = 0
self._hosts_file = os.path.join(standarddir.data(), 'blocked-hosts')
data_dir = standarddir.data()
if data_dir is None:
self._hosts_file = None
else:
self._hosts_file = os.path.join(data_dir, 'blocked-hosts')
objreg.get('config').changed.connect(self.on_config_changed)
def read_hosts(self):
"""Read hosts from the existing blocked-hosts file."""
self.blocked_hosts = set()
if self._hosts_file is None:
return
if os.path.exists(self._hosts_file):
try:
with open(self._hosts_file, 'r', encoding='utf-8') as f:
@ -111,6 +117,8 @@ class HostBlocker:
@cmdutils.register(instance='host-blocker', win_id='win_id')
def adblock_update(self, win_id):
"""Update the adblock block lists."""
if self._hosts_file is None:
raise cmdexc.CommandError("No data storage is configured!")
self.blocked_hosts = set()
self._done_count = 0
urls = config.get('content', 'host-block-lists')

View File

@ -21,6 +21,7 @@
import os.path
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtNetwork import QNetworkDiskCache, QNetworkCacheMetaData
from qutebrowser.config import config
@ -29,23 +30,41 @@ from qutebrowser.utils import utils, standarddir, objreg
class DiskCache(QNetworkDiskCache):
"""Disk cache which sets correct cache dir and size."""
"""Disk cache which sets correct cache dir and size.
Attributes:
_activated: Whether the cache should be used.
"""
def __init__(self, parent=None):
super().__init__(parent)
self.setCacheDirectory(os.path.join(standarddir.cache(), 'http'))
cache_dir = standarddir.cache()
if config.get('general', 'private-browsing') or cache_dir is None:
self._activated = False
else:
self._activated = True
self.setCacheDirectory(os.path.join(standarddir.cache(), 'http'))
self.setMaximumCacheSize(config.get('storage', 'cache-size'))
objreg.get('config').changed.connect(self.cache_size_changed)
objreg.get('config').changed.connect(self.on_config_changed)
def __repr__(self):
return utils.get_repr(self, size=self.cacheSize(),
maxsize=self.maximumCacheSize(),
path=self.cacheDirectory())
@config.change_filter('storage', 'cache-size')
def cache_size_changed(self):
"""Update cache size if the config was changed."""
self.setMaximumCacheSize(config.get('storage', 'cache-size'))
@pyqtSlot()
def on_config_changed(self, section, option):
"""Update cache size/activated if the config was changed."""
if (section, option) == ('storage', 'cache-size'):
self.setMaximumCacheSize(config.get('storage', 'cache-size'))
elif (section, option) == ('general', 'private-browsing'):
if (config.get('general', 'private-browsing') or
standarddir.cache() is None):
self._activated = False
else:
self._activated = True
self.setCacheDirectory(
os.path.join(standarddir.cache(), 'http'))
def cacheSize(self):
"""Return the current size taken up by the cache.
@ -53,10 +72,10 @@ class DiskCache(QNetworkDiskCache):
Return:
An int.
"""
if config.get('general', 'private-browsing'):
return 0
else:
if self._activated:
return super().cacheSize()
else:
return 0
def fileMetaData(self, filename):
"""Return the QNetworkCacheMetaData for the cache file filename.
@ -67,10 +86,10 @@ class DiskCache(QNetworkDiskCache):
Return:
A QNetworkCacheMetaData object.
"""
if config.get('general', 'private-browsing'):
return QNetworkCacheMetaData()
else:
if self._activated:
return super().fileMetaData(filename)
else:
return QNetworkCacheMetaData()
def data(self, url):
"""Return the data associated with url.
@ -81,10 +100,10 @@ class DiskCache(QNetworkDiskCache):
return:
A QIODevice or None.
"""
if config.get('general', 'private-browsing'):
return None
else:
if self._activated:
return super().data(url)
else:
return None
def insert(self, device):
"""Insert the data in device and the prepared meta data into the cache.
@ -92,10 +111,10 @@ class DiskCache(QNetworkDiskCache):
Args:
device: A QIODevice.
"""
if config.get('general', 'private-browsing'):
return
else:
if self._activated:
super().insert(device)
else:
return None
def metaData(self, url):
"""Return the meta data for the url url.
@ -106,10 +125,10 @@ class DiskCache(QNetworkDiskCache):
Return:
A QNetworkCacheMetaData object.
"""
if config.get('general', 'private-browsing'):
return QNetworkCacheMetaData()
else:
if self._activated:
return super().metaData(url)
else:
return QNetworkCacheMetaData()
def prepare(self, meta_data):
"""Return the device that should be populated with the data.
@ -120,10 +139,10 @@ class DiskCache(QNetworkDiskCache):
Return:
A QIODevice or None.
"""
if config.get('general', 'private-browsing'):
return None
else:
if self._activated:
return super().prepare(meta_data)
else:
return None
def remove(self, url):
"""Remove the cache entry for url.
@ -131,10 +150,10 @@ class DiskCache(QNetworkDiskCache):
Return:
True on success, False otherwise.
"""
if config.get('general', 'private-browsing'):
return False
else:
if self._activated:
return super().remove(url)
else:
return False
def updateMetaData(self, meta_data):
"""Update the cache meta date for the meta_data's url to meta_data.
@ -142,14 +161,14 @@ class DiskCache(QNetworkDiskCache):
Args:
meta_data: A QNetworkCacheMetaData object.
"""
if config.get('general', 'private-browsing'):
return
else:
if self._activated:
super().updateMetaData(meta_data)
else:
return
def clear(self):
"""Remove all items from the cache."""
if config.get('general', 'private-browsing'):
return
else:
if self._activated:
super().clear()
else:
return

View File

@ -83,6 +83,28 @@ class WebHistory(QWebHistoryInterface):
self._lineparser = lineparser.AppendLineParser(
standarddir.data(), 'history', parent=self)
self._history_dict = collections.OrderedDict()
self._read_history()
self._new_history = []
self._saved_count = 0
objreg.get('save-manager').add_saveable(
'history', self.save, self.item_added)
def __repr__(self):
return utils.get_repr(self, length=len(self))
def __getitem__(self, key):
return self._new_history[key]
def __iter__(self):
return iter(self._history_dict.values())
def __len__(self):
return len(self._history_dict)
def _read_history(self):
"""Read the initial history."""
if standarddir.data() is None:
return
with self._lineparser.open():
for line in self._lineparser:
data = line.rstrip().split(maxsplit=1)
@ -108,22 +130,6 @@ class WebHistory(QWebHistoryInterface):
# list of atimes.
self._history_dict[url] = HistoryEntry(atime, url)
self._history_dict.move_to_end(url)
self._new_history = []
self._saved_count = 0
objreg.get('save-manager').add_saveable(
'history', self.save, self.item_added)
def __repr__(self):
return utils.get_repr(self, length=len(self))
def __getitem__(self, key):
return self._new_history[key]
def __iter__(self):
return iter(self._history_dict.values())
def __len__(self):
return len(self._history_dict)
def get_recent(self):
"""Get the most recent history entries."""

View File

@ -47,11 +47,15 @@ class ReadConfigParser(configparser.ConfigParser):
self.optionxform = lambda opt: opt # be case-insensitive
self._configdir = configdir
self._fname = fname
if self._configdir is None:
self._configfile = None
return
self._configfile = os.path.join(self._configdir, fname)
if not os.path.isfile(self._configfile):
return
log.init.debug("Reading config from {}".format(self._configfile))
self.read(self._configfile, encoding='utf-8')
if self._configfile is not None:
self.read(self._configfile, encoding='utf-8')
def __repr__(self):
return utils.get_repr(self, constructor=True,
@ -64,6 +68,8 @@ class ReadWriteConfigParser(ReadConfigParser):
def save(self):
"""Save the config file."""
if self._configdir is None:
return
if not os.path.exists(self._configdir):
os.makedirs(self._configdir, 0o755)
log.destroy.debug("Saving config to {}".format(self._configfile))

View File

@ -377,16 +377,20 @@ MAPPINGS = {
def init():
"""Initialize the global QWebSettings."""
if config.get('general', 'private-browsing'):
cache_path = standarddir.cache()
data_path = standarddir.data()
if config.get('general', 'private-browsing') or cache_path is None:
QWebSettings.setIconDatabasePath('')
else:
QWebSettings.setIconDatabasePath(standarddir.cache())
QWebSettings.setOfflineWebApplicationCachePath(
os.path.join(standarddir.cache(), 'application-cache'))
QWebSettings.globalSettings().setLocalStoragePath(
os.path.join(standarddir.data(), 'local-storage'))
QWebSettings.setOfflineStoragePath(
os.path.join(standarddir.data(), 'offline-storage'))
QWebSettings.setIconDatabasePath(cache_path)
if cache_path is not None:
QWebSettings.setOfflineWebApplicationCachePath(
os.path.join(cache_path, 'application-cache'))
if data_path is not None:
QWebSettings.globalSettings().setLocalStoragePath(
os.path.join(data_path, 'local-storage'))
QWebSettings.setOfflineStoragePath(
os.path.join(data_path, 'offline-storage'))
for sectname, section in MAPPINGS.items():
for optname, mapping in section.items():
@ -402,11 +406,12 @@ def init():
def update_settings(section, option):
"""Update global settings when qwebsettings changed."""
cache_path = standarddir.cache()
if (section, option) == ('general', 'private-browsing'):
if config.get('general', 'private-browsing'):
if config.get('general', 'private-browsing') or cache_path is None:
QWebSettings.setIconDatabasePath('')
else:
QWebSettings.setIconDatabasePath(standarddir.cache())
QWebSettings.setIconDatabasePath(cache_path)
else:
try:
mapping = MAPPINGS[section][option]

View File

@ -63,7 +63,10 @@ class CrashHandler(QObject):
def handle_segfault(self):
"""Handle a segfault from a previous run."""
logname = os.path.join(standarddir.data(), 'crash.log')
data_dir = None
if data_dir is None:
return
logname = os.path.join(data_dir, 'crash.log')
try:
# First check if an old logfile exists.
if os.path.exists(logname):
@ -118,7 +121,10 @@ class CrashHandler(QObject):
def _init_crashlogfile(self):
"""Start a new logfile and redirect faulthandler to it."""
logname = os.path.join(standarddir.data(), 'crash.log')
data_dir = standarddir.data()
if data_dir is None:
return
logname = os.path.join(data_dir, 'crash.log')
try:
self._crash_log_file = open(logname, 'w', encoding='ascii')
except OSError:

View File

@ -35,7 +35,7 @@ class BaseLineParser(QObject):
"""A LineParser without any real data.
Attributes:
_configdir: The directory to read the config from.
_configdir: Directory to read the config from, or None.
_configfile: The config file path.
_fname: Filename of the config.
_binary: Whether to open the file in binary mode.
@ -57,7 +57,10 @@ class BaseLineParser(QObject):
"""
super().__init__(parent)
self._configdir = configdir
self._configfile = os.path.join(self._configdir, fname)
if self._configdir is None:
self._configfile = None
else:
self._configfile = os.path.join(self._configdir, fname)
self._fname = fname
self._binary = binary
self._opened = False
@ -68,10 +71,17 @@ class BaseLineParser(QObject):
binary=self._binary)
def _prepare_save(self):
"""Prepare saving of the file."""
"""Prepare saving of the file.
Return:
True if the file should be saved, False otherwise.
"""
if self._configdir is None:
return False
log.destroy.debug("Saving to {}".format(self._configfile))
if not os.path.exists(self._configdir):
os.makedirs(self._configdir, 0o755)
return True
@contextlib.contextmanager
def _open(self, mode):
@ -80,6 +90,7 @@ class BaseLineParser(QObject):
Args:
mode: The mode to use ('a'/'r'/'w')
"""
assert self._configfile is not None
if self._opened:
raise IOError("Refusing to double-open AppendLineParser.")
self._opened = True
@ -159,7 +170,9 @@ class AppendLineParser(BaseLineParser):
return data
def save(self):
self._prepare_save()
do_save = self._prepare_save()
if not do_save:
return
with self._open('a') as f:
self._write(f, self.new_data)
self.new_data = []
@ -182,7 +195,7 @@ class LineParser(BaseLineParser):
binary: Whether to open the file in binary mode.
"""
super().__init__(configdir, fname, binary=binary, parent=parent)
if not os.path.isfile(self._configfile):
if configdir is None or not os.path.isfile(self._configfile):
self.data = []
else:
log.init.debug("Reading {}".format(self._configfile))
@ -206,8 +219,11 @@ class LineParser(BaseLineParser):
"""Save the config file."""
if self._opened:
raise IOError("Refusing to double-open AppendLineParser.")
do_save = self._prepare_save()
if not do_save:
return
self._opened = True
self._prepare_save()
assert self._configfile is not None
with qtutils.savefile_open(self._configfile, self._binary) as f:
self._write(f, self.data)
self._opened = False
@ -226,14 +242,14 @@ class LimitLineParser(LineParser):
"""Constructor.
Args:
configdir: Directory to read the config from.
configdir: Directory to read the config from, or None.
fname: Filename of the config file.
limit: Config tuple (section, option) which contains a limit.
binary: Whether to open the file in binary mode.
"""
super().__init__(configdir, fname, binary=binary, parent=parent)
self._limit = limit
if limit is not None:
if limit is not None and configdir is not None:
objreg.get('config').changed.connect(self.cleanup_file)
def __repr__(self):
@ -244,6 +260,7 @@ class LimitLineParser(LineParser):
@pyqtSlot(str, str)
def cleanup_file(self, section, option):
"""Delete the file if the limit was changed to 0."""
assert self._configfile is not None
if (section, option) != self._limit:
return
value = config.get(section, option)
@ -256,6 +273,9 @@ class LimitLineParser(LineParser):
limit = config.get(*self._limit)
if limit == 0:
return
self._prepare_save()
do_save = self._prepare_save()
if not do_save:
return
assert self._configfile is not None
with qtutils.savefile_open(self._configfile, self._binary) as f:
self._write(f, self.data[-limit:])

View File

@ -82,10 +82,14 @@ class SessionManager(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self._current = None
self._base_path = os.path.join(standarddir.data(), 'sessions')
data_dir = standarddir.data()
if data_dir is None:
self._base_path = None
else:
self._base_path = os.path.join(standarddir.data(), 'sessions')
self._last_window_session = None
self.did_load = False
if not os.path.exists(self._base_path):
if self._base_path is not None and not os.path.exists(self._base_path):
os.mkdir(self._base_path)
def _get_session_path(self, name, check_exists=False):
@ -100,6 +104,11 @@ class SessionManager(QObject):
if os.path.isabs(path) and ((not check_exists) or
os.path.exists(path)):
return path
elif self._base_path is None:
if check_exists:
raise SessionNotFoundError(name)
else:
return None
else:
path = os.path.join(self._base_path, name + '.yml')
if check_exists and not os.path.exists(path):
@ -194,6 +203,8 @@ class SessionManager(QObject):
else:
name = 'default'
path = self._get_session_path(name)
if path is None:
raise SessionError("No data storage configured.")
log.sessions.debug("Saving session {} to {}...".format(name, path))
if last_window:
@ -289,6 +300,8 @@ class SessionManager(QObject):
def list_sessions(self):
"""Get a list of all session names."""
sessions = []
if self._base_path is None:
return sessions
for filename in os.listdir(self._base_path):
base, ext = os.path.splitext(filename)
if ext == '.yml':

View File

@ -48,6 +48,10 @@ def get_argparser():
description=qutebrowser.__description__)
parser.add_argument('-c', '--confdir', help="Set config directory (empty "
"for no config storage).")
parser.add_argument('--datadir', help="Set data directory (empty for "
"no data storage).")
parser.add_argument('--cachedir', help="Set cache directory (empty for "
"no cache storage).")
parser.add_argument('-V', '--version', help="Show version and quit.",
action='store_true')
parser.add_argument('-s', '--set', help="Set a temporary setting for "

View File

@ -80,7 +80,9 @@ def _from_args(typ, args):
path: The overridden path, or None to turn off storage.
"""
typ_to_argparse_arg = {
QStandardPaths.ConfigLocation: 'confdir'
QStandardPaths.ConfigLocation: 'confdir',
QStandardPaths.DataLocation: 'datadir',
QStandardPaths.CacheLocation: 'cachedir',
}
if args is None:
return (False, None)
@ -135,8 +137,18 @@ def init(args):
"""Initialize all standard dirs."""
global _args
_args = args
# http://www.brynosaurus.com/cachedir/spec.html
cachedir_tag = os.path.join(cache(), 'CACHEDIR.TAG')
_init_cachedir_tag()
def _init_cachedir_tag():
"""Create CACHEDIR.TAG if it doesn't exist.
See http://www.brynosaurus.com/cachedir/spec.html
"""
cache_dir = cache()
if cache_dir is None:
return
cachedir_tag = os.path.join(cache_dir, 'CACHEDIR.TAG')
if not os.path.exists(cachedir_tag):
try:
with open(cachedir_tag, 'w', encoding='utf-8') as f:

View File

@ -280,7 +280,7 @@ class TestConfigInit:
def test_config_none(self, monkeypatch):
"""Test initializing with config path set to None."""
args = types.SimpleNamespace(confdir='')
args = types.SimpleNamespace(confdir='', datadir='', cachedir='')
for k, v in self.env.items():
monkeypatch.setenv(k, v)
standarddir.init(args)

View File

@ -69,6 +69,7 @@ class LineParserWrapper:
def _prepare_save(self):
"""Keep track if _prepare_save has been called."""
self._test_save_prepared = True
return True
class TestableAppendLineParser(LineParserWrapper, lineparser.AppendLineParser):

View File

@ -22,6 +22,7 @@
import os
import os.path
import sys
import types
from PyQt5.QtWidgets import QApplication
import pytest
@ -114,3 +115,29 @@ class TestGetStandardDirWindows:
"""Test cache dir."""
expected = ['qutebrowser_test', 'cache']
assert standarddir.cache().split(os.sep)[-2:] == expected
class TestArguments:
"""Tests with confdir/cachedir/datadir arguments."""
@pytest.mark.parametrize('arg, expected', [('', None), ('foo', 'foo')])
def test_confdir(self, arg, expected):
"""Test --confdir."""
args = types.SimpleNamespace(confdir=arg, cachedir=None, datadir=None)
standarddir.init(args)
assert standarddir.config() == expected
@pytest.mark.parametrize('arg, expected', [('', None), ('foo', 'foo')])
def test_confdir(self, arg, expected):
"""Test --cachedir."""
args = types.SimpleNamespace(confdir=None, cachedir=arg, datadir=None)
standarddir.init(args)
assert standarddir.cache() == expected
@pytest.mark.parametrize('arg, expected', [('', None), ('foo', 'foo')])
def test_datadir(self, arg, expected):
"""Test --datadir."""
args = types.SimpleNamespace(confdir=None, cachedir=None, datadir=arg)
standarddir.init(args)
assert standarddir.data() == expected