~ubuntu-branches/ubuntu/natty/miro/natty

« back to all changes in this revision

Viewing changes to platform/windows-xul/plat/pipeipc.py

  • Committer: Bazaar Package Importer
  • Author(s): Bryce Harrington
  • Date: 2011-01-22 02:46:33 UTC
  • mfrom: (1.4.10 upstream) (1.7.5 experimental)
  • Revision ID: james.westby@ubuntu.com-20110122024633-kjme8u93y2il5nmf
Tags: 3.5.1-1ubuntu1
* Merge from debian.  Remaining ubuntu changes:
  - Use python 2.7 instead of python 2.6
  - Relax dependency on python-dbus to >= 0.83.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Miro - an RSS based video player application
2
 
# Copyright (C) 2005-2010 Participatory Culture Foundation
3
 
#
4
 
# This program is free software; you can redistribute it and/or modify
5
 
# it under the terms of the GNU General Public License as published by
6
 
# the Free Software Foundation; either version 2 of the License, or
7
 
# (at your option) any later version.
8
 
#
9
 
# This program is distributed in the hope that it will be useful,
10
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 
# GNU General Public License for more details.
13
 
#
14
 
# You should have received a copy of the GNU General Public License
15
 
# along with this program; if not, write to the Free Software
16
 
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
17
 
#
18
 
# In addition, as a special exception, the copyright holders give
19
 
# permission to link the code of portions of this program with the OpenSSL
20
 
# library.
21
 
#
22
 
# You must obey the GNU General Public License in all respects for all of
23
 
# the code used other than OpenSSL. If you modify file(s) with this
24
 
# exception, you may extend this exception to your version of the file(s),
25
 
# but you are not obligated to do so. If you do not wish to do so, delete
26
 
# this exception statement from your version. If you delete this exception
27
 
# statement from all source files in the program, then also delete it here.
28
 
 
29
 
"""pipeipc.py -- Create a windows named pipe to controll IPC between different
30
 
Miro processes.
31
 
 
32
 
The first proccess to start creates a named pipe, then a thread that listens
33
 
to that pipe.  Subsequent processes send a message over that pipe containing
34
 
command line arguments.  When the first process receives a message from the
35
 
pipe, we try to open them using the commandline module.
36
 
"""
37
 
 
38
 
import cPickle as pickle
39
 
import ctypes
40
 
from ctypes.wintypes import DWORD, HANDLE, ULONG
41
 
import os
42
 
import logging
43
 
import threading
44
 
from miro.plat import commandline
45
 
 
46
 
kernel32 = ctypes.windll.kernel32
47
 
 
48
 
ERROR_IO_PENDING = 997
49
 
ERROR_MORE_DATA = 234
50
 
ERROR_PIPE_CONNECTED = 535
51
 
ERROR_PIPE_LISTENING = 536
52
 
FILE_FLAG_OVERLAPPED = 0x40000000
53
 
INFINITE = 0xFFFF
54
 
INVALID_HANDLE_VALUE = -1
55
 
PIPE_ACCESS_DUPLEX = 0x00000003
56
 
PIPE_READMODE_MESSAGE = 0x00000002
57
 
PIPE_TYPE_MESSAGE = 0x00000004
58
 
PIPE_WAIT = 0x00000000
59
 
 
60
 
MIRO_IPC_PIPE_NAME = r'\\.\pipe\MiroIPC'
61
 
 
62
 
 
63
 
class PipeExists(Exception):
64
 
    """We tried to create a named pipe, but it already exists.  Probably a
65
 
    different Miro instance created it.
66
 
    """
67
 
 
68
 
class QuitThread(Exception):
69
 
    """Raised to exit out of the pipe listening thread."""
70
 
 
71
 
class PipeError(IOError):
72
 
    """An IO Error occurred on a pipe."""
73
 
    def __init__(self, description):
74
 
        str = "%s failed: %d" % (description, kernel32.GetLastError())
75
 
        IOError.__init__(self, str)
76
 
 
77
 
class _inner_struct(ctypes.Structure):
78
 
    _fields_ = [('Offset', DWORD),
79
 
                ('OffsetHigh', DWORD),
80
 
               ]
81
 
 
82
 
class _inner_union(ctypes.Union):
83
 
    _fields_  = [('anon_struct', _inner_struct), # struct
84
 
                 ('Pointer', ctypes.c_void_p),
85
 
                ]
86
 
 
87
 
class OVERLAPPED(ctypes.Structure):
88
 
    _fields_ = [('Internal', ctypes.POINTER(ULONG)),
89
 
                ('InternalHigh', ctypes.POINTER(ULONG)),
90
 
                ('union', _inner_union),
91
 
                ('hEvent', HANDLE),
92
 
               ]
93
 
 
94
 
class Server(object):
95
 
    def __init__(self):
96
 
        self.pipe = kernel32.CreateNamedPipeA(
97
 
                MIRO_IPC_PIPE_NAME, 
98
 
                PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
99
 
                PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
100
 
                1, 51200, 51200, 100, None)
101
 
        if self.pipe == INVALID_HANDLE_VALUE:
102
 
            raise PipeExists()
103
 
        self.pipe_event = kernel32.CreateEventA(None, True, False, None)
104
 
        self.quit_event = kernel32.CreateEventA(None, True, False, None)
105
 
        self.event_array = (HANDLE * 2)(self.pipe_event, self.quit_event)
106
 
        self.overlapped = OVERLAPPED()
107
 
        self.overlapped.hEvent = self.pipe_event
108
 
        self.overlapped.Internal = None
109
 
        self.overlapped.InternalHigh = None
110
 
        self.overlapped.union.Pointer = None
111
 
        self.message_handler = MessageHandler()
112
 
 
113
 
    def start_process(self):
114
 
        self.thread = threading.Thread(target=self._thread)
115
 
        self.thread.start()
116
 
 
117
 
    def quit(self):
118
 
        kernel32.SetEvent(self.quit_event)
119
 
        self.thread.join()
120
 
 
121
 
    def _wait_for_pipe(self):
122
 
        kernel32.WaitForMultipleObjects(2, self.event_array, False, INFINITE)
123
 
        if kernel32.WaitForSingleObject(self.quit_event, 0) == 0:
124
 
            raise QuitThread()
125
 
 
126
 
    def _process_overlap(self, length):
127
 
        return kernel32.GetOverlappedResult(self.pipe,
128
 
                self._overlapped_ref(), ctypes.byref(length), False)
129
 
 
130
 
    def _overlapped_ref(self):
131
 
        return ctypes.byref(self.overlapped)
132
 
 
133
 
    def _connect(self):
134
 
        rv = kernel32.ConnectNamedPipe(self.pipe, self._overlapped_ref())
135
 
        if not rv and kernel32.GetLastError() == ERROR_PIPE_CONNECTED:
136
 
            return
137
 
        if not rv and kernel32.GetLastError() == ERROR_IO_PENDING:
138
 
            self._wait_for_pipe()
139
 
        else:
140
 
            raise PipeError("ConnectNamedPipe")
141
 
 
142
 
    def _read_in(self):
143
 
        read_in = []
144
 
        c_length = DWORD()
145
 
        buffer = ctypes.create_string_buffer(1024)
146
 
        while True:
147
 
            rv = kernel32.ReadFile(self.pipe, buffer, 1023,
148
 
                    ctypes.byref(c_length), self._overlapped_ref())
149
 
            if not rv:
150
 
                if kernel32.GetLastError() == ERROR_IO_PENDING:
151
 
                    self._wait_for_pipe()
152
 
                elif kernel32.GetLastError() == ERROR_PIPE_LISTENING:
153
 
                    # Not sure why this happens, but it means we should just
154
 
                    # start at _connect()
155
 
                    return None
156
 
                elif kernel32.GetLastError() != ERROR_MORE_DATA:
157
 
                    raise PipeError("ReadFile")
158
 
 
159
 
            rv = self._process_overlap(c_length)
160
 
 
161
 
            buffer[c_length.value] = '\0'
162
 
            read_in.append(buffer.value)
163
 
            if rv:
164
 
                break
165
 
        return ''.join(read_in)
166
 
 
167
 
    def _write_out(self, data):
168
 
        c_length = DWORD()
169
 
        rv = kernel32.WriteFile(self.pipe, ctypes.create_string_buffer(data),
170
 
                len(data), ctypes.byref(c_length), self._overlapped_ref())
171
 
        if not rv and kernel32.GetLastError() == ERROR_IO_PENDING:
172
 
            self._wait_for_pipe()
173
 
            rv = self._process_overlap(c_length)
174
 
        if not rv: 
175
 
            raise PipeError("WriteFile")
176
 
 
177
 
    def _log_error(self, name):
178
 
        logging.warn("%s failed: %d" % (name, kernel32.GetLastError()))
179
 
 
180
 
    def _thread(self):
181
 
        while True:
182
 
            try:
183
 
                self._connect()
184
 
                data = self._read_in()
185
 
                if data is None:
186
 
                    continue
187
 
                self.message_handler.handle_message(data)
188
 
                self._write_out("OK")
189
 
                kernel32.DisconnectNamedPipe(self.pipe)
190
 
            except QuitThread:
191
 
                break
192
 
        logging.info("Pipe reader finished")
193
 
 
194
 
class MessageHandler(object):
195
 
    def handle_message(self, data):
196
 
        # import this stuff inside the function, so that import errors don't
197
 
        # mess with other code, which is part of the startup process
198
 
        from miro import app
199
 
        from miro import eventloop
200
 
        from miro.commandline import parse_command_line_args
201
 
        import gobject
202
 
        try:
203
 
            cmd_line = pickle.loads(data)
204
 
        except:
205
 
            logging.warn("Error unpickling message (%r)" % data)
206
 
        else:
207
 
            args = commandline.parse_command_line_string(cmd_line)
208
 
            eventloop.addIdle(parse_command_line_args, 
209
 
                    'parse command line', args=(args[1:],))
210
 
            if (hasattr(app, "widgetapp") and app.widgetapp is not None and
211
 
                    app.widgetapp.window is not None):
212
 
                gobject.idle_add(app.widgetapp.window._window.present)
213
 
 
214
 
def send_command_line_args():
215
 
    message = pickle.dumps(commandline.get_command_line_string())
216
 
    out_length = DWORD(0)
217
 
    out_buffer = ctypes.create_string_buffer(1024)
218
 
    kernel32.CallNamedPipeA(MIRO_IPC_PIPE_NAME,
219
 
            ctypes.create_string_buffer(message), len(message), out_buffer,
220
 
            1023, ctypes.byref(out_length), 20000)