This URL has Read-Only access.

Statistics
| Branch: | Tag: | Revision:

root / py / maugis / scenic / application.py @ e21a6d08

History | View | Annotate | Download (13.3 kB)

1
#!/usr/bin/env python
2
# -*- coding: utf-8 -*-
3
# 
4
# Scenic
5
# Copyright (C) 2008 Société des arts technologiques (SAT)
6
# http://www.sat.qc.ca
7
# All rights reserved.
8
#
9
# This file is free software: you can redistribute it and/or modify
10
# it under the terms of the GNU General Public License as published by
11
# the Free Software Foundation, either version 2 of the License, or
12
# (at your option) any later version.
13
#
14
# Scenic is distributed in the hope that it will be useful,
15
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
# GNU General Public License for more details.
18
#
19
# You should have received a copy of the GNU General Public License
20
# along with Scenic. If not, see <http://www.gnu.org/licenses/>.
21

    
22
"""
23
Main application 
24
"""
25
import os
26
import gtk # for dialog responses. TODO: remove
27

    
28
from twisted.internet import defer
29
from twisted.internet import error
30
from twisted.internet import reactor
31

    
32
from scenic import communication
33
from scenic import saving
34
from scenic import process # just for constants
35
from scenic.streamer import StreamerManager
36
from scenic import dialogs
37
from scenic import ports
38
from scenic import gui
39
from scenic.gui import _ # gettext
40

    
41
class Config(saving.ConfigStateSaving):
42
    """
43
    Class attributes are default.
44
    """
45
    # Default values
46
    negotiation_port = 17446 # receiving TCP (SIC) messages on it.
47
    smtpserver = "smtp.sat.qc.ca"
48
    email_info = "scenic@sat.qc.ca"
49
    audio_source = "jackaudiosrc"
50
    audio_sink = "jackaudiosink"
51
    audio_codec = "raw"
52
    audio_channels = 8
53
    video_source = "v4l2src"
54
    video_device = "/dev/video0"
55
    video_sink = "xvimagesink"
56
    video_codec = "mpeg4"
57
    video_display = ":0.0"
58
    video_bitrate = "3000000"
59
    video_width = 640
60
    video_height = 480
61
    confirm_quit = False
62
    theme = "Darklooks"
63

    
64
    def __init__(self):
65
        config_file = 'scenic.cfg'
66
        if os.path.isfile('/etc/' + config_file):
67
            config_dir = '/etc'
68
        else:
69
            config_dir = os.environ['HOME'] + '/.scenic'
70
        config_file_path = os.path.join(config_dir, config_file)
71
        saving.ConfigStateSaving.__init__(self, config_file_path)
72

    
73
class Application(object):
74
    def __init__(self, kiosk_mode=False, fullscreen=False):
75
        self.config = Config()
76
        self.send_video_port = None
77
        self.recv_video_port = None
78
        self.send_audio_port = None
79
        self.recv_audio_port = None
80
        self.ports_allocator = ports.PortsAllocator()
81
        self.address_book = saving.AddressBook()
82
        self.streamer_manager = StreamerManager(self)
83
        self._has_session = False
84
        self.streamer_manager.state_changed_signal.connect(self.on_streamer_state_changed) # XXX
85
        print("Starting SIC server on port %s" % (self.config.negotiation_port)) 
86
        self.server = communication.Server(self, self.config.negotiation_port) # XXX
87
        self.client = None 
88
        self.got_bye = False 
89
        # starting the GUI:
90
        self.gui = gui.Gui(self, kiosk_mode=kiosk_mode, fullscreen=fullscreen)
91
        reactor.addSystemEventTrigger("before", "shutdown", self.before_shutdown)
92
        try:
93
            self.server.start_listening()
94
        except error.CannotListenError, e:
95
            print("Cannot start SIC server.")
96
            print(str(e))
97
            raise
98
    
99
    def before_shutdown(self):
100
        """
101
        Last things done before quitting.
102
        """
103
        print("The application is shutting down.")
104
        # TODO: stop streamers
105
        if self.client is not None:
106
            if not self.got_bye:
107
                self.send_bye()
108
                self.stop_streamers()
109
            self.disconnect_client()
110
        print('stopping server')
111
        self.server.close()
112
    # ------------------------- session occuring -------------
113
    def has_session(self):
114
        """
115
        @rettype: bool
116
        """
117
        return self._has_session
118
    # -------------------- streamer ports -----------------
119

    
120
    def allocate_ports(self):
121
        # TODO: start_session
122
        self.recv_video_port = self.ports_allocator.allocate()
123
        self.recv_audio_port = self.ports_allocator.allocate()
124

    
125
    def free_ports(self):
126
        # TODO: stop_session
127
        for port in [self.recv_video_port, self.recv_audio_port]:
128
            try:
129
                self.ports_allocator.free(port)
130
            except ports.PortsAllocatorError, e:
131
                print(e)
132

    
133
    def save_configuration(self):
134
        """
135
        Saves the configuration to a file.
136
        Reads the widget value prior to do it.
137
        """
138
        self.gui._gather_configuration() # need to get the value of the configuration widgets.
139
        self.config.save()
140
        self.address_book.save() # addressbook values are already stored.
141
    # --------------------------- network receives ------------
142
    def handle_invite(self, message, addr):
143
        self.got_bye = False
144
        send_to_port = message["please_send_to_port"]
145
        
146
        def _on_contact_request_dialog_response(response):
147
            """
148
            User is accetping or declining an offer.
149
            @param result: Answer to the dialog.
150
            """
151
            if response == gtk.RESPONSE_OK:
152
                if self.client is not None:
153
                    self.send_accept(message, addr)
154
                else:
155
                    print("Error: connection lost, so we could not accept.")# FIXME
156
            elif response == gtk.RESPONSE_CANCEL or gtk.RESPONSE_DELETE_EVENT:
157
                self.send_refuse_and_disconnect() 
158
            else:
159
                pass
160
            return True
161

    
162
        if self.streamer_manager.is_busy():
163
            print("Got invitation, but we are busy.")
164
            communication.connect_send_and_disconnect(addr, send_to_port, {'msg':'REFUSE', 'sid':0}) #FIXME: where do we get the port number from?
165
        else:
166
            self.client = communication.Client(self, send_to_port)
167
            self.client.connect(addr)
168
            # TODO: if a contact in the addressbook has this address, displays it!
169
            text = _("<b><big>%s is inviting you.</big></b>\n\nDo you accept the connection?" % addr)
170
            self.gui.show_invited_dialog(text, _on_contact_request_dialog_response)
171

    
172
    def handle_cancel(self):
173
        if self.client is not None:
174
            self.client.disconnect()
175
            self.client = None
176
        self.gui.invited_dialog.hide()
177
        dialogs.ErrorDialog.create("Remote peer cancelled invitation.", parent=self.gui.main_window)
178

    
179
    def handle_accept(self, message, addr):
180
        self.gui._unschedule_offerer_invite_timeout()
181
        # FIXME: this doesn't make sense here
182
        self.got_bye = False
183
        # TODO: Use session to contain settings and ports
184
        if self.client is not None:
185
            self.gui.hide_calling_dialog("accept")
186
            self.send_video_port = message["videoport"]
187
            self.send_audio_port = message["audioport"]
188
            if self.streamer_manager.is_busy():
189
                dialogs.ErrorDialog.create("A streaming session is already in progress.", parent=self.gui.main_window)
190
            else:
191
                print("Got ACCEPT. Starting streamers as initiator.")
192
                self.start_streamers(addr)
193
                self.send_ack()
194
        else:
195
            print("Error ! Connection lost.") # FIXME
196

    
197
    def handle_refuse(self):
198
        self.gui._unschedule_offerer_invite_timeout()
199
        self.free_ports()
200
        self.gui.hide_calling_dialog("refuse")
201

    
202
    def handle_ack(self, addr):
203
        print("Got ACK. Starting streamers as answerer.")
204
        self.start_streamers(addr)
205

    
206
    def handle_bye(self):
207
        self.got_bye = True
208
        self.stop_streamers()
209
        if self.client is not None:
210
            print('disconnecting client and sending BYE')
211
            self.client.send({"msg":"OK", "sid":0})
212
            self.disconnect_client()
213

    
214
    def handle_ok(self):
215
        print("received ok. Everything has an end.")
216
        print('disconnecting client')
217
        self.disconnect_client()
218

    
219
    def on_server_receive_command(self, message, addr):
220
        # XXX
221
        msg = message["msg"]
222
        print("Got %s from %s" % (msg, addr))
223
        
224
        if msg == "INVITE":
225
            self.handle_invite(message, addr)
226
        elif msg == "CANCEL":
227
            self.handle_cancel()
228
        elif msg == "ACCEPT":
229
            self.handle_accept(message, addr)
230
        elif msg == "REFUSE":
231
            self.handle_refuse()
232
        elif msg == "ACK":
233
            self.handle_ack(addr)
234
        elif msg == "BYE":
235
            self.handle_bye()
236
        elif msg == "OK":
237
            self.handle_ok()
238
        else:
239
            print ('WARNING: Unexpected message %s' % (msg))
240

    
241
    # -------------------------- actions on streamer manager --------
242

    
243
    def start_streamers(self, addr):
244
        self._has_session = True
245
        self.streamer_manager.start(addr, self.config)
246

    
247
    def stop_streamers(self):
248
        self.streamer_manager.stop()
249

    
250
    def on_streamers_stopped(self, addr):
251
        """
252
        We call this when all streamers are stopped.
253
        """
254
        print("on_streamers_stopped got called")
255
        self._has_session = False
256
        self.free_ports()
257

    
258
    # ---------------------- sending messages -----------
259
        
260
    def disconnect_client(self):
261
        """
262
        Disconnects the SIC sender.
263
        @rettype: L{Deferred}
264
        """
265
        def _cb(result, d1):
266
            self.client = None
267
            d1.callback(True)
268
        def _cl(d1):
269
            if self.client is not None:
270
                d2 = self.client.disconnect()
271
                d2.addCallback(_cb, d1)
272
            else:
273
                d1.callback(True)
274
        if self.client is not None:
275
            d = defer.Deferred()
276
            reactor.callLater(0, _cl, d)
277
            return d
278
        else: 
279
            return defer.succeed(True)
280

    
281
    def send_invite(self):
282
        self.allocate_ports()
283
        if self.streamer_manager.is_busy():
284
            dialogs.ErrorDialog.create("Impossible to invite a contact to start streaming. A streaming session is already in progress.", parent=self.gui.main_window)
285
        else:
286
            # UPDATE when initiating session
287
            self.gui._gather_configuration()
288
        msg = {
289
            "msg":"INVITE",
290
            "sid":0, 
291
            "videoport": self.recv_video_port,
292
            "audioport": self.recv_audio_port,
293
            "please_send_to_port": self.config.negotiation_port
294
            }
295
        port = self.config.negotiation_port
296
        ip = self.address_book.selected_contact["address"]
297

    
298
        def _on_connected(proto):
299
            self.gui._schedule_offerer_invite_timeout()
300
            self.client.send(msg)
301
            return proto
302
        def _on_error(reason):
303
            print ("error trying to connect to %s:%s : %s" % (ip, port, reason))
304
            self.gui.calling_dialog.hide()
305
            self.client = None
306
            return None
307
           
308
        print ("sending %s to %s:%s" % (msg, ip, port))
309
        self.client = communication.Client(self, port)
310
        deferred = self.client.connect(ip)
311
        deferred.addCallback(_on_connected).addErrback(_on_error)
312
        self.gui.calling_dialog.show()
313
        # window will be hidden when we receive ACCEPT or REFUSE
314
    
315
    def send_accept(self, message, addr):
316
        # UPDATE config once we accept the invitie
317
        self.gui._gather_configuration()
318
        self.allocate_ports()
319
        self.client.send({"msg":"ACCEPT", "videoport":self.recv_video_port, "audioport":self.recv_audio_port, "sid":0})
320
        # TODO: Use session to contain settings and ports
321
        self.send_video_port = message["videoport"]
322
        self.send_audio_port = message["audioport"]
323
        
324
    def send_ack(self):
325
        self.client.send({"msg":"ACK", "sid":0})
326

    
327
    def send_bye(self):
328
        """
329
        Sends BYE
330
        BYE stops the streaming on the remote host.
331
        """
332
        if self.client is not None:
333
            self.client.send({"msg":"BYE", "sid":0})
334
    
335
    def send_cancel_and_disconnect(self):
336
        """
337
        Sends CANCEL
338
        CANCEL cancels the invite on the remote host.
339
        """
340
        if self.client is not None:
341
            self.client.send({"msg":"CANCEL", "sid":0})
342
            self.client.disconnect()
343
            self.client = None
344
        else:
345
            print('Warning: Trying to send CANCEL even though client is None')
346
    
347
    def send_refuse_and_disconnect(self):
348
        """
349
        Sends REFUSE 
350
        REFUSE tells the offerer we can't have a session.
351
        """
352
        if self.client is not None:
353
            self.client.send({"msg":"REFUSE", "sid":0})
354
            self.client.disconnect()
355
            self.client = None
356
        else:
357
            print('Warning: Trying to send REFUSE even though client is None')
358

    
359
    # ------------------- streaming events handlers ----------------
360
    
361
    def on_streamer_state_changed(self, streamer, new_state):
362
        """
363
        Slot for scenic.streamer.StreamerManager.state_changed_signal
364
        """
365
        if new_state in [process.STATE_STOPPED]:
366
            if not self.got_bye:
367
                """ got_bye means our peer sent us a BYE, so we shouldn't send one back """
368
                print("Local StreamerManager stopped. Sending BYE")
369
                self.send_bye()
370
            
371
    def on_client_socket_error(self, client, err, msg):
372
        # XXX
373
        self.gui.hide_calling_dialog(msg)
374
        text = _("%s: %s") % (str(err), str(msg))
375
        self.gui.show_error_dialog(text)