* Added the 'imclist' command to show other games connected to IMC.

* Added the automatic cleaning/pruning code to weed out entries that are probably disconnected.
* Added 'imcwhois <player>' command. Still needs some sanitizing on the outgoing string.
* Added the beginnings of a -reply packet handler through reply_listener.py.
* Fleshed out a few more packets in packets.py.
Next up: Make the ANSI system a little more modular. Create a class for ANSI tables so developers can pick and choose different tables on their own, but keep the same API. This will be used so we don't have to copy/paste src/ansi.py to src/imc2/ansi.py and duplicate stuff.
This commit is contained in:
Greg Taylor 2009-04-13 06:36:51 +00:00
parent 317a4f1532
commit 31a78f9fb6
7 changed files with 213 additions and 45 deletions

View file

@ -1,7 +1,7 @@
"""
IMC2 user and administrative commands.
"""
import time
from time import time
from django.conf import settings
from src.config.models import ConfigValue
from src.objects.models import Object
@ -11,25 +11,67 @@ from src.util import functions_general
from src.cmdtable import GLOBAL_CMD_TABLE
from src.imc2 import connection as imc2_conn
from src.imc2.packets import *
from src.imc2.trackers import IMC2_MUDLIST
def cmd_imctest(command):
def cmd_imcwhois(command):
"""
Shows a player's inventory.
"""
source_object = command.source_object
source_object.emit_to("Sending")
packet = IMC2PacketWhois(source_object, 'Cratylus')
imc2_conn.IMC2_PROTOCOL_INSTANCE.send_packet(packet)
source_object.emit_to("Sent")
GLOBAL_CMD_TABLE.add_command("imctest", cmd_imctest)
if not command.command_argument:
source_object.emit_to("Get what?")
return
else:
source_object.emit_to("Sending IMC whois request. If you receive no response, no matches were found.")
packet = IMC2PacketWhois(source_object, command.command_argument)
imc2_conn.IMC2_PROTOCOL_INSTANCE.send_packet(packet)
GLOBAL_CMD_TABLE.add_command("imcwhois", cmd_imcwhois)
def cmd_imckeepalive(command):
"""
Shows a player's inventory.
Sends an is-alive packet to the network.
"""
source_object = command.source_object
source_object.emit_to("Sending")
packet = IMC2PacketIsAlive()
imc2_conn.IMC2_PROTOCOL_INSTANCE.send_packet(packet)
source_object.emit_to("Sent")
GLOBAL_CMD_TABLE.add_command("imckeepalive", cmd_imckeepalive)
GLOBAL_CMD_TABLE.add_command("imckeepalive", cmd_imckeepalive)
def cmd_imckeeprequest(command):
"""
Sends a keepalive-request packet to the network.
"""
source_object = command.source_object
source_object.emit_to("Sending")
packet = IMC2PacketKeepAliveRequest()
imc2_conn.IMC2_PROTOCOL_INSTANCE.send_packet(packet)
source_object.emit_to("Sent")
GLOBAL_CMD_TABLE.add_command("imckeeprequest", cmd_imckeeprequest)
def cmd_imclist(command):
"""
Shows the list of cached games from the IMC2 Mud list.
"""
source_object = command.source_object
retval = 'Active MUDs on %s\n\r' % imc2_conn.IMC2_PROTOCOL_INSTANCE.network_name
for name, mudinfo in IMC2_MUDLIST.mud_list.items():
mudline = ' %-20s %s' % (name, mudinfo.versionid)
retval += '%s\n\r' % mudline[:78]
retval += '%s active MUDs found.' % len(IMC2_MUDLIST.mud_list)
source_object.emit_to(retval)
GLOBAL_CMD_TABLE.add_command("imclist", cmd_imclist)
def cmd_imclistupdated(command):
"""
Shows the list of cached games from the IMC2 Mud list.
"""
source_object = command.source_object
retval = 'Active MUDs on %s\n\r' % imc2_conn.IMC2_PROTOCOL_INSTANCE.network_name
for name, mudinfo in IMC2_MUDLIST.mud_list.items():
tdelta = time() - mudinfo.last_updated
retval += ' %-20s %s\n\r' % (name, tdelta)
source_object.emit_to(retval)
GLOBAL_CMD_TABLE.add_command("imclistupdated", cmd_imclistupdated)

View file

@ -14,16 +14,7 @@ class IntervalEvent(object):
"""
Represents an event that is triggered periodically. Sub-class this and
fill in the stub function.
"""
# This is what shows up on @ps in-game.
name = None
# An interval (in seconds) for execution.
interval = None
# A timestamp (int) for the last time the event was fired.
time_last_executed = None
# A reference to the task.LoopingCall object.
looped_task = None
"""
def __init__(self):
"""
Executed when the class is instantiated.
@ -31,6 +22,12 @@ class IntervalEvent(object):
# This is set to prevent a Nonetype exception on @ps before the
# event is fired for the first time.
self.time_last_executed = time.time()
# This is what shows up on @ps in-game.
self.name = None
# An interval (in seconds) for execution.
self.interval = None
# A reference to the task.LoopingCall object.
self.looped_task = None
def __unicode__(self):
"""
@ -78,9 +75,11 @@ class IEvt_Check_Sessions(IntervalEvent):
"""
Event: Check all of the connected sessions.
"""
name = 'IEvt_Check_Sessions'
interval = 60
description = "Session consistency checks."
def __init__(self):
super(IEvt_Check_Sessions, self).__init__()
self.name = 'IEvt_Check_Sessions'
self.interval = 60
self.description = "Session consistency checks."
def event_function(self):
"""

View file

@ -2,14 +2,16 @@
IMC2 client module. Handles connecting to and communicating with an IMC2 server.
"""
import telnetlib
import time
from time import time
from twisted.internet.protocol import ClientFactory
from twisted.protocols.basic import LineReceiver
from twisted.internet import reactor, task
from twisted.conch.telnet import StatefulTelnetProtocol
from django.conf import settings
from src.imc2.packets import *
from src import logger
from src.imc2.packets import *
from src.imc2.trackers import *
from src.imc2 import reply_listener
# The active instance of IMC2Protocol. Set at server startup.
IMC2_PROTOCOL_INSTANCE = None
@ -20,8 +22,9 @@ class IMC2Protocol(StatefulTelnetProtocol):
authentication, and all necessary packets.
"""
def __init__(self):
print "IMC2: Client connecting to %s:%s..." % (settings.IMC2_SERVER_ADDRESS,
settings.IMC2_SERVER_PORT)
logger.log_infomsg("IMC2: Client connecting to %s:%s..." % (
settings.IMC2_SERVER_ADDRESS,
settings.IMC2_SERVER_PORT))
global IMC2_PROTOCOL_INSTANCE
IMC2_PROTOCOL_INSTANCE = self
self.is_authenticated = False
@ -34,9 +37,9 @@ class IMC2Protocol(StatefulTelnetProtocol):
"""
Triggered after connecting to the IMC2 network.
"""
print "IMC2: Connected to network server."
logger.log_infomsg("IMC2: Connected to network server.")
self.auth_type = "plaintext"
print "IMC2: Sending authentication packet."
logger.log_infomsg("IMC2: Sending authentication packet.")
self.send_packet(IMC2PacketAuthPlaintext())
def send_packet(self, packet):
@ -50,7 +53,7 @@ class IMC2Protocol(StatefulTelnetProtocol):
packet.imc2_protocol = self
packet_str = packet.assemble()
print "IMC2: SENT> %s" % packet_str
logger.log_infomsg("IMC2: SENT> %s" % packet_str)
self.sendLine(packet_str)
def _parse_auth_response(self, line):
@ -66,8 +69,11 @@ class IMC2Protocol(StatefulTelnetProtocol):
self.server_name = line_split[1]
self.network_name = line_split[4]
self.is_authenticated = True
self.sequence = int(time.time())
print "IMC2: Successfully authenticated to the '%s' network." % self.network_name
self.sequence = int(time())
logger.log_infomsg("IMC2: Successfully authenticated to the '%s' network." % self.network_name)
# Let everyone know we've arrived.
#self.send_packet(IMC2PacketKeepAliveRequest())
self.send_packet(IMC2PacketIsAlive())
def lineReceived(self, line):
"""
@ -78,8 +84,14 @@ class IMC2Protocol(StatefulTelnetProtocol):
self._parse_auth_response(line)
else:
logger.log_infomsg("PACKET: %s" % line)
logger.log_infomsg(IMC2Packet(packet_str = line))
#print "receive:", line
packet = IMC2Packet(packet_str = line)
logger.log_infomsg(packet)
if packet.packet_type == 'is-alive':
IMC2_MUDLIST.update_mud_from_packet(packet)
elif packet.packet_type == 'whois-reply':
reply_listener.handle_whois_reply(packet)
elif packet.packet_type == 'close-notify':
IMC2_MUDLIST.remove_mud_from_packet(packet)
class IMC2ClientFactory(ClientFactory):
"""

View file

@ -3,30 +3,78 @@ This module contains all IMC2 events that are triggered periodically.
Most of these are used to maintain the existing connection and keep various
lists/caches up to date.
"""
from time import time
from src import events
from src import scheduler
from src.imc2 import connection as imc2_conn
from src.imc2.packets import *
from src.imc2.trackers import IMC2_MUDLIST
class IEvt_IMC2_IsAlive(events.IntervalEvent):
class IEvt_IMC2_Send_IsAlive(events.IntervalEvent):
"""
Event: Send periodic keepalives to network neighbors. This lets the other
games know that our game is still up and connected to the network. Also
provides some useful information about the client game.
"""
name = 'IEvt_IMC2_IsAlive'
# Send keep-alive packets every 15 minutes.
interval = 900
description = "Send an IMC2 is-alive packet."
def __init__(self):
super(IEvt_IMC2_Send_IsAlive, self).__init__()
self.name = 'IEvt_IMC2_Send_IsAlive'
# Send keep-alive packets every 15 minutes.
self.interval = 900
self.description = "Send an IMC2 is-alive packet."
def event_function(self):
"""
This is the function that is fired every self.interval seconds.
"""
imc2_conn.IMC2_PROTOCOL_INSTANCE.send_packet(IMC2PacketIsAlive())
class IEvt_IMC2_Send_Keepalive_Request(events.IntervalEvent):
"""
Event: Sends a keepalive-request to connected games in order to see who
is connected.
"""
def __init__(self):
super(IEvt_IMC2_Send_Keepalive_Request, self).__init__()
self.name = 'IEvt_IMC2_Send_Keepalive_Request'
self.interval = 3500
self.description = "Send an IMC2 keepalive-request packet."
def event_function(self):
"""
This is the function that is fired every self.interval seconds.
"""
imc2_conn.IMC2_PROTOCOL_INSTANCE.send_packet(IMC2PacketKeepAliveRequest())
class IEvt_IMC2_Prune_Inactive_Muds(events.IntervalEvent):
"""
Event: Prunes games that have not sent is-alive packets for a while. If
we haven't heard from them, they're probably not connected or don't
implement the protocol correctly. In either case, good riddance to them.
"""
def __init__(self):
super(IEvt_IMC2_Prune_Inactive_Muds, self).__init__()
self.name = 'IEvt_IMC2_Prune_Inactive_Muds'
# Check every 30 minutes.
self.interval = 1800
self.description = "Check IMC2 list for inactive games."
# Threshold for game inactivity (in seconds).
self.inactive_thresh = 3599
def event_function(self):
"""
This is the function that is fired every self.interval seconds.
"""
for name, mudinfo in IMC2_MUDLIST.mud_list.items():
# If we haven't heard from the game within our threshold time,
# we assume that they're dead.
if time() - mudinfo.last_updated > self.inactive_thresh:
del IMC2_MUDLIST.mud_list[name]
def add_events():
"""
Adds the IMC2 events to the scheduler.
"""
scheduler.add_event(IEvt_IMC2_IsAlive())
scheduler.add_event(IEvt_IMC2_Send_IsAlive())
scheduler.add_event(IEvt_IMC2_Prune_Inactive_Muds())
scheduler.add_event(IEvt_IMC2_Send_Keepalive_Request())

View file

@ -40,7 +40,7 @@ class IMC2Packet(object):
self.target = None
self.destination = None
# Optional data.
self.optional_data = []
self.optional_data = {}
# Reference to the IMC2Protocol object doing the sending.
self.imc2_protocol = None
@ -56,7 +56,6 @@ class IMC2Packet(object):
if counter == 0:
# This is the sender@origin token.
sender_origin = token
print token
split_sender_origin = sender_origin.split('@')
self.sender = split_sender_origin[0].strip()
self.origin = split_sender_origin[1]
@ -84,8 +83,12 @@ class IMC2Packet(object):
self.destination = split_target_destination[0]
elif counter > 4:
# Populate optional data.
key, value = token.split('=', 1)
self.optional_data.append((key, value))
try:
key, value = token.split('=', 1)
self.optional_data[key] = value
except ValueError:
# Failed to split on equal sign, disregard.
pass
# Increment and continue to the next token (if applicable)
counter += 1
@ -129,6 +132,8 @@ class IMC2Packet(object):
if self.sender == '*':
# Some packets have no sender.
return '*'
elif str(self.sender).isdigit():
return self.sender
elif self.sender:
# Player object.
name = self.sender.get_name(fullname=False, show_dbref=False,
@ -199,7 +204,12 @@ class IMC2PacketKeepAliveRequest(IMC2Packet):
Example of a sent keepalive-request:
*@YourMUD 1234567890 YourMUD keepalive-request *@*
"""
pass
def __init__(self):
super(IMC2PacketKeepAliveRequest, self).__init__()
self.sender = '*'
self.packet_type = 'keepalive-request'
self.target = '*'
self.destination = '*'
class IMC2PacketIsAlive(IMC2Packet):
"""
@ -607,7 +617,8 @@ class IMC2PacketWhois(IMC2Packet):
"""
def __init__(self, pobject, whois_target):
super(IMC2PacketWhois, self).__init__()
self.sender = pobject
# Use the dbref, it's easier to trace back for the whois-reply.
self.sender = pobject.id
self.packet_type = 'whois'
self.target = whois_target
self.destination = '*'

View file

@ -0,0 +1,12 @@
"""
This module handles some of the -reply packets like whois-reply.
"""
from src.objects.models import Object
def handle_whois_reply(packet):
try:
pobject = Object.objects.get(id=packet.target)
pobject.emit_to('Whois reply: %s' % packet.optional_data.get('text', 'Unknown'))
except Object.DoesNotExist:
# No match found for whois sender. Ignore it.
pass

44
src/imc2/trackers.py Normal file
View file

@ -0,0 +1,44 @@
"""
Certain periodic packets are sent by connected MUDs (is-alive, user-cache,
etc). The IMC2 protocol assumes that each connected MUD will capture these and
populate/maintain their own lists of other servers connected. This module
contains stuff like this.
"""
from time import time
class IMC2Mud(object):
"""
Stores information about other games connected to our current IMC2 network.
"""
def __init__(self, packet):
self.name = packet.origin
self.versionid = packet.optional_data.get('versionid', None)
self.networkname = packet.optional_data.get('networkname', None)
self.url = packet.optional_data.get('url', None)
self.host = packet.optional_data.get('host', None)
self.port = packet.optional_data.get('port', None)
self.sha256 = packet.optional_data.get('sha256', None)
# This is used to determine when a Mud has fallen into inactive status.
self.last_updated = time()
class IMC2MudList(object):
"""
Keeps track of other MUDs connected to the IMC network.
"""
def __init__(self):
# Mud list is stored in a dict, key being the IMC Mud name.
self.mud_list = {}
def update_mud_from_packet(self, packet):
# This grabs relevant info from the packet and stuffs it in the
# Mud list for later retrieval.
mud = IMC2Mud(packet)
self.mud_list[mud.name] = mud
def remove_mud_from_packet(self, packet):
# Removes a mud from the Mud list when given a packet.
mud = IMC2Mud(packet)
del self.mud_list[mud.name]
# Use this instance to keep track of the other games on the network.
IMC2_MUDLIST = IMC2MudList()