sflphonectrlsimple.py 19.1 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
#
Alexandre Savard's avatar
Alexandre Savard committed
5
6
# Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com>
#
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 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

28
import gtk
Alexandre Savard's avatar
Alexandre Savard committed
29
30
import gobject
from gobject import GObject
31
from gobject import MainLoop
32

Alexandre Savard's avatar
Alexandre Savard committed
33
import getopt
34

Alexandre Savard's avatar
Alexandre Savard committed
35
import time
36
import hashlib
37

Alexandre Savard's avatar
Alexandre Savard committed
38
from threading import Thread
39
from threading import Event
40

Alexandre Savard's avatar
Alexandre Savard committed
41
from Errors import *
42

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


50
class SflPhoneCtrlSimple(Thread):
51
52
53
54
55
    """ Simple class for controlling SflPhoned through DBUS

        If option testSuite (ts) is put to true, 
	simple actions are implemented on incoming call.
    """ 
56
57
58
59

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

60
61
62
    def __init__(self, test=False, name=sys.argv[0]):
        print "Create SFLphone instance"
	Thread.__init__(self)
63
64
65
66
67
68
69
       	# current active account
        self.account = None
        # client name
        self.name = name
        # client registered to sflphoned ?
        self.registered = False
        self.register()
70
	self.currentCallId = ""
71

72
73
74
75
76
77
	self.loop = MainLoop()

	self.test = test
	self.onIncomingCall_cb = None
	self.event = Event()
	
78
79
80
81
82


    def __del__(self):
        if self.registered:
            self.unregister()
83
	self.loop.quit()
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121


    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:
            
122
            raise SPdbusError("Unable to bind to sflphoned api, ask core-dev team to implement getVersion method and start to pray.")
123
124
125
126
127
128
129
130

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

        try:
131
            print "Adding Incoming call method"
132
133
134
135
            proxy_callmgr.connect_to_signal('incomingCall', self.onIncomingCall)
            proxy_callmgr.connect_to_signal('callStateChanged', self.onCallStateChanged)
        except dbus.DBusException, e:
            print e
136
137


Alexandre Savard's avatar
Alexandre Savard committed
138

139
    def unregister(self):
Alexandre Savard's avatar
Alexandre Savard committed
140

141
142
        print "Unregister"

143
144
145
146
147
148
149
150
        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
151
152


153
154
    def isRegistered(self):
        return self.registered
Alexandre Savard's avatar
Alexandre Savard committed
155

156

157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
    def getEvent(self):
        return self.event

    def wait(self):
        self.event.wait()

    def isSet(self):
        self.event.isSet()

    def set(self):
        self.event.set()

    def clear(self):
        self.event.clear()

172
173
174
    #
    # Signal handling
    #
Alexandre Savard's avatar
Alexandre Savard committed
175

176
177
178
179
    # 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': '' }
180
	self.currentCallId = callid
181
182
183
184
185
186
187
188

	if(self.test):
            # TODO fix this bug in daemon, cannot answer too fast
            time.sleep(0.5)
	    if self.onIncomingCall_cb(self) is not None:
                self.onIncomingCall_cb(self)
	

189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205

    # 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)
Alexandre Savard's avatar
Alexandre Savard committed
206
207
                self.activeCalls[callid] = {'Account': callDetails['ACCOUNTID'], 
					    'To': callDetails['PEER_NUMBER'], 'State': state }
208
209
210
211
212
        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
213
214
215
216
217

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


218
219
220
    #
    # Account management
    #
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
    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)

248
    def getAllAccounts(self):
249
        """Return a list with all accounts"""
250
        return self.configurationmanager.getAccountList()
Alexandre Savard's avatar
Alexandre Savard committed
251
252


253
    def getAllEnabledAccounts(self):
254
        """Return a list with all enabled accounts"""
255
256
257
258
259
260
        accounts = self.getAllAccounts()
        activeaccounts = []
        for testedaccount in accounts:
            if self.isAccountEnable(testedaccount):
                activeaccounts.append(testedaccount)
        return activeaccounts
Alexandre Savard's avatar
Alexandre Savard committed
261
262


263
264
    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
265

266
267
268
269
270
271
272
        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
273

274
                return self.configurationmanager.getAccountDetails(account)
Alexandre Savard's avatar
Alexandre Savard committed
275
276


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

280
281
282
283
284
285
286
        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
287
288


289
290
    def getAccountByAlias(self, alias):
        """Get account name having its alias"""
Alexandre Savard's avatar
Alexandre Savard committed
291

292
293
294
295
        for account in self.getAllAccounts():
            details = self.getAccountDetails(account)
            if details['Account.alias'] == alias:
                return account
Alexandre Savard's avatar
Alexandre Savard committed
296

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

299
    def setAccount(self, account):
300
301
302
303
        """Define the active account

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

305
306
307
        if account in self.getAllAccounts():
            self.account = account
        else:
308
            print account
309
            raise SflPhoneError("Not a valid account")
Alexandre Savard's avatar
Alexandre Savard committed
310

311
312
    def setFirstRegisteredAccount(self):
        """Find the first enabled account and define it as active"""
Alexandre Savard's avatar
Alexandre Savard committed
313

314
315
316
317
        rAccounts = self.getAllRegisteredAccounts()
        if 0 == len(rAccounts):
            raise SflPhoneError("No registered account !")
        self.account = rAccounts[0]
Alexandre Savard's avatar
Alexandre Savard committed
318

319
320
    def setFirstActiveAccount(self):
        """Find the first enabled account and define it as active"""
Alexandre Savard's avatar
Alexandre Savard committed
321

322
323
324
325
        aAccounts = self.getAllEnabledAccounts()
        if 0 == len(aAccounts):
            raise SflPhoneError("No active account !")
        self.account = aAccounts[0]
Alexandre Savard's avatar
Alexandre Savard committed
326
327


328
329
    def getAccount(self):
        """Return the active account"""
Alexandre Savard's avatar
Alexandre Savard committed
330

331
        return self.account
Alexandre Savard's avatar
Alexandre Savard committed
332

333

334
335
    def isAccountRegistered(self, account=None):
        """Return True if the account is registered. If no account is provided, active account is used"""
336

337
338
339
340
341
        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"
342
343


344
345
    def isAccountEnable(self, account=None):
        """Return True if the account is enabled. If no account is provided, active account is used"""
346

347
348
349
350
351
        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"
352

353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
    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"""
377

378
379
380
381
        registeredAccountsList = []
        for account in self.getAllAccounts():
            if self.isAccountRegistered(account):
                registeredAccountsList.append(account)
382

383
        return registeredAccountsList
384

385
386
    def getAllEnabledAccounts(self):
        """Return a list of enabled accounts"""
387

388
389
390
391
        enabledAccountsList = []
        for accountName in self.getAllAccounts():
            if self.getAccountDetails(accountName)['Account.enable'] == "TRUE":
                 enabledAccountsList.append(accountName)
392

393
        return enabledAccountsList
394

395
396
397
398
399
400
    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)
401

402
        return sipAccountsList
403

404
405
    def getAllIaxAccounts(self):
        """Return a list of IAX accounts"""
406

407
408
409
410
        iaxAccountsList = []
        for accountName in self.getAllAccounts():
            if  self.getAccountDetails(accountName)['Account.type'] == "IAX":
                iaxAccountsList.append(accountName)
411

412
        return iaxAccountsList
413

414
415
    def setAccountRegistered(self, account=None, register=False):
       	""" Tries to register the account """
416

417
418
419
420
       	if account is None:
       		if self.account is None:
       			raise SflPhoneError("No provided or current account !")
       		account = self.account
421

422
423
424
425
426
427
428
429
430
       	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
431

432
433
434
    #
    # Codec manager
    #
435
                        
436
437
438
    def getCodecList(self):
        """ Return the codec list """
        return self.configurationmanager.getCodecList()
439

440
441
442
    def getActiveCodecList(self):
        """ Return the active codec list """
        return self.configurationmanager.getActiveCodecList()
443
444


445

446
447
448
    #
    # Call management
    #
449

450
451
    def getCurrentCallID(self):
        """Return the callID of the current call if any"""
452

453
        return self.callmanager.getCurrentCallID()
454
455


456
457
    def getCurrentCallDetails(self):
        """Return informations on the current call if any"""
458

459
        return self.callmanager.getCallDetails(self.getCurrentCallID())
460

461
462
    def getCallDetails(self, callid):
        """Return informations on this call if exists"""
463

464
        return self.callmanager.getCallDetails(callid)
465

466
467
468
469
470
    def printClientCallList(self):
        print "Client active call list:"
        print "------------------------"
        for call in self.activeCalls:
            print "\t" + call
471

472
473
474
475
    #
    # Action
    #
    def Call(self, dest):
476
        """Start a call and return a CallID
477

478
479
480
481
482
483
484
485
	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
	"""
486

487
	if dest is None or dest == "":
488
            raise SflPhoneError("Invalid call destination")
489
490
491
492
493
494
495
	
        # 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()
496

497
498
499
500
501
        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()	
502

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

506
507
        # Send the request to the CallManager
        self.callmanager.placeCall(self.account, callid, dest)
508

509
        return callid
510
511


512
513
514
515
    def HangUp(self, callid):
        """End a call identified by a CallID"""
        if not self.account:
            self.setFirstRegisteredAccount()
516

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

520
521
522
        if callid is None or callid == "":
            pass # just to see
            #raise SflPhoneError("Invalid callID")
523

524
	self.callmanager.hangUp(callid)
525
526


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

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

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

538
        self.callmanager.transfert(callid, to)
539
540


541
542
    def Refuse(self, callid):
        """Refuse an incoming call identified by a CallID"""
Alexandre Savard's avatar
Alexandre Savard committed
543

544
	print "Refuse call " + callid
Alexandre Savard's avatar
Alexandre Savard committed
545

546
547
        # if not self.account:
        #     self.setFirstRegisteredAccount()
548

549
550
        # if not self.isAccountRegistered():
        #     raise SflPhoneError("Can't refuse a call without a registered account")
551

552
553
        if callid is None or callid == "":
            raise SflPhoneError("Invalid callID")
554

555
        self.callmanager.refuse(callid)
556
557


558
559
    def Accept(self, callid):
        """Accept an incoming call identified by a CallID"""
560
	print "Accept call " + callid
561
562
        if not self.account:
            self.setFirstRegisteredAccount()
563

564
565
       	if not self.isAccountRegistered():
            raise SflPhoneError("Can't accept a call without a registered account")
566

567
568
        if callid is None or callid == "":
            raise SflPhoneError("Invalid callID")
569
	
570
        self.callmanager.accept(callid)
571
572


573
574
    def Hold(self, callid):
        """Hold a call identified by a CallID"""
575
576
        # if not self.account:
        #    self.setFirstRegisteredAccount()
577

578
579
        # if not self.isAccountRegistered():
        #    raise SflPhoneError("Can't hold a call without a registered account")
580

581
582
        if callid is None or callid == "":
            raise SflPhoneError("Invalid callID")
583

584
        self.callmanager.hold(callid)
585
586


587
588
    def UnHold(self, callid):
        """Unhold an incoming call identified by a CallID"""
589
590
        # if not self.account:
        #    self.setFirstRegisteredAccount()
591

592
593
        # if not self.isAccountRegistered():
        #    raise SflPhoneError("Can't unhold a call without a registered account")
594

595
596
        if callid is None or callid == "":
            raise SflPhoneError("Invalid callID")
597

598
        self.callmanager.unhold(callid)
599
600


601
602
603
    def Dtmf(self, key):
        """Send a DTMF"""
        self.callmanager.playDTMF(key)
604
605


606
607
608
609
610
611
612
613
    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
614
615

    def run(self):
Alexandre Savard's avatar
Alexandre Savard committed
616
        """Processing method for this thread"""
617
618
619
620
621
622
        gobject.threads_init()
        # self.loop.run()
	context = self.loop.get_context()

	while 1:
            context.iteration(True)