3
# Copyright (C) 2009 by the Free Software Foundation, Inc.
5
# Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com>
7
# This program is free software; you can redistribute it and/or
8
# modify it under the terms of the GNU General Public License
9
# as published by the Free Software Foundation; either version 2
10
# of the License, or (at your option) any later version.
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
17
# You should have received a copy of the GNU General Public License
18
# along with this program; if not, write to the Free Software
19
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21
"""Simple class for controlling SflPhoned through DBUS"""
26
from traceback import print_exc
30
from gobject import GObject
31
from gobject import MainLoop
38
from threading import Thread
39
from threading import Event
45
from dbus.mainloop.glib import DBusGMainLoop
46
except ImportError, e:
47
raise SflPhoneError("No python-dbus module found")
50
class SflPhoneCtrlSimple(Thread):
51
""" Simple class for controlling SflPhoned through DBUS
53
If option testSuite (ts) is put to true,
54
simple actions are implemented on incoming call.
57
# list of active calls (known by the client)
60
def __init__(self, test=False, name=sys.argv[0]):
61
print "Create SFLphone instance"
63
# current active account
67
# client registered to sflphoned ?
68
self.registered = False
70
self.currentCallId = ""
72
self.loop = MainLoop()
77
self.onIncomingCall_cb = None
80
gobject.threads_init()
91
print "Stop PySFLphone"
101
# register the main loop for d-bus events
102
DBusGMainLoop(set_as_default=True)
103
self.bus = dbus.SessionBus()
104
except dbus.DBusException, e:
105
raise SPdbusError("Unable to connect DBUS session bus")
107
dbus_objects = dbus.Interface(self.bus.get_object(
108
'org.freedesktop.DBus', '/org/freedesktop/DBus'),
109
'org.freedesktop.DBus').ListNames()
111
if not "org.sflphone.SFLphone" in dbus_objects:
112
raise SPdbusError("Unable to find org.sflphone.SFLphone in DBUS. Check if sflphoned is running")
115
proxy_instance = self.bus.get_object("org.sflphone.SFLphone",
116
"/org/sflphone/SFLphone/Instance", introspect=False)
117
proxy_callmgr = self.bus.get_object("org.sflphone.SFLphone",
118
"/org/sflphone/SFLphone/CallManager", introspect=False)
119
proxy_confmgr = self.bus.get_object("org.sflphone.SFLphone",
120
"/org/sflphone/SFLphone/ConfigurationManager",
123
self.instance = dbus.Interface(proxy_instance,
124
"org.sflphone.SFLphone.Instance")
125
self.callmanager = dbus.Interface(proxy_callmgr,
126
"org.sflphone.SFLphone.CallManager")
127
self.configurationmanager = dbus.Interface(proxy_confmgr,
128
"org.sflphone.SFLphone.ConfigurationManager")
130
except dbus.DBusException, e:
132
raise SPdbusError("Unable to bind to sflphoned api, ask core-dev team to implement getVersion method and start to pray.")
135
self.instance.Register(os.getpid(), self.name)
136
self.registered = True
138
raise SPdaemonError("Client registration failed")
141
print "Adding Incoming call method"
142
proxy_callmgr.connect_to_signal('incomingCall', self.onIncomingCall)
143
proxy_callmgr.connect_to_signal('callStateChanged', self.onCallStateChanged)
144
except dbus.DBusException, e:
149
def unregister(self):
153
if not self.registered:
155
#raise SflPhoneError("Not registered !")
157
self.instance.Unregister(os.getpid())
158
self.registered = False
160
raise SPdaemonError("Client unregistration failed")
163
def isRegistered(self):
164
return self.registered
186
# On incoming call event, add the call to the list of active calls
187
def onIncomingCall(self, account, callid, to):
188
print "Incoming call: " + account + ", " + callid + ", " + to
189
self.activeCalls[callid] = {'Account': account, 'To': to, 'State': '' }
190
self.currentCallId = callid
193
# TODO fix this bug in daemon, cannot answer too fast
195
if self.onIncomingCall_cb(self) is not None:
196
self.onIncomingCall_cb(self)
200
# On call state changed event, set the values for new calls,
201
# or delete the call from the list of active calls
202
def onCallStateChanged(self, callid, state):
203
print "Call state changed: " + callid + ", " + state
204
if state == "HUNGUP":
206
del self.activeCalls[callid]
208
print "Call " + callid + " didn't exist. Cannot delete."
210
elif state in [ "RINGING", "CURRENT", "INCOMING", "HOLD" ]:
212
self.activeCalls[callid]['State'] = state
214
print "This call didn't exist!: " + callid + ". Adding it to the list."
215
callDetails = self.getCallDetails(callid)
216
self.activeCalls[callid] = {'Account': callDetails['ACCOUNTID'],
217
'To': callDetails['PEER_NUMBER'], 'State': state }
218
elif state in [ "BUSY", "FAILURE" ]:
220
del self.activeCalls[callid]
222
print "This call didn't exist!: " + callid
224
# elif state == "UNHOLD_CURRENT":
225
# self.activeCalls[callid]['State'] = "UNHOLD_CURRENT"
231
def addAccount(self, details=None):
232
"""Add a new account account
234
Add a new account to the SFLphone-daemon. Default parameters are \
235
used for missing account configuration field.
237
Required parameters are type, alias, hostname, username and password
244
raise SPaccountError("Must specifies type, alias, hostname, \
245
username and password in \
246
order to create a new account")
248
return self.configurationmanager.addAccount(details)
250
def removeAccount(self, accountID=None):
251
"""Remove an account from internal list"""
253
if accountID is None:
254
raise SPaccountError("Account ID must be specified")
256
self.configurationmanager.removeAccount(accountID)
258
def getAllAccounts(self):
259
"""Return a list with all accounts"""
260
return self.configurationmanager.getAccountList()
263
def getAllEnabledAccounts(self):
264
"""Return a list with all enabled accounts"""
265
accounts = self.getAllAccounts()
267
for testedaccount in accounts:
268
if self.isAccountEnable(testedaccount):
269
activeaccounts.append(testedaccount)
270
return activeaccounts
273
def getAccountDetails(self, account=None):
274
"""Return a list of string. If no account is provided, active account is used"""
277
if self.account is None:
278
raise SflPhoneError("No provided or current account !")
279
if checkAccountExists(self.account):
280
return self.configurationmanager.getAccountDetails(self.account)
282
if self.checkAccountExists(account):
284
return self.configurationmanager.getAccountDetails(account)
287
def setAccountByAlias(self, alias):
288
"""Define as active the first account who match with the alias"""
290
for testedaccount in self.getAllAccounts():
291
details = self.getAccountDetails(testedaccount)
292
if ( details['Account.enable'] == "TRUE" and
293
details['Account.alias'] == alias ):
294
self.account = testedaccount
296
raise SPaccountError("No enabled account matched with alias")
299
def getAccountByAlias(self, alias):
300
"""Get account name having its alias"""
302
for account in self.getAllAccounts():
303
details = self.getAccountDetails(account)
304
if details['Account.alias'] == alias:
307
raise SPaccountError("No account matched with alias")
309
def setAccount(self, account):
310
"""Define the active account
312
The active account will be used when sending a new call
315
if account in self.getAllAccounts():
316
self.account = account
319
raise SflPhoneError("Not a valid account")
321
def setFirstRegisteredAccount(self):
322
"""Find the first enabled account and define it as active"""
324
rAccounts = self.getAllRegisteredAccounts()
325
if 0 == len(rAccounts):
326
raise SflPhoneError("No registered account !")
327
self.account = rAccounts[0]
329
def setFirstActiveAccount(self):
330
"""Find the first enabled account and define it as active"""
332
aAccounts = self.getAllEnabledAccounts()
333
if 0 == len(aAccounts):
334
raise SflPhoneError("No active account !")
335
self.account = aAccounts[0]
338
def getAccount(self):
339
"""Return the active account"""
344
def isAccountRegistered(self, account=None):
345
"""Return True if the account is registered. If no account is provided, active account is used"""
348
if self.account is None:
349
raise SflPhoneError("No provided or current account !")
350
account = self.account
351
return self.getAccountDetails(account)['Status'] == "REGISTERED"
354
def isAccountEnable(self, account=None):
355
"""Return True if the account is enabled. If no account is provided, active account is used"""
358
if self.account is None:
359
raise SflPhoneError("No provided or current account !")
360
account = self.account
361
return self.getAccountDetails(account)['Account.enable'] == "TRUE"
363
def setAccountEnable(self, account=None, enable=False):
364
"""Set account enabled"""
366
if self.account is None:
367
raise SflPhoneError("No provided or current account !")
368
account = self.account
371
details = self.getAccountDetails(account)
372
details['Account.enable'] = "TRUE"
373
self.configurationmanager.setAccountDetails(account, details)
375
details = self.getAccountDetails(account)
376
details['Account.enable'] = "FALSE"
377
self.configurationmanager.setAccountDetails(account, details)
379
def checkAccountExists(self, account=None):
380
""" Checks if the account exists """
382
raise SflPhoneError("No provided or current account !")
383
return account in self.getAllAccounts()
385
def getAllRegisteredAccounts(self):
386
"""Return a list of registered accounts"""
388
registeredAccountsList = []
389
for account in self.getAllAccounts():
390
if self.isAccountRegistered(account):
391
registeredAccountsList.append(account)
393
return registeredAccountsList
395
def getAllEnabledAccounts(self):
396
"""Return a list of enabled accounts"""
398
enabledAccountsList = []
399
for accountName in self.getAllAccounts():
400
if self.getAccountDetails(accountName)['Account.enable'] == "TRUE":
401
enabledAccountsList.append(accountName)
403
return enabledAccountsList
405
def getAllSipAccounts(self):
406
"""Return a list of SIP accounts"""
408
for accountName in self.getAllAccounts():
409
if self.getAccountDetails(accountName)['Account.type'] == "SIP":
410
sipAccountsList.append(accountName)
412
return sipAccountsList
414
def getAllIaxAccounts(self):
415
"""Return a list of IAX accounts"""
418
for accountName in self.getAllAccounts():
419
if self.getAccountDetails(accountName)['Account.type'] == "IAX":
420
iaxAccountsList.append(accountName)
422
return iaxAccountsList
424
def setAccountRegistered(self, account=None, register=False):
425
""" Tries to register the account """
428
if self.account is None:
429
raise SflPhoneError("No provided or current account !")
430
account = self.account
434
self.configurationmanager.sendRegister(account, int(1))
435
#self.setAccount(account)
437
self.configurationmanager.sendRegister(account, int(0))
438
#self.setFirstRegisteredAccount()
439
except SflPhoneError, e:
446
def getCodecList(self):
447
""" Return the codec list """
448
return self.configurationmanager.getCodecList()
450
def getActiveCodecList(self):
451
""" Return the active codec list """
452
return self.configurationmanager.getActiveCodecList()
460
def getCurrentCallID(self):
461
"""Return the callID of the current call if any"""
463
return self.callmanager.getCurrentCallID()
466
def getCurrentCallDetails(self):
467
"""Return informations on the current call if any"""
469
return self.callmanager.getCallDetails(self.getCurrentCallID())
471
def getCallDetails(self, callid):
472
"""Return informations on this call if exists"""
474
return self.callmanager.getCallDetails(callid)
476
def printClientCallList(self):
477
print "Client active call list:"
478
print "------------------------"
479
for call in self.activeCalls:
485
def Call(self, dest):
486
"""Start a call and return a CallID
488
Use the current account previously set using setAccount().
489
If no account specified, first registered one in account list is used.
491
For phone number prefixed using SIP scheme (i.e. sip: or sips:),
492
IP2IP profile is automatically selected and set as the default account
494
return callID Newly generated callidentifier for this call
497
if dest is None or dest == "":
498
raise SflPhoneError("Invalid call destination")
500
# Set the account to be used for this call
501
if dest.find('sip:') is 0 or dest.find('sips:') is 0:
503
self.setAccount("IP2IP")
504
elif not self.account:
505
self.setFirstRegisteredAccount()
507
if self.account is "IP2IP" and self.isAccountRegistered():
508
raise SflPhoneError("Can't place a call without a registered account")
510
# Generate a call ID for this call
511
callid = self.GenerateCallID()
513
# Add the call to the list of active calls and set status to SENT
514
self.activeCalls[callid] = {'Account': self.account, 'To': dest, 'State': 'SENT' }
516
# Send the request to the CallManager
517
self.callmanager.placeCall(self.account, callid, dest)
522
def HangUp(self, callid):
523
"""End a call identified by a CallID"""
525
self.setFirstRegisteredAccount()
527
# if not self.isAccountRegistered() and self.accout is not "IP2IP":
528
# raise SflPhoneError("Can't hangup a call without a registered account")
530
if callid is None or callid == "":
532
#raise SflPhoneError("Invalid callID")
534
self.callmanager.hangUp(callid)
537
def Transfer(self, callid, to):
538
"""Transfert a call identified by a CallID"""
539
# if not self.account:
540
# self.setFirstRegisteredAccount()
542
# if not self.isAccountRegistered():
543
# raise SflPhoneError("Can't transfert a call without a registered account")
545
if callid is None or callid == "":
546
raise SflPhoneError("Invalid callID")
548
self.callmanager.transfert(callid, to)
551
def Refuse(self, callid):
552
"""Refuse an incoming call identified by a CallID"""
554
print "Refuse call " + callid
556
# if not self.account:
557
# self.setFirstRegisteredAccount()
559
# if not self.isAccountRegistered():
560
# raise SflPhoneError("Can't refuse a call without a registered account")
562
if callid is None or callid == "":
563
raise SflPhoneError("Invalid callID")
565
self.callmanager.refuse(callid)
568
def Accept(self, callid):
569
"""Accept an incoming call identified by a CallID"""
570
print "Accept call " + callid
572
self.setFirstRegisteredAccount()
574
if not self.isAccountRegistered():
575
raise SflPhoneError("Can't accept a call without a registered account")
577
if callid is None or callid == "":
578
raise SflPhoneError("Invalid callID")
580
self.callmanager.accept(callid)
583
def Hold(self, callid):
584
"""Hold a call identified by a CallID"""
585
# if not self.account:
586
# self.setFirstRegisteredAccount()
588
# if not self.isAccountRegistered():
589
# raise SflPhoneError("Can't hold a call without a registered account")
591
if callid is None or callid == "":
592
raise SflPhoneError("Invalid callID")
594
self.callmanager.hold(callid)
597
def UnHold(self, callid):
598
"""Unhold an incoming call identified by a CallID"""
599
# if not self.account:
600
# self.setFirstRegisteredAccount()
602
# if not self.isAccountRegistered():
603
# raise SflPhoneError("Can't unhold a call without a registered account")
605
if callid is None or callid == "":
606
raise SflPhoneError("Invalid callID")
608
self.callmanager.unhold(callid)
613
self.callmanager.playDTMF(key)
616
def GenerateCallID(self):
617
"""Generate Call ID"""
619
t = long( time.time() * 1000 )
620
r = long( random.random()*100000000000000000L )
621
m.update(str(t) + str(r))
622
callid = m.hexdigest()
626
"""Processing method for this thread"""
628
context = self.loop.get_context()
631
context.iteration(True)