From 54131e9d3e9e2559dfada27ea0dcdff8f7731f9c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 16 May 2015 23:10:20 +0200 Subject: [PATCH] Add --basedir arg with multiple instance support. Closes #510. --- doc/qutebrowser.1.asciidoc | 3 +++ qutebrowser/app.py | 19 ++++++++++------ qutebrowser/misc/ipc.py | 37 +++++++++++++++++++++++--------- qutebrowser/qutebrowser.py | 2 ++ qutebrowser/utils/standarddir.py | 14 ++++++++++++ tests/utils/test_standarddir.py | 10 +++++++++ 6 files changed, 69 insertions(+), 16 deletions(-) diff --git a/doc/qutebrowser.1.asciidoc b/doc/qutebrowser.1.asciidoc index 2bbc9fdb8..dd7857c80 100644 --- a/doc/qutebrowser.1.asciidoc +++ b/doc/qutebrowser.1.asciidoc @@ -47,6 +47,9 @@ show it. *--cachedir* 'CACHEDIR':: Set cache directory (empty for no cache storage). +*--basedir* 'BASEDIR':: + Base directory for all storage. Other --*dir arguments are ignored if this is given. + *-V*, *--version*:: Show version and quit. diff --git a/qutebrowser/app.py b/qutebrowser/app.py index e5dba1fac..2928b3bf8 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -84,11 +84,11 @@ def run(args): objreg.register('signal-handler', signal_handler) try: - sent = ipc.send_to_running_instance(args.command) + sent = ipc.send_to_running_instance(args) if sent: sys.exit(0) log.init.debug("Starting IPC server...") - server = ipc.IPCServer(qApp) + server = ipc.IPCServer(args, qApp) objreg.register('ipc-server', server) server.got_args.connect(lambda args, cwd: process_pos_args(args, cwd=cwd, via_ipc=True)) @@ -96,7 +96,7 @@ def run(args): # This could be a race condition... log.init.debug("Got AddressInUseError, trying again.") time.sleep(500) - sent = ipc.send_to_running_instance(args.command) + sent = ipc.send_to_running_instance(args) if sent: sys.exit(0) else: @@ -199,7 +199,7 @@ def _process_args(args): process_pos_args(args.command) _open_startpage() - _open_quickstart() + _open_quickstart(args) def _load_session(name): @@ -303,8 +303,15 @@ def _open_startpage(win_id=None): tabbed_browser.tabopen(url) -def _open_quickstart(): - """Open quickstart if it's the first start.""" +def _open_quickstart(args): + """Open quickstart if it's the first start. + + Args: + args: The argparse namespace. + """ + if args.datadir is not None or args.basedir is not None: + # With --datadir or --basedir given, don't open quickstart. + return state_config = objreg.get('state-config') try: quickstart_done = state_config['general']['quickstart-done'] == '1' diff --git a/qutebrowser/misc/ipc.py b/qutebrowser/misc/ipc.py index d38606cc7..ba0ec87c9 100644 --- a/qutebrowser/misc/ipc.py +++ b/qutebrowser/misc/ipc.py @@ -23,6 +23,7 @@ import os import json import getpass import binascii +import hashlib from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject from PyQt5.QtNetwork import QLocalSocket, QLocalServer, QAbstractSocket @@ -31,12 +32,20 @@ from PyQt5.QtWidgets import QMessageBox from qutebrowser.utils import log, usertypes -SOCKETNAME = 'qutebrowser-{}'.format(getpass.getuser()) CONNECT_TIMEOUT = 100 WRITE_TIMEOUT = 1000 READ_TIMEOUT = 5000 +def _get_socketname(args): + """Get a socketname to use.""" + parts = ['qutebrowser', getpass.getuser()] + if args.basedir is not None: + md5 = hashlib.md5(args.basedir.encode('utf-8')) + parts.append(md5.hexdigest()) + return '-'.join(parts) + + class Error(Exception): """Exception raised when there was a problem with IPC.""" @@ -80,6 +89,7 @@ class IPCServer(QObject): _timer: A timer to handle timeouts. _server: A QLocalServer to accept new connections. _socket: The QLocalSocket we're currently connected to. + _socketname: The socketname to use. Signals: got_args: Emitted when there was an IPC connection and arguments were @@ -88,16 +98,22 @@ class IPCServer(QObject): got_args = pyqtSignal(list, str) - def __init__(self, parent=None): - """Start the IPC server and listen to commands.""" + def __init__(self, args, parent=None): + """Start the IPC server and listen to commands. + + Args: + args: The argparse namespace. + parent: The parent to be used. + """ super().__init__(parent) self.ignored = False + self._socketname = _get_socketname(args) self._remove_server() self._timer = usertypes.Timer(self, 'ipc-timeout') self._timer.setInterval(READ_TIMEOUT) self._timer.timeout.connect(self.on_timeout) self._server = QLocalServer(self) - ok = self._server.listen(SOCKETNAME) + ok = self._server.listen(self._socketname) if not ok: if self._server.serverError() == QAbstractSocket.AddressInUseError: raise AddressInUseError(self._server) @@ -108,9 +124,10 @@ class IPCServer(QObject): def _remove_server(self): """Remove an existing server.""" - ok = QLocalServer.removeServer(SOCKETNAME) + ok = QLocalServer.removeServer(self._socketname) if not ok: - raise Error("Error while removing server {}!".format(SOCKETNAME)) + raise Error("Error while removing server {}!".format( + self._socketname)) @pyqtSlot(int) def on_error(self, error): @@ -223,23 +240,23 @@ def _socket_error(action, socket): action, socket.errorString(), socket.error())) -def send_to_running_instance(cmdlist): +def send_to_running_instance(args): """Try to send a commandline to a running instance. Blocks for CONNECT_TIMEOUT ms. Args: - cmdlist: A list to send (URLs/commands) + args: The argparse namespace. Return: True if connecting was successful, False if no connection was made. """ socket = QLocalSocket() - socket.connectToServer(SOCKETNAME) + socket.connectToServer(_get_socketname(args)) connected = socket.waitForConnected(100) if connected: log.ipc.info("Opening in existing instance") - json_data = {'args': cmdlist} + json_data = {'args': args.command} try: cwd = os.getcwd() except OSError: diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py index 1ae9f60e2..21604c406 100644 --- a/qutebrowser/qutebrowser.py +++ b/qutebrowser/qutebrowser.py @@ -52,6 +52,8 @@ def get_argparser(): "no data storage).") parser.add_argument('--cachedir', help="Set cache directory (empty for " "no cache storage).") + parser.add_argument('--basedir', help="Base directory for all storage. " + "Other --*dir arguments are ignored if this is given.") parser.add_argument('-V', '--version', help="Show version and quit.", action='store_true') parser.add_argument('-s', '--set', help="Set a temporary setting for " diff --git a/qutebrowser/utils/standarddir.py b/qutebrowser/utils/standarddir.py index aeba60b77..9dd8c5752 100644 --- a/qutebrowser/utils/standarddir.py +++ b/qutebrowser/utils/standarddir.py @@ -84,8 +84,22 @@ def _from_args(typ, args): QStandardPaths.DataLocation: 'datadir', QStandardPaths.CacheLocation: 'cachedir', } + basedir_suffix = { + QStandardPaths.ConfigLocation: 'config', + QStandardPaths.DataLocation: 'data', + QStandardPaths.CacheLocation: 'cache', + QStandardPaths.DownloadLocation: 'download', + QStandardPaths.RuntimeLocation: 'runtime', + } + if args is None: return (False, None) + + if getattr(args, 'basedir', None) is not None: + basedir = args.basedir + suffix = basedir_suffix[typ] + return (True, os.path.join(basedir, suffix)) + try: argname = typ_to_argparse_arg[typ] except KeyError: diff --git a/tests/utils/test_standarddir.py b/tests/utils/test_standarddir.py index ede5f8964..6be44d92c 100644 --- a/tests/utils/test_standarddir.py +++ b/tests/utils/test_standarddir.py @@ -154,3 +154,13 @@ class TestArguments: datadir=testcase.arg) standarddir.init(args) assert standarddir.data() == testcase.expected + + @pytest.mark.parametrize('typ', ['config', 'data', 'cache', 'download', + 'runtime']) + def test_basedir(self, tmpdir, typ): + """Test --basedir.""" + expected = str(tmpdir / typ) + args = types.SimpleNamespace(basedir=str(tmpdir)) + standarddir.init(args) + func = getattr(standarddir, typ) + assert func() == expected