Compare commits
10 Commits
a306df9df0
...
61c39dc9c2
Author | SHA1 | Date | |
---|---|---|---|
|
61c39dc9c2 | ||
|
4ac2d61359 | ||
|
8ef29f3358 | ||
|
f9340c7582 | ||
|
ec6cc0db7d | ||
|
25ae5444f7 | ||
|
3dea38f51d | ||
|
018b360f61 | ||
|
adde1343f1 | ||
|
690ff8f21e |
82
api/event-types.yml
Normal file
82
api/event-types.yml
Normal 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
|
451
api/events.py
451
api/events.py
@@ -1,155 +1,358 @@
|
||||
from django.conf import settings
|
||||
import os
|
||||
from django.core.cache import cache
|
||||
import time
|
||||
from minecraft.models import Server
|
||||
from json import loads, dumps, JSONEncoder
|
||||
from json import loads, dumps
|
||||
import beanstalkc
|
||||
from django.contrib.auth.models import User
|
||||
import yaml
|
||||
import django.dispatch
|
||||
|
||||
class EventEncoder(JSONEncoder):
|
||||
def default(self, obj):
|
||||
if isinstance(obj, Event):
|
||||
return {'type': obj.type, 'payload': obj.data}
|
||||
return super(EventEncoder, self).default(obj)
|
||||
|
||||
on_event = django.dispatch.Signal(providing_args=["event"])
|
||||
|
||||
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):
|
||||
def __init__(self, type, data):
|
||||
self.type = type
|
||||
self.data = data
|
||||
def __init__(self, type=None, *args, **kwargs):
|
||||
self._id = -1
|
||||
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 __init__(self, sender, message):
|
||||
super(ChatEvent, self).__init__(type='chat', data={'sender': sender,
|
||||
'message': message})
|
||||
def _delete(self):
|
||||
self._conn.delete(self._id)
|
||||
|
||||
class VaultContentsEvent(Event):
|
||||
def __init__(self, player, items):
|
||||
super(VaultContentsEvent, self).__init__(type='vault-contents',
|
||||
data={'player': player, 'items': items})
|
||||
def _bury(self, priority=0):
|
||||
self._conn.bury(self._id, 0)
|
||||
|
||||
class QuitEvent(Event):
|
||||
def __init__(self, player):
|
||||
super(QuitEvent, self).__init__(type='quit', data={'player': player})
|
||||
def __getattribute__(self, name):
|
||||
if name.startswith('_'):
|
||||
return super(Event, self).__getattribute__(name)
|
||||
return self._properties[name]
|
||||
|
||||
class JoinEvent(Event):
|
||||
def __init__(self, player):
|
||||
super(JoinEvent, self).__init__(type='join', data={'player': player})
|
||||
def __setattr__(self, name, value):
|
||||
if name.startswith('_'):
|
||||
return super(Event, self).__setattr__(name, value)
|
||||
if name in self._properties:
|
||||
raise NotImplementedError, "Event properties are not modifyable"
|
||||
|
||||
class BroadcastEvent(Event):
|
||||
def __init__(self, message):
|
||||
super(BroadcastEvent, self).__init__(type='broadcast', data={'message':
|
||||
message})
|
||||
@classmethod
|
||||
def _marshallType(cls, data, typeDescription, dataTypes, paramPath=""):
|
||||
assert(isinstance(dataTypes, dict))
|
||||
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):
|
||||
def __init__(self, player, message):
|
||||
super(PlayerDeathEvent, self).__init__(type='player-death',
|
||||
data={'player': player, 'message': message})
|
||||
@classmethod
|
||||
def _types(cls):
|
||||
types = cache.get('caminus-event-types')
|
||||
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):
|
||||
def __init__(self, server, data):
|
||||
now = server.current_time()
|
||||
t = now.second+now.minute*60+now.hour*60*60
|
||||
super(ServerHeartbeatEvent, self).__init__(type='server-heartbeat',
|
||||
data={'server': server.id, 'heartbeat': data, 'time': t,
|
||||
'day-period': server.day_period()})
|
||||
@classmethod
|
||||
def _datatypes(cls):
|
||||
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'))['types']
|
||||
return types
|
||||
|
||||
class KickEvent(Event):
|
||||
def __init__(self, user, message):
|
||||
super(KickEvent, self).__init__(type='player-kick',
|
||||
data={'player': user, 'message': message})
|
||||
def __repr__(self):
|
||||
return "Event(%r)"%(self._properties)
|
||||
|
||||
class PlayerMessageEvent(Event):
|
||||
def __init__(self, user, message):
|
||||
super(PlayerMessageEvent, self).__init__(type='player-message',
|
||||
data={'message': message, 'player': user})
|
||||
def __eq__(self, other):
|
||||
return self._properties == other._properties and self._id == other._id
|
||||
|
||||
class MarketOrderEvent(Event):
|
||||
def __init__(self, orderID):
|
||||
super(MarketOrderEvent, self).__init__(type='market-order',
|
||||
data={'orderID': orderID})
|
||||
@classmethod
|
||||
def _fromBeanstalkJob(cls, job):
|
||||
assert(isinstance(job, beanstalkc.Job))
|
||||
id = job.jid
|
||||
evt = cls._fromJSON(job.body)
|
||||
evt._id = int(id)
|
||||
evt._conn = job.conn
|
||||
return evt
|
||||
|
||||
def server_queue(server, users=[]):
|
||||
queueName = 'caminus-broadcast-%s'%server.id
|
||||
queue = beanstalkc.Connection(host=settings.CAMINUS_BEANSTALKD_HOST,
|
||||
def _toDict(self):
|
||||
ret = {
|
||||
'_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)
|
||||
queue.use(queueName)
|
||||
queue.watch(queueName)
|
||||
if len(users) > 0:
|
||||
for user in users:
|
||||
queue.watch("caminus-user-%s"%user)
|
||||
return queue
|
||||
self.queue.use(self.name)
|
||||
self.queue.watch(self.name)
|
||||
|
||||
def send_server_event(server, event):
|
||||
if settings.CAMINUS_USE_BEANSTALKD:
|
||||
queue = server_queue(server)
|
||||
json = dumps(event, cls=EventEncoder)
|
||||
queue.put(json)
|
||||
@classmethod
|
||||
def route(self, evt):
|
||||
raise NotImplementedError
|
||||
|
||||
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():
|
||||
send_server_event(server, event)
|
||||
queue = ServerQueue(server)
|
||||
queue.sendEvent(evt)
|
||||
|
||||
def server_broadcast(message, *args):
|
||||
message = message%args
|
||||
for server in Server.objects.all():
|
||||
event = BroadcastEvent(message)
|
||||
send_server_event(server, event)
|
||||
send_web_event(event)
|
||||
class WebQueue(EventQueue):
|
||||
def __init__(self, id):
|
||||
self.id = id
|
||||
super(WebQueue, self).__init__('caminus-web-%s'%(id))
|
||||
if id != "0":
|
||||
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):
|
||||
player = user.minecraftprofile.mc_username
|
||||
player_message(player, message, *args)
|
||||
@classmethod
|
||||
def route(cls, event):
|
||||
cls.broadcast(event)
|
||||
|
||||
def player_message(playername, message, *args):
|
||||
message = message%args
|
||||
for server in Server.objects.all():
|
||||
event = PlayerMessageEvent(playername, message)
|
||||
send_server_event(server, event)
|
||||
@classmethod
|
||||
def broadcast(cls, event):
|
||||
latest = cache.get('minecraft-web-events')
|
||||
if latest is None:
|
||||
latest = []
|
||||
latest.append(dumps(event._toJSON()))
|
||||
while len(latest) > 10:
|
||||
latest.pop(0)
|
||||
cache.set('minecraft-web-events', latest, 86400)
|
||||
|
||||
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
|
||||
for queue in cls.activeQueues():
|
||||
queue.sendEvent(event)
|
||||
|
||||
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
|
||||
@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
|
||||
|
||||
def queue_market_event(event):
|
||||
queue = market_queue()
|
||||
json = dumps({'stamp': time.time(), 'event': event}, cls=EventEncoder)
|
||||
queue.put(json)
|
||||
@staticmethod
|
||||
def clearActiveQueues():
|
||||
cache.set('minecraft-web-queues', None)
|
||||
|
||||
def send_web_event(event):
|
||||
latest = cache.get('minecraft-web-events')
|
||||
if latest is None:
|
||||
latest = []
|
||||
latest.append(dumps(event, cls=EventEncoder))
|
||||
while len(latest) > 10:
|
||||
latest.pop(0)
|
||||
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):
|
||||
evt = ChatEvent(playername, message)
|
||||
send_web_event(evt)
|
||||
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
|
||||
|
@@ -5,14 +5,14 @@ from django.conf import settings
|
||||
import appversion
|
||||
from minecraft.models import MinecraftProfile
|
||||
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.http import HttpResponse
|
||||
from urllib2 import urlopen
|
||||
import json
|
||||
from datetime import datetime
|
||||
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 vault.models import VaultSlot
|
||||
|
||||
@@ -44,7 +44,7 @@ class NewPlayerSessionHandler(BaseHandler):
|
||||
server = request.server
|
||||
profile = MinecraftProfile.objects.get(mc_username__exact=playername)
|
||||
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}
|
||||
else:
|
||||
return {'success': False, 'error': 'Your account is inactive.', 'permissions': []}
|
||||
@@ -57,7 +57,7 @@ class ClosePlayerSessionHandler(BaseHandler):
|
||||
for session in sessions:
|
||||
session.end = datetime.now()
|
||||
session.save()
|
||||
send_web_event(QuitEvent(playername))
|
||||
WebQueue.broadcast(Event('quit', player=playername))
|
||||
return {'valid': True}
|
||||
|
||||
class EconomyHandler(BaseHandler):
|
||||
@@ -88,42 +88,26 @@ class ServerEventHandler(BaseHandler):
|
||||
allowed_methods = ('GET', 'POST', 'PUT')
|
||||
|
||||
def read(self, request):
|
||||
queue = server_queue(request.server)
|
||||
queue.watch('caminus-broadcast-%s'%request.server.id)
|
||||
events = []
|
||||
job = queue.reserve(timeout=30)
|
||||
while job:
|
||||
job.bury()
|
||||
events.append({'id': job.jid, 'event': json.loads(job.body)})
|
||||
job = queue.reserve(timeout=0)
|
||||
return {'events': events, 'is-live': settings.CAMINUS_USE_BEANSTALKD}
|
||||
queue = ServerQueue(request.server)
|
||||
events = []
|
||||
for e in queue.getEvents():
|
||||
events.append(json.loads(e.body)['event'])
|
||||
e.bury()
|
||||
print {'events': events}
|
||||
return {'events': events, 'is-live': settings.CAMINUS_USE_BEANSTALKD}
|
||||
|
||||
def create(self, request):
|
||||
queue = server_queue(request.server)
|
||||
try:
|
||||
queue.delete(int(request.POST['job']))
|
||||
except Exception, e:
|
||||
pass
|
||||
return {'result': 'success'}
|
||||
queue = ServerQueue(request.server)
|
||||
try:
|
||||
queue.deleteJob(int(request.POST['job']))
|
||||
except Exception, e:
|
||||
pass
|
||||
return {'result': 'success'}
|
||||
|
||||
def update(self, request):
|
||||
events = json.loads(request.POST['events'])['events']
|
||||
for evt in events:
|
||||
if evt['type'] == 'chat':
|
||||
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']))
|
||||
Dispatcher.dispatch(Event.fromDict(evt)
|
||||
return {'result': 'success'}
|
||||
|
||||
class ChatHandler(BaseHandler):
|
||||
@@ -131,8 +115,8 @@ class ChatHandler(BaseHandler):
|
||||
|
||||
def create(self, request):
|
||||
chat(request.user.minecraftprofile.mc_username, request.POST['message'])
|
||||
server_broadcast("<%s> %s"%(request.user.minecraftprofile.mc_username,
|
||||
request.POST['message']))
|
||||
ServerQueue.broadcast(BroadcastEvent("<%s> %s",
|
||||
request.user.minecraftprofile.mc_username, request.POST['message']))
|
||||
|
||||
class PollHandler(BaseHandler):
|
||||
allowed_methods = ('GET',)
|
||||
@@ -150,20 +134,8 @@ class PollHandler(BaseHandler):
|
||||
pollData['events'] = []
|
||||
pollData['info'] = info
|
||||
pollData['poll-id'] = timestamp
|
||||
if timestamp == "0" and settings.CAMINUS_USE_BEANSTALKD:
|
||||
pollData['poll-id'] = time.time()
|
||||
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()
|
||||
eventQueue = WebQueue(timestamp)
|
||||
pollData['events'] = eventQueue.getEvents()
|
||||
return pollData
|
||||
|
||||
class VaultHandler(BaseHandler):
|
||||
@@ -173,9 +145,17 @@ class VaultHandler(BaseHandler):
|
||||
player = MinecraftProfile.objects.get(mc_username__exact=playername)
|
||||
items = []
|
||||
for slot in player.vault_slots.all():
|
||||
items.append({'item': slot.item, 'quantity':
|
||||
slot.quantity, 'damage': slot.damage, 'data': slot.data,
|
||||
'position': slot.position})
|
||||
data = {}
|
||||
data['quantity'] = slot.quantity
|
||||
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}
|
||||
|
||||
def update(self, request, playername):
|
||||
@@ -186,18 +166,13 @@ class VaultHandler(BaseHandler):
|
||||
updated = False
|
||||
slot,created = VaultSlot.objects.get_or_create(player=player,
|
||||
position=stack['position'])
|
||||
if slot.item != stack['item']:
|
||||
slot.item = stack['item']
|
||||
item = Item.get(stack['item'], stack['damage'], stack['data'])
|
||||
if slot.item != item:
|
||||
slot.item = item
|
||||
updated = True
|
||||
if slot.quantity != stack['quantity']:
|
||||
slot.quantity = stack['quantity']
|
||||
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:
|
||||
slot.save()
|
||||
return {'success': True}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.core.cache import cache
|
||||
from api import events
|
||||
from minecraft.models import Server
|
||||
|
||||
@@ -8,17 +9,23 @@ class Command(BaseCommand):
|
||||
def handle(self, *args, **options):
|
||||
servers = Server.objects.all()
|
||||
for s in servers:
|
||||
queue = events.server_queue(s)
|
||||
stats = queue.stats()
|
||||
queue = events.ServerQueue(s)
|
||||
stats = queue.queue.stats()
|
||||
print s
|
||||
for k,v in stats.iteritems():
|
||||
print "\t%s: %s"%(k, v)
|
||||
print "\tTubes:"
|
||||
for t in queue.tubes():
|
||||
print "\t\t%s"%(t)
|
||||
queue.use(t)
|
||||
next = queue.peek_ready()
|
||||
if next:
|
||||
print "\t\t\tNext job: %s"%(next.body)
|
||||
else:
|
||||
print "\t\t\tNo pending job."
|
||||
print "Tubes:"
|
||||
for t in queue.queue.tubes():
|
||||
print "\t%s"%(t)
|
||||
queue.queue.use(t)
|
||||
next = queue.queue.peek_ready()
|
||||
if next:
|
||||
print "\t\tNext job: %s"%(next.body)
|
||||
else:
|
||||
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)
|
||||
|
140
api/management/commands/event_types.py
Normal file
140
api/management/commands/event_types.py
Normal 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)
|
@@ -8,12 +8,12 @@ class Command(BaseCommand):
|
||||
def handle(self, *args, **options):
|
||||
servers = Server.objects.all()
|
||||
for s in servers:
|
||||
queue = events.server_queue(s)
|
||||
stats = queue.stats()
|
||||
for t in queue.tubes():
|
||||
queue.use(t)
|
||||
job = queue.peek_ready()
|
||||
queue = events.ServerQueue(s)
|
||||
stats = queue.queue.stats()
|
||||
for t in queue.queue.tubes():
|
||||
queue.queue.use(t)
|
||||
job = queue.queue.peek_ready()
|
||||
while job:
|
||||
print "Deleting %s from %s: %s"%(job.jid, t, job.body)
|
||||
job.delete()
|
||||
job = queue.peek_ready()
|
||||
job = queue.queue.peek_ready()
|
||||
|
252
api/oldtests.py
Normal file
252
api/oldtests.py
Normal 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'])
|
354
api/tests.py
354
api/tests.py
@@ -1,196 +1,216 @@
|
||||
from django.utils import unittest
|
||||
import json
|
||||
from django.test.client import Client
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.cache import cache
|
||||
from minecraft.models import MinecraftProfile, Server, PlayerSession, MOTD, Ban
|
||||
from local.models import Quote
|
||||
import hashlib
|
||||
import events
|
||||
from django.conf import settings
|
||||
import json
|
||||
|
||||
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())
|
||||
class QueueAssertMixin(object):
|
||||
def assertQueueLength(self, queue, count):
|
||||
self._queue_events = queue.getEvents(0)
|
||||
self.assertEqual(len(self._queue_events), count)
|
||||
|
||||
def tearDown(self):
|
||||
self.user.delete()
|
||||
self.server.delete()
|
||||
class EventTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
events.EventQueue.flushAll()
|
||||
self.server = Server.objects.create(hostname='localhost', secret='secret')
|
||||
|
||||
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)
|
||||
def tearDown(self):
|
||||
events.EventQueue.flushAll()
|
||||
self.server.delete()
|
||||
|
||||
if settings.CAMINUS_USE_BEANSTALKD:
|
||||
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())
|
||||
class ServerEventTest(EventTest, QueueAssertMixin):
|
||||
def testHeartbeatCache(self):
|
||||
oldBeat = cache.get('caminus-server-heartbeat-%s'%(self.server.id))
|
||||
self.assertIsNone(oldBeat)
|
||||
events.Dispatcher.dispatch(events.Event('server-heartbeat', port=self.server.port,
|
||||
name=self.server.hostname, worldTimes={'world': 0}))
|
||||
newBeat = cache.get('caminus-server-heartbeat-%s'%(self.server.id))
|
||||
self.assertIsNotNone(oldBeat)
|
||||
|
||||
def tearDown(self):
|
||||
self.user.delete()
|
||||
self.server.delete()
|
||||
class DispatcherTest(EventTest, QueueAssertMixin):
|
||||
def testDispatch(self):
|
||||
queue = events.ServerQueue(self.server)
|
||||
self.assertQueueLength(queue, 0)
|
||||
events.Dispatcher.dispatch(events.Event('test'))
|
||||
self.assertQueueLength(queue, 1)
|
||||
|
||||
def testBroadcast(self):
|
||||
events.server_broadcast("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 testBroadcast(self):
|
||||
queue = events.ServerQueue(self.server)
|
||||
self.assertQueueLength(queue, 0)
|
||||
events.Dispatcher.broadcast('test')
|
||||
self.assertQueueLength(queue, 1)
|
||||
|
||||
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 WebQueueTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
events.EventQueue.flushAll()
|
||||
|
||||
class MOTDTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.server = Server.objects.create(hostname="localhost", secret="")
|
||||
def tearDown(self):
|
||||
events.EventQueue.flushAll()
|
||||
|
||||
def tearDown(self):
|
||||
self.server.delete()
|
||||
def testBroadcast(self):
|
||||
queue = events.WebQueue(1)
|
||||
events.WebQueue.broadcast(events.Event('test'))
|
||||
webEvents = queue.getEvents()
|
||||
self.assertEqual(len(webEvents), 1)
|
||||
|
||||
def testUnregisteredUser(self):
|
||||
response = json.loads(self.client.get('/api/motd/NewUser').content)
|
||||
self.assertIsInstance(response['motd'], list)
|
||||
def testHugeBroadcast(self):
|
||||
queues = []
|
||||
for i in range(0, 30):
|
||||
queues.append(events.WebQueue(i))
|
||||
events.WebQueue.broadcast(events.Event('test'))
|
||||
for queue in queues:
|
||||
eventList = queue.getEvents()
|
||||
self.assertEqual(len(eventList), 1)
|
||||
|
||||
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)
|
||||
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))
|
||||
|
||||
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 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)
|
||||
|
||||
def tearDown(self):
|
||||
self.user.delete()
|
||||
self.server.delete()
|
||||
class EventQueueTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.queue = events.EventQueue('test')
|
||||
events.EventQueue.flushAll()
|
||||
|
||||
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 tearDown(self):
|
||||
events.EventQueue.flushAll()
|
||||
|
||||
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 testSend(self):
|
||||
self.queue.sendEvent(events.Event('test'))
|
||||
|
||||
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 testReceive(self):
|
||||
sentEvent = events.Event('test')
|
||||
self.queue.sendEvent(sentEvent)
|
||||
eventList = self.queue.getEvents()
|
||||
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 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 testFlush(self):
|
||||
evt = events.Event('test')
|
||||
for i in range(0, 300):
|
||||
self.queue.sendEvent(evt)
|
||||
self.assertEqual(len(self.queue.getEvents(0)), 300)
|
||||
self.queue.flush()
|
||||
self.assertEqual(len(self.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)
|
||||
def testFlushAll(self):
|
||||
evt = events.Event('test')
|
||||
queues = []
|
||||
for i in range(0, 10):
|
||||
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)
|
||||
|
||||
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('server-info' in data)
|
||||
self.assertTrue('user-info' in data)
|
||||
self.assertEqual(len(data['user-info']), 0)
|
||||
class EventTest(unittest.TestCase):
|
||||
def testEqual(self):
|
||||
e = events.Event('test')
|
||||
e2 = events.Event('test')
|
||||
self.assertEqual(e, e2)
|
||||
|
||||
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 testNewEvent(self):
|
||||
e = events.Event('test')
|
||||
self.assertEqual(e._id, -1)
|
||||
self.assertEqual(e._conn, None)
|
||||
self.assertDictEqual(e._properties, {'data': None})
|
||||
|
||||
def tearDown(self):
|
||||
self.user.delete()
|
||||
self.server.delete()
|
||||
def testValidTypes(self):
|
||||
e = events.Event('quit', player="TestPlayer")
|
||||
self.assertEqual(e.player, "TestPlayer")
|
||||
|
||||
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 testInvalidType(self):
|
||||
with self.assertRaises(ValueError):
|
||||
e = events.Event('invalid')
|
||||
|
||||
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 testMissingArgs(self):
|
||||
with self.assertRaises(KeyError):
|
||||
e = events.Event('quit')
|
||||
|
||||
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 testExtraArgs(self):
|
||||
e = events.Event('quit', player='TestPlayer', extra='extra')
|
||||
|
||||
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'])
|
||||
class EventMarshallTest(unittest.TestCase):
|
||||
def testToDict(self):
|
||||
self.assertDictEqual(events.Event('test')._toDict(), {
|
||||
'_id': -1,
|
||||
'_stamp': 0,
|
||||
'_type': 'test',
|
||||
'data': None,
|
||||
})
|
||||
|
||||
def testJSONIdentity(self):
|
||||
evt = events.Event('test')
|
||||
self.assertEqual(events.Event._fromJSON(evt._toJSON()), evt)
|
||||
|
||||
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})
|
||||
|
@@ -1,7 +1,7 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
import badges.api
|
||||
from caminus.api.events import user_message
|
||||
from caminus.api.events import Dispatcher
|
||||
from notification import models as notification
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
@@ -58,7 +58,7 @@ class Award(models.Model):
|
||||
super(Award, self).save(*args, **kwargs)
|
||||
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')})
|
||||
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):
|
||||
return "%s for %s"%(self.badge.__unicode__(), self.user.__unicode__())
|
||||
|
@@ -18,3 +18,13 @@ class Bounty(models.Model):
|
||||
self.save()
|
||||
killer.currencyaccount.balance = F('balance') + self.price
|
||||
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
83
bulkops.py
Normal 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)
|
@@ -9,7 +9,7 @@ from django.template import RequestContext
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from notification import models as notification
|
||||
from api.events import server_broadcast, user_message
|
||||
from api.events import Dispatcher
|
||||
|
||||
def index(request):
|
||||
forums = models.Forum.objects.filter(parent=None)
|
||||
@@ -45,7 +45,7 @@ def reply(request, topicID=None):
|
||||
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})
|
||||
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)
|
||||
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))
|
||||
@@ -84,7 +84,7 @@ def newTopic(request, forumID=None):
|
||||
topic.rootPost = reply
|
||||
topic.save()
|
||||
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 render_to_response('forums/newTopic.html', {"forum":parentForum, "replyForm":replyForm, "topicForm": topicForm}, context_instance = RequestContext(request))
|
||||
|
||||
|
@@ -12,7 +12,7 @@ from django.contrib.auth import authenticate, login
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
import forms
|
||||
import models
|
||||
from api.events import user_message
|
||||
from api.events import Dispatcher
|
||||
from forums.models import Forum
|
||||
from minecraft.forms import ProfileForm
|
||||
from minecraft.models import MinecraftProfile
|
||||
@@ -80,7 +80,7 @@ def register(request):
|
||||
profile.save()
|
||||
user = authenticate(username=userForm.cleaned_data['username'], password=userForm.cleaned_data['password'])
|
||||
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)
|
||||
del request.session['profile-invite']
|
||||
return HttpResponseRedirect(reverse('welcome'))
|
||||
|
@@ -1,7 +1,7 @@
|
||||
from django.core.management.base import NoArgsCommand
|
||||
from market.models import MarketOrder
|
||||
import json
|
||||
from api.events import market_queue
|
||||
from api.events import MarketQueue
|
||||
from datetime import datetime
|
||||
|
||||
class Command(NoArgsCommand):
|
||||
@@ -11,27 +11,27 @@ class Command(NoArgsCommand):
|
||||
print "Processing currently open orders"
|
||||
for order in MarketOrder.objects.filter(close_stamp__isnull=True):
|
||||
order.process()
|
||||
queue = market_queue()
|
||||
queue = MarketQueue()
|
||||
while True:
|
||||
job = queue.reserve()
|
||||
jobInfo = json.loads(job.body)
|
||||
if jobInfo['event']['type'] == "market-order":
|
||||
try:
|
||||
order = MarketOrder.objects.get(id=jobInfo['event']['payload']['orderID'])
|
||||
except MarketOrder.DoesNotExist:
|
||||
# The orders might not be saved yet due to transactions
|
||||
job.release()
|
||||
continue
|
||||
if order.close_stamp:
|
||||
print "Got event for a closed order", order.id
|
||||
job.delete()
|
||||
continue
|
||||
if order.quantity == 0:
|
||||
print "Saving bugged order", order.id
|
||||
order.close_stamp = datetime.now()
|
||||
order.save()
|
||||
job.delete()
|
||||
continue
|
||||
order.process()
|
||||
for job in queue.getEvents():
|
||||
jobInfo = json.loads(job.body)
|
||||
if jobInfo['event']['type'] == "market-order":
|
||||
try:
|
||||
order = MarketOrder.objects.get(id=jobInfo['event']['payload']['orderID'])
|
||||
except MarketOrder.DoesNotExist:
|
||||
# The orders might not be saved yet due to transactions
|
||||
job.release()
|
||||
continue
|
||||
if order.close_stamp:
|
||||
print "Got event for a closed order", order.id
|
||||
job.delete()
|
||||
continue
|
||||
if order.quantity == 0:
|
||||
print "Saving bugged order", order.id
|
||||
order.close_stamp = datetime.now()
|
||||
order.save()
|
||||
job.delete()
|
||||
continue
|
||||
order.process()
|
||||
|
||||
job.delete()
|
||||
job.delete()
|
||||
|
@@ -4,7 +4,7 @@ from django.db import transaction
|
||||
from django.db.models import F
|
||||
from datetime import datetime
|
||||
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 django.contrib.auth.models import User
|
||||
from minecraft.items import ITEMS
|
||||
@@ -68,8 +68,9 @@ class MarketOrder(models.Model):
|
||||
|
||||
def enqueue_process(self):
|
||||
if settings.CAMINUS_USE_BEANSTALKD:
|
||||
evt = MarketOrderEvent(self.id)
|
||||
queue_market_event(evt)
|
||||
evt = Event('market-order', orderID=self.id)
|
||||
queue = MarketQueue()
|
||||
queue.sendEvent(evt)
|
||||
else:
|
||||
self.process()
|
||||
|
||||
|
@@ -8,9 +8,3 @@ Replace this with more appropriate tests for your application.
|
||||
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)
|
||||
|
@@ -1,6 +1,9 @@
|
||||
import models
|
||||
|
||||
def server_info(request):
|
||||
period = models.Server.objects.all()[0].day_period()
|
||||
try:
|
||||
period = models.Server.objects.all()[0].day_period()
|
||||
except IndexError, e:
|
||||
period = "daytime"
|
||||
return {'minecraft_servers': models.Server.objects.all(),
|
||||
'first_day_period': period}
|
||||
|
@@ -15,6 +15,10 @@ class Item(models.Model):
|
||||
|
||||
@classmethod
|
||||
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,
|
||||
data=data)[0]
|
||||
|
||||
@@ -174,5 +178,6 @@ import api.events
|
||||
def kick_banned_user(sender, instance, created, **kwargs):
|
||||
player = instance.player
|
||||
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)
|
||||
|
@@ -8,7 +8,7 @@ from django.core.urlresolvers import reverse
|
||||
from django.contrib.auth.models import User
|
||||
from notification import models as notification
|
||||
from django.contrib import messages
|
||||
from api.events import user_message
|
||||
from api.events import Dispatcher
|
||||
|
||||
@login_required
|
||||
def create(request):
|
||||
@@ -24,7 +24,7 @@ def create(request):
|
||||
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})
|
||||
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.")
|
||||
return HttpResponseRedirect(reverse('petition.views.view', kwargs={"id":petition.id}))
|
||||
return render_to_response('petition/create.html', {'form':form}, context_instance = RequestContext(request))
|
||||
@@ -59,7 +59,7 @@ def comment(request, id):
|
||||
comment.save()
|
||||
adminUsers = User.objects.filter(is_staff=True)
|
||||
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})
|
||||
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})
|
||||
|
@@ -11,3 +11,4 @@ pydot==1.0.28
|
||||
beanstalkc
|
||||
stripe
|
||||
django-devserver
|
||||
PyYAML==3.10
|
||||
|
@@ -4,9 +4,10 @@ if [ ! -f virtualenv/bin/activate ];then
|
||||
fi
|
||||
source virtualenv/bin/activate
|
||||
pip install -r pip-requirements
|
||||
coverage erase
|
||||
coverage run ./manage.py test $@
|
||||
ret=$?
|
||||
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
|
||||
exit $ret
|
||||
|
@@ -104,7 +104,6 @@ MIDDLEWARE_CLASSES = (
|
||||
'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware',
|
||||
'django.middleware.http.ConditionalGetMiddleware',
|
||||
'django.middleware.gzip.GZipMiddleware',
|
||||
'caminus.devtools.PrintExceptionMiddleware',
|
||||
)
|
||||
|
||||
ROOT_URLCONF = 'caminus.urls'
|
||||
|
@@ -38,6 +38,10 @@ div[class|="inventory-item"] .quantity {
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
div[class|="inventory-item"] .damage.empty{
|
||||
display: none;
|
||||
}
|
||||
|
||||
div[class|="inventory-item"] .damage {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
|
@@ -20,7 +20,6 @@ EventPoller.prototype.start = function() {
|
||||
};
|
||||
|
||||
EventPoller.prototype.dispatchEvent = function(evt) {
|
||||
console.log(evt['type']);
|
||||
if (evt['type'] in this.handlers) {
|
||||
this.handlers[evt['type']].forEach(function (callback, idx, handlers) {
|
||||
callback(evt, evt['payload']);
|
||||
@@ -50,6 +49,7 @@ EventPoller.prototype._failedPoll = function(data) {
|
||||
});
|
||||
}
|
||||
this.id = 0;
|
||||
window.setTimeout(this.poll.bind(this), 5000);
|
||||
}
|
||||
|
||||
EventPoller.prototype._successfulPoll = function(data) {
|
||||
@@ -78,6 +78,7 @@ EventPoller.prototype._successfulPoll = function(data) {
|
||||
|
||||
var that = this;
|
||||
$(data['events']).each(function(idx, evt) {
|
||||
console.log(evt['type']);
|
||||
that.dispatchEvent(evt);
|
||||
});
|
||||
|
||||
|
78
static/js/vault.js
Normal file
78
static/js/vault.js
Normal 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);
|
||||
});
|
||||
});
|
@@ -1,12 +1,12 @@
|
||||
<div class="inventory-item-{{item.material}}">
|
||||
{% if quantity > 0 %}
|
||||
<div class="quantity">{{quantity}}</div>
|
||||
<div class="inventory-item-{{item.material}}" data-material="{{item.material}}"
|
||||
data-quantity="{{quantity}}" data-damage="{{item.damage}}">
|
||||
<div class="quantity">
|
||||
{% if quantity > 0 %}{{quantity}}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="name">{{item.name}}</div>
|
||||
{% if item.damage %}
|
||||
<div class="damage">
|
||||
<div class="current"
|
||||
style="width:{{item.damagePct}}%"></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="damage {% if not item.damage %}empty{%endif%}">
|
||||
<div class="current"
|
||||
style="width:{{item.damagePct}}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,8 +1,14 @@
|
||||
{% extends "base_simple.html" %}
|
||||
{%load static %}
|
||||
{% get_static_prefix as STATIC_PREFIX %}
|
||||
|
||||
{% block title %}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 %}
|
||||
<table class="inventory">
|
||||
<tr>
|
||||
@@ -11,10 +17,8 @@
|
||||
</tr>
|
||||
<tr>
|
||||
{% endif %}
|
||||
<td>
|
||||
{% if slot.item %}
|
||||
<td data-id="{{slot.id}}" data-position="{{slot.position}}">
|
||||
{% include 'common/item.html' with quantity=slot.quantity item=slot.item %}
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
|
@@ -1,9 +1,10 @@
|
||||
from django.db import models
|
||||
from minecraft.models import MinecraftProfile
|
||||
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 django.conf import settings
|
||||
from caminus.bulkops import insert_many
|
||||
|
||||
class VaultError(Exception):
|
||||
pass
|
||||
@@ -59,43 +60,46 @@ class VaultSlot(models.Model):
|
||||
raise VaultError, "Insufficient available 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):
|
||||
if created and instance.item:
|
||||
if instance.item:
|
||||
slots = [
|
||||
{
|
||||
'item': instance.item.material,
|
||||
'material': instance.item.material,
|
||||
'quantity': instance.quantity,
|
||||
'damage': instance.item.damage,
|
||||
'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_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()
|
||||
if len(slots) < settings.CAMINUS_VAULT_SLOTS:
|
||||
neededPositions = range(0, settings.CAMINUS_VAULT_SLOTS)
|
||||
for s in slots:
|
||||
neededPositions.remove(s.position)
|
||||
bulk = []
|
||||
for pos in neededPositions:
|
||||
VaultSlot.objects.create(player=instance, position=pos)
|
||||
|
||||
post_save.connect(create_initial_slots, sender=MinecraftProfile)
|
||||
bulk.append(VaultSlot(player=instance, position=pos))
|
||||
insert_many(bulk)
|
||||
|
||||
post_save.connect(ensure_initial_slots, sender=MinecraftProfile)
|
||||
|
Reference in New Issue
Block a user