This commit is contained in:
Torrie Fischer
2023-06-10 13:21:31 +02:00
parent 4ac2d61359
commit 61c39dc9c2
17 changed files with 753 additions and 328 deletions

View File

@@ -1,4 +1,21 @@
---
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
@@ -9,6 +26,8 @@ types:
name: !optional string
durability: !optional int
events:
test:
data: !optional string
quit:
player: string
join:

View File

@@ -3,41 +3,109 @@ import os
from django.core.cache import cache
import time
from minecraft.models import Server
from json import loads, dumps, JSONEncoder, JSONDecoder
from json import loads, dumps
import beanstalkc
from django.contrib.auth.models import User
import yaml
import django.dispatch
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 broadcastMessage(cls, message, *args):
event = BroadcastEvent(message%args)
ServerQueue.broadcast(event)
WebQueue.broadcast(event)
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
class EventEncoder(JSONEncoder):
def default(self, obj):
if isinstance(obj, Event):
return {'type': obj.type, 'properties': obj.properties}
if isinstance(obj, beanstalkc.Job):
return {'id': obj.id, 'event': loads(obj.body)}
return super(EventEncoder, self).default(obj)
@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, *args, **kwargs):
types = self.types()
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], type)
self._type = type
self._properties = self._marshallType(kwargs, types[type], self._datatypes(), type)
else:
raise ValueError, "Unknown event type '%s'"%(type)
def _delete(self):
self._conn.delete(self._id)
def _bury(self, priority=0):
self._conn.bury(self._id, 0)
def __getattribute__(self, name):
if name.startswith('_'):
return super(Event, self).__getattribute__(name)
return self._properties[name]
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"
@classmethod
def marshallType(cls, data, typeDescription, paramPath=""):
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), paramPath)
return cls._marshallType(data, str(typeDescription), dataTypes, paramPath)
try:
if typeDescription == "string":
return str(data)
@@ -55,25 +123,30 @@ class Event(object):
for key, elementType in typeDescription.iteritems():
key = key.replace('-', '_')
if key in data:
ret[key] = cls.marshallType(data[key], elementType,
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], "%s[%d]"%(paramPath, i)))
ret.append(cls._marshallType(item, typeDescription[0], dataTypes, "%s[%d]"%(paramPath, i)))
i += 1
return ret
if typeDescription in cls.datatypes():
return cls.marshallType(data, cls.datatypes()[typeDescription], paramPath)
if typeDescription in dataTypes:
return cls._marshallType(data, dataTypes[typeDescription], dataTypes, paramPath)
raise TypeError, "Unknown event data type '%s.%s'"%(paramPath, typeDescription)
@classmethod
def types(cls):
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]+
@@ -85,7 +158,7 @@ class Event(object):
return types
@classmethod
def datatypes(cls):
def _datatypes(cls):
typefile = os.path.sep.join(
__file__.split(os.path.sep)[0:-1]+
['event-types.yml',]
@@ -95,19 +168,57 @@ class Event(object):
return types
def __repr__(self):
return dumps(self, cls=EventEncoder)
return "Event(%r)"%(self._properties)
def toJSON(self):
return dumps(self, cls=EventEncoder)
def __eq__(self, other):
return self._properties == other._properties and self._id == other._id
@classmethod
def fromJSON(cls, json):
decoded = loads(json, cls=EventDecoder)
print decoded
evt = Event(json['type'], json['event']['payload'])
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 _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, node):
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):
@@ -118,13 +229,13 @@ class EventQueue(object):
self.queue.use(self.name)
self.queue.watch(self.name)
@classmethod
def route(self, evt):
raise NotImplementedError
def sendEvent(self, evt):
assert(isinstance(evt, Event))
eventData = {}
eventData['stamp'] = time.time()
eventData['event'] = evt
json = dumps(eventData, cls=EventEncoder)
self.queue.put(json)
id = self.queue.put(evt._toJSON())
def getEvents(self, timeout=30):
ret = []
@@ -132,7 +243,12 @@ class EventQueue(object):
while job:
ret.append(job)
job = self.queue.reserve(timeout=0)
return ret
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()
@@ -153,17 +269,22 @@ class EventQueue(object):
ret.append(EventQueue(c))
return ret
def deleteJob(self, id):
self.queue.delete(id)
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():
@@ -183,18 +304,23 @@ class WebQueue(EventQueue):
for queueName, stamp in activeCache.iteritems():
if time.time()-stamp > 120:
expired.append(queueName)
del activeCache[queueName]
for e in expired:
del activeCache[e]
cache.set('minecraft-web-queues', activeCache, 3600)
for e in expired:
q = WebQueue(e)
q.flush()
@classmethod
def route(cls, event):
cls.broadcast(event)
@classmethod
def broadcast(cls, event):
latest = cache.get('minecraft-web-events')
if latest is None:
latest = []
latest.append(dumps(event, cls=EventEncoder))
latest.append(dumps(event._toJSON()))
while len(latest) > 10:
latest.pop(0)
cache.set('minecraft-web-events', latest, 86400)
@@ -212,7 +338,11 @@ class WebQueue(EventQueue):
ret.append(WebQueue(name))
return ret
def getEvents(self):
@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:
@@ -222,59 +352,7 @@ class WebQueue(EventQueue):
ret.append(loads(e))
return ret
else:
ret = super(WebQueue, self).getEvents()
ret = super(WebQueue, self).getEvents(timeout)
for evt in ret:
evt.delete()
evt._delete()
return ret
def server_broadcast(message, *args):
message = message%args
for server in Server.objects.all():
event = Event('broadcast', message=message)
queue = ServerQueue(server)
queue.sendEvent(event)
send_web_event(event)
def user_message(user, message, *args):
player = user.minecraftprofile.mc_username
player_message(player, message, *args)
def player_message(playername, message, *args):
message = message%args
for server in Server.objects.all():
event = PlayerMessageEvent(playername, message)
queue = ServerQueue(server)
queue.sendEvent(event)
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)
activeQueues = cache.get('minecraft-web-queues')
#print "Active queues", repr(activeQueues)
if activeQueues is None:
activeQueues = {}
for tube in activeQueues.iterkeys():
queue.use('caminus-web-%s'%(tube))
print "Web queue", tube
pendingJob = queue.peek_ready()
while pendingJob:
pending = loads(pendingJob.body)
if time.time()-pending['stamp'] > 30:
pendingJob.delete()
pendingJob = queue.peek_ready()
else:
pendingJob = None
queue.put(json)
def chat(playername, message):
evt = ChatEvent(playername, message)
WebQueue.broadcast(evt)

View File

@@ -12,7 +12,7 @@ from urllib2 import urlopen
import json
from datetime import datetime
from models import cachePlayerList
from events import ServerQueue, WebQueue, Event, chat
from events import ServerQueue, WebQueue, Event, Dispatcher
from bounty.models import Bounty
from vault.models import VaultSlot
@@ -107,24 +107,7 @@ class ServerEventHandler(BaseHandler):
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':
WebQueue.broadcast(Event('player-death', player=evt['payload']['player'],
message=evt['payload']['message']))
if evt['type'] == 'server-heartbeat':
cache.set('caminus-server-heartbeat-%s'%(request.server.id), evt['payload'], 86400)
WebQueue.broadcast(Event('web-heartbeat', server=request.server.id,
heartbeat=evt['payload'],
day_period=request.server.day_period(),
time=request.server.current_time().second))
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:
Dispatcher.broadcastMessage("The bounty on %s has been collected."%(evt['payload']['player']))
Dispatcher.dispatch(Event.fromDict(evt)
return {'result': 'success'}
class ChatHandler(BaseHandler):

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,216 +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 EventTypeTest(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):
pass
events.EventQueue.flushAll()
self.server = Server.objects.create(hostname='localhost', secret='secret')
def tearDown(self):
pass
events.EventQueue.flushAll()
self.server.delete()
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)
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):
queue = events.ServerQueue(self.server)
self.assertQueueLength(queue, 0)
events.Dispatcher.broadcast('test')
self.assertQueueLength(queue, 1)
class WebQueueTest(unittest.TestCase):
def setUp(self):
events.EventQueue.flushAll()
def tearDown(self):
events.EventQueue.flushAll()
def testBroadcast(self):
queue = events.WebQueue(1)
events.WebQueue.broadcast(events.Event('test'))
webEvents = queue.getEvents()
self.assertEqual(len(webEvents), 1)
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 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):
self.queue = events.EventQueue('test')
events.EventQueue.flushAll()
def tearDown(self):
events.EventQueue.flushAll()
def testSend(self):
self.queue.sendEvent(events.Event('test'))
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 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 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 EventTest(unittest.TestCase):
def testEqual(self):
e = events.Event('test')
e2 = events.Event('test')
self.assertEqual(e, e2)
def testNewEvent(self):
e = events.Event('test')
self.assertEqual(e._id, -1)
self.assertEqual(e._conn, None)
self.assertDictEqual(e._properties, {'data': None})
def testValidTypes(self):
e = events.Event('quit', player="TestPlayer")
self.assertEqual(e.player, "TestPlayer")
def testInvalidType(self):
e = events.Event('invalid')
with self.assertRaises(ValueError):
e = events.Event('invalid')
def testMissingArgs(self):
e = events.Event('quit')
with self.assertRaises(KeyError):
e = events.Event('quit')
def testExtraArgs(self):
e = events.Event('quit', player='TestPlayer', extra='extra')
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 EventMarshallTest(unittest.TestCase):
def testToDict(self):
self.assertDictEqual(events.Event('test')._toDict(), {
'_id': -1,
'_stamp': 0,
'_type': 'test',
'data': None,
})
def tearDown(self):
self.user.delete()
self.server.delete()
def testJSONIdentity(self):
evt = events.Event('test')
self.assertEqual(events.Event._fromJSON(evt._toJSON()), evt)
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 testDictIdentity(self):
evt = events.Event('test')
self.assertEqual(events.Event._fromDict(evt._toDict()), evt)
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())
def testEmptyType(self):
desc = {}
data = events.Event._marshallType({}, desc, events.Event._datatypes(), 'marshall-test')
self.assertEqual(len(data), 0)
def tearDown(self):
self.user.delete()
self.server.delete()
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 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 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 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')
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'}})
class MOTDTest(unittest.TestCase):
def setUp(self):
self.client = Client()
self.server = Server.objects.create(hostname="localhost", secret="")
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 tearDown(self):
self.server.delete()
def testMissingOptionalAttribute(self):
desc = {'data': events.OptionalEventProperty('string')}
data = events.Event._marshallType({}, desc, events.Event._datatypes(),
'marshall-test')
self.assertDictEqual(data, {'data': None})
def testUnregisteredUser(self):
response = json.loads(self.client.get('/api/motd/NewUser').content)
self.assertIsInstance(response['motd'], list)
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 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('server-info' in data)
self.assertTrue('user-info' in data)
self.assertEqual(len(data['user-info']), 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'])
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.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__())

View File

@@ -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
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.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))

View File

@@ -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'))

View File

@@ -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)

View File

@@ -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}

View File

@@ -178,5 +178,6 @@ import api.events
def kick_banned_user(sender, instance, created, **kwargs):
player = instance.player
if player.isBanned():
api.events.ServerQueue.broadcast(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)

View File

@@ -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})

View File

@@ -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

View File

@@ -96,7 +96,6 @@ TEMPLATE_LOADERS = (
)
MIDDLEWARE_CLASSES = (
'caminus.devtools.PrintExceptionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',

View File

@@ -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 Event, ServerQueue, send_web_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
@@ -86,7 +87,7 @@ def send_vault_update(sender, instance, created, *args, **kwargs):
]
evt = Event('vault-contents', player=instance.player.mc_username, items=slots)
ServerQueue.broadcast(evt)
send_web_event(evt)
WebQueue.broadcast(evt)
post_save.connect(send_vault_update, sender=VaultSlot, dispatch_uid='derp')
@@ -96,8 +97,9 @@ def ensure_initial_slots(sender, instance, created, *args, **kwargs):
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)
bulk.append(VaultSlot(player=instance, position=pos))
insert_many(bulk)
post_save.connect(ensure_initial_slots, sender=MinecraftProfile)
post_delete.connect(ensure_initial_slots, sender=VaultSlot, dispatch_uid='derp')