Statistics
| Revision:

root / sip-settings @ 151

History | View | Download

1
#!/usr/bin/env python
2
# Copyright (C) 2008-2010 AG Projects. See LICENSE for details.
3
#
4

    
5
import fcntl
6
import os
7
import re
8
import struct
9
import sys
10
import termios
11

    
12
from collections import deque
13
from optparse import OptionParser
14

    
15
from sipsimple.account import Account, BonjourAccount, AccountManager
16
from sipsimple.application import SIPApplication
17
from sipsimple.configuration import ConfigurationError, ConfigurationManager, DefaultValue, Setting, SettingsGroupMeta
18
from sipsimple.configuration.datatypes import List, STUNServerAddress
19
from sipsimple.configuration.settings import SIPSimpleSettings
20
from sipsimple.storage import FileStorage
21
from sipsimple.threading import ThreadManager
22

    
23
from sipclient.configuration import config_directory
24
from sipclient.configuration.account import AccountExtension
25
from sipclient.configuration.settings import SIPSimpleSettingsExtension
26

    
27

    
28
def format_child(obj, attrname, maxchars):
29
    linebuf = attrname
30
    if isinstance(getattr(type(obj), attrname, None), Setting):
31
        attr = getattr(obj, attrname)
32
        if isinstance(attr, unicode):
33
            string = attr.encode(sys.getfilesystemencoding())
34
        else:
35
            string = str(attr)
36
        if maxchars is not None:
37
            maxchars -= len(attrname)+4
38
            if len(string) > maxchars:
39
                string = string[:maxchars-3]+'...'
40
        linebuf += ' = ' + string
41
    return linebuf
42

    
43
def display_object(obj, name):
44
    # get terminal width
45
    if sys.stdout.isatty():
46
        width = struct.unpack('HHHH', fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, struct.pack('HHHH', 0, 0, 0, 0)))[1]
47
    else:
48
        width = None
49

    
50
    children = deque([child for child in dir(type(obj)) if isinstance(getattr(type(obj), child, None), Setting)] + \
51
                     [child for child in dir(type(obj)) if isinstance(getattr(type(obj), child, None), SettingsGroupMeta)])
52
    # display first line
53
    linebuf = ' '*(len(name)+3) + '+'
54
    if children:
55
        linebuf += '-- ' + format_child(obj, children.popleft(), width-(len(name)+7) if width is not None else None)
56
    print linebuf
57
    # display second line
58
    linebuf = name + ' --|'
59
    if children:
60
        linebuf += '-- ' + format_child(obj, children.popleft(), width-(len(name)+7) if width is not None else None)
61
    print linebuf
62
    # display the rest of the lines
63
    if children:
64
        while children:
65
            child = children.popleft()
66
            linebuf = ' '*(len(name)+3) + ('|' if children else '+') + '-- ' + format_child(obj, child, width-(len(name)+7) if width is not None else None)
67
            print linebuf
68
    else:
69
        linebuf = ' '*(len(name)+3) + '+'
70
        print linebuf
71

    
72
    print
73

    
74
    [display_object(getattr(obj, child), child) for child in dir(type(obj)) if isinstance(getattr(type(obj), child, None), SettingsGroupMeta)]
75

    
76
class SettingsParser(object):
77

    
78
    @classmethod
79
    def parse_default(cls, type, value):
80
        if issubclass(type, List):
81
            values = re.split(r'\s*,\s*', value)
82
            return values
83
        elif issubclass(type, bool):
84
            if value.lower() == 'true':
85
                return True
86
            else:
87
                return False
88
        elif issubclass(type, unicode):
89
            if isinstance(value, str):
90
                return value.decode(sys.getfilesystemencoding())
91
            return value
92
        else:
93
            return value
94

    
95
    @classmethod
96
    def parse_MSRPRelayAddress(cls, type, value):
97
        return type.from_description(value)
98

    
99
    @classmethod
100
    def parse_SIPProxyAddress(cls, type, value):
101
        return type.from_description(value)
102

    
103
    @classmethod
104
    def parse_STUNServerAddress(cls, type, value):
105
        return type.from_description(value)
106

    
107
    @classmethod
108
    def parse_STUNServerAddressList(cls, type, value):
109
        values = re.split(r'\s*,\s*', value)
110
        return [STUNServerAddress.from_description(v) for v in values]
111

    
112
    @classmethod
113
    def parse_PortRange(cls, type, value):
114
        return type(*value.split(':', 1))
115

    
116
    @classmethod
117
    def parse_Resolution(cls, type, value):
118
        return type(*value.split('x', 1))
119

    
120
    @classmethod
121
    def parse_SoundFile(cls, type, value):
122
        if ',' in value:
123
            path, volume = value.split(',', 1)
124
        else:
125
            path, volume = value, 100
126
        return type(path, volume)
127

    
128
    @classmethod
129
    def parse_AccountSoundFile(cls, type, value):
130
        if ',' in value:
131
            path, volume = value.split(',', 1)
132
        else:
133
            path, volume = value, 100
134
        return type(path, volume)
135

    
136
    @classmethod
137
    def parse(cls, type, value):
138
        if value == 'None':
139
            return None
140
        if value == 'DEFAULT':
141
            return DefaultValue
142
        parser = getattr(cls, 'parse_%s' % type.__name__, cls.parse_default)
143
        return parser(type, value)
144

    
145

    
146
class AccountConfigurator(object):
147
    def __init__(self):
148
        Account.register_extension(AccountExtension)
149
        BonjourAccount.register_extension(AccountExtension)
150
        self.configuration_manager = ConfigurationManager()
151
        self.configuration_manager.start()
152
        self.account_manager = AccountManager()
153
        self.account_manager.load()
154

    
155
    def list(self):
156
        print 'Accounts:'
157
        bonjour_account = BonjourAccount()
158
        accounts = [account for account in self.account_manager.get_accounts() if account.id != bonjour_account.id]
159
        accounts.sort(cmp=lambda a, b: cmp(a.id, b.id))
160
        accounts.append(bonjour_account)
161
        for account in accounts:
162
            print '  %s (%s)%s' % (account.id, 'enabled' if account.enabled else 'disabled', ' - default_account' if account is self.account_manager.default_account else '')
163

    
164
    def add(self, sip_address, password):
165
        if self.account_manager.has_account(sip_address):
166
            print 'Account %s already exists' % sip_address
167
            return
168
        try:
169
            account = Account(sip_address)
170
        except ValueError, e:
171
            print 'Cannot add SIP account: %s' % str(e)
172
            return
173
        account.auth.password = password
174
        account.enabled = True
175
        account.save()
176
        print 'Account added'
177

    
178
    def delete(self, sip_address):
179
        if sip_address != 'ALL':
180
            possible_accounts = [account for account in self.account_manager.iter_accounts() if sip_address in account.id]
181
            if len(possible_accounts) > 1:
182
                print "More than one account exists which matches %s: %s" % (sip_address, ", ".join(sorted(account.id for account in possible_accounts)))
183
                return
184
            if len(possible_accounts) == 0:
185
                print 'Account %s does not exist' % sip_address
186
                return
187
            account = possible_accounts[0]
188
            if account == BonjourAccount():
189
                print 'Cannot delete bonjour account'
190
                return
191
            account.delete()
192
            print 'Account deleted'
193
        else:
194
            for account in self.account_manager.get_accounts():
195
                account.delete()
196
            print 'Accounts deleted'
197

    
198
    def show(self, sip_address=None):
199
        if sip_address is None:
200
            accounts = [self.account_manager.default_account]
201
            if accounts[0] is None:
202
                print "No accounts configured"
203
                return
204
        else:
205
            if sip_address != 'ALL':
206
                accounts = [account for account in self.account_manager.iter_accounts() if sip_address in account.id]
207
            else:
208
                accounts = self.account_manager.get_accounts()
209
            if not accounts:
210
                print 'No accounts which match %s' % sip_address
211
                return
212
        for account in accounts:
213
            print 'Account %s:' % account.id
214
            display_object(account, 'account')
215

    
216
    def set(self, *args):
217
        if not args:
218
            raise TypeError("set must receive at least one argument")
219
        if '=' in args[0]:
220
            accounts = [self.account_manager.default_account]
221
            if accounts[0] is None:
222
                print "No accounts configured"
223
                return
224
        else:
225
            sip_address = args[0]
226
            args = args[1:]
227
            if sip_address != 'ALL':
228
                accounts = [account for account in self.account_manager.iter_accounts() if sip_address in account.id]
229
            else:
230
                accounts = self.account_manager.get_accounts()
231
            if not accounts:
232
                print 'No accounts which match %s' % sip_address
233
                return
234

    
235
        try:
236
            settings = dict(arg.split('=', 1) for arg in args)
237
        except ValueError:
238
            print 'Illegal arguments: %s' % ' '.join(args)
239
            return
240

    
241
        for account in accounts:
242
            for attrname, value in settings.iteritems():
243
                object = account
244
                name = attrname
245
                while '.' in name:
246
                    local_name, name = name.split('.', 1)
247
                    try:
248
                        object = getattr(object, local_name)
249
                    except AttributeError:
250
                        print 'Unknown setting: %s' % attrname
251
                        object = None
252
                        break
253
                if object is not None:
254
                    try:
255
                        attribute = getattr(type(object), name)
256
                        value = SettingsParser.parse(attribute.type, value)
257
                        setattr(object, name, value)
258
                    except AttributeError:
259
                        print 'Unknown setting: %s' % attrname
260
                    except ValueError, e:
261
                        print '%s: %s' % (attrname, str(e))
262

    
263
            account.save()
264
        print 'Account%s updated' % ('s' if len(accounts) > 1 else '')
265

    
266
    def default(self, sip_address):
267
        possible_accounts = [account for account in self.account_manager.iter_accounts() if sip_address in account.id]
268
        if len(possible_accounts) > 1:
269
            print "More than one account exists which matches %s: %s" % (sip_address, ", ".join(sorted(account.id for account in possible_accounts)))
270
            return
271
        if len(possible_accounts) == 0:
272
            print 'Account %s does not exist' % sip_address
273
            return
274
        account = possible_accounts[0]
275
        try:
276
            self.account_manager.default_account = account
277
        except ValueError, e:
278
            print str(e)
279
            return
280
        print 'Account %s is now default account' % account.id
281

    
282

    
283
class SIPSimpleConfigurator(object):
284
    def __init__(self):
285
        SIPSimpleSettings.register_extension(SIPSimpleSettingsExtension)
286
        self.configuration_manager = ConfigurationManager()
287
        self.configuration_manager.start()
288
        SIPSimpleSettings()
289

    
290
    def show(self):
291
        print 'SIP SIMPLE settings:'
292
        display_object(SIPSimpleSettings(), 'SIP SIMPLE')
293

    
294
    def set(self, *args):
295
        sipsimple_settings = SIPSimpleSettings()
296
        try:
297
            settings = dict(arg.split('=', 1) for arg in args)
298
        except ValueError:
299
            print 'Illegal arguments: %s' % ' '.join(args)
300
            return
301

    
302
        for attrname, value in settings.iteritems():
303
            object = sipsimple_settings
304
            name = attrname
305
            while '.' in name:
306
                local_name, name = name.split('.', 1)
307
                try:
308
                    object = getattr(object, local_name)
309
                except AttributeError:
310
                    print 'Unknown setting: %s' % attrname
311
                    object = None
312
                    break
313
            if object is not None:
314
                try:
315
                    attribute = getattr(type(object), name)
316
                    value = SettingsParser.parse(attribute.type, value)
317
                    setattr(object, name, value)
318
                except AttributeError:
319
                    print 'Unknown setting: %s' % attrname
320
                except ValueError, e:
321
                    print '%s: %s' % (attrname, str(e))
322

    
323
        sipsimple_settings.save()
324
        print 'SIP SIMPLE general settings updated'
325

    
326

    
327
if __name__ == '__main__':
328
    description = "This script manages the SIP SIMPLE client SDK settings."
329
    usage = """%prog [--general|--account] [options] command [arguments]
330
       %prog --general show
331
       %prog --general set key1=value1 [key2=value2 ...]
332
       %prog --account list
333
       %prog --account add user@domain password
334
       %prog --account delete user@domain|ALL
335
       %prog --account show [user@domain|ALL]
336
       %prog --account set [user@domain|ALL] key1=value1|DEFAULT [key2=value2|DEFAULT ...]
337
       %prog --account default user@domain"""
338
    parser = OptionParser(usage=usage, description=description)
339
    parser.print_usage = parser.print_help
340
    parser.add_option('-c', '--config-directory', type='string', dest='config_directory', help='The configuration directory to use. This overrides the default location.')
341
    parser.add_option("-a", "--account", action="store_true", dest="account", help="Manage SIP accounts' settings")
342
    parser.add_option("-g", "--general", action="store_true", dest="general", help="Manage general SIP SIMPLE middleware settings")
343
    options, args = parser.parse_args()
344
    # exactly one of -a or -g must be specified
345
    if (not (options.account or options.general)) or (options.account and options.general):
346
        parser.print_usage()
347
        sys.exit(1)
348

    
349
    # there must be at least one command
350
    if not args:
351
        sys.stderr.write("Error: no command specified\n")
352
        parser.print_usage()
353
        sys.exit(1)
354

    
355
    SIPApplication.storage = FileStorage(options.config_directory or config_directory)
356
    thread_manager = ThreadManager()
357
    thread_manager.start()
358

    
359
    # execute the handlers
360
    try:
361
        if options.account:
362
            object = AccountConfigurator()
363
        else:
364
            object = SIPSimpleConfigurator()
365
    except ConfigurationError, e:
366
        sys.stderr.write("Failed to load sipclient's configuration: %s\n" % str(e))
367
        sys.stderr.write("If an old configuration file is in place, delete it or move it and recreate the configuration using the sip_settings script.\n")
368
    else:
369
        command, args = args[0], args[1:]
370
        handler = getattr(object, command, None)
371
        if handler is None or not callable(handler):
372
            sys.stderr.write("Error: illegal command: %s\n" % command)
373
            parser.print_usage()
374
            sys.exit(1)
375

    
376
        try:
377
            handler(*args)
378
        except TypeError:
379
            sys.stderr.write("Error: illegal usage of command %s\n" % command)
380
            parser.print_usage()
381
            sys.exit(1)
382
    finally:
383
        thread_manager.stop()
384

    
385