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