diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..f014ec2 --- /dev/null +++ b/README.txt @@ -0,0 +1,4 @@ +Introduction +============ + + diff --git a/docs/HISTORY.txt b/docs/HISTORY.txt new file mode 100644 index 0000000..149cf49 --- /dev/null +++ b/docs/HISTORY.txt @@ -0,0 +1,8 @@ +Changelog +========= + +1.0 - Unreleased +---------------- + +* Initial release + diff --git a/pyogp/__init__.py b/pyogp/__init__.py new file mode 100644 index 0000000..f48ad10 --- /dev/null +++ b/pyogp/__init__.py @@ -0,0 +1,6 @@ +# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages +try: + __import__('pkg_resources').declare_namespace(__name__) +except ImportError: + from pkgutil import extend_path + __path__ = extend_path(__path__, __name__) diff --git a/pyogp/lib/__init__.py b/pyogp/lib/__init__.py new file mode 100644 index 0000000..f48ad10 --- /dev/null +++ b/pyogp/lib/__init__.py @@ -0,0 +1,6 @@ +# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages +try: + __import__('pkg_resources').declare_namespace(__name__) +except ImportError: + from pkgutil import extend_path + __path__ = extend_path(__path__, __name__) diff --git a/pyogp/lib/base/__init__.py b/pyogp/lib/base/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pyogp/lib/base/agent.py b/pyogp/lib/base/agent.py new file mode 100644 index 0000000..5d69f9a --- /dev/null +++ b/pyogp/lib/base/agent.py @@ -0,0 +1,46 @@ +from zope.interface import implements +from zope.component import adapts + +from interfaces import IAgent, IPlaceAvatarAdapter + +class Agent(object): + """an OGP agent""" + + implements(IAgent) + + def __init__(self, agentdomain): + """initialize this agent""" + self.agentdomain = agentdomain + + + +class PlaceAvatarAdapter(object): + """handles placing an avatar for an agent object""" + implements(IPlaceAvatarAdapter) + adapts(IAgent) + + def __init__(self, agent): + """initialize this adapter""" + self.agent = agent + + # let's retrieve the cap we need + self.seed_cap = self.agent.agentdomain.seed_cap # ISeedCapability + self.place_avatar_cap = self.seed_cap.get(['place_avatar'])['place_avatar'] + + def __call__(self, region): + """initiate the placing process""" + region_uri = region.uri + payload = {'region_url' : region_uri } + result = self.place_avatar_cap(payload) + + return result + +# now we register this adapter so it can be used later: +from zope.component import provideAdapter + +# register adapters for the HTML node +provideAdapter(PlaceAvatarAdapter) + + + + \ No newline at end of file diff --git a/pyogp/lib/base/agentdomain.py b/pyogp/lib/base/agentdomain.py new file mode 100644 index 0000000..9899fdd --- /dev/null +++ b/pyogp/lib/base/agentdomain.py @@ -0,0 +1,43 @@ +from agent import Agent +from interfaces import ICredentialSerializer +from caps import SeedCapability +import urllib2 + + +# URL Opener for the agent domain login +# +class RedirectHandler(urllib2.HTTPRedirectHandler): + + def http_error_302(self, req, fp, code, msg, headers): + #ignore the redirect, grabbing the seed cap url from the headers + # TODO: add logging and error handling + return headers['location'] + + +# post to auth.cgi, ignoring the built in redirect +AgentDomainLoginOpener = urllib2.build_opener(RedirectHandler()) + + +class AgentDomain(object): + """an agent domain endpoint""" + + def __init__(self,uri): + """initialize the agent domain endpoint""" + self.uri = uri + + def login(self, credentials): + """login to the agent domain and return an agent object""" + serializer = ICredentialSerializer(credentials) # convert to string via adapter + payload = serializer.serialize() + headers = serializer.headers + print payload, headers + + # now create the request. We assume for now that self.uri is the login uri + # TODO: make this pluggable so we can use other transports like eventlet in the future + # TODO: add logging and error handling + request = urllib2.Request(self.uri,payload,headers) + seed_cap_url = AgentDomainLoginOpener.open(request) + self.seed_cap = SeedCapability('seed_cap', seed_cap_url) + return Agent(self) + + diff --git a/pyogp/lib/base/caps.py b/pyogp/lib/base/caps.py new file mode 100644 index 0000000..85e4b9c --- /dev/null +++ b/pyogp/lib/base/caps.py @@ -0,0 +1,58 @@ +from zope.interface import implements + +import urllib2 +from indra.base import llsd + +from interfaces import ICapability, ISeedCapability + +class Capability(object): + """models a capability""" + + implements(ICapability) + + def __init__(self, name, private_url): + """initialize the capability""" + + self.name = name + self.private_url = private_url + + def __call__(self,payload,custom_headers={}): + """call this capability, return the parsed result""" + + headers = {"Content-type" : "application/llsd+xml"} + headers.update(custom_headers) + llsd_payload = llsd.format_xml(payload) + + # TODO: better errorhandling with own exceptions + try: + request = urllib2.Request(self.private_url, llsd_payload, headers) + result = urllib2.urlopen(request).read() + except urllib2.HTTPError, e: + print "** failure while calling cap:", + print e.read() + raise + return llsd.parse(result) + + +class SeedCapability(Capability): + """a seed capability which is able to retrieve other capabilities""" + + implements(ISeedCapability) + + def get(self, names=[]): + """if this is a seed cap we can retrieve other caps here""" + payload = {'caps':names} + parsed_result = self(payload)['caps'] + + caps = {} + for name in names: + # TODO: some caps might be seed caps, how do we know? + caps[name]=Capability(name, parsed_result[name]) + + return caps + + + + + + diff --git a/pyogp/lib/base/credentials.py b/pyogp/lib/base/credentials.py new file mode 100644 index 0000000..ca5a65a --- /dev/null +++ b/pyogp/lib/base/credentials.py @@ -0,0 +1,54 @@ +from zope.interface import implements +from zope.component import adapts + +from indra.base import llsd + + +from interfaces import IPlainPasswordCredential, ICredentialSerializer + +class PlainPasswordCredential(object): + """a plain password credential""" + + implements(IPlainPasswordCredential) + + def __init__(self, firstname, lastname, password): + """initialize this credential""" + self.firstname = firstname + self.lastname = lastname + self.password = password + + +# an adapter to serialize this to LLSD + +class PlainPasswordLLSDSerializer(object): + """converts a plain password credential to LLSD""" + + implements(ICredentialSerializer) + adapts(IPlainPasswordCredential) + + def __init__(self, context): + """initialize this adapter by storing the context (the credential)""" + self.context = context + + def serialize(self): + """return the credential as a string""" + + loginparams={ + 'password' : self.context.password, + 'lastname' : self.context.lastname, + 'firstname' : self.context.firstname + } + + llsdlist = llsd.format_xml(loginparams) + return llsdlist + + @property + def headers(self): + """return HTTP headers needed here""" + return {"Content-type" : "application/llsd+xml"} + +# now we register this adapter so it can be used later: +from zope.component import provideAdapter + +# register adapters for the HTML node +provideAdapter(PlainPasswordLLSDSerializer) diff --git a/pyogp/lib/base/example.py b/pyogp/lib/base/example.py new file mode 100644 index 0000000..8795523 --- /dev/null +++ b/pyogp/lib/base/example.py @@ -0,0 +1,46 @@ +from pyogp.lib.base.credentials import PlainPasswordCredential +from pyogp.lib.base.agentdomain import AgentDomain +from pyogp.lib.base.regiondomain import Region + +from pyogp.lib.base.interfaces import IPlaceAvatarAdapter + +import getpass, sys +from optparse import OptionParser + + +class ExampleLogin(object): + + def login(self): + parser = OptionParser() + + parser.add_option("-a", "--agentdomain", dest="loginuri", default="https://login1.aditi.lindenlab.com/cgi-bin/auth.cgi", + help="URI of Agent Domain") + parser.add_option("-r", "--region", dest="regionuri", default="http://sim1.vaak.lindenlab.com:13000", + help="URI of Region to connect to") + + (options, args) = parser.parse_args() + + firstname = args[0] + lastname = args[1] + password = getpass.getpass() + + credentials = PlainPasswordCredential(firstname, lastname, password) + + agentdomain = AgentDomain(options.loginuri) + agent = agentdomain.login(credentials) + + print "logged in, we now have an agent: ", agent + + place = IPlaceAvatarAdapter(agent) + region = Region(options.regionuri) + + print "now we try to place the avatar on a region" + avatar = place(region) + + #avatar.establish_presence() + # +def main(): + return ExampleLogin().login() + +if __name__=="__main__": + main() diff --git a/pyogp/lib/base/interfaces.py b/pyogp/lib/base/interfaces.py new file mode 100644 index 0000000..251d860 --- /dev/null +++ b/pyogp/lib/base/interfaces.py @@ -0,0 +1,69 @@ +from zope.interface import Interface, Attribute + +class ICredential(Interface): + """base interface for credentials""" + +class IPlainPasswordCredential(ICredential): + """a plain password credential""" + + firstname = Attribute("""first name of avatar""") + lastname = Attribute("""last name of avatar""") + password = Attribute("""plain password""") + +class ICredentialSerializer(Interface): + """converts a credential to a serialized format for sending it over the network""" + + def serialize(): + """return a serialized string""" + + def headers(): + """return headers eventually needed for sending it out""" + + +class IAgent(Interface): + """models an agent""" + + agentdomain = Attribute("""the agent domain endpoint""") + + +class IRegion(Interface): + """a region endpoint""" + + def place_avatar(agent): + """place an avatar on this region, returns IAvatar""" + +class IAvatar(Interface): + """an OGP avatar (region representation of an agent)""" + + def establish_presence(): + """for now it will do a loop to establish a presence on a region""" + +class IPlaceAvatarAdapter(Interface): + """adapts an agents to a method which can place an avatar on a region""" + + def __call__(region): + """takes a region objects and tries to place the agent there as an avatar + + return an IAvatar""" + +class ICapability(Interface): + """a capability""" + + name = Attribute('''name of the capability''') + private_url = Attribute('''private url of this capability''') + + def __call__(payload): + """call this capability + + payload -- the payload as python dictionary + returns a python dictionary with the results + + """ + +class ISeedCapability(ICapability): + """a seed capability which is able to retrieve further capabilities""" + + def get(names=[]): + """retrieve the given set of named capabilities + + returns a dict of ICapabilty objects keyed by their name""" \ No newline at end of file diff --git a/pyogp/lib/base/regiondomain.py b/pyogp/lib/base/regiondomain.py new file mode 100644 index 0000000..e1a3b97 --- /dev/null +++ b/pyogp/lib/base/regiondomain.py @@ -0,0 +1,14 @@ +from zope.interface import implements + +from interfaces import IRegion + +class Region(object): + """models a region endpoint""" + + implements(IRegion) + + def __init__(self, uri): + """initialize the region with the region uri""" + self.uri = uri + + \ No newline at end of file diff --git a/pyogp/lib/base/tests/login.txt b/pyogp/lib/base/tests/login.txt new file mode 100644 index 0000000..4d21b48 --- /dev/null +++ b/pyogp/lib/base/tests/login.txt @@ -0,0 +1,30 @@ +Login +===== + +>>> from pyogp.lib.base.credentials import PlainPasswordCredential +>>> from pyogp.lib.base.agentdomain import AgentDomain +>>> from pyogp.lib.base.regiondomain import Region + +First we create some credentials: +>>> credentials = PlainPasswordCredential('Firstname', 'Lastname', 'password') + +Then we need some agent domain to connect to. This might automatically retrieve some XRDS file to get the actual login endpoint: +>>> agentdomain = AgentDomain('http://agent.domain') + +Now we can use both to get an agent object (which transparently handles capabilities etc.): +>>> agent = AgentDomain.login(credentials) + +The next step is to use this agent to actually place the avatar somewhere. We therefor need a region: +>>> region = Region('http://region.uri') + +Note that we can also first retrieve a RegionDomain object and ask this for possible regions and a map etc. + + +So let's place the agent in form of an avatar there (or try it at least): +>>> avatar = region.place_avatar(agent) + +Now we should establish a presence there: + +avatar.establish_presence() + +As this is an infinite loop the question is how this could be handled. Maybe in a different thread? \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..01bb954 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,3 @@ +[egg_info] +tag_build = dev +tag_svn_revision = true diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..ff384e5 --- /dev/null +++ b/setup.py @@ -0,0 +1,37 @@ +from setuptools import setup, find_packages +import os + +version = '1.0' + +setup(name='pyogp.lib.base', + version=version, + description="basic pyogp library package", + long_description=open("README.txt").read() + "\n" + + open(os.path.join("docs", "HISTORY.txt")).read(), + # Get more strings from http://www.python.org/pypi?%3Aaction=list_classifiers + classifiers=[ + "Programming Language :: Python", + "Topic :: Software Development :: Libraries :: Python Modules", + ], + keywords='pyogp login awg virtualworlds', + author='Christian Scholz', + author_email='mrtopf@gmail.com', + url='', + license='GPL', + packages=find_packages(exclude=['ez_setup']), + namespace_packages=['pyogp', 'pyogp.lib'], + include_package_data=True, + zip_safe=False, + install_requires=[ + 'setuptools', + 'zope.interface', + 'zope.component' + # -*- Extra requirements: -*- + ], + entry_points={ + 'console_scripts': [ + 'login = pyogp.lib.base.example:main', + ], + }, + + ) diff --git a/zopeskel.txt b/zopeskel.txt new file mode 100644 index 0000000..263c12d --- /dev/null +++ b/zopeskel.txt @@ -0,0 +1 @@ +nested_namespace \ No newline at end of file