Add --basedir arg with multiple instance support.

Closes #510.
This commit is contained in:
Florian Bruhin 2015-05-16 23:10:20 +02:00
parent aab5411317
commit 54131e9d3e
6 changed files with 69 additions and 16 deletions

View File

@ -47,6 +47,9 @@ show it.
*--cachedir* 'CACHEDIR':: *--cachedir* 'CACHEDIR'::
Set cache directory (empty for no cache storage). 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*:: *-V*, *--version*::
Show version and quit. Show version and quit.

View File

@ -84,11 +84,11 @@ def run(args):
objreg.register('signal-handler', signal_handler) objreg.register('signal-handler', signal_handler)
try: try:
sent = ipc.send_to_running_instance(args.command) sent = ipc.send_to_running_instance(args)
if sent: if sent:
sys.exit(0) sys.exit(0)
log.init.debug("Starting IPC server...") log.init.debug("Starting IPC server...")
server = ipc.IPCServer(qApp) server = ipc.IPCServer(args, qApp)
objreg.register('ipc-server', server) objreg.register('ipc-server', server)
server.got_args.connect(lambda args, cwd: server.got_args.connect(lambda args, cwd:
process_pos_args(args, cwd=cwd, via_ipc=True)) process_pos_args(args, cwd=cwd, via_ipc=True))
@ -96,7 +96,7 @@ def run(args):
# This could be a race condition... # This could be a race condition...
log.init.debug("Got AddressInUseError, trying again.") log.init.debug("Got AddressInUseError, trying again.")
time.sleep(500) time.sleep(500)
sent = ipc.send_to_running_instance(args.command) sent = ipc.send_to_running_instance(args)
if sent: if sent:
sys.exit(0) sys.exit(0)
else: else:
@ -199,7 +199,7 @@ def _process_args(args):
process_pos_args(args.command) process_pos_args(args.command)
_open_startpage() _open_startpage()
_open_quickstart() _open_quickstart(args)
def _load_session(name): def _load_session(name):
@ -303,8 +303,15 @@ def _open_startpage(win_id=None):
tabbed_browser.tabopen(url) tabbed_browser.tabopen(url)
def _open_quickstart(): def _open_quickstart(args):
"""Open quickstart if it's the first start.""" """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') state_config = objreg.get('state-config')
try: try:
quickstart_done = state_config['general']['quickstart-done'] == '1' quickstart_done = state_config['general']['quickstart-done'] == '1'

View File

@ -23,6 +23,7 @@ import os
import json import json
import getpass import getpass
import binascii import binascii
import hashlib
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject
from PyQt5.QtNetwork import QLocalSocket, QLocalServer, QAbstractSocket from PyQt5.QtNetwork import QLocalSocket, QLocalServer, QAbstractSocket
@ -31,12 +32,20 @@ from PyQt5.QtWidgets import QMessageBox
from qutebrowser.utils import log, usertypes from qutebrowser.utils import log, usertypes
SOCKETNAME = 'qutebrowser-{}'.format(getpass.getuser())
CONNECT_TIMEOUT = 100 CONNECT_TIMEOUT = 100
WRITE_TIMEOUT = 1000 WRITE_TIMEOUT = 1000
READ_TIMEOUT = 5000 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): class Error(Exception):
"""Exception raised when there was a problem with IPC.""" """Exception raised when there was a problem with IPC."""
@ -80,6 +89,7 @@ class IPCServer(QObject):
_timer: A timer to handle timeouts. _timer: A timer to handle timeouts.
_server: A QLocalServer to accept new connections. _server: A QLocalServer to accept new connections.
_socket: The QLocalSocket we're currently connected to. _socket: The QLocalSocket we're currently connected to.
_socketname: The socketname to use.
Signals: Signals:
got_args: Emitted when there was an IPC connection and arguments were got_args: Emitted when there was an IPC connection and arguments were
@ -88,16 +98,22 @@ class IPCServer(QObject):
got_args = pyqtSignal(list, str) got_args = pyqtSignal(list, str)
def __init__(self, parent=None): def __init__(self, args, parent=None):
"""Start the IPC server and listen to commands.""" """Start the IPC server and listen to commands.
Args:
args: The argparse namespace.
parent: The parent to be used.
"""
super().__init__(parent) super().__init__(parent)
self.ignored = False self.ignored = False
self._socketname = _get_socketname(args)
self._remove_server() self._remove_server()
self._timer = usertypes.Timer(self, 'ipc-timeout') self._timer = usertypes.Timer(self, 'ipc-timeout')
self._timer.setInterval(READ_TIMEOUT) self._timer.setInterval(READ_TIMEOUT)
self._timer.timeout.connect(self.on_timeout) self._timer.timeout.connect(self.on_timeout)
self._server = QLocalServer(self) self._server = QLocalServer(self)
ok = self._server.listen(SOCKETNAME) ok = self._server.listen(self._socketname)
if not ok: if not ok:
if self._server.serverError() == QAbstractSocket.AddressInUseError: if self._server.serverError() == QAbstractSocket.AddressInUseError:
raise AddressInUseError(self._server) raise AddressInUseError(self._server)
@ -108,9 +124,10 @@ class IPCServer(QObject):
def _remove_server(self): def _remove_server(self):
"""Remove an existing server.""" """Remove an existing server."""
ok = QLocalServer.removeServer(SOCKETNAME) ok = QLocalServer.removeServer(self._socketname)
if not ok: if not ok:
raise Error("Error while removing server {}!".format(SOCKETNAME)) raise Error("Error while removing server {}!".format(
self._socketname))
@pyqtSlot(int) @pyqtSlot(int)
def on_error(self, error): def on_error(self, error):
@ -223,23 +240,23 @@ def _socket_error(action, socket):
action, socket.errorString(), socket.error())) 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. """Try to send a commandline to a running instance.
Blocks for CONNECT_TIMEOUT ms. Blocks for CONNECT_TIMEOUT ms.
Args: Args:
cmdlist: A list to send (URLs/commands) args: The argparse namespace.
Return: Return:
True if connecting was successful, False if no connection was made. True if connecting was successful, False if no connection was made.
""" """
socket = QLocalSocket() socket = QLocalSocket()
socket.connectToServer(SOCKETNAME) socket.connectToServer(_get_socketname(args))
connected = socket.waitForConnected(100) connected = socket.waitForConnected(100)
if connected: if connected:
log.ipc.info("Opening in existing instance") log.ipc.info("Opening in existing instance")
json_data = {'args': cmdlist} json_data = {'args': args.command}
try: try:
cwd = os.getcwd() cwd = os.getcwd()
except OSError: except OSError:

View File

@ -52,6 +52,8 @@ def get_argparser():
"no data storage).") "no data storage).")
parser.add_argument('--cachedir', help="Set cache directory (empty for " parser.add_argument('--cachedir', help="Set cache directory (empty for "
"no cache storage).") "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.", parser.add_argument('-V', '--version', help="Show version and quit.",
action='store_true') action='store_true')
parser.add_argument('-s', '--set', help="Set a temporary setting for " parser.add_argument('-s', '--set', help="Set a temporary setting for "

View File

@ -84,8 +84,22 @@ def _from_args(typ, args):
QStandardPaths.DataLocation: 'datadir', QStandardPaths.DataLocation: 'datadir',
QStandardPaths.CacheLocation: 'cachedir', QStandardPaths.CacheLocation: 'cachedir',
} }
basedir_suffix = {
QStandardPaths.ConfigLocation: 'config',
QStandardPaths.DataLocation: 'data',
QStandardPaths.CacheLocation: 'cache',
QStandardPaths.DownloadLocation: 'download',
QStandardPaths.RuntimeLocation: 'runtime',
}
if args is None: if args is None:
return (False, 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: try:
argname = typ_to_argparse_arg[typ] argname = typ_to_argparse_arg[typ]
except KeyError: except KeyError:

View File

@ -154,3 +154,13 @@ class TestArguments:
datadir=testcase.arg) datadir=testcase.arg)
standarddir.init(args) standarddir.init(args)
assert standarddir.data() == testcase.expected 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