pypsyc/mjacob2/pypsyc/client/model.py

350 lines
11 KiB
Python

"""
pypsyc.client.model
~~~~~~~~~~~~~~~~~~~
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
from collections import namedtuple, defaultdict
from datetime import datetime
from json import dump, load
from twisted.internet import reactor
from twisted.internet.protocol import ClientFactory
from pypsyc.core.mmp import Circuit, Uni
from pypsyc.core.psyc import PSYCObject
from pypsyc.client.observable import ObsObj, ObsAttr, ObsList, ObsDict
from pypsyc.protocol import (UnknownTargetError, DeliveryFailedError,
AuthenticationError, LinkingClient as LinkingProtocol,
RoutingErrorClient as RoutingErrorProtocol,
Messaging as MessagingProtocol,
ConferencingClient as ConferencingProtocol,
FriendshipClient as FriendshipProtocol,
ClientInterfaceClient as ClientInterfaceProtocol)
from pypsyc.util import (schedule, Event, resolve_hostname, connect,
key_intersection)
class Message(object):
def __init__(self, source, message):
self.source = source
self.message = message
self.time = datetime.now()
class Conversation(object):
def __init__(self, messaging, uni):
self.messaging = messaging
self.uni = uni
self.messages = ObsList()
self.unknown_target_evt = Event()
self.delivery_failed_evt = Event()
def send_message(self, message):
self.messaging.protocol.send_private_message(
self.uni, message, relay=self.messaging.account.uni)
self.messages.append(Message(self.messaging.account.uni, message))
Member = namedtuple('Member', ('uni', 'nick'))
class Conference(object):
def __init__(self, conferencing, uni):
self.conferencing = conferencing
self.uni = uni
self.members = ObsList()
self.messages = ObsList()
self.unknown_target_evt = Event()
self.delivery_failed_evt = Event()
def send_message(self, message):
self.conferencing.protocol.send_public_message(self.uni, message)
class Messaging(object):
def __init__(self, account):
self.account = account
self.protocol = MessagingProtocol(self)
def private_message(self, source, message):
conversation = self.account.get_conversation(source)
conversation.messages.append(Message(source, message))
def connected(self, circuit):
self.psyc = circuit.psyc
self.psyc.add_handler(self.protocol)
def disconnected(self):
pass
class Conferencing(object):
def __init__(self, account):
self.account = account
self.protocol = ConferencingProtocol(self)
def member_entered(self, place, uni, nick):
conference = self.account.get_conference(place)
conference.members.append(Member(uni, nick))
def member_left(self, place, uni):
conference = self.account.get_conference(place)
for i in conference.members:
if i.uni == uni:
conference.members.remove(i)
def public_message(self, place, source, message):
conference = self.account.get_conference(place)
conference.messages.append(Message(source, message))
def connected(self, circuit):
self.psyc = circuit.psyc
self.psyc.add_handler(self.protocol)
circuit.add_pvar_handler(self.protocol)
def disconnected(self):
pass
Presence = namedtuple('Presence', ('availability',))
class Friend(object):
def __init__(self, account, uni):
self.account = account
self.uni = uni
self.presence = Presence(0)
class FriendList(object):
def __init__(self, account):
self.account = account
self.protocol = FriendshipProtocol(self)
def friendship(self, uni, state):
friend = self.account.friends.get(uni) or Friend(self.account, uni)
friend.state = state
if uni in self.account.friends:
self.account.friends.updated_item(uni)
else:
self.account.friends[uni] = friend
def friendship_removed(self, uni):
del self.account.friends[uni]
def presence(self, context, value):
self.account.friends[context].presence = Presence(value)
self.account.friends.updated_item(context)
def connected(self, circuit):
self.psyc = circuit.psyc
self.psyc.add_handler(self.protocol)
circuit.add_pvar_handler(self.protocol)
def disconnected(self):
self.account.friends.clear()
class Account(ObsObj):
_active = False
circuit = ObsAttr('circuit')
def __init__(self, client, server, person, password, save_password,
active):
ObsObj.__init__(self)
self.no_password_evt = Event()
self.connection_error_evt = Event()
self.no_such_user_evt = Event()
self.auth_error_evt = Event()
self.client = client
self.server = server
self.person = person
self.resource = "*Resource"
self.password = password
self.save_password = save_password
schedule(setattr, self, 'active', active)
self.conversations = ObsDict()
self.conferences = ObsDict()
self.conferences.delitem_evt += self._del_conference
self.friends = ObsDict()
self.messaging = Messaging(self)
self.conferencing = Conferencing(self)
self.friend_list = FriendList(self)
@property
def uni(self):
return Uni.from_parts([self.server, '~' + self.person])
def get_conversation(self, uni):
if not uni in self.conversations:
self.conversations[uni] = Conversation(self.messaging, uni)
return self.conversations[uni]
def get_conference(self, uni, subscribe=False):
if not uni in self.conferences:
self.conferences[uni] = Conference(self.conferencing, uni)
if subscribe:
schedule(self._subscribe, uni)
return self.conferences[uni]
def _subscribe(self, uni):
try:
self.client_interface.subscribe(uni)
except UnknownTargetError:
self.conferences[uni].unknown_target_evt()
except DeliveryFailedError as e:
self.conferences[uni].delivery_failed_evt(e.args[0])
def _del_conference(self, uni, old):
schedule(self.client_interface.unsubscribe, uni)
def add_friend(self, uni):
self.client_interface.add_friend(uni)
def remove_friend(self, uni):
self.client_interface.remove_friend(uni)
def unknown_target_error(self, uni):
conversation = self.conversations.get(uni) or self.conferences.get(uni)
if conversation:
conversation.unknown_target_evt()
def delivery_failed_error(self, uni, message):
conversation = self.conversations.get(uni) or self.conferences.get(uni)
if conversation:
conversation.delivery_failed_evt(message)
@property
def active(self):
return self._active
@active.setter
def active(self, active):
if active != self._active:
self._active = active
if active:
schedule(self._activate)
elif hasattr(self, 'circuit'):
self._deactivate()
def _activate(self):
if not self.save_password and not self.password:
self.active = False
self.no_password_evt()
return
try:
ip, port = resolve_hostname(self.server)
circuit = self.circuit = connect(ip, port, self.client)
except Exception as e:
return self._error(self.connection_error_evt, e)
try:
linking_protocol = LinkingProtocol(circuit)
linking_protocol.link(self.uni, self.resource, self.password)
self.psyc = circuit.psyc
self.psyc.uni = self.uni.chain(self.resource)
except UnknownTargetError as e:
return self._error(self.no_such_user_evt, e)
except AuthenticationError as e:
return self._error(self.auth_error_evt, e)
circuit.psyc.add_handler(RoutingErrorProtocol(self))
self.client_interface = ClientInterfaceProtocol(self)
self.messaging.connected(circuit)
self.conferencing.connected(circuit)
self.friend_list.connected(circuit)
def _deactivate(self):
self.circuit.transport.loseConnection()
del self.circuit
self.messaging.disconnected()
self.conferencing.disconnected()
self.friend_list.disconnected()
def _error(self, event, error):
self.active = False
self.client.accounts.updated_item(self)
event(error)
def connection_error(self, error):
self._error(self.connection_error_evt, error)
class ClientCircuit(Circuit):
def __init__(self):
self.dump_evt = Event()
Circuit.__init__(self)
self.psyc = PSYCObject(self.send)
self.pvar_handlers = defaultdict(dict)
def connectionMade(self):
if hasattr(self.transport, 'write'):
self.orig_write = self.transport.write
self.transport.write = self.dump_write
Circuit.connectionMade(self)
def dump_write(self, data):
for line in data.strip('\n').split('\n'):
self.dump_evt('o', line)
self.orig_write(data)
def lineReceived(self, line):
self.dump_evt('i', line)
return Circuit.lineReceived(self, line)
def packet_received(self, header, content):
if '_source_relay' in header:
assert header.source.is_ancestor_of(self.psyc.uni)
header.source = Uni(header['_source_relay'])
packet = self.psyc.handle_packet(header, content)
for modifier in key_intersection(self.pvar_handlers, packet.modifiers):
handlers = self.pvar_handlers[modifier]
vars = packet.modifiers[modifier]
for var in key_intersection(handlers, vars):
handlers[var](packet.header.context, vars)
def add_pvar_handler(self, handler):
for i in dir(handler):
if i.startswith('state_'):
_, operation, var = i.split('_', 2)
modifier = {'set': '=', 'add': '+', 'remove': '-'}[operation]
self.pvar_handlers[modifier]['_' + var] = getattr(handler, i)
class Client(ClientFactory, object):
protocol = ClientCircuit
def __init__(self, accounts_file=None):
self.accounts = ObsList()
self.accounts_file = accounts_file
def load_accounts(self):
with open(self.accounts_file, 'r') as f:
self.accounts.extend(Account(self, d['server'].encode('utf-8'),
d['person'].encode('utf-8'),
d['password'].encode('utf-8'),
d['save_password'], d['active'])
for d in load(f))
def save_accounts(self):
with open(self.accounts_file, 'w') as f:
dump(self.accounts, f, indent=4, default=lambda a:
{'server': a.server, 'person': a.person,
'password': a.password if a.save_password else '',
'save_password': a.save_password, 'active': a.active})
def connection_lost(self, circuit, error):
for account in self.accounts:
if getattr(account, 'circuit', None) is circuit:
account.connection_error(Exception(str(error)))
def quit(self):
for account in self.accounts:
account.active = False
reactor.stop()