Compare commits

...

10 Commits

Author SHA1 Message Date
Torrie Fischer
61c39dc9c2 wip? 2023-06-10 13:21:31 +02:00
Trever Fischer
4ac2d61359 Implement new event marshalling system 2012-11-21 22:09:35 -05:00
Trever Fischer
8ef29f3358 crash-- 2012-11-18 09:21:16 -05:00
Trever Fischer
f9340c7582 debug-- 2012-11-18 09:21:09 -05:00
Trever Fischer
ec6cc0db7d Fix event dispatching and attempt to reconnect on poll error 2012-11-18 09:20:53 -05:00
Trever Fischer
25ae5444f7 Use None where appropriate 2012-11-18 09:20:20 -05:00
Trever Fischer
3dea38f51d Send multiple events to web polls and fix server events 2012-11-18 09:20:04 -05:00
Trever Fischer
018b360f61 Fix up the event_stats command and include web queue data 2012-11-18 09:19:18 -05:00
Trever Fischer
adde1343f1 Fix web queues not always being present in beanstalkd, meaning dropped events 2012-11-18 09:18:51 -05:00
Trever Fischer
690ff8f21e Support handling vault-content events in the vault UI 2012-11-17 21:33:42 -05:00
28 changed files with 1318 additions and 451 deletions

82
api/event-types.yml Normal file
View File

@@ -0,0 +1,82 @@
---
routes:
quit: [api, web]
join: [api, web]
broadcast: [server, web]
chat: [web]
vault-contents: [server, web]
player-death: [api, web]
server-heartbeat: [api ]
web-heartbeat: [web]
player-kick: [server, web]
player-message: [server, web]
market-order: [market]
block-break: [api]
block-place: [api]
player-murder: [api]
weather: [api, web]
test: [api, web, server]
types:
vault-slot:
material: int
quantity: int
damage: int
data: int
position: int
name: !optional string
durability: !optional int
events:
test:
data: !optional string
quit:
player: string
join:
player: string
broadcast:
message: string
chat:
message: string
sender: string
vault-contents:
player: string
items:
- vault-slot
player-death:
player: string
message: string
server-heartbeat:
port: int
name: string
worldTimes: dict
web-heartbeat:
server: int
heartbeat: dict
time: int
day-period: string
player-kick:
player: string
message: string
player-message:
player: string
message: string
market-order:
orderID: int
block-break:
player: string
x: int
y: int
z: int
material: int
block-place:
player: string
x: int
y: int
z: int
material: int
player-murder:
player: string
killer: string
message: string
weather:
world: string
isRaining: boolean

View File

@@ -1,155 +1,358 @@
from django.conf import settings from django.conf import settings
import os
from django.core.cache import cache from django.core.cache import cache
import time import time
from minecraft.models import Server from minecraft.models import Server
from json import loads, dumps, JSONEncoder from json import loads, dumps
import beanstalkc import beanstalkc
from django.contrib.auth.models import User from django.contrib.auth.models import User
import yaml
import django.dispatch
class EventEncoder(JSONEncoder): on_event = django.dispatch.Signal(providing_args=["event"])
def default(self, obj):
if isinstance(obj, Event):
return {'type': obj.type, 'payload': obj.data}
return super(EventEncoder, self).default(obj)
def cache_heartbeat(sender, event, *args, **kwargs):
#cache.set('caminus-server-heartbeat-%s'%(request.server.id), evt['payload'], 86400)
pass
on_event.connect(cache_heartbeat, sender=intern('heartbeat'))
class Dispatcher(object):
@classmethod
def routes(cls):
types = cache.get('caminus-event-routes')
types = None
if types is None:
typefile = os.path.sep.join(
__file__.split(os.path.sep)[0:-1]+
['event-types.yml',]
)
yaml.add_constructor(r'!optional', OptionalEventProperty)
types = yaml.load(open(typefile, 'r'))['routes']
cache.set('caminus-event-routes', types, 3600)
return types
@classmethod
def dispatch(cls, event):
routes = cls.routes()
if event._type in routes:
for route in routes[event._type]:
if route == "api":
on_event.send_robust(sender=intern(event._type), event=event)
elif route == "web":
WebQueue.route(event)
elif route == "server":
ServerQueue.route(event)
elif route == "market":
MarketQueue.route(event)
else:
raise KeyError, "Unknown route %s"%route
else:
raise KeyError, "Unable to find route for %s event"%(event._type)
@classmethod
def broadcast(cls, msg, *args):
cls.dispatch(Event('broadcast', message=msg%args))
@classmethod
def user_message(cls, user, message, args):
player = user.minecraftprofile.mc_username
cls.player_message(player, message, *args)
@classmethod
def player_message(cls, playername, message, *args):
message = message % args
cls.dispatch(Event('player-message', player=playername,
message=message))
@classmethod
def chat(cls, playername, message, *args):
cls.dispatch(Event('chat', player=playername, message=message%args))
class Event(object): class Event(object):
def __init__(self, type, data): def __init__(self, type=None, *args, **kwargs):
self.type = type self._id = -1
self.data = data self._stamp = 0
self._conn = None
self._properties = {}
types = self._types()
if type in types:
self._type = type
self._properties = self._marshallType(kwargs, types[type], self._datatypes(), type)
else:
raise ValueError, "Unknown event type '%s'"%(type)
class ChatEvent(Event): def _delete(self):
def __init__(self, sender, message): self._conn.delete(self._id)
super(ChatEvent, self).__init__(type='chat', data={'sender': sender,
'message': message})
class VaultContentsEvent(Event): def _bury(self, priority=0):
def __init__(self, player, items): self._conn.bury(self._id, 0)
super(VaultContentsEvent, self).__init__(type='vault-contents',
data={'player': player, 'items': items})
class QuitEvent(Event): def __getattribute__(self, name):
def __init__(self, player): if name.startswith('_'):
super(QuitEvent, self).__init__(type='quit', data={'player': player}) return super(Event, self).__getattribute__(name)
return self._properties[name]
class JoinEvent(Event): def __setattr__(self, name, value):
def __init__(self, player): if name.startswith('_'):
super(JoinEvent, self).__init__(type='join', data={'player': player}) return super(Event, self).__setattr__(name, value)
if name in self._properties:
raise NotImplementedError, "Event properties are not modifyable"
class BroadcastEvent(Event): @classmethod
def __init__(self, message): def _marshallType(cls, data, typeDescription, dataTypes, paramPath=""):
super(BroadcastEvent, self).__init__(type='broadcast', data={'message': assert(isinstance(dataTypes, dict))
message}) if data is None:
return data
if isinstance(typeDescription, OptionalEventProperty):
return cls._marshallType(data, str(typeDescription), dataTypes, paramPath)
try:
if typeDescription == "string":
return str(data)
if typeDescription == "int":
return int(data)
if typeDescription == "dict":
return dict(data)
if typeDescription == "list":
return list(data)
except TypeError, e:
raise ValueError, "Could not convert '%s' to %s type for %s"%(data,
typeDescription, paramPath)
if isinstance(typeDescription, dict):
ret = {}
for key, elementType in typeDescription.iteritems():
key = key.replace('-', '_')
if key in data:
if key.startswith("_"):
raise ValueError, "Parameter '%s.%s' cannot start with '_'"%(key, paramPath)
ret[key] = cls._marshallType(data[key], elementType, dataTypes,
"%s.%s"%(paramPath, key))
elif not isinstance(elementType, OptionalEventProperty):
raise KeyError, "Missing parameter '%s.%s'"%(paramPath, key)
else:
ret[key] = None
return ret
if isinstance(typeDescription, list):
ret = []
i = 0
for item in data:
ret.append(cls._marshallType(item, typeDescription[0], dataTypes, "%s[%d]"%(paramPath, i)))
i += 1
return ret
if typeDescription in dataTypes:
return cls._marshallType(data, dataTypes[typeDescription], dataTypes, paramPath)
raise TypeError, "Unknown event data type '%s.%s'"%(paramPath, typeDescription)
class PlayerDeathEvent(Event): @classmethod
def __init__(self, player, message): def _types(cls):
super(PlayerDeathEvent, self).__init__(type='player-death', types = cache.get('caminus-event-types')
data={'player': player, 'message': message}) types = None
if types is None:
typefile = os.path.sep.join(
__file__.split(os.path.sep)[0:-1]+
['event-types.yml',]
)
yaml.add_constructor(r'!optional', OptionalEventProperty)
types = yaml.load(open(typefile, 'r'))['events']
cache.set('caminus-event-types', types, 3600)
return types
class ServerHeartbeatEvent(Event): @classmethod
def __init__(self, server, data): def _datatypes(cls):
now = server.current_time() typefile = os.path.sep.join(
t = now.second+now.minute*60+now.hour*60*60 __file__.split(os.path.sep)[0:-1]+
super(ServerHeartbeatEvent, self).__init__(type='server-heartbeat', ['event-types.yml',]
data={'server': server.id, 'heartbeat': data, 'time': t, )
'day-period': server.day_period()}) yaml.add_constructor(r'!optional', OptionalEventProperty)
types = yaml.load(open(typefile, 'r'))['types']
return types
class KickEvent(Event): def __repr__(self):
def __init__(self, user, message): return "Event(%r)"%(self._properties)
super(KickEvent, self).__init__(type='player-kick',
data={'player': user, 'message': message})
class PlayerMessageEvent(Event): def __eq__(self, other):
def __init__(self, user, message): return self._properties == other._properties and self._id == other._id
super(PlayerMessageEvent, self).__init__(type='player-message',
data={'message': message, 'player': user})
class MarketOrderEvent(Event): @classmethod
def __init__(self, orderID): def _fromBeanstalkJob(cls, job):
super(MarketOrderEvent, self).__init__(type='market-order', assert(isinstance(job, beanstalkc.Job))
data={'orderID': orderID}) id = job.jid
evt = cls._fromJSON(job.body)
evt._id = int(id)
evt._conn = job.conn
return evt
def server_queue(server, users=[]): def _toDict(self):
queueName = 'caminus-broadcast-%s'%server.id ret = {
queue = beanstalkc.Connection(host=settings.CAMINUS_BEANSTALKD_HOST, '_id': self._id,
'_type': self._type,
'_stamp': self._stamp,
}
ret.update(self._properties)
return ret
@classmethod
def _fromDict(cls, data):
assert(isinstance(data, dict))
type = data['_type']
properties = {}
for prop in data:
if prop.startswith('_'):
continue
properties[prop] = data[prop]
evt = Event(type, **properties)
if '_id' in data:
evt._id = int(data['_id'])
return evt
def _toJSON(self):
return dumps(self._toDict())
@classmethod
def _fromJSON(cls, json):
assert(isinstance(json, str))
return cls._fromDict(loads(json))
class OptionalEventProperty(str):
def __new__(cls, loader=None, node=None):
if node is None:
# Used with OptionalEventProperty("some-prop-name"), such as with pickling
return str.__new__(cls, loader)
# Used with yaml loader
return str.__new__(cls, loader.construct_scalar(node))
class EventQueue(object):
def __init__(self, name):
self.name = name
self.queue = beanstalkc.Connection(host=settings.CAMINUS_BEANSTALKD_HOST,
port=settings.CAMINUS_BEANSTALKD_PORT) port=settings.CAMINUS_BEANSTALKD_PORT)
queue.use(queueName) self.queue.use(self.name)
queue.watch(queueName) self.queue.watch(self.name)
if len(users) > 0:
for user in users:
queue.watch("caminus-user-%s"%user)
return queue
def send_server_event(server, event): @classmethod
if settings.CAMINUS_USE_BEANSTALKD: def route(self, evt):
queue = server_queue(server) raise NotImplementedError
json = dumps(event, cls=EventEncoder)
queue.put(json)
def broadcast_server_event(event): def sendEvent(self, evt):
assert(isinstance(evt, Event))
id = self.queue.put(evt._toJSON())
def getEvents(self, timeout=30):
ret = []
job = self.queue.reserve(timeout)
while job:
ret.append(job)
job = self.queue.reserve(timeout=0)
return map(lambda x: Event._fromBeanstalkJob(x), ret)
@classmethod
def flushAll(cls):
for q in cls.allQueues():
q.flush()
def flush(self):
job = self.queue.peek_ready()
while job:
job.delete()
job = self.queue.peek_ready()
@staticmethod
def connection():
return beanstalkc.Connection(host=settings.CAMINUS_BEANSTALKD_HOST,
port=settings.CAMINUS_BEANSTALKD_PORT)
@classmethod
def allQueues(cls):
c = cls.connection()
ret = []
for c in c.tubes():
ret.append(EventQueue(c))
return ret
class MarketQueue(EventQueue):
def __init__(self):
super(MarketQueue, self).__init__('caminus-market')
@classmethod
def route(cls, evt):
cls().sendEvent(evt)
class ServerQueue(EventQueue):
def __init__(self, server):
super(ServerQueue, self).__init__('caminus-broadcast-%s'%server.id)
@classmethod
def route(cls, evt):
cls.broadcast(evt)
@classmethod
def broadcast(cls, evt):
for server in Server.objects.all(): for server in Server.objects.all():
send_server_event(server, event) queue = ServerQueue(server)
queue.sendEvent(evt)
def server_broadcast(message, *args): class WebQueue(EventQueue):
message = message%args def __init__(self, id):
for server in Server.objects.all(): self.id = id
event = BroadcastEvent(message) super(WebQueue, self).__init__('caminus-web-%s'%(id))
send_server_event(server, event) if id != "0":
send_web_event(event) activeCache = cache.get('minecraft-web-queues')
if activeCache is None:
activeCache = {}
activeCache[id] = time.time()
expired = []
for queueName, stamp in activeCache.iteritems():
if time.time()-stamp > 120:
expired.append(queueName)
for e in expired:
del activeCache[e]
cache.set('minecraft-web-queues', activeCache, 3600)
for e in expired:
q = WebQueue(e)
q.flush()
def user_message(user, message, *args): @classmethod
player = user.minecraftprofile.mc_username def route(cls, event):
player_message(player, message, *args) cls.broadcast(event)
def player_message(playername, message, *args): @classmethod
message = message%args def broadcast(cls, event):
for server in Server.objects.all():
event = PlayerMessageEvent(playername, message)
send_server_event(server, event)
def web_queue(id):
queueName = 'caminus-web-%s'%id
queue = beanstalkc.Connection(host=settings.CAMINUS_BEANSTALKD_HOST,
port = settings.CAMINUS_BEANSTALKD_PORT)
queue.use(queueName)
queue.watch(queueName)
return queue
def market_queue():
queueName = 'caminus-market'
queue = beanstalkc.Connection(host=settings.CAMINUS_BEANSTALKD_HOST,
port = settings.CAMINUS_BEANSTALKD_PORT)
queue.use(queueName)
queue.watch(queueName)
return queue
def queue_market_event(event):
queue = market_queue()
json = dumps({'stamp': time.time(), 'event': event}, cls=EventEncoder)
queue.put(json)
def send_web_event(event):
latest = cache.get('minecraft-web-events') latest = cache.get('minecraft-web-events')
if latest is None: if latest is None:
latest = [] latest = []
latest.append(dumps(event, cls=EventEncoder)) latest.append(dumps(event._toJSON()))
while len(latest) > 10: while len(latest) > 10:
latest.pop(0) latest.pop(0)
cache.set('minecraft-web-events', latest, 86400); cache.set('minecraft-web-events', latest, 86400)
if settings.CAMINUS_USE_BEANSTALKD:
queue = beanstalkc.Connection(host=settings.CAMINUS_BEANSTALKD_HOST,
port = settings.CAMINUS_BEANSTALKD_PORT)
json = dumps({'stamp': time.time(), 'event':event}, cls=EventEncoder)
for tube in queue.tubes():
if tube.startswith("caminus-web-"):
queue.use(tube)
pendingJob = queue.peek_ready()
if pendingJob:
pending = loads(pendingJob.body)
if time.time()-pending['stamp'] > 30:
pendingJob.delete()
queue.put(json)
def chat(playername, message): for queue in cls.activeQueues():
evt = ChatEvent(playername, message) queue.sendEvent(event)
send_web_event(evt)
@staticmethod
def activeQueues():
activeCache = cache.get('minecraft-web-queues')
if activeCache is None:
activeCache = {}
ret = []
for name in activeCache.iterkeys():
ret.append(WebQueue(name))
return ret
@staticmethod
def clearActiveQueues():
cache.set('minecraft-web-queues', None)
def getEvents(self, timeout=30):
if self.id == "0":
latestEvents = cache.get('minecraft-web-events')
if latestEvents is None:
latestEvents = []
ret = []
for e in latestEvents:
ret.append(loads(e))
return ret
else:
ret = super(WebQueue, self).getEvents(timeout)
for evt in ret:
evt._delete()
return ret

View File

@@ -5,14 +5,14 @@ from django.conf import settings
import appversion import appversion
from minecraft.models import MinecraftProfile from minecraft.models import MinecraftProfile
from local.models import Quote from local.models import Quote
from minecraft.models import MOTD, Server, PlayerSession from minecraft.models import MOTD, Server, PlayerSession, Item
from django.db.models import F from django.db.models import F
from django.http import HttpResponse from django.http import HttpResponse
from urllib2 import urlopen from urllib2 import urlopen
import json import json
from datetime import datetime from datetime import datetime
from models import cachePlayerList from models import cachePlayerList
from events import server_queue, web_queue, chat, server_broadcast, send_web_event, QuitEvent, JoinEvent, PlayerDeathEvent, ServerHeartbeatEvent from events import ServerQueue, WebQueue, Event, Dispatcher
from bounty.models import Bounty from bounty.models import Bounty
from vault.models import VaultSlot from vault.models import VaultSlot
@@ -44,7 +44,7 @@ class NewPlayerSessionHandler(BaseHandler):
server = request.server server = request.server
profile = MinecraftProfile.objects.get(mc_username__exact=playername) profile = MinecraftProfile.objects.get(mc_username__exact=playername)
session = PlayerSession.objects.create(server=server, player=profile, ip=ip) session = PlayerSession.objects.create(server=server, player=profile, ip=ip)
send_web_event(JoinEvent(playername)) WebQueue.broadcast(Event('join', player=playername))
return {'success': True, 'error': '', 'permissions': profile.serverPermissions(), 'sessionId': session.id} return {'success': True, 'error': '', 'permissions': profile.serverPermissions(), 'sessionId': session.id}
else: else:
return {'success': False, 'error': 'Your account is inactive.', 'permissions': []} return {'success': False, 'error': 'Your account is inactive.', 'permissions': []}
@@ -57,7 +57,7 @@ class ClosePlayerSessionHandler(BaseHandler):
for session in sessions: for session in sessions:
session.end = datetime.now() session.end = datetime.now()
session.save() session.save()
send_web_event(QuitEvent(playername)) WebQueue.broadcast(Event('quit', player=playername))
return {'valid': True} return {'valid': True}
class EconomyHandler(BaseHandler): class EconomyHandler(BaseHandler):
@@ -88,20 +88,18 @@ class ServerEventHandler(BaseHandler):
allowed_methods = ('GET', 'POST', 'PUT') allowed_methods = ('GET', 'POST', 'PUT')
def read(self, request): def read(self, request):
queue = server_queue(request.server) queue = ServerQueue(request.server)
queue.watch('caminus-broadcast-%s'%request.server.id)
events = [] events = []
job = queue.reserve(timeout=30) for e in queue.getEvents():
while job: events.append(json.loads(e.body)['event'])
job.bury() e.bury()
events.append({'id': job.jid, 'event': json.loads(job.body)}) print {'events': events}
job = queue.reserve(timeout=0)
return {'events': events, 'is-live': settings.CAMINUS_USE_BEANSTALKD} return {'events': events, 'is-live': settings.CAMINUS_USE_BEANSTALKD}
def create(self, request): def create(self, request):
queue = server_queue(request.server) queue = ServerQueue(request.server)
try: try:
queue.delete(int(request.POST['job'])) queue.deleteJob(int(request.POST['job']))
except Exception, e: except Exception, e:
pass pass
return {'result': 'success'} return {'result': 'success'}
@@ -109,21 +107,7 @@ class ServerEventHandler(BaseHandler):
def update(self, request): def update(self, request):
events = json.loads(request.POST['events'])['events'] events = json.loads(request.POST['events'])['events']
for evt in events: for evt in events:
if evt['type'] == 'chat': Dispatcher.dispatch(Event.fromDict(evt)
chat(evt['payload']['sender'], evt['payload']['message'])
if evt['type'] == 'player-death':
send_web_event(PlayerDeathEvent(evt['payload']['player'],
evt['payload']['message']))
if evt['type'] == 'heartbeat':
cache.set('caminus-server-heartbeat-%s'%(request.server.id), evt['payload'], 86400)
send_web_event(ServerHeartbeatEvent(request.server, evt['payload']))
if evt['type'] == 'player-murder':
bounties = Bounty.objects.filter(target__mc_username=evt['payload']['player'])
killer = MinecraftProfile.objects.get(mc_username=evt['payload']['killer'])
for bounty in bounties:
bounty.close(killer)
if len(bounties) > 0:
server_broadcast("The bounty on %s has been collected."%(evt['payload']['player']))
return {'result': 'success'} return {'result': 'success'}
class ChatHandler(BaseHandler): class ChatHandler(BaseHandler):
@@ -131,8 +115,8 @@ class ChatHandler(BaseHandler):
def create(self, request): def create(self, request):
chat(request.user.minecraftprofile.mc_username, request.POST['message']) chat(request.user.minecraftprofile.mc_username, request.POST['message'])
server_broadcast("<%s> %s"%(request.user.minecraftprofile.mc_username, ServerQueue.broadcast(BroadcastEvent("<%s> %s",
request.POST['message'])) request.user.minecraftprofile.mc_username, request.POST['message']))
class PollHandler(BaseHandler): class PollHandler(BaseHandler):
allowed_methods = ('GET',) allowed_methods = ('GET',)
@@ -150,20 +134,8 @@ class PollHandler(BaseHandler):
pollData['events'] = [] pollData['events'] = []
pollData['info'] = info pollData['info'] = info
pollData['poll-id'] = timestamp pollData['poll-id'] = timestamp
if timestamp == "0" and settings.CAMINUS_USE_BEANSTALKD: eventQueue = WebQueue(timestamp)
pollData['poll-id'] = time.time() pollData['events'] = eventQueue.getEvents()
latestEvents = cache.get('minecraft-web-events')
if not latestEvents:
latestEvents = []
for e in latestEvents:
pollData['events'].append(json.loads(e))
elif settings.CAMINUS_USE_BEANSTALKD:
eventQueue = web_queue(timestamp)
event = eventQueue.reserve(timeout=30)
if event:
eventData = json.loads(event.body)
pollData['events'].append(eventData['event'])
event.delete()
return pollData return pollData
class VaultHandler(BaseHandler): class VaultHandler(BaseHandler):
@@ -173,9 +145,17 @@ class VaultHandler(BaseHandler):
player = MinecraftProfile.objects.get(mc_username__exact=playername) player = MinecraftProfile.objects.get(mc_username__exact=playername)
items = [] items = []
for slot in player.vault_slots.all(): for slot in player.vault_slots.all():
items.append({'item': slot.item, 'quantity': data = {}
slot.quantity, 'damage': slot.damage, 'data': slot.data, data['quantity'] = slot.quantity
'position': slot.position}) data['position'] = slot.position
data['item'] = None
data['damage'] = 0
data['data'] = 0
if slot.item:
data['data'] = slot.item.data
data['item'] = slot.item.material
data['damage'] = slot.item.damage
items.append(data)
return {'items': items} return {'items': items}
def update(self, request, playername): def update(self, request, playername):
@@ -186,18 +166,13 @@ class VaultHandler(BaseHandler):
updated = False updated = False
slot,created = VaultSlot.objects.get_or_create(player=player, slot,created = VaultSlot.objects.get_or_create(player=player,
position=stack['position']) position=stack['position'])
if slot.item != stack['item']: item = Item.get(stack['item'], stack['damage'], stack['data'])
slot.item = stack['item'] if slot.item != item:
slot.item = item
updated = True updated = True
if slot.quantity != stack['quantity']: if slot.quantity != stack['quantity']:
slot.quantity = stack['quantity'] slot.quantity = stack['quantity']
updated = True updated = True
if slot.damage != stack['damage']:
slot.damage = stack['damage']
updated = True
if slot.data != stack['data']:
slot.data = stack['data']
updated = True
if updated: if updated:
slot.save() slot.save()
return {'success': True} return {'success': True}

View File

@@ -1,4 +1,5 @@
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.core.cache import cache
from api import events from api import events
from minecraft.models import Server from minecraft.models import Server
@@ -8,17 +9,23 @@ class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
servers = Server.objects.all() servers = Server.objects.all()
for s in servers: for s in servers:
queue = events.server_queue(s) queue = events.ServerQueue(s)
stats = queue.stats() stats = queue.queue.stats()
print s print s
for k,v in stats.iteritems(): for k,v in stats.iteritems():
print "\t%s: %s"%(k, v) print "\t%s: %s"%(k, v)
print "\tTubes:" print "Tubes:"
for t in queue.tubes(): for t in queue.queue.tubes():
print "\t\t%s"%(t) print "\t%s"%(t)
queue.use(t) queue.queue.use(t)
next = queue.peek_ready() next = queue.queue.peek_ready()
if next: if next:
print "\t\t\tNext job: %s"%(next.body) print "\t\tNext job: %s"%(next.body)
else: else:
print "\t\t\tNo pending job." print "\t\tNo pending job."
print "Web queues:"
webQueues = cache.get("minecraft-web-queues")
if webQueues is None:
webQueues = {}
for queue,stamp in webQueues.iteritems():
"\t%s: %d"%(queue, stamp)

View File

@@ -0,0 +1,140 @@
from django.core.management.base import BaseCommand
from django.core.cache import cache
from api import events
from pprint import pprint
from optparse import make_option
_t = type
class Command(BaseCommand):
help = 'Display information about available event types'
option_list = BaseCommand.option_list + (
make_option('--header',
help='Header to append to each file'),
make_option('--output',
help='Directory to write files to',
default='.'),
make_option('--lang',
help='Language to export to'),
)
def handle(self, *args, **options):
if options['lang'] == 'java':
if options['output']:
outdir = options['output']
else:
outdir = '.'
header = ""
if options['header']:
fh = open(options['header'], 'r')
header = fh.read()
for typename, properties in events.Event.types().iteritems():
outfile = open(outdir+'/'+self.javaName(typename)+"Event.java", 'w')
outfile.write(header)
outfile.write("import org.json.*;\n")
outfile.write("import java.util.*;\n")
outfile.write("public class %sEvent extends Event {\n"%(self.javaName(typename)))
for propname,type in properties.iteritems():
outfile.write("\tpublic %s %s;\n"%(self.javaType(type), self.javaName(propname, False)))
outfile.write("\tpublic static %sEvent fromJSON(JSONObject obj) throws JSONException {\n"%(self.javaName(typename)))
outfile.write("\t\t%sEvent ret = new %sEvent();\n"%(self.javaName(typename), self.javaName(typename)))
for propname, type in properties.iteritems():
if isinstance(type, events.OptionalEventProperty):
outfile.write("\t\ttry {\n\t")
outfile.write("\t\t"+self.jsonCode(propname, type)+"\n");
if isinstance(type, events.OptionalEventProperty):
outfile.write("\t\t} catch (JSONException e) {}\n")
outfile.write("\t\treturn ret;\n")
outfile.write("\t}\n")
outfile.write("\tpublic JSONWriter toJSON(JSONWriter writer) throws JSONException {\n")
for propname, type in properties.iteritems():
outfile.write("writer.key(\"%s\").value(this.%s);\n"%(propname, self.javaName(propname, False)))
outfile.write("\t\treturn writer;\n")
outfile.write("\t}\n")
outfile.write("\tpublic String jsonName() { return \"%s\"; }\n"%(typename))
outfile.write("\tstatic { Event.registerHandler(\"%s\", %sEvent.class); }\n"%(typename, self.javaName(typename)))
outfile.write("}\n")
outfile.close()
print "Wrote", self.javaName(typename)+"Event.java"
for typename, properties in events.Event.datatypes().iteritems():
outfile = open(outdir+'/'+self.javaName(typename)+".java", 'w')
outfile.write(header)
outfile.write("import org.json.*;\n")
outfile.write("import java.util.*;\n")
outfile.write("public class %s {\n"%(self.javaName(typename)))
for propname, type in properties.iteritems():
outfile.write("\tpublic %s %s;\n"%(self.javaType(type), self.javaName(propname, False)))
outfile.write("\tpublic static %s fromJSON(JSONObject obj) throws JSONException {\n"%(self.javaName(typename)))
outfile.write("\t\t%s ret = new %s();\n"%(self.javaName(typename), self.javaName(typename)))
for propname, type in properties.iteritems():
if isinstance(type, events.OptionalEventProperty):
outfile.write("\t\ttry {\n\t")
outfile.write("\t\t"+self.jsonCode(propname, type)+"\n");
if isinstance(type, events.OptionalEventProperty):
outfile.write("\t\t} catch (JSONException e) {}\n")
outfile.write("\t\treturn ret;\n")
outfile.write("\t}\n")
outfile.write("}\n")
outfile.close()
print "Wrote", self.javaName(typename)+".java"
else:
print 'Unknown language', options['lang']
@staticmethod
def javaName(name, isClass=True):
name = ''.join(map(lambda x:x[0].upper()+x[1:], name.split('-')))
if not isClass:
name = name[0].lower()+name[1:]
return ''.join(name)
@classmethod
def jsonCode(cls, propname, type):
propname = cls.javaName(propname, False)
tmpl = "ret.%s = obj.%s(\"%s\");"
if type == "string":
return tmpl%(propname, "getString", propname)
if type == "int":
return tmpl%(propname, "getInt", propname)
if type == "boolean":
return tmpl%(propname, "getBoolean", propname)
if type == "dict":
return """
JSONObject %s_obj = obj.getJSONObject("%s");
for(String name : JSONObject.getNames(%s_obj)) {
ret.%s.put(name, %s_obj.get(name));
}
"""%(propname, propname, propname, propname, propname)
if isinstance(type, str):
return "ret.%s = %s.fromJSON(obj.getJSONObject(%s));"%(propname,
cls.javaName(type), propname)
if isinstance(type, list):
return """
JSONArray %s_list = obj.getJSONArray("%s");
ret.%s = new ArrayList<%s>();
for(int i = 0;i<%s_list.length();i++) {
ret.%s.add(%s.fromJSON(%s_list.getJSONObject(i)));
}"""%(
propname, propname, propname, cls.javaName(type[0]), propname, propname,
cls.javaName(type[0]), propname
)
raise TypeError, "Cannot convert %s to java type from JSON."%(type)
@classmethod
def javaType(cls, type):
if type == "string":
return "String"
if type == "int":
return "int"
if type == "list":
return "List<?>"
if type == "dict":
return "Map<String, Object>"
if type == "boolean":
return "boolean"
if isinstance(type, str):
return cls.javaName(type)
if isinstance(type, list):
return "List<%s>"%(cls.javaType(type[0]))
if isinstance(type, dict):
return "Map<String, Object>"
raise TypeError, "Cannot convert %s to java type."%(type)

View File

@@ -8,12 +8,12 @@ class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
servers = Server.objects.all() servers = Server.objects.all()
for s in servers: for s in servers:
queue = events.server_queue(s) queue = events.ServerQueue(s)
stats = queue.stats() stats = queue.queue.stats()
for t in queue.tubes(): for t in queue.queue.tubes():
queue.use(t) queue.queue.use(t)
job = queue.peek_ready() job = queue.queue.peek_ready()
while job: while job:
print "Deleting %s from %s: %s"%(job.jid, t, job.body) print "Deleting %s from %s: %s"%(job.jid, t, job.body)
job.delete() job.delete()
job = queue.peek_ready() job = queue.queue.peek_ready()

252
api/oldtests.py Normal file
View File

@@ -0,0 +1,252 @@
from django.utils import unittest
import json
from django.test.client import Client
from django.contrib.auth.models import User
from minecraft.models import MinecraftProfile, Server, PlayerSession, MOTD, Ban
from local.models import Quote
import hashlib
import events
from django.conf import settings
class EventDispatcherTest(unittest.TestCase):
def setUp(self):
self.server = Server.objects.create(hostname='localhost', secret='secret')
events.EventQueue.flushAll()
def tearDown(self):
self.server.delete()
events.EventQueue.flushAll()
def testBroadcast(self):
webQueue = events.WebQueue(1)
serverQueue = events.ServerQueue(self.server)
events.Dispatcher.dispatch(events.Event('broadcast', message="Test"))
webEvents = webQueue.getEvents()
serverEvents = serverQueue.getEvents()
print webEvents
self.assertEqual(len(webEvents), 1)
self.assertEqual(len(serverEvents), 1)
self.assertEqual(webEvents[0]._type, 'broadcast')
self.assertEqual(serverEvents[0]._type, 'broadcast')
class EventQueueTest(unittest.TestCase):
def setUp(self):
self.queue = events.EventQueue("test")
self.queue.flush()
def tearDown(self):
self.queue.flush()
def testSendEvent(self):
self.queue.sendEvent(events.Event('test'))
def testSendReceiveEvent(self):
self.queue.sendEvent(events.Event('test'))
self.assertEqual(len(self.queue.getEvents()), 1)
def testMarshallEvent(self):
sentEvent = events.Event('test')
self.queue.sendEvent(sentEvent)
recvEvent = self.queue.getEvents()[0]
self.assertEqual(type(sentEvent), type(recvEvent))
self.assertEqual(sentEvent._type, recvEvent._type)
self.assertNotEqual(sentEvent._id, recvEvent._id)
class EventMarshallTest(unittest.TestCase):
def testMarshallToValidJSON(self):
json.loads(events.Event('test')._toJSON())
def testReprToValidJSON(self):
json.loads(repr(events.Event('test')))
def testLoadFromJSON(self):
evt = events.Event('test', player='')
jsonData = evt._toJSON()
decodedEvent = events.Event._fromJSON(jsonData)
class ServerPingTest(unittest.TestCase):
def setUp(self):
self.client = Client()
self.user = User.objects.create_user('ValidUsername', 'test@example.com')
self.user.minecraftprofile.mc_username = "ValidUsername"
self.user.minecraftprofile.save()
self.server = Server.objects.create(hostname='localhost', secret='secret')
tokenHash = hashlib.sha1()
tokenHash.update("%s%s%s"%('localhost', 0, 'secret'))
self.token = "%s$%s$%s"%('localhost', 0, tokenHash.hexdigest())
def tearDown(self):
self.user.delete()
self.server.delete()
def testPing(self):
resp = self.client.get('/api/server/whoami',
HTTP_AUTHORIZATION='X-Caminus %s'%(self.token))
self.assertEqual(resp.status_code, 200)
resp = json.loads(resp.content)
self.assertEqual(resp["api-version"], 2)
self.assertTrue("api-version" in resp)
class ServerEventTest(unittest.TestCase):
def setUp(self):
self.client = Client()
self.user = User.objects.create_user('ValidUsername', 'test@example.com')
self.user.minecraftprofile.mc_username = "ValidUsername"
self.user.minecraftprofile.save()
self.server = Server.objects.create(hostname='localhost', secret='secret')
tokenHash = hashlib.sha1()
tokenHash.update("%s%s%s"%('localhost', 0, 'secret'))
self.token = "%s$%s$%s"%('localhost', 0, tokenHash.hexdigest())
def tearDown(self):
self.user.delete()
self.server.delete()
def testBroadcast(self):
events.ServerQueue.broadcast(events.Event('broadcast', message="Test message"))
response = json.loads(self.client.get('/api/server/events',
HTTP_AUTHORIZATION='X-Caminus %s'%(self.token)).content)
self.assertTrue(len(response['events']) > 0)
response = json.loads(self.client.post('/api/server/events', {'job':response['events'][0]['id']},
HTTP_AUTHORIZATION='X-Caminus %s'%(self.token)).content)
self.assertEqual(response['result'], 'success')
def testUserMessage(self):
events.user_message(self.user, "Test user message")
response = json.loads(self.client.get('/api/server/events',
HTTP_AUTHORIZATION='X-Caminus %s'%(self.token)).content)
self.assertTrue(len(response['events']) > 0)
response = json.loads(self.client.post('/api/server/events', {'job':response['events'][0]['id']},
HTTP_AUTHORIZATION='X-Caminus %s'%(self.token)).content)
self.assertEqual(response['result'], 'success')
class MOTDTest(unittest.TestCase):
def setUp(self):
self.client = Client()
self.server = Server.objects.create(hostname="localhost", secret="")
def tearDown(self):
self.server.delete()
def testUnregisteredUser(self):
response = json.loads(self.client.get('/api/motd/NewUser').content)
self.assertIsInstance(response['motd'], list)
def testRandomMOTD(self):
m = MOTD.objects.create(server=self.server, text="testMOTD")
q = Quote.objects.create(text="testQUOTE")
response = json.loads(self.client.get('/api/motd/NewUser').content)
m.delete()
q.delete()
foundMOTD = False
foundQuote = False
for line in response['motd']:
if line == "testMOTD":
foundMOTD = True
if line == '"testQUOTE"':
foundQuote = True
self.assertTrue(foundMOTD)
self.assertTrue(foundQuote)
class SessionTest(unittest.TestCase):
def setUp(self):
self.client = Client()
self.user = User.objects.create_user('ValidUsername', 'test@example.com')
self.user.minecraftprofile.mc_username = "ValidUsername"
self.user.minecraftprofile.save()
self.server = Server.objects.create(hostname='localhost', secret='secret')
tokenHash = hashlib.sha1()
tokenHash.update("%s%s%s"%('localhost', 0, 'secret'))
self.token = "%s$%s$%s"%('localhost', 0, tokenHash.hexdigest())
def tearDown(self):
self.user.delete()
self.server.delete()
def testBannedStart(self):
b = Ban.objects.create(player=self.user.minecraftprofile, banner=self.user, reason="test")
resp = self.client.post('/api/server/session/%s/new'%(self.user.minecraftprofile.mc_username), {'ip': '127.0.0.1'}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
b.delete()
self.assertEqual(resp.status_code, 200)
session = json.loads(resp.content)
self.assertFalse(session['success'])
def testBadUserStart(self):
resp = self.client.post('/api/server/session/SomeUnknownUser/new', {'ip': '127.0.0.1'}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
self.assertEqual(resp.status_code, 200)
session = json.loads(resp.content)
self.assertFalse(session['success'])
def testInactiveStart(self):
self.user.is_active = False
self.user.save()
resp = self.client.post('/api/server/session/%s/new'%(self.user.minecraftprofile.mc_username), {'ip': '127.0.0.1'}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
self.user.is_active = True
self.user.save()
self.assertEqual(resp.status_code, 200)
session = json.loads(resp.content)
self.assertFalse(session['success'])
def testSessionStart(self):
resp = self.client.post('/api/server/session/%s/new'%(self.user.minecraftprofile.mc_username), {'ip': '127.0.0.1'}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
self.assertEqual(resp.status_code, 200)
session = json.loads(resp.content)
self.assertTrue(session['success'])
def testSessionEnd(self):
resp = self.client.post('/api/server/session/%s/new'%(self.user.minecraftprofile.mc_username), {'ip': '127.0.0.1'}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
session = json.loads(resp.content)
resp = self.client.get('/api/server/session/%s/close'%(self.user.minecraftprofile.mc_username), HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
self.assertEqual(resp.status_code, 200)
class PollTest(unittest.TestCase):
def setUp(self):
self.client = Client()
def testAnonymousPoll(self):
resp = self.client.get('/api/poll/0')
self.assertEqual(resp.status_code, 200)
data = json.loads(resp.content)
self.assertTrue('info' in data)
self.assertTrue('server' in data['info'])
self.assertTrue('user' in data['info'])
self.assertEqual(len(data['info']['user']), 0)
class EconomyTest(unittest.TestCase):
def setUp(self):
self.client = Client()
self.user = User.objects.create_user('ValidUsername', 'test@example.com')
self.user.minecraftprofile.mc_username = "ValidUsername"
self.user.minecraftprofile.save()
self.user.minecraftprofile.currencyaccount.balance=42
self.user.minecraftprofile.currencyaccount.save()
self.server = Server.objects.create(hostname='localhost', secret='secret')
tokenHash = hashlib.sha1()
tokenHash.update("%s%s%s"%('localhost', 0, 'secret'))
self.token = "%s$%s$%s"%('localhost', 0, tokenHash.hexdigest())
def tearDown(self):
self.user.delete()
self.server.delete()
def testBalanceQuery(self):
resp = self.client.get('/api/server/economy/ValidUsername', HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
data = json.loads(resp.content)
self.assertEqual(data['balance'], 42)
def testDeposit(self):
resp = self.client.put('/api/server/economy/ValidUsername', {'delta': 100}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
data = json.loads(resp.content)
self.assertEqual(data['balance'], 142)
self.assertTrue(data['success'])
def testWithdraw(self):
resp = self.client.put('/api/server/economy/ValidUsername', {'delta': -40}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
data = json.loads(resp.content)
self.assertEqual(data['balance'], 2)
self.assertTrue(data['success'])
def testOverdraw(self):
resp = self.client.put('/api/server/economy/ValidUsername', {'delta': -400}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
data = json.loads(resp.content)
self.assertFalse(data['success'])

View File

@@ -1,196 +1,216 @@
from django.utils import unittest from django.utils import unittest
import json from django.core.cache import cache
from django.test.client import Client
from django.contrib.auth.models import User
from minecraft.models import MinecraftProfile, Server, PlayerSession, MOTD, Ban from minecraft.models import MinecraftProfile, Server, PlayerSession, MOTD, Ban
from local.models import Quote
import hashlib
import events import events
from django.conf import settings import json
class ServerPingTest(unittest.TestCase): class QueueAssertMixin(object):
def assertQueueLength(self, queue, count):
self._queue_events = queue.getEvents(0)
self.assertEqual(len(self._queue_events), count)
class EventTest(unittest.TestCase):
def setUp(self): def setUp(self):
self.client = Client() events.EventQueue.flushAll()
self.user = User.objects.create_user('ValidUsername', 'test@example.com')
self.user.minecraftprofile.mc_username = "ValidUsername"
self.user.minecraftprofile.save()
self.server = Server.objects.create(hostname='localhost', secret='secret') self.server = Server.objects.create(hostname='localhost', secret='secret')
tokenHash = hashlib.sha1()
tokenHash.update("%s%s%s"%('localhost', 0, 'secret'))
self.token = "%s$%s$%s"%('localhost', 0, tokenHash.hexdigest())
def tearDown(self): def tearDown(self):
self.user.delete() events.EventQueue.flushAll()
self.server.delete() self.server.delete()
def testPing(self): class ServerEventTest(EventTest, QueueAssertMixin):
resp = self.client.get('/api/server/whoami', def testHeartbeatCache(self):
HTTP_AUTHORIZATION='X-Caminus %s'%(self.token)) oldBeat = cache.get('caminus-server-heartbeat-%s'%(self.server.id))
self.assertEqual(resp.status_code, 200) self.assertIsNone(oldBeat)
resp = json.loads(resp.content) events.Dispatcher.dispatch(events.Event('server-heartbeat', port=self.server.port,
self.assertEqual(resp["api-version"], 2) name=self.server.hostname, worldTimes={'world': 0}))
self.assertTrue("api-version" in resp) newBeat = cache.get('caminus-server-heartbeat-%s'%(self.server.id))
self.assertIsNotNone(oldBeat)
if settings.CAMINUS_USE_BEANSTALKD: class DispatcherTest(EventTest, QueueAssertMixin):
class ServerEventTest(unittest.TestCase): def testDispatch(self):
def setUp(self): queue = events.ServerQueue(self.server)
self.client = Client() self.assertQueueLength(queue, 0)
self.user = User.objects.create_user('ValidUsername', 'test@example.com') events.Dispatcher.dispatch(events.Event('test'))
self.user.minecraftprofile.mc_username = "ValidUsername" self.assertQueueLength(queue, 1)
self.user.minecraftprofile.save()
self.server = Server.objects.create(hostname='localhost', secret='secret')
tokenHash = hashlib.sha1()
tokenHash.update("%s%s%s"%('localhost', 0, 'secret'))
self.token = "%s$%s$%s"%('localhost', 0, tokenHash.hexdigest())
def tearDown(self):
self.user.delete()
self.server.delete()
def testBroadcast(self): def testBroadcast(self):
events.server_broadcast("Test message") queue = events.ServerQueue(self.server)
response = json.loads(self.client.get('/api/server/events', self.assertQueueLength(queue, 0)
HTTP_AUTHORIZATION='X-Caminus %s'%(self.token)).content) events.Dispatcher.broadcast('test')
self.assertTrue(len(response['events']) > 0) self.assertQueueLength(queue, 1)
response = json.loads(self.client.post('/api/server/events', {'job':response['events'][0]['id']},
HTTP_AUTHORIZATION='X-Caminus %s'%(self.token)).content)
self.assertEqual(response['result'], 'success')
def testUserMessage(self): class WebQueueTest(unittest.TestCase):
events.user_message(self.user, "Test user message")
response = json.loads(self.client.get('/api/server/events',
HTTP_AUTHORIZATION='X-Caminus %s'%(self.token)).content)
self.assertTrue(len(response['events']) > 0)
response = json.loads(self.client.post('/api/server/events', {'job':response['events'][0]['id']},
HTTP_AUTHORIZATION='X-Caminus %s'%(self.token)).content)
self.assertEqual(response['result'], 'success')
class MOTDTest(unittest.TestCase):
def setUp(self): def setUp(self):
self.client = Client() events.EventQueue.flushAll()
self.server = Server.objects.create(hostname="localhost", secret="")
def tearDown(self): def tearDown(self):
self.server.delete() events.EventQueue.flushAll()
def testUnregisteredUser(self): def testBroadcast(self):
response = json.loads(self.client.get('/api/motd/NewUser').content) queue = events.WebQueue(1)
self.assertIsInstance(response['motd'], list) events.WebQueue.broadcast(events.Event('test'))
webEvents = queue.getEvents()
self.assertEqual(len(webEvents), 1)
def testRandomMOTD(self): def testHugeBroadcast(self):
m = MOTD.objects.create(server=self.server, text="testMOTD") queues = []
q = Quote.objects.create(text="testQUOTE") for i in range(0, 30):
response = json.loads(self.client.get('/api/motd/NewUser').content) queues.append(events.WebQueue(i))
m.delete() events.WebQueue.broadcast(events.Event('test'))
q.delete() for queue in queues:
foundMOTD = False eventList = queue.getEvents()
foundQuote = False self.assertEqual(len(eventList), 1)
for line in response['motd']:
if line == "testMOTD":
foundMOTD = True
if line == '"testQUOTE"':
foundQuote = True
self.assertTrue(foundMOTD)
self.assertTrue(foundQuote)
class SessionTest(unittest.TestCase): def testActiveQueues(self):
events.WebQueue.clearActiveQueues()
self.assertEqual(len(events.WebQueue.activeQueues()), 0)
queues = []
for i in range(0, 30):
queues.append(events.WebQueue(i))
self.assertEqual(len(events.WebQueue.activeQueues()), len(queues))
def testSingleUseEvent(self):
queue = events.WebQueue(1)
queue.sendEvent(events.Event('test'))
self.assertEqual(len(queue.getEvents(0)), 1)
self.assertEqual(len(queue.getEvents(0)), 0)
class EventQueueTest(unittest.TestCase):
def setUp(self): def setUp(self):
self.client = Client() self.queue = events.EventQueue('test')
self.user = User.objects.create_user('ValidUsername', 'test@example.com') events.EventQueue.flushAll()
self.user.minecraftprofile.mc_username = "ValidUsername"
self.user.minecraftprofile.save()
self.server = Server.objects.create(hostname='localhost', secret='secret')
tokenHash = hashlib.sha1()
tokenHash.update("%s%s%s"%('localhost', 0, 'secret'))
self.token = "%s$%s$%s"%('localhost', 0, tokenHash.hexdigest())
def tearDown(self): def tearDown(self):
self.user.delete() events.EventQueue.flushAll()
self.server.delete()
def testBannedStart(self): def testSend(self):
b = Ban.objects.create(player=self.user.minecraftprofile, banner=self.user, reason="test") self.queue.sendEvent(events.Event('test'))
resp = self.client.post('/api/server/session/%s/new'%(self.user.minecraftprofile.mc_username), {'ip': '127.0.0.1'}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
b.delete()
self.assertEqual(resp.status_code, 200)
session = json.loads(resp.content)
self.assertFalse(session['success'])
def testBadUserStart(self): def testReceive(self):
resp = self.client.post('/api/server/session/SomeUnknownUser/new', {'ip': '127.0.0.1'}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token)) sentEvent = events.Event('test')
self.assertEqual(resp.status_code, 200) self.queue.sendEvent(sentEvent)
session = json.loads(resp.content) eventList = self.queue.getEvents()
self.assertFalse(session['success']) self.assertEqual(len(eventList), 1)
self.assertEqual(type(eventList[0]), events.Event)
self.assertNotEqual(eventList[0]._id, sentEvent._id)
self.assertEqual(eventList[0]._type, sentEvent._type)
self.assertNotEqual(eventList[0]._conn, sentEvent._conn)
def testInactiveStart(self): def testFlush(self):
self.user.is_active = False evt = events.Event('test')
self.user.save() for i in range(0, 300):
resp = self.client.post('/api/server/session/%s/new'%(self.user.minecraftprofile.mc_username), {'ip': '127.0.0.1'}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token)) self.queue.sendEvent(evt)
self.user.is_active = True self.assertEqual(len(self.queue.getEvents(0)), 300)
self.user.save() self.queue.flush()
self.assertEqual(resp.status_code, 200) self.assertEqual(len(self.queue.getEvents(0)), 0)
session = json.loads(resp.content)
self.assertFalse(session['success'])
def testSessionStart(self): def testFlushAll(self):
resp = self.client.post('/api/server/session/%s/new'%(self.user.minecraftprofile.mc_username), {'ip': '127.0.0.1'}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token)) evt = events.Event('test')
self.assertEqual(resp.status_code, 200) queues = []
session = json.loads(resp.content) for i in range(0, 10):
self.assertTrue(session['success']) queues.append(events.EventQueue('test-%d'%(i)))
for i in range(0, 10):
for queue in queues:
queue.sendEvent(evt)
for queue in queues:
self.assertEqual(len(queue.getEvents(0)), 10)
events.EventQueue.flushAll()
for queue in queues:
self.assertEqual(len(queue.getEvents(0)), 0)
def testSessionEnd(self):
resp = self.client.post('/api/server/session/%s/new'%(self.user.minecraftprofile.mc_username), {'ip': '127.0.0.1'}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
session = json.loads(resp.content)
resp = self.client.get('/api/server/session/%s/close'%(self.user.minecraftprofile.mc_username), HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
self.assertEqual(resp.status_code, 200)
class PollTest(unittest.TestCase): class EventTest(unittest.TestCase):
def setUp(self): def testEqual(self):
self.client = Client() e = events.Event('test')
e2 = events.Event('test')
self.assertEqual(e, e2)
def testAnonymousPoll(self): def testNewEvent(self):
resp = self.client.get('/api/poll/0') e = events.Event('test')
self.assertEqual(resp.status_code, 200) self.assertEqual(e._id, -1)
data = json.loads(resp.content) self.assertEqual(e._conn, None)
self.assertTrue('server-info' in data) self.assertDictEqual(e._properties, {'data': None})
self.assertTrue('user-info' in data)
self.assertEqual(len(data['user-info']), 0)
class EconomyTest(unittest.TestCase): def testValidTypes(self):
def setUp(self): e = events.Event('quit', player="TestPlayer")
self.client = Client() self.assertEqual(e.player, "TestPlayer")
self.user = User.objects.create_user('ValidUsername', 'test@example.com')
self.user.minecraftprofile.mc_username = "ValidUsername"
self.user.minecraftprofile.save()
self.user.minecraftprofile.currencyaccount.balance=42
self.user.minecraftprofile.currencyaccount.save()
self.server = Server.objects.create(hostname='localhost', secret='secret')
tokenHash = hashlib.sha1()
tokenHash.update("%s%s%s"%('localhost', 0, 'secret'))
self.token = "%s$%s$%s"%('localhost', 0, tokenHash.hexdigest())
def tearDown(self): def testInvalidType(self):
self.user.delete() with self.assertRaises(ValueError):
self.server.delete() e = events.Event('invalid')
def testBalanceQuery(self): def testMissingArgs(self):
resp = self.client.get('/api/server/economy/ValidUsername', HTTP_AUTHORIZATION="X-Caminus %s"%(self.token)) with self.assertRaises(KeyError):
data = json.loads(resp.content) e = events.Event('quit')
self.assertEqual(data['balance'], 42)
def testDeposit(self): def testExtraArgs(self):
resp = self.client.put('/api/server/economy/ValidUsername', {'delta': 100}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token)) e = events.Event('quit', player='TestPlayer', extra='extra')
data = json.loads(resp.content)
self.assertEqual(data['balance'], 142)
self.assertTrue(data['success'])
def testWithdraw(self): class EventMarshallTest(unittest.TestCase):
resp = self.client.put('/api/server/economy/ValidUsername', {'delta': -40}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token)) def testToDict(self):
data = json.loads(resp.content) self.assertDictEqual(events.Event('test')._toDict(), {
self.assertEqual(data['balance'], 2) '_id': -1,
self.assertTrue(data['success']) '_stamp': 0,
'_type': 'test',
'data': None,
})
def testOverdraw(self): def testJSONIdentity(self):
resp = self.client.put('/api/server/economy/ValidUsername', {'delta': -400}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token)) evt = events.Event('test')
data = json.loads(resp.content) self.assertEqual(events.Event._fromJSON(evt._toJSON()), evt)
self.assertFalse(data['success'])
def testDictIdentity(self):
evt = events.Event('test')
self.assertEqual(events.Event._fromDict(evt._toDict()), evt)
def testEmptyType(self):
desc = {}
data = events.Event._marshallType({}, desc, events.Event._datatypes(), 'marshall-test')
self.assertEqual(len(data), 0)
def testSimpleType(self):
desc = {'data': 'string'}
data = events.Event._marshallType({'data': 'value'}, desc,
events.Event._datatypes(), 'marshall-test')
self.assertEqual(len(data), 1)
self.assertTrue(isinstance(data['data'], str))
self.assertEqual(data['data'], 'value')
def testTypeMismatch(self):
desc = {'data': 'int'}
with self.assertRaises(ValueError):
events.Event._marshallType({'data': 'this is not an integer'}, desc,
events.Event._datatypes(), 'marshall-test')
def testComplexType(self):
desc = {'data': 'complex-type'}
typeDesc = {'complex-type': {'value': 'string'}}
data = events.Event._marshallType({'data': {'value': 'test'}}, desc, typeDesc, 'marshall-test')
self.assertDictEqual(data, {'data': {'value': 'test'}})
def testComplexTypeMismatch(self):
desc = {'data': 'complex-type'}
typeDesc = {'complex-type': {'value': 'string'}}
with self.assertRaises(KeyError):
events.Event._marshallType({'data': 'this is just a string'}, desc,
typeDesc, 'marshall-test')
def testMissingOptionalAttribute(self):
desc = {'data': events.OptionalEventProperty('string')}
data = events.Event._marshallType({}, desc, events.Event._datatypes(),
'marshall-test')
self.assertDictEqual(data, {'data': None})
def testOptionalAttribute(self):
desc = {'data': events.OptionalEventProperty('string')}
data = events.Event._marshallType({'data': 'value'}, desc,
events.Event._datatypes(),
'marshall-test')
self.assertDictEqual(data, {'data': 'value'})
def testMissingOptionalComplextType(self):
desc = {'data': events.OptionalEventProperty('complex-type')}
typeDesc = {'complex-type': {'value': 'string'}}
data = events.Event._marshallType({}, desc, typeDesc, 'marshall-test')
self.assertDictEqual(data, {'data': None})

View File

@@ -1,7 +1,7 @@
from django.db import models from django.db import models
from django.contrib.auth.models import User from django.contrib.auth.models import User
import badges.api import badges.api
from caminus.api.events import user_message from caminus.api.events import Dispatcher
from notification import models as notification from notification import models as notification
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
@@ -58,7 +58,7 @@ class Award(models.Model):
super(Award, self).save(*args, **kwargs) super(Award, self).save(*args, **kwargs)
badges.api.badge_awarded.send_robust(sender=intern(str(self.badge.slug)), award=self) badges.api.badge_awarded.send_robust(sender=intern(str(self.badge.slug)), award=self)
notification.send([self.user], "badge_awarded", {"award": self, 'notice_description': self.badge, 'notice_url': reverse('user_profile')}) notification.send([self.user], "badge_awarded", {"award": self, 'notice_description': self.badge, 'notice_url': reverse('user_profile')})
user_message(self.user, "You have been awarded the '%s' badge."%self.badge) Dispatcher.user_message(self.user, "You have been awarded the '%s' badge."%self.badge)
def __unicode__(self): def __unicode__(self):
return "%s for %s"%(self.badge.__unicode__(), self.user.__unicode__()) return "%s for %s"%(self.badge.__unicode__(), self.user.__unicode__())

View File

@@ -18,3 +18,13 @@ class Bounty(models.Model):
self.save() self.save()
killer.currencyaccount.balance = F('balance') + self.price killer.currencyaccount.balance = F('balance') + self.price
killer.currencyaccount.save() killer.currencyaccount.save()
def handle_murder(sender, event, *args, **kwargs):
bounties = Bounty.objects.filter(target__mc_username=evt['payload']['player'])
killer = MinecraftProfile.objects.get(mc_username=evt['payload']['killer'])
for bounty in bounties:
bounty.close(killer)
if len(bounties) > 0:
Dispatcher.broadcast("The bounty on %s has been collected."%(evt['payload']['player']))
events.on_event.connect(handle_murder, sender=intern('player-murder'))

83
bulkops.py Normal file
View File

@@ -0,0 +1,83 @@
# Bulk insert/update DB operations for the Django ORM. Useful when
# inserting/updating lots of objects where the bottleneck is overhead
# in talking to the database. Instead of doing this
#
# for x in seq:
# o = SomeObject()
# o.foo = x
# o.save()
#
# or equivalently this
#
# for x in seq:
# SomeObject.objects.create(foo=x)
#
# do this
#
# l = []
# for x in seq:
# o = SomeObject()
# o.foo = x
# l.append(o)
# insert_many(l)
#
# Note that these operations are really simple. They won't work with
# many-to-many relationships, and you may have to divide really big
# lists into smaller chunks before sending them through.
#
# History
# 2010-12-10: quote column names, reported by Beres Botond.
def insert_many(objects, using="default"):
"""Insert list of Django objects in one SQL query. Objects must be
of the same Django model. Note that save is not called and signals
on the model are not raised."""
if not objects:
return
import django.db.models
from django.db import connections
con = connections[using]
model = objects[0].__class__
fields = [f for f in model._meta.fields if not isinstance(f, django.db.models.AutoField)]
parameters = []
for o in objects:
parameters.append(tuple(f.get_db_prep_save(f.pre_save(o, True), connection=con) for f in fields))
table = model._meta.db_table
column_names = ",".join(con.ops.quote_name(f.column) for f in fields)
placeholders = ",".join(("%s",) * len(fields))
con.cursor().executemany(
"insert into %s (%s) values (%s)" % (table, column_names, placeholders),
parameters)
def update_many(objects, fields=[], using="default"):
"""Update list of Django objects in one SQL query, optionally only
overwrite the given fields (as names, e.g. fields=["foo"]).
Objects must be of the same Django model. Note that save is not
called and signals on the model are not raised."""
if not objects:
return
import django.db.models
from django.db import connections
con = connections[using]
names = fields
meta = objects[0]._meta
fields = [f for f in meta.fields if not isinstance(f, django.db.models.AutoField) and (not names or f.name in names)]
if not fields:
raise ValueError("No fields to update, field names are %s." % names)
fields_with_pk = fields + [meta.pk]
parameters = []
for o in objects:
parameters.append(tuple(f.get_db_prep_save(f.pre_save(o, True), connection=con) for f in fields_with_pk))
table = meta.db_table
assignments = ",".join(("%s=%%s"% con.ops.quote_name(f.column)) for f in fields)
con.cursor().executemany(
"update %s set %s where %s=%%s" % (table, assignments, con.ops.quote_name(meta.pk.column)),
parameters)

View File

@@ -9,7 +9,7 @@ from django.template import RequestContext
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from notification import models as notification from notification import models as notification
from api.events import server_broadcast, user_message from api.events import Dispatcher
def index(request): def index(request):
forums = models.Forum.objects.filter(parent=None) forums = models.Forum.objects.filter(parent=None)
@@ -45,7 +45,7 @@ def reply(request, topicID=None):
if reply.parent.user != request.user: if reply.parent.user != request.user:
notification.send([reply.parent.user], "forum_reply", {"reply": reply, 'notice_url': reverse('forums.views.post', kwargs={'id':reply.id}), 'notice_description': reply.topic().title}) notification.send([reply.parent.user], "forum_reply", {"reply": reply, 'notice_url': reverse('forums.views.post', kwargs={'id':reply.id}), 'notice_description': reply.topic().title})
messages.info(request, "Reply successful") messages.info(request, "Reply successful")
user_message(reply.parent.user, "%s replied to your post in '%s'", Dispatcher.user_message(reply.parent.user, "%s replied to your post in '%s'",
request.user, reply.topic().title) request.user, reply.topic().title)
return HttpResponseRedirect(reverse('forums.views.post', kwargs={"id":reply.id})) return HttpResponseRedirect(reverse('forums.views.post', kwargs={"id":reply.id}))
return render_to_response('forums/reply.html', {"parent":parentPost, "form":form}, context_instance = RequestContext(request)) return render_to_response('forums/reply.html', {"parent":parentPost, "form":form}, context_instance = RequestContext(request))
@@ -84,7 +84,7 @@ def newTopic(request, forumID=None):
topic.rootPost = reply topic.rootPost = reply
topic.save() topic.save()
messages.info(request, "Posting successful") messages.info(request, "Posting successful")
server_broadcast("New forum topic: %s", topic.title) Dispatcher.broadcast("New forum topic: %s", topic.title)
return HttpResponseRedirect(reverse('forums.views.post', kwargs={'id': reply.id})) return HttpResponseRedirect(reverse('forums.views.post', kwargs={'id': reply.id}))
return render_to_response('forums/newTopic.html', {"forum":parentForum, "replyForm":replyForm, "topicForm": topicForm}, context_instance = RequestContext(request)) return render_to_response('forums/newTopic.html', {"forum":parentForum, "replyForm":replyForm, "topicForm": topicForm}, context_instance = RequestContext(request))

View File

@@ -12,7 +12,7 @@ from django.contrib.auth import authenticate, login
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
import forms import forms
import models import models
from api.events import user_message from api.events import Dispatcher
from forums.models import Forum from forums.models import Forum
from minecraft.forms import ProfileForm from minecraft.forms import ProfileForm
from minecraft.models import MinecraftProfile from minecraft.models import MinecraftProfile
@@ -80,7 +80,7 @@ def register(request):
profile.save() profile.save()
user = authenticate(username=userForm.cleaned_data['username'], password=userForm.cleaned_data['password']) user = authenticate(username=userForm.cleaned_data['username'], password=userForm.cleaned_data['password'])
notification.send_now([invite.creator], "invite_accepted", {"new_user": user}) notification.send_now([invite.creator], "invite_accepted", {"new_user": user})
user_message(invite.creator, "%s has accepted your invite."%(user.username)) Dispatcher.user_message(invite.creator, "%s has accepted your invite."%(user.username))
login(request, user) login(request, user)
del request.session['profile-invite'] del request.session['profile-invite']
return HttpResponseRedirect(reverse('welcome')) return HttpResponseRedirect(reverse('welcome'))

View File

@@ -1,7 +1,7 @@
from django.core.management.base import NoArgsCommand from django.core.management.base import NoArgsCommand
from market.models import MarketOrder from market.models import MarketOrder
import json import json
from api.events import market_queue from api.events import MarketQueue
from datetime import datetime from datetime import datetime
class Command(NoArgsCommand): class Command(NoArgsCommand):
@@ -11,9 +11,9 @@ class Command(NoArgsCommand):
print "Processing currently open orders" print "Processing currently open orders"
for order in MarketOrder.objects.filter(close_stamp__isnull=True): for order in MarketOrder.objects.filter(close_stamp__isnull=True):
order.process() order.process()
queue = market_queue() queue = MarketQueue()
while True: while True:
job = queue.reserve() for job in queue.getEvents():
jobInfo = json.loads(job.body) jobInfo = json.loads(job.body)
if jobInfo['event']['type'] == "market-order": if jobInfo['event']['type'] == "market-order":
try: try:

View File

@@ -4,7 +4,7 @@ from django.db import transaction
from django.db.models import F from django.db.models import F
from datetime import datetime from datetime import datetime
from django.db.models.signals import post_save from django.db.models.signals import post_save
from api.events import queue_market_event, MarketOrderEvent from api.events import MarketQueue, Event
from vault.models import VaultSlot from vault.models import VaultSlot
from django.contrib.auth.models import User from django.contrib.auth.models import User
from minecraft.items import ITEMS from minecraft.items import ITEMS
@@ -68,8 +68,9 @@ class MarketOrder(models.Model):
def enqueue_process(self): def enqueue_process(self):
if settings.CAMINUS_USE_BEANSTALKD: if settings.CAMINUS_USE_BEANSTALKD:
evt = MarketOrderEvent(self.id) evt = Event('market-order', orderID=self.id)
queue_market_event(evt) queue = MarketQueue()
queue.sendEvent(evt)
else: else:
self.process() self.process()

View File

@@ -8,9 +8,3 @@ Replace this with more appropriate tests for your application.
from django.test import TestCase from django.test import TestCase
class SimpleTest(TestCase):
def test_basic_addition(self):
"""
Tests that 1 + 1 always equals 2.
"""
self.assertEqual(1 + 1, 2)

View File

@@ -1,6 +1,9 @@
import models import models
def server_info(request): def server_info(request):
try:
period = models.Server.objects.all()[0].day_period() period = models.Server.objects.all()[0].day_period()
except IndexError, e:
period = "daytime"
return {'minecraft_servers': models.Server.objects.all(), return {'minecraft_servers': models.Server.objects.all(),
'first_day_period': period} 'first_day_period': period}

View File

@@ -15,6 +15,10 @@ class Item(models.Model):
@classmethod @classmethod
def get(cls, material, damage=0, data=0): def get(cls, material, damage=0, data=0):
if damage is None:
damage = 0
if data is None:
data = 0
return cls.objects.get_or_create(material=material, damage=damage, return cls.objects.get_or_create(material=material, damage=damage,
data=data)[0] data=data)[0]
@@ -174,5 +178,6 @@ import api.events
def kick_banned_user(sender, instance, created, **kwargs): def kick_banned_user(sender, instance, created, **kwargs):
player = instance.player player = instance.player
if player.isBanned(): if player.isBanned():
api.events.broadcast_server_event(api.events.KickEvent(player.mc_username, instance.reason)) api.events.ServerQueue.broadcast(api.events.Event('player-kick',
player=player.mc_username, message=instance.reason))
post_save.connect(kick_banned_user, sender=Ban) post_save.connect(kick_banned_user, sender=Ban)

View File

@@ -8,7 +8,7 @@ from django.core.urlresolvers import reverse
from django.contrib.auth.models import User from django.contrib.auth.models import User
from notification import models as notification from notification import models as notification
from django.contrib import messages from django.contrib import messages
from api.events import user_message from api.events import Dispatcher
@login_required @login_required
def create(request): def create(request):
@@ -24,7 +24,7 @@ def create(request):
adminUsers = User.objects.filter(is_staff=True) adminUsers = User.objects.filter(is_staff=True)
notification.send(adminUsers, "petition_opened", {"petition": petition, 'notice_url': reverse('petition.views.view', kwargs={'id':petition.id}),'notice_description': petition.id}) notification.send(adminUsers, "petition_opened", {"petition": petition, 'notice_url': reverse('petition.views.view', kwargs={'id':petition.id}),'notice_description': petition.id})
for user in adminUsers: for user in adminUsers:
user_message(user, "%s has opened a petition."%(request.user)) Dispatcher.user_message(user, "%s has opened a petition."%(request.user))
messages.info(request, "Petition created.") messages.info(request, "Petition created.")
return HttpResponseRedirect(reverse('petition.views.view', kwargs={"id":petition.id})) return HttpResponseRedirect(reverse('petition.views.view', kwargs={"id":petition.id}))
return render_to_response('petition/create.html', {'form':form}, context_instance = RequestContext(request)) return render_to_response('petition/create.html', {'form':form}, context_instance = RequestContext(request))
@@ -59,7 +59,7 @@ def comment(request, id):
comment.save() comment.save()
adminUsers = User.objects.filter(is_staff=True) adminUsers = User.objects.filter(is_staff=True)
for user in adminUsers: for user in adminUsers:
user_message(user, "%s has opened a petition."%(request.user)) Dispatcher.user_message(user, "%s has opened a petition."%(request.user))
notification.send(adminUsers, "petition_commented", {"petition": petition, 'notice_url': reverse('petition.views.view', kwargs={'id':petition.id}),'notice_description': petition.id, 'comment': comment}) notification.send(adminUsers, "petition_commented", {"petition": petition, 'notice_url': reverse('petition.views.view', kwargs={'id':petition.id}),'notice_description': petition.id, 'comment': comment})
if comment.author != petition.author: if comment.author != petition.author:
notification.send([petition.author], "petition_commented", {"petition": petition, 'notice_url': reverse('petition.views.view', kwargs={'id':petition.id}),'notice_description': petition.id, 'comment': comment}) notification.send([petition.author], "petition_commented", {"petition": petition, 'notice_url': reverse('petition.views.view', kwargs={'id':petition.id}),'notice_description': petition.id, 'comment': comment})

View File

@@ -11,3 +11,4 @@ pydot==1.0.28
beanstalkc beanstalkc
stripe stripe
django-devserver django-devserver
PyYAML==3.10

View File

@@ -4,9 +4,10 @@ if [ ! -f virtualenv/bin/activate ];then
fi fi
source virtualenv/bin/activate source virtualenv/bin/activate
pip install -r pip-requirements pip install -r pip-requirements
coverage erase
coverage run ./manage.py test $@ coverage run ./manage.py test $@
ret=$? ret=$?
if [ $ret -eq 0 ];then if [ $ret -eq 0 ];then
coverage report -m --include=\* --omit=\*/migrations/\*,settings.py,local_settings.py,manage.py coverage report -m --include=\* --omit=\*/migrations/\*,settings.py,local_settings.py,manage.py,bulkops.py
fi fi
exit $ret exit $ret

View File

@@ -104,7 +104,6 @@ MIDDLEWARE_CLASSES = (
'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware', 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware',
'django.middleware.http.ConditionalGetMiddleware', 'django.middleware.http.ConditionalGetMiddleware',
'django.middleware.gzip.GZipMiddleware', 'django.middleware.gzip.GZipMiddleware',
'caminus.devtools.PrintExceptionMiddleware',
) )
ROOT_URLCONF = 'caminus.urls' ROOT_URLCONF = 'caminus.urls'

View File

@@ -38,6 +38,10 @@ div[class|="inventory-item"] .quantity {
padding: 3px; padding: 3px;
} }
div[class|="inventory-item"] .damage.empty{
display: none;
}
div[class|="inventory-item"] .damage { div[class|="inventory-item"] .damage {
position: absolute; position: absolute;
bottom: 0; bottom: 0;

View File

@@ -20,7 +20,6 @@ EventPoller.prototype.start = function() {
}; };
EventPoller.prototype.dispatchEvent = function(evt) { EventPoller.prototype.dispatchEvent = function(evt) {
console.log(evt['type']);
if (evt['type'] in this.handlers) { if (evt['type'] in this.handlers) {
this.handlers[evt['type']].forEach(function (callback, idx, handlers) { this.handlers[evt['type']].forEach(function (callback, idx, handlers) {
callback(evt, evt['payload']); callback(evt, evt['payload']);
@@ -50,6 +49,7 @@ EventPoller.prototype._failedPoll = function(data) {
}); });
} }
this.id = 0; this.id = 0;
window.setTimeout(this.poll.bind(this), 5000);
} }
EventPoller.prototype._successfulPoll = function(data) { EventPoller.prototype._successfulPoll = function(data) {
@@ -78,6 +78,7 @@ EventPoller.prototype._successfulPoll = function(data) {
var that = this; var that = this;
$(data['events']).each(function(idx, evt) { $(data['events']).each(function(idx, evt) {
console.log(evt['type']);
that.dispatchEvent(evt); that.dispatchEvent(evt);
}); });

78
static/js/vault.js Normal file
View File

@@ -0,0 +1,78 @@
var InventoryWidget = function(tableElement) {
this.e = $(tableElement);
this.slots = new Array();
this.e.find("td[data-position]").each((function (idx, td) {
var slot = new ItemSlot(td);
this.slots[slot.getPosition()] = slot;
}).bind(this));
Caminus.eventPoll.onEvent('vault-contents', this._vaultEvent.bind(this));
}
InventoryWidget.prototype._vaultEvent = function(evt, payload) {
$(payload.items).each((function(idx, item) {
var slot = this.slotByPosition(item.position);
slot.updateFromJSON(item);
}).bind(this));
};
InventoryWidget.prototype.slotByPosition = function(pos) {
console.log(this);
return this.slots[pos];
}
var ItemSlot = function(tdElement) {
this.e = $(tdElement);
this.item = $(this.e.find('div[class|="inventory-item"]')[0]);
}
ItemSlot.prototype.getPosition = function() {
return this.e.data('position');
}
ItemSlot.prototype.updateFromJSON = function(data) {
this.setMaterial(data.item);
this.setDurability(data.durablility);
this.setDamage(data.damage);
this.setQuantity(data.quantity);
this.setName(data.name);
}
ItemSlot.prototype.setName = function(s) {
this.item.data('name', s);
this.item.find('.name').html(s);
}
ItemSlot.prototype.setMaterial = function(id) {
this.item.data('material', id);
this.item.attr('class',"inventory-item-"+id);
}
ItemSlot.prototype.setQuantity = function(qty) {
this.item.data('quantity', qty);
this.item.find('.quantity').html(qty);
}
ItemSlot.prototype.setDamage = function(dmg) {
this.item.data('damage', dmg);
this.item.find('.damage', "50%");
if (dmg == 0)
this.item.find('.damage').addClass("empty");
else
this.item.find('.damage').removeClass("empty");
}
ItemSlot.prototype.getDamage = function() {
return this.item.data('damage');
}
ItemSlot.prototype.setDurability = function(d) {
this.item.data('durability', d);
this.setDamage(this.getDamage());
}
$(document).ready(function() {
$('table.inventory').each(function (idx, inventory) {
new InventoryWidget(inventory);
});
});

View File

@@ -1,12 +1,12 @@
<div class="inventory-item-{{item.material}}"> <div class="inventory-item-{{item.material}}" data-material="{{item.material}}"
{% if quantity > 0 %} data-quantity="{{quantity}}" data-damage="{{item.damage}}">
<div class="quantity">{{quantity}}</div> <div class="quantity">
{% if quantity > 0 %}{{quantity}}
</div>
{% endif %} {% endif %}
<div class="name">{{item.name}}</div> <div class="name">{{item.name}}</div>
{% if item.damage %} <div class="damage {% if not item.damage %}empty{%endif%}">
<div class="damage">
<div class="current" <div class="current"
style="width:{{item.damagePct}}%"></div> style="width:{{item.damagePct}}%"></div>
</div> </div>
{% endif %}
</div> </div>

View File

@@ -1,8 +1,14 @@
{% extends "base_simple.html" %} {% extends "base_simple.html" %}
{%load static %}
{% get_static_prefix as STATIC_PREFIX %}
{% block title %}Vault{% endblock %} {% block title %}Vault{% endblock %}
{% block sectiontitle %}Your Vault{% endblock %} {% block sectiontitle %}Your Vault{% endblock %}
{% block extrahead %}
<script type="text/javascript" language="javascript" src="{{STATIC_PREFIX}}/js/vault.js"></script>
{% endblock %}
{% block content %} {% block content %}
<table class="inventory"> <table class="inventory">
<tr> <tr>
@@ -11,10 +17,8 @@
</tr> </tr>
<tr> <tr>
{% endif %} {% endif %}
<td> <td data-id="{{slot.id}}" data-position="{{slot.position}}">
{% if slot.item %}
{% include 'common/item.html' with quantity=slot.quantity item=slot.item %} {% include 'common/item.html' with quantity=slot.quantity item=slot.item %}
{% endif %}
</td> </td>
{% endfor %} {% endfor %}
</tr> </tr>

View File

@@ -1,9 +1,10 @@
from django.db import models from django.db import models
from minecraft.models import MinecraftProfile from minecraft.models import MinecraftProfile
from django.db.models.signals import post_save, post_delete from django.db.models.signals import post_save, post_delete
from api.events import VaultContentsEvent, broadcast_server_event from api.events import Event, ServerQueue, WebQueue
from minecraft.models import Item from minecraft.models import Item
from django.conf import settings from django.conf import settings
from caminus.bulkops import insert_many
class VaultError(Exception): class VaultError(Exception):
pass pass
@@ -59,43 +60,46 @@ class VaultSlot(models.Model):
raise VaultError, "Insufficient available slots." raise VaultError, "Insufficient available slots."
return slots return slots
def send_vault_delete(sender, instance, *args, **kwargs):
slots = [
{
'item': None,
'quantity': -1,
'damage': None,
'data': None,
'position': instance.position
}
]
broadcast_server_event(VaultContentsEvent(instance.player.mc_username,
slots))
def send_vault_update(sender, instance, created, *args, **kwargs): def send_vault_update(sender, instance, created, *args, **kwargs):
if created and instance.item: if instance.item:
slots = [ slots = [
{ {
'item': instance.item.material, 'material': instance.item.material,
'quantity': instance.quantity, 'quantity': instance.quantity,
'damage': instance.item.damage, 'damage': instance.item.damage,
'data': instance.item.data, 'data': instance.item.data,
'position': instance.position 'position': instance.position,
'name': instance.item.name(),
'durability': instance.item.metadata['durability']
} }
] ]
broadcast_server_event(VaultContentsEvent(instance.player.mc_username, slots)) else:
slots = [
{
'material': None,
'quantity': None,
'damage': None,
'data': None,
'position': instance.position,
'name': None,
'durability': None
}
]
evt = Event('vault-contents', player=instance.player.mc_username, items=slots)
ServerQueue.broadcast(evt)
WebQueue.broadcast(evt)
post_save.connect(send_vault_update, sender=VaultSlot, dispatch_uid='derp') post_save.connect(send_vault_update, sender=VaultSlot, dispatch_uid='derp')
post_delete.connect(send_vault_delete, sender=VaultSlot, dispatch_uid='derp')
def create_initial_slots(sender, instance, created, *args, **kwargs): def ensure_initial_slots(sender, instance, created, *args, **kwargs):
slots = instance.vault_slots.all() slots = instance.vault_slots.all()
if len(slots) < settings.CAMINUS_VAULT_SLOTS: if len(slots) < settings.CAMINUS_VAULT_SLOTS:
neededPositions = range(0, settings.CAMINUS_VAULT_SLOTS) neededPositions = range(0, settings.CAMINUS_VAULT_SLOTS)
for s in slots: for s in slots:
neededPositions.remove(s.position) neededPositions.remove(s.position)
bulk = []
for pos in neededPositions: for pos in neededPositions:
VaultSlot.objects.create(player=instance, position=pos) bulk.append(VaultSlot(player=instance, position=pos))
insert_many(bulk)
post_save.connect(create_initial_slots, sender=MinecraftProfile)
post_save.connect(ensure_initial_slots, sender=MinecraftProfile)