sflphonectrlsimple.py 18.2 KB
Newer Older
1
2
#!/usr/bin/env python
#
Alexandre Savard's avatar
Alexandre Savard committed
3
# Copyright (C) 2009 by the Free Software Foundation, Inc.
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

"""Simple class for controlling SflPhoned through DBUS"""

import sys
import os
import random
from traceback import print_exc

Alexandre Savard's avatar
Alexandre Savard committed
26
27
import gobject
from gobject import GObject
28

Alexandre Savard's avatar
Alexandre Savard committed
29
import getopt
30

Alexandre Savard's avatar
Alexandre Savard committed
31
import time
32
import hashlib
33

Alexandre Savard's avatar
Alexandre Savard committed
34
from threading import Thread
35
from threading import Event
36

Alexandre Savard's avatar
Alexandre Savard committed
37
from Errors import *
38

Alexandre Savard's avatar
Alexandre Savard committed
39
40
41
42
43
try:
	import dbus
	from dbus.mainloop.glib import DBusGMainLoop
except ImportError, e:
	raise SflPhoneError("No python-dbus module found")
44
45


46
class SflPhoneCtrlSimple(Thread):
47
48
49
50
51
52
    """Simple class for controlling SflPhoned through DBUS"""

    # list of active calls (known by the client)
    activeCalls = {}

    def __init__(self, name=sys.argv[0]):
53
        Thread.__init__(self)
54
55
56
57
58
59
60
       	# current active account
        self.account = None
        # client name
        self.name = name
        # client registered to sflphoned ?
        self.registered = False
        self.register()
61
62
	self.event = Event()
	self.currentCallId = ""
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105


    def __del__(self):
        if self.registered:
            self.unregister()


    def register(self):
        if self.registered:
            return

        try:
            # register the main loop for d-bus events
            DBusGMainLoop(set_as_default=True)
            self.bus = dbus.SessionBus()
        except dbus.DBusException, e:
            raise SPdbusError("Unable to connect DBUS session bus")

        dbus_objects = dbus.Interface(self.bus.get_object(
              'org.freedesktop.DBus', '/org/freedesktop/DBus'), 
                      'org.freedesktop.DBus').ListNames()

        if not "org.sflphone.SFLphone" in dbus_objects:
            raise SPdbusError("Unable to find org.sflphone.SFLphone in DBUS. Check if sflphoned is running")

        try:
            proxy_instance = self.bus.get_object("org.sflphone.SFLphone",
		 "/org/sflphone/SFLphone/Instance", introspect=False)
            proxy_callmgr = self.bus.get_object("org.sflphone.SFLphone",
		 "/org/sflphone/SFLphone/CallManager", introspect=False)
            proxy_confmgr = self.bus.get_object("org.sflphone.SFLphone", 
                 "/org/sflphone/SFLphone/ConfigurationManager", 
                        introspect=False)

            self.instance = dbus.Interface(proxy_instance,
                          "org.sflphone.SFLphone.Instance")
            self.callmanager = dbus.Interface(proxy_callmgr,
		          "org.sflphone.SFLphone.CallManager")
            self.configurationmanager = dbus.Interface(proxy_confmgr,
			  "org.sflphone.SFLphone.ConfigurationManager")

        except dbus.DBusException, e:
            
106
            raise SPdbusError("Unable to bind to sflphoned api, ask core-dev team to implement getVersion method and start to pray.")
107
108
109
110
111
112
113
114
115
116
117
118

        try:
            self.instance.Register(os.getpid(), self.name)
            self.registered = True
        except:
            raise SPdaemonError("Client registration failed")

        try:
            proxy_callmgr.connect_to_signal('incomingCall', self.onIncomingCall)
            proxy_callmgr.connect_to_signal('callStateChanged', self.onCallStateChanged)
        except dbus.DBusException, e:
            print e
Alexandre Savard's avatar
Alexandre Savard committed
119
120
			

121
    def unregister(self):
Alexandre Savard's avatar
Alexandre Savard committed
122

123
124
125
126
127
128
129
130
        if not self.registered:
            return
            #raise SflPhoneError("Not registered !")
        try:
            self.instance.Unregister(os.getpid())
            self.registered = False
        except:
            raise SPdaemonError("Client unregistration failed")
Alexandre Savard's avatar
Alexandre Savard committed
131
132


133
134
    def isRegistered(self):
        return self.registered
Alexandre Savard's avatar
Alexandre Savard committed
135

136
137
138
139

    #
    # Signal handling
    #
Alexandre Savard's avatar
Alexandre Savard committed
140

141
142
143
144
    # On incoming call event, add the call to the list of active calls
    def onIncomingCall(self, account, callid, to):
        print "Incoming call: " + account + ", " + callid + ", " + to
        self.activeCalls[callid] = {'Account': account, 'To': to, 'State': '' }
145
146
147
	self.currentCallId = callid
	self.event.set()
	Answer(callid)
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170

    # On call state changed event, set the values for new calls, 
    # or delete the call from the list of active calls
    def onCallStateChanged(self, callid, state):
        print "Call state changed: " + callid + ", " + state
        if state == "HUNGUP":
            try:
                del self.activeCalls[callid]
            except KeyError:
                print "Call " + callid + " didn't exist. Cannot delete."

        elif state in [ "RINGING", "CURRENT", "INCOMING", "HOLD" ]:
            try:
                self.activeCalls[callid]['State'] = state 
            except KeyError, e:
                print "This call didn't exist!: " + callid + ". Adding it to the list."
                callDetails = self.getCallDetails(callid)
                self.activeCalls[callid] = {'Account': callDetails['ACCOUNTID'], 'To': callDetails['PEER_NUMBER'], 'State': state }
        elif state in [ "BUSY", "FAILURE" ]:
            try:
                del self.activeCalls[callid]
            except KeyError, e:
                print "This call didn't exist!: " + callid
Alexandre Savard's avatar
Alexandre Savard committed
171
172
173
174
175

#		elif state == "UNHOLD_CURRENT":
#			self.activeCalls[callid]['State'] = "UNHOLD_CURRENT"


176
177
178
    #
    # Account management
    #
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
    def addAccount(self, details=None):
        """Add a new account account

	Add a new account to the SFLphone-daemon. Default parameters are \ 
	used for missing account configuration field.

	Required parameters are type, alias, hostname, username and password

	input details
	
	"""

	if details is None:
            raise SPaccountError("Must specifies type, alias, hostname, \
                                  username and password in \
                                  order to create a new account")

	return self.configurationmanager.addAccount(details)

    def removeAccount(self, accountID=None):
        """Remove an account from internal list"""

	if accountID is None:
            raise SPaccountError("Account ID must be specified")

        self.configurationmanager.removeAccount(accountID)

206
    def getAllAccounts(self):
207
        """Return a list with all accounts"""
208
        return self.configurationmanager.getAccountList()
Alexandre Savard's avatar
Alexandre Savard committed
209
210


211
    def getAllEnabledAccounts(self):
212
        """Return a list with all enabled accounts"""
213
214
215
216
217
218
        accounts = self.getAllAccounts()
        activeaccounts = []
        for testedaccount in accounts:
            if self.isAccountEnable(testedaccount):
                activeaccounts.append(testedaccount)
        return activeaccounts
Alexandre Savard's avatar
Alexandre Savard committed
219
220


221
222
    def getAccountDetails(self, account=None):
        """Return a list of string. If no account is provided, active account is used"""
Alexandre Savard's avatar
Alexandre Savard committed
223

224
225
226
227
228
229
230
        if account is None:
            if self.account is None:
                raise SflPhoneError("No provided or current account !")
                if checkAccountExists(self.account):
                    return self.configurationmanager.getAccountDetails(self.account)
        else:
            if self.checkAccountExists(account):
Alexandre Savard's avatar
Alexandre Savard committed
231

232
                return self.configurationmanager.getAccountDetails(account)
Alexandre Savard's avatar
Alexandre Savard committed
233
234


235
236
    def setAccountByAlias(self, alias):
        """Define as active the first account who match with the alias"""
Alexandre Savard's avatar
Alexandre Savard committed
237

238
239
240
241
242
243
244
        for testedaccount in self.getAllAccounts():
            details = self.getAccountDetails(testedaccount)
            if ( details['Account.enable'] == "TRUE" and 
                              details['Account.alias'] == alias ):
                self.account = testedaccount
                return
        raise SPaccountError("No enabled account matched with alias")
Alexandre Savard's avatar
Alexandre Savard committed
245
246


247
248
    def getAccountByAlias(self, alias):
        """Get account name having its alias"""
Alexandre Savard's avatar
Alexandre Savard committed
249

250
251
252
253
        for account in self.getAllAccounts():
            details = self.getAccountDetails(account)
            if details['Account.alias'] == alias:
                return account
Alexandre Savard's avatar
Alexandre Savard committed
254

255
        raise SPaccountError("No account matched with alias")
Alexandre Savard's avatar
Alexandre Savard committed
256

257
    def setAccount(self, account):
258
259
260
261
        """Define the active account

	The active account will be used when sending a new call
	"""
Alexandre Savard's avatar
Alexandre Savard committed
262

263
264
265
        if account in self.getAllAccounts():
            self.account = account
        else:
266
            print account
267
            raise SflPhoneError("Not a valid account")
Alexandre Savard's avatar
Alexandre Savard committed
268

269
270
    def setFirstRegisteredAccount(self):
        """Find the first enabled account and define it as active"""
Alexandre Savard's avatar
Alexandre Savard committed
271

272
273
274
275
        rAccounts = self.getAllRegisteredAccounts()
        if 0 == len(rAccounts):
            raise SflPhoneError("No registered account !")
        self.account = rAccounts[0]
Alexandre Savard's avatar
Alexandre Savard committed
276

277
278
    def setFirstActiveAccount(self):
        """Find the first enabled account and define it as active"""
Alexandre Savard's avatar
Alexandre Savard committed
279

280
281
282
283
        aAccounts = self.getAllEnabledAccounts()
        if 0 == len(aAccounts):
            raise SflPhoneError("No active account !")
        self.account = aAccounts[0]
Alexandre Savard's avatar
Alexandre Savard committed
284
285


286
287
    def getAccount(self):
        """Return the active account"""
Alexandre Savard's avatar
Alexandre Savard committed
288

289
        return self.account
Alexandre Savard's avatar
Alexandre Savard committed
290

291

292
293
    def isAccountRegistered(self, account=None):
        """Return True if the account is registered. If no account is provided, active account is used"""
294

295
296
297
298
299
        if account is None:
                if self.account is None:
                        raise SflPhoneError("No provided or current account !")
                account = self.account
        return self.getAccountDetails(account)['Status'] == "REGISTERED"
300
301


302
303
    def isAccountEnable(self, account=None):
        """Return True if the account is enabled. If no account is provided, active account is used"""
304

305
306
307
308
309
        if account is None:
	       	if self.account is None:
		       	raise SflPhoneError("No provided or current account !")
                account = self.account
        return self.getAccountDetails(account)['Account.enable'] == "TRUE"
310

311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
    def setAccountEnable(self, account=None, enable=False):
       	"""Set account enabled"""
        if account is None:
	       	if self.account is None:
		       	raise SflPhoneError("No provided or current account !")
                account = self.account

       	if enable == True:
	       	details = self.getAccountDetails(account)
                details['Account.enable'] = "TRUE"
                self.configurationmanager.setAccountDetails(account, details)
        else:
	       	details = self.getAccountDetails(account)
                details['Account.enable'] = "FALSE"
                self.configurationmanager.setAccountDetails(account, details)

    def checkAccountExists(self, account=None):
        """ Checks if the account exists """
        if account is None:
            raise SflPhoneError("No provided or current account !")
        return account in self.getAllAccounts()
			
    def getAllRegisteredAccounts(self):
        """Return a list of registered accounts"""
335

336
337
338
339
        registeredAccountsList = []
        for account in self.getAllAccounts():
            if self.isAccountRegistered(account):
                registeredAccountsList.append(account)
340

341
        return registeredAccountsList
342

343
344
    def getAllEnabledAccounts(self):
        """Return a list of enabled accounts"""
345

346
347
348
349
        enabledAccountsList = []
        for accountName in self.getAllAccounts():
            if self.getAccountDetails(accountName)['Account.enable'] == "TRUE":
                 enabledAccountsList.append(accountName)
350

351
        return enabledAccountsList
352

353
354
355
356
357
358
    def getAllSipAccounts(self):
        """Return a list of SIP accounts"""
        sipAccountsList = []
        for accountName in self.getAllAccounts(): 
            if  self.getAccountDetails(accountName)['Account.type'] == "SIP":
                sipAccountsList.append(accountName)
359

360
        return sipAccountsList
361

362
363
    def getAllIaxAccounts(self):
        """Return a list of IAX accounts"""
364

365
366
367
368
        iaxAccountsList = []
        for accountName in self.getAllAccounts():
            if  self.getAccountDetails(accountName)['Account.type'] == "IAX":
                iaxAccountsList.append(accountName)
369

370
        return iaxAccountsList
371

372
373
    def setAccountRegistered(self, account=None, register=False):
       	""" Tries to register the account """
374

375
376
377
378
       	if account is None:
       		if self.account is None:
       			raise SflPhoneError("No provided or current account !")
       		account = self.account
379

380
381
382
383
384
385
386
387
388
       	try:
       		if register:
       			self.configurationmanager.sendRegister(account, int(1))
       			#self.setAccount(account)
       		else:
       			self.configurationmanager.sendRegister(account, int(0))
       			#self.setFirstRegisteredAccount()
        except SflPhoneError, e:
       		print e
389

390
391
392
    #
    # Codec manager
    #
393
                        
394
395
396
    def getCodecList(self):
        """ Return the codec list """
        return self.configurationmanager.getCodecList()
397

398
399
400
    def getActiveCodecList(self):
        """ Return the active codec list """
        return self.configurationmanager.getActiveCodecList()
401
402


403

404
405
406
    #
    # Call management
    #
407

408
409
    def getCurrentCallID(self):
        """Return the callID of the current call if any"""
410

411
        return self.callmanager.getCurrentCallID()
412
413


414
415
    def getCurrentCallDetails(self):
        """Return informations on the current call if any"""
416

417
        return self.callmanager.getCallDetails(self.getCurrentCallID())
418

419
420
    def getCallDetails(self, callid):
        """Return informations on this call if exists"""
421

422
        return self.callmanager.getCallDetails(callid)
423

424
425
426
427
428
    def printClientCallList(self):
        print "Client active call list:"
        print "------------------------"
        for call in self.activeCalls:
            print "\t" + call
429

430
431
432
433
    #
    # Action
    #
    def Call(self, dest):
434
        """Start a call and return a CallID
435

436
437
438
439
440
441
442
443
	Use the current account previously set using setAccount().
	If no account specified, first registered one in account list is used.

	For phone number prefixed using SIP scheme (i.e. sip: or sips:),
	IP2IP profile is automatically selected and set as the default account

	return callID Newly generated callidentifier for this call
	"""
444

445
	if dest is None or dest == "":
446
            raise SflPhoneError("Invalid call destination")
447
448
449
450
451
452
453
	
        # Set the account to be used for this call
	if dest.find('sip:') is 0 or dest.find('sips:') is 0:
            print "Ip 2 IP call"
	    self.setAccount("IP2IP")
	elif not self.account:
            self.setFirstRegisteredAccount()
454

455
456
457
458
459
        if self.account is "IP2IP" and self.isAccountRegistered():
            raise SflPhoneError("Can't place a call without a registered account")

        # Generate a call ID for this call
        callid = self.GenerateCallID()	
460

461
462
        # Add the call to the list of active calls and set status to SENT
        self.activeCalls[callid] = {'Account': self.account, 'To': dest, 'State': 'SENT' }
463

464
465
        # Send the request to the CallManager
        self.callmanager.placeCall(self.account, callid, dest)
466

467
        return callid
468
469


470
471
472
473
    def HangUp(self, callid):
        """End a call identified by a CallID"""
        if not self.account:
            self.setFirstRegisteredAccount()
474

475
476
        # if not self.isAccountRegistered() and self.accout is not "IP2IP":
        #    raise SflPhoneError("Can't hangup a call without a registered account")
477

478
479
480
        if callid is None or callid == "":
            pass # just to see
            #raise SflPhoneError("Invalid callID")
481

482
	self.callmanager.hangUp(callid)
483
484


485
486
487
488
    def Transfert(self, callid, to):
        """Transfert a call identified by a CallID"""
        if not self.account:
            self.setFirstRegisteredAccount()
489

490
491
        if not self.isAccountRegistered():
            raise SflPhoneError("Can't transfert a call without a registered account")
492

493
494
        if callid is None or callid == "":
            raise SflPhoneError("Invalid callID")
495

496
        self.callmanager.transfert(callid, to)
497
498


499
500
501
502
    def Refuse(self, callid):
        """Refuse an incoming call identified by a CallID"""
        if not self.account:
            self.setFirstRegisteredAccount()
503

504
505
        if not self.isAccountRegistered():
            raise SflPhoneError("Can't refuse a call without a registered account")
506

507
508
        if callid is None or callid == "":
            raise SflPhoneError("Invalid callID")
509

510
        self.callmanager.refuse(callid)
511
512


513
514
515
516
    def Accept(self, callid):
        """Accept an incoming call identified by a CallID"""
        if not self.account:
            self.setFirstRegisteredAccount()
517

518
519
       	if not self.isAccountRegistered():
            raise SflPhoneError("Can't accept a call without a registered account")
520

521
522
        if callid is None or callid == "":
            raise SflPhoneError("Invalid callID")
523

524
        self.callmanager.accept(callid)
525
526


527
528
529
530
    def Hold(self, callid):
        """Hold a call identified by a CallID"""
        if not self.account:
            self.setFirstRegisteredAccount()
531

532
533
        if not self.isAccountRegistered():
            raise SflPhoneError("Can't hold a call without a registered account")
534

535
536
        if callid is None or callid == "":
            raise SflPhoneError("Invalid callID")
537

538
        self.callmanager.hold(callid)
539
540


541
542
543
544
    def UnHold(self, callid):
        """Unhold an incoming call identified by a CallID"""
        if not self.account:
            self.setFirstRegisteredAccount()
545

546
547
        if not self.isAccountRegistered():
            raise SflPhoneError("Can't unhold a call without a registered account")
548

549
550
        if callid is None or callid == "":
            raise SflPhoneError("Invalid callID")
551

552
        self.callmanager.unhold(callid)
553
554


555
556
557
    def Dtmf(self, key):
        """Send a DTMF"""
        self.callmanager.playDTMF(key)
558
559


560
561
562
563
564
565
566
567
    def GenerateCallID(self):
        """Generate Call ID"""
	m = hashlib.md5()
        t = long( time.time() * 1000 )
        r = long( random.random()*100000000000000000L )
        m.update(str(t) + str(r))
        callid = m.hexdigest()
	return callid
568
569
570
571
572
573
574
575
576
577


    def run(self):

        while(True):
            print "Waiting Event"
	    self.event.wait()
	    sflphone.Accept(sflphone.currentCallId)
	    self.event.clear()
	    print "Call Accepted"