1# Copyright 2014 Christoph Reiter 2# 3# This program is free software; you can redistribute it and/or modify 4# it under the terms of the GNU General Public License as published by 5# the Free Software Foundation; either version 2 of the License, or 6# (at your option) any later version. 7 8import os 9 10from senf import path2fsn, fsn2bytes, bytes2fsn, fsnative 11 12from quodlibet.util import fifo, print_w 13from quodlibet import get_user_dir 14try: 15 from quodlibet.util import winpipe 16except ImportError: 17 winpipe = None 18 19 20class RemoteError(Exception): 21 pass 22 23 24class RemoteBase(object): 25 """A thing for communicating with existing instances of ourself.""" 26 27 def __init__(self, app, cmd_registry): 28 """ 29 Args: 30 app (Application) 31 cmd_registry (CommandRegistry) 32 """ 33 34 raise NotImplementedError 35 36 @classmethod 37 def remote_exists(self): 38 """See if another instance exists 39 40 Returns: 41 bool 42 """ 43 44 raise NotImplementedError 45 46 @classmethod 47 def send_message(cls, message): 48 """Send data to the existing instance if possible and returns 49 a response. 50 51 Args: 52 message (fsnative) 53 Returns: 54 fsnative or None 55 Raises: 56 RemoteError: in case the message couldn't be send or 57 there was no response. 58 """ 59 60 raise NotImplementedError 61 62 def start(self): 63 """Start the listener for other instances. 64 65 Raises: 66 RemoteError: in case another instance is already listening. 67 """ 68 69 raise NotImplementedError 70 71 def stop(self): 72 """Stop the listener for other instances""" 73 74 raise NotImplementedError 75 76 77class QuodLibetWinRemote(RemoteBase): 78 79 _NAME = "quodlibet" 80 81 def __init__(self, app, cmd_registry): 82 self._app = app 83 self._cmd_registry = cmd_registry 84 self._server = winpipe.NamedPipeServer(self._NAME, self._callback) 85 86 @classmethod 87 def remote_exists(cls): 88 return winpipe.pipe_exists(cls._NAME) 89 90 @classmethod 91 def send_message(cls, message): 92 data = fsn2bytes(path2fsn(message), "utf-8") 93 try: 94 winpipe.write_pipe(cls._NAME, data) 95 except EnvironmentError as e: 96 raise RemoteError(e) 97 98 def start(self): 99 try: 100 self._server.start() 101 except winpipe.NamedPipeServerError as e: 102 raise RemoteError(e) 103 104 def stop(self): 105 self._server.stop() 106 107 def _callback(self, data): 108 message = bytes2fsn(data, "utf-8") 109 self._cmd_registry.handle_line(self._app, message) 110 111 112class QuodLibetUnixRemote(RemoteBase): 113 114 _FIFO_NAME = "control" 115 _PATH = os.path.join(get_user_dir(), _FIFO_NAME) 116 117 def __init__(self, app, cmd_registry): 118 self._app = app 119 self._cmd_registry = cmd_registry 120 self._fifo = fifo.FIFO(self._PATH, self._callback) 121 122 @classmethod 123 def remote_exists(cls): 124 return fifo.fifo_exists(cls._PATH) 125 126 @classmethod 127 def send_message(cls, message): 128 assert isinstance(message, fsnative) 129 130 try: 131 return fifo.write_fifo(cls._PATH, fsn2bytes(message, None)) 132 except EnvironmentError as e: 133 raise RemoteError(e) 134 135 def start(self): 136 try: 137 self._fifo.open() 138 except fifo.FIFOError as e: 139 raise RemoteError(e) 140 141 def stop(self): 142 self._fifo.destroy() 143 144 def _callback(self, data): 145 try: 146 messages = list(fifo.split_message(data)) 147 except ValueError: 148 print_w("invalid message: %r" % data) 149 return 150 151 for command, path in messages: 152 command = bytes2fsn(command, None) 153 response = self._cmd_registry.handle_line(self._app, command) 154 if path is not None: 155 path = bytes2fsn(path, None) 156 with open(path, "wb") as h: 157 if response is not None: 158 assert isinstance(response, fsnative) 159 h.write(fsn2bytes(response, None)) 160 161 162if os.name == "nt": 163 Remote = QuodLibetWinRemote 164else: 165 Remote = QuodLibetUnixRemote 166