mirror of
git://git.psyced.org/git/pypsyc
synced 2024-08-15 03:20:04 +00:00
last state we had in cvs
This commit is contained in:
commit
0f02e9cd76
128 changed files with 9224 additions and 0 deletions
24
fippos-twisted/README
Normal file
24
fippos-twisted/README
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
pyPSYC - asyncore based library for PSYC
|
||||
|
||||
The application programming interface (API) of this library is stable,
|
||||
says fippo. But he left it unmaintained since 2005.
|
||||
|
||||
|
||||
News at http://about.psyc.eu/pypsyc
|
||||
Download by anonCVS from the same server as psyced.
|
||||
:pserver:anonymous@cvs.psyced.org:/CVS/anoCVS
|
||||
|
||||
|
||||
QUESTIONS?
|
||||
/tell psyc://psyced.org/~fippo or come to psyc://psyced.org/@welcome
|
||||
|
||||
AUTHORS
|
||||
Philipp Hancke
|
||||
<psyc://psyced.org/~fippo>
|
||||
|
||||
Tim Head
|
||||
<psyc://psyced.org/~betatim>
|
||||
|
||||
Andreas Neue
|
||||
<psyc://psyced.org/~an>
|
||||
|
||||
230
fippos-twisted/contrib/ircd.py
Normal file
230
fippos-twisted/contrib/ircd.py
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
# usage: twistd -noy ircd.py
|
||||
|
||||
import sys
|
||||
from twisted.application import service, internet
|
||||
from pypsyc.center import ServerCenter
|
||||
from pypsyc.net import PSYCServerFactory
|
||||
from pypsyc.objects import PSYCReceiver
|
||||
from pypsyc.objects.server import Person, Place, GroupSlave
|
||||
from pypsyc import parseURL
|
||||
|
||||
from twisted.protocols import irc
|
||||
from twisted.internet.protocol import ServerFactory
|
||||
|
||||
|
||||
"""
|
||||
watch out, the main purpose of this thing is to debug the context slave
|
||||
system
|
||||
"""
|
||||
|
||||
class IRCD(irc.IRC, PSYCReceiver):
|
||||
center = None
|
||||
password = ''
|
||||
nick = ''
|
||||
url = ''
|
||||
source = None
|
||||
def __init__(self, center, hostname):
|
||||
self.center = center
|
||||
self.hostname = hostname
|
||||
def connectionMade(self):
|
||||
peer = self.transport.getPeer()
|
||||
self.source = 'object:%s:%d'%(peer.host, peer.port)
|
||||
self.center.register_object(self.source, self)
|
||||
def connectionLost(self, reason):
|
||||
if self.url:
|
||||
self.irc_QUIT(None, None)
|
||||
elif self.nick is '':
|
||||
print 'closing unknown connection'
|
||||
def sendNumeric(self, numeric, msg):
|
||||
self.sendLine(':%s %s %s :%s'%(self.hostname, numeric, self.nick, msg))
|
||||
# def sendLine(self, line):
|
||||
# irc.IRC.sendLine(self, line.encode('iso-8859-1'))
|
||||
def sendMessage(self, source, target, cmd, data = None):
|
||||
s = ':%s %s %s'%(self.url2prefix(source), target, cmd)
|
||||
if data:
|
||||
s += ' :%s'%data
|
||||
self.sendLine(s)
|
||||
def sNotice(self, data):
|
||||
self.sendLine(':%s NOTICE %s :%s'%(self.hostname, self.nick, data))
|
||||
"""helper functions"""
|
||||
def expandUrl(self, target):
|
||||
"""quick and dirty is_uniform check"""
|
||||
if target.find(':') is -1:
|
||||
if target[0] == '#':
|
||||
return self.hostname + '/@%s'%target[1:]
|
||||
return self.hostname + '/~%s'%target
|
||||
if target[0] == '#':
|
||||
return target[1:]
|
||||
return target
|
||||
def minimizeUrl(self, source):
|
||||
if source.startswith(self.hostname):
|
||||
# remark: +2 to skip trailing /@
|
||||
return source[len(self.hostname) + 2:]
|
||||
return source
|
||||
def url2prefix(self, url):
|
||||
if url.find(':') != -1:
|
||||
u = parseURL(url)
|
||||
if u['resource'][0] == '~':
|
||||
ident = u['resource'][1:]
|
||||
else:
|
||||
ident = '*'
|
||||
host = u['host']
|
||||
else:
|
||||
ident = url # its a local nick
|
||||
host = 'localuser'
|
||||
return '%s!%s@%s'%(url, ident, host)
|
||||
|
||||
"""irc_ command hooks"""
|
||||
def irc_USER(self, prefix, params):
|
||||
pass
|
||||
def irc_PASS(self, prefix, params):
|
||||
self.password = params[0]
|
||||
def irc_NICK(self, prefix, params):
|
||||
self.nick = params[0]
|
||||
self.center.msg({'_source' : self.source,
|
||||
'_target' : self.expandUrl(self.nick),
|
||||
'_password' : self.password},
|
||||
'_request_link', '')
|
||||
def irc_PRIVMSG(self, prefix, params):
|
||||
target = params[0]
|
||||
mc = '_message_private'
|
||||
if target[0] == '#':
|
||||
mc = '_message_public'
|
||||
self.center.msg({ '_source' : self.source,
|
||||
'_target' : self.expandUrl(target),
|
||||
'_nick' : self.nick},
|
||||
mc, params[-1])
|
||||
def irc_JOIN(self, prefix, params):
|
||||
chan = params[0]
|
||||
self.center.msg({ '_source' : self.source,
|
||||
'_target' : self.expandUrl(chan),
|
||||
'_nick' : self.nick},
|
||||
'_request_enter', '')
|
||||
def irc_PART(self, prefix, params):
|
||||
chan = params[0]
|
||||
self.center.msg({ '_source' : self.source,
|
||||
'_target' : self.expandUrl(chan),
|
||||
'_nick' : self.nick},
|
||||
'_request_leave', '')
|
||||
def irc_QUIT(self, prefix, params):
|
||||
self.center.msg({ '_source' : self.source,
|
||||
'_target' : self.url },
|
||||
'_request_unlink', '')
|
||||
def irc_unknown(self, prefix, command, params):
|
||||
if command == 'ROSTER':
|
||||
self.center.msg({ '_source' : self.source,
|
||||
'_target' : self.url },
|
||||
'_request_roster', '')
|
||||
elif command == 'FRIEND':
|
||||
self.center.msg({ '_source' : self.source,
|
||||
'_target' : self.expandUrl(params[0]) },
|
||||
'_request_friendship', 'Lass uns Freunde sein')
|
||||
else:
|
||||
print 'unknown irc cmd %s'%command
|
||||
"""pypsyc msg API"""
|
||||
def msgUnknownMethod(self, vars, mc, data):
|
||||
print 'unsupported %s from %s'%(mc, vars['_source'])
|
||||
def msg_notice_link(self, vars, mc, data):
|
||||
self.url = vars['_source']
|
||||
self.sendNumeric(irc.RPL_WELCOME, 'Hello, %s'%self.nick)
|
||||
self.sendNumeric(irc.RPL_YOURHOST, 'Welcome to %s'%self.hostname)
|
||||
self.sendNumeric(irc.RPL_MYINFO, '%s is a pyPSYC daemon IRC interface'%self.hostname)
|
||||
def msg_message_private(self, vars, mc, data):
|
||||
if vars['_target'] is self.source:
|
||||
t = self.nick
|
||||
else: # should not happen
|
||||
raise
|
||||
# TODO: might be appropriate to use self.privmsg()
|
||||
# self.privmsg(vars['_source'], self.nick, None, data)
|
||||
s = self.minimizeUrl(vars['_source'])
|
||||
self.sendMessage(s, 'PRIVMSG', t, data)
|
||||
def msg_message_echo_private(self, vars, mc, data):
|
||||
pass # echo is not common in irc
|
||||
def msg_message_public(self, vars, mc, data):
|
||||
if vars['_source'] != self.url: # skip echo
|
||||
if vars.has_key('_context'):
|
||||
t = '#' + self.minimizeUrl(vars['_context'])
|
||||
else:
|
||||
t = '#' + self.minimizeUrl(vars['_source'])
|
||||
s = self.minimizeUrl(vars['_source'])
|
||||
# TODO: might be appropriate to use self.privmsg()
|
||||
# self.privmsg(vars['_source'], None, t, data)
|
||||
self.sendMessage(s, 'PRIVMSG', t, data)
|
||||
else:
|
||||
pass # echo is not common in IRC
|
||||
def msg_echo_place_enter(self, vars, mc, data):
|
||||
t = '#' + self.minimizeUrl(vars['_source'])
|
||||
self.sendMessage(self.nick, 'JOIN', t)
|
||||
def msg_echo_place_leave(self, vars, mc, data):
|
||||
t = '#' + self.minimizeUrl(vars['_source'])
|
||||
self.sendMessage(self.nick, 'PART', t)
|
||||
def msg_status_place_members(self, vars, mc, data):
|
||||
t = '#' + self.minimizeUrl(vars['_source'])
|
||||
self.names(self.nick, t, map(self.minimizeUrl, vars['_list_members']))
|
||||
def msg_notice_unlink(self, vars, mc, data):
|
||||
if vars['_source'] == self.url:
|
||||
self.url = None
|
||||
self.transport.loseConnection()
|
||||
def msg_notice_place_enter(self, vars, mc, data):
|
||||
s = vars['_source']
|
||||
c = '#' + self.minimizeUrl(vars['_context'])
|
||||
if s == self.url:
|
||||
return # we dont like being joined via notice!
|
||||
self.join(self.url2prefix(self.minimizeUrl(s)), c)
|
||||
def msg_notice_place_leave(self, vars, mc, data):
|
||||
s = vars['_source']
|
||||
c = '#' + self.minimizeUrl(vars['_context'])
|
||||
self.part(self.url2prefix(self.minimizeUrl(s)), c)
|
||||
def msg_notice_roster(self, vars, mc, data):
|
||||
friends = vars['_list_friends']
|
||||
places = vars['_list_places']
|
||||
if friends:
|
||||
self.sNotice('Friends')
|
||||
for friend in vars['_list_friends']:
|
||||
self.sNotice('~ %s'%friend)
|
||||
if places:
|
||||
self.sNotice('Places')
|
||||
for place in places:
|
||||
self.sNotice('@ %s'%place)
|
||||
def msg_request_friendship(self, vars, mc, data):
|
||||
sni = self.minimizeUrl(vars['_source'])
|
||||
self.notice(sni, self.nick,
|
||||
'%s wants to be your friend'%(sni))
|
||||
def msg_notice_friendship_established(self, vars, mc, data):
|
||||
sni = self.minimizeUrl(vars['_source'])
|
||||
self.notice(sni, self.nick,
|
||||
'%s is now your friend'%(sni))
|
||||
|
||||
class IRCDFactory(ServerFactory):
|
||||
center = None
|
||||
def __init__(self, center, location):
|
||||
self.center = center
|
||||
self.location = location
|
||||
def buildProtocol(self, addr):
|
||||
p = IRCD(self.center, self.location)
|
||||
p.factory = self
|
||||
return p
|
||||
|
||||
class MyServerCenter(ServerCenter):
|
||||
def create_user(self, netname):
|
||||
return Person(netname, self)
|
||||
def create_place(self, netname):
|
||||
return Place(netname, self)
|
||||
def create_context(self, netname):
|
||||
return GroupSlave(netname, self)
|
||||
|
||||
|
||||
root = 'psyc://ente' # TODO: this does belong into a config file!
|
||||
application = service.Application('psycserver')
|
||||
|
||||
center = MyServerCenter(root)
|
||||
|
||||
factory = PSYCServerFactory(center, None, root)
|
||||
psycServer = internet.TCPServer(4404, factory)
|
||||
|
||||
ircfactory = IRCDFactory(center, root)
|
||||
ircServer = internet.TCPServer(6667, ircfactory)
|
||||
|
||||
myService = service.IServiceCollection(application)
|
||||
psycServer.setServiceParent(myService)
|
||||
ircServer.setServiceParent(myService)
|
||||
9
fippos-twisted/contrib/rss/README
Normal file
9
fippos-twisted/contrib/rss/README
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
Purpose:
|
||||
this is designed to be a news distributing server only. It fetches RSS feeds
|
||||
|
||||
Running:
|
||||
twistd -noy rss_server.py
|
||||
This code depends on
|
||||
- rss.py from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/277099
|
||||
- feedparser.py from http://diveintomark.org/projects/feed_parser/
|
||||
- twisted python (python is less fun without)
|
||||
131
fippos-twisted/contrib/rss/rss_server.py
Normal file
131
fippos-twisted/contrib/rss/rss_server.py
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
# usage: twistd -noy rss_server.py
|
||||
from twisted.application import service, internet
|
||||
from twisted.internet import reactor
|
||||
from pypsyc.center import ServerCenter
|
||||
from pypsyc.net import PSYCServerFactory
|
||||
from pypsyc.objects.server import Place
|
||||
|
||||
from pypsyc import parseUNL
|
||||
|
||||
try:
|
||||
from rss import FeederFactory
|
||||
except ImportError:
|
||||
print 'error while importing rss.py'
|
||||
print 'make sure you have rss.py from ',
|
||||
print 'from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/277099'
|
||||
print 'and feedparser.py from ',
|
||||
print 'http://diveintomark.org/projects/feed_parser/'
|
||||
|
||||
|
||||
class PlaceFeeder(FeederFactory):
|
||||
def getFeeds(self):
|
||||
return []
|
||||
|
||||
|
||||
class Feedplace(Place):
|
||||
channel = None
|
||||
silent = True
|
||||
def __init__(self, netname, center, feed):
|
||||
Place.__init__(self, netname, center)
|
||||
self.feed = feed
|
||||
self.error = 0 # number of times that we didnt succeed
|
||||
self.fetched = 0 # number of times we fetched sucessfully
|
||||
self.fetched_items = 0 # the average number of new items per fetch
|
||||
|
||||
self.fetch_interval = 15 * 60 # initial feed interval
|
||||
|
||||
self.feeder = PlaceFeeder(False)
|
||||
self.news = []
|
||||
reactor.callLater(5, self.fetchFeed)
|
||||
def fetchFeed(self):
|
||||
d = self.feeder.start([(self.feed, '')])
|
||||
d.addCallback(self.gotFeed)
|
||||
d.addErrback(self.gotError)
|
||||
def gotError(self, error):
|
||||
self.error += 1
|
||||
# TODO: react on feeds that are temp/perm unreachable
|
||||
reactor.callLater(self.fetch_interval, self.fetchFeed)
|
||||
print 'looks as if feed %s is unreachable'%self.feed
|
||||
print error
|
||||
def gotFeed(self, data):
|
||||
self.fetched += 1
|
||||
new = []
|
||||
items = {}
|
||||
if self.channel is None:
|
||||
self.channel = data['channel']
|
||||
self.castmsg({ '_nick' : self.netname,
|
||||
'_topic' : self.showTopic()},
|
||||
'_status_place_topic',
|
||||
'Topic by [_nick]: [_topic]')
|
||||
for item in data['items']:
|
||||
# diff by url
|
||||
href = item['link']
|
||||
new.append(href)
|
||||
items[href] = item
|
||||
|
||||
diff = filter(lambda x: x not in self.news, new)
|
||||
for href in diff:
|
||||
item = items[href]
|
||||
v = {'_news_headline' : item['title_detail']['value'],
|
||||
'_page_news' : href,
|
||||
'_channel_title' : data['channel']['title'] }
|
||||
self.castmsg(v, '_notice_news_headline_rss',
|
||||
'([_channel_title]) [_news_headline]\n[_page_news]')
|
||||
|
||||
self.news = new
|
||||
|
||||
# feeds whose average number of new items is < x
|
||||
# can be polled with less frequency
|
||||
self.fetched_items += len(diff)
|
||||
avg = float(self.fetched_items) / self.fetched
|
||||
print 'avg no of new items per fetch for %s is %f'%(self.feed, avg)
|
||||
if avg < 1.5: # x
|
||||
# lower frequency
|
||||
self.fetch_interval *= avg
|
||||
elif avg > 4.5 and self.fetched > 10: # y
|
||||
# increase frequenzy
|
||||
self.fetch_interval /= 2
|
||||
print 'callLater in %d'%(self.fetch_interval)
|
||||
reactor.callLater(self.fetch_interval, self.fetchFeed)
|
||||
def showMembers(self):
|
||||
return []
|
||||
def showTopic(self):
|
||||
if self.channel is not None:
|
||||
return 'feed \'%s\' available from %s'%(self.channel['title'],
|
||||
self.feed)
|
||||
else:
|
||||
return 'stand by while fetching feed %s'%self.feed
|
||||
def msg_message_public(self, vars, mc, data):
|
||||
pass # they're not for talking
|
||||
|
||||
|
||||
class MyServerCenter(ServerCenter):
|
||||
feeds = {}
|
||||
def create_user(self, netname):
|
||||
return False
|
||||
def create_place(self, netname):
|
||||
u = parseUNL(netname)
|
||||
res = u['resource'][1:]
|
||||
if self.feeds.has_key(res):
|
||||
return Feedplace(netname, center, self.feeds[res])
|
||||
return False
|
||||
def create_context(self, netname):
|
||||
return False
|
||||
def setFeeds(self, feeds):
|
||||
self.feeds = feeds
|
||||
def getFeeds(self):
|
||||
return self.feeds
|
||||
|
||||
|
||||
root = 'psyc://ente'
|
||||
application = service.Application('psyc news distributor')
|
||||
|
||||
center = MyServerCenter(root)
|
||||
factory = PSYCServerFactory(center, None, root)
|
||||
psycServer = internet.TCPServer(4404, factory)
|
||||
|
||||
center.setFeeds({ 'heise' : 'http://www.heise.de/newsticker/heise.rdf' })
|
||||
|
||||
myService = service.IServiceCollection(application)
|
||||
psycServer.setServiceParent(myService)
|
||||
|
||||
28
fippos-twisted/contrib/whitepaper.py
Normal file
28
fippos-twisted/contrib/whitepaper.py
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
"""p2p manager and client as described in the old PSYC whitepaper at
|
||||
http://psyc.pages.de/whitepaper/
|
||||
probably broken currently"""
|
||||
from pypsyc.center import ServerCenter, ClientCenter
|
||||
from pypsyc.objects.PSYCObject import PSYCClient
|
||||
from pypsyc.objects.Advanced import AdvancedManager, AdvancedPlace
|
||||
import sys
|
||||
import asyncore
|
||||
|
||||
location = 'psyc://adamantine.aquarium'
|
||||
|
||||
type = sys.argv[1]
|
||||
if type == 'manager':
|
||||
center = ServerCenter([location + ':4405/', location + ':4406',
|
||||
location + ':4407', location + ':4408'])
|
||||
center2 = ServerCenter([location])
|
||||
AdvancedManager(location + '/@advanced', center2)
|
||||
if type == 'client':
|
||||
center = ClientCenter()
|
||||
me = PSYCClient(location + '/~fippo', center)
|
||||
me.online()
|
||||
AdvancedPlace(location + "/@advanced", center)
|
||||
me.sendmsg({'_target' : location + '/@advanced',
|
||||
'_source' : location + '/~fippo'},
|
||||
'_request_enter', '')
|
||||
|
||||
while center:
|
||||
asyncore.poll(timeout=0.5)
|
||||
10
fippos-twisted/doc/DESIGN
Normal file
10
fippos-twisted/doc/DESIGN
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
Design of the pyPSYC library
|
||||
|
||||
Asynchronus programming
|
||||
|
||||
The role of the center object
|
||||
|
||||
Writing a client
|
||||
- GUI event loop Integration
|
||||
|
||||
Writing a server
|
||||
1
fippos-twisted/doc/FEATURES
Normal file
1
fippos-twisted/doc/FEATURES
Normal file
|
|
@ -0,0 +1 @@
|
|||
onesided zlib compression
|
||||
70
fippos-twisted/pypsyc/PSYC.py
Normal file
70
fippos-twisted/pypsyc/PSYC.py
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
from pypsyc.State import State
|
||||
|
||||
from twisted.protocols.basic import LineReceiver
|
||||
|
||||
|
||||
class PSYCProtocol(LineReceiver):
|
||||
statemachine = None
|
||||
state = 'vars'
|
||||
mc = None
|
||||
text = None
|
||||
delimiter = '\n'
|
||||
initialized = False
|
||||
def connectionMade(self):
|
||||
self.statemachine = State()
|
||||
self.reset()
|
||||
self.transport.write('.\n')
|
||||
def msg(self, vars, mc, text):
|
||||
"""serialize a packet and send to the other side
|
||||
|
||||
@type vars: C{dict}
|
||||
@param vars: Dictionary of variables to be serialized.
|
||||
Variables should be strings or lists, variable names start
|
||||
with an underscore
|
||||
|
||||
@type mc: C{str}
|
||||
@param mc: Methodname of the packet, starts with an underscore
|
||||
|
||||
@type text: C{str}
|
||||
@param text: Data part of the packet"""
|
||||
packet = self.statemachine.serialize(vars) # this has a newline already!
|
||||
packet += mc + '\n'
|
||||
packet += text + '\n'
|
||||
packet += '.\n'
|
||||
self.transport.write(packet.encode('iso-8859-1'))
|
||||
def reset(self):
|
||||
self.statemachine.reset()
|
||||
self.state = 'vars'
|
||||
self.mc = ''
|
||||
self.text = ''
|
||||
def lineReceived(self, line):
|
||||
"""this does not yet handle binary mode and fragments"""
|
||||
line = line.strip()
|
||||
if self.initialized is False:
|
||||
if line != '.':
|
||||
self.msg({}, '_error_syntax_protocol_initialization',
|
||||
'The protocol begins with a dot on a line of by itself')
|
||||
self.transport.loseConnection()
|
||||
return
|
||||
else:
|
||||
self.initialized = True
|
||||
return
|
||||
if line == '.':
|
||||
self.packetReceived()
|
||||
elif self.state is 'vars' and line.startswith('_'):
|
||||
self.statemachine.eat(None)
|
||||
self.mc = line
|
||||
self.state = 'text'
|
||||
elif self.state == 'vars':
|
||||
self.statemachine.eat(line)
|
||||
else:
|
||||
self.text += line + '\n'
|
||||
def packetReceived(self):
|
||||
vars = self.statemachine.copy()
|
||||
peer = self.transport.getPeer()
|
||||
if not vars.has_key('_source'):
|
||||
vars['_source'] = 'psyc://%s:-%d'%(peer.host, peer.port)
|
||||
mc = self.mc[:]
|
||||
data = self.text[:].strip().decode('iso-8859-1')
|
||||
self.reset()
|
||||
self.factory.packetReceived(vars, mc, data, self, peer)
|
||||
156
fippos-twisted/pypsyc/State.py
Normal file
156
fippos-twisted/pypsyc/State.py
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
# TODO: write tests
|
||||
from pypsyc import MMPVARS
|
||||
|
||||
from copy import deepcopy
|
||||
|
||||
class SenderState:
|
||||
def __init__(self):
|
||||
self.laststate = {}
|
||||
self.persistent_out = {}
|
||||
def serializeList(self, varname, value):
|
||||
t = []
|
||||
old = self.persistent_out.get(varname)
|
||||
# that is far to experimental
|
||||
if True: # not old: # no previous list sent
|
||||
self.persistent_out[varname] = value
|
||||
t.append(':%s\t%s\n'%(varname, value[0].replace('\n', '\n\t')))
|
||||
for item in value[1:]:
|
||||
t.append(':\t%s\n'%(item.replace('\n', '\n\t')))
|
||||
|
||||
return '\n'.join(t)
|
||||
if False:
|
||||
augmented = filter(lambda x: x not in old, value)
|
||||
if augmented:
|
||||
packet += '+%s\t%s\n'%(varname, augmented[0].replace('\n',
|
||||
'\n\t'))
|
||||
for item in augmented[1:]:
|
||||
packet += '+\t%s\n'%(item.replace('\n', '\n\t'))
|
||||
diminished = filter(lambda x: x not in value, old)
|
||||
if diminished:
|
||||
packet += '-%s\t%s\n'%(varname, diminished[0].replace('\n',
|
||||
'\n\t'))
|
||||
for item in diminished[1:]:
|
||||
packet += '-\t%s\n'%(item.replace('\n', '\n\t'))
|
||||
self.persistent_out[varname] = value
|
||||
def serialize(self, state):
|
||||
"""
|
||||
serializes a set of variables into a string
|
||||
|
||||
@type state: C{dict}
|
||||
@param state: Dictionary of variables to be serialized
|
||||
"""
|
||||
L = []
|
||||
# beware of the lambdas!
|
||||
L.append(self.varencode(filter(lambda x: x[0] in MMPVARS and x[1],
|
||||
state.items())))
|
||||
if L != []:
|
||||
L.append('\n')
|
||||
L.append(self.varencode(filter(lambda x: x[0] not in MMPVARS and x[1],
|
||||
state.items())))
|
||||
self.laststate = state
|
||||
bytes = '\n'.join(L)
|
||||
return bytes
|
||||
def varencode(self, v):
|
||||
"""
|
||||
encodes a set of variables, setting the state persistent according to
|
||||
some strategy
|
||||
|
||||
@type v: C{dict}
|
||||
@param v: Dictionary of variables to be serialized
|
||||
"""
|
||||
t = []
|
||||
for (varname, value) in v:
|
||||
if self.persistent_out.get(varname) == value:
|
||||
pass
|
||||
elif varname.startswith('_list') or type(value) == type([]):
|
||||
t.append(self.serializeList(varname, value))
|
||||
elif self.laststate.get(varname) == value and varname != '_context':
|
||||
self.persistent_out[varname] = value
|
||||
t.append('=%s\t%s\n'%(varname, value.replace('\n', '\n\t')))
|
||||
else:
|
||||
t.append(':%s\t%s\n'%(varname, value.replace('\n', '\n\t')))
|
||||
return '\n'.join(t)
|
||||
|
||||
|
||||
class ReceiverState:
|
||||
glyph = ''
|
||||
varname = ''
|
||||
listFlag = False
|
||||
value = ''
|
||||
def __init__(self):
|
||||
self.state = {}
|
||||
self.persistent = {}
|
||||
def reset(self):
|
||||
self.state = {}
|
||||
self.glyph = ''
|
||||
self.varname = ''
|
||||
self.listFlag= False
|
||||
self.value = ''
|
||||
def copy(self):
|
||||
# do we actually need those deep copys? TODO
|
||||
t = deepcopy(self.persistent)
|
||||
t.update(deepcopy(self.state))
|
||||
return t
|
||||
def eat(self, line):
|
||||
"""
|
||||
this one is tricky... first it handles the previous line,
|
||||
and then it prepares the current line.
|
||||
This is needed to implement multiline-continuations in lists
|
||||
|
||||
@type line: C{str} or C{None}
|
||||
@param line: line to be parsed, a None signals that variable
|
||||
parsing for current packet is finished
|
||||
"""
|
||||
if line: line = line.decode('iso-8859-1') # we use unicode internally
|
||||
if line and (line[0] == ' ' or line[0] == '\t'): # multiline support
|
||||
self.value += '\n' + line[1:]
|
||||
return
|
||||
|
||||
# glyph handling
|
||||
if self.glyph == ':':
|
||||
if self.listFlag:
|
||||
if not type(self.state[self.varname]) == list:
|
||||
self.state[self.varname] = [self.state[self.varname]]
|
||||
self.state[self.varname].append(self.value)
|
||||
else:
|
||||
if self.varname.startswith('_list'):
|
||||
self.value = [self.value]
|
||||
self.state[self.varname] = self.value
|
||||
elif self.glyph == '=':
|
||||
if self.listFlag:
|
||||
if not type(self.persistent[self.varname]) == list:
|
||||
self.persistent[self.varname] = [self.self.persistent[self.varname]]
|
||||
self.persistent[self.varname].append(self.value)
|
||||
else:
|
||||
self.persistent[self.varname] = self.value
|
||||
elif self.glyph == '+':
|
||||
self.persistent.get(self.varname, []).append(self.value)
|
||||
elif self.glyph == '-':
|
||||
raise NotImplementedError
|
||||
elif self.glyph == '?':
|
||||
raise NotImplementedError
|
||||
|
||||
if not line: # feeding done
|
||||
return
|
||||
|
||||
# here we parse the current line
|
||||
self.glyph = line[0]
|
||||
if line[1] == '\t':
|
||||
# lastvarname-optimization: varname remains the same
|
||||
self.listFlag = True
|
||||
self.value = line[2:]
|
||||
else:
|
||||
self.listFlag = False
|
||||
if line.find('\t') == -1:
|
||||
self.varname = line[1:]
|
||||
self.value = ''
|
||||
else:
|
||||
self.varname, self.value = line[1:].split('\t', 1)
|
||||
self.value = self.value
|
||||
|
||||
|
||||
class State(SenderState, ReceiverState):
|
||||
"""combination of sender and receiver state"""
|
||||
def __init__(self):
|
||||
SenderState.__init__(self)
|
||||
ReceiverState.__init__(self)
|
||||
101
fippos-twisted/pypsyc/__init__.py
Normal file
101
fippos-twisted/pypsyc/__init__.py
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
"""common methods and constants for pyPSYC"""
|
||||
|
||||
# URL parsing functions modelled after psycMUVE parseURL
|
||||
def parseURL(url):
|
||||
u = { 'scheme' : '',
|
||||
'user' : '',
|
||||
'pass' : '',
|
||||
'host' : '',
|
||||
'port' : '4404',
|
||||
'transport' : '',
|
||||
'string' : url,
|
||||
'body' : '',
|
||||
'userAtHost' : '',
|
||||
'hostPort' : '',
|
||||
'root' : '',
|
||||
'circuit' : '',
|
||||
'size' : ''
|
||||
}
|
||||
if url.find(':') == -1: return u
|
||||
u['scheme'], t = url.split(':', 1)
|
||||
if t[0:2] == '//': t = t[2:]
|
||||
u['body'] = t[:]
|
||||
if t.find('/') != -1:
|
||||
t, u['resource'] = t.split('/', 1)
|
||||
else:
|
||||
u['resource'] = ''
|
||||
if u.has_key('resource') and u['resource'].find('#') != -1:
|
||||
u['resource'], u['fragment'] = u['resource'].split('#', 1)
|
||||
u['userAtHost'] = t[:]
|
||||
if t.find('@') != -1:
|
||||
s, t = t.split('@', 1)
|
||||
if s.find(':') != -1:
|
||||
u['user'], u['pass'] = s.split(':', 1)
|
||||
else:
|
||||
u['user'] = s
|
||||
u['hostPort'] = t[:]
|
||||
u['root'] = u['scheme'] + '://' + u['hostPort']
|
||||
if t.find(':') != -1:
|
||||
t, s = t.split(':', 1)
|
||||
# TODO: split s in Port (numeric), Transport
|
||||
if s and s[-1] in ['c', 'd', 'm']:
|
||||
u['transport'] = s[-1]
|
||||
u['port'] = s[:-1] or '4404'
|
||||
else:
|
||||
u['port'] = s or '4404'
|
||||
u['host'] = t[:]
|
||||
# print "parseurl(%s)"%url, u
|
||||
return u
|
||||
|
||||
|
||||
def parseUNL(unl): return parseURL(unl) # alias
|
||||
|
||||
def UNL2Location(unl):
|
||||
# if we did not have the user@host syntax this would
|
||||
# reduce to a simple splitting in front of #
|
||||
u = parseUNL(unl)
|
||||
short = u['scheme'] + '://' + u['host']
|
||||
if u['port'] != '4404':
|
||||
short += ':' + u['port']
|
||||
if u['resource']:
|
||||
return short + '/' + u['resource']
|
||||
return short
|
||||
|
||||
|
||||
def netLocation(unl):
|
||||
u = parseURL(unl)
|
||||
return u['root']
|
||||
|
||||
def parsetext(vars, mc, data, caller=None):
|
||||
pstring = data
|
||||
#print '---'
|
||||
#print type(data)
|
||||
#print '---'
|
||||
try:
|
||||
for (varname, value) in vars.items():
|
||||
if type(value) == list:
|
||||
no_list = u''
|
||||
for x in value:
|
||||
no_list += x + ', '
|
||||
pstring = pstring.replace(u'[' + varname + u']', no_list[:-2])
|
||||
else:
|
||||
pstring = pstring.replace(u'[' + varname + u']', value)
|
||||
except:
|
||||
print 'Error in parsetext() for vars'
|
||||
return pstring
|
||||
|
||||
# debugging helper
|
||||
def dump_packet(banner, vars, mc, data):
|
||||
print banner + ' ',
|
||||
for key in vars.keys():
|
||||
try:
|
||||
print key + '=' + vars[key] + ' ',
|
||||
except:
|
||||
pass
|
||||
print mc,
|
||||
print '[' + parsetext(vars, mc, data) + ']'
|
||||
|
||||
|
||||
# constants
|
||||
GLYPHS = [':', '=', '+', '-', '?', ' ', '\t' ]
|
||||
MMPVARS = ["_source", "_target", "_context"]
|
||||
269
fippos-twisted/pypsyc/center.py
Normal file
269
fippos-twisted/pypsyc/center.py
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
#!/usr/bin/env python
|
||||
from pypsyc import parseUNL, UNL2Location, netLocation
|
||||
|
||||
from twisted.internet import reactor, defer
|
||||
from pypsyc.net import PSYCClientConnector, PSYCActiveConnector
|
||||
|
||||
from pypsyc import dump_packet
|
||||
|
||||
|
||||
class Center:
|
||||
object_handlers = {}
|
||||
remote_connections = {}
|
||||
def msg(self, vars, mc, data):
|
||||
raise NotImplementedError
|
||||
|
||||
def sendmsg(self, vars, mc, data, target = None):
|
||||
raise NotImplementedError
|
||||
|
||||
def register_object(self, netname, object):
|
||||
# multiple handlers are not possible
|
||||
# we only want lower case netnames
|
||||
self.object_handlers[netname.lower()] = object
|
||||
|
||||
def find_object(self, netname):
|
||||
"""find a local object that corresponds to netname"""
|
||||
# try "real/local" object"
|
||||
# we only have lower case netnames
|
||||
return self.object_handlers.get(netname.lower())
|
||||
|
||||
def find_remote(self, netname):
|
||||
"""find a remote location where netname may be located"""
|
||||
address = netLocation(netname)
|
||||
if self.remote_connections.has_key(address):
|
||||
return self.remote_connections[address]
|
||||
return None
|
||||
def register_remote(self, obj, where):
|
||||
self.remote_connections[where] = obj
|
||||
def unregister_remote(self, obj, where):
|
||||
if self.remote_connections.get(where) == obj:
|
||||
self.remote_connections.pop(where)
|
||||
def connect(self, location):
|
||||
raise NotImplementedError
|
||||
def create_user(self, netname):
|
||||
raise NotImplementedError
|
||||
def create_place(self, netname):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class ClientCenter(Center):
|
||||
# connection to the homeserver, default return for find_remote
|
||||
default_connection = None
|
||||
options = {}
|
||||
def get_option(self, option):
|
||||
return self.options.get(option)
|
||||
def connect(self, location):
|
||||
# already connected or connecting?
|
||||
a = self.find_remote(location)
|
||||
if a:
|
||||
return a
|
||||
a = PSYCClientConnector(self, location)
|
||||
return a.get_queue()
|
||||
def find_remote(self, netname):
|
||||
"""clients will send most things via their homeserver
|
||||
p2p connections are an exception to that, but they will be
|
||||
created by user objects"""
|
||||
return Center.find_remote(self, netname) or Center.find_remote(self, self.default_connection)
|
||||
def msg(self, vars, mc, data):
|
||||
if not mc:
|
||||
#print "warning: minor bug in pypsyc: msg() without mc"
|
||||
return
|
||||
if self.get_option('debug'):
|
||||
dump_packet(">>>", vars, mc, data)
|
||||
source = vars.get('_context') or vars.get('_source')
|
||||
if not source: return # ignore empty packets?
|
||||
obj = self.find_object(source)
|
||||
if obj:
|
||||
obj.msg(vars, mc, data)
|
||||
return
|
||||
print 'unhandled packet from %s'%source
|
||||
# do something about it... pop up a window, etc
|
||||
u = parseUNL(source)
|
||||
if u['resource'] and u['resource'].startswith('~'):
|
||||
# create a user object
|
||||
obj = self.create_user(source)
|
||||
obj.msg(vars, mc, data)
|
||||
elif u['resource'] and u['resource'].startswith('@'):
|
||||
# create a place object
|
||||
obj = self.create_place(source)
|
||||
obj.msg(vars, mc, data)
|
||||
else:
|
||||
print 'no handler for %s object'%(source)
|
||||
def sendmsg(self, vars, mc, data):
|
||||
target = vars.get('_target')
|
||||
if not target: return
|
||||
obj = self.find_remote(target)
|
||||
if obj:
|
||||
obj.msg(vars, mc, data)
|
||||
else:
|
||||
raise
|
||||
# this should not happen!
|
||||
|
||||
|
||||
# TODO this does not belong here
|
||||
import sha
|
||||
class Authenticator:
|
||||
def __init__(self, center, uni, password):
|
||||
self.center = center
|
||||
self.uni = uni
|
||||
self.password = password
|
||||
def startLink(self):
|
||||
print 'start link'
|
||||
self.sendmsg({'_target' : uni }, '_request_link', '')
|
||||
def msg_query_password(self, vars, mc, data):
|
||||
if vars['_nonce'] and self.center.get_option('auth_sha'):
|
||||
digest = sha.sha(vars['_nonce'] + self.password).hexdigest()
|
||||
self.center.sendmsg({ '_method' : 'sha1',
|
||||
'_password' : digest },
|
||||
'_set_password', '')
|
||||
elif self.center.get_option('auth_plain'):
|
||||
self.sendmsg({ '_password' : self.password },
|
||||
'_set_password', '')
|
||||
else:
|
||||
print 'no authorization method available!'
|
||||
return
|
||||
|
||||
|
||||
class Client(ClientCenter):
|
||||
default_uni = ''
|
||||
nick = ''
|
||||
online_callback = None
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
|
||||
self.default_uni = config.get('main', 'uni')
|
||||
|
||||
u = parseUNL(self.default_uni)
|
||||
self.nick = u['resource'][1:]
|
||||
self.default_connection = netLocation(self.default_uni)
|
||||
if self.config.has_section('library'):
|
||||
for option in self.config.options('library'):
|
||||
self.options[option] = self.config.getboolean('library', option)
|
||||
def online(self):
|
||||
self.connect(self.default_connection)
|
||||
# experimental API using the cool Deferred's
|
||||
self.online_callback = defer.Deferred()
|
||||
return self.online_callback
|
||||
def gotOnline(self):
|
||||
if self.online_callback:
|
||||
self.online_callback.callback(None)
|
||||
print 'connected to host of default location'
|
||||
def sendmsg(self, vars, mc, data):
|
||||
if vars.get('_nick') == '':
|
||||
vars['_nick'] = self.nick
|
||||
# TODO: I am not sure, it this is correct
|
||||
if vars.get('_source') == '':
|
||||
vars['_source'] = self.default_uni
|
||||
ClientCenter.sendmsg(self, vars, mc, data)
|
||||
|
||||
|
||||
class ServerCenter(Center):
|
||||
unl2uni = {}
|
||||
remote_contexts = {}
|
||||
def __init__(self, location):
|
||||
self.location = location
|
||||
def msg(self, vars, mc, data):
|
||||
if not mc:
|
||||
return # ignore empty packets
|
||||
source = vars['_source']
|
||||
# TODO: auth check 'global' or per-object
|
||||
if vars.has_key('_identification'):
|
||||
print 'Identification of %s is %s'%(source, vars['_identification'])
|
||||
context = vars.get('_context')
|
||||
if context and not self.is_local_object(context):
|
||||
target = context
|
||||
else:
|
||||
target = vars['_target']
|
||||
if self.unl2uni.has_key(source):
|
||||
"""local client who is using us as a proxy"""
|
||||
self.unl2uni[source].sendmsg(vars, mc, data)
|
||||
elif target:
|
||||
obj = self.find_object(target)
|
||||
if obj:
|
||||
return obj.msg(vars, mc, data)
|
||||
# probably object has to be created
|
||||
if self.is_local_object(target):
|
||||
u = parseUNL(target)
|
||||
if (u['resource'] and u['resource'].startswith('@')):
|
||||
obj = self.create_place(target)
|
||||
if obj:
|
||||
return obj.msg(vars, mc, data)
|
||||
else:
|
||||
vars['_target'], vars['_source'] = vars['_source'], vars['_target']
|
||||
self.sendmsg(vars, '_error_unknown_name_place',
|
||||
'No such place: [_source]')
|
||||
elif (u['resource'] and u['resource'].startswith('~')):
|
||||
obj = self.create_user(target)
|
||||
if obj:
|
||||
return obj.msg(vars, mc, data)
|
||||
else:
|
||||
vars['_target'], vars['_source'] = vars['_source'], vars['_target']
|
||||
self.sendmsg(vars, '_error_unknown_name_user',
|
||||
'No such user: [_source]')
|
||||
elif u['resource']:
|
||||
vars['_target'] = source
|
||||
vars['_source'] = target
|
||||
self.sendmsg(vars, '_error_unknown_name',
|
||||
'No such object: [_source]')
|
||||
else: # rootmsg
|
||||
pass
|
||||
elif context is not None and self.remote_contexts.has_key(context):
|
||||
self.remote_contexts[context].castmsg(vars, mc, data)
|
||||
else: # nonlocal target
|
||||
print 'rejected relay %s from %s to %s'%(mc, source, target)
|
||||
return # skip it for now
|
||||
self.sendmsg({ '_target' : source, '_source' : self.location,
|
||||
'_destination' : target },
|
||||
'_error_rejected_relay',
|
||||
'You are not allowed to send messages to [_destination]')
|
||||
else:
|
||||
# no target???
|
||||
print 'no target in packet???'
|
||||
|
||||
def sendmsg(self, vars, mc, data):
|
||||
target = vars['_target']
|
||||
if self.is_local_object(target):
|
||||
self.msg(vars, mc, data)
|
||||
else:
|
||||
u = parseUNL(target)
|
||||
target = (self.find_object(target) or
|
||||
self.find_remote(target) or
|
||||
self.find_remote(target))
|
||||
if not target and u['scheme'] == 'psyc':
|
||||
q = self.connect(u['root'])
|
||||
q.msg(vars, mc, data)
|
||||
elif target:
|
||||
target.msg(vars, mc, data)
|
||||
else:
|
||||
raise 'can not find %s'%(target) # programming error?
|
||||
def register_object(self, netname, object):
|
||||
self.object_handlers[netname.lower()] = object
|
||||
def link_unl(self, unl, uni):
|
||||
self.unl2uni[unl] = uni
|
||||
def unlink_unl(self, unl, uni):
|
||||
if self.unl2uni.has_key(unl) and self.unl2uni[unl] == uni:
|
||||
self.unl2uni.pop(unl)
|
||||
def is_local_object(self, location):
|
||||
return netLocation(location) == netLocation(self.location)
|
||||
def connect(self, location):
|
||||
a = PSYCActiveConnector(self, location, self.location)
|
||||
self.remote_connections[location] = a.get_queue()
|
||||
return a.get_queue()
|
||||
def find_remote(self, netname):
|
||||
address = netLocation(netname)
|
||||
return self.remote_connections.get(address, None)
|
||||
def join_context(self, context, obj):
|
||||
if self.is_local_object(context):
|
||||
print 'skipping join to local context'
|
||||
if not self.remote_contexts.has_key(context):
|
||||
self.remote_contexts[context] = self.create_context(context)
|
||||
self.remote_contexts[context].join(obj)
|
||||
def leave_context(self, context, obj):
|
||||
if self.is_local_object(context):
|
||||
print 'skipping part of local context'
|
||||
if self.remote_contexts.has_key(context):
|
||||
self.remote_contexts[context].leave(obj)
|
||||
# TODO possibly clean_up that context
|
||||
def create_context(self, netname):
|
||||
"""this is how we create context slaves"""
|
||||
raise NotImplementedError
|
||||
186
fippos-twisted/pypsyc/net.py
Normal file
186
fippos-twisted/pypsyc/net.py
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
"""network code for pyPSYC"""
|
||||
|
||||
from pypsyc import GLYPHS
|
||||
|
||||
from pypsyc.PSYC import PSYCProtocol
|
||||
from pypsyc import parseUNL, UNL2Location
|
||||
|
||||
from twisted.names import client
|
||||
from twisted.internet import reactor
|
||||
from twisted.internet.protocol import ClientFactory, ServerFactory
|
||||
|
||||
|
||||
class HostChecker:
|
||||
"""checks validity of _source/_context in a packet
|
||||
this could well implement policies like trusted from localhost"""
|
||||
def __init__(self, center, target, myname):
|
||||
self.target = target
|
||||
self.myname = myname
|
||||
self.center = center
|
||||
def check(self, vars, mc, data, protocol, peer):
|
||||
source = vars.get('_context') or vars.get('_source')
|
||||
if (source and not
|
||||
source.startswith('psyc://%s'%(peer.host))):
|
||||
u = parseUNL(source)
|
||||
d = client.lookupAddress(u['host'])
|
||||
d.addCallback(self.resolved, peer.host, vars, mc, data, protocol)
|
||||
elif source:
|
||||
# numeric address
|
||||
self.handle(vars, mc, data, protocol)
|
||||
else:
|
||||
protocol.msg({ '_source' : self.myname },
|
||||
'_error_syntax_protocol_missing_source',
|
||||
'Your implementation is broken')
|
||||
def handle(self, vars, mc, data, protocol):
|
||||
method = getattr(self, 'recv%s'%mc, None)
|
||||
if method is not None:
|
||||
method(vars, mc, data, protocol)
|
||||
else:
|
||||
u = parseUNL(vars['_source'])
|
||||
self.center.register_remote(protocol, u['root'])
|
||||
self.center.msg(vars, mc, data)
|
||||
def resolved(self, record, shouldbe, vars, mc, data, protocol):
|
||||
v = { '_source' : self.myname,
|
||||
'_target' : vars['_source'] }
|
||||
if not record[0]:
|
||||
print 'name not resolvable'
|
||||
protocol.msg(v, '_error_rejected_relay',
|
||||
'[_source] is not resolvable. Goodbye')
|
||||
protocol.transport.loseConnection()
|
||||
elif record[0][0].type == 5:
|
||||
payload = record[0][0].payload
|
||||
d = client.lookupAddress(payload.name.name)
|
||||
d.addCallback(self.resolved, shouldbe, vars, mc, data, protocol)
|
||||
else:
|
||||
payload = record[0][0].payload
|
||||
if payload.dottedQuad() == shouldbe:
|
||||
self.handle(vars, mc, data, protocol)
|
||||
else:
|
||||
print 'rejected relay'
|
||||
protocol.msg(v, '_error_rejected_relay',
|
||||
'[_source] does not resolve to your ip.')
|
||||
protocol.transport.loseConnection()
|
||||
|
||||
|
||||
class PSYCConnector:
|
||||
hostname = ''
|
||||
port = 4404
|
||||
factory = None
|
||||
factory_type = None
|
||||
real_hostname = ''
|
||||
def __init__(self, center, target, myname = None):
|
||||
# TODO: dont try to resolve IP addresses ;)
|
||||
# must subclass
|
||||
if not self.factory_type: raise NotImplementedError
|
||||
try:
|
||||
self.factory = self.factory_type(center, target, myname)
|
||||
except:
|
||||
print self.factory_type
|
||||
raise
|
||||
u = parseUNL(target)
|
||||
self.host = u['host']
|
||||
d = client.lookupService('_psyc._tcp.' + self.host)
|
||||
d.addCallback(self.srvResolved)
|
||||
def get_queue(self):
|
||||
return self.factory
|
||||
def resolved(self, record):
|
||||
if not record[0]:
|
||||
print 'resolution failed...'
|
||||
else:
|
||||
payload = record[0][0].payload
|
||||
if record[0][0].type == 5: # evil cname
|
||||
d = client.lookupAddress(payload.name.name)
|
||||
d.addCallback(self.resolved)
|
||||
else:
|
||||
reactor.connectTCP(payload.dottedQuad(), self.port, self.factory)
|
||||
return True
|
||||
def srvResolved(self, record):
|
||||
if not record[0]:
|
||||
d = client.lookupAddress(self.host)
|
||||
d.addCallback(self.resolved)
|
||||
else:
|
||||
payload = record[0][0].payload
|
||||
self.port = payload.port
|
||||
d = client.lookupAddress(payload.target.name)
|
||||
d.addCallback(self.resolved)
|
||||
return True
|
||||
|
||||
|
||||
class PSYCClientFactory(ClientFactory):
|
||||
"""a factory for a client which does not have to check
|
||||
for validity of host names"""
|
||||
class PSYCClient(PSYCProtocol):
|
||||
def connectionMade(self):
|
||||
PSYCProtocol.connectionMade(self)
|
||||
self.factory.connectionMade(self)
|
||||
|
||||
protocol = PSYCClient
|
||||
center = None
|
||||
location = None
|
||||
def __init__(self, center, location, myname):
|
||||
self.center = center
|
||||
self.location = location
|
||||
def connectionMade(self, proto):
|
||||
self.center.register_remote(proto, self.location)
|
||||
self.center.gotOnline()
|
||||
def packetReceived(self, vars, mc, data, protocol, peer):
|
||||
self.center.msg(vars, mc, data)
|
||||
|
||||
|
||||
class PSYCClientConnector(PSYCConnector):
|
||||
factory_type = PSYCClientFactory
|
||||
|
||||
|
||||
class PSYCActiveFactory(ClientFactory, HostChecker):
|
||||
"""a factory for a client which does hostname checks
|
||||
maybe this will also become a Q
|
||||
probably we want to override connectionMade?"""
|
||||
protocol = PSYCProtocol
|
||||
queue = []
|
||||
connected = None
|
||||
def packetReceived(self, vars, mc, data, protocol, peer):
|
||||
self.check(vars, mc, data, protocol, peer)
|
||||
def msg(self, vars, mc, data):
|
||||
# watch out, if we dont use dict-vars we may need deepcopy
|
||||
if self.connected is not None:
|
||||
self.connected.msg(vars, mc, data)
|
||||
else:
|
||||
self.queue.append((vars, mc, data))
|
||||
def run_queue(self, target):
|
||||
for (vars, mc, data) in self.queue:
|
||||
target.msg(vars, mc, data)
|
||||
self.queue = []
|
||||
self.connected = target
|
||||
def recv_notice_circuit_established(self, vars, mc, data, protocol):
|
||||
print 'got _notice_circuit_established'
|
||||
protocol.msg({'_source' : self.myname,
|
||||
'_target' : self.target or vars['_source'] },
|
||||
'_notice_circuit_established',
|
||||
'hi there')
|
||||
# TODO: what do we register here? source oder our definition of
|
||||
# what the source should be (self.target)
|
||||
self.center.register_remote(protocol, self.target)
|
||||
self.run_queue(protocol)
|
||||
|
||||
|
||||
class PSYCActiveConnector(PSYCConnector):
|
||||
factory_type = PSYCActiveFactory
|
||||
|
||||
|
||||
class PSYCServerFactory(ServerFactory, HostChecker):
|
||||
"""funny question... do we deal all the stuff about
|
||||
modules, compression, tls etc here?"""
|
||||
class PSYCServerProtocol(PSYCProtocol):
|
||||
def connectionMade(self):
|
||||
peer = self.transport.getPeer()
|
||||
PSYCProtocol.connectionMade(self)
|
||||
self.msg({ '_source' : self.factory.myname,
|
||||
'_target' : 'psyc://%s:-%d'%(peer.host, peer.port)},
|
||||
'_notice_circuit_established',
|
||||
'Connection to [_source] established')
|
||||
|
||||
protocol = PSYCServerProtocol
|
||||
def packetReceived(self, vars, mc, data, protocol, peer):
|
||||
self.check(vars, mc, data, protocol, peer)
|
||||
def recv_notice_circuit_established(self, vars, mc, data, protocol):
|
||||
self.center.register_remote(protocol, vars['_source'])
|
||||
49
fippos-twisted/pypsyc/objects/__init__.py
Normal file
49
fippos-twisted/pypsyc/objects/__init__.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
class PSYCReceiver:
|
||||
def msg(self, vars, mc, data):
|
||||
'called when a message is received'
|
||||
# method inheritance
|
||||
if mc.count('_') > 10:
|
||||
# considered abusive
|
||||
return
|
||||
|
||||
l = len(mc)
|
||||
while l > 0:
|
||||
method = getattr(self, 'msg%s'%(mc[:l]), None)
|
||||
if method is not None:
|
||||
# could this method return the next methodname
|
||||
# to be called? freaky!
|
||||
# actually, this a much more flexible fallthrough
|
||||
# than switch/case provides.
|
||||
# yet it is more expensive to evaluate
|
||||
method(vars, mc, data)
|
||||
break
|
||||
l = mc.rfind('_', 0, l)
|
||||
else:
|
||||
self.msgUnknownMethod(vars, mc, data)
|
||||
def msgUnknownMethod(self, vars, mc, data):
|
||||
print 'unknown %s'%mc
|
||||
|
||||
|
||||
class PSYCObject(PSYCReceiver):
|
||||
"""generic PSYC object"""
|
||||
def __init__(self, netname, center):
|
||||
self.center = center
|
||||
self.netname = netname.lower()
|
||||
self.center.register_object(netname, self)
|
||||
def url(self):
|
||||
return self.netname
|
||||
def str(self):
|
||||
return self.netname
|
||||
def sendmsg(self, vars, mc, data):
|
||||
'called to send a message'
|
||||
l = len(mc)
|
||||
while l > 0:
|
||||
method = getattr(self, 'sendmsg%s'%(mc[:l]), None)
|
||||
if method is not None:
|
||||
method(vars, mc, data)
|
||||
break
|
||||
l = mc.rfind('_', 0, l)
|
||||
else:
|
||||
self.center.sendmsg(vars, mc, data)
|
||||
def castmsg(self, vars, mc, data):
|
||||
'called to send a message to a group'
|
||||
14
fippos-twisted/pypsyc/objects/client.py
Normal file
14
fippos-twisted/pypsyc/objects/client.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
"""Client API for PSYC objects"""
|
||||
|
||||
class IPSYCClientObject:
|
||||
def msg(self, vars, mc, data):
|
||||
"""receive a message"""
|
||||
def sendmsg(self, vars, mc, data):
|
||||
"""send a message to a single person or a group manager
|
||||
use this make requests to the other side that should not be
|
||||
distributed in an unchangend fashion"""
|
||||
def castmsg(self, vars, mc, data):
|
||||
"""send a message that is destined to be delivered to a group
|
||||
Note that you should use this for all communication with the
|
||||
group, as it enables transparent distribution for both centralistic
|
||||
and peer2peer scenarios"""
|
||||
319
fippos-twisted/pypsyc/objects/server.py
Normal file
319
fippos-twisted/pypsyc/objects/server.py
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
from pypsyc.objects import PSYCObject
|
||||
from pypsyc import parseUNL
|
||||
|
||||
from twisted.internet import defer
|
||||
|
||||
class GroupMaster(PSYCObject):
|
||||
"""
|
||||
@type members: C{dict}
|
||||
@ivar members: list of members of the group
|
||||
"""
|
||||
def __init__(self, netname, center):
|
||||
PSYCObject.__init__(self, netname, center)
|
||||
self.members = {}
|
||||
self.flat = {}
|
||||
def sizeof(self):
|
||||
return len(self.flat)
|
||||
def remove(self, whom, origin = None):
|
||||
self.flat.pop(whom, None)
|
||||
if origin is not None:
|
||||
self.members.get(origin, {}).pop(whom, None)
|
||||
if not self.members.get(origin, None):
|
||||
self.members.pop(origin, None)
|
||||
else:
|
||||
self.members.pop(whom, None)
|
||||
def insert(self, whom, origin = None, data = None):
|
||||
if not data: data = True
|
||||
self.flat[whom] = data
|
||||
if origin and not self.netname.startswith(origin):
|
||||
if not self.members.get(origin):
|
||||
self.members[origin] = {}
|
||||
self.members[origin][whom] = True
|
||||
else:
|
||||
self.members[whom] = True
|
||||
def getData(self, whose):
|
||||
return self.flat[whose]
|
||||
def setData(self, whose, data):
|
||||
self.flat[whose] = data
|
||||
def member(self, who):
|
||||
return who in self.flat
|
||||
def castmsg(self, vars, mc, data):
|
||||
vars['_nick_place'] = 'muh' # IMHO _nick_place is superflous
|
||||
vars['_context'] = self.netname
|
||||
for (route, list) in self.members.items():
|
||||
# TODO: deepcopy needed?
|
||||
vars['_target'] = route
|
||||
self.center.sendmsg(vars, mc, data)
|
||||
|
||||
|
||||
class GroupSlave:
|
||||
def __init__(self, netname, center):
|
||||
self.center = center
|
||||
self.context = netname
|
||||
self.members = {}
|
||||
def join(self, obj):
|
||||
url = obj.url()
|
||||
self.members[url] = obj
|
||||
# TODO: install a watcher on memberobj?
|
||||
def leave(self, obj):
|
||||
url = obj.url()
|
||||
if url in self.members:
|
||||
self.members.pop(url)
|
||||
def castmsg(self, vars, mc, data):
|
||||
for (target, obj) in self.members.items():
|
||||
vars['_target'] = target
|
||||
obj.msg(vars, mc, data)
|
||||
|
||||
|
||||
class Place(GroupMaster):
|
||||
silent = False
|
||||
def __init__(self, netname, center):
|
||||
self.idcallbacks = {}
|
||||
self.identifications = {}
|
||||
self.reverseidentifications = {}
|
||||
GroupMaster.__init__(self, netname, center)
|
||||
def sendmsg(self, vars, mc, data):
|
||||
# things like setting vars['_nick_place']
|
||||
vars['_source'] = self.netname
|
||||
t = self.reverseidentifications.get(vars['_target'])
|
||||
if t:
|
||||
print 'setting target from %s to %s'%(vars['_target'], t)
|
||||
vars['_target'] = t
|
||||
GroupMaster.sendmsg(self, vars, mc, data)
|
||||
def showMembers(self):
|
||||
return self.flat.keys()
|
||||
def showTopic(self):
|
||||
return ''
|
||||
def msg(self, vars, mc, data):
|
||||
if '_context' in vars:
|
||||
print '%s got %s with context %s from %s, bad' % (self.netname, mc,
|
||||
vars['_context'],
|
||||
vars['_source'])
|
||||
return
|
||||
ident = vars.get('_identification')
|
||||
source = vars['_source']
|
||||
if ident and self.identifications.get(source) == ident:
|
||||
print 'ident of %s is %s'%(source,
|
||||
self.identifications[vars['_source']])
|
||||
vars['_source'] = ident
|
||||
vars.pop('_identification')
|
||||
GroupMaster.msg(self, vars, mc, data)
|
||||
def msg_error_invalid_authentication(self, vars, mc, data):
|
||||
print 'invalid auth'
|
||||
d = self.idcallbacks.pop((vars['_location'], vars['_source']), None)
|
||||
if d:
|
||||
d.errback(1)
|
||||
def msg_notice_authentication(self, vars, mc, data):
|
||||
print 'valid auth'
|
||||
d = self.idcallbacks.pop((vars['_location'], vars['_source']), None)
|
||||
if d:
|
||||
self.identifications[vars['_location']] = vars['_source']
|
||||
self.reverseidentifications[vars['_source']] = vars['_location']
|
||||
d.callback(1)
|
||||
def helper(self, res, vars, mc, data):
|
||||
self.msg(vars, mc, data)
|
||||
def msg_request_enter(self, vars, mc, data):
|
||||
source = vars['_source']
|
||||
if '_identification' in vars:
|
||||
ident = vars.get('_identification')
|
||||
print 'looking up identification %s'%(ident,)
|
||||
self.sendmsg({ '_target' : ident,
|
||||
'_location' : source },
|
||||
'_request_authenticate', 'Is that really you?')
|
||||
d = defer.Deferred()
|
||||
d.addCallback(self.helper, vars, mc, data)
|
||||
d.addErrback(self.helper, vars, mc, data)
|
||||
self.idcallbacks[(source, ident)] = d
|
||||
return
|
||||
# TODO: while it is not the final plan to make this via parseUNL
|
||||
# it is acceptable for now
|
||||
origin = parseUNL(source)['root']
|
||||
v = { '_target' : source,
|
||||
'_source' : self.netname }
|
||||
if '_tag' in vars:
|
||||
v['_tag'] = vars['_tag']
|
||||
else:
|
||||
pass
|
||||
if '_nick' in vars:
|
||||
v['_nick'] = vars['_nick']
|
||||
else:
|
||||
pass
|
||||
if self.silent is True:
|
||||
v['_control'] = '_silent'
|
||||
self.sendmsg(v, '_echo_place_enter',
|
||||
'[_nick] enters')
|
||||
v.pop('_control', None)
|
||||
v.pop('_tag', None)
|
||||
|
||||
v['_list_members'] = self.showMembers() + [ source ]
|
||||
self.sendmsg(v, '_status_place_members', '...')
|
||||
|
||||
v.pop('_list_members')
|
||||
if not self.member(source):
|
||||
v['_source'] = source
|
||||
self.castmsg(v, '_notice_place_enter', '[_nick] enters')
|
||||
self.insert(vars['_source'], origin)
|
||||
def msg_request_leave(self, vars, mc, data):
|
||||
source = vars['_source']
|
||||
# TODO again
|
||||
origin = parseUNL(source)['root']
|
||||
v = { '_source' : source }
|
||||
if self.member(source):
|
||||
self.castmsg({ '_source' : source }, '_notice_place_leave',
|
||||
'[_nick] leaves')
|
||||
self.remove(source)
|
||||
else: # not a member for whatever reason
|
||||
self.sendmsg({ '_target' : source}, '_echo_place_leave',
|
||||
'You are not even a member')
|
||||
pass
|
||||
if self.sizeof() == 0:
|
||||
# empty
|
||||
pass
|
||||
def msg_message_public(self, vars, mc, data):
|
||||
if self.silent is False and self.member(vars['_source']):
|
||||
self.castmsg(vars, mc, data)
|
||||
|
||||
|
||||
class Person(GroupMaster):
|
||||
def __init__(self, netname, center):
|
||||
GroupMaster.__init__(self, netname, center)
|
||||
self.location = None
|
||||
self.forward = False
|
||||
self.places = {}
|
||||
self.tags = {}
|
||||
def disconnected(self, prot, loc):
|
||||
"""client has closed connection"""
|
||||
self.location = None
|
||||
print 'should leave', places.keys()
|
||||
self.center.disconnected(prot, loc)
|
||||
print self.center.remote_connections
|
||||
def msg(self, vars, mc, data):
|
||||
if '_context' in vars and not vars['_context'] in self.places:
|
||||
# context faking, should be impossible as of now
|
||||
print 'catching fake context!'
|
||||
return
|
||||
# dont forward messages from our location to it...
|
||||
self.forward = vars['_source'] is not self.location
|
||||
GroupMaster.msg(self, vars, mc, data)
|
||||
if self.forward is True and self.location is not None:
|
||||
vars['_target'] = self.location
|
||||
self.sendmsg(vars, mc, data)
|
||||
self.forward = False
|
||||
# user is offline
|
||||
def sendmsg(self, vars, mc, data):
|
||||
# TODO: things like tag-creation for joins belong here
|
||||
# as well as setting vars['_nick']
|
||||
if not '_source' in vars or vars['_source'] == self.location:
|
||||
vars['_source'] = self.netname
|
||||
if vars['_target'] == self.location:
|
||||
self.center.sendmsg(vars, mc, data)
|
||||
else:
|
||||
GroupMaster.sendmsg(self, vars, mc, data)
|
||||
def sendmsg_request_enter(self, vars, mc, data):
|
||||
import random
|
||||
t = str(random.randint(0, 10000))
|
||||
self.tags[vars['_target']] = t
|
||||
vars['_tag'] = t
|
||||
# tobi says, tags become invalid after a certain amount of time
|
||||
self.center.sendmsg(vars, mc, data)
|
||||
def sendmsg_request_leave(self, vars, mc, data):
|
||||
# prepare to leave context after a reasonable amount of time
|
||||
self.center.sendmsg(vars, mc, data)
|
||||
def msgUnknownMethod(self, vars, mc, data):
|
||||
if not self.location:
|
||||
print 'person:unknown %s for offline user'%mc
|
||||
def msg_request_link(self, vars, mc, data):
|
||||
if vars['_password']:
|
||||
self.msg_set_password(vars, mc, data)
|
||||
else:
|
||||
self.sendmsg({ '_target' : vars['_source'] },
|
||||
'_query_password', 'Is that really you?')
|
||||
|
||||
self.forward = False
|
||||
def msg_set_password(self, vars, mc, data):
|
||||
"""note that we have NO authorization"""
|
||||
# TODO: here we have to use origin
|
||||
if self.location is not None:
|
||||
self.sendmsg({ '_target' : self.location},
|
||||
'_notice_unlink',
|
||||
'You have just lost the magic feeling.')
|
||||
self.location = vars['_source']
|
||||
# TODO: we may need to deregister this thing...
|
||||
self.center.link_unl(self.location, self)
|
||||
self.sendmsg({ '_target' : self.location},
|
||||
'_notice_link', 'You are now connected')
|
||||
self.forward = False
|
||||
def msg_request_unlink(self, vars, mc, data):
|
||||
if vars['_source'] == self.netname: # heh, this one is nifty!
|
||||
self.sendmsg({'_source' : self.netname,
|
||||
'_target' : self.location },
|
||||
'_notice_unlink',
|
||||
'You have just lost the magic feeling.')
|
||||
self.location = None
|
||||
self.center.unlink_unl(self.location, self.netname)
|
||||
for place in self.places.copy():
|
||||
self.sendmsg({'_source' : self.netname,
|
||||
'_target' : place },
|
||||
'_request_leave_logout',
|
||||
'l8er')
|
||||
self.forward = False
|
||||
def msg_echo_place_enter(self, vars, mc, data):
|
||||
source = vars['_source']
|
||||
tag = vars.get('_tag')
|
||||
if not source in self.tags:
|
||||
self.forward = False
|
||||
elif self.tags[source] != tag:
|
||||
self.tags.pop(source) # wrong tag, you get invalid
|
||||
self.forward = False
|
||||
else:
|
||||
self.places[vars['_source']] = 1
|
||||
self.center.join_context(source, self)
|
||||
def msg_notice_place_leave(self, vars, mc, data):
|
||||
if vars['_source'] == self.netname:
|
||||
mc = '_echo' + mc[7:]
|
||||
vars['_source'] = vars['_context']
|
||||
self.msg(vars, mc, data)
|
||||
def msg_echo_place_leave(self, vars, mc, data):
|
||||
place = vars.get('_context') or vars['_source']
|
||||
if place in self.places:
|
||||
self.places.pop(place)
|
||||
self.forward = True
|
||||
def msg_request_roster(self, vars, mc, data):
|
||||
print 'Roster: %s'%(self.flat)
|
||||
self.sendmsg({'_source' : self.netname,
|
||||
'_target' : self.location,
|
||||
'_list_places' : self.places,
|
||||
'_list_friends' : self.flat.keys()},
|
||||
'_notice_roster',
|
||||
'Your friends are [_friends], you have entered in [_list_places].')
|
||||
self.forward = False
|
||||
def msg_request_friendship(self, vars, mc, data):
|
||||
"""
|
||||
Friendship states:
|
||||
known -> 0
|
||||
asked from -> 1
|
||||
offered to -> 2
|
||||
dual accepted -> 3
|
||||
"""
|
||||
source = vars['_source']
|
||||
if self.member(source) and self.getData(source) == 2:
|
||||
self.setData(source, 3)
|
||||
mc = '_notice_friendship_establshed'
|
||||
# TODO
|
||||
else:
|
||||
self.insert(source, None, 1)
|
||||
self.forward = True
|
||||
def sendmsg_request_friendship(self, vars, mc, data):
|
||||
target = vars['_target']
|
||||
if self.member(target) and self.getData(target) == 1:
|
||||
self.setData(target, 3)
|
||||
mc = '_notice_friendship_established'
|
||||
# TODO: echo_notice_friendship_established
|
||||
else:
|
||||
self.insert(target, None, 2)
|
||||
self.center.sendmsg(vars, mc, data)
|
||||
self.forward = True
|
||||
def msg_notice_friendship_established(self, vars, mc, data):
|
||||
source = vars['_source']
|
||||
if self.member(source) and self.getData(source) == 2:
|
||||
self.setData(source, 3)
|
||||
54
fippos-twisted/test.py
Normal file
54
fippos-twisted/test.py
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
#!/usr/bin/env python
|
||||
from pypsyc import parseUNL, UNL2Location, netLocation
|
||||
|
||||
from pypsyc.objects.PSYCObject import PSYCQueueObject, PSYCObject, PSYCUNI, PSYCPlace, PSYCClient, ClientUser, ClientPlace, AdvancedManager, AdvancedPlace
|
||||
|
||||
from pypsyc.net import PSYCUDPSender
|
||||
|
||||
from pypsyc.center import ServerCenter, ClientCenter
|
||||
|
||||
import asyncore
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
type = sys.argv[1]
|
||||
location = 'psyc://adamantine.aquarium'
|
||||
center = None
|
||||
if type == "server":
|
||||
# tcp only server
|
||||
center = ServerCenter([location + ':c'])
|
||||
PSYCPlace(location + '/@place', center)
|
||||
PSYCUNI(location + '/~fippo', center)
|
||||
|
||||
if type == "server2":
|
||||
# tcp and udp server on non-standard port
|
||||
center = ServerCenter([location + ':4405'])
|
||||
center.connect(location)
|
||||
if type == "udpserver":
|
||||
center = ServerCenter([location + ':4405d'])
|
||||
PSYCUNI(location + ':4405/~fool', center)
|
||||
if type == "udpclient":
|
||||
# this should better be done via a Center which can parse
|
||||
# URLs and them handle according to their transport
|
||||
# but for a quick udp sender this is okay...
|
||||
q = PSYCUDPSender(location + ':4405d')
|
||||
q.msg({'_target' : 'psyc://adamantine.aquarium:d/~fippo'},
|
||||
{'_nick' : 'udpclient'},
|
||||
'_message_private',
|
||||
'hallo udp welt')
|
||||
if type == "client":
|
||||
center = ClientCenter()
|
||||
PSYCObject('psyc://adamantine.aquarium', center)
|
||||
# maybe add config information here?
|
||||
# and let this thing connect as well?
|
||||
me = PSYCClient('psyc://adamantine.aquarium/~fippo', center)
|
||||
me.online()
|
||||
# but thats the fast way to do it
|
||||
me.sendmsg({'_target' : 'psyc://adamantine.aquarium/~fippo'},
|
||||
{'_password' : 'xfippox'},
|
||||
'_request_link',
|
||||
'')
|
||||
|
||||
|
||||
while center:
|
||||
asyncore.poll(timeout=0.5)
|
||||
Loading…
Add table
Add a link
Reference in a new issue