mirror of git://git.psyced.org/git/pypsyc
added new pypsyc as 'mjacob2'
This commit is contained in:
parent
0abc7a0888
commit
9f7c4147c7
|
@ -0,0 +1,21 @@
|
|||
The MIT License
|
||||
|
||||
Copyright (c) 2010 Manuel Jacob
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,42 @@
|
|||
#!/usr/bin/env python
|
||||
"""
|
||||
:copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
import logging
|
||||
logging.basicConfig()
|
||||
from optparse import OptionParser
|
||||
from os import path, mkdir
|
||||
|
||||
|
||||
def main():
|
||||
parser = OptionParser()
|
||||
parser.add_option('-c', '--config', dest='config_dir',
|
||||
default=path.expanduser(path.join('~', '.pypsyc')))
|
||||
options, args = parser.parse_args()
|
||||
|
||||
if not path.exists(options.config_dir):
|
||||
mkdir(options.config_dir)
|
||||
|
||||
accounts_file = path.join(options.config_dir, 'accounts')
|
||||
if not path.exists(accounts_file):
|
||||
with open(accounts_file, 'w') as f:
|
||||
f.write('[]')
|
||||
|
||||
from twisted.internet import gtk2reactor
|
||||
gtk2reactor.install()
|
||||
|
||||
from pypsyc.client.controller import MainController
|
||||
from pypsyc.client.model import Client
|
||||
from pypsyc.client.view import MainView
|
||||
|
||||
model = Client(accounts_file=accounts_file)
|
||||
view = MainView()
|
||||
MainController(model, view)
|
||||
model.load_accounts()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
from twisted.internet import reactor
|
||||
reactor.run()
|
|
@ -0,0 +1,45 @@
|
|||
#!/usr/bin/env python
|
||||
"""
|
||||
:copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
import logging
|
||||
logging.basicConfig()
|
||||
from optparse import OptionParser
|
||||
from os import path, mkdir
|
||||
from socket import gethostname
|
||||
|
||||
from twisted.internet import reactor
|
||||
|
||||
from pypsyc.server import Server
|
||||
|
||||
|
||||
def main():
|
||||
parser = OptionParser()
|
||||
parser.add_option('-d', '--directory', dest='directory',
|
||||
default=path.expanduser(path.join('~', '.pypsycd')),
|
||||
help="directory where the database is stored "
|
||||
"(defaults to ~/.pypsycd)")
|
||||
parser.add_option('-H', '--hostname', dest='hostname',
|
||||
help="hostname of the psyc server")
|
||||
parser.add_option('-i', '--interface', dest='interface', default='',
|
||||
help="interface to bind to (defaults to all)")
|
||||
parser.add_option('-p', '--psyc-port', type='int', default=4404,
|
||||
help="port to listen for incoming psyc connections "
|
||||
"or 0 to disable them (defaults to 4404)")
|
||||
parser.add_option('-w', '--webif-port', type='int', default=8080,
|
||||
help="port of the web interface or 0 to disable it "
|
||||
"(defaults to 8080)")
|
||||
options = parser.parse_args()[0]
|
||||
|
||||
if not path.exists(options.directory):
|
||||
mkdir(options.directory)
|
||||
Server(hostname=options.hostname or gethostname(),
|
||||
interface=options.interface,
|
||||
psyc_port=options.psyc_port,
|
||||
webif_port=options.webif_port,
|
||||
db_file=path.join(options.directory, 'database'))
|
||||
reactor.run()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,8 @@
|
|||
"""
|
||||
pypsyc
|
||||
~~~~~~
|
||||
|
||||
Pypsyc is a pythonic framework for PSYC servers and clients.
|
||||
"""
|
||||
|
||||
__version__ = '0.0dev'
|
|
@ -0,0 +1,4 @@
|
|||
"""
|
||||
pypsyc.client
|
||||
~~~~~~~~~~~~~
|
||||
"""
|
|
@ -0,0 +1,383 @@
|
|||
"""
|
||||
pypsyc.client.controller
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Classes which connect the models with the views. They observe the models
|
||||
and update the view. They are injected into the views and have methods
|
||||
that can change the models after user input validation.
|
||||
|
||||
:copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
from functools import partial
|
||||
|
||||
from pypsyc.client.model import Account
|
||||
from pypsyc.client.observable import ObsList
|
||||
from pypsyc.util import schedule
|
||||
|
||||
|
||||
def _observe_list(obs_list, insert, delitem, update, *args):
|
||||
if insert:
|
||||
for i, v in enumerate(obs_list):
|
||||
insert(i, v, *args)
|
||||
obs_list.insert_evt.add_observer(insert, *args)
|
||||
if delitem is not None:
|
||||
obs_list.delitem_evt.add_observer(delitem)
|
||||
if update is not None:
|
||||
obs_list.update_evt += update
|
||||
|
||||
def _observe_dict(obs_dict, setitem, delitem, update, *args):
|
||||
if setitem is not None:
|
||||
for k, v in obs_dict.iteritems():
|
||||
setitem(k, None, v, *args)
|
||||
obs_dict.setitem_evt.add_observer(setitem, *args)
|
||||
if delitem is not None:
|
||||
obs_dict.delitem_evt += delitem
|
||||
if update is not None:
|
||||
obs_dict.update_evt += update
|
||||
|
||||
def _unobserve_list(obs_list, insert, delitem, update):
|
||||
if insert is not None:
|
||||
obs_list.insert_evt -= insert
|
||||
if delitem is not None:
|
||||
obs_list.delitem_evt -= delitem
|
||||
for v in obs_list:
|
||||
delitem(0, v)
|
||||
if update is not None:
|
||||
obs_list.update_evt -= update
|
||||
|
||||
def _unobserve_dict(obs_dict, setitem, delitem, update):
|
||||
if setitem is not None:
|
||||
obs_dict.setitem_evt -= setitem
|
||||
if delitem is not None:
|
||||
obs_dict.delitem_evt -= delitem
|
||||
for k, v in obs_dict.iteritems():
|
||||
delitem(k, v)
|
||||
if update is not None:
|
||||
obs_dict.update_evt -= update
|
||||
|
||||
|
||||
class ListBinding(object):
|
||||
def __init__(self, model_list, view_list, make_row):
|
||||
self.model_list = model_list
|
||||
self.view_list = view_list
|
||||
self.make_row = make_row
|
||||
_observe_list(model_list, self._add, self._del, self._update)
|
||||
|
||||
def unbind(self):
|
||||
_unobserve_list(self.model_list, self._add, self._del, self._update)
|
||||
|
||||
def _add(self, index, obj):
|
||||
self.view_list.append(self.make_row(obj))
|
||||
|
||||
def _del(self, index, obj):
|
||||
del self.view_list[index]
|
||||
|
||||
def _update(self, index, obj):
|
||||
self.view_list[index] = self.make_row(obj)
|
||||
|
||||
|
||||
class AccountsController(object):
|
||||
def __init__(self, client, view):
|
||||
self.client = client
|
||||
self.view = view
|
||||
view.controller = self
|
||||
|
||||
self.binding = ListBinding(self.client.accounts, view.accounts,
|
||||
lambda obj: (obj.uni, obj.active))
|
||||
|
||||
def add_account(self):
|
||||
account = Account(self.client, "", "", "", False, False)
|
||||
self.view.show_addedit_dialog(account.__dict__, True,
|
||||
partial(self._saved, account, True))
|
||||
|
||||
def edit_account(self, pos):
|
||||
account = self.client.accounts[pos]
|
||||
self.view.show_addedit_dialog(account.__dict__, False,
|
||||
partial(self._saved, account, False))
|
||||
|
||||
def _saved(self, account, add):
|
||||
if add:
|
||||
self.client.accounts.append(account)
|
||||
else:
|
||||
self.client.accounts.updated_item(account)
|
||||
self.client.save_accounts()
|
||||
|
||||
def remove_account(self, pos):
|
||||
del self.client.accounts[pos]
|
||||
self.client.save_accounts()
|
||||
|
||||
def set_active(self, pos, active):
|
||||
account = self.client.accounts[pos]
|
||||
account.active = active
|
||||
self.client.accounts.updated_item(account)
|
||||
self.client.save_accounts()
|
||||
|
||||
def closed(self):
|
||||
self.binding.unbind()
|
||||
|
||||
|
||||
class DumpController(object):
|
||||
def __init__(self, client, view):
|
||||
self.view = view
|
||||
view.controller = self
|
||||
self.accounts = client.accounts
|
||||
_observe_list(self.accounts, self._add, self._del, None)
|
||||
|
||||
def _add(self, index, account):
|
||||
self._update(None, getattr(account, 'circuit', None), account.uni)
|
||||
account.update_evt['circuit'].add_observer(self._update, account.uni)
|
||||
|
||||
def _del(self, index, account):
|
||||
account.update_evt['circuit'] -= self._update
|
||||
if hasattr(account, 'circuit'):
|
||||
account.circuit.dump_evt -= self.view.show_line
|
||||
|
||||
def _update(self, old, value, account_uni):
|
||||
if old is not None:
|
||||
old.dump_evt -= self.view.show_line
|
||||
if value is not None:
|
||||
value.dump_evt.add_observer(self.view.show_line, account_uni)
|
||||
|
||||
def closed(self):
|
||||
_unobserve_list(self.accounts, self._add, self._del, None)
|
||||
|
||||
|
||||
class ConversationController(object):
|
||||
def __init__(self, tabs_controller, conversation, view):
|
||||
self.tabs_controller = tabs_controller
|
||||
self.conversation = conversation
|
||||
self.view = view
|
||||
view.controller = self
|
||||
|
||||
conversation.unknown_target_evt += view.show_unknown_target
|
||||
conversation.delivery_failed_evt += view.show_delivery_failed
|
||||
_observe_list(conversation.messages, self.add_message, None, None)
|
||||
|
||||
def add_message(self, index, message):
|
||||
line = "(%s) <%s> %s" % (message.time.strftime("%H:%M:%S"),
|
||||
message.source, message.message)
|
||||
self.view.show_message(line)
|
||||
|
||||
def enter(self, text):
|
||||
if text.startswith('/'):
|
||||
self.view.show_unknown_command()
|
||||
else:
|
||||
self.conversation.send_message(text)
|
||||
|
||||
def closed(self):
|
||||
_unobserve_list(self.conversation.messages, self.add_message, None, None)
|
||||
self.conversation.unknown_target_evt -= self.view.show_unknown_target
|
||||
self.conversation.delivery_failed_evt -= self.view.show_delivery_failed
|
||||
|
||||
|
||||
class ConferenceController(ConversationController):
|
||||
def __init__(self, tabs_controller, conference, view):
|
||||
self.conference = conference
|
||||
ConversationController.__init__(self, tabs_controller, conference,
|
||||
view)
|
||||
self.binding = ListBinding(conference.members, view.members,
|
||||
lambda member: (member.uni, member.nick))
|
||||
|
||||
def open_conversation(self, pos):
|
||||
account = self.conference.conferencing.account
|
||||
member = self.conference.members[pos]
|
||||
conversation = account.get_conversation(member.uni)
|
||||
self.tabs_controller.focus_conversation(conversation)
|
||||
|
||||
def closed(self):
|
||||
self.binding.unbind()
|
||||
ConversationController.closed(self)
|
||||
|
||||
|
||||
class TabsController(object):
|
||||
def __init__(self, client, view):
|
||||
self.view = view
|
||||
view.controller = self
|
||||
|
||||
self.view_to_model = {}
|
||||
self.model_to_controller = {}
|
||||
_observe_list(client.accounts, self._add_account, self._del_account, None)
|
||||
|
||||
def _add_account(self, index, account):
|
||||
_observe_dict(account.conversations, self._add_conversation,
|
||||
self._del_conversation, None, account.conversations,
|
||||
self.view.show_conversation, ConversationController)
|
||||
_observe_dict(account.conferences, self._add_conversation,
|
||||
self._del_conversation, None, account.conferences,
|
||||
self.view.show_conference, ConferenceController)
|
||||
|
||||
def _del_account(self, index, account):
|
||||
_unobserve_dict(account.conversations, self._add_conversation,
|
||||
self._del_conversation, None)
|
||||
_unobserve_dict(account.conferences, self._add_conversation,
|
||||
self._del_conversation, None)
|
||||
|
||||
def _add_conversation(self, uni, old, conversation, list, show, Class):
|
||||
conv_view = show(conversation.uni)
|
||||
controller = Class(self, conversation, conv_view)
|
||||
self.view_to_model[conv_view] = list, conversation.uni
|
||||
self.model_to_controller[conversation] = controller, conv_view
|
||||
|
||||
def _del_conversation(self, uni, conversation):
|
||||
controller, view = self.model_to_controller.pop(conversation)
|
||||
del self.view_to_model[view]
|
||||
self.view.remove_tab(view)
|
||||
controller.closed()
|
||||
|
||||
def focus_conversation(self, conversation):
|
||||
self.view.focus_tab(self.model_to_controller[conversation][1])
|
||||
|
||||
def close_tab(self, view):
|
||||
list, uni = self.view_to_model[view]
|
||||
del list[uni]
|
||||
|
||||
|
||||
class FriendListController(object):
|
||||
def __init__(self, client, view, tabs_controller):
|
||||
self.view = view
|
||||
view.controller = self
|
||||
self.tabs_controller = tabs_controller
|
||||
self.friends = ObsList()
|
||||
self.binding = ListBinding(self.friends, view.friends, self._make_row)
|
||||
|
||||
_observe_list(client.accounts, self._add_account, self._del_account, None)
|
||||
|
||||
def _add_account(self, index, account):
|
||||
_observe_dict(account.friends, self._add_friend, self._del_friend,
|
||||
self._update_friend)
|
||||
|
||||
def _del_account(self, index, account):
|
||||
_unobserve_dict(account.friends, self._add_friend, self._del_friend,
|
||||
self._update_friend)
|
||||
|
||||
def _add_friend(self, uni, old, friend):
|
||||
self.friends.append(friend)
|
||||
|
||||
def _del_friend(self, uni, friend):
|
||||
self.friends.remove(friend)
|
||||
|
||||
def _update_friend(self, uni, friend):
|
||||
self.friends.updated_item(friend)
|
||||
|
||||
def _make_row(self, friend):
|
||||
uni = friend.uni
|
||||
if uni.startswith('psyc://' + friend.account.server + '/'):
|
||||
uni = uni.rpartition('/~')[2]
|
||||
return (uni, friend.presence.availability > 1, friend.state)
|
||||
|
||||
def open_conversation(self, pos):
|
||||
friend = self.friends[pos]
|
||||
model = friend.account.get_conversation(friend.uni)
|
||||
self.tabs_controller.focus_conversation(model)
|
||||
|
||||
def accept_friendship(self, pos):
|
||||
friend = self.friends[pos]
|
||||
schedule(friend.account.add_friend, friend.uni)
|
||||
|
||||
def cancel_friendship(self, pos):
|
||||
friend = self.friends[pos]
|
||||
schedule(friend.account.remove_friend, friend.uni)
|
||||
|
||||
|
||||
class MainController(object):
|
||||
def __init__(self, client, view):
|
||||
self.client = client
|
||||
self.view = view
|
||||
view.controller = self
|
||||
self.active_accounts = set()
|
||||
|
||||
self.tabs_controller = TabsController(client, view.tabs_view)
|
||||
FriendListController(client, view.friends_view, self.tabs_controller)
|
||||
|
||||
_observe_list(client.accounts, self._add_account, self._del_account,
|
||||
self._update_account)
|
||||
|
||||
def _add_account(self, index, account):
|
||||
account.no_password_evt.add_observer(self._no_password, account)
|
||||
account.connection_error_evt.add_observer(self._conn_error, account)
|
||||
account.no_such_user_evt += self._no_such_user
|
||||
account.auth_error_evt.add_observer(self._auth_error, account)
|
||||
if account.active:
|
||||
self._account_activated(account)
|
||||
|
||||
def _del_account(self, index, account):
|
||||
account.no_password_evt -= self._no_password
|
||||
account.connection_error_evt -= self._conn_error
|
||||
account.no_such_user_evt -= self._no_such_user
|
||||
account.auth_error_evt -= self._auth_error
|
||||
if account.active:
|
||||
self._account_deactivated(account)
|
||||
|
||||
def _update_account(self, index, account):
|
||||
if account.active:
|
||||
self._account_activated(account)
|
||||
else:
|
||||
self._account_deactivated(account)
|
||||
|
||||
def _account_activated(self, account):
|
||||
had_active_accounts = bool(self.active_accounts)
|
||||
self.active_accounts.add(account)
|
||||
if not had_active_accounts:
|
||||
self.view.show_active_accounts(True)
|
||||
|
||||
def _account_deactivated(self, account):
|
||||
had_active_accounts = bool(self.active_accounts)
|
||||
self.active_accounts.discard(account)
|
||||
if had_active_accounts and not self.active_accounts:
|
||||
self.view.show_active_accounts(False)
|
||||
|
||||
def _no_password(self, account):
|
||||
def callback():
|
||||
account.active = True
|
||||
self.client.accounts.updated_item(account)
|
||||
self.client.save_accounts()
|
||||
self.view.show_password_dialog(account.uni, account.__dict__, callback)
|
||||
|
||||
def _conn_error(self, error, account):
|
||||
self.view.show_conn_error(account.uni, error.args[0])
|
||||
|
||||
def _no_such_user(self, error):
|
||||
self.view.show_no_such_user(error.args[0])
|
||||
|
||||
def _auth_error(self, error, account):
|
||||
self.view.show_auth_error(account.uni, error.args[0])
|
||||
|
||||
def open_accounts(self):
|
||||
AccountsController(self.client, self.view.show_accounts())
|
||||
|
||||
def open_dump(self):
|
||||
DumpController(self.client, self.view.show_dump_win())
|
||||
|
||||
def open_conversation(self):
|
||||
accounts = [acc for acc in self.client.accounts if acc.active]
|
||||
def callback(account, server, person):
|
||||
uni = 'psyc://%s/~%s' % (server, person)
|
||||
model = accounts[account].get_conversation(uni)
|
||||
self.tabs_controller.focus_conversation(model)
|
||||
|
||||
self.view.show_open_conv_dialog((account.uni for account in accounts),
|
||||
callback)
|
||||
|
||||
def open_conference(self):
|
||||
accounts = [acc for acc in self.client.accounts if acc.active]
|
||||
def callback(account, server, place):
|
||||
uni = 'psyc://%s/@%s' % (server, place)
|
||||
model = accounts[account].get_conference(uni, subscribe=True)
|
||||
self.tabs_controller.focus_conversation(model)
|
||||
|
||||
self.view.show_open_conf_dialog((account.uni for account in accounts),
|
||||
callback)
|
||||
|
||||
def add_friend(self):
|
||||
accounts = [acc for acc in self.client.accounts if acc.active]
|
||||
def callback(account, server, person):
|
||||
uni = 'psyc://%s/~%s' % (server, person)
|
||||
schedule(accounts[account].add_friend, uni)
|
||||
|
||||
self.view.show_add_friend_dialog((account.uni for account in accounts),
|
||||
callback)
|
||||
def quit(self):
|
||||
_unobserve_list(self.client.accounts, self._add_account,
|
||||
self._del_account, self._update_account)
|
||||
self.client.quit()
|
|
@ -0,0 +1,349 @@
|
|||
"""
|
||||
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()
|
|
@ -0,0 +1,108 @@
|
|||
"""
|
||||
pypsyc.client.observable
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
:copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
from collections import MutableSequence, MutableMapping
|
||||
|
||||
from pypsyc.util import Event
|
||||
|
||||
|
||||
class ObsAttr(object):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
try:
|
||||
return instance.__dict__[self.name]
|
||||
except KeyError:
|
||||
raise AttributeError
|
||||
|
||||
def __set__(self, instance, value):
|
||||
old = instance.__dict__.get(self.name)
|
||||
instance.__dict__[self.name] = value
|
||||
if value != old:
|
||||
instance.update_evt[self.name](old, value)
|
||||
|
||||
def __delete__(self, instance):
|
||||
old = instance.__dict__.get(self.name)
|
||||
try:
|
||||
del instance.__dict__[self.name]
|
||||
except KeyError:
|
||||
raise AttributeError
|
||||
instance.update_evt[self.name](old, None)
|
||||
|
||||
|
||||
class ObsObj(object):
|
||||
def __init__(self):
|
||||
self.update_evt = dict((obs_attr.name, Event()) for obs_attr in
|
||||
self.__class__.__dict__.itervalues()
|
||||
if isinstance(obs_attr, ObsAttr))
|
||||
|
||||
|
||||
class ObsList(list, MutableSequence):
|
||||
def __init__(self, *args, **kwds):
|
||||
list.__init__(self, *args, **kwds)
|
||||
|
||||
self.setitem_evt = Event()
|
||||
self.delitem_evt = Event()
|
||||
self.insert_evt = Event()
|
||||
self.update_evt = Event()
|
||||
|
||||
def __setitem__(self, index, value):
|
||||
old = self[index]
|
||||
list.__setitem__(self, index, value)
|
||||
if value != old:
|
||||
self.setitem_evt(index, old, value)
|
||||
|
||||
def __delitem__(self, index):
|
||||
if index < 0:
|
||||
index = len(self) + index
|
||||
old = self[index]
|
||||
list.__delitem__(self, index)
|
||||
self.delitem_evt(index, old)
|
||||
|
||||
def insert(self, index, value):
|
||||
list.insert(self, index, value)
|
||||
self.insert_evt(index, value)
|
||||
|
||||
append = MutableSequence.append
|
||||
reverse = MutableSequence.reverse
|
||||
extend = MutableSequence.extend
|
||||
pop = MutableSequence.pop
|
||||
remove = MutableSequence.remove
|
||||
__iadd__ = MutableSequence.__iadd__
|
||||
|
||||
def updated_item(self, obj):
|
||||
self.update_evt(self.index(obj), obj)
|
||||
|
||||
|
||||
class ObsDict(dict, MutableMapping):
|
||||
def __init__(self, *args, **kwds):
|
||||
dict.__init__(self, *args, **kwds)
|
||||
|
||||
self.setitem_evt = Event()
|
||||
self.delitem_evt = Event()
|
||||
self.update_evt = Event()
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
old = self.get(key)
|
||||
dict.__setitem__(self, key, value)
|
||||
if value != old:
|
||||
self.setitem_evt(key, old, value)
|
||||
|
||||
def __delitem__(self, key):
|
||||
old = self.get(key)
|
||||
dict.__delitem__(self, key)
|
||||
self.delitem_evt(key, old)
|
||||
|
||||
pop = MutableMapping.pop
|
||||
popitem = MutableMapping.popitem
|
||||
clear = MutableMapping.clear
|
||||
update = MutableMapping.update
|
||||
setdefault = MutableMapping.setdefault
|
||||
|
||||
def updated_item(self, key):
|
||||
self.update_evt(key, self[key])
|
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,686 @@
|
|||
"""
|
||||
pypsyc.client.view
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
pypsyc GTK views
|
||||
|
||||
:copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
import pygtk
|
||||
pygtk.require('2.0')
|
||||
import gtk
|
||||
import pango
|
||||
from gobject import idle_add
|
||||
from pkg_resources import resource_filename
|
||||
|
||||
|
||||
def _(message):
|
||||
return message
|
||||
|
||||
|
||||
class Form(gtk.HBox):
|
||||
def __init__(self, dict_, attrs):
|
||||
gtk.HBox.__init__(self)
|
||||
self.dict = dict_
|
||||
self.fields = []
|
||||
|
||||
left = gtk.VBox()
|
||||
self.pack_start(left, False, False, 10)
|
||||
|
||||
right = gtk.VBox()
|
||||
self.pack_start(right)
|
||||
|
||||
for l, a in attrs:
|
||||
x = dict_[a]
|
||||
le, ri, value_func = getattr(self, '_' + type(x).__name__)(l, x)
|
||||
left.pack_start(le, padding=10)
|
||||
right.pack_start(ri)
|
||||
self.fields.append((a, value_func))
|
||||
|
||||
def _str(self, l, s):
|
||||
label = gtk.Label(_(l) + ":")
|
||||
label.set_alignment(0, 0.5)
|
||||
entry = gtk.Entry()
|
||||
entry.set_text(s)
|
||||
if l.lower() == 'password':
|
||||
entry.set_visibility(False)
|
||||
return label, entry, entry.get_text
|
||||
|
||||
def _bool(self, l, b):
|
||||
check = gtk.CheckButton(_(l))
|
||||
check.set_active(b)
|
||||
return gtk.Label(), check, check.get_active
|
||||
|
||||
def _list(self, l, list_):
|
||||
label = gtk.Label(_(l) + ":")
|
||||
label.set_alignment(0, 0.5)
|
||||
combobox = gtk.combo_box_new_text()
|
||||
for i in list_:
|
||||
combobox.append_text(i)
|
||||
combobox.set_active(0)
|
||||
return label, combobox, combobox.get_active
|
||||
|
||||
_generator = _list
|
||||
|
||||
def save(self):
|
||||
self.dict.update((a, f()) for a, f in self.fields)
|
||||
return self.dict
|
||||
|
||||
def _open_form_dialog(title, window, dict_, attrs, buttons, func, markup=None):
|
||||
dialog = gtk.Dialog(title, window,
|
||||
(gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT |
|
||||
gtk.DIALOG_NO_SEPARATOR), buttons)
|
||||
if markup is not None:
|
||||
label = gtk.Label()
|
||||
label.set_markup(markup)
|
||||
dialog.vbox.pack_start(label)
|
||||
form = Form(dict_, attrs)
|
||||
dialog.vbox.pack_start(form)
|
||||
dialog.connect('response', _dialog_response, form, func)
|
||||
dialog.show_all()
|
||||
|
||||
def _dialog_response(w, response, form, func):
|
||||
if response == gtk.RESPONSE_ACCEPT:
|
||||
func(form.save())
|
||||
if response != gtk.RESPONSE_DELETE_EVENT:
|
||||
w.destroy()
|
||||
|
||||
|
||||
class AccountsView(object):
|
||||
def __init__(self):
|
||||
self.window = self.create_window()
|
||||
self.window.show_all()
|
||||
|
||||
def create_window(self):
|
||||
win = gtk.Window()
|
||||
win.set_title(_("Accounts"))
|
||||
win.connect('destroy', lambda w: self.controller.closed())
|
||||
|
||||
vbox = gtk.VBox()
|
||||
win.add(vbox)
|
||||
|
||||
scroll = gtk.ScrolledWindow()
|
||||
scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
|
||||
scroll.add(self.create_tree_view())
|
||||
vbox.pack_start(scroll)
|
||||
|
||||
buttons = gtk.HButtonBox()
|
||||
vbox.pack_start(buttons)
|
||||
|
||||
add_button = gtk.Button(stock=gtk.STOCK_ADD)
|
||||
buttons.pack_start(add_button)
|
||||
|
||||
remove_button = self.remove_button = gtk.Button(stock=gtk.STOCK_REMOVE)
|
||||
remove_button.set_sensitive(False)
|
||||
buttons.pack_start(remove_button)
|
||||
|
||||
edit_button = self.edit_button = gtk.Button(stock=gtk.STOCK_EDIT)
|
||||
edit_button.set_sensitive(False)
|
||||
buttons.pack_start(edit_button)
|
||||
|
||||
add_button.connect('clicked', lambda w: self.controller.add_account())
|
||||
remove_button.connect('clicked', self.on_remove)
|
||||
edit_button.connect('clicked', self.on_edit)
|
||||
|
||||
return win
|
||||
|
||||
def create_tree_view(self):
|
||||
self.accounts = gtk.ListStore(str, bool)
|
||||
tv = gtk.TreeView(self.accounts)
|
||||
tv.set_size_request(400, 200)
|
||||
self.selection = tv.get_selection()
|
||||
self.selection.connect('changed', self.on_selection)
|
||||
|
||||
renderer = gtk.CellRendererToggle()
|
||||
renderer.connect('toggled', self.on_toggle)
|
||||
column = gtk.TreeViewColumn(_("Active"), renderer, active=1)
|
||||
tv.append_column(column)
|
||||
|
||||
column = gtk.TreeViewColumn("UNI", gtk.CellRendererText(), text=0)
|
||||
tv.append_column(column)
|
||||
|
||||
return tv
|
||||
|
||||
def on_selection(self, selection):
|
||||
selected = selection.get_selected()[1] is not None
|
||||
self.remove_button.set_sensitive(selected)
|
||||
self.edit_button.set_sensitive(selected)
|
||||
|
||||
def on_remove(self, w):
|
||||
model, iter = self.selection.get_selected()
|
||||
self.controller.remove_account(model.get_path(iter)[0])
|
||||
|
||||
def on_edit(self, w):
|
||||
model, iter = self.selection.get_selected()
|
||||
self.controller.edit_account(model.get_path(iter)[0])
|
||||
|
||||
def on_toggle(self, w, path):
|
||||
self.controller.set_active(int(path), not self.accounts[path][1])
|
||||
|
||||
def show_addedit_dialog(self, account, add, callback):
|
||||
attrs = (('Server', 'server'),
|
||||
('User', 'person'),
|
||||
('Password', 'password'),
|
||||
('Save password', 'save_password'))
|
||||
_open_form_dialog(_("Add Account" if add else "Edit Account"),
|
||||
self.window, account, attrs,
|
||||
(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
|
||||
gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT),
|
||||
lambda d: callback())
|
||||
|
||||
|
||||
class ConversationView(gtk.VBox):
|
||||
def __init__(self, tabs_view):
|
||||
self.tabs_view = tabs_view
|
||||
gtk.VBox.__init__(self)
|
||||
|
||||
self.pack_start(self.create_content())
|
||||
|
||||
self.entry = gtk.Entry()
|
||||
self.entry.connect('activate', self._enter)
|
||||
self.pack_start(self.entry, False)
|
||||
|
||||
def create_content(self):
|
||||
scroll = gtk.ScrolledWindow()
|
||||
scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
|
||||
self.vadjustment = scroll.get_vadjustment()
|
||||
|
||||
tv = gtk.TextView()
|
||||
self.text_buffer = tv.get_buffer()
|
||||
tv.set_editable(False)
|
||||
tv.set_cursor_visible(False)
|
||||
tv.set_wrap_mode(gtk.WRAP_WORD_CHAR)
|
||||
scroll.add(tv)
|
||||
|
||||
return scroll
|
||||
|
||||
def _enter(self, w):
|
||||
text = w.get_text()
|
||||
if not text:
|
||||
return
|
||||
self.controller.enter(text)
|
||||
w.set_text("")
|
||||
|
||||
def show_message(self, message):
|
||||
self._show_line(message)
|
||||
self.tabs_view.showed_message(self)
|
||||
|
||||
def show_unknown_command(self):
|
||||
self._show_line(_("Unknown command"))
|
||||
|
||||
def show_unknown_target(self):
|
||||
self._show_line(_("Unknown user"))
|
||||
|
||||
def show_delivery_failed(self, message):
|
||||
self._show_line(_("Delivery failed") + ': ' + message)
|
||||
|
||||
def _show_line(self, line):
|
||||
va = self.vadjustment
|
||||
must_scroll = (va.value == va.upper - va.page_size)
|
||||
self.text_buffer.insert(self.text_buffer.get_end_iter(), '\n' + line)
|
||||
if must_scroll:
|
||||
idle_add(self._scroll)
|
||||
|
||||
def _scroll(self):
|
||||
va = self.vadjustment
|
||||
va.set_value(va.upper - va.page_size)
|
||||
|
||||
|
||||
class ConferenceView(ConversationView):
|
||||
def create_content(self):
|
||||
hpane = gtk.HPaned()
|
||||
hpane.pack1(ConversationView.create_content(self), True)
|
||||
|
||||
scroll = gtk.ScrolledWindow()
|
||||
scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
|
||||
scroll.add(self.create_tree_view())
|
||||
hpane.pack2(scroll, True)
|
||||
|
||||
hpane.set_position(450)
|
||||
return hpane
|
||||
|
||||
def create_tree_view(self):
|
||||
self.members = gtk.ListStore(str, str)
|
||||
tv = gtk.TreeView(self.members)
|
||||
tv.set_headers_visible(False)
|
||||
tv.set_tooltip_column(0)
|
||||
|
||||
cell = gtk.CellRendererText()
|
||||
cell.set_property('ellipsize', pango.ELLIPSIZE_END)
|
||||
column = gtk.TreeViewColumn(None, cell, text=1)
|
||||
tv.append_column(column)
|
||||
|
||||
tv.connect('row-activated', self._double_click)
|
||||
tv.connect('button-press-event', self._button_press)
|
||||
tv.connect('popup-menu', self._popup_menu)
|
||||
return tv
|
||||
|
||||
def _double_click(self, tv, path, view_column):
|
||||
self.controller.open_conversation(path[0])
|
||||
|
||||
def _button_press(self, widget, event):
|
||||
if event.type == gtk.gdk.BUTTON_PRESS and event.button == 3:
|
||||
model = widget.get_model()
|
||||
path = widget.get_path_at_pos(int(event.x), int(event.y))[0]
|
||||
menu = self.create_context_menu(model, model.get_iter(path))
|
||||
menu.popup(None, None, None, event.button, event.time)
|
||||
|
||||
def _popup_menu(self, widget):
|
||||
model, iter = widget.get_selection().get_selected()
|
||||
menu = self.create_context_menu(model, iter)
|
||||
menu.popup(None, None, None, 3, 0)
|
||||
return True
|
||||
|
||||
def create_context_menu(self, model, iter):
|
||||
menu = gtk.Menu()
|
||||
|
||||
conv = gtk.MenuItem(_("Open Conversation"))
|
||||
menu.append(conv)
|
||||
conv.connect('activate', lambda w:
|
||||
self.controller.open_conversation(model.get_path(iter)[0]))
|
||||
|
||||
if model.get_value(iter, 2) == 'offered':
|
||||
accept = gtk.MenuItem(_("Accept Friendship Request"))
|
||||
menu.append(accept)
|
||||
accept.connect('activate', lambda w:
|
||||
self.controller.accept_friendship(model.get_path(iter)[0]))
|
||||
|
||||
menu.show_all()
|
||||
return menu
|
||||
|
||||
def show_unknown_target(self):
|
||||
self._show_line(_("Unknown place"))
|
||||
|
||||
|
||||
class TabsView(object):
|
||||
def __init__(self, status_icon):
|
||||
self.status_icon = status_icon
|
||||
self.window = self.create_window()
|
||||
self.window.set_default_size(600, 400)
|
||||
self.unread_tabs = []
|
||||
|
||||
settings = gtk.Entry().get_settings()
|
||||
settings.set_property('gtk-entry-select-on-focus', False)
|
||||
|
||||
def create_window(self):
|
||||
win = gtk.Window()
|
||||
win.set_title("pypsyc")
|
||||
win.connect("delete_event", self._delete_event)
|
||||
win.connect('focus-in-event', self._window_focus)
|
||||
|
||||
self.notebook = gtk.Notebook()
|
||||
self.notebook.set_scrollable(True)
|
||||
self.notebook.connect('switch-page', self._switched_page)
|
||||
win.add(self.notebook)
|
||||
self.notebook.show()
|
||||
|
||||
return win
|
||||
|
||||
def _delete_event(self, w, e):
|
||||
self.window.hide()
|
||||
for tab in self.notebook:
|
||||
self.controller.close_tab(tab)
|
||||
return True
|
||||
|
||||
def _window_focus(self, w, e):
|
||||
current_page = self.notebook.get_current_page()
|
||||
if current_page != -1:
|
||||
self._switched_page(None, None, current_page)
|
||||
|
||||
def _switched_page(self, w, page_ptr, page_num):
|
||||
tab = self.notebook.get_nth_page(page_num)
|
||||
if tab in self.unread_tabs:
|
||||
self._change_tab_color(tab, 'black')
|
||||
self.unread_tabs.remove(tab)
|
||||
if not self.unread_tabs:
|
||||
self.status_icon.set_blinking(False)
|
||||
idle_add(tab.entry.grab_focus)
|
||||
|
||||
def showed_message(self, tab):
|
||||
if (not self.window.is_active() or
|
||||
self.notebook.get_current_page() != self.notebook.page_num(tab)):
|
||||
self._change_tab_color(tab, 'red')
|
||||
if tab not in self.unread_tabs:
|
||||
if not self.unread_tabs:
|
||||
self.status_icon.set_blinking(True)
|
||||
self.unread_tabs.append(tab)
|
||||
|
||||
def _change_tab_color(self, tab, color_spec):
|
||||
tab_label = self.notebook.get_tab_label(tab).get_children()[0]
|
||||
color = gtk.gdk.color_parse(color_spec)
|
||||
tab_label.modify_fg(gtk.STATE_NORMAL, color)
|
||||
tab_label.modify_fg(gtk.STATE_ACTIVE, color)
|
||||
|
||||
def on_status_icon_click(self):
|
||||
if not self.unread_tabs:
|
||||
return True
|
||||
self.focus_tab(self.unread_tabs[0])
|
||||
|
||||
def show_conversation(self, label):
|
||||
view = ConversationView(self)
|
||||
self._add_tab(view, label)
|
||||
return view
|
||||
|
||||
def show_conference(self, label):
|
||||
view = ConferenceView(self)
|
||||
self._add_tab(view, label)
|
||||
return view
|
||||
|
||||
def _add_tab(self, tab, label):
|
||||
tab_label = gtk.HBox()
|
||||
tab_label.pack_start(gtk.Label(label))
|
||||
|
||||
image = gtk.image_new_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU)
|
||||
button = gtk.Button()
|
||||
button.set_image(image)
|
||||
button.set_relief(gtk.RELIEF_NONE)
|
||||
button.connect('clicked', lambda w: self.controller.close_tab(tab))
|
||||
tab_label.pack_start(button)
|
||||
|
||||
tab.show_all()
|
||||
tab_label.show_all()
|
||||
self.notebook.append_page(tab, tab_label)
|
||||
|
||||
def focus_tab(self, tab):
|
||||
self.notebook.set_current_page(self.notebook.page_num(tab))
|
||||
self.window.present()
|
||||
|
||||
def remove_tab(self, tab):
|
||||
self.notebook.remove_page(self.notebook.page_num(tab))
|
||||
if self.notebook.get_n_pages() == 0:
|
||||
self.window.hide()
|
||||
if tab in self.unread_tabs:
|
||||
self.unread_tabs.remove(tab)
|
||||
if not self.unread_tabs:
|
||||
self.status_icon.set_blinking(False)
|
||||
|
||||
|
||||
class FriendsView(object):
|
||||
def __init__(self):
|
||||
self.list_view = self.create_tree_view()
|
||||
|
||||
def create_tree_view(self):
|
||||
self.friends = gtk.ListStore(str, bool, str)
|
||||
tv = gtk.TreeView(self.friends)
|
||||
tv.set_headers_visible(False)
|
||||
tv.set_size_request(200, 400)
|
||||
tv.set_property('has-tooltip', True)
|
||||
|
||||
cell = gtk.CellRendererText()
|
||||
cell.set_property('foreground-set', True)
|
||||
column = gtk.TreeViewColumn(None, cell, text=0)
|
||||
column.set_cell_data_func(cell, self._foreground)
|
||||
tv.append_column(column)
|
||||
|
||||
tv.connect('row-activated', self._double_click)
|
||||
tv.connect('query-tooltip', self._tooltip)
|
||||
tv.connect('button-press-event', self._button_press)
|
||||
tv.connect('popup-menu', self._popup_menu)
|
||||
return tv
|
||||
|
||||
def _foreground(self, column, cell, model, iter):
|
||||
if model.get_value(iter, 1):
|
||||
cell.set_property('foreground', 'black')
|
||||
else:
|
||||
cell.set_property('foreground', 'grey')
|
||||
|
||||
def _double_click(self, tv, path, view_column):
|
||||
self.controller.open_conversation(path[0])
|
||||
|
||||
def _tooltip(self, widget, x, y, keyboard_mode, tooltip):
|
||||
tooltip_context = widget.get_tooltip_context(x, y, keyboard_mode)
|
||||
if tooltip_context is None:
|
||||
return False
|
||||
model, path, iter = tooltip_context
|
||||
tooltip.set_text(_("Friendship state") + ": %s" % model.get(iter, 2))
|
||||
widget.set_tooltip_row(tooltip, path)
|
||||
return True
|
||||
|
||||
def _button_press(self, widget, event):
|
||||
if event.type == gtk.gdk.BUTTON_PRESS and event.button == 3:
|
||||
model = widget.get_model()
|
||||
path = widget.get_path_at_pos(int(event.x), int(event.y))[0]
|
||||
menu = self.create_context_menu(model, model.get_iter(path))
|
||||
menu.popup(None, None, None, event.button, event.time)
|
||||
|
||||
def _popup_menu(self, widget):
|
||||
model, iter = widget.get_selection().get_selected()
|
||||
menu = self.create_context_menu(model, iter)
|
||||
menu.popup(None, None, None, 3, 0)
|
||||
return True
|
||||
|
||||
def create_context_menu(self, model, iter):
|
||||
menu = gtk.Menu()
|
||||
|
||||
conv = gtk.MenuItem(_("Open Conversation"))
|
||||
menu.append(conv)
|
||||
conv.connect('activate', lambda w:
|
||||
self.controller.open_conversation(model.get_path(iter)[0]))
|
||||
|
||||
if model.get_value(iter, 2) == 'offered':
|
||||
accept = gtk.MenuItem(_("Accept Friendship Request"))
|
||||
menu.append(accept)
|
||||
accept.connect('activate', lambda w:
|
||||
self.controller.accept_friendship(model.get_path(iter)[0]))
|
||||
|
||||
deny = gtk.MenuItem(_("Deny Friendship Request"))
|
||||
menu.append(deny)
|
||||
deny.connect('activate', lambda w:
|
||||
self.controller.cancel_friendship(model.get_path(iter)[0]))
|
||||
|
||||
elif model.get_value(iter, 2) == 'pending':
|
||||
cancel = gtk.MenuItem(_("Cancel Friendship Request"))
|
||||
menu.append(cancel)
|
||||
cancel.connect('activate', lambda w:
|
||||
self.controller.cancel_friendship(model.get_path(iter)[0]))
|
||||
else:
|
||||
cancel = gtk.MenuItem(_("Cancel Friendship"))
|
||||
menu.append(cancel)
|
||||
cancel.connect('activate', lambda w:
|
||||
self.controller.cancel_friendship(model.get_path(iter)[0]))
|
||||
|
||||
menu.show_all()
|
||||
return menu
|
||||
|
||||
|
||||
class DumpView(object):
|
||||
def __init__(self):
|
||||
self.buffers = {}
|
||||
self.window = self.create_window()
|
||||
self.window.show_all()
|
||||
|
||||
def create_window(self):
|
||||
win = gtk.Window()
|
||||
win.set_title(_("pypsyc protocol dump"))
|
||||
win.set_default_size(500, 300)
|
||||
win.connect('destroy', lambda w: self.controller.closed())
|
||||
|
||||
self.notebook = gtk.Notebook()
|
||||
win.add(self.notebook)
|
||||
|
||||
return win
|
||||
|
||||
def show_line(self, direction, line, account):
|
||||
if account not in self.buffers:
|
||||
scroll = gtk.ScrolledWindow()
|
||||
tv = gtk.TextView()
|
||||
tv.set_editable(False)
|
||||
scroll.add(tv)
|
||||
|
||||
scroll.show_all()
|
||||
self.notebook.append_page(scroll, gtk.Label(account))
|
||||
self.buffers[account] = tv.get_buffer(), scroll.get_vadjustment()
|
||||
buffer, va = self.buffers[account]
|
||||
|
||||
must_scroll = (va.value == va.upper - va.page_size)
|
||||
line = repr(line)[1:-1]
|
||||
buffer.insert(buffer.get_end_iter(),
|
||||
('\n< ' if direction == 'o' else '\n> ') + line)
|
||||
if must_scroll:
|
||||
idle_add(self.scroll, va)
|
||||
|
||||
def scroll(self, va):
|
||||
va.set_value(va.upper - va.page_size)
|
||||
|
||||
|
||||
class MainView(object):
|
||||
def __init__(self):
|
||||
self.window = self.create_window()
|
||||
self.window.show_all()
|
||||
self.position = self.window.get_position()
|
||||
|
||||
self.status_icon = self.create_status_icon()
|
||||
self.tabs_view = TabsView(self.status_icon)
|
||||
|
||||
def create_window(self):
|
||||
win = gtk.Window()
|
||||
win.set_title("pypsyc")
|
||||
win.connect("delete_event", lambda w, e: win.hide() or True)
|
||||
|
||||
vbox = gtk.VBox()
|
||||
win.add(vbox)
|
||||
|
||||
menu_bar = gtk.MenuBar()
|
||||
vbox.pack_start(menu_bar, False)
|
||||
menu_bar.append(self.create_file_menu())
|
||||
|
||||
scroll = gtk.ScrolledWindow()
|
||||
scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
|
||||
vbox.pack_start(scroll)
|
||||
|
||||
self.friends_view = FriendsView()
|
||||
scroll.add(self.friends_view.list_view)
|
||||
|
||||
return win
|
||||
|
||||
def create_file_menu(self):
|
||||
menu = gtk.Menu()
|
||||
|
||||
accounts = gtk.MenuItem(_("Accounts"))
|
||||
menu.append(accounts)
|
||||
accounts.connect('activate', lambda w: self.controller.open_accounts())
|
||||
|
||||
conv = gtk.MenuItem(_("Open Conversation"))
|
||||
menu.append(conv)
|
||||
conv.connect('activate', lambda w: self.controller.open_conversation())
|
||||
|
||||
conf = gtk.MenuItem(_("Open Conference"))
|
||||
menu.append(conf)
|
||||
conf.connect('activate', lambda w: self.controller.open_conference())
|
||||
|
||||
add_friend = gtk.MenuItem(_("Add Friend"))
|
||||
menu.append(add_friend)
|
||||
add_friend.connect('activate', lambda w: self.controller.add_friend())
|
||||
|
||||
self.account_buttons = (conv, conf, add_friend)
|
||||
self.show_active_accounts(False)
|
||||
|
||||
dump = gtk.MenuItem(_("Protocol Dump"))
|
||||
menu.append(dump)
|
||||
dump.connect('activate', lambda w: self.controller.open_dump())
|
||||
|
||||
menu.append(gtk.MenuItem())
|
||||
|
||||
quit = gtk.MenuItem(_("Quit"))
|
||||
menu.append(quit)
|
||||
quit.connect('activate', lambda w: self.controller.quit())
|
||||
|
||||
item = gtk.MenuItem(_("File"))
|
||||
item.set_submenu(menu)
|
||||
return item
|
||||
|
||||
def create_status_icon(self):
|
||||
status_icon = gtk.StatusIcon()
|
||||
status_icon.set_from_file(resource_filename(__name__, 'psyc.ico'))
|
||||
status_icon.connect('activate', self._activated_status_icon)
|
||||
return status_icon
|
||||
|
||||
def _activated_status_icon(self, w):
|
||||
if self.tabs_view.on_status_icon_click():
|
||||
if self.window.is_active():
|
||||
self.position = self.window.get_position()
|
||||
self.window.hide()
|
||||
else:
|
||||
self.window.move(*self.position)
|
||||
self.window.present()
|
||||
|
||||
def show_active_accounts(self, active):
|
||||
for i in self.account_buttons:
|
||||
i.set_sensitive(active)
|
||||
|
||||
def show_password_dialog(self, uni, account, callback):
|
||||
title = _("Enter Password")
|
||||
attrs = (('Password', 'password'),
|
||||
('Save password', 'save_password'))
|
||||
text = "<big><b>Please enter password for account %s.</b></big>" % uni
|
||||
_open_form_dialog(title, self.window, account, attrs,
|
||||
(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT,
|
||||
gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT),
|
||||
lambda d: callback(), markup=text)
|
||||
|
||||
def show_conn_error(self, account, message):
|
||||
text = "%s - %s" % (_("Connection error"), account)
|
||||
dialog = gtk.MessageDialog(self.window, (gtk.DIALOG_MODAL |
|
||||
gtk.DIALOG_DESTROY_WITH_PARENT),
|
||||
gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, text)
|
||||
dialog.format_secondary_text(message)
|
||||
dialog.connect('response', lambda w, r: dialog.destroy())
|
||||
dialog.show()
|
||||
|
||||
def show_no_such_user(self, account):
|
||||
text = "No such user"
|
||||
dialog = gtk.MessageDialog(self.window, (gtk.DIALOG_MODAL |
|
||||
gtk.DIALOG_DESTROY_WITH_PARENT),
|
||||
gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, text)
|
||||
dialog.format_secondary_text(account)
|
||||
dialog.connect('response', lambda w, r: dialog.destroy())
|
||||
dialog.show()
|
||||
|
||||
def show_auth_error(self, account, message):
|
||||
text = "%s - %s" % (_("Authentication error"), account)
|
||||
dialog = gtk.MessageDialog(self.window, (gtk.DIALOG_MODAL |
|
||||
gtk.DIALOG_DESTROY_WITH_PARENT),
|
||||
gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, text)
|
||||
dialog.format_secondary_text(message)
|
||||
dialog.connect('response', lambda w, r: dialog.destroy())
|
||||
dialog.show()
|
||||
|
||||
def show_accounts(self):
|
||||
return AccountsView()
|
||||
|
||||
def show_open_conv_dialog(self, accounts, callback):
|
||||
data = {'account': accounts, 'server': "", 'person': ""}
|
||||
attrs = (('Account', 'account'),
|
||||
('Server', 'server'),
|
||||
('Person', 'person'))
|
||||
_open_form_dialog(_("Open Conversation"),
|
||||
self.window, data, attrs,
|
||||
(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT,
|
||||
gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT),
|
||||
lambda d: callback(**d))
|
||||
|
||||
def show_open_conf_dialog(self, accounts, callback):
|
||||
data = {'account': accounts, 'server': "", 'place': ""}
|
||||
attrs = (('Account', 'account'),
|
||||
('Server', 'server'),
|
||||
('Place', 'place'))
|
||||
_open_form_dialog(_("Open Conference"),
|
||||
self.window, data, attrs,
|
||||
(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT,
|
||||
gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT),
|
||||
lambda d: callback(**d))
|
||||
|
||||
def show_add_friend_dialog(self, accounts, callback):
|
||||
data = {'account': accounts, 'server': "", 'person': ""}
|
||||
attrs = (('Account', 'account'),
|
||||
('Server', 'server'),
|
||||
('Person', 'person'))
|
||||
_open_form_dialog(_("Add Friend"),
|
||||
self.window, data, attrs,
|
||||
(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT,
|
||||
gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT),
|
||||
lambda d: callback(**d))
|
||||
|
||||
def show_dump_win(self):
|
||||
return DumpView()
|
|
@ -0,0 +1,4 @@
|
|||
"""
|
||||
pypsyc.core
|
||||
~~~~~~~~~~~
|
||||
"""
|
|
@ -0,0 +1,158 @@
|
|||
"""
|
||||
pypsyc.core.mmp
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Classes which are related to the routing layer (called MMP).
|
||||
|
||||
:copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
from cStringIO import StringIO
|
||||
|
||||
from twisted.internet.tcp import BaseClient
|
||||
from twisted.protocols.basic import LineReceiver
|
||||
|
||||
|
||||
class Uni(str):
|
||||
"""
|
||||
This class provides some helpers for working with PSYC Uniforms.
|
||||
It can be initialized with a string or a list (:meth:`from_parts`).
|
||||
"""
|
||||
|
||||
def __init__(self, uni):
|
||||
str.__init__(uni)
|
||||
self.prefix = uni.split(':', 1)[0]
|
||||
|
||||
def into_parts(self):
|
||||
tmp = self.split(':', 1)[1]
|
||||
return tmp.strip('/').split('/')
|
||||
|
||||
@classmethod
|
||||
def from_parts(cls, parts, prefix='psyc'):
|
||||
"""
|
||||
Construct a new UNI object from a list of parts. A prefix other than
|
||||
'psyc' can be given optionally.
|
||||
|
||||
:param parts: the parts which represent the UNI's path
|
||||
:type parts: list of strings
|
||||
:param prefix: prefix of the UNI
|
||||
:type prefix: string
|
||||
"""
|
||||
uni = str.__new__(cls, '%s://%s' % (prefix, '/'.join(parts)))
|
||||
uni.prefix = prefix
|
||||
return uni
|
||||
|
||||
def chain(self, child):
|
||||
return Uni('/'.join((self.strip('/'), child)))
|
||||
|
||||
def is_descendant_of(self, other):
|
||||
"""Return whether this uni is a descendant of another."""
|
||||
other = other.strip('/')
|
||||
l = len(other)
|
||||
return self[0:l] == other and self[l] == '/'
|
||||
|
||||
def is_ancestor_of(self, other):
|
||||
"""Return whether this uni is an ancestor of another."""
|
||||
self_ = self.strip('/')
|
||||
l = len(self_)
|
||||
return other[0:l] == self_ and other[l] == '/'
|
||||
|
||||
|
||||
class Header(dict):
|
||||
"""
|
||||
A class which provides simple access to the header of a PSYC Packet.
|
||||
|
||||
.. attribute:: source
|
||||
|
||||
'_source' variable of the packet header as :class:`Uni`
|
||||
|
||||
.. attribute:: target
|
||||
|
||||
'_target' variable of the packet header as :class:`Uni`
|
||||
|
||||
.. attribute:: context
|
||||
|
||||
'_context' variable of the packet header as :class:`Uni`
|
||||
"""
|
||||
|
||||
def _init(self):
|
||||
"""Initialize the header with the set values."""
|
||||
source = self.get('_source')
|
||||
if source: self.source = Uni(source)
|
||||
else: self.source = None
|
||||
|
||||
target = self.get('_target')
|
||||
if target: self.target = Uni(target)
|
||||
else: self.target = None
|
||||
|
||||
context = self.get('_context')
|
||||
if context: self.context = Uni(context)
|
||||
else: self.context = None
|
||||
|
||||
|
||||
class Circuit(LineReceiver, object):
|
||||
"""Base class for all PSYC (MMP) Circuits."""
|
||||
delimiter = '\n'
|
||||
|
||||
def __init__(self):
|
||||
# states:
|
||||
# 0 = uninitialized
|
||||
# 1 = header
|
||||
# 2 = content
|
||||
self.state = 0
|
||||
self.vars = {'=': {}}
|
||||
self.inited = None
|
||||
|
||||
def connectionMade(self):
|
||||
if not hasattr(self, 'initiator'):
|
||||
self.initiator = isinstance(self.transport, BaseClient)
|
||||
|
||||
if self.initiator:
|
||||
self._init()
|
||||
|
||||
def _init(self):
|
||||
self.send({}, '')
|
||||
if callable(self.inited):
|
||||
self.inited()
|
||||
del self.inited
|
||||
del self.initiator
|
||||
|
||||
def lineReceived(self, line):
|
||||
if line == '|': # end of packet
|
||||
if self.state != 0: # the connection is inited already
|
||||
header = Header(self.vars['='])
|
||||
header.update(self.vars[':'])
|
||||
header._init()
|
||||
self.packet_received(header, self.content)
|
||||
elif not getattr(self, 'initiator', True):
|
||||
self._init()
|
||||
|
||||
# reset parser state
|
||||
self.state = 1
|
||||
self.vars[':'] = {}
|
||||
self.content = []
|
||||
|
||||
elif self.state == 1:
|
||||
if line == '':
|
||||
self.state = 2
|
||||
else:
|
||||
self.vars[line[0]].__setitem__(*line[1:].split('\t', 1))
|
||||
|
||||
elif self.state == 2:
|
||||
self.content.append(line)
|
||||
|
||||
def packet_received(self, header, content):
|
||||
"""Overide this in subclass."""
|
||||
raise NotImplementedError
|
||||
|
||||
def send(self, header, content):
|
||||
"""Send a packet over this circuit."""
|
||||
out = StringIO()
|
||||
out.writelines(':%s\t%s\n' % x for x in header.iteritems())
|
||||
content = '\n'.join(content)
|
||||
if content:
|
||||
out.write('\n')
|
||||
out.write(content)
|
||||
out.write('\n')
|
||||
out.write('|\n')
|
||||
self.transport.write(out.getvalue())
|
|
@ -0,0 +1,254 @@
|
|||
"""
|
||||
pypsyc.core.psyc
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Classes which are related to the entity layer.
|
||||
|
||||
:copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
from os import urandom
|
||||
|
||||
from greenlet import greenlet
|
||||
|
||||
from pypsyc.core.mmp import Header
|
||||
from pypsyc.util import Waiter
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _get_binary_arg(it, must_len, first_fragment):
|
||||
fragments = [first_fragment]
|
||||
cur_len = len(first_fragment)
|
||||
while cur_len < must_len:
|
||||
f = it.next()
|
||||
fragments.append(f)
|
||||
cur_len += len(f) + 1
|
||||
return '\n'.join(fragments)
|
||||
|
||||
class PSYCPacket(object):
|
||||
"""
|
||||
Initialize a new PSYCPacket with the given arguments.
|
||||
|
||||
:param modifiers: entity modifiers
|
||||
:type modifiers: dict
|
||||
:param mc: method
|
||||
:type mc: string or None
|
||||
:param data: data
|
||||
:type data: string
|
||||
|
||||
Attributes:
|
||||
|
||||
.. attribute:: modifiers
|
||||
|
||||
entity modifiers as described `here <http://about.psyc.eu/Spec:Packet>`_
|
||||
|
||||
.. attribute:: cvars
|
||||
|
||||
same as :attr:`modifiers`\[':'\]
|
||||
|
||||
.. attribute:: mc
|
||||
|
||||
method as described `here <http://about.psyc.eu/Spec:Packet>`_
|
||||
|
||||
.. attribute:: data
|
||||
|
||||
data as described `here <http://about.psyc.eu/Spec:Packet>`_
|
||||
"""
|
||||
|
||||
def __init__(self, modifiers=None, mc=None, data=''):
|
||||
self.modifiers = modifiers or {}
|
||||
self.cvars = self.modifiers.setdefault(':', {})
|
||||
self.mc = mc
|
||||
self.data = data
|
||||
|
||||
@classmethod
|
||||
def from_kwds(cls, mc=None, data='', **kwds):
|
||||
"""
|
||||
Return a new PSYCPacket which is initialized with mc and data given by
|
||||
arguments and cvars given by keyword arguments.
|
||||
|
||||
::
|
||||
|
||||
packet1 = PSYCPacket.from_kwds(mc='_message_public',
|
||||
data="hello", _somevar='foo')
|
||||
|
||||
packet2 = PSYCPacket(mc='_message_public', data="hello",
|
||||
modifiers={':': {'_somevar': 'foo'}})
|
||||
|
||||
packet1 == packet2 # True
|
||||
|
||||
:param mc: method
|
||||
:type mc: string or None
|
||||
:param data: data
|
||||
:type data: string
|
||||
:rtype: PSYCPacket
|
||||
"""
|
||||
return cls({':': kwds}, mc, data)
|
||||
|
||||
def render(self):
|
||||
"""Return a generator of lines which represent this packet."""
|
||||
for glyph, vars in self.modifiers.iteritems():
|
||||
for x, y in vars.iteritems():
|
||||
if '\n' in y:
|
||||
yield '%s%s %s\t%s' % (glyph, x, len(y), y)
|
||||
else:
|
||||
yield '%s%s\t%s' % (glyph, x, y)
|
||||
if self.mc:
|
||||
yield self.mc
|
||||
if self.data:
|
||||
yield self.data
|
||||
else:
|
||||
assert not self.data
|
||||
|
||||
@classmethod
|
||||
def parse(cls, content):
|
||||
"""Parse a list of lines to a packet."""
|
||||
modifiers = defaultdict(dict)
|
||||
it = iter(content)
|
||||
for line in it:
|
||||
if line.startswith('_'): # body
|
||||
return cls(modifiers, line, '\n'.join(it))
|
||||
else: # entity-modifier
|
||||
glyph = line[0]
|
||||
x, y = line[1:].split('\t', 1)
|
||||
if ' ' in x: # binary-arg
|
||||
var, l = x.split(' ')
|
||||
modifiers[glyph][var] = _get_binary_arg(it, int(l), y)
|
||||
else: # simple-arg
|
||||
modifiers[glyph][x] = y
|
||||
return cls(modifiers, None, '')
|
||||
|
||||
def __repr__(self):
|
||||
return '\n'.join((
|
||||
"PSYCPacket(",
|
||||
" modifiers={",
|
||||
",\n".join(' '*8 + "'%s': %s" % i for i in self.modifiers.items()),
|
||||
" },",
|
||||
" mc=%r,",
|
||||
" data=%r",
|
||||
")"
|
||||
)) % (self.mc, self.data)
|
||||
|
||||
def __eq__(self, other):
|
||||
return (self.modifiers == other.modifiers and
|
||||
self.mc == other.mc and
|
||||
self.data == other.data)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
|
||||
class PSYCObject(object):
|
||||
"""
|
||||
This is the base class for all classes that provide PSYC functionality,
|
||||
such as receiving and sending :class:`PSYCPacket`\s.
|
||||
|
||||
Subclassed by :class:`pypsyc.server.Entity`. Used by
|
||||
:class:`pypsyc.server.routing.ServerCircuit`.
|
||||
"""
|
||||
|
||||
def __init__(self, sendfunc, uni=None):
|
||||
self._send = sendfunc
|
||||
self.uni = uni
|
||||
self.handlers = {}
|
||||
self.tags = {}
|
||||
|
||||
def handle_packet(self, header, content):
|
||||
packet = PSYCPacket.parse(content)
|
||||
packet.header = header
|
||||
|
||||
tag_relay = header.get('_tag_relay')
|
||||
if tag_relay:
|
||||
self.tags.pop(tag_relay).callback(packet)
|
||||
return packet
|
||||
|
||||
mc = packet.mc
|
||||
while mc:
|
||||
f = self.handlers.get(mc)
|
||||
if f is not None:
|
||||
break
|
||||
mc = mc.rpartition('_')[0]
|
||||
else:
|
||||
return packet
|
||||
|
||||
tag = header.get('_tag')
|
||||
if tag is None:
|
||||
try:
|
||||
f(packet)
|
||||
except Exception:
|
||||
log.exception("error calling handler %s of %s", mc, self)
|
||||
else:
|
||||
def respond():
|
||||
try:
|
||||
response = f(packet)
|
||||
except Exception:
|
||||
log.exception("error calling handler %s of %s", mc, self)
|
||||
response = PSYCPacket(mc='_error_internal')
|
||||
self.sendmsg(header.source, response, {'_tag_relay': tag})
|
||||
greenlet(respond).switch()
|
||||
return packet
|
||||
|
||||
def sendmsg(self, target, packet=None, header=None, **kwds):
|
||||
"""
|
||||
Send a packet.
|
||||
|
||||
1.
|
||||
Simple example usage::
|
||||
|
||||
packet = PSYCPacket(mc='_message_public',
|
||||
modifiers={':': {'_somevar': 'foo'}})
|
||||
psyc.sendmsg(target, packet)
|
||||
|
||||
This simply sends the packet to the target.
|
||||
|
||||
|
||||
2.
|
||||
The following code block is equal to the previous::
|
||||
|
||||
psyc.sendmsg(target, mc='_message_public', _somevar='foo')
|
||||
|
||||
See also :meth:`PSYCPacket.from_kwds`.
|
||||
|
||||
:param target: target of the packet
|
||||
:type target: :class:`pypsyc.core.mmp.Uni` or \
|
||||
:class:`pypsyc.core.mmp.Circuit`
|
||||
:param packet: packet to send, use keyword arguments if None
|
||||
:type packet: :class:`PSYCPacket` or None
|
||||
:param header: additional header variables
|
||||
:type header: dict or None
|
||||
:rtype: PSYCPacket or None
|
||||
"""
|
||||
if packet is None:
|
||||
packet = PSYCPacket.from_kwds(**kwds)
|
||||
|
||||
header = Header() if header is None else Header(header)
|
||||
if self.uni is not None:
|
||||
header['_source'] = self.uni
|
||||
|
||||
if packet.mc and packet.mc.startswith('_request'):
|
||||
tag = urandom(20).replace('\n', '')
|
||||
header['_tag'] = tag
|
||||
waiter = self.tags[tag] = Waiter()
|
||||
else:
|
||||
waiter = None
|
||||
|
||||
if isinstance(target, str):
|
||||
header['_target'] = target
|
||||
header._init()
|
||||
self._send(header, packet.render())
|
||||
else: # the target is a circuit
|
||||
target.send(header, packet.render())
|
||||
|
||||
if waiter is not None:
|
||||
return waiter.get()
|
||||
|
||||
def add_handler(self, handler):
|
||||
self.handlers.update((i[6:], getattr(handler, i)) for i in
|
||||
dir(handler) if i.startswith('handle_'))
|
||||
|
||||
def __repr__(self):
|
||||
return '<PSYCObject uni=%s>' % self.uni
|
|
@ -0,0 +1,333 @@
|
|||
"""
|
||||
pypsyc.protocol
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Protocol classes for PSYC :class:`~pypsyc.server.Entity`\s.
|
||||
|
||||
:copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
import hmac
|
||||
from hashlib import sha256
|
||||
from itertools import izip
|
||||
from os import urandom
|
||||
|
||||
from pypsyc.core.psyc import PSYCPacket
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
pass
|
||||
|
||||
class ServerProtocol(object):
|
||||
def __init__(self, package):
|
||||
self.package = package
|
||||
|
||||
def sendmsg(self, *args, **kwds):
|
||||
return self.package.entity.sendmsg(*args, **kwds)
|
||||
|
||||
def castmsg(self, *args, **kwds):
|
||||
return self.package.entity.castmsg(*args, **kwds)
|
||||
|
||||
def state_set(self, *args, **kwds):
|
||||
return self.package.entity.context_master.state_set(*args, **kwds)
|
||||
|
||||
def state_add(self, *args, **kwds):
|
||||
return self.package.entity.context_master.state_add(*args, **kwds)
|
||||
|
||||
def state_remove(self, *args, **kwds):
|
||||
return self.package.entity.context_master.state_remove(*args, **kwds)
|
||||
|
||||
class ClientProtocol(object):
|
||||
def __init__(self, package):
|
||||
self.package = package
|
||||
|
||||
def sendmsg(self, *args, **kwds):
|
||||
return self.package.psyc.sendmsg(*args, **kwds)
|
||||
|
||||
|
||||
class UnknownTargetError(Error):
|
||||
pass
|
||||
|
||||
class DeliveryFailedError(Error):
|
||||
pass
|
||||
|
||||
def check_response(packet, default_mc):
|
||||
if packet.mc == '_error_unknown_target':
|
||||
raise UnknownTargetError(packet.cvars['_uni'])
|
||||
if packet.mc == '_failure_unsuccessful_delivery':
|
||||
raise DeliveryFailedError(packet.data, packet.cvars['_uni'])
|
||||
assert packet.mc == default_mc, "response mc is %s" % packet.mc
|
||||
|
||||
|
||||
class RoutingErrorRoot(ServerProtocol):
|
||||
def send_unknown_target_error(self, target, uni):
|
||||
self.sendmsg(target, mc='_error_unknown_target', _uni=uni)
|
||||
|
||||
def send_delivery_failed_error(self, target, uni, message):
|
||||
self.sendmsg(target, mc='_failure_unsuccessful_delivery', data=message,
|
||||
_uni=uni)
|
||||
|
||||
|
||||
class RoutingErrorRelaying(ServerProtocol):
|
||||
def handle_error_unknown_target(self, packet):
|
||||
self.package.unknown_target_error(packet.cvars['_uni'])
|
||||
|
||||
def handle_failure_unsuccessful_delivery(self, packet):
|
||||
self.package.delivery_failed_error(packet.cvars['_uni'], packet.data)
|
||||
|
||||
def relay_unknown_target_error(self, target, uni):
|
||||
self.sendmsg(target, mc='_error_unknown_target', _uni=uni)
|
||||
|
||||
def relay_delivery_failed_error(self, target, uni, message):
|
||||
self.sendmsg(target, mc='_failure_unsuccessful_delivery', data=message,
|
||||
_uni=uni)
|
||||
|
||||
|
||||
class RoutingErrorClient(ClientProtocol):
|
||||
def handle_error_unknown_target(self, packet):
|
||||
self.package.unknown_target_error(packet.cvars['_uni'])
|
||||
|
||||
def handle_failure_unsuccessful_delivery(self, packet):
|
||||
self.package.delivery_failed_error(packet.cvars['_uni'], packet.data)
|
||||
|
||||
|
||||
class AuthenticationError(Error):
|
||||
pass
|
||||
|
||||
class LinkingServer(ServerProtocol):
|
||||
def __init__(self, package):
|
||||
ServerProtocol.__init__(self, package)
|
||||
self.attempts = {}
|
||||
|
||||
def handle_request_link(self, p):
|
||||
password = p.cvars.get('_password')
|
||||
if not password:
|
||||
nonce = urandom(20)
|
||||
self.attempts[p.header.source] = (nonce, p.cvars['_resource'])
|
||||
return PSYCPacket.from_kwds(mc='_query_password', _nonce=nonce)
|
||||
|
||||
else:
|
||||
nonce, resource = self.attempts[p.header.source]
|
||||
try:
|
||||
self.package.authenticate(('hmac', password, nonce),
|
||||
p.header.source, resource)
|
||||
except AuthenticationError as e:
|
||||
return PSYCPacket(mc='_error_authentication', data=e.args[0])
|
||||
return PSYCPacket(mc='_echo_link')
|
||||
|
||||
class LinkingClient(ClientProtocol):
|
||||
def link(self, target, resource, password):
|
||||
r = self.sendmsg(target, mc='_request_link', _resource=resource)
|
||||
check_response(r, '_query_password')
|
||||
|
||||
digest = hmac.new(password, r.cvars['_nonce'], sha256).digest()
|
||||
r = self.sendmsg(target, mc='_request_link', _password=digest)
|
||||
if r.mc == '_error_authentication':
|
||||
raise AuthenticationError(r.data)
|
||||
check_response(r, '_echo_link')
|
||||
|
||||
|
||||
class Messaging(ClientProtocol):
|
||||
def send_private_message(self, target, message, relay=None):
|
||||
packet = PSYCPacket(mc='_message_private', data=message)
|
||||
if relay:
|
||||
self.sendmsg(relay, packet, {'_target_relay': target})
|
||||
else:
|
||||
self.sendmsg(target, packet)
|
||||
|
||||
def handle_message_private(self, packet):
|
||||
self.package.private_message(packet.header.source, packet.data)
|
||||
|
||||
class MessageRelaying(ServerProtocol):
|
||||
def handle_message_private(self, packet):
|
||||
target_relay = packet.header.get('_target_relay')
|
||||
if target_relay is not None:
|
||||
self.package.private_message_relay(packet.header.source,
|
||||
target_relay,
|
||||
packet.data)
|
||||
else:
|
||||
self.package.private_message(packet.header.source, packet.data)
|
||||
|
||||
def send_private_message(self, target, message):
|
||||
self.sendmsg(target, mc='_message_private', data=message)
|
||||
|
||||
def relay_private_message(self, source, target, message):
|
||||
self.sendmsg(target, header={'_source_relay': source},
|
||||
mc='_message_private', data=message)
|
||||
|
||||
|
||||
class ConferencingServer(ServerProtocol):
|
||||
def cast_member_entered(self, member, nick):
|
||||
self.state_add(_list_members=member, _list_members_nick=nick)
|
||||
|
||||
def cast_member_left(self, member):
|
||||
self.state_remove(_list_members=member)
|
||||
|
||||
def handle_message_public(self, packet):
|
||||
self.package.public_message(packet.header.source, packet.data)
|
||||
|
||||
def cast_public_message(self, source, message):
|
||||
self.castmsg(mc='_message_public', data=message, _member=source)
|
||||
|
||||
class ConferencingClient(ClientProtocol):
|
||||
def state_set_list_members(self, context, vars):
|
||||
for args in izip(vars['_list_members'].split('|'),
|
||||
vars['_list_members_nick'].split('|')):
|
||||
self.package.member_entered(context, *args)
|
||||
|
||||
def state_add_list_members(self, context, vars):
|
||||
self.package.member_entered(context, vars['_list_members'],
|
||||
vars['_list_members_nick'])
|
||||
|
||||
def state_remove_list_members(self, context, vars):
|
||||
self.package.member_left(context, vars['_list_members'])
|
||||
|
||||
def send_public_message(self, target, message):
|
||||
self.sendmsg(target, mc='_message_public', data=message)
|
||||
|
||||
def handle_message_public(self, packet):
|
||||
self.package.public_message(
|
||||
packet.header.context, packet.cvars['_member'], packet.data)
|
||||
|
||||
|
||||
class EntryDeniedError(Error):
|
||||
pass
|
||||
|
||||
class ContextMaster(ServerProtocol):
|
||||
def handle_request_context_enter(self, packet):
|
||||
try:
|
||||
state = self.package.enter_request(packet.header.source)
|
||||
except EntryDeniedError as e:
|
||||
return PSYCPacket(mc='_error_illegal_entry', data=e.args[0])
|
||||
return PSYCPacket({'=': state}, '_echo_context_enter')
|
||||
|
||||
def handle_request_context_leave(self, packet):
|
||||
self.package.leave_context(packet.header.source)
|
||||
return PSYCPacket(mc='_echo_context_leave')
|
||||
|
||||
class ContextSlave(ServerProtocol):
|
||||
def enter(self, target):
|
||||
r = self.sendmsg(target, mc='_request_context_enter')
|
||||
if r.mc == '_error_illegal_entry':
|
||||
raise EntryDeniedError(r.data)
|
||||
check_response(r, '_echo_context_enter')
|
||||
return r.modifiers.get('=')
|
||||
|
||||
def leave(self, target):
|
||||
r = self.sendmsg(target, mc='_request_context_leave')
|
||||
check_response(r, '_echo_context_leave')
|
||||
|
||||
|
||||
class UnauthorizedError(Error):
|
||||
pass
|
||||
|
||||
class ClientInterfaceServer(ServerProtocol):
|
||||
client_interface = {
|
||||
'subscribe': lambda p: (p.cvars['_group'], p.header.source),
|
||||
'unsubscribe': lambda p: (p.cvars['_group'], p.header.source),
|
||||
'presence': lambda p: (int(p.cvars['_degree_availability']),),
|
||||
'add_friend': lambda p: (p.cvars['_person'],),
|
||||
'remove_friend': lambda p: (p.cvars['_person'],)
|
||||
}
|
||||
|
||||
def handle_request_do(self, packet):
|
||||
if not packet.header.source.is_descendant_of(self.package.entity.uni):
|
||||
return PSYCPacket(mc='_error_illegal_source')
|
||||
action = packet.mc[12:]
|
||||
method = self.client_interface.get(action, None)
|
||||
try:
|
||||
getattr(self.package, 'client_' + action)(*method(packet))
|
||||
except UnknownTargetError as e:
|
||||
return PSYCPacket.from_kwds(mc='_error_unknown_target',
|
||||
_uni=e.args[0])
|
||||
except DeliveryFailedError as e:
|
||||
return PSYCPacket.from_kwds(mc='_failure_unsuccessful_delivery',
|
||||
data=e.args[0], _uni=e.args[1])
|
||||
return PSYCPacket(mc='_echo_do_' + action)
|
||||
|
||||
class ClientInterfaceClient(ClientProtocol):
|
||||
def subscribe(self, uni):
|
||||
self._request_do('subscribe', _group=uni)
|
||||
|
||||
def unsubscribe(self, uni):
|
||||
self._request_do('unsubscribe', _group=uni)
|
||||
|
||||
def cast_presence(self, availability):
|
||||
self._request_do('presence', _degree_availability=str(availability))
|
||||
|
||||
def add_friend(self, uni):
|
||||
self._request_do('add_friend', _person=uni)
|
||||
|
||||
def remove_friend(self, uni):
|
||||
self._request_do('remove_friend', _person=uni)
|
||||
|
||||
def _request_do(self, command, **kwds):
|
||||
r = self.sendmsg(self.package.uni, mc='_request_do_' + command, **kwds)
|
||||
if r.mc == '_error_illegal_source':
|
||||
raise UnauthorizedError
|
||||
check_response(r, '_echo_do_' + command)
|
||||
|
||||
|
||||
class FriendshipPendingError(Error):
|
||||
pass
|
||||
|
||||
class FriendshipEstablishedError(Error):
|
||||
pass
|
||||
|
||||
class FriendshipServer(ServerProtocol):
|
||||
def handle_request_friendship(self, packet):
|
||||
state = self.package.friendship_request(packet.header.source)
|
||||
return PSYCPacket(mc='_echo_friendship_' + state)
|
||||
|
||||
def establish(self, uni):
|
||||
r = self.sendmsg(uni, mc='_request_friendship')
|
||||
if r.mc == '_echo_friendship_pending':
|
||||
return 'pending'
|
||||
if r.mc == '_echo_friendship_established':
|
||||
return 'established'
|
||||
else:
|
||||
raise Exception("unknown mc: %s" % r.mc)
|
||||
|
||||
def handle_request_friendship_remove(self, packet):
|
||||
self.package.friendship_cancel(packet.header.source)
|
||||
return PSYCPacket(mc='_echo_friendship_removed')
|
||||
|
||||
def remove(self, uni):
|
||||
r = self.sendmsg(uni, mc='_request_friendship_remove')
|
||||
check_response(r, '_echo_friendship_removed')
|
||||
|
||||
def send_friendships(self, target, friendships):
|
||||
unis = friendships.iterkeys()
|
||||
states = friendships.itervalues()
|
||||
self.sendmsg(target, mc='_notice_list_friendships',
|
||||
_list_friendships='|'.join(unis),
|
||||
_list_friendships_state='|'.join(i['state'] for i
|
||||
in states))
|
||||
|
||||
def send_updated_friendship(self, target, uni, fs):
|
||||
self.sendmsg(target, mc='_notice_friendship_updated',
|
||||
_person=uni, _state=fs['state'])
|
||||
|
||||
def send_removed_friendship(self, target, uni):
|
||||
self.sendmsg(target, mc='_notice_friendship_removed', _person=uni)
|
||||
|
||||
def cast_presence(self, availability):
|
||||
self.state_set(_degree_availability=str(availability))
|
||||
|
||||
class FriendshipClient(ClientProtocol):
|
||||
def handle_notice_list_friendships(self, packet):
|
||||
if packet.cvars['_list_friendships'] == '':
|
||||
return
|
||||
for args in izip(packet.cvars['_list_friendships'].split('|'),
|
||||
packet.cvars['_list_friendships_state'].split('|')):
|
||||
self.package.friendship(*args)
|
||||
|
||||
def handle_notice_friendship_updated(self, packet):
|
||||
self.package.friendship(packet.cvars['_person'],
|
||||
packet.cvars['_state'])
|
||||
|
||||
def handle_notice_friendship_removed(self, packet):
|
||||
self.package.friendship_removed(packet.cvars['_person'])
|
||||
|
||||
def state_set_degree_availability(self, context, vars):
|
||||
self.package.presence(context, int(vars['_degree_availability']))
|
|
@ -0,0 +1,105 @@
|
|||
"""
|
||||
pypsyc.server
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
:copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
import signal
|
||||
|
||||
from pkg_resources import iter_entry_points
|
||||
from twisted.internet import reactor
|
||||
|
||||
from pypsyc.core.mmp import Uni, Header
|
||||
from pypsyc.core.psyc import PSYCObject, PSYCPacket
|
||||
from pypsyc.server.db import Database
|
||||
from pypsyc.server.routing import _TreeNode, Routing
|
||||
from pypsyc.server.multicast import ContextMaster
|
||||
from pypsyc.server.webif import run_webif
|
||||
from pypsyc.util import schedule
|
||||
|
||||
|
||||
class Entity(PSYCObject, _TreeNode):
|
||||
def __init__(self, parent=None, name='', server=None):
|
||||
_TreeNode.__init__(self, parent, name)
|
||||
self.server = (server or self._root.server)
|
||||
if parent is None:
|
||||
uni = Uni('psyc://%s/' % self.server.hostname)
|
||||
else:
|
||||
uni = parent.uni.chain(name)
|
||||
PSYCObject.__init__(self, self.server.routing.route_singlecast, uni)
|
||||
self.context_master = ContextMaster(self)
|
||||
self.packages = {}
|
||||
|
||||
def castmsg(self, packet=None, header=None, **kwds):
|
||||
if packet is None:
|
||||
packet = PSYCPacket.from_kwds(**kwds)
|
||||
header = Header() if header is None else Header(header)
|
||||
header['_context'] = self.uni
|
||||
header._init()
|
||||
self.server.routing.route_multicast(header, packet.render())
|
||||
|
||||
|
||||
class Server(object):
|
||||
def __init__(self, hostname, interface, psyc_port, webif_port, db_file):
|
||||
self.hostname = hostname
|
||||
self.routing = Routing(hostname, interface)
|
||||
self.root = Entity(server=self)
|
||||
self.routing.init(self.root)
|
||||
if psyc_port:
|
||||
self.routing.listen(psyc_port)
|
||||
|
||||
self.database = Database(db_file)
|
||||
schedule(self._load_entities)
|
||||
|
||||
if webif_port:
|
||||
schedule(run_webif, self, interface, webif_port, None)
|
||||
|
||||
signal.signal(signal.SIGINT, self.shutdown)
|
||||
signal.signal(signal.SIGTERM, self.shutdown)
|
||||
try:
|
||||
signal.signal(signal.SIGBREAK, self.shutdown)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def _load_entities(self):
|
||||
self.database.execute(
|
||||
'CREATE TABLE IF NOT EXISTS packages ('
|
||||
'entity TEXT, package TEXT, PRIMARY KEY (entity, package))')
|
||||
entities = self.database.fetch('SELECT entity, package FROM packages')
|
||||
for entity_name, package_name in entities:
|
||||
self._load_package(entity_name, package_name)
|
||||
|
||||
def _load_package(self, entity_name, package_name):
|
||||
if entity_name is None:
|
||||
entity = self.root
|
||||
else:
|
||||
try:
|
||||
entity = self.root.children[entity_name]
|
||||
except KeyError:
|
||||
entity = Entity(self.root, entity_name)
|
||||
|
||||
assert package_name not in entity.packages
|
||||
l = list(iter_entry_points('pypsyc.server.packages', package_name))
|
||||
assert len(l) == 1
|
||||
package = l[0].load()(entity)
|
||||
entity.packages[package_name] = package
|
||||
return package
|
||||
|
||||
def add_package(self, entity_name, package_name):
|
||||
package = self._load_package(entity_name, package_name)
|
||||
self.database.execute('INSERT INTO packages VALUES (?, ?)',
|
||||
entity_name, package_name)
|
||||
return package
|
||||
|
||||
def add_place(self, name):
|
||||
return self.add_package('@' + name, 'place')
|
||||
|
||||
def register_person(self, name, password):
|
||||
person = self.add_package('~' + name, 'person')
|
||||
person.register(password)
|
||||
return person
|
||||
|
||||
def shutdown(self, signum=None, frame=None):
|
||||
self.database.stop()
|
||||
reactor.stop()
|
|
@ -0,0 +1,105 @@
|
|||
"""
|
||||
pypsyc.server.db
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
:copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
import sqlite3
|
||||
from threading import Thread
|
||||
from Queue import Queue
|
||||
|
||||
from greenlet import getcurrent
|
||||
from twisted.internet import reactor
|
||||
|
||||
|
||||
class Database(object):
|
||||
"""
|
||||
Open a sqlite database that queues the database operations and executes
|
||||
them in a thread.
|
||||
"""
|
||||
|
||||
def __init__(self, filename):
|
||||
self.filename = filename
|
||||
self.sync = (filename == ':memory:')
|
||||
if self.sync:
|
||||
self._connect()
|
||||
else:
|
||||
self.queue = Queue()
|
||||
self.thread = Thread(target=self.run_async)
|
||||
self.thread.start()
|
||||
self.main_greenlet = getcurrent()
|
||||
|
||||
def execute(self, *args):
|
||||
"""
|
||||
Execute a query.
|
||||
|
||||
:param query: the sqlite query
|
||||
:type query: string
|
||||
:param \*params: additional query parameters
|
||||
:type \*params: one or more strings
|
||||
"""
|
||||
if self.sync:
|
||||
return self._execute(*args)
|
||||
self.queue.put(('execute', args, getcurrent()))
|
||||
assert getcurrent() is not self.main_greenlet
|
||||
return self.main_greenlet.switch()
|
||||
|
||||
def fetch(self, *args):
|
||||
"""
|
||||
Fetch a result from the database asynchronously.
|
||||
|
||||
:param query: the sqlite query
|
||||
:type query: string
|
||||
:param \*params: additional query parameters
|
||||
:type \*params: one or more strings
|
||||
:rtype: result rows
|
||||
"""
|
||||
if self.sync:
|
||||
return self._fetch(*args)
|
||||
self.queue.put(('fetch', args, getcurrent()))
|
||||
assert getcurrent() is not self.main_greenlet
|
||||
return self.main_greenlet.switch()
|
||||
|
||||
def stop(self):
|
||||
"""Close the database and stop the worker thread."""
|
||||
if self.sync:
|
||||
return self._close()
|
||||
del self.sync # make database unusable
|
||||
self.queue.put(('stop', (), None))
|
||||
self.thread.join()
|
||||
|
||||
def run_async(self):
|
||||
self._connect()
|
||||
while 1:
|
||||
cmd, args, gl = self.queue.get()
|
||||
if cmd == 'stop':
|
||||
self._close()
|
||||
break
|
||||
try:
|
||||
if cmd == 'execute':
|
||||
ret = self._execute(*args)
|
||||
elif cmd == 'fetch':
|
||||
ret = self._fetch(*args)
|
||||
except Exception, e:
|
||||
reactor.callFromThread(gl.throw, e)
|
||||
else:
|
||||
reactor.callFromThread(gl.switch, ret)
|
||||
|
||||
def _connect(self):
|
||||
self.conn = sqlite3.connect(self.filename)
|
||||
# return bytestrings instead of unicode strings
|
||||
self.conn.text_factory = str
|
||||
|
||||
def _close(self):
|
||||
self.conn.close()
|
||||
|
||||
def _execute(self, query, *params):
|
||||
cur = self.conn.cursor()
|
||||
cur.execute(query, params)
|
||||
self.conn.commit()
|
||||
|
||||
def _fetch(self, query, *params):
|
||||
cur = self.conn.cursor()
|
||||
cur.execute(query, params)
|
||||
return cur.fetchall()
|
|
@ -0,0 +1,100 @@
|
|||
"""
|
||||
pypsyc.server.multicast
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
:copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
from collections import defaultdict
|
||||
|
||||
from pypsyc.core.mmp import Header
|
||||
from pypsyc.core.psyc import PSYCPacket
|
||||
from pypsyc.protocol import ContextSlave as ContextSlaveProtocol
|
||||
|
||||
|
||||
class ContextMaster(object):
|
||||
def __init__(self, entity):
|
||||
self.state = {}
|
||||
self.list_state = defaultdict(list)
|
||||
self.entity = entity
|
||||
if hasattr(entity.server.routing.mrouting_table, '__setitem__'):
|
||||
entity.server.routing.mrouting_table[entity.uni] = set()
|
||||
|
||||
def add_member(self, uni):
|
||||
if not uni.is_descendant_of(self.entity._root.uni):
|
||||
routing = self.entity.server.routing
|
||||
circuit = routing.srouting_table[uni.into_parts()[0]]
|
||||
routing.mrouting_table[self.entity.uni].add(circuit)
|
||||
state = self.state.copy()
|
||||
state.update((k, '|'.join(v)) for k, v in self.list_state.iteritems())
|
||||
return state
|
||||
|
||||
def remove_member(self, uni):
|
||||
if not uni.is_descendant_of(self.entity._root.uni):
|
||||
routing = self.entity.server.routing
|
||||
circuit = routing.srouting_table[uni.into_parts()[0]]
|
||||
routing.mrouting_table[self.entity.uni].remove(circuit)
|
||||
|
||||
def state_set(self, **kwds):
|
||||
self.state.update(kwds)
|
||||
self.entity.castmsg(PSYCPacket({'=': kwds}))
|
||||
|
||||
def state_add(self, **kwds):
|
||||
for key, value in kwds.iteritems():
|
||||
self.list_state[key].append(value)
|
||||
self.entity.castmsg(PSYCPacket({'+': kwds}))
|
||||
|
||||
def state_remove(self, **kwds):
|
||||
for key, value in kwds.iteritems():
|
||||
idx = self.list_state[key].index(value)
|
||||
for i, j in self.list_state.items():
|
||||
if i.startswith(key):
|
||||
del j[idx]
|
||||
if not j:
|
||||
del self.list_state[i]
|
||||
self.entity.castmsg(PSYCPacket({'-': kwds}))
|
||||
|
||||
|
||||
class ContextSlave(object):
|
||||
def __init__(self, person):
|
||||
self.protocol = ContextSlaveProtocol(person)
|
||||
self.person = person
|
||||
self.subscribed_contexts = set()
|
||||
|
||||
def enter(self, context, resource=None):
|
||||
if context not in self.subscribed_contexts:
|
||||
state = self.protocol.enter(context)
|
||||
self.subscribed_contexts.add(context)
|
||||
else:
|
||||
state = None # TODO: get state from context master
|
||||
entity = self.person.entity
|
||||
table = entity.server.routing.mrouting_table.setdefault(context, set())
|
||||
resources = ([entity.children[resource.into_parts()[-1]]] if resource
|
||||
else entity.children.values())
|
||||
|
||||
table.update(resource.circuit for resource in resources)
|
||||
if state:
|
||||
header = Header({'_context': context})
|
||||
content = list(PSYCPacket({'=': state}).render())
|
||||
for resource in resources:
|
||||
resource.circuit.send(header, content)
|
||||
|
||||
def leave(self, context, resource=None):
|
||||
if context not in self.subscribed_contexts:
|
||||
return
|
||||
entity = self.person.entity
|
||||
table = entity.server.routing.mrouting_table[context]
|
||||
circuits = (r.circuit for r in entity.children.itervalues())
|
||||
if resource:
|
||||
table.remove(entity.children[resource.into_parts()[-1]].circuit)
|
||||
if table.intersection(circuits): return
|
||||
else:
|
||||
table.difference_update(circuits)
|
||||
self.protocol.leave(context)
|
||||
self.subscribed_contexts.remove(context)
|
||||
if not (table or entity._root.uni.is_ancestor_of(context)):
|
||||
del entity.server.routing.mrouting_table[context]
|
||||
|
||||
def leave_all(self):
|
||||
for i in list(self.subscribed_contexts):
|
||||
self.leave(i)
|
|
@ -0,0 +1,188 @@
|
|||
"""
|
||||
pypsyc.server.person
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
:copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
import hmac
|
||||
from hashlib import sha256
|
||||
|
||||
from pypsyc.server.multicast import ContextSlave
|
||||
from pypsyc.protocol import (RoutingErrorRelaying, LinkingServer,
|
||||
MessageRelaying, AuthenticationError, EntryDeniedError,
|
||||
ContextMaster as ContextMasterProtocol, FriendshipPendingError,
|
||||
FriendshipEstablishedError, FriendshipServer as FriendshipProtocol,
|
||||
ClientInterfaceServer as ClientInterfaceProtocol)
|
||||
from pypsyc.server import _TreeNode
|
||||
from pypsyc.util import schedule
|
||||
|
||||
|
||||
class Resource(_TreeNode):
|
||||
def __init__(self, parent, resource, circuit):
|
||||
_TreeNode.__init__(self, parent=parent, name=resource)
|
||||
self.circuit = circuit
|
||||
if hasattr(circuit, 'allowed_sources'):
|
||||
circuit.allowed_sources.append(parent.uni.chain(resource))
|
||||
|
||||
def handle_packet(self, header, content):
|
||||
self.circuit.send(header, content)
|
||||
|
||||
|
||||
class Person(object):
|
||||
def __init__(self, entity):
|
||||
self.entity = entity
|
||||
self.uni = entity.uni
|
||||
self.routing_error_relaying = RoutingErrorRelaying(self)
|
||||
entity.add_handler(self.routing_error_relaying)
|
||||
entity.add_handler(LinkingServer(self))
|
||||
self.message_relaying = MessageRelaying(self)
|
||||
entity.add_handler(self.message_relaying)
|
||||
entity.add_handler(ContextMasterProtocol(self))
|
||||
self.context_slave = ContextSlave(self)
|
||||
self.friendship_protocol = FriendshipProtocol(self)
|
||||
entity.add_handler(self.friendship_protocol)
|
||||
entity.add_handler(ClientInterfaceProtocol(self))
|
||||
|
||||
def register(self, password):
|
||||
"""
|
||||
Register this person entity with the given password. This method has to
|
||||
be called after the entity was created.
|
||||
"""
|
||||
sql = ('CREATE TABLE IF NOT EXISTS passwords ('
|
||||
'person TEXT PRIMARY KEY, password BLOB)')
|
||||
self.entity.server.database.execute(sql)
|
||||
sql = ('CREATE TABLE IF NOT EXISTS friendships ('
|
||||
'person TEXT, uni TEXT, state TEXT, PRIMARY KEY (person, uni))')
|
||||
self.entity.server.database.execute(sql)
|
||||
sql = 'INSERT INTO passwords VALUES (?, ?)'
|
||||
self.entity.server.database.execute(sql, self.uni, password)
|
||||
|
||||
def unknown_target_error(self, uni):
|
||||
for i in self.entity.children:
|
||||
self.routing_error_relaying.relay_unknown_target_error(
|
||||
self.uni.chain(i), uni)
|
||||
|
||||
def delivery_failed_error(self, uni, message):
|
||||
for i in self.entity.children:
|
||||
self.routing_error_relaying.relay_delivery_failed_error(
|
||||
self.uni.chain(i), uni, message)
|
||||
|
||||
def authenticate(self, pw, circuit, resource):
|
||||
sql = 'SELECT password FROM passwords WHERE person = ?'
|
||||
db_pass = self.entity.server.database.fetch(sql, self.uni)[0][0]
|
||||
type_, req_pass, nonce = pw
|
||||
assert type_ == 'hmac'
|
||||
if req_pass != hmac.new(db_pass, nonce, sha256).digest():
|
||||
raise AuthenticationError("Incorrect password.")
|
||||
Resource(self.entity, resource, circuit)
|
||||
self.friendship_protocol.cast_presence(7)
|
||||
schedule(self._after_authentication, self.uni.chain(resource))
|
||||
|
||||
def _after_authentication(self, uni):
|
||||
sql = 'SELECT uni, state FROM friendships WHERE person = ?'
|
||||
rows = self.entity.server.database.fetch(sql, self.uni)
|
||||
friendships = dict((i[0], {'state': i[1]}) for i in rows)
|
||||
self.friendship_protocol.send_friendships(uni, friendships)
|
||||
for context, state in rows:
|
||||
if state == 'established':
|
||||
self.context_slave.enter(context, uni)
|
||||
|
||||
def unlink(self, resource):
|
||||
del self.entity.children[resource]
|
||||
self.friendship_protocol.cast_presence(1)
|
||||
self.context_slave.leave_all()
|
||||
|
||||
def private_message_relay(self, source, target, message):
|
||||
if source.is_descendant_of(self.uni):
|
||||
self.message_relaying.send_private_message(target, message)
|
||||
|
||||
def private_message(self, source, message):
|
||||
for i in self.entity.children:
|
||||
self.message_relaying.relay_private_message(
|
||||
source, self.uni.chain(i), message)
|
||||
|
||||
def friendship_request(self, uni):
|
||||
state = self._get_friendship_state(uni)
|
||||
if state == 'none':
|
||||
self._update_friendship(uni, 'offered', True)
|
||||
return 'pending'
|
||||
elif state == 'pending':
|
||||
self._update_friendship(uni, 'established', False)
|
||||
self.context_slave.enter(uni)
|
||||
return 'established'
|
||||
elif state == 'offered':
|
||||
return 'pending'
|
||||
elif state == 'established':
|
||||
return 'established'
|
||||
else:
|
||||
raise Exception("unknown friendship state: " + state)
|
||||
|
||||
def friendship_cancel(self, uni):
|
||||
self.context_slave.leave(uni)
|
||||
self._update_friendship(uni, None)
|
||||
|
||||
def client_add_friend(self, uni):
|
||||
state = self._get_friendship_state(uni)
|
||||
if state == 'none':
|
||||
self.friendship_protocol.establish(uni)
|
||||
self._update_friendship(uni, 'pending', True)
|
||||
elif state == 'pending':
|
||||
raise FriendshipPendingError
|
||||
elif state == 'offered':
|
||||
self.friendship_protocol.establish(uni)
|
||||
self._update_friendship(uni, 'established', False)
|
||||
self.context_slave.enter(uni)
|
||||
elif state == 'established':
|
||||
raise FriendshipEstablishedError
|
||||
else:
|
||||
raise Exception("unknown friendship state: " + state)
|
||||
|
||||
def client_remove_friend(self, uni):
|
||||
self.context_slave.leave(uni)
|
||||
self.friendship_protocol.remove(uni)
|
||||
self._update_friendship(uni, None)
|
||||
|
||||
def _get_friendship_state(self, uni):
|
||||
sql = 'SELECT state FROM friendships WHERE person = ? AND uni = ?'
|
||||
ret = self.entity.server.database.fetch(sql, self.uni, uni)
|
||||
if ret:
|
||||
return ret[0][0]
|
||||
return 'none'
|
||||
|
||||
def _update_friendship(self, uni, state, insert=False):
|
||||
if state is None:
|
||||
sql = 'DELETE FROM friendships WHERE person = ? AND uni = ?'
|
||||
self.entity.server.database.execute(sql, self.uni, uni)
|
||||
elif insert:
|
||||
sql = 'INSERT INTO friendships VALUES(?, ?, ?)'
|
||||
self.entity.server.database.execute(sql, self.uni, uni, state)
|
||||
else:
|
||||
sql = ('UPDATE friendships SET state = ? '
|
||||
'WHERE person = ? AND uni = ?')
|
||||
self.entity.server.database.execute(sql, state, self.uni, uni)
|
||||
for resource in self.entity.children:
|
||||
if state:
|
||||
self.friendship_protocol.send_updated_friendship(
|
||||
self.uni.chain(resource), uni, {'state': state})
|
||||
else:
|
||||
self.friendship_protocol.send_removed_friendship(
|
||||
self.uni.chain(resource), uni)
|
||||
|
||||
def enter_request(self, uni):
|
||||
if self._get_friendship_state(uni) == 'established':
|
||||
return self.entity.context_master.add_member(uni)
|
||||
else:
|
||||
raise EntryDeniedError("You are not my friend!")
|
||||
|
||||
def leave_context(self, uni):
|
||||
self.entity.context_master.remove_member(uni)
|
||||
|
||||
def client_presence(self, availability):
|
||||
self.friendship_protocol.cast_presence(availability)
|
||||
|
||||
def client_subscribe(self, uni, resource_uni):
|
||||
self.context_slave.enter(uni, resource_uni)
|
||||
|
||||
def client_unsubscribe(self, uni, resource_uni):
|
||||
self.context_slave.leave(uni, resource_uni)
|
|
@ -0,0 +1,34 @@
|
|||
"""
|
||||
pypsyc.server.place
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
:copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
from pypsyc.protocol import (ContextMaster as ContextProtocol,
|
||||
ConferencingServer as ConferencingProtocol)
|
||||
|
||||
|
||||
class Place(object):
|
||||
def __init__(self, entity):
|
||||
self.entity = entity
|
||||
entity.add_handler(ContextProtocol(self))
|
||||
self.conferencing_protocol = ConferencingProtocol(self)
|
||||
entity.add_handler(self.conferencing_protocol)
|
||||
self.members = set()
|
||||
|
||||
def enter_request(self, uni):
|
||||
nick = uni.rsplit('/', 1)[1].lstrip('~')
|
||||
self.conferencing_protocol.cast_member_entered(uni, nick)
|
||||
self.members.add(uni)
|
||||
return self.entity.context_master.add_member(uni)
|
||||
|
||||
def leave_context(self, uni):
|
||||
self.entity.context_master.remove_member(uni)
|
||||
self.conferencing_protocol.cast_member_left(uni)
|
||||
self.members.remove(uni)
|
||||
|
||||
def public_message(self, source, message):
|
||||
member = source.rsplit('/', 1)[0]
|
||||
if member in self.members:
|
||||
self.conferencing_protocol.cast_public_message(member, message)
|
|
@ -0,0 +1,13 @@
|
|||
"""
|
||||
pypsyc.server.root
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
:copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
|
||||
|
||||
class Root(object):
|
||||
def __init__(self, entity):
|
||||
self.entity = entity
|
||||
self.server = entity.server
|
|
@ -0,0 +1,209 @@
|
|||
"""
|
||||
pypsyc.server.routing
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module defines the following classes:
|
||||
- `ServerCircuit`
|
||||
- `_TreeNode`
|
||||
- `Routing`
|
||||
|
||||
:copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
import logging
|
||||
|
||||
from twisted.internet import reactor
|
||||
from twisted.internet.protocol import Factory
|
||||
|
||||
from pypsyc.core.mmp import Circuit, Uni
|
||||
from pypsyc.core.psyc import PSYCPacket, PSYCObject
|
||||
from pypsyc.protocol import Error, check_response
|
||||
from pypsyc.util import schedule, resolve_hostname, DNSError, connect
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class InvalidTargetError(Error):
|
||||
pass
|
||||
|
||||
class InvalidSourceError(Error):
|
||||
pass
|
||||
|
||||
class ServerCircuit(Circuit):
|
||||
def __init__(self):
|
||||
Circuit.__init__(self)
|
||||
self.allowed_sources = []
|
||||
self.psyc = PSYCObject(self.send)
|
||||
self.psyc.add_handler(self)
|
||||
|
||||
def packet_received(self, header, content):
|
||||
"""Handle a packet that was received."""
|
||||
if header.context:
|
||||
if header.source:
|
||||
raise NotImplementedError
|
||||
if header.target:
|
||||
self.factory.route_singlecast(header, content)
|
||||
else:
|
||||
self.factory.route_multicast(header, content)
|
||||
|
||||
else:
|
||||
if header.source:
|
||||
if not any((header.source == i or
|
||||
header.source.is_descendant_of(i))
|
||||
for i in self.allowed_sources):
|
||||
return
|
||||
else:
|
||||
header.source = self
|
||||
|
||||
if header.target:
|
||||
self.factory.route_singlecast(header, content)
|
||||
else:
|
||||
self.psyc.handle_packet(header, content)
|
||||
|
||||
def handle_request_verification(self, packet):
|
||||
source_uni = Uni(packet.cvars['_uni_source'])
|
||||
target_uni = packet.cvars['_uni_target']
|
||||
try:
|
||||
self.factory.verify_address(self, source_uni, target_uni)
|
||||
except InvalidSourceError:
|
||||
return PSYCPacket(mc='_error_invalid_source')
|
||||
except InvalidTargetError:
|
||||
return PSYCPacket(mc='_error_invalid_target')
|
||||
self.allowed_sources.append(source_uni)
|
||||
return PSYCPacket(mc='_echo_verification')
|
||||
|
||||
def request_verification(self, source_uni, target_uni):
|
||||
r = self.psyc.sendmsg(self, mc='_request_verification',
|
||||
_uni_source=source_uni, _uni_target=target_uni)
|
||||
if r.mc == '_error_invalid_source':
|
||||
raise InvalidSourceError
|
||||
if r.mc == '_error_invalid_target':
|
||||
raise InvalidTargetError
|
||||
check_response(r, '_echo_verification')
|
||||
self.allowed_sources.append(target_uni)
|
||||
|
||||
def connectionLost(self, reason):
|
||||
for uni in self.allowed_sources:
|
||||
parts = uni.into_parts()
|
||||
if len(parts) == 1:
|
||||
del self.factory.srouting_table[parts[0]]
|
||||
else:
|
||||
entity = self.factory.root.children[parts[1]]
|
||||
entity.packages['person'].unlink(parts[2])
|
||||
|
||||
|
||||
class _TreeNode(object):
|
||||
def __init__(self, parent=None, name=''):
|
||||
self._parent = parent
|
||||
self.children = {}
|
||||
if parent:
|
||||
self._root = parent._root
|
||||
parent.children[name] = self
|
||||
else:
|
||||
self._root = self
|
||||
|
||||
|
||||
class Routing(Factory, object):
|
||||
"""This class handles routing and circuit managment."""
|
||||
protocol = ServerCircuit
|
||||
|
||||
def __init__(self, hostname, interface):
|
||||
self.hostname = hostname
|
||||
self.interface = interface
|
||||
|
||||
self.circuits = {} # ip -> circuit
|
||||
self.queues = {} # hostname -> list
|
||||
self.srouting_table = {} # hostname -> circuit
|
||||
self.mrouting_table = {} # context -> list of circuits
|
||||
|
||||
def init(self, root):
|
||||
self.root = root
|
||||
|
||||
def route_singlecast(self, header, content):
|
||||
"""Route the packet to the right target."""
|
||||
parts = header.target.into_parts()
|
||||
host = parts[0]
|
||||
if host == self.hostname:
|
||||
node = self.root
|
||||
try:
|
||||
for i in parts[1:]:
|
||||
node = node.children[i]
|
||||
except KeyError:
|
||||
self._error(header, '_error_unknown_target')
|
||||
else:
|
||||
node.handle_packet(header, content)
|
||||
elif header.get('_source'):
|
||||
try:
|
||||
self.srouting_table[host].send(header, content)
|
||||
except KeyError:
|
||||
if host not in self.queues:
|
||||
self.queues[host] = []
|
||||
schedule(self._add_route, host)
|
||||
self.queues[host].append((header, content))
|
||||
else:
|
||||
client = header.source.transport.client
|
||||
log.error("Dropped packet without _source from %s", client)
|
||||
|
||||
def _error(self, header, error_mc, message=None):
|
||||
tag = header.get('_tag')
|
||||
self.root.sendmsg(header.source, None, tag and {'_tag_relay': tag},
|
||||
mc=error_mc, _uni=header.target, data=message)
|
||||
|
||||
def route_multicast(self, header, content):
|
||||
content = list(content)
|
||||
for circuit in self.mrouting_table[header.context]:
|
||||
circuit.send(header, content)
|
||||
|
||||
def listen(self, port):
|
||||
reactor.listenTCP(port, self, interface=self.interface)
|
||||
|
||||
def _add_route(self, host):
|
||||
"""
|
||||
Add a route to host.
|
||||
|
||||
First check if we have an open connection to the target host.
|
||||
If, do address verification.
|
||||
If not, try to connect to the server and then do address verification.
|
||||
"""
|
||||
try:
|
||||
addr = resolve_hostname(host)
|
||||
except DNSError:
|
||||
self._unsuccessful_delivery(host, "Can't resolve '%s'" % host)
|
||||
return
|
||||
try:
|
||||
circuit = self.circuits[addr]
|
||||
except KeyError:
|
||||
try:
|
||||
ip, p = addr
|
||||
circuit = connect(ip, p, self, bindAddress=(self.interface, 0))
|
||||
except Exception:
|
||||
self._unsuccessful_delivery(host, "Can't connect '%s'" % host)
|
||||
return
|
||||
self.circuits[addr] = circuit
|
||||
|
||||
try:
|
||||
target_uni = Uni('psyc://%s/' % host)
|
||||
circuit.request_verification(self.root.uni, target_uni)
|
||||
except Error:
|
||||
self._unsuccessful_delivery(host, "Can't verify %s" % target_uni)
|
||||
else:
|
||||
self.srouting_table[host] = circuit
|
||||
for header, content in self.queues.pop(host):
|
||||
circuit.send(header, content)
|
||||
|
||||
def _unsuccessful_delivery(self, host, message):
|
||||
log.exception(message)
|
||||
for header, content in self.queues.pop(host):
|
||||
self._error(header, '_failure_unsuccessful_delivery', message)
|
||||
|
||||
def verify_address(self, circuit, source_uni, target_uni):
|
||||
if target_uni != self.root.uni:
|
||||
raise InvalidTargetError
|
||||
source_host = source_uni.into_parts()[0]
|
||||
if resolve_hostname(source_host)[0] != circuit.transport.client[0]:
|
||||
raise InvalidSourceError
|
||||
self.srouting_table[source_host] = circuit
|
||||
|
||||
def connection_lost(self, circuit, error):
|
||||
pass
|
|
@ -0,0 +1,117 @@
|
|||
"""
|
||||
pypsyc.server.webif
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
:copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
from collections import MutableMapping
|
||||
from logging import getLogger
|
||||
from os import urandom
|
||||
from sqlite3 import IntegrityError
|
||||
|
||||
from flask import Flask, request, flash, url_for, redirect, render_template
|
||||
from greenlet import greenlet
|
||||
from twisted.internet import reactor
|
||||
from twisted.web.server import Site
|
||||
from twisted.web.wsgi import WSGIResource
|
||||
|
||||
|
||||
log = getLogger(__name__)
|
||||
|
||||
app = Flask(__name__)
|
||||
app.jinja_env.trim_blocks = True
|
||||
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return render_template('index.html')
|
||||
|
||||
@app.route('/register', methods=['GET', 'POST'])
|
||||
def register():
|
||||
error = None
|
||||
if request.method == 'POST':
|
||||
username = request.form.get('username')
|
||||
password = request.form.get('password')
|
||||
password2 = request.form.get('password2')
|
||||
if not username:
|
||||
error = 'please specify a valid username'
|
||||
elif '~' + username in server.root.children:
|
||||
error = 'username already in use'
|
||||
elif not password or len(password) < 6:
|
||||
error = 'your password must have at least 6 characters'
|
||||
elif password != password2:
|
||||
error = 'the two passwords do not match'
|
||||
else:
|
||||
server.register_person(username, password)
|
||||
flash("you were registered")
|
||||
return redirect(url_for('index'))
|
||||
return render_template('register.html', error=error)
|
||||
|
||||
|
||||
class PersistentStore(MutableMapping):
|
||||
def __init__(self):
|
||||
server.database.execute('CREATE TABLE IF NOT EXISTS webif_store ('
|
||||
'key TEXT, value BLOB, PRIMARY KEY (key))')
|
||||
|
||||
def __getitem__(self, key):
|
||||
sql = 'SELECT value FROM webif_store WHERE key = ?'
|
||||
ret = server.database.fetch(sql, key)
|
||||
if not ret:
|
||||
raise KeyError
|
||||
return ret[0][0]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
try:
|
||||
sql = 'INSERT INTO webif_store VALUES (?, ?)'
|
||||
server.database.execute(sql, key, value)
|
||||
except IntegrityError:
|
||||
sql = 'UPDATE webif_store SET value = ? WHERE key = ?'
|
||||
server.database.execute(sql, value, key)
|
||||
|
||||
def __delitem__(self, key):
|
||||
server.database.execute('DELETE FROM webif_store WHERE key = ?', key)
|
||||
|
||||
def __iter__(self):
|
||||
sql = 'SELECT key FROM webif_store'
|
||||
return (ret[0] for ret in server.database.fetch(sql))
|
||||
|
||||
def __len__(self):
|
||||
return server.database.fetch('SELECT count(*) FROM webif_store')[0][0]
|
||||
|
||||
|
||||
class _Reactor(object):
|
||||
def callFromThread(self, f, *args, **kwds):
|
||||
f(*args, **kwds)
|
||||
|
||||
class _Threadpool(object):
|
||||
def callInThread(self, f, *args, **kwds):
|
||||
greenlet(f).switch(*args, **kwds)
|
||||
|
||||
def run_webif(server_, interface, port, context_factory):
|
||||
global server
|
||||
server = server_
|
||||
global store
|
||||
store = PersistentStore()
|
||||
try:
|
||||
app.secret_key = store['secret_key']
|
||||
except:
|
||||
app.secret_key = store['secret_key'] = urandom(20)
|
||||
assert len(app.secret_key) == 20
|
||||
|
||||
site = Site(WSGIResource(_Reactor(), _Threadpool(), app.wsgi_app))
|
||||
if context_factory is None:
|
||||
log.warning("listening on localhost because ssl is disabled")
|
||||
reactor.listenTCP(port, site, interface='localhost')
|
||||
else:
|
||||
reactor.listenSSL(port, site, context_factory, interface=interface)
|
||||
|
||||
|
||||
if __name__ == '__main__': # pragma: no cover
|
||||
from minimock import Mock
|
||||
server = Mock('server')
|
||||
server.root.children = {}
|
||||
|
||||
app.debug = True
|
||||
app.secret_key='debug key'
|
||||
app.run(port=8080)
|
|
@ -0,0 +1,6 @@
|
|||
{% extends "layout.html" %}
|
||||
{% block title %}welcome{% endblock %}
|
||||
{% block content %}
|
||||
<h1>pypsyc</h1>
|
||||
<a href="{{ url_for('register') }}">register</a>
|
||||
{% endblock %}
|
|
@ -0,0 +1,20 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>{% block title %}{% endblock %} - pypsyc</title>
|
||||
</head>
|
||||
<body>
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
<ul class=flashes>
|
||||
{% for message in messages %}
|
||||
<li>{{ message }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
<div id="content">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,23 @@
|
|||
{% extends "layout.html" %}
|
||||
{% block title %}register{% endblock %}
|
||||
{% macro input(name, type='text', label=None) %}
|
||||
<tr>
|
||||
<td align="right">{{ label or name|capitalize }}:</td>
|
||||
<td><input name="{{ name }}" type="{{ type }}" size="30"></td>
|
||||
</tr>
|
||||
{%- endmacro %}
|
||||
{% block content %}
|
||||
<h1>register your identity</h1>
|
||||
{% if error %}<p class=error><strong>Error:</strong> {{ error }}{% endif %}
|
||||
<form action="{{ url_for('register') }}" method="post">
|
||||
<table border="0" cellpadding="0" cellspacing="4">
|
||||
{{ input('username') }}
|
||||
{{ input('password', type='password') }}
|
||||
{{ input('password2', label='Confirm password', type='password') }}
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td><input type="submit" value="Register"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -0,0 +1,157 @@
|
|||
"""
|
||||
pypsyc.util
|
||||
~~~~~~~~~~~
|
||||
|
||||
:copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
import logging
|
||||
|
||||
from greenlet import greenlet, getcurrent
|
||||
from twisted.internet import reactor, error
|
||||
from twisted.internet.defer import inlineCallbacks, returnValue
|
||||
from twisted.internet.tcp import Connector
|
||||
|
||||
|
||||
scheduler = getcurrent()
|
||||
|
||||
def schedule(f, *args, **kwds):
|
||||
g = greenlet(f, scheduler)
|
||||
if getcurrent() is scheduler:
|
||||
g.switch(*args, **kwds)
|
||||
else:
|
||||
reactor.callLater(0, g.switch, *args, **kwds)
|
||||
|
||||
|
||||
class Waiter(object):
|
||||
def __init__(self):
|
||||
self.greenlet = None
|
||||
self.value = None
|
||||
self.exception = None
|
||||
|
||||
def callback(self, *args, **kwds):
|
||||
if self.greenlet is None:
|
||||
self.value, = args
|
||||
else:
|
||||
assert getcurrent() is not self.greenlet
|
||||
self.greenlet.switch(*args, **kwds)
|
||||
|
||||
def errback(self, exception):
|
||||
if self.greenlet is None:
|
||||
self.exception = exception
|
||||
else:
|
||||
self.greenlet.throw(exception)
|
||||
|
||||
def get(self):
|
||||
if self.value is not None:
|
||||
return self.value
|
||||
if self.exception is not None:
|
||||
raise self.exception
|
||||
self.greenlet = getcurrent()
|
||||
return self.greenlet.parent.switch()
|
||||
|
||||
|
||||
class DNSError(Exception):
|
||||
pass
|
||||
|
||||
@inlineCallbacks
|
||||
def _resolve_hostname(host):
|
||||
try:
|
||||
name = '_psyc._tcp.%s.' % host
|
||||
answers, auth, add = yield lookupService(name)
|
||||
|
||||
srv_rr = answers[0]
|
||||
assert srv_rr.name.name == name
|
||||
host = srv_rr.payload.target.name
|
||||
port = srv_rr.payload.port
|
||||
|
||||
a_rr = [rr for rr in add if rr.name.name == host][0]
|
||||
ip = a_rr.payload.dottedQuad()
|
||||
|
||||
except (DNSNameError, IndexError):
|
||||
try:
|
||||
ip = yield getHostByName(host)
|
||||
except DNSNameError:
|
||||
raise DNSError("Unknown host %s." % host)
|
||||
port = 4404
|
||||
|
||||
returnValue((ip, port))
|
||||
|
||||
try:
|
||||
from twisted.names.client import lookupService, getHostByName
|
||||
from twisted.names.error import DNSNameError
|
||||
except ImportError:
|
||||
log = logging.getLogger(__name__)
|
||||
log.warn("twisted names isn't installed -- DNS SRV is disabled")
|
||||
def resolve_hostname(host):
|
||||
return host, 4404
|
||||
else:
|
||||
def resolve_hostname(host):
|
||||
waiter = Waiter()
|
||||
_resolve_hostname(host).addCallbacks(waiter.callback,
|
||||
lambda f: waiter.errback(f.value))
|
||||
return waiter.get()
|
||||
|
||||
|
||||
class _PSYCConnector(Connector):
|
||||
def __init__(self, *args):
|
||||
Connector.__init__(self, *args, reactor=reactor)
|
||||
self.waiter = Waiter()
|
||||
|
||||
def connect(self):
|
||||
assert self.state == 'disconnected', "can't connect in this state"
|
||||
self.state = 'connecting'
|
||||
|
||||
self.transport = transport = self._makeTransport()
|
||||
self.timeoutID = self.reactor.callLater(self.timeout,
|
||||
transport.failIfNotConnected,
|
||||
error.TimeoutError())
|
||||
self.waiter.get()
|
||||
return self.circuit
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
self.circuit = Connector.buildProtocol(self, addr)
|
||||
self.circuit.inited = self.waiter.callback
|
||||
return self.circuit
|
||||
|
||||
def connectionFailed(self, reason):
|
||||
self.cancelTimeout()
|
||||
self.transport = None
|
||||
self.state = 'disconnected'
|
||||
self.waiter.errback(reason.value)
|
||||
|
||||
def connectionLost(self, reason):
|
||||
self.state = 'disconnected'
|
||||
self.factory.connection_lost(self.circuit, reason.value)
|
||||
|
||||
def connect(host, port, factory, timeout=30, bindAddress=None):
|
||||
return _PSYCConnector(host, port, factory, timeout, bindAddress).connect()
|
||||
|
||||
|
||||
class Event(object):
|
||||
def __init__(self):
|
||||
self.observers = []
|
||||
|
||||
def add_observer(self, observer, *args, **kwds):
|
||||
self.observers.append((observer, args, kwds))
|
||||
|
||||
def __iadd__(self, observer):
|
||||
self.observers.append((observer, (), {}))
|
||||
return self
|
||||
|
||||
def __isub__(self, observer):
|
||||
observers = [i for i in self.observers if i[0] == observer]
|
||||
assert len(observers) == 1, observers
|
||||
self.observers.remove(observers[0])
|
||||
return self
|
||||
|
||||
def __call__(self, *args, **kwds):
|
||||
for observer, args2, kwds2 in self.observers:
|
||||
kwds.update(kwds2)
|
||||
observer(*(args + args2), **kwds)
|
||||
|
||||
|
||||
def key_intersection(a, b):
|
||||
for k in b:
|
||||
if k in a:
|
||||
yield k
|
|
@ -0,0 +1,21 @@
|
|||
from setuptools import setup, find_packages
|
||||
|
||||
import pypsyc
|
||||
|
||||
setup(
|
||||
name = "pypsyc",
|
||||
version = pypsyc.__version__,
|
||||
packages = find_packages(exclude='tests'),
|
||||
package_data = {
|
||||
'pypsyc.client': ['psyc.ico'],
|
||||
'pypsyc.server.webif': ['templates/*']
|
||||
},
|
||||
zip_safe = False,
|
||||
scripts = ['bin/pypsyc', 'bin/pypsycd'],
|
||||
entry_points = '''
|
||||
[pypsyc.server.packages]
|
||||
root = pypsyc.server.root:Root
|
||||
person = pypsyc.server.person:Person
|
||||
place = pypsyc.server.place:Place
|
||||
'''
|
||||
)
|
|
@ -0,0 +1,4 @@
|
|||
import tests.helpers
|
||||
from twisted.python.log import defaultObserver, PythonLoggingObserver
|
||||
defaultObserver.stop()
|
||||
PythonLoggingObserver().start()
|
|
@ -0,0 +1,41 @@
|
|||
from pypsyc.client.model import Presence
|
||||
from pypsyc.core.mmp import Uni
|
||||
|
||||
|
||||
SERVER1 = 'server1'
|
||||
SERVER1_UNI = Uni('psyc://%s/' % SERVER1)
|
||||
USER1 = '~user1'
|
||||
USER2 = '~user2'
|
||||
USER1_UNI = SERVER1_UNI.chain(USER1)
|
||||
USER2_UNI = SERVER1_UNI.chain(USER2)
|
||||
USER1_NICK = 'user1'
|
||||
USER2_NICK = 'user2'
|
||||
RESOURCE = '*Resource'
|
||||
RESOURCE1_UNI = USER1_UNI.chain(RESOURCE)
|
||||
RESOURCE2_UNI = USER2_UNI.chain(RESOURCE)
|
||||
PLACE = '@place'
|
||||
PLACE_UNI = SERVER1_UNI.chain(PLACE)
|
||||
SERVER2 = 'server2'
|
||||
SERVER2_UNI = Uni('psyc://%s/' % SERVER2)
|
||||
SERVER3 = 'server3'
|
||||
SERVER3_UNI = Uni('psyc://%s/' % SERVER3)
|
||||
|
||||
INTERFACE = 'interface'
|
||||
IP = '10.0.0.1'
|
||||
PORT = 4404
|
||||
VARIABLES = {'_key': 'value'}
|
||||
CONTENT = ['']
|
||||
|
||||
PASSWORD = 'password'
|
||||
MESSAGE = 'message'
|
||||
UNKNOWN = 0
|
||||
OFFLINE = 1
|
||||
ONLINE = 7
|
||||
PRESENCE_UNKNOWN = Presence(UNKNOWN)
|
||||
PRESENCE_OFFLINE = Presence(OFFLINE)
|
||||
PRESENCE_ONLINE = Presence(ONLINE)
|
||||
PENDING = 'pending'
|
||||
OFFERED = 'offered'
|
||||
ESTABLISHED = 'established'
|
||||
ERROR = 'error'
|
||||
EXCEPTION = Exception(ERROR)
|
|
@ -0,0 +1,133 @@
|
|||
"""
|
||||
:copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
from functools import wraps
|
||||
from traceback import format_exc
|
||||
import sys
|
||||
|
||||
from greenlet import getcurrent
|
||||
from mock import Mock
|
||||
import twisted.internet
|
||||
|
||||
from pypsyc.core.mmp import Header
|
||||
|
||||
|
||||
# keep reactor from being installed
|
||||
|
||||
assert not 'twisted.internet.reactor' in sys.modules
|
||||
twisted.internet.reactor = sys.modules['twisted.internet.reactor'] = None
|
||||
|
||||
|
||||
# random helpers
|
||||
|
||||
def inited_header(*args, **kwds):
|
||||
header = Header(*args, **kwds)
|
||||
header._init()
|
||||
return header
|
||||
|
||||
def _make_send_function(other_circuit):
|
||||
def f(header, content):
|
||||
try:
|
||||
other_circuit.packet_received(inited_header(header), content)
|
||||
except:
|
||||
raise Exception("%s.packet_received raised exception:\n%s" %
|
||||
(other_circuit, format_exc()))
|
||||
return f
|
||||
|
||||
def connect_circuits(c1, c2):
|
||||
"""Make a virtual connection between two curcuits."""
|
||||
c1.send = c1.psyc._send = _make_send_function(c2)
|
||||
c2.send = c2.psyc._send = _make_send_function(c1)
|
||||
return c1, c2
|
||||
|
||||
|
||||
class iter_(object):
|
||||
def __init__(self, *iter_):
|
||||
self.iter_ = iter_
|
||||
|
||||
def __eq__(self, iter_):
|
||||
return tuple(iter_) == self.iter_
|
||||
|
||||
def rendered(packet):
|
||||
return iter_(*packet.render())
|
||||
|
||||
|
||||
def check_success(f):
|
||||
"""
|
||||
Wrap `f` so that it checks if :attr:`TestCase.success` is ``True``. This is
|
||||
useful to check if an asynchronous test has passed completely.
|
||||
"""
|
||||
@wraps(f)
|
||||
def _wrapper(self):
|
||||
self.success = False
|
||||
f(self)
|
||||
assert self.success
|
||||
del self.success
|
||||
return _wrapper
|
||||
|
||||
|
||||
class AsyncMethod(object):
|
||||
def __call__(self, *args, **kwds):
|
||||
self.child = getcurrent()
|
||||
return self.child.parent.switch()
|
||||
|
||||
def callback(self, *args, **kwds):
|
||||
self.child.switch(*args, **kwds)
|
||||
|
||||
def errback(self, *args, **kwds):
|
||||
self.child.throw(*args, **kwds)
|
||||
|
||||
|
||||
def mockify(obj, attrs):
|
||||
"""Replace attrs of obj with mocks. Return the original attributes."""
|
||||
originals = {}
|
||||
for attr in attrs:
|
||||
orig = originals[attr] = getattr(obj, attr)
|
||||
mock = Mock(spec=orig)
|
||||
setattr(obj, attr, mock)
|
||||
return originals
|
||||
|
||||
class mockified(object):
|
||||
"""
|
||||
Wrap a function so that it 'mockifies' and resets `obj`. If `obj` is a
|
||||
string, mockify the module named like it.
|
||||
Can also be used as a context manager.
|
||||
"""
|
||||
def __init__(self, obj, attrs):
|
||||
if isinstance(obj, basestring):
|
||||
self.obj = __import__(obj, globals(), locals(), attrs)
|
||||
else:
|
||||
self.obj = obj
|
||||
self.attrs = attrs
|
||||
|
||||
def __enter__(self):
|
||||
self.originals = mockify(self.obj, self.attrs)
|
||||
return (getattr(self.obj, attr) for attr in self.attrs)
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
for attr, original in self.originals.iteritems():
|
||||
setattr(self.obj, attr, original)
|
||||
|
||||
def __call__(self, f):
|
||||
@wraps(f)
|
||||
def _wrapper(*args, **kwds):
|
||||
with self:
|
||||
kwds.update((attr, getattr(self.obj, attr)) for attr in self.attrs)
|
||||
return f(*args, **kwds)
|
||||
return _wrapper
|
||||
|
||||
|
||||
class PlaceHolder(object):
|
||||
"""
|
||||
An object that be used as a placeholder for comparisons. The object on the
|
||||
other side of the comparison is saved as attribute `obj`.
|
||||
"""
|
||||
|
||||
def __eq__(self, other):
|
||||
self.obj = other
|
||||
return True
|
||||
|
||||
|
||||
class StubException(Exception):
|
||||
pass
|
|
@ -0,0 +1,680 @@
|
|||
"""
|
||||
:copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
from mock import Mock, MagicMock
|
||||
from nose.tools import assert_raises
|
||||
from tests.constants import (SERVER1, USER1_UNI, USER2_UNI, USER1_NICK,
|
||||
USER2_NICK, PLACE_UNI, SERVER2_UNI, MESSAGE, PRESENCE_OFFLINE,
|
||||
PRESENCE_ONLINE, ERROR, EXCEPTION)
|
||||
from tests.helpers import mockified, AsyncMethod, PlaceHolder, iter_
|
||||
|
||||
from pypsyc.client.controller import (ListBinding, AccountsController,
|
||||
ConversationController, ConferenceController, TabsController,
|
||||
FriendListController, DumpController, MainController)
|
||||
from pypsyc.client.model import Message
|
||||
from pypsyc.client.observable import ObsList, ObsDict, ObsObj, ObsAttr
|
||||
from pypsyc.util import Event
|
||||
|
||||
|
||||
def _check_obs_list(obs_list):
|
||||
assert obs_list.setitem_evt.observers == []
|
||||
assert obs_list.delitem_evt.observers == []
|
||||
assert obs_list.insert_evt.observers == []
|
||||
assert obs_list.update_evt.observers == []
|
||||
return True
|
||||
|
||||
|
||||
def _check_obs_dict(obs_dict):
|
||||
assert obs_dict.setitem_evt.observers == []
|
||||
assert obs_dict.delitem_evt.observers == []
|
||||
assert obs_dict.update_evt.observers == []
|
||||
return True
|
||||
|
||||
|
||||
def _check_obs_obj(obs_obj):
|
||||
assert obs_obj.update_evt != {}
|
||||
for evt in obs_obj.update_evt.itervalues():
|
||||
assert evt.observers == []
|
||||
return True
|
||||
|
||||
|
||||
class TestListBinding(object):
|
||||
def setup(self):
|
||||
self.model = Mock()
|
||||
self.model_list = ObsList([self.model])
|
||||
self.view_list = []
|
||||
self.make_row = Mock()
|
||||
self.binding = ListBinding(self.model_list, self.view_list,
|
||||
self.make_row)
|
||||
|
||||
def test_bind(self):
|
||||
assert self.make_row.call_args_list == [((self.model,),)]
|
||||
assert self.view_list == [self.make_row.return_value]
|
||||
|
||||
def test_add(self):
|
||||
assert self.make_row.call_args_list == [((self.model,),)]
|
||||
self.make_row.reset_mock()
|
||||
|
||||
model2 = Mock()
|
||||
self.model_list.append(model2)
|
||||
assert self.make_row.call_args_list == [((model2,),)]
|
||||
assert self.view_list == [self.make_row.return_value] * 2
|
||||
|
||||
def test_update(self):
|
||||
assert self.make_row.call_args_list == [((self.model,),)]
|
||||
self.make_row.reset_mock()
|
||||
|
||||
self.model_list.updated_item(self.model)
|
||||
assert self.make_row.call_args_list == [((self.model,),)]
|
||||
assert self.view_list == [self.make_row.return_value]
|
||||
|
||||
def test_delete(self):
|
||||
del self.model_list[0]
|
||||
assert self.view_list == []
|
||||
|
||||
def test_unbind(self):
|
||||
self.binding.unbind()
|
||||
assert _check_obs_list(self.model_list)
|
||||
assert self.view_list == []
|
||||
|
||||
|
||||
class _List(list):
|
||||
def __init__(self, *args, **kwds):
|
||||
list.__init__(self, *args, **kwds)
|
||||
self.updated_item = Mock()
|
||||
|
||||
class TestAccountsController(object):
|
||||
@mockified('pypsyc.client.controller', ['ListBinding'])
|
||||
def setup(self, ListBinding):
|
||||
self.ListBinding = ListBinding
|
||||
self.account = Mock()
|
||||
self.client = Mock()
|
||||
self.client.accounts = _List([self.account])
|
||||
self.view = Mock()
|
||||
self.controller = AccountsController(self.client, self.view)
|
||||
|
||||
def test_binding(self):
|
||||
make_row_ph = PlaceHolder()
|
||||
assert self.ListBinding.call_args_list == [
|
||||
((self.client.accounts, self.view.accounts, make_row_ph),)]
|
||||
|
||||
account = Mock()
|
||||
row = make_row_ph.obj(account)
|
||||
assert row == (account.uni, account.active)
|
||||
|
||||
@mockified('pypsyc.client.controller', ['Account'])
|
||||
def test_add_account(self, Account):
|
||||
account = Account.return_value
|
||||
callback_ph = PlaceHolder()
|
||||
|
||||
self.controller.add_account()
|
||||
assert Account.call_args_list == [
|
||||
((self.client, '', '', '', False, False),)]
|
||||
assert self.view.method_calls == [
|
||||
('show_addedit_dialog', (account.__dict__, True, callback_ph))]
|
||||
|
||||
callback_ph.obj()
|
||||
assert self.client.accounts == [self.account, account]
|
||||
assert self.client.method_calls == [('save_accounts',)]
|
||||
|
||||
def test_edit_account(self):
|
||||
callback_ph = PlaceHolder()
|
||||
|
||||
self.controller.edit_account(0)
|
||||
assert self.view.method_calls == [
|
||||
('show_addedit_dialog', (self.account.__dict__, False,
|
||||
callback_ph))]
|
||||
|
||||
callback_ph.obj()
|
||||
assert self.client.accounts.updated_item.call_args_list == [
|
||||
((self.account,),)]
|
||||
assert self.client.method_calls == [('save_accounts',)]
|
||||
|
||||
def test_remove_account(self):
|
||||
assert_raises(IndexError, self.controller.remove_account, 1)
|
||||
self.controller.remove_account(0)
|
||||
assert self.client.accounts == []
|
||||
assert self.client.method_calls == [('save_accounts',)]
|
||||
|
||||
def test_set_active(self):
|
||||
self.controller.set_active(0, True)
|
||||
assert self.account.active == True
|
||||
assert self.client.accounts.updated_item.call_args_list == [
|
||||
((self.account,),)]
|
||||
assert self.client.method_calls == [('save_accounts',)]
|
||||
|
||||
def test_closed(self):
|
||||
self.controller.closed()
|
||||
assert self.ListBinding.return_value.method_calls == [('unbind',)]
|
||||
|
||||
|
||||
IN = 'i'
|
||||
OUT = 'o'
|
||||
LINE = 'line'
|
||||
|
||||
class _StubAccount(ObsObj):
|
||||
circuit = ObsAttr('circuit')
|
||||
|
||||
class TestDumpController:
|
||||
def setup(self):
|
||||
self.circuit = Mock()
|
||||
self.circuit.dump_evt = Event()
|
||||
self.account = _StubAccount()
|
||||
self.account.uni = USER1_UNI
|
||||
self.client = Mock()
|
||||
self.client.accounts = ObsList()
|
||||
self.view = Mock()
|
||||
|
||||
def test_connected(self):
|
||||
self.account.circuit = self.circuit
|
||||
self.client.accounts.append(self.account)
|
||||
DumpController(self.client, self.view)
|
||||
|
||||
self.circuit.dump_evt(IN, LINE)
|
||||
assert self.view.method_calls == [('show_line', (IN, LINE, USER1_UNI))]
|
||||
|
||||
def test_connect(self):
|
||||
DumpController(self.client, self.view)
|
||||
self.client.accounts.append(self.account)
|
||||
self.account.circuit = self.circuit
|
||||
|
||||
self.circuit.dump_evt(IN, LINE)
|
||||
assert self.view.method_calls == [('show_line', (IN, LINE, USER1_UNI))]
|
||||
|
||||
def test_disconnect(self):
|
||||
self.account.circuit = self.circuit
|
||||
self.client.accounts.append(self.account)
|
||||
DumpController(self.client, self.view)
|
||||
|
||||
del self.account.circuit
|
||||
assert self.circuit.dump_evt.observers == []
|
||||
|
||||
def test_reconnect(self):
|
||||
DumpController(self.client, self.view)
|
||||
self.account.circuit = self.circuit
|
||||
self.client.accounts.append(self.account)
|
||||
|
||||
circuit2 = Mock()
|
||||
circuit2.dump_evt = Event()
|
||||
self.account.circuit = circuit2
|
||||
assert self.circuit.dump_evt.observers == []
|
||||
|
||||
circuit2.dump_evt(OUT, LINE)
|
||||
assert self.view.method_calls == [
|
||||
('show_line', (OUT, LINE, USER1_UNI))]
|
||||
|
||||
def test_remove(self):
|
||||
self.client.accounts.append(self.account)
|
||||
self.account.circuit = self.circuit
|
||||
DumpController(self.client, self.view)
|
||||
|
||||
self.client.accounts.remove(self.account)
|
||||
assert _check_obs_obj(self.account)
|
||||
assert self.circuit.dump_evt.observers == []
|
||||
|
||||
def test_remove_nocircuit(self):
|
||||
self.client.accounts.append(self.account)
|
||||
DumpController(self.client, self.view)
|
||||
|
||||
self.client.accounts.remove(self.account)
|
||||
assert _check_obs_obj(self.account)
|
||||
|
||||
def test_closed(self):
|
||||
self.client.accounts.append(self.account)
|
||||
self.account.circuit = self.circuit
|
||||
dump_controller = DumpController(self.client, self.view)
|
||||
|
||||
dump_controller.closed()
|
||||
assert _check_obs_list(self.client.accounts)
|
||||
assert _check_obs_obj(self.account)
|
||||
assert self.circuit.dump_evt.observers == []
|
||||
|
||||
|
||||
class TestConversationController(object):
|
||||
def setup(self):
|
||||
self.tabs_controller = Mock()
|
||||
self.conversation = Mock()
|
||||
self.conversation.messages = ObsList()
|
||||
self.conversation.unknown_target_evt = Event()
|
||||
self.conversation.delivery_failed_evt = Event()
|
||||
self.view = Mock()
|
||||
self.controller = ConversationController(self.tabs_controller,
|
||||
self.conversation, self.view)
|
||||
|
||||
def test_message(self):
|
||||
MESSAGE_OBJ = Message(USER2_UNI, MESSAGE)
|
||||
MESSAGE_LINE = "(%s) <%s> %s" % (
|
||||
MESSAGE_OBJ.time.strftime("%H:%M:%S"), USER2_UNI, MESSAGE)
|
||||
|
||||
self.conversation.messages.append(MESSAGE_OBJ)
|
||||
assert self.view.method_calls == [('show_message', (MESSAGE_LINE,))]
|
||||
|
||||
def test_unknown_target(self):
|
||||
self.conversation.unknown_target_evt()
|
||||
assert self.view.method_calls == [('show_unknown_target',)]
|
||||
|
||||
def test_delivery_failed(self):
|
||||
self.conversation.delivery_failed_evt(ERROR)
|
||||
assert self.view.method_calls == [('show_delivery_failed', (ERROR,))]
|
||||
|
||||
def test_enter_message(self):
|
||||
self.controller.enter(MESSAGE)
|
||||
assert self.conversation.method_calls == [('send_message', (MESSAGE,))]
|
||||
|
||||
def test_enter_command(self):
|
||||
self.controller.enter("/command")
|
||||
assert self.view.method_calls == [('show_unknown_command',)]
|
||||
assert self.conversation.method_calls == []
|
||||
|
||||
def test_closed(self):
|
||||
self.conversation.messages.append(Message(None, None))
|
||||
self.controller.closed()
|
||||
assert _check_obs_list(self.conversation.messages)
|
||||
assert self.conversation.unknown_target_evt.observers == []
|
||||
assert self.conversation.delivery_failed_evt.observers == []
|
||||
|
||||
|
||||
class TestConferenceController(object):
|
||||
@mockified('pypsyc.client.controller', ['ListBinding'])
|
||||
def setup(self, ListBinding):
|
||||
self.ListBinding = ListBinding
|
||||
self.tabs_controller = Mock()
|
||||
self.conference = Mock()
|
||||
self.conference.members = []
|
||||
self.conference.messages = ObsList()
|
||||
self.conference.unknown_target_evt = Event()
|
||||
self.conference.delivery_failed_evt = Event()
|
||||
self.view = Mock()
|
||||
self.controller = ConferenceController(self.tabs_controller,
|
||||
self.conference, self.view)
|
||||
|
||||
def test_binding(self):
|
||||
makerow_ph = PlaceHolder()
|
||||
assert self.ListBinding.call_args_list == [
|
||||
((self.conference.members, self.view.members, makerow_ph),)]
|
||||
|
||||
member = Mock()
|
||||
row = makerow_ph.obj(member)
|
||||
assert row == (member.uni, member.nick)
|
||||
|
||||
def test_open_conversation(self):
|
||||
member = Mock()
|
||||
self.conference.members.append(member)
|
||||
account = self.conference.conferencing.account
|
||||
conversation = account.get_conversation.return_value
|
||||
|
||||
self.controller.open_conversation(0)
|
||||
assert self.conference.conferencing.account.method_calls == [
|
||||
('get_conversation', (member.uni,))]
|
||||
assert self.tabs_controller.method_calls == [
|
||||
('focus_conversation', (conversation,))]
|
||||
|
||||
def test_closed(self):
|
||||
self.controller.closed()
|
||||
assert self.ListBinding.return_value.method_calls == [('unbind',)]
|
||||
assert _check_obs_list(self.conference.messages)
|
||||
assert self.conference.unknown_target_evt.observers == []
|
||||
assert self.conference.delivery_failed_evt.observers == []
|
||||
|
||||
|
||||
class TestTabsController(object):
|
||||
def setup(self):
|
||||
self.client = Mock()
|
||||
self.client.accounts = ObsList()
|
||||
self.view = Mock()
|
||||
|
||||
self.account = Mock()
|
||||
self.account.conversations = ObsDict()
|
||||
self.account.conferences = ObsDict()
|
||||
self.client.accounts.append(self.account)
|
||||
|
||||
@mockified('pypsyc.client.controller', ['ConversationController',
|
||||
'ConferenceController'])
|
||||
def test_deleted_account(self, ConversationController,
|
||||
ConferenceController):
|
||||
self.account.conversations[USER2_UNI] = Mock()
|
||||
self.account.conferences[PLACE_UNI] = Mock()
|
||||
conv_view = self.view.show_conversation.return_value
|
||||
conv_controller = ConversationController.return_value
|
||||
conf_view = self.view.show_conference.return_value
|
||||
conf_controller = ConferenceController.return_value
|
||||
TabsController(self.client, self.view)
|
||||
self.view.reset_mock()
|
||||
|
||||
del self.client.accounts[0]
|
||||
assert _check_obs_dict(self.account.conversations)
|
||||
assert _check_obs_dict(self.account.conferences)
|
||||
assert self.view.method_calls == [('remove_tab', (conv_view,)),
|
||||
('remove_tab', (conf_view,))]
|
||||
assert conv_controller.method_calls == [('closed',)]
|
||||
assert conf_controller.method_calls == [('closed',)]
|
||||
|
||||
@mockified('pypsyc.client.controller', ['ConversationController'])
|
||||
def test_conversation(self, ConversationController):
|
||||
conversation = Mock()
|
||||
conversation.uni = USER2_UNI
|
||||
conv_view = self.view.show_conversation.return_value
|
||||
conv_controller = ConversationController.return_value
|
||||
controller = TabsController(self.client, self.view)
|
||||
|
||||
self.account.conversations[USER2_UNI] = conversation
|
||||
assert self.view.method_calls == [('show_conversation', (USER2_UNI,))]
|
||||
assert ConversationController.call_args_list == [
|
||||
((controller, conversation, conv_view),)]
|
||||
self.view.reset_mock()
|
||||
|
||||
controller.focus_conversation(conversation)
|
||||
assert self.view.method_calls == [('focus_tab', (conv_view,))]
|
||||
self.view.reset_mock()
|
||||
|
||||
controller.close_tab(conv_view)
|
||||
assert self.account.conversations == {}
|
||||
assert self.view.method_calls == [('remove_tab', (conv_view,))]
|
||||
assert conv_controller.method_calls == [('closed',)]
|
||||
|
||||
@mockified('pypsyc.client.controller', ['ConferenceController'])
|
||||
def test_conference(self, ConferenceController):
|
||||
conference = Mock()
|
||||
conference.uni = PLACE_UNI
|
||||
conf_view = self.view.show_conference.return_value
|
||||
conf_controller = ConferenceController.return_value
|
||||
self.account.conferences[PLACE_UNI] = conference
|
||||
|
||||
controller = TabsController(self.client, self.view)
|
||||
assert self.view.method_calls == [
|
||||
('show_conference', (PLACE_UNI,),)]
|
||||
assert ConferenceController.call_args_list == [
|
||||
((controller, conference, conf_view),)]
|
||||
self.view.reset_mock()
|
||||
|
||||
controller.focus_conversation(conference)
|
||||
assert self.view.method_calls == [('focus_tab', (conf_view,))]
|
||||
self.view.reset_mock()
|
||||
|
||||
controller.close_tab(conf_view)
|
||||
assert self.account.conferences == {}
|
||||
assert self.view.method_calls == [('remove_tab', (conf_view,))]
|
||||
assert conf_controller.method_calls == [('closed',)]
|
||||
|
||||
|
||||
|
||||
class TestFriendListController(object):
|
||||
@mockified('pypsyc.client.controller', ['ListBinding'])
|
||||
def setup(self, ListBinding):
|
||||
self.ListBinding = ListBinding
|
||||
model_list_ph = PlaceHolder()
|
||||
make_row_ph = PlaceHolder()
|
||||
self.client = Mock()
|
||||
self.client.accounts = ObsList()
|
||||
self.view = Mock()
|
||||
self.tabs_controller = Mock()
|
||||
|
||||
self.controller = FriendListController(self.client, self.view,
|
||||
self.tabs_controller)
|
||||
assert self.ListBinding.call_args_list == [
|
||||
((model_list_ph, self.view.friends, make_row_ph),)]
|
||||
self.model_list = model_list_ph.obj
|
||||
self.make_row = make_row_ph.obj
|
||||
|
||||
def test_binding(self):
|
||||
EXTERN_FRIEND_UNI = SERVER2_UNI.chain('~friend')
|
||||
|
||||
friend = Mock()
|
||||
friend.account.server = SERVER1
|
||||
friend.uni = USER1_UNI
|
||||
friend.presence = PRESENCE_OFFLINE
|
||||
row = self.make_row(friend)
|
||||
assert row == (USER1_NICK, False, friend.state)
|
||||
|
||||
friend.uni = EXTERN_FRIEND_UNI
|
||||
friend.presence = PRESENCE_ONLINE
|
||||
row = self.make_row(friend)
|
||||
assert row == (EXTERN_FRIEND_UNI, True, friend.state)
|
||||
|
||||
def test_account(self):
|
||||
update_evt = Mock()
|
||||
friend1 = Mock()
|
||||
friend2 = Mock()
|
||||
account = Mock()
|
||||
account.friends = ObsDict({USER1_UNI: friend1})
|
||||
|
||||
self.model_list.update_evt += update_evt
|
||||
assert self.model_list == []
|
||||
|
||||
self.client.accounts.append(account)
|
||||
assert self.model_list == [friend1]
|
||||
|
||||
account.friends[USER2_UNI] = friend2
|
||||
assert self.model_list == [friend1, friend2]
|
||||
|
||||
account.friends.updated_item(USER2_UNI)
|
||||
assert update_evt.call_args_list == [((1, friend2),)]
|
||||
|
||||
del account.friends[USER1_UNI]
|
||||
assert self.model_list == [friend2]
|
||||
|
||||
del self.client.accounts[0]
|
||||
assert _check_obs_dict(account.friends)
|
||||
assert self.model_list == []
|
||||
|
||||
def test_open_conversation(self):
|
||||
friend = Mock()
|
||||
friend.uni = USER2_UNI
|
||||
self.model_list.append(friend)
|
||||
conversation = friend.account.get_conversation.return_value
|
||||
|
||||
self.controller.open_conversation(0)
|
||||
assert friend.account.method_calls == [
|
||||
('get_conversation', (USER2_UNI,))]
|
||||
assert self.tabs_controller.method_calls == [
|
||||
('focus_conversation', (conversation,))]
|
||||
|
||||
def test_accept_friendship(self):
|
||||
friend = Mock()
|
||||
friend.uni = USER2_UNI
|
||||
friend.account.add_friend.side_effect = AsyncMethod()
|
||||
self.model_list.append(friend)
|
||||
|
||||
self.controller.accept_friendship(0)
|
||||
assert friend.account.method_calls == [('add_friend', (USER2_UNI,))]
|
||||
|
||||
def test_cancel_friendship(self):
|
||||
friend = Mock()
|
||||
friend.uni = USER2_UNI
|
||||
friend.account.remove_friend.side_effect = AsyncMethod()
|
||||
self.model_list.append(friend)
|
||||
|
||||
self.controller.cancel_friendship(0)
|
||||
assert friend.account.method_calls == [('remove_friend', (USER2_UNI,))]
|
||||
|
||||
|
||||
class TestMainController(object):
|
||||
@mockified('pypsyc.client.controller', ['TabsController',
|
||||
'FriendListController'])
|
||||
def setup(self, TabsController, FriendListController):
|
||||
self.client = Mock()
|
||||
self.client.accounts = ObsList()
|
||||
self.view = Mock()
|
||||
self.controller = MainController(self.client, self.view)
|
||||
self.TabsController = TabsController
|
||||
self.FriendListController = FriendListController
|
||||
|
||||
def test_main_controller(self):
|
||||
tabs_controller = self.TabsController.return_value
|
||||
assert self.TabsController.call_args_list == [
|
||||
((self.client, self.view.tabs_view),)]
|
||||
assert self.FriendListController.call_args_list == [
|
||||
((self.client, self.view.friends_view, tabs_controller),)]
|
||||
|
||||
def test_no_password(self):
|
||||
account = self._make_account()
|
||||
self.client.accounts.append(account)
|
||||
callback_ph = PlaceHolder()
|
||||
update_evt = Mock()
|
||||
self.client.accounts.update_evt += update_evt
|
||||
|
||||
account.no_password_evt()
|
||||
assert self.view.method_calls == [
|
||||
('show_password_dialog', (account.uni, account.__dict__,
|
||||
callback_ph))]
|
||||
callback_ph.obj()
|
||||
assert account.active == True
|
||||
assert update_evt.call_args_list == [((0, account),)]
|
||||
assert self.client.method_calls == [('save_accounts',)]
|
||||
|
||||
del self.client.accounts[0]
|
||||
assert account.no_password_evt.observers == []
|
||||
|
||||
def test_connection_error(self):
|
||||
account = self._make_account()
|
||||
self.client.accounts.append(account)
|
||||
|
||||
account.connection_error_evt(EXCEPTION)
|
||||
assert self.view.method_calls == [
|
||||
('show_conn_error', (account.uni, ERROR))]
|
||||
|
||||
del self.client.accounts[0]
|
||||
assert account.connection_error_evt.observers == []
|
||||
|
||||
def test_no_such_user(self):
|
||||
account = self._make_account()
|
||||
self.client.accounts.append(account)
|
||||
|
||||
account.no_such_user_evt(EXCEPTION)
|
||||
assert self.view.method_calls == [('show_no_such_user', (ERROR,))]
|
||||
|
||||
del self.client.accounts[0]
|
||||
assert account.no_such_user_evt.observers == []
|
||||
|
||||
def test_auth_error(self):
|
||||
account = self._make_account()
|
||||
self.client.accounts.append(account)
|
||||
|
||||
account.auth_error_evt(EXCEPTION)
|
||||
assert self.view.method_calls == [
|
||||
('show_auth_error', (account.uni, ERROR))]
|
||||
|
||||
del self.client.accounts[0]
|
||||
assert account.auth_error_evt.observers == []
|
||||
|
||||
def _make_account(self, active=False):
|
||||
account = Mock()
|
||||
account.active = active
|
||||
account.no_password_evt = Event()
|
||||
account.connection_error_evt = Event()
|
||||
account.no_such_user_evt = Event()
|
||||
account.auth_error_evt = Event()
|
||||
return account
|
||||
|
||||
@mockified('pypsyc.client.controller', ['AccountsController'])
|
||||
def test_open_accounts(self, AccountsController):
|
||||
self.controller.open_accounts()
|
||||
assert AccountsController.call_args_list == [
|
||||
((self.client, self.view.show_accounts.return_value),)]
|
||||
|
||||
@mockified('pypsyc.client.controller', ['DumpController'])
|
||||
def test_open_dump(self, DumpController):
|
||||
self.controller.open_dump()
|
||||
assert DumpController.call_args_list == [
|
||||
((self.client, self.view.show_dump_win.return_value),)]
|
||||
|
||||
def test_open_conversation(self):
|
||||
account1 = Mock()
|
||||
account1.active = False
|
||||
account2 = Mock()
|
||||
account2.active = True
|
||||
self.client.accounts = [account1, account2]
|
||||
callback_ph = PlaceHolder()
|
||||
conversation = account2.get_conversation.return_value
|
||||
|
||||
self.controller.open_conversation()
|
||||
assert self.view.method_calls == [
|
||||
('show_open_conv_dialog', (iter_(account2.uni), callback_ph))]
|
||||
|
||||
callback_ph.obj(0, SERVER1, USER2_NICK)
|
||||
assert account2.method_calls == [('get_conversation', (USER2_UNI,))]
|
||||
assert self.TabsController.return_value.method_calls == [
|
||||
('focus_conversation', (conversation,))]
|
||||
|
||||
def test_open_conference(self):
|
||||
account1 = Mock()
|
||||
account1.active = False
|
||||
account2 = Mock()
|
||||
account2.active = True
|
||||
self.client.accounts = [account1, account2]
|
||||
callback_ph = PlaceHolder()
|
||||
conference = account2.get_conference.return_value
|
||||
|
||||
self.controller.open_conference()
|
||||
assert self.view.method_calls == [
|
||||
('show_open_conf_dialog', (iter_(account2.uni), callback_ph))]
|
||||
|
||||
callback_ph.obj(0, SERVER1, 'place')
|
||||
assert account2.method_calls == [
|
||||
('get_conference', (PLACE_UNI,), {'subscribe': True})]
|
||||
assert self.TabsController.return_value.method_calls == [
|
||||
('focus_conversation', (conference,))]
|
||||
|
||||
def test_add_friend(self):
|
||||
account1 = Mock()
|
||||
account1.active = False
|
||||
account2 = Mock()
|
||||
account2.active = True
|
||||
account2.add_friend.side_effect = AsyncMethod()
|
||||
callback_ph = PlaceHolder()
|
||||
self.client.accounts = [account1, account2]
|
||||
|
||||
self.controller.add_friend()
|
||||
assert self.view.method_calls == [
|
||||
('show_add_friend_dialog', (iter_(account2.uni), callback_ph))]
|
||||
|
||||
callback_ph.obj(0, SERVER1, USER2_NICK)
|
||||
assert account2.method_calls == [('add_friend', (USER2_UNI,))]
|
||||
|
||||
def test_show_active_accounts(self):
|
||||
account1 = self._make_account(active=True)
|
||||
account2 = self._make_account(active=True)
|
||||
|
||||
self.client.accounts.append(account1)
|
||||
assert self.view.method_calls == [('show_active_accounts', (True,))]
|
||||
self.view.reset_mock()
|
||||
|
||||
self.client.accounts.append(account2)
|
||||
assert self.view.method_calls == []
|
||||
|
||||
self.client.accounts.remove(account1)
|
||||
assert self.view.method_calls == []
|
||||
|
||||
self.client.accounts.remove(account2)
|
||||
assert self.view.method_calls == [('show_active_accounts', (False,))]
|
||||
|
||||
def test_show_active_accounts_updated(self):
|
||||
account = self._make_account(active=False)
|
||||
|
||||
self.client.accounts.append(account)
|
||||
assert self.view.method_calls == []
|
||||
|
||||
account.active = True
|
||||
self.client.accounts.updated_item(account)
|
||||
assert self.view.method_calls == [('show_active_accounts', (True,))]
|
||||
self.view.reset_mock()
|
||||
|
||||
self.client.accounts.updated_item(account)
|
||||
assert self.view.method_calls == []
|
||||
|
||||
account.active = False
|
||||
self.client.accounts.updated_item(account)
|
||||
assert self.view.method_calls == [('show_active_accounts', (False,))]
|
||||
self.view.reset_mock()
|
||||
|
||||
self.client.accounts.updated_item(account)
|
||||
assert self.view.method_calls == []
|
||||
|
||||
del self.client.accounts[0]
|
||||
assert self.view.method_calls == []
|
||||
|
||||
def test_quit(self):
|
||||
self.controller.quit()
|
||||
assert _check_obs_list(self.client.accounts)
|
||||
assert self.client.method_calls == [('quit',)]
|
|
@ -0,0 +1,563 @@
|
|||
"""
|
||||
:copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
from os import remove
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
from mock import Mock, MagicMock, sentinel
|
||||
from nose.tools import assert_raises
|
||||
from tests.constants import (SERVER1, SERVER2, USER1_UNI, USER2_UNI,
|
||||
USER1_NICK, USER2_NICK, RESOURCE, RESOURCE1_UNI, PLACE_UNI, VARIABLES,
|
||||
CONTENT, PASSWORD, MESSAGE, ONLINE, PRESENCE_UNKNOWN, PRESENCE_ONLINE,
|
||||
OFFERED, ESTABLISHED, ERROR, EXCEPTION)
|
||||
from tests.helpers import mockified, PlaceHolder, inited_header, AsyncMethod
|
||||
|
||||
from pypsyc.client.model import (Conversation, Member, Conference, Messaging,
|
||||
Conferencing, Account, FriendList, ClientCircuit, Circuit, Client)
|
||||
from pypsyc.core.psyc import PSYCPacket
|
||||
from pypsyc.protocol import (UnknownTargetError, DeliveryFailedError,
|
||||
AuthenticationError)
|
||||
|
||||
|
||||
class TestConversation(object):
|
||||
def test_send_message(self):
|
||||
message_ph = PlaceHolder()
|
||||
messaging = Mock()
|
||||
conversation = Conversation(messaging, USER2_UNI)
|
||||
|
||||
conversation.send_message(MESSAGE)
|
||||
assert messaging.protocol.method_calls == [
|
||||
('send_private_message', (USER2_UNI, MESSAGE),
|
||||
{'relay': messaging.account.uni})]
|
||||
assert conversation.messages == [message_ph]
|
||||
assert message_ph.obj.source == messaging.account.uni
|
||||
|
||||
|
||||
class TestConference(object):
|
||||
def test_send_message(self):
|
||||
conferencing = Mock()
|
||||
conference = Conference(conferencing, PLACE_UNI)
|
||||
|
||||
conference.send_message(MESSAGE)
|
||||
assert conferencing.protocol.method_calls == [
|
||||
('send_public_message', (PLACE_UNI, MESSAGE))]
|
||||
|
||||
|
||||
class TestMessaging(object):
|
||||
def test_recv_message(self):
|
||||
account = Mock()
|
||||
conversation = account.get_conversation.return_value
|
||||
conversation.messages = []
|
||||
message_ph = PlaceHolder()
|
||||
messaging = Messaging(account)
|
||||
|
||||
messaging.private_message(USER2_UNI, MESSAGE)
|
||||
assert account.get_conversation.call_args_list == [((USER2_UNI,),)]
|
||||
assert conversation.messages == [message_ph]
|
||||
assert message_ph.obj.source == USER2_UNI
|
||||
assert message_ph.obj.message == MESSAGE
|
||||
|
||||
@mockified('pypsyc.client.model', ['MessagingProtocol'])
|
||||
def test_connected(self, MessagingProtocol):
|
||||
circuit = Mock()
|
||||
messaging = Messaging(Mock())
|
||||
|
||||
messaging.connected(circuit)
|
||||
assert circuit.psyc.method_calls == [
|
||||
('add_handler', (MessagingProtocol.return_value,))]
|
||||
|
||||
def test_disconnected(self):
|
||||
Messaging(Mock).disconnected()
|
||||
|
||||
|
||||
class TestConferencing(object):
|
||||
def test_member_entered(self):
|
||||
account = Mock()
|
||||
conference = account.get_conference.return_value
|
||||
conference.members = []
|
||||
conferencing = Conferencing(account)
|
||||
|
||||
conferencing.member_entered(PLACE_UNI, USER2_UNI, USER2_NICK)
|
||||
assert account.get_conference.call_args_list == [((PLACE_UNI,),)]
|
||||
assert conference.members == [Member(USER2_UNI, USER2_NICK)]
|
||||
|
||||
def test_member_left(self):
|
||||
account = Mock()
|
||||
conference = account.get_conference.return_value
|
||||
conference.members = [Member(USER1_UNI, USER1_NICK),
|
||||
Member(USER2_UNI, USER2_NICK)]
|
||||
conferencing = Conferencing(account)
|
||||
|
||||
conferencing.member_left(PLACE_UNI, USER2_UNI)
|
||||
assert conference.members == [Member(USER1_UNI, USER1_NICK)]
|
||||
|
||||
def test_recv_message(self):
|
||||
account = Mock()
|
||||
conference = account.get_conference.return_value
|
||||
conference.messages = []
|
||||
message_ph = PlaceHolder()
|
||||
conferencing = Conferencing(account)
|
||||
|
||||
conferencing.public_message(PLACE_UNI, USER2_UNI, MESSAGE)
|
||||
assert account.get_conference.call_args_list == [((PLACE_UNI,),)]
|
||||
assert conference.messages == [message_ph]
|
||||
assert message_ph.obj.source == USER2_UNI
|
||||
assert message_ph.obj.message == MESSAGE
|
||||
|
||||
@mockified('pypsyc.client.model', ['ConferencingProtocol'])
|
||||
def test_connected(self, ConferencingProtocol):
|
||||
circuit = Mock()
|
||||
conferencing = Conferencing(Mock())
|
||||
|
||||
conferencing.connected(circuit)
|
||||
assert circuit.method_calls == [
|
||||
('psyc.add_handler', (ConferencingProtocol.return_value,)),
|
||||
('add_pvar_handler', (ConferencingProtocol.return_value,))]
|
||||
|
||||
def test_disconnected(self):
|
||||
Conferencing(Mock()).disconnected()
|
||||
|
||||
|
||||
class _Dict(dict):
|
||||
def __init__(self, *args, **kwds):
|
||||
dict.__init__(self, *args, **kwds)
|
||||
self.updated_item = Mock()
|
||||
|
||||
class TestFriendList(object):
|
||||
def test_friendship(self):
|
||||
friend_ph = PlaceHolder()
|
||||
account = Mock()
|
||||
account.friends = _Dict()
|
||||
friend_list = FriendList(account)
|
||||
|
||||
friend_list.friendship(USER1_UNI, OFFERED)
|
||||
assert account.friends == {USER1_UNI: friend_ph}
|
||||
friend = friend_ph.obj
|
||||
assert friend.account == account
|
||||
assert friend.uni == USER1_UNI
|
||||
assert friend.presence == PRESENCE_UNKNOWN
|
||||
assert friend.state == OFFERED
|
||||
|
||||
friend_list.friendship(USER1_UNI, ESTABLISHED)
|
||||
assert friend.state == ESTABLISHED
|
||||
assert account.friends.updated_item.call_args_list == [((USER1_UNI,),)]
|
||||
|
||||
friend_list.friendship_removed(USER1_UNI)
|
||||
assert account.friends == {}
|
||||
|
||||
def test_presence(self):
|
||||
account = Mock()
|
||||
account.friends = _Dict({USER2_UNI: Mock()})
|
||||
friend_list = FriendList(account)
|
||||
|
||||
friend_list.presence(USER2_UNI, ONLINE)
|
||||
assert account.friends[USER2_UNI].presence == PRESENCE_ONLINE
|
||||
assert account.friends.updated_item.call_args_list == [((USER2_UNI,),)]
|
||||
|
||||
@mockified('pypsyc.client.model', ['FriendshipProtocol'])
|
||||
def test_connected(self, FriendshipProtocol):
|
||||
circuit = Mock()
|
||||
friend_list = FriendList(Mock())
|
||||
|
||||
friend_list.connected(circuit)
|
||||
assert circuit.method_calls == [
|
||||
('psyc.add_handler', (FriendshipProtocol.return_value,)),
|
||||
('add_pvar_handler', (FriendshipProtocol.return_value,))]
|
||||
|
||||
def test_disconnected(self):
|
||||
account = Mock()
|
||||
account.friends = _Dict({USER2_UNI: Mock()})
|
||||
friend_list = FriendList(account)
|
||||
|
||||
friend_list.disconnected()
|
||||
assert account.friends == {}
|
||||
|
||||
|
||||
class TestAccount(object):
|
||||
@mockified('pypsyc.client.model', ['ObsDict'])
|
||||
def test_account(self, ObsDict):
|
||||
obs_dict = ObsDict.return_value = MagicMock()
|
||||
account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False)
|
||||
assert account.uni == USER1_UNI
|
||||
assert ObsDict.call_args_list == [(), (), ()]
|
||||
assert account.conversations == obs_dict
|
||||
assert account.conferences == obs_dict
|
||||
assert account.friends == obs_dict
|
||||
|
||||
@mockified('pypsyc.client.model', ['Messaging', 'Conversation'])
|
||||
def test_get_conversation(self, Messaging, Conversation):
|
||||
messaging = Messaging.return_value
|
||||
conversation = Conversation.return_value
|
||||
account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False)
|
||||
assert Messaging.call_args_list == [((account,),)]
|
||||
|
||||
assert account.get_conversation(USER2_UNI) == conversation
|
||||
assert account.get_conversation(USER2_UNI) == conversation
|
||||
assert Conversation.call_args_list == [((messaging, USER2_UNI),)]
|
||||
assert account.conversations == {USER2_UNI: conversation}
|
||||
|
||||
@mockified('pypsyc.client.model', ['Conferencing', 'Conference'])
|
||||
def test_get_conference(self, Conferencing, Conference):
|
||||
conferencing = Conferencing.return_value
|
||||
conference = Conference.return_value
|
||||
account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False)
|
||||
assert Conferencing.call_args_list == [((account,),)]
|
||||
|
||||
assert account.get_conference(PLACE_UNI) == conference
|
||||
assert account.get_conference(PLACE_UNI) == conference
|
||||
assert Conference.call_args_list == [((conferencing, PLACE_UNI),)]
|
||||
assert account.conferences == {PLACE_UNI: conference}
|
||||
|
||||
@mockified('pypsyc.client.model', ['Conferencing', 'Conference'])
|
||||
def test_get_conference_subscribe(self, Conferencing, Conference):
|
||||
conferencing = Conferencing.return_value
|
||||
conference = Conference.return_value
|
||||
account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False)
|
||||
client_interface = account.client_interface = Mock()
|
||||
client_interface.subscribe.side_effect = AsyncMethod()
|
||||
|
||||
assert account.get_conference(PLACE_UNI, subscribe=True) == conference
|
||||
assert account.get_conference(PLACE_UNI, subscribe=True) == conference
|
||||
assert Conference.call_args_list == [((conferencing, PLACE_UNI),)]
|
||||
assert account.conferences == {PLACE_UNI: conference}
|
||||
assert client_interface.method_calls == [('subscribe', (PLACE_UNI,))]
|
||||
|
||||
@mockified('pypsyc.client.model', ['Conference'])
|
||||
def test_get_conference_subscribe_unknown_target(self, Conference):
|
||||
conference = Conference.return_value
|
||||
account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False)
|
||||
client_interface = account.client_interface = Mock()
|
||||
client_interface.subscribe.side_effect = UnknownTargetError
|
||||
|
||||
account.get_conference(PLACE_UNI, subscribe=True)
|
||||
assert conference.method_calls == [('unknown_target_evt',)]
|
||||
|
||||
@mockified('pypsyc.client.model', ['Conference'])
|
||||
def test_get_conference_subscribe_delivery_failed(self, Conference):
|
||||
conference = Conference.return_value
|
||||
account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False)
|
||||
client_interface = account.client_interface = Mock()
|
||||
client_interface.subscribe.side_effect = DeliveryFailedError(ERROR)
|
||||
|
||||
account.get_conference(PLACE_UNI, subscribe=True)
|
||||
assert conference.method_calls == [('delivery_failed_evt', (ERROR,))]
|
||||
|
||||
def test_remove_conference(self):
|
||||
account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False)
|
||||
account.conferences[PLACE_UNI] = Mock()
|
||||
client_interface = account.client_interface = Mock()
|
||||
client_interface.unsubscribe.side_effect = AsyncMethod()
|
||||
|
||||
del account.conferences[PLACE_UNI]
|
||||
assert client_interface.method_calls == [('unsubscribe', (PLACE_UNI,))]
|
||||
|
||||
def test_add_friend(self):
|
||||
account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False)
|
||||
client_interface = account.client_interface = Mock()
|
||||
|
||||
account.add_friend(USER2_UNI)
|
||||
assert client_interface.method_calls == [('add_friend', (USER2_UNI,))]
|
||||
|
||||
def test_remove_friend(self):
|
||||
account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False)
|
||||
client_interface = account.client_interface = Mock()
|
||||
|
||||
account.remove_friend(USER2_UNI)
|
||||
assert client_interface.method_calls == [
|
||||
('remove_friend', (USER2_UNI,))]
|
||||
|
||||
@mockified('pypsyc.client.model', ['FriendList'])
|
||||
def test_friendlist(self, FriendList):
|
||||
account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False)
|
||||
assert FriendList.call_args_list == [((account,),)]
|
||||
|
||||
def test_unknown_target_error(self):
|
||||
account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False)
|
||||
account.conversations[USER1_UNI] = conversation = Mock()
|
||||
account.conferences[PLACE_UNI] = conference = Mock()
|
||||
|
||||
account.unknown_target_error(USER1_UNI)
|
||||
account.unknown_target_error(USER2_UNI)
|
||||
account.unknown_target_error(PLACE_UNI)
|
||||
assert conversation.method_calls == [('unknown_target_evt',)]
|
||||
assert conference.method_calls == [('unknown_target_evt',)]
|
||||
|
||||
def test_delivery_failed_error(self):
|
||||
account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False)
|
||||
account.conversations[USER1_UNI] = conversation = Mock()
|
||||
account.conferences[PLACE_UNI] = conference = Mock()
|
||||
|
||||
account.delivery_failed_error(USER1_UNI, ERROR)
|
||||
account.delivery_failed_error(USER2_UNI, ERROR)
|
||||
account.delivery_failed_error(PLACE_UNI, ERROR)
|
||||
assert conversation.method_calls == [('delivery_failed_evt', (ERROR,))]
|
||||
assert conference.method_calls == [('delivery_failed_evt', (ERROR,))]
|
||||
|
||||
@mockified('pypsyc.client.model', ['resolve_hostname', 'connect',
|
||||
'LinkingProtocol',
|
||||
'RoutingErrorProtocol',
|
||||
'ClientInterfaceProtocol', 'Messaging',
|
||||
'Conferencing', 'FriendList'])
|
||||
def test_active(self, resolve_hostname, connect, LinkingProtocol,
|
||||
RoutingErrorProtocol, ClientInterfaceProtocol, Messaging,
|
||||
Conferencing, FriendList):
|
||||
resolve_hostname.return_value = (sentinel.ip, sentinel.port)
|
||||
circuit = connect.return_value
|
||||
linking_protocol = LinkingProtocol.return_value
|
||||
linked = linking_protocol.link.side_effect = AsyncMethod()
|
||||
messaging = Messaging.return_value
|
||||
conferencing = Conferencing.return_value
|
||||
friend_list = FriendList.return_value
|
||||
client = Mock()
|
||||
account = Account(client, SERVER1, USER1_NICK, PASSWORD, True, False)
|
||||
|
||||
account.active = True
|
||||
account.active = True
|
||||
assert resolve_hostname.call_args_list == [((account.server,),)]
|
||||
assert connect.call_args_list == [
|
||||
((sentinel.ip, sentinel.port, client),)]
|
||||
assert LinkingProtocol.call_args_list == [((circuit,),)]
|
||||
assert linking_protocol.method_calls == [
|
||||
('link', (USER1_UNI, RESOURCE, PASSWORD))]
|
||||
|
||||
linked.callback()
|
||||
assert circuit.psyc.uni == RESOURCE1_UNI
|
||||
assert RoutingErrorProtocol.call_args_list == [((account,),)]
|
||||
assert circuit.psyc.method_calls == [
|
||||
('add_handler', (RoutingErrorProtocol.return_value,))]
|
||||
assert ClientInterfaceProtocol.call_args_list == [((account,),)]
|
||||
assert messaging.method_calls == [('connected', (circuit,))]
|
||||
assert conferencing.method_calls == [('connected', (circuit,))]
|
||||
assert friend_list.method_calls == [('connected', (circuit,))]
|
||||
messaging.reset_mock()
|
||||
conferencing.reset_mock()
|
||||
friend_list.reset_mock()
|
||||
|
||||
account.active = False
|
||||
account.active = False
|
||||
assert circuit.transport.method_calls == [('loseConnection',)]
|
||||
assert not hasattr(account, 'circuit')
|
||||
assert messaging.method_calls == [('disconnected',)]
|
||||
assert conferencing.method_calls == [('disconnected',)]
|
||||
assert friend_list.method_calls == [('disconnected',)]
|
||||
|
||||
@mockified('pypsyc.client.model', ['resolve_hostname'])
|
||||
def test_active_no_password(self, resolve_hostname):
|
||||
no_password_evt = Mock()
|
||||
account = Account(None, SERVER1, USER1_NICK, '', False, False)
|
||||
account.no_password_evt += no_password_evt
|
||||
|
||||
account.active = True
|
||||
assert account.active == False
|
||||
assert no_password_evt.call_args_list == [()]
|
||||
assert resolve_hostname.call_args_list == []
|
||||
|
||||
@mockified('pypsyc.client.model', ['resolve_hostname', 'connect'])
|
||||
def test_active_dns_error(self, resolve_hostname, connect):
|
||||
resolve_hostname.side_effect = EXCEPTION
|
||||
client = Mock()
|
||||
connection_error_evt = Mock()
|
||||
account = Account(client, SERVER1, USER1_NICK, PASSWORD, False, False)
|
||||
account.connection_error_evt += connection_error_evt
|
||||
|
||||
account.active = True
|
||||
assert account.active == False
|
||||
assert client.accounts.updated_item.call_args_list == [((account,),)]
|
||||
assert connection_error_evt.call_args_list == [((EXCEPTION,),)]
|
||||
assert connect.call_args_list == []
|
||||
|
||||
@mockified('pypsyc.client.model', ['resolve_hostname', 'connect',
|
||||
'LinkingProtocol'])
|
||||
def test_active_connect_error(self, resolve_hostname, connect,
|
||||
LinkingProtocol):
|
||||
client = Mock()
|
||||
resolve_hostname.return_value = (sentinel.ip, sentinel.port)
|
||||
connect.side_effect = EXCEPTION
|
||||
connection_error_evt = Mock()
|
||||
account = Account(client, SERVER1, USER1_NICK, PASSWORD, False, False)
|
||||
account.connection_error_evt += connection_error_evt
|
||||
|
||||
account.active = True
|
||||
assert account.active == False
|
||||
assert client.accounts.updated_item.call_args_list == [((account,),)]
|
||||
assert connection_error_evt.call_args_list == [((EXCEPTION,),)]
|
||||
assert LinkingProtocol.call_args_list == []
|
||||
|
||||
@mockified('pypsyc.client.model', ['resolve_hostname', 'connect',
|
||||
'LinkingProtocol'])
|
||||
def test_active_no_such_user(self, resolve_hostname, connect,
|
||||
LinkingProtocol):
|
||||
ERROR = UnknownTargetError()
|
||||
client = Mock()
|
||||
resolve_hostname.return_value = (sentinel.ip, sentinel.port)
|
||||
circuit = connect.return_value
|
||||
LinkingProtocol.return_value.link.side_effect = ERROR
|
||||
no_such_user_evt = Mock()
|
||||
account = Account(client, SERVER1, USER1_NICK, PASSWORD, False, False)
|
||||
account.no_such_user_evt += no_such_user_evt
|
||||
|
||||
account.active = True
|
||||
assert account.active == False
|
||||
assert client.accounts.updated_item.call_args_list == [((account,),)]
|
||||
assert no_such_user_evt.call_args_list == [((ERROR,),)]
|
||||
assert circuit.psyc.uni != account.uni.chain(account.resource)
|
||||
|
||||
@mockified('pypsyc.client.model', ['resolve_hostname', 'connect',
|
||||
'LinkingProtocol'])
|
||||
def test_active_incorrect_password(self, resolve_hostname, connect,
|
||||
LinkingProtocol):
|
||||
ERROR = AuthenticationError()
|
||||
client = Mock()
|
||||
resolve_hostname.return_value = (sentinel.ip, sentinel.port)
|
||||
circuit = connect.return_value
|
||||
LinkingProtocol.return_value.link.side_effect = ERROR
|
||||
auth_error_evt = Mock()
|
||||
account = Account(client, SERVER1, USER1_NICK, 'wrong', False, False)
|
||||
account.auth_error_evt += auth_error_evt
|
||||
|
||||
account.active = True
|
||||
assert account.active == False
|
||||
assert client.accounts.updated_item.call_args_list == [((account,),)]
|
||||
assert auth_error_evt.call_args_list == [((ERROR,),)]
|
||||
assert circuit.psyc.uni != account.uni.chain(account.resource)
|
||||
|
||||
def test_connection_error(self):
|
||||
client = Mock()
|
||||
connection_error_evt = Mock()
|
||||
account = Account(client, SERVER1, USER1_NICK, PASSWORD, False, False)
|
||||
account.connection_error_evt += connection_error_evt
|
||||
|
||||
account.connection_error(EXCEPTION)
|
||||
assert account.active == False
|
||||
assert client.accounts.updated_item.call_args_list == [((account,),)]
|
||||
assert connection_error_evt.call_args_list == [((EXCEPTION,),)]
|
||||
|
||||
|
||||
class TestClientCircuit(object):
|
||||
@mockified(Circuit, ['connectionMade'])
|
||||
def test_dump_out(self, connectionMade):
|
||||
LINE1 = ':_target\t' + USER2_UNI
|
||||
LINE2 = '|'
|
||||
|
||||
cc = ClientCircuit()
|
||||
dump_evt = Mock()
|
||||
cc.dump_evt += dump_evt
|
||||
cc.transport = Mock()
|
||||
orig_write = cc.transport.write
|
||||
|
||||
cc.connectionMade()
|
||||
assert connectionMade.call_args_list == [((cc,),)]
|
||||
|
||||
cc.send({'_target': USER2_UNI}, '')
|
||||
assert dump_evt.call_args_list == [(('o', LINE1),), (('o', LINE2),)]
|
||||
assert orig_write.call_args_list == [((LINE1+'\n'+LINE2+'\n',),)]
|
||||
|
||||
@mockified(Circuit, ['lineReceived'])
|
||||
def test_dump_in(self, lineReceived):
|
||||
lineReceived.return_value = None
|
||||
cc = ClientCircuit()
|
||||
dump_evt = Mock()
|
||||
cc.dump_evt += dump_evt
|
||||
|
||||
cc.dataReceived('a\nb\n')
|
||||
assert dump_evt.call_args_list == [(('i', 'a'),), (('i', 'b'),)]
|
||||
assert lineReceived.call_args_list == [((cc, 'a'),), ((cc, 'b'),)]
|
||||
|
||||
@mockified('pypsyc.client.model', ['PSYCObject'])
|
||||
def test_packet_received(self, PSYCObject):
|
||||
psyc = PSYCObject.return_value
|
||||
psyc.handle_packet.return_value = PSYCPacket()
|
||||
|
||||
cc = ClientCircuit()
|
||||
assert PSYCObject.call_args_list == [((cc.send,),)]
|
||||
|
||||
cc.packet_received({}, CONTENT)
|
||||
assert psyc.method_calls == [('handle_packet', ({}, CONTENT))]
|
||||
|
||||
@mockified('pypsyc.client.model', ['PSYCObject'])
|
||||
def test_relay(self, PSYCObject):
|
||||
psyc = PSYCObject.return_value
|
||||
psyc.uni = RESOURCE1_UNI
|
||||
psyc.handle_packet.return_value = PSYCPacket()
|
||||
cc = ClientCircuit()
|
||||
|
||||
header = inited_header(_source=USER1_UNI, _source_relay=USER2_UNI)
|
||||
cc.packet_received(header, CONTENT)
|
||||
assert header.source == USER2_UNI
|
||||
|
||||
header2 = inited_header(_source=USER2_UNI, _source_relay=USER2_UNI)
|
||||
assert_raises(AssertionError, cc.packet_received, header2, CONTENT)
|
||||
|
||||
@mockified('pypsyc.client.model', ['PSYCObject'])
|
||||
def test_persistent(self, PSYCObject):
|
||||
HEADER = {'_context': USER2_UNI}
|
||||
packet = PSYCPacket({'=': VARIABLES, '+': VARIABLES, '-': VARIABLES})
|
||||
packet.header = inited_header(HEADER)
|
||||
psyc = PSYCObject.return_value
|
||||
psyc.handle_packet.return_value = packet
|
||||
cc = ClientCircuit()
|
||||
|
||||
handler = Mock()
|
||||
state_set_key = handler.state_set_key = Mock()
|
||||
state_add_key = handler.state_add_key = Mock()
|
||||
state_remove_key = handler.state_remove_key = Mock()
|
||||
cc.add_pvar_handler(handler)
|
||||
|
||||
cc.packet_received(HEADER, CONTENT)
|
||||
assert psyc.method_calls == [('handle_packet', (HEADER, CONTENT))]
|
||||
assert state_set_key.call_args_list == [((USER2_UNI, VARIABLES),)]
|
||||
assert state_add_key.call_args_list == [((USER2_UNI, VARIABLES),)]
|
||||
assert state_remove_key.call_args_list == [((USER2_UNI, VARIABLES),)]
|
||||
|
||||
|
||||
class TestClient(object):
|
||||
@mockified('pypsyc.client.model', ['Account'])
|
||||
def test_accounts(self, Account):
|
||||
account = Account.return_value
|
||||
error_ph = PlaceHolder()
|
||||
|
||||
account1 = Mock()
|
||||
account1.server = SERVER1
|
||||
account1.person = USER1_NICK
|
||||
account1.password = PASSWORD
|
||||
account1.save_password = True
|
||||
account1.active = False
|
||||
account2 = Mock()
|
||||
account2.server = SERVER2
|
||||
account2.person = USER2_NICK
|
||||
account2.password = PASSWORD
|
||||
account2.save_password = False
|
||||
account2.active = True
|
||||
|
||||
try:
|
||||
with NamedTemporaryFile(delete=False) as f:
|
||||
f.write('[]')
|
||||
|
||||
client = Client(accounts_file=f.name)
|
||||
assert client.accounts == []
|
||||
client.accounts = [account1, account2]
|
||||
client.save_accounts()
|
||||
|
||||
client = Client(accounts_file=f.name)
|
||||
client.load_accounts()
|
||||
assert client.accounts == [Account.return_value] * 2
|
||||
assert Account.call_args_list == [
|
||||
((client, SERVER1, USER1_NICK, PASSWORD, True, False),),
|
||||
((client, SERVER2, USER2_NICK, '', False, True),)]
|
||||
finally:
|
||||
remove(f.name)
|
||||
|
||||
client.connection_lost(account.circuit, sentinel.error)
|
||||
assert account.method_calls == [('connection_error', (error_ph,))] * 2
|
||||
assert error_ph.obj.args[0] == str(sentinel.error)
|
||||
|
||||
@mockified('pypsyc.client.model', ['reactor'])
|
||||
def test_quit(self, reactor):
|
||||
account = Mock()
|
||||
client = Client()
|
||||
client.accounts = [account]
|
||||
|
||||
client.quit()
|
||||
assert account.active == False
|
||||
assert reactor.method_calls == [('stop',)]
|
|
@ -0,0 +1,173 @@
|
|||
"""
|
||||
:copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
from mock import Mock
|
||||
from nose.tools import assert_raises
|
||||
|
||||
from pypsyc.client.observable import ObsAttr, ObsObj, ObsList, ObsDict
|
||||
|
||||
|
||||
class StubObsObj(ObsObj):
|
||||
foo = ObsAttr('foo')
|
||||
|
||||
|
||||
def test_obs_obj():
|
||||
obs_obj = StubObsObj()
|
||||
update_evt = Mock()
|
||||
obs_obj.update_evt['foo'] += update_evt
|
||||
|
||||
obs_obj.ignore = 'foo'
|
||||
assert not hasattr(obs_obj, 'foo')
|
||||
obs_obj.foo = 'foo'
|
||||
obs_obj.foo = 'foo'
|
||||
obs_obj.foo = 'bar'
|
||||
assert obs_obj.foo == 'bar'
|
||||
|
||||
del obs_obj.foo
|
||||
assert not hasattr(obs_obj, 'foo')
|
||||
assert_raises(AttributeError, obs_obj.__delattr__, 'foo')
|
||||
|
||||
assert update_evt.call_args_list == [
|
||||
((None, 'foo'),), (('foo', 'bar'),), (('bar', None),)]
|
||||
|
||||
|
||||
class TestObsList(object):
|
||||
def test_basic(self):
|
||||
obs_list = ObsList(xrange(10))
|
||||
setitem_evt = Mock()
|
||||
obs_list.setitem_evt += setitem_evt
|
||||
delitem_evt = Mock()
|
||||
obs_list.delitem_evt += delitem_evt
|
||||
insert_evt = Mock()
|
||||
obs_list.insert_evt += insert_evt
|
||||
|
||||
obs_list[3] = 3
|
||||
obs_list[3] = 333
|
||||
del obs_list[3]
|
||||
obs_list.insert(3, 3)
|
||||
|
||||
assert obs_list == list(xrange(10))
|
||||
assert setitem_evt.call_args_list == [((3, 3, 333),)]
|
||||
assert delitem_evt.call_args_list == [((3, 333),)]
|
||||
assert insert_evt.call_args_list == [((3, 3),)]
|
||||
|
||||
def test_ext_add(self):
|
||||
obs_list = ObsList()
|
||||
insert_evt = Mock()
|
||||
obs_list.insert_evt += insert_evt
|
||||
|
||||
obs_list.append(42)
|
||||
obs_list.extend([42, 1337])
|
||||
obs_list += [42, 1337]
|
||||
|
||||
assert obs_list == [42, 42, 1337, 42, 1337]
|
||||
assert insert_evt.call_args_list == [
|
||||
((0, 42),), ((1, 42),), ((2, 1337),), ((3, 42),), ((4, 1337),)]
|
||||
|
||||
def test_ext_del(self):
|
||||
obs_list = ObsList(xrange(10))
|
||||
delitem_evt = Mock()
|
||||
obs_list.delitem_evt += delitem_evt
|
||||
|
||||
assert obs_list.pop() == 9
|
||||
assert obs_list.pop(8) == 8
|
||||
obs_list.remove(7)
|
||||
|
||||
assert delitem_evt.call_args_list == [((9, 9),), ((8, 8),), ((7, 7),)]
|
||||
assert obs_list == list(xrange(7))
|
||||
|
||||
def test_reverse(self):
|
||||
obs_list = ObsList(xrange(10))
|
||||
obs_list.reverse()
|
||||
assert obs_list == list(reversed(xrange(10)))
|
||||
|
||||
def test_updated_item(self):
|
||||
obs_list = ObsList(['a', 'b'])
|
||||
update_evt = Mock()
|
||||
obs_list.update_evt += update_evt
|
||||
|
||||
obs_list.updated_item('b')
|
||||
assert update_evt.call_args_list == [((1, 'b'),)]
|
||||
|
||||
|
||||
class TestObsDict(object):
|
||||
def test_basic(self):
|
||||
obs_dict = ObsDict(a='A')
|
||||
setitem_evt = Mock()
|
||||
obs_dict.setitem_evt += setitem_evt
|
||||
delitem_evt = Mock()
|
||||
obs_dict.delitem_evt += delitem_evt
|
||||
|
||||
obs_dict['a'] = 'A'
|
||||
obs_dict['a'] = 'X'
|
||||
del obs_dict['a']
|
||||
obs_dict['b'] = 'B'
|
||||
|
||||
assert obs_dict == dict(b='B')
|
||||
assert setitem_evt.call_args_list == [(('a', 'A', 'X'),),
|
||||
(('b', None, 'B'),)]
|
||||
assert delitem_evt.call_args_list == [(('a', 'X'),)]
|
||||
|
||||
def test_pop(self):
|
||||
obs_dict = ObsDict(a='A', b='B')
|
||||
delitem_evt = Mock()
|
||||
obs_dict.delitem_evt += delitem_evt
|
||||
|
||||
assert obs_dict.pop('a') == 'A'
|
||||
assert obs_dict.pop('a', 'X') == 'X'
|
||||
assert obs_dict.pop('b', 'X') == 'B'
|
||||
|
||||
assert obs_dict == {}
|
||||
assert delitem_evt.call_args_list == [(('a', 'A'),), (('b', 'B'),)]
|
||||
|
||||
def test_popitem(self):
|
||||
obs_dict = ObsDict(a='A', b='B')
|
||||
delitem_evt = Mock()
|
||||
obs_dict.delitem_evt += delitem_evt
|
||||
|
||||
k, v = obs_dict.popitem()
|
||||
assert v == k.upper()
|
||||
|
||||
assert len(obs_dict) == 1
|
||||
assert delitem_evt.call_args_list == [((k, v),)]
|
||||
|
||||
def test_clear(self):
|
||||
obs_dict = ObsDict(a='A', b='B')
|
||||
delitem_evt = Mock()
|
||||
obs_dict.delitem_evt += delitem_evt
|
||||
|
||||
obs_dict.clear()
|
||||
|
||||
assert delitem_evt.call_args_list == [(('a', 'A'),), (('b', 'B'),)]
|
||||
|
||||
def test_update(self):
|
||||
obs_dict = ObsDict()
|
||||
setitem_evt = Mock()
|
||||
obs_dict.setitem_evt += setitem_evt
|
||||
|
||||
obs_dict.update({'a': 'A'}, b='B')
|
||||
obs_dict.update((('c', 'C'),))
|
||||
|
||||
assert obs_dict == dict(a='A', b='B', c='C')
|
||||
assert setitem_evt.call_args_list == [
|
||||
(('a', None, 'A'),), (('b', None, 'B'),), (('c', None, 'C'),)]
|
||||
|
||||
def test_setdefault(self):
|
||||
obs_dict = ObsDict(a='A')
|
||||
setitem_evt = Mock()
|
||||
obs_dict.setitem_evt += setitem_evt
|
||||
|
||||
assert obs_dict.setdefault('a') == 'A'
|
||||
assert obs_dict.setdefault('b', 'B') == 'B'
|
||||
|
||||
assert obs_dict == dict(a='A', b='B')
|
||||
assert setitem_evt.call_args_list == [(('b', None, 'B'),)]
|
||||
|
||||
def test_updated_item(self):
|
||||
obs_dict = ObsDict({'a': 'A', 'b': 'B'})
|
||||
update_evt = Mock()
|
||||
obs_dict.update_evt += update_evt
|
||||
|
||||
obs_dict.updated_item('b')
|
||||
assert update_evt.call_args_list == [(('b', 'B'),)]
|
|
@ -0,0 +1,111 @@
|
|||
"""
|
||||
:copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
|
||||
|
||||
def _callback(*args, **kwds): # pragma: no cover
|
||||
print ('callback', args, kwds)
|
||||
|
||||
def _test_accounts_view(): # pragma: no cover
|
||||
accounts_view = AccountsView()
|
||||
Controller(controller.AccountsController, accounts_view)
|
||||
accounts_view.window.connect('destroy', lambda w: gtk.main_quit())
|
||||
for i in xrange(10):
|
||||
accounts_view.accounts.append(("psyc://server/~account%i" % i, True))
|
||||
accounts_view.accounts[4] = ("psyc://server/~updatedaccount4", False)
|
||||
#return
|
||||
|
||||
from pypsyc.client.model import Account
|
||||
account = Account(None, "", "", "", False, False)
|
||||
accounts_view.show_addedit_dialog(account.__dict__, True, _callback)
|
||||
|
||||
def _test_tabs_view(): # pragma: no cover
|
||||
status_icon = gtk.StatusIcon()
|
||||
status_icon.set_from_file('pypsyc/client/psyc.ico')
|
||||
status_icon.connect('activate', lambda w: tabs_view.on_status_icon_click())
|
||||
tabs_view = TabsView(status_icon)
|
||||
tabs_view.window.connect('destroy', lambda w: gtk.main_quit())
|
||||
Controller(controller.TabsController, tabs_view)
|
||||
|
||||
conversation_view = tabs_view.show_conversation("Conversation 1")
|
||||
Controller(controller.ConversationController, conversation_view)
|
||||
tabs_view.focus_tab(conversation_view)
|
||||
|
||||
conference_view = tabs_view.show_conference("Conversation 2")
|
||||
Controller(controller.ConferenceController, conference_view)
|
||||
|
||||
def show():
|
||||
for i in xrange(40):
|
||||
conversation_view.show_message("Message %02i" % i)
|
||||
for i in xrange(40):
|
||||
conference_view.members.append(("psyc://server/~person%02i" % i,
|
||||
"person%02i" % i))
|
||||
conference_view.show_message("Message %02i" % i)
|
||||
conference_view.members[20] = ("psyc://server/~longpersonname20",
|
||||
"longpersonname20")
|
||||
idle_add(show)
|
||||
|
||||
def _test_dump_view(): # pragma: no cover
|
||||
dump_view = DumpView()
|
||||
Controller(controller.DumpController, dump_view)
|
||||
dump_view.show_line('o', 'a', 'psyc://server/~account')
|
||||
dump_view.show_line('i', ':_tag_relay\t\xc8\xe6', 'psyc://server/~account')
|
||||
|
||||
def _test_main_view(): # pragma: no cover
|
||||
main_view = MainView()
|
||||
Controller(controller.MainController, main_view)
|
||||
Controller(controller.FriendListController, main_view.friends_view)
|
||||
for i in xrange(40):
|
||||
main_view.friends_view.friends.append(("Friend %02i" % i, False,
|
||||
'pending'))
|
||||
for i in xrange(0, 40, 2):
|
||||
main_view.friends_view.friends[i] = ("Friend %02i updated" % i, False,
|
||||
'offered')
|
||||
for i in xrange(0, 40, 3):
|
||||
main_view.friends_view.friends[i] = ("Friend %02i updated 2" % i, True,
|
||||
'established')
|
||||
#return
|
||||
|
||||
account_dict = {'password': '', 'save_password': False}
|
||||
main_view.show_password_dialog("psyc://server/~account", account_dict,
|
||||
_callback)
|
||||
main_view.show_no_such_user("psyc://server/~account")
|
||||
main_view.show_auth_error("psyc://server/~account", "Error description")
|
||||
main_view.show_open_conv_dialog(["psyc://server/~person1",
|
||||
"psyc://server/~person2"], _callback)
|
||||
main_view.show_open_conf_dialog(["psyc://server/~person1",
|
||||
"psyc://server/~person2"], _callback)
|
||||
main_view.show_add_friend_dialog(["psyc://server/~person1",
|
||||
"psyc://server/~person2"], _callback)
|
||||
|
||||
|
||||
if __name__ == '__main__': # pragma: no cover
|
||||
class Controller(object):
|
||||
def __init__(self, controller, view):
|
||||
self.controller = controller
|
||||
self.c_name = controller.__name__
|
||||
view.controller = self
|
||||
def __getattr__(self, attr):
|
||||
m = getattr(self.controller, attr, None)
|
||||
assert m, "%s has no method %s" % (self.c_name, attr)
|
||||
def f(*args, **kwds):
|
||||
m_argcount = m.__code__.co_argcount - 1 # minus self
|
||||
f_argcount = len(args) + len(kwds)
|
||||
assert m_argcount == f_argcount, \
|
||||
"%s.%s has %s arguments, called with %s arguments" % (
|
||||
self.c_name, attr, m_argcount, f_argcount)
|
||||
print "%s.%s called: %s, %s" % (self.c_name, attr, args, kwds)
|
||||
return f
|
||||
|
||||
from gobject import idle_add
|
||||
import gtk
|
||||
|
||||
from pypsyc.client.view import AccountsView, TabsView, DumpView, MainView
|
||||
from pypsyc.client import controller
|
||||
|
||||
# _test_accounts_view()
|
||||
# _test_tabs_view()
|
||||
# _test_dump_view()
|
||||
# _test_main_view()
|
||||
gtk.main()
|
|
@ -0,0 +1,97 @@
|
|||
"""
|
||||
:copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
from nose.tools import assert_raises
|
||||
from twisted.protocols.loopback import loopbackAsync as loopback_async
|
||||
|
||||
from pypsyc.core.mmp import Uni, Header, Circuit
|
||||
|
||||
|
||||
UNI = 'psyc://server/foo/bar'
|
||||
|
||||
class TestUni(object):
|
||||
def test_uni(self):
|
||||
uni = Uni(UNI)
|
||||
assert uni == UNI
|
||||
assert uni.prefix == 'psyc'
|
||||
|
||||
def test_parts(self):
|
||||
uni = Uni(UNI)
|
||||
assert uni.into_parts() == ['server', 'foo', 'bar']
|
||||
assert Uni.from_parts(uni.into_parts()) == uni
|
||||
|
||||
def test_descendant_ancestor(self):
|
||||
uni = Uni(UNI)
|
||||
assert uni.is_descendant_of('psyc://server/foo')
|
||||
assert not uni.is_descendant_of('psyc://server/spam')
|
||||
assert uni.is_ancestor_of('psyc://server/foo/bar/child')
|
||||
assert not uni.is_ancestor_of('psyc://server/spam/eggs/child')
|
||||
|
||||
assert uni.is_descendant_of('psyc://server/')
|
||||
assert Uni('psyc://server/').is_ancestor_of('psyc://server/foo/bar')
|
||||
|
||||
def test_chain(self):
|
||||
assert Uni(UNI).chain('foobar') == 'psyc://server/foo/bar/foobar'
|
||||
assert Uni('psyc://server/').chain('foo') == 'psyc://server/foo'
|
||||
|
||||
|
||||
class TestHeader(object):
|
||||
def test_empty(self):
|
||||
header = Header()
|
||||
assert header == {}
|
||||
header._init()
|
||||
assert header.source is None
|
||||
assert header.target is None
|
||||
assert header.context is None
|
||||
|
||||
def test_header(self):
|
||||
header_dict = {
|
||||
'_source': 'psyc://server/uni1',
|
||||
'_target': 'psyc://server/uni2',
|
||||
'_context': 'psyc://server/@place'
|
||||
}
|
||||
header = Header(header_dict)
|
||||
assert header == header_dict
|
||||
header._init()
|
||||
assert header.source == Uni('psyc://server/uni1')
|
||||
assert header.target == Uni('psyc://server/uni2')
|
||||
assert header.context == Uni('psyc://server/@place')
|
||||
|
||||
|
||||
class TestCircuit(object):
|
||||
def setup(self):
|
||||
self.success = False
|
||||
|
||||
def test_render_empty(self):
|
||||
self._test({}, [])
|
||||
|
||||
def test_onlyvars(self):
|
||||
self._test({'_target': 'psyc://example.org/'}, [])
|
||||
|
||||
def test_onlycontent(self):
|
||||
self._test({}, ['content'])
|
||||
|
||||
def test_packet(self):
|
||||
self._test({'_target': 'psyc://example.org/'}, ['content'])
|
||||
|
||||
def _test(self, header, content):
|
||||
def received(header_, content_):
|
||||
assert header_ == header
|
||||
assert content_ == content
|
||||
self.success = True
|
||||
|
||||
circuit1 = Circuit()
|
||||
circuit1.packet_received = received
|
||||
|
||||
circuit2 = Circuit()
|
||||
circuit2.initiator = True
|
||||
circuit2.inited = lambda: circuit2.send(header, iter(content))
|
||||
|
||||
loopback_async(circuit1, circuit2)
|
||||
assert self.success
|
||||
|
||||
def test_subclass(self):
|
||||
circuit = Circuit()
|
||||
circuit.lineReceived('|')
|
||||
assert_raises(NotImplementedError, circuit.lineReceived, '|')
|
|
@ -0,0 +1,151 @@
|
|||
"""
|
||||
:copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
from mock import Mock
|
||||
from nose.tools import assert_raises
|
||||
from tests.constants import USER1_UNI, USER2_UNI
|
||||
from tests.helpers import check_success, rendered
|
||||
|
||||
from pypsyc.core.psyc import PSYCPacket, PSYCObject
|
||||
from pypsyc.util import schedule
|
||||
|
||||
|
||||
class TestPSYCPacket(object):
|
||||
def test_vars(self):
|
||||
packet = PSYCPacket(
|
||||
modifiers={
|
||||
':': {'_foo': 'bar'},
|
||||
'=': {'_spam': 'eggs'}
|
||||
}
|
||||
)
|
||||
self._test(packet, [':_foo\tbar', '=_spam\teggs'])
|
||||
|
||||
def test_method(self):
|
||||
packet = PSYCPacket(mc='_notice_foo')
|
||||
self._test(packet, ['_notice_foo'])
|
||||
|
||||
def test_packet(self):
|
||||
packet = PSYCPacket(
|
||||
modifiers={
|
||||
':': {'_foo': 'bar'}
|
||||
},
|
||||
mc='_message_foo',
|
||||
data='this is\ndata'
|
||||
)
|
||||
self._test(packet, [':_foo\tbar', '_message_foo', 'this is\ndata'])
|
||||
|
||||
def test_binary1(self):
|
||||
packet = PSYCPacket({':': {'_foo': 'foo\nbar'}})
|
||||
self._test(packet, [':_foo 7\tfoo\nbar'])
|
||||
assert PSYCPacket.parse([':_foo 7\tfoo', 'bar']) == packet
|
||||
|
||||
def test_binary2(self):
|
||||
packet = PSYCPacket({':': {'_foo': 'foo\n\nbar'}})
|
||||
self._test(packet, [':_foo 8\tfoo\n\nbar'])
|
||||
assert PSYCPacket.parse([':_foo 8\tfoo', '', 'bar']) == packet
|
||||
|
||||
def _test(self, packet, rendered_):
|
||||
assert PSYCPacket.parse(packet.render()) == packet
|
||||
assert rendered(packet) == rendered_
|
||||
|
||||
def test_nomethod(self):
|
||||
packet = PSYCPacket(data='foobar')
|
||||
assert_raises(AssertionError, list, packet.render())
|
||||
|
||||
def test_repr(self):
|
||||
s = '\n'.join((
|
||||
r"PSYCPacket(",
|
||||
r" modifiers={",
|
||||
r" ':': {'_foo': 'bar'},",
|
||||
r" '=': {'_foo': 'bar'}",
|
||||
r" },",
|
||||
r" mc='_message_foo',",
|
||||
r" data='this is\ndata'",
|
||||
r")"
|
||||
))
|
||||
assert repr(eval(s)) == s
|
||||
|
||||
def test_equal(self):
|
||||
assert PSYCPacket() == PSYCPacket()
|
||||
assert not PSYCPacket() != PSYCPacket()
|
||||
|
||||
def test_empty(self):
|
||||
packet = PSYCPacket()
|
||||
self._test(packet, [])
|
||||
assert packet.modifiers == {':': {}}
|
||||
assert packet.mc is None
|
||||
assert packet.data == ''
|
||||
|
||||
def test_from_kwds(self):
|
||||
packet1 = PSYCPacket.from_kwds(mc='_message_public',
|
||||
data="hello", _somevar='foo')
|
||||
packet2 = PSYCPacket(mc='_message_public', data="hello",
|
||||
modifiers={':': {'_somevar': 'foo'}})
|
||||
assert packet1 == packet2
|
||||
|
||||
|
||||
class TestPSYCObject(object):
|
||||
def test_handle_packet(self):
|
||||
psyc = PSYCObject(None)
|
||||
psyc.handlers['_message'] = message = Mock()
|
||||
psyc.handlers['_message_public'] = message_public = Mock()
|
||||
|
||||
p1 = PSYCPacket() # psyc_packet should return
|
||||
p2 = PSYCPacket(mc='_message_public')
|
||||
p3 = PSYCPacket(mc='_message_private')
|
||||
|
||||
assert psyc.handle_packet({}, p1.render()) == p1
|
||||
assert psyc.handle_packet({}, p2.render()) == p2
|
||||
assert psyc.handle_packet({}, p3.render()) == p3
|
||||
assert message_public.call_args_list == [((p2,),)]
|
||||
assert message.call_args_list == [((p3,),)]
|
||||
|
||||
def test_handle_packet_internal_error(self):
|
||||
psyc = PSYCObject(None)
|
||||
psyc.handlers['_message_public'] = Mock(side_effect=Exception)
|
||||
|
||||
packet = PSYCPacket(mc='_message_public')
|
||||
assert psyc.handle_packet({}, packet.render()) == packet
|
||||
|
||||
def test_sendmsg_packet(self):
|
||||
packet = PSYCPacket()
|
||||
sendfunc = Mock()
|
||||
psyc = PSYCObject(sendfunc, USER1_UNI)
|
||||
|
||||
psyc.sendmsg(USER2_UNI, packet)
|
||||
header = {'_source': USER1_UNI, '_target': USER2_UNI}
|
||||
assert sendfunc.call_args_list == [((header, rendered(packet)),)]
|
||||
|
||||
def test_sendmsg_kwds(self):
|
||||
packet = PSYCPacket({':': {'_somevar': 'foo'}}, mc='_message_private')
|
||||
sendfunc = Mock()
|
||||
psyc = PSYCObject(sendfunc, USER1_UNI)
|
||||
|
||||
psyc.sendmsg(USER2_UNI, mc='_message_private', _somevar='foo')
|
||||
header = {'_source': USER1_UNI, '_target': USER2_UNI}
|
||||
assert sendfunc.call_args_list == [((header, rendered(packet)),)]
|
||||
|
||||
@check_success
|
||||
def test_async(self):
|
||||
MC = '_request_foo'
|
||||
ECHO = PSYCPacket(mc='_echo_foo')
|
||||
o1 = PSYCObject(lambda *args: o2.handle_packet(*args), USER1_UNI)
|
||||
o2 = PSYCObject(lambda *args: o1.handle_packet(*args), USER2_UNI)
|
||||
o2.handlers[MC] = lambda _: ECHO
|
||||
|
||||
def _test():
|
||||
ret = o1.sendmsg(USER2_UNI, mc=MC)
|
||||
assert ret == ECHO
|
||||
self.success = True
|
||||
schedule(_test)
|
||||
|
||||
def test_async_internal_error(self):
|
||||
MC = '_request_foo'
|
||||
ERROR = PSYCPacket(mc='_error_internal')
|
||||
o1 = PSYCObject(lambda *args: o2.handle_packet(*args), USER1_UNI)
|
||||
o2 = PSYCObject(lambda *args: o1.handle_packet(*args), USER2_UNI)
|
||||
o2.handlers[MC] = Mock(side_effect=Exception)
|
||||
|
||||
ret = o1.sendmsg(USER2_UNI, mc=MC)
|
||||
assert ret == ERROR
|
|
@ -0,0 +1,396 @@
|
|||
"""
|
||||
:copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
import hmac
|
||||
from hashlib import sha256
|
||||
|
||||
from mock import Mock
|
||||
from nose.tools import assert_raises
|
||||
from tests.constants import (SERVER1, USER1, USER2, USER1_UNI, USER2_UNI,
|
||||
USER1_NICK, RESOURCE, RESOURCE1_UNI, RESOURCE2_UNI, PLACE, PLACE_UNI,
|
||||
INTERFACE, VARIABLES, PASSWORD, MESSAGE, ONLINE, PENDING, OFFERED,
|
||||
ESTABLISHED, ERROR)
|
||||
from tests.helpers import connect_circuits, PlaceHolder
|
||||
|
||||
from pypsyc.client.model import ClientCircuit
|
||||
from pypsyc.core.psyc import PSYCPacket
|
||||
from pypsyc.protocol import (UnknownTargetError, DeliveryFailedError,
|
||||
RoutingErrorRoot, RoutingErrorRelaying, RoutingErrorClient,
|
||||
AuthenticationError, LinkingServer, LinkingClient, Messaging,
|
||||
MessageRelaying, ConferencingServer, ConferencingClient, EntryDeniedError,
|
||||
ContextMaster, ContextSlave, UnauthorizedError, ClientInterfaceServer,
|
||||
ClientInterfaceClient, FriendshipServer, FriendshipClient)
|
||||
from pypsyc.server import Entity
|
||||
from pypsyc.server.person import Resource
|
||||
from pypsyc.server.routing import Routing
|
||||
|
||||
|
||||
def _setup():
|
||||
server = Mock()
|
||||
server.hostname = SERVER1
|
||||
routing = Routing(server.hostname, INTERFACE)
|
||||
server.routing = routing
|
||||
routing.init(_entity(server=server))
|
||||
return routing
|
||||
|
||||
def _entity(*args, **kwds):
|
||||
entity = Entity(*args, **kwds)
|
||||
entity.package = Mock()
|
||||
entity.package.entity = entity
|
||||
return entity
|
||||
|
||||
def _c2s_circuit(routing, link_to=None):
|
||||
sc, cc = connect_circuits(routing.buildProtocol(None), ClientCircuit())
|
||||
cc.package = Mock()
|
||||
cc.package.psyc = cc.psyc
|
||||
if link_to is not None:
|
||||
Resource(link_to, RESOURCE, sc)
|
||||
cc.psyc.uni = link_to.uni.chain(RESOURCE)
|
||||
return sc, cc
|
||||
|
||||
def _enter(routing, entity, sc):
|
||||
routing.mrouting_table[entity.uni].add(sc)
|
||||
state = entity.context_master.add_member(USER1_UNI)
|
||||
sc.send({'_context': entity.uni}, PSYCPacket({'=': state}).render())
|
||||
|
||||
|
||||
class TestRoutingErrorProtocol(object):
|
||||
def setup(self):
|
||||
routing = _setup()
|
||||
self.root = routing.root
|
||||
self.user1 = _entity(routing.root, USER1)
|
||||
self.sc, self.client = _c2s_circuit(routing, link_to=self.user1)
|
||||
|
||||
def test_unknown_target_error(self):
|
||||
rer = RoutingErrorRoot(self.root.package)
|
||||
self.client.psyc.add_handler(RoutingErrorClient(self.client.package))
|
||||
|
||||
rer.send_unknown_target_error(RESOURCE1_UNI, USER2_UNI)
|
||||
assert self.client.package.method_calls == [
|
||||
('unknown_target_error', (USER2_UNI,))]
|
||||
|
||||
def test_relay_unknown_target_error1(self):
|
||||
rer = RoutingErrorRoot(self.root.package)
|
||||
self.user1.add_handler(RoutingErrorRelaying(self.user1.package))
|
||||
|
||||
rer.send_unknown_target_error(USER1_UNI, USER2_UNI)
|
||||
assert self.user1.package.method_calls == [
|
||||
('unknown_target_error', (USER2_UNI,))]
|
||||
|
||||
def test_relay_unknown_target_error2(self):
|
||||
rer = RoutingErrorRelaying(self.root.package)
|
||||
self.client.psyc.add_handler(RoutingErrorClient(self.client.package))
|
||||
|
||||
rer.relay_unknown_target_error(RESOURCE1_UNI, USER2_UNI)
|
||||
assert self.client.package.method_calls == [
|
||||
('unknown_target_error', (USER2_UNI,))]
|
||||
|
||||
def test_delivery_failed_error(self):
|
||||
rer = RoutingErrorRoot(self.root.package)
|
||||
self.client.psyc.add_handler(RoutingErrorClient(self.client.package))
|
||||
|
||||
rer.send_delivery_failed_error(RESOURCE1_UNI, USER2_UNI, ERROR)
|
||||
assert self.client.package.method_calls == [
|
||||
('delivery_failed_error', (USER2_UNI, ERROR))]
|
||||
|
||||
def test_relay_delivery_failed_error1(self):
|
||||
rer = RoutingErrorRoot(self.root.package)
|
||||
self.user1.add_handler(RoutingErrorRelaying(self.user1.package))
|
||||
|
||||
rer.send_delivery_failed_error(USER1_UNI, USER2_UNI, ERROR)
|
||||
assert self.user1.package.method_calls == [
|
||||
('delivery_failed_error', (USER2_UNI, ERROR))]
|
||||
|
||||
def test_relay_delivery_failed_error2(self):
|
||||
rer = RoutingErrorRelaying(self.root.package)
|
||||
self.client.psyc.add_handler(RoutingErrorClient(self.client.package))
|
||||
|
||||
rer.relay_delivery_failed_error(RESOURCE1_UNI, USER2_UNI, ERROR)
|
||||
assert self.client.package.method_calls == [
|
||||
('delivery_failed_error', (USER2_UNI, ERROR))]
|
||||
|
||||
|
||||
class TestLinkingProtocol(object):
|
||||
def setup(self):
|
||||
routing = _setup()
|
||||
self.user1 = _entity(routing.root, USER1)
|
||||
self.sc, self.client = _c2s_circuit(routing)
|
||||
|
||||
def test_linking(self):
|
||||
lc = LinkingClient(self.client.package)
|
||||
self.user1.add_handler(LinkingServer(self.user1.package))
|
||||
pw = PlaceHolder()
|
||||
|
||||
lc.link(USER1_UNI, RESOURCE, PASSWORD)
|
||||
assert self.user1.package.method_calls == [
|
||||
('authenticate', (pw, self.sc, RESOURCE))]
|
||||
assert pw.obj[0] == 'hmac'
|
||||
assert pw.obj[1] == hmac.new(PASSWORD, pw.obj[2], sha256).digest()
|
||||
|
||||
def test_incorrect_password(self):
|
||||
lc = LinkingClient(self.client.package)
|
||||
self.user1.add_handler(LinkingServer(self.user1.package))
|
||||
self.user1.package.authenticate.side_effect = AuthenticationError('xy')
|
||||
|
||||
with assert_raises(AuthenticationError) as cm:
|
||||
lc.link(USER1_UNI, RESOURCE, 'pw')
|
||||
assert cm.exception.args == ('xy',)
|
||||
|
||||
|
||||
class TestMessagingProtocol(object):
|
||||
def setup(self):
|
||||
routing = _setup()
|
||||
self.user1 = _entity(routing.root, USER1)
|
||||
self.user2 = _entity(routing.root, USER2)
|
||||
|
||||
sc1, self.client1 = _c2s_circuit(routing, link_to=self.user1)
|
||||
sc2, self.client2 = _c2s_circuit(routing, link_to=self.user2)
|
||||
|
||||
def test_private_message(self):
|
||||
msg1 = Messaging(self.client1.package)
|
||||
self.client2.psyc.add_handler(Messaging(self.client2.package))
|
||||
|
||||
msg1.send_private_message(RESOURCE2_UNI, MESSAGE)
|
||||
assert self.client2.package.method_calls == [
|
||||
('private_message', (RESOURCE1_UNI, MESSAGE))]
|
||||
|
||||
def test_message_relaying1(self):
|
||||
msg1 = Messaging(self.client1.package)
|
||||
self.user1.add_handler(MessageRelaying(self.user1.package))
|
||||
|
||||
msg1.send_private_message(USER2_UNI, MESSAGE, relay=USER1_UNI)
|
||||
assert self.user1.package.method_calls == [
|
||||
('private_message_relay', (RESOURCE1_UNI, USER2_UNI, MESSAGE))]
|
||||
|
||||
def test_message_relaying2(self):
|
||||
msg_relaying1 = MessageRelaying(self.user1.package)
|
||||
self.user2.add_handler(MessageRelaying(self.user2.package))
|
||||
|
||||
msg_relaying1.send_private_message(USER2_UNI, MESSAGE)
|
||||
assert self.user2.package.method_calls == [
|
||||
('private_message', (USER1_UNI, MESSAGE))]
|
||||
|
||||
def test_message_relaying3(self):
|
||||
msg_relaying2 = MessageRelaying(self.user2.package)
|
||||
self.client2.psyc.add_handler(Messaging(self.client2.package))
|
||||
|
||||
msg_relaying2.relay_private_message(USER1_UNI, RESOURCE2_UNI, MESSAGE)
|
||||
assert self.client2.package.method_calls == [
|
||||
('private_message', (USER1_UNI, MESSAGE))]
|
||||
|
||||
|
||||
class TestConferencingProtocol(object):
|
||||
def setup(self):
|
||||
routing = self.routing = _setup()
|
||||
self.place = _entity(routing.root, PLACE)
|
||||
self.user1 = _entity(routing.root, USER1)
|
||||
self.user2 = _entity(routing.root, USER2)
|
||||
|
||||
self.sc1, self.client1 = _c2s_circuit(routing, link_to=self.user1)
|
||||
self.sc2, self.client2 = _c2s_circuit(routing, link_to=self.user2)
|
||||
_enter(routing, self.place, self.sc1)
|
||||
|
||||
def test_cast_member(self):
|
||||
con_server = ConferencingServer(self.place.package)
|
||||
self.client1.add_pvar_handler(ConferencingClient(self.client1.package))
|
||||
self.client2.add_pvar_handler(ConferencingClient(self.client2.package))
|
||||
|
||||
con_server.cast_member_entered(USER1_UNI, USER1_NICK)
|
||||
assert self.client1.package.method_calls == [
|
||||
('member_entered', (PLACE_UNI, USER1_UNI, USER1_NICK))]
|
||||
|
||||
_enter(self.routing, self.place, self.sc2)
|
||||
assert self.client2.package.method_calls == [
|
||||
('member_entered', (PLACE_UNI, USER1_UNI, USER1_NICK))]
|
||||
self.client1.package.reset_mock()
|
||||
|
||||
con_server.cast_member_left(USER1_UNI)
|
||||
assert self.client1.package.method_calls == [
|
||||
('member_left', (PLACE_UNI, USER1_UNI))]
|
||||
|
||||
def test_public_message1(self):
|
||||
con_client = ConferencingClient(self.client1.package)
|
||||
self.place.add_handler(ConferencingServer(self.place.package))
|
||||
|
||||
con_client.send_public_message(PLACE_UNI, MESSAGE)
|
||||
assert self.place.package.method_calls == [
|
||||
('public_message', (RESOURCE1_UNI, MESSAGE))]
|
||||
|
||||
def test_public_message2(self):
|
||||
con_server = ConferencingServer(self.place.package)
|
||||
self.client1.psyc.add_handler(ConferencingClient(self.client1.package))
|
||||
self.client2.psyc.add_handler(ConferencingClient(self.client2.package))
|
||||
|
||||
con_server.cast_public_message(USER1_UNI, MESSAGE)
|
||||
assert self.client1.package.method_calls == [
|
||||
('public_message', (PLACE_UNI, USER1_UNI, MESSAGE))]
|
||||
|
||||
|
||||
class TestContextProtocol(object):
|
||||
def setup(self):
|
||||
routing = _setup()
|
||||
self.place = _entity(routing.root, PLACE)
|
||||
self.user1 = _entity(routing.root, USER1)
|
||||
|
||||
def test_enter(self):
|
||||
cs = ContextSlave(self.user1.package)
|
||||
self.place.add_handler(ContextMaster(self.place.package))
|
||||
self.place.package.enter_request.return_value = VARIABLES
|
||||
|
||||
state = cs.enter(PLACE_UNI)
|
||||
assert state == VARIABLES
|
||||
assert self.place.package.method_calls == [
|
||||
('enter_request', (USER1_UNI,))]
|
||||
|
||||
def test_enter_denied(self):
|
||||
cs = ContextSlave(self.user1.package)
|
||||
self.place.add_handler(ContextMaster(self.place.package))
|
||||
self.place.package.enter_request.side_effect = EntryDeniedError('xy')
|
||||
|
||||
with assert_raises(EntryDeniedError) as cm:
|
||||
cs.enter(PLACE_UNI)
|
||||
assert cm.exception.args == ('xy',)
|
||||
|
||||
def test_leave(self):
|
||||
cs = ContextSlave(self.user1.package)
|
||||
self.place.add_handler(ContextMaster(self.place.package))
|
||||
|
||||
cs.leave(PLACE_UNI)
|
||||
assert self.place.package.method_calls == [
|
||||
('leave_context', (USER1_UNI,))]
|
||||
|
||||
|
||||
class TestClientInterface(object):
|
||||
def setup(self):
|
||||
routing = self.routing = _setup()
|
||||
self.user1 = _entity(routing.root, USER1)
|
||||
_, self.client = _c2s_circuit(routing, link_to=self.user1)
|
||||
self.client.package.uni = USER1_UNI
|
||||
|
||||
self.ci_client = ClientInterfaceClient(self.client.package)
|
||||
self.user1.add_handler(ClientInterfaceServer(self.user1.package))
|
||||
|
||||
def test_unauthorized(self):
|
||||
user2 = _entity(self.routing.root, USER2)
|
||||
_, client2 = _c2s_circuit(self.routing, link_to=user2)
|
||||
client2.package.uni = USER1_UNI
|
||||
ci_client2 = ClientInterfaceClient(client2.package)
|
||||
|
||||
assert_raises(UnauthorizedError, ci_client2.subscribe, PLACE_UNI)
|
||||
assert self.user1.package.method_calls == []
|
||||
|
||||
def test_unknown_target_error(self):
|
||||
error = UnknownTargetError('xy')
|
||||
self.user1.package.client_subscribe.side_effect = error
|
||||
with assert_raises(UnknownTargetError) as cm:
|
||||
self.ci_client.subscribe(PLACE_UNI)
|
||||
assert cm.exception.args == ('xy',)
|
||||
|
||||
def test_delivery_failed_error(self):
|
||||
error = DeliveryFailedError('x', 'y')
|
||||
self.user1.package.client_subscribe.side_effect = error
|
||||
with assert_raises(DeliveryFailedError) as cm:
|
||||
self.ci_client.subscribe(PLACE_UNI)
|
||||
assert cm.exception.args == ('x', 'y')
|
||||
|
||||
def test_subscribe(self):
|
||||
self.ci_client.subscribe(PLACE_UNI)
|
||||
assert self.user1.package.method_calls == [
|
||||
('client_subscribe', (PLACE_UNI, RESOURCE1_UNI))]
|
||||
|
||||
def test_unsubscribe(self):
|
||||
self.ci_client.unsubscribe(PLACE_UNI)
|
||||
assert self.user1.package.method_calls == [
|
||||
('client_unsubscribe', (PLACE_UNI, RESOURCE1_UNI))]
|
||||
|
||||
def test_presence(self):
|
||||
self.ci_client.cast_presence(ONLINE)
|
||||
assert self.user1.package.method_calls == [
|
||||
('client_presence', (ONLINE,))]
|
||||
|
||||
def test_add_friend(self):
|
||||
self.ci_client.add_friend(USER2_UNI)
|
||||
assert self.user1.package.method_calls == [
|
||||
('client_add_friend', (USER2_UNI,))]
|
||||
|
||||
def test_remove_friend(self):
|
||||
self.ci_client.remove_friend(USER2_UNI)
|
||||
assert self.user1.package.method_calls == [
|
||||
('client_remove_friend', (USER2_UNI,))]
|
||||
|
||||
|
||||
class TestFriendshipProtocol(object):
|
||||
def setup(self):
|
||||
routing = _setup()
|
||||
self.user1 = _entity(routing.root, USER1)
|
||||
self.user2 = _entity(routing.root, USER2)
|
||||
|
||||
sc1, self.client1 = _c2s_circuit(routing, link_to=self.user1)
|
||||
sc2, self.client2 = _c2s_circuit(routing, link_to=self.user2)
|
||||
routing.mrouting_table[USER1_UNI] = [sc2]
|
||||
|
||||
def test_establish_pending(self):
|
||||
friendship_server1 = FriendshipServer(self.user1.package)
|
||||
self.user2.add_handler(FriendshipServer(self.user2.package))
|
||||
self.user2.package.friendship_request.return_value = PENDING
|
||||
|
||||
state = friendship_server1.establish(USER2_UNI)
|
||||
assert state == PENDING
|
||||
assert self.user2.package.method_calls == [
|
||||
('friendship_request', (USER1_UNI,))]
|
||||
|
||||
def test_establish_established(self):
|
||||
friendship_server1 = FriendshipServer(self.user1.package)
|
||||
self.user2.add_handler(FriendshipServer(self.user2.package))
|
||||
self.user2.package.friendship_request.return_value = ESTABLISHED
|
||||
|
||||
state = friendship_server1.establish(USER2_UNI)
|
||||
assert state == ESTABLISHED
|
||||
assert self.user2.package.method_calls == [
|
||||
('friendship_request', (USER1_UNI,))]
|
||||
|
||||
def test_remove(self):
|
||||
friendship_server1 = FriendshipServer(self.user1.package)
|
||||
self.user2.add_handler(FriendshipServer(self.user2.package))
|
||||
|
||||
friendship_server1.remove(USER2_UNI)
|
||||
assert self.user2.package.method_calls == [
|
||||
('friendship_cancel', (USER1_UNI,))]
|
||||
|
||||
def test_friendships(self):
|
||||
friendship_server = FriendshipServer(self.user1.package)
|
||||
self.client1.psyc.add_handler(FriendshipClient(self.client1.package))
|
||||
|
||||
friendship_server.send_friendships(RESOURCE1_UNI, {
|
||||
USER1_UNI: {'state': ESTABLISHED},
|
||||
USER2_UNI: {'state': OFFERED}})
|
||||
friendship_server.send_friendships(RESOURCE1_UNI, {})
|
||||
assert self.client1.package.method_calls == [
|
||||
('friendship', (USER1_UNI, ESTABLISHED)),
|
||||
('friendship', (USER2_UNI, OFFERED))]
|
||||
|
||||
def test_updated_friendship(self):
|
||||
friendship_server = FriendshipServer(self.user1.package)
|
||||
self.client1.psyc.add_handler(FriendshipClient(self.client1.package))
|
||||
|
||||
friendship_server.send_updated_friendship(RESOURCE1_UNI, USER1_UNI,
|
||||
{'state': 'established'})
|
||||
assert self.client1.package.method_calls == [
|
||||
('friendship', (USER1_UNI, ESTABLISHED))]
|
||||
|
||||
def test_removed_friendship(self):
|
||||
friendship_server = FriendshipServer(self.user1.package)
|
||||
self.client1.psyc.add_handler(FriendshipClient(self.client1.package))
|
||||
|
||||
friendship_server.send_removed_friendship(RESOURCE1_UNI, USER1_UNI)
|
||||
assert self.client1.package.method_calls == [
|
||||
('friendship_removed', (USER1_UNI,))]
|
||||
|
||||
def test_presence(self):
|
||||
friendship_server = FriendshipServer(self.user1.package)
|
||||
self.client2.add_pvar_handler(FriendshipClient(self.client2.package))
|
||||
|
||||
friendship_server.cast_presence(ONLINE)
|
||||
assert self.client2.package.method_calls == [
|
||||
('presence', (USER1_UNI, ONLINE))]
|
|
@ -0,0 +1,50 @@
|
|||
"""
|
||||
:copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
from greenlet import greenlet
|
||||
from nose.tools import assert_raises
|
||||
from tests.helpers import mockify, mockified, check_success
|
||||
|
||||
from pypsyc.server.db import Database
|
||||
|
||||
|
||||
class TestDatabase:
|
||||
def test_sync(self):
|
||||
db = Database(':memory:')
|
||||
db.execute('CREATE TABLE test(test TEXT)')
|
||||
db.execute("INSERT INTO test VALUES ('test')")
|
||||
|
||||
result = db.fetch('SELECT test FROM test')
|
||||
assert result == [('test',)]
|
||||
assert isinstance(result[0][0], str)
|
||||
|
||||
db.stop()
|
||||
|
||||
@check_success
|
||||
@mockified('pypsyc.server.db',['sqlite3', 'Thread', 'reactor'])
|
||||
def test_async(self, sqlite3, Thread, reactor):
|
||||
db = Database('')
|
||||
mockify(db, ['_execute', '_fetch'])
|
||||
e = Exception()
|
||||
db._execute.side_effect = e
|
||||
|
||||
def f():
|
||||
assert_raises(Exception, db.execute)
|
||||
assert db.fetch() == db._fetch.return_value
|
||||
self.success = True
|
||||
gl = greenlet(f)
|
||||
gl.switch()
|
||||
|
||||
# emulate reactor calling this methods
|
||||
gl.throw(e)
|
||||
gl.switch(db._fetch.return_value)
|
||||
|
||||
db.stop()
|
||||
assert db.thread.method_calls == [('start',), ('join',)]
|
||||
|
||||
db.run_async()
|
||||
assert sqlite3.method_calls == [('connect', ('',))]
|
||||
assert reactor.method_calls == [
|
||||
('callFromThread', (gl.throw, e)),
|
||||
('callFromThread', (gl.switch, db._fetch.return_value))]
|
|
@ -0,0 +1,162 @@
|
|||
"""
|
||||
::copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
from mock import Mock
|
||||
from tests.constants import (SERVER1, SERVER2, SERVER1_UNI, SERVER2_UNI, USER1, USER2,
|
||||
USER1_UNI, USER2_UNI, RESOURCE, PLACE_UNI, VARIABLES)
|
||||
from tests.helpers import mockified, rendered
|
||||
|
||||
from pypsyc.core.psyc import PSYCPacket
|
||||
from pypsyc.server import Entity
|
||||
from pypsyc.server.multicast import ContextSlave
|
||||
from pypsyc.server.person import Resource
|
||||
|
||||
|
||||
EXTERN_USER_UNI = SERVER2_UNI.chain(USER1)
|
||||
SET_PACKET = PSYCPacket({'=': VARIABLES})
|
||||
ADD_PACKET = PSYCPacket({'+': VARIABLES})
|
||||
REMOVE_PACKET = PSYCPacket({'-': VARIABLES})
|
||||
|
||||
class TestContextMaster(object):
|
||||
def setup(self):
|
||||
self.server = Mock()
|
||||
self.server.hostname = SERVER1
|
||||
self.server.routing.mrouting_table = self.mrouting_table = {}
|
||||
root = Entity(server=self.server)
|
||||
self.user1 = Entity(root, USER1)
|
||||
self.user1.castmsg = Mock()
|
||||
user2 = Entity(root, USER2)
|
||||
Resource(user2, RESOURCE, Mock())
|
||||
|
||||
def test_extern_member(self):
|
||||
circuit = Mock()
|
||||
self.server.routing.srouting_table = {SERVER2: circuit}
|
||||
|
||||
self.user1.context_master.add_member(EXTERN_USER_UNI)
|
||||
assert self.mrouting_table[USER1_UNI] == set([circuit])
|
||||
|
||||
self.user1.context_master.remove_member(EXTERN_USER_UNI)
|
||||
assert self.mrouting_table[USER1_UNI] == set()
|
||||
|
||||
def test_set_persistent(self):
|
||||
self.user1.context_master.state_set(**VARIABLES)
|
||||
assert self.user1.castmsg.call_args_list == [((SET_PACKET,),),]
|
||||
|
||||
state = self.user1.context_master.add_member(USER2_UNI)
|
||||
assert state == VARIABLES
|
||||
|
||||
def test_persistent_list(self):
|
||||
self.user1.context_master.state_add(**VARIABLES)
|
||||
assert self.user1.castmsg.call_args_list == [((ADD_PACKET,),)]
|
||||
self.user1.castmsg.reset_mock()
|
||||
|
||||
state = self.user1.context_master.add_member(USER2_UNI)
|
||||
assert state == VARIABLES
|
||||
|
||||
self.user1.context_master.state_remove(**VARIABLES)
|
||||
assert self.user1.castmsg.call_args_list == [((REMOVE_PACKET,),)]
|
||||
|
||||
state = self.user1.context_master.add_member(USER2_UNI)
|
||||
assert state == {}
|
||||
|
||||
def test_remove_inheritance(self):
|
||||
self.user1.context_master.state_add(_a='a', _a_b='b')
|
||||
self.user1.context_master.state_remove(_a='a')
|
||||
|
||||
state = self.user1.context_master.add_member(USER2_UNI)
|
||||
assert state == {}
|
||||
|
||||
|
||||
RESOURCE1 = '*resource1'
|
||||
RESOURCE2 = '*resource2'
|
||||
USER1_RESOURCE1_UNI = USER1_UNI.chain(RESOURCE1)
|
||||
USER1_RESOURCE2_UNI = USER1_UNI.chain(RESOURCE2)
|
||||
|
||||
class TestContextSlave(object):
|
||||
@mockified('pypsyc.server.multicast', ['ContextSlaveProtocol'])
|
||||
def setup(self, ContextSlaveProtocol):
|
||||
person = Mock()
|
||||
self.entity = person.entity
|
||||
self.entity._root.uni = SERVER1_UNI
|
||||
self.entity.uni = USER1_UNI
|
||||
self.entity.server.routing.mrouting_table = self.mrouting_table = {}
|
||||
self.entity.children = {}
|
||||
self.entity.children[RESOURCE1] = self.resource1 = Mock()
|
||||
self.entity.children[RESOURCE2] = self.resource2 = Mock()
|
||||
self.protocol = ContextSlaveProtocol.return_value
|
||||
self.protocol.enter.return_value = VARIABLES
|
||||
self.context_slave = ContextSlave(person)
|
||||
assert ContextSlaveProtocol.call_args_list == [((person,),)]
|
||||
|
||||
def test_enter_leave(self):
|
||||
self.context_slave.enter(PLACE_UNI, USER1_RESOURCE1_UNI)
|
||||
assert self.protocol.method_calls == [('enter', (PLACE_UNI,))]
|
||||
assert self.mrouting_table[PLACE_UNI] == set([self.resource1.circuit])
|
||||
assert self.resource1.circuit.method_calls == [
|
||||
('send', ({'_context': PLACE_UNI}, rendered(SET_PACKET)))]
|
||||
self.protocol.reset_mock()
|
||||
|
||||
self.context_slave.leave(PLACE_UNI, USER1_RESOURCE1_UNI)
|
||||
assert self.protocol.method_calls == [('leave', (PLACE_UNI,))]
|
||||
assert self.mrouting_table[PLACE_UNI] == set()
|
||||
|
||||
def test_enter_leave_all_resources(self):
|
||||
self.context_slave.enter(PLACE_UNI)
|
||||
assert self.protocol.method_calls == [('enter', (PLACE_UNI,))]
|
||||
assert self.mrouting_table[PLACE_UNI] == set([self.resource1.circuit,
|
||||
self.resource2.circuit])
|
||||
assert self.resource1.circuit.method_calls == [
|
||||
('send', ({'_context': PLACE_UNI}, rendered(SET_PACKET)),)]
|
||||
assert self.resource2.circuit.method_calls == [
|
||||
('send', ({'_context': PLACE_UNI}, rendered(SET_PACKET)),)]
|
||||
self.protocol.reset_mock()
|
||||
|
||||
self.context_slave.leave(PLACE_UNI)
|
||||
assert self.protocol.method_calls == [('leave', (PLACE_UNI,))]
|
||||
assert self.mrouting_table[PLACE_UNI] == set()
|
||||
|
||||
def test_enter_leave_two_resources(self):
|
||||
extern_circuit = Mock()
|
||||
self.mrouting_table[PLACE_UNI] = set([extern_circuit])
|
||||
|
||||
self.context_slave.enter(PLACE_UNI, USER1_RESOURCE1_UNI)
|
||||
self.context_slave.enter(PLACE_UNI, USER1_RESOURCE2_UNI)
|
||||
assert self.protocol.method_calls == [('enter', (PLACE_UNI,))]
|
||||
assert self.mrouting_table[PLACE_UNI] == set([extern_circuit,
|
||||
self.resource1.circuit,
|
||||
self.resource2.circuit])
|
||||
assert self.resource1.circuit.method_calls == [
|
||||
('send', ({'_context': PLACE_UNI}, rendered(SET_PACKET)))]
|
||||
self.protocol.reset_mock()
|
||||
|
||||
self.context_slave.leave(PLACE_UNI, USER1_RESOURCE1_UNI)
|
||||
assert self.protocol.method_calls == []
|
||||
self.context_slave.leave(PLACE_UNI, USER1_RESOURCE2_UNI)
|
||||
assert self.protocol.method_calls == [('leave', (PLACE_UNI,))]
|
||||
assert self.mrouting_table[PLACE_UNI] == set([extern_circuit])
|
||||
|
||||
def test_enter_leave_extern(self):
|
||||
self.context_slave.enter(EXTERN_USER_UNI, USER1_RESOURCE1_UNI)
|
||||
assert self.protocol.method_calls == [('enter', (EXTERN_USER_UNI,))]
|
||||
assert self.mrouting_table[EXTERN_USER_UNI] == \
|
||||
set([self.resource1.circuit])
|
||||
assert self.resource1.circuit.method_calls == [
|
||||
('send', ({'_context':EXTERN_USER_UNI}, rendered(SET_PACKET)))]
|
||||
self.protocol.reset_mock()
|
||||
|
||||
self.context_slave.leave(EXTERN_USER_UNI, USER1_RESOURCE1_UNI)
|
||||
assert self.protocol.method_calls == [('leave', (EXTERN_USER_UNI,))]
|
||||
assert EXTERN_USER_UNI not in self.mrouting_table
|
||||
|
||||
def test_leave_all(self):
|
||||
self.context_slave.enter(PLACE_UNI, USER1_RESOURCE1_UNI)
|
||||
self.protocol.reset_mock()
|
||||
|
||||
self.context_slave.leave_all()
|
||||
assert self.protocol.method_calls == [('leave', (PLACE_UNI,))]
|
||||
assert self.mrouting_table[PLACE_UNI] == set()
|
||||
|
||||
def test_not_leave(self):
|
||||
self.context_slave.leave(PLACE_UNI)
|
||||
assert self.protocol.method_calls == []
|
|
@ -0,0 +1,268 @@
|
|||
"""
|
||||
:copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
import hmac
|
||||
from hashlib import sha256
|
||||
|
||||
from mock import Mock
|
||||
from nose.tools import assert_raises
|
||||
from tests.constants import (SERVER1, USER1, USER1_UNI, USER2_UNI, RESOURCE,
|
||||
RESOURCE1_UNI, RESOURCE2_UNI, PLACE_UNI, VARIABLES, PASSWORD, MESSAGE,
|
||||
ONLINE, ERROR)
|
||||
from tests.helpers import mockified
|
||||
|
||||
from pypsyc.protocol import (AuthenticationError, FriendshipPendingError,
|
||||
FriendshipEstablishedError, EntryDeniedError)
|
||||
from pypsyc.server import Entity
|
||||
from pypsyc.server.db import Database
|
||||
from pypsyc.server.person import Person
|
||||
|
||||
|
||||
class TestPerson(object):
|
||||
def setup(self):
|
||||
self.server = Mock()
|
||||
self.server.hostname = SERVER1
|
||||
self.server.database = Database(':memory:')
|
||||
|
||||
root = Entity(server=self.server)
|
||||
self.entity = Entity(root, USER1)
|
||||
|
||||
@mockified('pypsyc.server.person', ['RoutingErrorRelaying'])
|
||||
def test_unknown_target_error(self, RoutingErrorRelaying):
|
||||
RESOURCE1 = USER1_UNI.chain('*resource1')
|
||||
RESOURCE2 = USER1_UNI.chain('*resource2')
|
||||
|
||||
Entity(self.entity, '*resource1')
|
||||
Entity(self.entity, '*resource2')
|
||||
routing_error_relaying = RoutingErrorRelaying.return_value
|
||||
person = Person(self.entity)
|
||||
assert RoutingErrorRelaying.call_args_list == [((person,),)]
|
||||
|
||||
person.unknown_target_error(USER2_UNI)
|
||||
assert routing_error_relaying.method_calls == [
|
||||
('relay_unknown_target_error', (RESOURCE1, USER2_UNI)),
|
||||
('relay_unknown_target_error', (RESOURCE2, USER2_UNI))]
|
||||
|
||||
@mockified('pypsyc.server.person', ['RoutingErrorRelaying'])
|
||||
def test_unknown_target_error(self, RoutingErrorRelaying):
|
||||
RESOURCE1 = USER1_UNI.chain('*resource1')
|
||||
RESOURCE2 = USER1_UNI.chain('*resource2')
|
||||
|
||||
Entity(self.entity, '*resource1')
|
||||
Entity(self.entity, '*resource2')
|
||||
routing_error_relaying = RoutingErrorRelaying.return_value
|
||||
person = Person(self.entity)
|
||||
assert RoutingErrorRelaying.call_args_list == [((person,),)]
|
||||
|
||||
person.delivery_failed_error(USER2_UNI, ERROR)
|
||||
assert routing_error_relaying.method_calls == [
|
||||
('relay_delivery_failed_error', (RESOURCE1, USER2_UNI, ERROR)),
|
||||
('relay_delivery_failed_error', (RESOURCE2, USER2_UNI, ERROR))]
|
||||
|
||||
@mockified('pypsyc.server.person', ['LinkingServer', 'FriendshipProtocol',
|
||||
'ContextSlave'])
|
||||
def test_authenticate(self, LinkingServer, FriendshipProtocol,
|
||||
ContextSlave):
|
||||
NONCE = 'random_nonce'
|
||||
DIGEST = hmac.new(PASSWORD, NONCE, sha256).digest()
|
||||
|
||||
circuit = Mock()
|
||||
circuit.allowed_sources = []
|
||||
friendship_protocol = FriendshipProtocol.return_value
|
||||
context_slave = ContextSlave.return_value
|
||||
|
||||
person = Person(self.entity)
|
||||
person.register(PASSWORD)
|
||||
assert LinkingServer.call_args_list == [((person,),)]
|
||||
|
||||
assert_raises(AuthenticationError,
|
||||
person.authenticate, ('hmac', 'wrong', NONCE), None, RESOURCE)
|
||||
|
||||
sql = "INSERT INTO friendships VALUES(?, ?, 'established')"
|
||||
self.server.database.execute(sql, USER1_UNI, USER2_UNI)
|
||||
|
||||
person.authenticate(('hmac', DIGEST, NONCE), circuit, RESOURCE)
|
||||
assert self.entity.children[RESOURCE].circuit == circuit
|
||||
assert circuit.allowed_sources == [RESOURCE1_UNI]
|
||||
friendships = {USER2_UNI: {'state': 'established'}}
|
||||
assert friendship_protocol.method_calls == [
|
||||
('cast_presence', (7,)),
|
||||
('send_friendships', (RESOURCE1_UNI, friendships))]
|
||||
assert context_slave.method_calls == [
|
||||
('enter', (USER2_UNI, RESOURCE1_UNI))]
|
||||
friendship_protocol.reset_mock()
|
||||
context_slave.reset_mock()
|
||||
|
||||
person.unlink(RESOURCE)
|
||||
assert self.entity.children == {}
|
||||
assert friendship_protocol.method_calls == [('cast_presence', (1,))]
|
||||
assert context_slave.method_calls == [('leave_all',)]
|
||||
|
||||
@mockified('pypsyc.server.person', ['MessageRelaying'])
|
||||
def test_message_relay(self, MessageRelaying):
|
||||
message_relaying = MessageRelaying.return_value
|
||||
person = Person(self.entity)
|
||||
assert MessageRelaying.call_args_list == [((person,),)]
|
||||
|
||||
person.private_message_relay(RESOURCE1_UNI, USER2_UNI, MESSAGE)
|
||||
person.private_message_relay(RESOURCE2_UNI, USER2_UNI, MESSAGE)
|
||||
assert message_relaying.method_calls == [
|
||||
('send_private_message', (USER2_UNI, MESSAGE))]
|
||||
|
||||
@mockified('pypsyc.server.person', ['MessageRelaying'])
|
||||
def test_message(self, MessageRelaying):
|
||||
RESOURCE1 = USER1_UNI.chain('*resource1')
|
||||
RESOURCE2 = USER1_UNI.chain('*resource2')
|
||||
|
||||
Entity(self.entity, '*resource1')
|
||||
Entity(self.entity, '*resource2')
|
||||
message_relaying = MessageRelaying.return_value
|
||||
person = Person(self.entity)
|
||||
|
||||
person.private_message(USER2_UNI, MESSAGE)
|
||||
assert message_relaying.method_calls == [
|
||||
('relay_private_message', (USER2_UNI, RESOURCE1, MESSAGE)),
|
||||
('relay_private_message', (USER2_UNI, RESOURCE2, MESSAGE))]
|
||||
|
||||
@mockified('pypsyc.server.person', ['FriendshipProtocol', 'ContextSlave'])
|
||||
def test_outgoing_friendship(self, FriendshipProtocol, ContextSlave):
|
||||
friendship_protocol = FriendshipProtocol.return_value
|
||||
context_slave = ContextSlave.return_value
|
||||
person = Person(self.entity)
|
||||
person.register('')
|
||||
Entity(self.entity, RESOURCE)
|
||||
|
||||
person.client_add_friend(USER2_UNI)
|
||||
fs = {'state': 'pending'}
|
||||
assert friendship_protocol.method_calls == [
|
||||
('establish', (USER2_UNI,)),
|
||||
('send_updated_friendship', (RESOURCE1_UNI, USER2_UNI, fs))]
|
||||
friendship_protocol.reset_mock()
|
||||
assert_raises(FriendshipPendingError, person.client_add_friend,
|
||||
USER2_UNI)
|
||||
|
||||
state = person.friendship_request(USER2_UNI)
|
||||
assert state == 'established'
|
||||
state = person.friendship_request(USER2_UNI)
|
||||
assert state == 'established'
|
||||
fs = {'state': 'established'}
|
||||
assert friendship_protocol.method_calls == [
|
||||
('send_updated_friendship', (RESOURCE1_UNI, USER2_UNI, fs))]
|
||||
assert context_slave.method_calls == [('enter', (USER2_UNI,))]
|
||||
assert_raises(FriendshipEstablishedError, person.client_add_friend,
|
||||
USER2_UNI)
|
||||
|
||||
@mockified('pypsyc.server.person', ['FriendshipProtocol', 'ContextSlave'])
|
||||
def test_incoming_friendship(self, FriendshipProtocol, ContextSlave):
|
||||
friendship_protocol = FriendshipProtocol.return_value
|
||||
context_slave = ContextSlave.return_value
|
||||
person = Person(self.entity)
|
||||
person.register('')
|
||||
Entity(self.entity, RESOURCE)
|
||||
|
||||
state = person.friendship_request(USER2_UNI)
|
||||
assert state == 'pending'
|
||||
state = person.friendship_request(USER2_UNI)
|
||||
assert state == 'pending'
|
||||
fs = {'state': 'offered'}
|
||||
assert friendship_protocol.method_calls == [
|
||||
('send_updated_friendship', (RESOURCE1_UNI, USER2_UNI, fs))]
|
||||
friendship_protocol.reset_mock()
|
||||
|
||||
person.client_add_friend(USER2_UNI)
|
||||
fs = {'state': 'established'}
|
||||
assert friendship_protocol.method_calls == [
|
||||
('establish', (USER2_UNI,)),
|
||||
('send_updated_friendship', (RESOURCE1_UNI, USER2_UNI, fs))]
|
||||
assert context_slave.method_calls == [('enter', (USER2_UNI,))]
|
||||
assert_raises(FriendshipEstablishedError, person.client_add_friend,
|
||||
USER2_UNI)
|
||||
|
||||
@mockified('pypsyc.server.person', ['FriendshipProtocol', 'ContextSlave'])
|
||||
def test_cancel_friendship(self, FriendshipProtocol, ContextSlave):
|
||||
friendship_protocol = FriendshipProtocol.return_value
|
||||
context_slave = ContextSlave.return_value
|
||||
person = Person(self.entity)
|
||||
person.register('')
|
||||
Entity(self.entity, RESOURCE)
|
||||
|
||||
person.friendship_cancel(USER2_UNI)
|
||||
assert context_slave.method_calls == [('leave', (USER2_UNI,))]
|
||||
assert friendship_protocol.method_calls == [
|
||||
('send_removed_friendship', (RESOURCE1_UNI, USER2_UNI))]
|
||||
|
||||
@mockified('pypsyc.server.person', ['FriendshipProtocol', 'ContextSlave'])
|
||||
def test_remove_friend(self, FriendshipProtocol, ContextSlave):
|
||||
friendship_protocol = FriendshipProtocol.return_value
|
||||
context_slave = ContextSlave.return_value
|
||||
person = Person(self.entity)
|
||||
person.register('')
|
||||
Entity(self.entity, RESOURCE)
|
||||
|
||||
person.client_remove_friend(USER2_UNI)
|
||||
assert context_slave.method_calls == [('leave', (USER2_UNI,))]
|
||||
assert friendship_protocol.method_calls == [
|
||||
('remove', (USER2_UNI,)),
|
||||
('send_removed_friendship', (RESOURCE1_UNI, USER2_UNI))]
|
||||
|
||||
@mockified('pypsyc.server.person', ['FriendshipProtocol', 'ContextSlave',
|
||||
'ContextMasterProtocol'])
|
||||
def test_enter_context(self, FriendshipProtocol, ContextSlave,
|
||||
ContextMasterProtocol):
|
||||
cm = self.entity.context_master = Mock()
|
||||
cm.add_member.return_value = VARIABLES
|
||||
person = Person(self.entity)
|
||||
person.register('')
|
||||
person.friendship_request(USER2_UNI)
|
||||
person.client_add_friend(USER2_UNI)
|
||||
assert ContextMasterProtocol.call_args_list == [((person,),)]
|
||||
|
||||
state = person.enter_request(USER2_UNI)
|
||||
assert state == VARIABLES
|
||||
assert cm.method_calls == [('add_member', (USER2_UNI,))]
|
||||
|
||||
def test_enter_context_entry_denied(self):
|
||||
person = Person(self.entity)
|
||||
person.register('')
|
||||
|
||||
assert_raises(EntryDeniedError, person.enter_request, USER2_UNI)
|
||||
|
||||
def test_leave_context(self):
|
||||
cm = self.entity.context_master = Mock()
|
||||
person = Person(self.entity)
|
||||
|
||||
person.leave_context(USER2_UNI)
|
||||
assert cm.method_calls == [('remove_member', (USER2_UNI,))]
|
||||
|
||||
@mockified('pypsyc.server.person', ['FriendshipProtocol'])
|
||||
def test_client_presence(self, FriendshipProtocol):
|
||||
friendship_protocol = FriendshipProtocol.return_value
|
||||
|
||||
person = Person(self.entity)
|
||||
assert FriendshipProtocol.call_args_list == [((person,),)]
|
||||
|
||||
person.client_presence(ONLINE)
|
||||
assert friendship_protocol.method_calls == [
|
||||
('cast_presence', (ONLINE,))]
|
||||
|
||||
@mockified('pypsyc.server.person', ['ContextSlave',
|
||||
'ClientInterfaceProtocol'])
|
||||
def test_client_subscribe(self, ContextSlave, ClientInterfaceProtocol):
|
||||
context_slave = ContextSlave.return_value
|
||||
person = Person(self.entity)
|
||||
assert ContextSlave.call_args_list == [((person,),)]
|
||||
assert ClientInterfaceProtocol.call_args_list == [((person,),)]
|
||||
|
||||
person.client_subscribe(PLACE_UNI, RESOURCE1_UNI)
|
||||
assert context_slave.method_calls == [
|
||||
('enter', (PLACE_UNI, RESOURCE1_UNI))]
|
||||
|
||||
@mockified('pypsyc.server.person', ['ContextSlave'])
|
||||
def test_client_unsubscribe(self, ContextSlave):
|
||||
context_slave = ContextSlave.return_value
|
||||
person = Person(self.entity)
|
||||
|
||||
person.client_unsubscribe(PLACE_UNI, RESOURCE1_UNI)
|
||||
assert context_slave.method_calls == [
|
||||
('leave', (PLACE_UNI, RESOURCE1_UNI))]
|
|
@ -0,0 +1,68 @@
|
|||
"""
|
||||
:copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
from mock import Mock
|
||||
from tests.constants import (SERVER1, USER1_UNI, USER2_UNI, USER1_NICK,
|
||||
RESOURCE1_UNI, RESOURCE2_UNI, PLACE, VARIABLES, MESSAGE)
|
||||
from tests.helpers import mockified
|
||||
|
||||
from pypsyc.server import Entity
|
||||
from pypsyc.server.place import Place
|
||||
|
||||
|
||||
class TestPlace(object):
|
||||
def setup(self):
|
||||
self.server = Mock()
|
||||
self.server.hostname = SERVER1
|
||||
|
||||
root = Entity(server=self.server)
|
||||
self.entity = Entity(root, PLACE)
|
||||
self.entity.context_master = Mock()
|
||||
|
||||
@mockified('pypsyc.server.place', ['ContextProtocol',
|
||||
'ConferencingProtocol'])
|
||||
def test_subscription(self, ContextProtocol, ConferencingProtocol):
|
||||
conferencing_protocol = ConferencingProtocol.return_value
|
||||
self.entity.context_master.add_member.return_value = VARIABLES
|
||||
|
||||
place = Place(self.entity)
|
||||
assert ContextProtocol.call_args_list == [((place,),)]
|
||||
|
||||
state = place.enter_request(USER1_UNI)
|
||||
assert state == VARIABLES
|
||||
assert conferencing_protocol.method_calls == [
|
||||
('cast_member_entered', (USER1_UNI, USER1_NICK))]
|
||||
assert self.entity.context_master.method_calls == [
|
||||
('add_member', (USER1_UNI,))]
|
||||
|
||||
@mockified('pypsyc.server.place', ['ConferencingProtocol'])
|
||||
def test_leave(self, ConferencingProtocol):
|
||||
conferencing_protocol = ConferencingProtocol.return_value
|
||||
place = Place(self.entity)
|
||||
|
||||
place.enter_request(USER1_UNI)
|
||||
self.entity.context_master.reset_mock()
|
||||
conferencing_protocol.reset_mock()
|
||||
|
||||
place.leave_context(USER1_UNI)
|
||||
assert self.entity.context_master.method_calls == [
|
||||
('remove_member', (USER1_UNI,))]
|
||||
assert conferencing_protocol.method_calls == [
|
||||
('cast_member_left', (USER1_UNI,))]
|
||||
|
||||
@mockified('pypsyc.server.place', ['ConferencingProtocol'])
|
||||
def test_public_message(self, ConferencingProtocol):
|
||||
conferencing_protocol = ConferencingProtocol.return_value
|
||||
|
||||
place = Place(self.entity)
|
||||
assert ConferencingProtocol.call_args_list == [((place,),)]
|
||||
place.enter_request(USER1_UNI)
|
||||
place.enter_request(USER2_UNI)
|
||||
place.leave_context(USER2_UNI)
|
||||
conferencing_protocol.reset_mock()
|
||||
|
||||
place.public_message(RESOURCE2_UNI, MESSAGE)
|
||||
place.public_message(RESOURCE1_UNI, MESSAGE)
|
||||
assert conferencing_protocol.method_calls == [
|
||||
('cast_public_message', (USER1_UNI, MESSAGE))]
|
|
@ -0,0 +1,21 @@
|
|||
"""
|
||||
:copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
from mock import Mock
|
||||
from tests.constants import SERVER1
|
||||
|
||||
from pypsyc.server import Entity
|
||||
from pypsyc.server.root import Root
|
||||
|
||||
|
||||
class TestRoot(object):
|
||||
def setup(self):
|
||||
server = Mock()
|
||||
server.hostname = SERVER1
|
||||
|
||||
root_entity = Entity(server=server)
|
||||
self.root = Root(root_entity)
|
||||
|
||||
def test_root(self):
|
||||
pass
|
|
@ -0,0 +1,353 @@
|
|||
"""
|
||||
:copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
from mock import Mock
|
||||
from nose.tools import assert_raises
|
||||
from tests.constants import (SERVER1, SERVER2, SERVER1_UNI, SERVER2_UNI,
|
||||
SERVER3_UNI, USER1, USER2, USER1_UNI, USER2_UNI, RESOURCE, RESOURCE1_UNI,
|
||||
RESOURCE2_UNI, PLACE_UNI, INTERFACE, IP, PORT, CONTENT)
|
||||
from tests.helpers import (inited_header, connect_circuits, mockified,
|
||||
AsyncMethod, PlaceHolder)
|
||||
|
||||
from pypsyc.core.mmp import Uni
|
||||
from pypsyc.protocol import Error
|
||||
from pypsyc.server.routing import (InvalidTargetError, InvalidSourceError,
|
||||
ServerCircuit, _TreeNode, Routing)
|
||||
from pypsyc.util import DNSError
|
||||
|
||||
|
||||
class TestServerCircuit(object):
|
||||
def setup(self):
|
||||
self.sc = ServerCircuit()
|
||||
self.sc.factory = self.routing = Mock()
|
||||
self.sc.psyc = Mock()
|
||||
self.sc.allowed_sources.append(SERVER2_UNI)
|
||||
|
||||
def _send(self, header):
|
||||
self.sc.packet_received(inited_header(header), CONTENT)
|
||||
|
||||
def test_withtarget(self):
|
||||
HEADER1 = {'_target': SERVER1_UNI}
|
||||
HEADER2 = {'_target': SERVER1_UNI, '_source': SERVER2_UNI}
|
||||
HEADER3 = {'_target': SERVER1_UNI, '_source': SERVER3_UNI}
|
||||
|
||||
self._send(HEADER1)
|
||||
assert self.routing.method_calls == [
|
||||
('route_singlecast', (HEADER1, CONTENT))]
|
||||
|
||||
self._send(HEADER2)
|
||||
assert self.routing.method_calls == [
|
||||
('route_singlecast', (HEADER1, CONTENT)),
|
||||
('route_singlecast', (HEADER2, CONTENT))]
|
||||
|
||||
self._send(HEADER3)
|
||||
assert self.routing.method_calls == [
|
||||
('route_singlecast', (HEADER1, CONTENT)),
|
||||
('route_singlecast', (HEADER2, CONTENT))]
|
||||
|
||||
def test_withouttarget(self):
|
||||
HEADER1 = {}
|
||||
HEADER2 = {'_source': SERVER2_UNI}
|
||||
HEADER3 = {'_source': SERVER3_UNI}
|
||||
|
||||
self._send(HEADER1)
|
||||
assert self.routing.method_calls == []
|
||||
assert self.sc.psyc.method_calls == [
|
||||
('handle_packet', (HEADER1, CONTENT))]
|
||||
|
||||
self._send(HEADER2)
|
||||
assert self.routing.method_calls == []
|
||||
assert self.sc.psyc.method_calls == [
|
||||
('handle_packet', (HEADER1, CONTENT)),
|
||||
('handle_packet', (HEADER2, CONTENT))]
|
||||
|
||||
self._send(HEADER3)
|
||||
assert self.routing.method_calls == []
|
||||
assert self.sc.psyc.method_calls == [
|
||||
('handle_packet', (HEADER1, CONTENT)),
|
||||
('handle_packet', (HEADER2, CONTENT))]
|
||||
|
||||
def test_context(self):
|
||||
HEADER1 = {'_context': PLACE_UNI}
|
||||
HEADER2 = {'_context': PLACE_UNI, '_target': USER1_UNI}
|
||||
|
||||
self._send(HEADER1)
|
||||
assert self.routing.method_calls == [
|
||||
('route_multicast', (HEADER1, CONTENT))]
|
||||
|
||||
self._send(HEADER2)
|
||||
assert self.routing.method_calls == [
|
||||
('route_multicast', (HEADER1, CONTENT)),
|
||||
('route_singlecast', (HEADER2, CONTENT))]
|
||||
|
||||
def test_context_withsource(self):
|
||||
HEADER1 = {'_context': PLACE_UNI, '_source': USER1_UNI}
|
||||
HEADER2 = {'_context': PLACE_UNI, '_source': USER2_UNI,
|
||||
'_target': USER1_UNI}
|
||||
|
||||
assert_raises(NotImplementedError, self._send, HEADER1)
|
||||
assert self.routing.method_calls == []
|
||||
|
||||
assert_raises(NotImplementedError, self._send, HEADER2)
|
||||
assert self.routing.method_calls == []
|
||||
|
||||
def test_verification(self):
|
||||
sc1, sc2 = connect_circuits(ServerCircuit(), ServerCircuit())
|
||||
sc2.factory = Mock()
|
||||
|
||||
sc1.request_verification(SERVER1_UNI, SERVER2_UNI)
|
||||
assert sc2.factory.method_calls == [
|
||||
('verify_address', (sc2, SERVER1_UNI, SERVER2_UNI))]
|
||||
assert type(sc2.factory.method_calls[0][1][1]) is Uni
|
||||
assert sc1.allowed_sources == [SERVER2_UNI]
|
||||
assert sc2.allowed_sources == [SERVER1_UNI]
|
||||
assert type(sc2.allowed_sources[0]) is Uni
|
||||
|
||||
def test_verification_invalid_source(self):
|
||||
sc1, sc2 = connect_circuits(ServerCircuit(), ServerCircuit())
|
||||
sc2.factory = Mock()
|
||||
sc2.factory.verify_address.side_effect = InvalidSourceError
|
||||
|
||||
assert_raises(InvalidSourceError, sc1.request_verification,
|
||||
SERVER1_UNI, SERVER2_UNI)
|
||||
assert sc1.allowed_sources == []
|
||||
assert sc2.allowed_sources == []
|
||||
|
||||
def test_verification_invalid_target(self):
|
||||
sc1, sc2 = connect_circuits(ServerCircuit(), ServerCircuit())
|
||||
sc2.factory = Mock()
|
||||
sc2.factory.verify_address.side_effect = InvalidTargetError
|
||||
|
||||
assert_raises(InvalidTargetError, sc1.request_verification,
|
||||
SERVER1_UNI, SERVER2_UNI)
|
||||
assert sc1.allowed_sources == []
|
||||
assert sc2.allowed_sources == []
|
||||
|
||||
def test_connection_lost(self):
|
||||
root = self.routing.root = _TreeNode()
|
||||
person = Mock()
|
||||
person_entity = _TreeNode(root, USER1)
|
||||
person_entity.packages = {'person': person}
|
||||
self.sc.allowed_sources.append(RESOURCE1_UNI)
|
||||
|
||||
self.routing.srouting_table = {SERVER2: Mock()}
|
||||
self.sc.connectionLost(None)
|
||||
assert self.routing.srouting_table == {}
|
||||
assert person.method_calls == [('unlink', (RESOURCE,))]
|
||||
|
||||
|
||||
def test_treenode():
|
||||
root = _TreeNode()
|
||||
assert root._root == root
|
||||
|
||||
n1 = _TreeNode(root, 'n1')
|
||||
assert root.children == {'n1': n1}
|
||||
assert n1._parent == root
|
||||
assert n1._root == root
|
||||
|
||||
n2 = _TreeNode(n1, 'n2')
|
||||
assert n1.children == {'n2': n2}
|
||||
assert n2._parent == n1
|
||||
assert n2._root == root
|
||||
|
||||
|
||||
class StubEntity(_TreeNode):
|
||||
def __init__(self, *args, **kwds):
|
||||
_TreeNode.__init__(self, *args, **kwds)
|
||||
self.headers = []
|
||||
|
||||
def handle_packet(self, header, contents):
|
||||
self.headers.append(header)
|
||||
|
||||
EXTERN_HEADER = {'_source': USER1_UNI, '_target': SERVER2_UNI}
|
||||
|
||||
class TestRouting(object):
|
||||
def test_sroute_local(self):
|
||||
HEADER1 = {'_target': SERVER1_UNI}
|
||||
HEADER2 = {'_target': USER1_UNI}
|
||||
HEADER3 = {'_target': RESOURCE2_UNI}
|
||||
|
||||
root = StubEntity()
|
||||
user1 = StubEntity(root, USER1)
|
||||
user2 = StubEntity(root, USER2)
|
||||
home = StubEntity(user2, RESOURCE)
|
||||
routing = Routing(SERVER1, INTERFACE)
|
||||
routing.init(root)
|
||||
|
||||
routing.route_singlecast(inited_header(HEADER1), CONTENT)
|
||||
routing.route_singlecast(inited_header(HEADER2), CONTENT)
|
||||
routing.route_singlecast(inited_header(HEADER3), CONTENT)
|
||||
assert root.headers == [HEADER1]
|
||||
assert user1.headers == [HEADER2]
|
||||
assert user2.headers == []
|
||||
assert home.headers == [HEADER3]
|
||||
|
||||
def test_sroute_unkown_target(self):
|
||||
HEADER1 = inited_header({'_source': USER1_UNI, '_target': USER2_UNI})
|
||||
HEADER2 = inited_header({'_target': USER2_UNI, '_tag': 'tag'})
|
||||
KWDS = {'mc': '_error_unknown_target', '_uni': USER2_UNI, 'data': None}
|
||||
|
||||
root = StubEntity()
|
||||
root.sendmsg = Mock()
|
||||
routing = Routing(SERVER1, INTERFACE)
|
||||
routing.init(root)
|
||||
|
||||
routing.route_singlecast(HEADER1, CONTENT)
|
||||
routing.route_singlecast(HEADER2, CONTENT)
|
||||
assert root.sendmsg.call_args_list == [
|
||||
((USER1_UNI, None, None), KWDS),
|
||||
((None, None, {'_tag_relay': 'tag'}), KWDS)]
|
||||
assert root.headers == []
|
||||
|
||||
def test_sroute_extern(self):
|
||||
routing = Routing(SERVER1, INTERFACE)
|
||||
circuit = routing.srouting_table[SERVER2] = Mock()
|
||||
|
||||
routing.route_singlecast(inited_header(EXTERN_HEADER), CONTENT)
|
||||
assert circuit.method_calls == [(('send'), (EXTERN_HEADER, CONTENT))]
|
||||
|
||||
@mockified('pypsyc.server.routing', ['resolve_hostname', 'connect'])
|
||||
def test_sroute_extern_queued(self, resolve_hostname, connect):
|
||||
resolve_hostname.return_value = IP, PORT
|
||||
connected = connect.side_effect = AsyncMethod()
|
||||
root = Mock()
|
||||
root.uni = SERVER1_UNI
|
||||
routing = Routing(SERVER1, INTERFACE)
|
||||
routing.init(root)
|
||||
|
||||
routing.route_singlecast(inited_header(EXTERN_HEADER), CONTENT)
|
||||
routing.route_singlecast(inited_header(EXTERN_HEADER), CONTENT)
|
||||
assert connect.call_args_list == [
|
||||
((IP, PORT, routing), {'bindAddress': (INTERFACE, 0)})]
|
||||
|
||||
circuit = Mock()
|
||||
connected.callback(circuit)
|
||||
assert circuit.method_calls == [
|
||||
('request_verification', (SERVER1_UNI, SERVER2_UNI)),
|
||||
('send', (EXTERN_HEADER, CONTENT)),
|
||||
('send', (EXTERN_HEADER, CONTENT))]
|
||||
assert type(circuit.method_calls[0][1][1]) is Uni
|
||||
assert routing.srouting_table == {SERVER2: circuit}
|
||||
assert routing.queues == {}
|
||||
|
||||
@mockified('pypsyc.server.routing', ['resolve_hostname'])
|
||||
def test_sroute_extern_resolution_fail(self, resolve_hostname):
|
||||
KWDS = {'mc': '_failure_unsuccessful_delivery', '_uni': SERVER2_UNI}
|
||||
resolved = resolve_hostname.side_effect = AsyncMethod()
|
||||
data_ph = KWDS['data'] = PlaceHolder()
|
||||
root = Mock()
|
||||
routing = Routing(SERVER1, INTERFACE)
|
||||
routing.init(root)
|
||||
|
||||
routing.route_singlecast(inited_header(EXTERN_HEADER), CONTENT)
|
||||
routing.route_singlecast(inited_header(EXTERN_HEADER), CONTENT)
|
||||
resolved.errback(DNSError)
|
||||
assert root.method_calls == [
|
||||
('sendmsg', (USER1_UNI, None, None), KWDS)] * 2
|
||||
assert 'resolve' in data_ph.obj
|
||||
assert routing.queues == {}
|
||||
|
||||
@mockified('pypsyc.server.routing', ['resolve_hostname', 'connect'])
|
||||
def test_sroute_extern_connection_fail(self, resolve_hostname, connect):
|
||||
KWDS = {'mc': '_failure_unsuccessful_delivery', '_uni': SERVER2_UNI}
|
||||
resolve_hostname.return_value = None, None
|
||||
connected = connect.side_effect = AsyncMethod()
|
||||
data_ph = KWDS['data'] = PlaceHolder()
|
||||
root = Mock()
|
||||
routing = Routing(SERVER1, INTERFACE)
|
||||
routing.init(root)
|
||||
|
||||
routing.route_singlecast(inited_header(EXTERN_HEADER), CONTENT)
|
||||
connected.errback(Exception('error'))
|
||||
assert root.method_calls == [
|
||||
('sendmsg', (USER1_UNI, None, None), KWDS)]
|
||||
assert 'connect' in data_ph.obj
|
||||
assert routing.queues == {}
|
||||
|
||||
@mockified('pypsyc.server.routing', ['resolve_hostname', 'connect'])
|
||||
def test_sroute_extern_verification_fail(self, resolve_hostname, connect):
|
||||
KWDS = {'mc': '_failure_unsuccessful_delivery', '_uni': SERVER2_UNI}
|
||||
resolve_hostname.return_value = None, None
|
||||
circuit = connect.return_value
|
||||
verified = circuit.request_verification.side_effect = AsyncMethod()
|
||||
data_ph = KWDS['data'] = PlaceHolder()
|
||||
root = Mock()
|
||||
routing = Routing(SERVER1, INTERFACE)
|
||||
routing.init(root)
|
||||
|
||||
routing.route_singlecast(inited_header(EXTERN_HEADER), CONTENT)
|
||||
verified.errback(Error)
|
||||
assert root.method_calls == [
|
||||
('sendmsg', (USER1_UNI, None, None), KWDS)]
|
||||
assert 'verify' in data_ph.obj
|
||||
assert routing.queues == {}
|
||||
|
||||
@mockified('pypsyc.server.routing', ['resolve_hostname', 'connect'])
|
||||
def test_sroute_extern_no_source(self, resolve_hostname, connect):
|
||||
header = inited_header({'_target': SERVER2_UNI})
|
||||
header.source = Mock()
|
||||
routing = Routing(SERVER1, INTERFACE)
|
||||
|
||||
routing.route_singlecast(header, CONTENT)
|
||||
header['_source'] = ''
|
||||
routing.route_singlecast(header, CONTENT)
|
||||
assert resolve_hostname.call_args_list == []
|
||||
assert connect.call_args_list == []
|
||||
assert routing.srouting_table == {}
|
||||
assert routing.queues == {}
|
||||
|
||||
def test_mroute(self):
|
||||
HEADER = {'_context': PLACE_UNI}
|
||||
circuit1 = Mock()
|
||||
circuit2 = Mock()
|
||||
routing = Routing(SERVER1, INTERFACE)
|
||||
routing.mrouting_table[PLACE_UNI] = [circuit1, circuit2]
|
||||
|
||||
routing.route_multicast(inited_header(HEADER), iter(CONTENT))
|
||||
assert circuit1.method_calls == [('send', (HEADER, CONTENT))]
|
||||
assert circuit2.method_calls == [('send', (HEADER, CONTENT))]
|
||||
|
||||
@mockified('pypsyc.server.routing', ['reactor'])
|
||||
def test_listen(self, reactor):
|
||||
routing = Routing(SERVER1, INTERFACE)
|
||||
routing.listen(PORT)
|
||||
assert reactor.method_calls == [
|
||||
('listenTCP', (PORT, routing), {'interface': INTERFACE})]
|
||||
|
||||
def _setup_verification(self):
|
||||
root = Mock()
|
||||
root.uni = SERVER1_UNI
|
||||
routing = Routing(SERVER1, INTERFACE)
|
||||
routing.init(root)
|
||||
circuit = Mock()
|
||||
circuit.transport.client = IP, 35771
|
||||
return routing, circuit
|
||||
|
||||
@mockified('pypsyc.server.routing', ['resolve_hostname'])
|
||||
def test_verification(self, resolve_hostname):
|
||||
resolve_hostname.return_value = IP, PORT
|
||||
routing, circuit = self._setup_verification()
|
||||
|
||||
routing.verify_address(circuit, SERVER2_UNI, SERVER1_UNI)
|
||||
assert resolve_hostname.call_args_list == [((SERVER2,),)]
|
||||
assert routing.srouting_table == {SERVER2: circuit}
|
||||
|
||||
@mockified('pypsyc.server.routing', ['resolve_hostname'])
|
||||
def test_verification_invalid_target(self, resolve_hostname):
|
||||
routing, circuit = self._setup_verification()
|
||||
|
||||
assert_raises(InvalidTargetError, routing.verify_address,
|
||||
circuit, SERVER2_UNI, SERVER3_UNI)
|
||||
assert resolve_hostname.call_args_list == []
|
||||
assert routing.srouting_table == {}
|
||||
|
||||
@mockified('pypsyc.server.routing', ['resolve_hostname'])
|
||||
def test_verification_invalid_source(self, resolve_hostname):
|
||||
resolve_hostname.return_value = '10.0.0.2', PORT
|
||||
routing, circuit = self._setup_verification()
|
||||
|
||||
assert_raises(InvalidSourceError, routing.verify_address,
|
||||
circuit, SERVER2_UNI, SERVER1_UNI)
|
||||
assert resolve_hostname.call_args_list == [((SERVER2,),)]
|
||||
assert routing.srouting_table == {}
|
|
@ -0,0 +1,134 @@
|
|||
"""
|
||||
:copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
from contextlib import contextmanager
|
||||
|
||||
from mock import Mock
|
||||
from nose.tools import assert_raises
|
||||
from tests.constants import (SERVER1, SERVER1_UNI, USER1, USER1_UNI, PLACE,
|
||||
INTERFACE, PORT, PASSWORD)
|
||||
from tests.helpers import mockified, rendered, AsyncMethod
|
||||
|
||||
from pypsyc.core.psyc import PSYCPacket
|
||||
from pypsyc.server import Entity, Server
|
||||
from pypsyc.server.routing import _TreeNode
|
||||
|
||||
|
||||
class TestEntity(object):
|
||||
def setup(self):
|
||||
self.server = Mock()
|
||||
self.server.hostname = SERVER1
|
||||
self.entity = Entity(server=self.server)
|
||||
|
||||
def test_uni(self):
|
||||
assert self.entity.uni == SERVER1_UNI
|
||||
assert Entity(self.entity, USER1).uni == USER1_UNI
|
||||
|
||||
def test_castmsg(self):
|
||||
HEADER = {'_context': self.entity.uni}
|
||||
PACKET = PSYCPacket(mc='_message_public', data="Hello")
|
||||
|
||||
self.entity.castmsg(PACKET)
|
||||
assert self.server.routing.method_calls == [
|
||||
('route_multicast', (HEADER, rendered(PACKET)))]
|
||||
|
||||
def test_castmsg_kwds(self):
|
||||
HEADER = {'_context': self.entity.uni}
|
||||
PACKET = PSYCPacket(mc='_message_public', data="Hello")
|
||||
|
||||
self.entity.castmsg(mc='_message_public', data="Hello")
|
||||
assert self.server.routing.method_calls == [
|
||||
('route_multicast', (HEADER, rendered(PACKET)))]
|
||||
|
||||
|
||||
def StubEntity(*args, **kwds):
|
||||
entity = _TreeNode(*args, **kwds)
|
||||
entity.packages = {}
|
||||
return entity
|
||||
|
||||
class TestServer(object):
|
||||
@mockified('pypsyc.server', ['Routing', 'Entity', 'run_webif', 'signal'])
|
||||
def test_server1(self, Routing, Entity, run_webif, signal):
|
||||
WEBIF_PORT = 8080
|
||||
routing = Routing.return_value
|
||||
root = Entity.return_value = StubEntity()
|
||||
run_webif.side_effect = AsyncMethod()
|
||||
|
||||
server = Server(SERVER1, INTERFACE, PORT, WEBIF_PORT, ':memory:')
|
||||
assert Routing.call_args_list == [((SERVER1, INTERFACE),)]
|
||||
assert Entity.call_args_list == [({'server': server},)]
|
||||
assert routing.method_calls == [('init', (root,)), ('listen', (PORT,))]
|
||||
assert run_webif.call_args_list == [
|
||||
((server, INTERFACE, WEBIF_PORT, None),)]
|
||||
assert signal.signal.called
|
||||
|
||||
with mockified('pypsyc.server', ['iter_entry_points', 'Entity']) as x:
|
||||
with self._test_load_package(root, *x) as packages:
|
||||
assert server.add_package(None, 'package') == packages[0]
|
||||
assert server.add_place('place') == packages[1]
|
||||
assert server.add_package(PLACE, 'package') == packages[2]
|
||||
assert server.add_package(USER1, 'package') == packages[3]
|
||||
person = server.register_person('user1', PASSWORD)
|
||||
assert person == packages[4]
|
||||
assert person.method_calls == [('register', (PASSWORD,))]
|
||||
assert_raises(AssertionError, server.add_package, None, 'package')
|
||||
|
||||
self._test_server2(server.database)
|
||||
|
||||
@mockified('pypsyc.server', ['Routing', 'Entity', 'Database', 'run_webif'])
|
||||
def _test_server2(self, database, Routing, Entity, Database, run_webif):
|
||||
routing = Routing.return_value
|
||||
root = Entity.return_value = StubEntity()
|
||||
Database.return_value = database
|
||||
executed = database.execute = AsyncMethod()
|
||||
|
||||
server = Server(SERVER1, INTERFACE, 0, 0, ':memory:')
|
||||
assert routing.method_calls == [('init', (root,))]
|
||||
assert run_webif.call_args_list == []
|
||||
|
||||
with mockified('pypsyc.server', ['iter_entry_points', 'Entity']) as x:
|
||||
with self._test_load_package(root, *x):
|
||||
executed.callback()
|
||||
assert_raises(AssertionError, server.add_package, None, 'package1')
|
||||
|
||||
@contextmanager
|
||||
def _test_load_package(self, root, iter_entry_points, Entity):
|
||||
package_classes = [Mock(), Mock(), Mock(), Mock(), Mock()]
|
||||
entrypoint = Mock()
|
||||
entrypoint.load.side_effect = iter(package_classes).next
|
||||
iter_entry_points.return_value = [entrypoint]
|
||||
Entity.side_effect = StubEntity
|
||||
|
||||
packages = [package.return_value for package in package_classes]
|
||||
yield packages
|
||||
assert Entity.call_args_list == [((root, PLACE),), ((root, USER1),)]
|
||||
assert iter_entry_points.call_args_list == [
|
||||
(('pypsyc.server.packages', 'package'),),
|
||||
(('pypsyc.server.packages', 'place'),),
|
||||
(('pypsyc.server.packages', 'package'),),
|
||||
(('pypsyc.server.packages', 'package'),),
|
||||
(('pypsyc.server.packages', 'person'),)]
|
||||
place = root.children[PLACE]
|
||||
person = root.children[USER1]
|
||||
assert package_classes[0].call_args_list == [((root,),)]
|
||||
assert package_classes[1].call_args_list == [((place,),)]
|
||||
assert package_classes[2].call_args_list == [((place,),)]
|
||||
assert package_classes[3].call_args_list == [((person,),)]
|
||||
assert package_classes[4].call_args_list == [((person,),)]
|
||||
assert root.packages == {'package': packages[0]}
|
||||
assert place.packages == {'place': packages[1], 'package': packages[2]}
|
||||
assert person.packages == {'package': packages[3],
|
||||
'person': packages[4]}
|
||||
|
||||
@mockified('pypsyc.server', ['Routing', 'Entity', 'Database', 'signal',
|
||||
'reactor'])
|
||||
def test_shutdown(self, Routing, Entity, Database, signal, reactor):
|
||||
database = Database.return_value
|
||||
database.fetch.return_value = ()
|
||||
server = Server(SERVER1, INTERFACE, 0, 0, ':memory')
|
||||
database.reset_mock()
|
||||
|
||||
server.shutdown()
|
||||
assert database.method_calls == [('stop',)]
|
||||
assert reactor.method_calls == [('stop',)]
|
|
@ -0,0 +1,139 @@
|
|||
"""
|
||||
:copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
from mock import Mock, sentinel
|
||||
from nose.tools import assert_raises
|
||||
from tests.constants import USER1, USER1_NICK, PASSWORD
|
||||
from tests.helpers import mockified, PlaceHolder
|
||||
|
||||
from pypsyc.server import webif
|
||||
from pypsyc.server.db import Database
|
||||
from pypsyc.server.webif import app, PersistentStore, run_webif
|
||||
|
||||
|
||||
class TestViews(object):
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
app.secret_key = 'testing key'
|
||||
|
||||
def setup(self):
|
||||
self.server = webif.server = Mock()
|
||||
self.server.root.children = {}
|
||||
self.client = app.test_client()
|
||||
|
||||
def test_index(self):
|
||||
rv = self.client.get('/')
|
||||
assert 'register' in rv.data
|
||||
|
||||
def _register(self, username, password, password2):
|
||||
return self.client.post('/register', data=dict(
|
||||
username=username, password=password, password2=password2
|
||||
), follow_redirects=True)
|
||||
|
||||
def test_register_get(self):
|
||||
rv = self.client.get('/register')
|
||||
assert 'Username' in rv.data
|
||||
assert 'Register' in rv.data
|
||||
|
||||
def test_register_post(self):
|
||||
rv = self._register(USER1_NICK, PASSWORD, PASSWORD)
|
||||
assert 'you were registered' in rv.data
|
||||
assert self.server.method_calls == [
|
||||
('register_person', (USER1_NICK, PASSWORD))]
|
||||
|
||||
def test_register_nousername(self):
|
||||
rv = self._register('', '', '')
|
||||
assert 'please specify a valid username' in rv.data
|
||||
|
||||
def test_register_username_inuse(self):
|
||||
self.server.root.children = {USER1: True}
|
||||
|
||||
rv = self._register(USER1_NICK, '', '')
|
||||
assert 'username already in use' in rv.data
|
||||
|
||||
def test_register_nopassword(self):
|
||||
rv = self._register(USER1_NICK, '', '')
|
||||
assert 'your password must have at least 6 characters' in rv.data
|
||||
|
||||
def test_register_shortpassword(self):
|
||||
rv = self._register(USER1_NICK, 'passw', 'passw')
|
||||
assert 'your password must have at least 6 characters' in rv.data
|
||||
|
||||
def test_register_unmatching_passwords(self):
|
||||
rv = self._register(USER1_NICK, PASSWORD, 'password2')
|
||||
assert 'the two passwords do not match' in rv.data
|
||||
|
||||
|
||||
def test_persistent_store():
|
||||
webif.server = Mock()
|
||||
webif.server.database = Database(':memory:')
|
||||
|
||||
store = PersistentStore()
|
||||
assert_raises(KeyError, store.__getitem__, 'key')
|
||||
assert dict(store) == {}
|
||||
assert len(store) == 0
|
||||
|
||||
store['key'] = 'value'
|
||||
assert store['key'] == 'value'
|
||||
assert len(store) == 1
|
||||
|
||||
store['key'] = 'value2'
|
||||
assert dict(store) == {'key': 'value2'}
|
||||
assert len(store) == 1
|
||||
|
||||
del store['key']
|
||||
assert dict(store) == {}
|
||||
assert len(store) == 0
|
||||
|
||||
|
||||
@mockified('pypsyc.server.webif', ['PersistentStore', 'urandom',
|
||||
'WSGIResource', 'Site', 'reactor',
|
||||
'greenlet'])
|
||||
def test_run_webif_ssl(PersistentStore, urandom, WSGIResource, Site, reactor,
|
||||
greenlet):
|
||||
store = PersistentStore.return_value = {'secret_key': 'key1'*5}
|
||||
reactor_ph = PlaceHolder()
|
||||
threadpool_ph = PlaceHolder()
|
||||
wsgi_resource = WSGIResource.return_value
|
||||
site = Site.return_value
|
||||
|
||||
run_webif(sentinel.server, sentinel.interface, sentinel.port,
|
||||
sentinel.context_factory)
|
||||
assert webif.server == sentinel.server
|
||||
assert PersistentStore.call_args_list == [()]
|
||||
assert webif.store == store
|
||||
assert app.secret_key == 'key1'*5
|
||||
assert not urandom.called
|
||||
assert WSGIResource.call_args_list == [
|
||||
((reactor_ph, threadpool_ph, app.wsgi_app),)]
|
||||
assert Site.call_args_list == [((wsgi_resource,),)]
|
||||
assert reactor.method_calls == [
|
||||
('listenSSL', (sentinel.port, site, sentinel.context_factory),
|
||||
{'interface': sentinel.interface})]
|
||||
|
||||
func = Mock()
|
||||
reactor_ph.obj.callFromThread(func, sentinel.a, b=sentinel.b)
|
||||
assert func.call_args_list == [((sentinel.a,), {'b': sentinel.b})]
|
||||
|
||||
threadpool_ph.obj.callInThread(sentinel.func, sentinel.a, b=sentinel.b)
|
||||
assert greenlet.call_args_list == [((sentinel.func,),)]
|
||||
assert greenlet.return_value.method_calls == [
|
||||
('switch', (sentinel.a,), {'b': sentinel.b})]
|
||||
|
||||
|
||||
@mockified('pypsyc.server.webif', ['PersistentStore', 'urandom',
|
||||
'WSGIResource', 'Site', 'reactor'])
|
||||
def test_run_webif_tcp(PersistentStore, urandom, WSGIResource, Site, reactor):
|
||||
store = PersistentStore.return_value = {}
|
||||
rand = urandom.return_value = 'key2'*5
|
||||
wsgi_resource = WSGIResource.return_value
|
||||
site = Site.return_value
|
||||
|
||||
run_webif(sentinel.server, sentinel.interface, sentinel.port, None)
|
||||
assert urandom.call_args_list == [((20,),)]
|
||||
assert app.secret_key == rand
|
||||
assert store['secret_key'] == rand
|
||||
assert Site.call_args_list == [((wsgi_resource,),)]
|
||||
assert reactor.method_calls == [
|
||||
('listenTCP', (sentinel.port, site), {'interface': 'localhost'})]
|
|
@ -0,0 +1,206 @@
|
|||
"""
|
||||
:copyright: 2010 by Manuel Jacob
|
||||
:license: MIT
|
||||
"""
|
||||
from greenlet import greenlet
|
||||
from mock import Mock, sentinel
|
||||
from nose.tools import assert_raises
|
||||
from tests.constants import SERVER1, IP, PORT
|
||||
from tests.helpers import mockified, check_success, StubException
|
||||
from twisted.internet.defer import Deferred, fail
|
||||
from twisted.names.dns import RRHeader, Record_A, Record_SRV
|
||||
from twisted.names.error import DNSNameError
|
||||
from twisted.python.failure import Failure
|
||||
|
||||
from pypsyc.util import (scheduler, schedule, Waiter, DNSError,
|
||||
resolve_hostname, _PSYCConnector, connect, Event)
|
||||
|
||||
|
||||
@mockified('pypsyc.util', ['greenlet'])
|
||||
def test_schedule_scheduler(greenlet):
|
||||
func = Mock()
|
||||
|
||||
schedule(func)
|
||||
assert greenlet.call_args_list == [((func, scheduler),)]
|
||||
assert greenlet.return_value.method_calls == [('switch',)]
|
||||
|
||||
orig_greenlet = greenlet
|
||||
@mockified('pypsyc.util', ['greenlet', 'reactor'])
|
||||
def test_schedule_nonscheduler(greenlet, reactor):
|
||||
func = Mock()
|
||||
|
||||
orig_greenlet(schedule).switch(func)
|
||||
assert greenlet.call_args_list == [((func, scheduler),)]
|
||||
assert reactor.method_calls == [
|
||||
('callLater', (0, greenlet.return_value.switch))]
|
||||
|
||||
|
||||
class TestWaiter(object):
|
||||
def test_callback_sync(self):
|
||||
waiter = Waiter()
|
||||
|
||||
waiter.callback(sentinel.value)
|
||||
ret = waiter.get()
|
||||
assert ret == sentinel.value
|
||||
|
||||
def test_errback_sync(self):
|
||||
e = StubException()
|
||||
waiter = Waiter()
|
||||
|
||||
waiter.errback(e)
|
||||
assert_raises(StubException, waiter.get)
|
||||
|
||||
@check_success
|
||||
def test_callback_async(self):
|
||||
waiter = Waiter()
|
||||
|
||||
def _test():
|
||||
ret = waiter.get()
|
||||
assert ret == sentinel.value
|
||||
self.success = True
|
||||
greenlet(_test).switch()
|
||||
waiter.callback(sentinel.value)
|
||||
|
||||
@check_success
|
||||
def test_errback_async(self):
|
||||
e = StubException()
|
||||
waiter = Waiter()
|
||||
|
||||
def _test():
|
||||
assert_raises(StubException, waiter.get)
|
||||
self.success = True
|
||||
greenlet(_test).switch()
|
||||
waiter.errback(e)
|
||||
|
||||
|
||||
class TestResolveHostname(object):
|
||||
@check_success
|
||||
@mockified('pypsyc.util', ['lookupService', 'getHostByName'])
|
||||
def test_resolve_hostname_srv(self, lookupService, getHostByName):
|
||||
SERVICE = '_psyc._tcp.%s.' % SERVER1
|
||||
srv_rr = RRHeader(name=SERVICE, type=Record_SRV.TYPE,
|
||||
payload=Record_SRV(target=SERVER1, port=PORT))
|
||||
a_rr = RRHeader(name=SERVER1, type=Record_A.TYPE,
|
||||
payload=Record_A(IP))
|
||||
looked_up = lookupService.return_value = Deferred()
|
||||
|
||||
def _test():
|
||||
ip, port = resolve_hostname(SERVER1)
|
||||
assert ip == IP
|
||||
assert port == PORT
|
||||
assert lookupService.call_args_list == [((SERVICE,),)]
|
||||
self.success = True
|
||||
schedule(_test)
|
||||
looked_up.callback(([srv_rr], [], [a_rr]))
|
||||
|
||||
@check_success
|
||||
@mockified('pypsyc.util', ['lookupService', 'getHostByName'])
|
||||
def test_resolve_hostname_a(self, lookupService, getHostByName):
|
||||
lookupService.side_effect = DNSNameError
|
||||
looked_up = getHostByName.return_value = Deferred()
|
||||
|
||||
def _test():
|
||||
ip, port = resolve_hostname(SERVER1)
|
||||
assert ip == IP
|
||||
assert port == PORT
|
||||
assert getHostByName.call_args_list == [((SERVER1,),)]
|
||||
self.success = True
|
||||
schedule(_test)
|
||||
looked_up.callback(IP)
|
||||
|
||||
@check_success
|
||||
@mockified('pypsyc.util', ['lookupService', 'getHostByName'])
|
||||
def test_resolve_hostname_a_error(self, lookupService, getHostByName):
|
||||
lookupService.side_effect = DNSNameError
|
||||
getHostByName.return_value = fail(DNSNameError())
|
||||
|
||||
def _test():
|
||||
assert_raises(DNSError, resolve_hostname, SERVER1)
|
||||
self.success = True
|
||||
schedule(_test)
|
||||
|
||||
|
||||
class TestConnector(object):
|
||||
@mockified('pypsyc.util', ['reactor'])
|
||||
def setup(self, reactor):
|
||||
self.reactor = reactor
|
||||
self.factory = Mock()
|
||||
self.circuit = self.factory.buildProtocol.return_value
|
||||
self.connector = _PSYCConnector(SERVER1, PORT, self.factory, 30, None)
|
||||
self.transport = Mock()
|
||||
self.connector._makeTransport = lambda: self.transport
|
||||
self.connector.cancelTimeout = Mock()
|
||||
|
||||
@check_success
|
||||
def test_connect(self):
|
||||
def _test():
|
||||
circuit = self.connector.connect()
|
||||
assert circuit == self.circuit
|
||||
self.success = True
|
||||
schedule(_test)
|
||||
assert self.connector.state == 'connecting'
|
||||
assert self.connector.transport == self.transport
|
||||
assert self.connector.timeoutID == self.reactor.callLater.return_value
|
||||
|
||||
self.connector.buildProtocol(None)
|
||||
assert self.connector.state == 'connected'
|
||||
assert self.connector.cancelTimeout.called
|
||||
assert self.factory.method_calls == [('buildProtocol', (None,))]
|
||||
|
||||
self.circuit.inited()
|
||||
|
||||
@check_success
|
||||
def test_connection_failed(self):
|
||||
def _test():
|
||||
assert_raises(StubException, self.connector.connect)
|
||||
self.success = True
|
||||
schedule(_test)
|
||||
self.connector.connectionFailed(Failure(StubException()))
|
||||
assert self.connector.cancelTimeout.called
|
||||
assert self.connector.transport is None
|
||||
assert self.connector.state == 'disconnected'
|
||||
|
||||
def test_connection_lost(self):
|
||||
ERROR = StubException()
|
||||
self.connector.buildProtocol(None)
|
||||
self.connector.connectionLost(Failure(ERROR))
|
||||
assert self.connector.state == 'disconnected'
|
||||
assert self.factory.method_calls == [
|
||||
('buildProtocol', (None,)),
|
||||
('connection_lost', (self.circuit, ERROR))]
|
||||
|
||||
@mockified('pypsyc.util', ['_PSYCConnector'])
|
||||
def test_connect1(_PSYCConnector):
|
||||
connect(sentinel.host, sentinel.port, sentinel.factory)
|
||||
assert _PSYCConnector.call_args_list == [
|
||||
((sentinel.host, sentinel.port, sentinel.factory, 30, None),)]
|
||||
assert _PSYCConnector.return_value.method_calls == [('connect',)]
|
||||
|
||||
@mockified('pypsyc.util', ['_PSYCConnector'])
|
||||
def test_connect2(_PSYCConnector):
|
||||
connect(sentinel.host, sentinel.port, sentinel.factory,
|
||||
timeout=sentinel.timeout, bindAddress=sentinel.bindAddress)
|
||||
assert _PSYCConnector.call_args_list == [
|
||||
((sentinel.host, sentinel.port, sentinel.factory, sentinel.timeout,
|
||||
sentinel.bindAddress),)]
|
||||
assert _PSYCConnector.return_value.method_calls == [('connect',)]
|
||||
|
||||
|
||||
def test_event():
|
||||
event = Event()
|
||||
f1 = Mock()
|
||||
f2 = Mock()
|
||||
event += f1
|
||||
event.add_observer(f2, 1, i=2)
|
||||
|
||||
x = Mock()
|
||||
y = Mock()
|
||||
|
||||
event(x, y=y)
|
||||
assert f1.call_args_list == [((x,), {'y': y})]
|
||||
assert f2.call_args_list == [((x, 1), {'y': y, 'i': 2})]
|
||||
|
||||
event -= f2
|
||||
event(x)
|
||||
assert f1.call_args_list == [((x,), {'y': y}), ((x,),)]
|
||||
assert f2.call_args_list == [((x, 1), {'y': y, 'i': 2})]
|
Loading…
Reference in New Issue