#!/usr/bin/python # -*- encoding: utf-8 -*- # Copyright (c) Contributors, http://opensimulator.org/ # See CONTRIBUTORS.TXT for a full list of copyright holders. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the OpenSim Project nor the # names of its contributors may be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE # GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER # IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import xml.etree.ElementTree as ET import re import urllib import urllib2 import ConfigParser import optparse import os import sys def longHelp(): print ''' matrix.py is a little launcher tool that knows about the GridInfo protocol. it expects the grid coordinates to be passed as a command line argument either as a "matrix:" or as an "opensim:" style URI: matrix://osgrid.org:8002/ you can also provide region/X/Y/Z coordinates: matrix://osgrid.org:8002/Wright%20Plaza/128/50/75 and, it also understands avatar names and passwords: matrix://mr%20smart:secretpassword@osgrid.org:8002/Wright%20Plaza/128/50/75 when you run it the first time, it will complain about a missing .matrixcfg file --- this is needed so that it can remember where your secondlife client lives on your box. to generate that file, simply run matrix.py --secondlife path-to-your-secondlife-client-executable ''' def ParseOptions(): '''Parse the command line options and setup options. ''' parser = optparse.OptionParser() parser.add_option('-c', '--config', dest = 'config', help = 'config file', metavar = 'CONFIG') parser.add_option('-s', '--secondlife', dest = 'client', help = 'location of secondlife client', metavar = 'SL-CLIENT') parser.add_option('-l', '--longhelp', action='store_true', dest = 'longhelp', help = 'longhelp') (options, args) = parser.parse_args() if options.longhelp: parser.print_help() longHelp() sys.exit(0) return options def ParseConfig(options): '''Ensure configuration exists and parse it. ''' # # we are using ~/.matrixcfg to store the location of the # secondlife client, os.path.normpath and os.path.expanduser # should make sure that we are fine cross-platform # if not options.config: options.config = '~/.matrixcfg' cfgPath = os.path.normpath(os.path.expanduser(options.config)) # # if ~/.matrixcfg does not exist we are in trouble... # if not os.path.exists(cfgPath) and not options.client: print '''oops, i've no clue where your secondlife client lives around here. i suggest you run me with the "--secondlife path-of-secondlife-client" argument.''' sys.exit(1) # # ok, either ~/.matrixcfg does exist or we are asked to create it # config = ConfigParser.ConfigParser() if os.path.exists(cfgPath): config.readfp(open(cfgPath)) if options.client: if config.has_option('secondlife', 'client'): config.remove_option('secondlife', 'client') if not config.has_section('secondlife'): config.add_section('secondlife') config.set('secondlife', 'client', options.client) cfg = open(cfgPath, mode = 'w+') config.write(cfg) cfg.close() return config.get('secondlife', 'client') # # regex: parse a URI # reURI = re.compile(r'''^(?P[a-zA-Z0-9]+):// # scheme ((?P[^:@]+)(:(?P[^@]+))?@)? # avatar name and password (optional) (?P[^:/]+)(:(?P\d+))? # host, port (optional) (?P/.*) # path $''', re.IGNORECASE | re.VERBOSE) # # regex: parse path as location # reLOC = re.compile(r'''^/(?P[^/]+)/ # region name (?P\d+)/ # X position (?P\d+)/ # Y position (?P\d+) # Z position ''', re.IGNORECASE | re.VERBOSE) def ParseUri(uri): '''Parse a URI and return its constituent parts. ''' match = reURI.match(uri) if not match or not match.group('scheme') or not match.group('host'): print 'hmm... cannot parse URI %s, giving up' % uri sys.exit(1) scheme = match.group('scheme') host = match.group('host') port = match.group('port') avatar = match.group('avatar') password = match.group('password') path = match.group('path') return (scheme, host, port, avatar, password, path) def ParsePath(path): '''Try and parse path as /region/X/Y/Z. ''' loc = None match = reLOC.match(path) if match: loc = 'secondlife:///%s/%d/%d/%d' % (match.group('region'), int(match.group('x')), int(match.group('y')), int(match.group('z'))) return loc def GetGridInfo(host, port): '''Invoke /get_grid_info on target grid and obtain additional parameters ''' gridname = None gridnick = None login = None welcome = None economy = None # # construct GridInfo URL # if port: gridInfoURI = 'http://%s:%d/get_grid_info' % (host, int(port)) else: gridInfoURI = 'http://%s/get_grid_info' % (host) # # try to retrieve GridInfo # try: gridInfoXml = ET.parse(urllib2.urlopen(gridInfoURI)) gridname = gridInfoXml.findtext('/gridname') gridnick = gridInfoXml.findtext('/gridnick') login = gridInfoXml.findtext('/login') welcome = gridInfoXml.findtext('/welcome') economy = gridInfoXml.findtext('/economy') authenticator = gridInfoXml.findtext('/authenticator') except urllib2.URLError: print 'oops, failed to retrieve grid info, proceeding with guestimates...' return (gridname, gridnick, login, welcome, economy) def StartClient(client, nick, login, welcome, economy, avatar, password, location): clientArgs = [ client ] clientArgs += ['-loginuri', login] if welcome: clientArgs += ['-loginpage', welcome] if economy: clientArgs += ['-helperuri', economy] if avatar and password: clientArgs += ['-login'] clientArgs += urllib.unquote(avatar).split() clientArgs += [password] if location: clientArgs += [location] # # all systems go # os.execv(client, clientArgs) if __name__ == '__main__': # # parse command line options and deal with help requests # options = ParseOptions() client = ParseConfig(options) # # sanity check: URI supplied? # if not sys.argv: print 'missing opensim/matrix URI' sys.exit(1) # # parse URI and extract scheme. host, port?, avatar?, password? # uri = sys.argv.pop() (scheme, host, port, avatar, password, path) = ParseUri(uri) # # sanity check: matrix: or opensim: scheme? # if scheme != 'matrix' and scheme != 'opensim': print 'hmm...unknown scheme %s, calling it a day' % scheme # # get grid info from OpenSim server # (gridname, gridnick, login, welcome, economy) = GetGridInfo(host, port) # # fallback: use supplied uri in case GridInfo drew a blank # if not login: login = uri # # take a closer look at path: if it's a /region/X/Y/Z pattern, use # it as the "SLURL # location = ParsePath(path) StartClient(client, gridnick, login, welcome, economy, avatar, password, location)