""" 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()