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
|
||||||
445
api/events.py
445
api/events.py
@@ -1,155 +1,358 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
import os
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
import time
|
import time
|
||||||
from minecraft.models import Server
|
from minecraft.models import Server
|
||||||
from json import loads, dumps, JSONEncoder
|
from json import loads, dumps
|
||||||
import beanstalkc
|
import beanstalkc
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
import yaml
|
||||||
|
import django.dispatch
|
||||||
|
|
||||||
class EventEncoder(JSONEncoder):
|
on_event = django.dispatch.Signal(providing_args=["event"])
|
||||||
def default(self, obj):
|
|
||||||
if isinstance(obj, Event):
|
|
||||||
return {'type': obj.type, 'payload': obj.data}
|
|
||||||
return super(EventEncoder, self).default(obj)
|
|
||||||
|
|
||||||
|
def cache_heartbeat(sender, event, *args, **kwargs):
|
||||||
|
#cache.set('caminus-server-heartbeat-%s'%(request.server.id), evt['payload'], 86400)
|
||||||
|
pass
|
||||||
|
|
||||||
|
on_event.connect(cache_heartbeat, sender=intern('heartbeat'))
|
||||||
|
|
||||||
|
class Dispatcher(object):
|
||||||
|
@classmethod
|
||||||
|
def routes(cls):
|
||||||
|
types = cache.get('caminus-event-routes')
|
||||||
|
types = None
|
||||||
|
if types is None:
|
||||||
|
typefile = os.path.sep.join(
|
||||||
|
__file__.split(os.path.sep)[0:-1]+
|
||||||
|
['event-types.yml',]
|
||||||
|
)
|
||||||
|
yaml.add_constructor(r'!optional', OptionalEventProperty)
|
||||||
|
types = yaml.load(open(typefile, 'r'))['routes']
|
||||||
|
cache.set('caminus-event-routes', types, 3600)
|
||||||
|
return types
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def dispatch(cls, event):
|
||||||
|
routes = cls.routes()
|
||||||
|
if event._type in routes:
|
||||||
|
for route in routes[event._type]:
|
||||||
|
if route == "api":
|
||||||
|
on_event.send_robust(sender=intern(event._type), event=event)
|
||||||
|
elif route == "web":
|
||||||
|
WebQueue.route(event)
|
||||||
|
elif route == "server":
|
||||||
|
ServerQueue.route(event)
|
||||||
|
elif route == "market":
|
||||||
|
MarketQueue.route(event)
|
||||||
|
else:
|
||||||
|
raise KeyError, "Unknown route %s"%route
|
||||||
|
else:
|
||||||
|
raise KeyError, "Unable to find route for %s event"%(event._type)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def broadcast(cls, msg, *args):
|
||||||
|
cls.dispatch(Event('broadcast', message=msg%args))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def user_message(cls, user, message, args):
|
||||||
|
player = user.minecraftprofile.mc_username
|
||||||
|
cls.player_message(player, message, *args)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def player_message(cls, playername, message, *args):
|
||||||
|
message = message % args
|
||||||
|
cls.dispatch(Event('player-message', player=playername,
|
||||||
|
message=message))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def chat(cls, playername, message, *args):
|
||||||
|
cls.dispatch(Event('chat', player=playername, message=message%args))
|
||||||
|
|
||||||
class Event(object):
|
class Event(object):
|
||||||
def __init__(self, type, data):
|
def __init__(self, type=None, *args, **kwargs):
|
||||||
self.type = type
|
self._id = -1
|
||||||
self.data = data
|
self._stamp = 0
|
||||||
|
self._conn = None
|
||||||
|
self._properties = {}
|
||||||
|
types = self._types()
|
||||||
|
if type in types:
|
||||||
|
self._type = type
|
||||||
|
self._properties = self._marshallType(kwargs, types[type], self._datatypes(), type)
|
||||||
|
else:
|
||||||
|
raise ValueError, "Unknown event type '%s'"%(type)
|
||||||
|
|
||||||
class ChatEvent(Event):
|
def _delete(self):
|
||||||
def __init__(self, sender, message):
|
self._conn.delete(self._id)
|
||||||
super(ChatEvent, self).__init__(type='chat', data={'sender': sender,
|
|
||||||
'message': message})
|
|
||||||
|
|
||||||
class VaultContentsEvent(Event):
|
def _bury(self, priority=0):
|
||||||
def __init__(self, player, items):
|
self._conn.bury(self._id, 0)
|
||||||
super(VaultContentsEvent, self).__init__(type='vault-contents',
|
|
||||||
data={'player': player, 'items': items})
|
|
||||||
|
|
||||||
class QuitEvent(Event):
|
def __getattribute__(self, name):
|
||||||
def __init__(self, player):
|
if name.startswith('_'):
|
||||||
super(QuitEvent, self).__init__(type='quit', data={'player': player})
|
return super(Event, self).__getattribute__(name)
|
||||||
|
return self._properties[name]
|
||||||
|
|
||||||
class JoinEvent(Event):
|
def __setattr__(self, name, value):
|
||||||
def __init__(self, player):
|
if name.startswith('_'):
|
||||||
super(JoinEvent, self).__init__(type='join', data={'player': player})
|
return super(Event, self).__setattr__(name, value)
|
||||||
|
if name in self._properties:
|
||||||
|
raise NotImplementedError, "Event properties are not modifyable"
|
||||||
|
|
||||||
class BroadcastEvent(Event):
|
@classmethod
|
||||||
def __init__(self, message):
|
def _marshallType(cls, data, typeDescription, dataTypes, paramPath=""):
|
||||||
super(BroadcastEvent, self).__init__(type='broadcast', data={'message':
|
assert(isinstance(dataTypes, dict))
|
||||||
message})
|
if data is None:
|
||||||
|
return data
|
||||||
|
if isinstance(typeDescription, OptionalEventProperty):
|
||||||
|
return cls._marshallType(data, str(typeDescription), dataTypes, paramPath)
|
||||||
|
try:
|
||||||
|
if typeDescription == "string":
|
||||||
|
return str(data)
|
||||||
|
if typeDescription == "int":
|
||||||
|
return int(data)
|
||||||
|
if typeDescription == "dict":
|
||||||
|
return dict(data)
|
||||||
|
if typeDescription == "list":
|
||||||
|
return list(data)
|
||||||
|
except TypeError, e:
|
||||||
|
raise ValueError, "Could not convert '%s' to %s type for %s"%(data,
|
||||||
|
typeDescription, paramPath)
|
||||||
|
if isinstance(typeDescription, dict):
|
||||||
|
ret = {}
|
||||||
|
for key, elementType in typeDescription.iteritems():
|
||||||
|
key = key.replace('-', '_')
|
||||||
|
if key in data:
|
||||||
|
if key.startswith("_"):
|
||||||
|
raise ValueError, "Parameter '%s.%s' cannot start with '_'"%(key, paramPath)
|
||||||
|
ret[key] = cls._marshallType(data[key], elementType, dataTypes,
|
||||||
|
"%s.%s"%(paramPath, key))
|
||||||
|
elif not isinstance(elementType, OptionalEventProperty):
|
||||||
|
raise KeyError, "Missing parameter '%s.%s'"%(paramPath, key)
|
||||||
|
else:
|
||||||
|
ret[key] = None
|
||||||
|
return ret
|
||||||
|
if isinstance(typeDescription, list):
|
||||||
|
ret = []
|
||||||
|
i = 0
|
||||||
|
for item in data:
|
||||||
|
ret.append(cls._marshallType(item, typeDescription[0], dataTypes, "%s[%d]"%(paramPath, i)))
|
||||||
|
i += 1
|
||||||
|
return ret
|
||||||
|
if typeDescription in dataTypes:
|
||||||
|
return cls._marshallType(data, dataTypes[typeDescription], dataTypes, paramPath)
|
||||||
|
raise TypeError, "Unknown event data type '%s.%s'"%(paramPath, typeDescription)
|
||||||
|
|
||||||
class PlayerDeathEvent(Event):
|
@classmethod
|
||||||
def __init__(self, player, message):
|
def _types(cls):
|
||||||
super(PlayerDeathEvent, self).__init__(type='player-death',
|
types = cache.get('caminus-event-types')
|
||||||
data={'player': player, 'message': message})
|
types = None
|
||||||
|
if types is None:
|
||||||
|
typefile = os.path.sep.join(
|
||||||
|
__file__.split(os.path.sep)[0:-1]+
|
||||||
|
['event-types.yml',]
|
||||||
|
)
|
||||||
|
yaml.add_constructor(r'!optional', OptionalEventProperty)
|
||||||
|
types = yaml.load(open(typefile, 'r'))['events']
|
||||||
|
cache.set('caminus-event-types', types, 3600)
|
||||||
|
return types
|
||||||
|
|
||||||
class ServerHeartbeatEvent(Event):
|
@classmethod
|
||||||
def __init__(self, server, data):
|
def _datatypes(cls):
|
||||||
now = server.current_time()
|
typefile = os.path.sep.join(
|
||||||
t = now.second+now.minute*60+now.hour*60*60
|
__file__.split(os.path.sep)[0:-1]+
|
||||||
super(ServerHeartbeatEvent, self).__init__(type='server-heartbeat',
|
['event-types.yml',]
|
||||||
data={'server': server.id, 'heartbeat': data, 'time': t,
|
)
|
||||||
'day-period': server.day_period()})
|
yaml.add_constructor(r'!optional', OptionalEventProperty)
|
||||||
|
types = yaml.load(open(typefile, 'r'))['types']
|
||||||
|
return types
|
||||||
|
|
||||||
class KickEvent(Event):
|
def __repr__(self):
|
||||||
def __init__(self, user, message):
|
return "Event(%r)"%(self._properties)
|
||||||
super(KickEvent, self).__init__(type='player-kick',
|
|
||||||
data={'player': user, 'message': message})
|
|
||||||
|
|
||||||
class PlayerMessageEvent(Event):
|
def __eq__(self, other):
|
||||||
def __init__(self, user, message):
|
return self._properties == other._properties and self._id == other._id
|
||||||
super(PlayerMessageEvent, self).__init__(type='player-message',
|
|
||||||
data={'message': message, 'player': user})
|
|
||||||
|
|
||||||
class MarketOrderEvent(Event):
|
@classmethod
|
||||||
def __init__(self, orderID):
|
def _fromBeanstalkJob(cls, job):
|
||||||
super(MarketOrderEvent, self).__init__(type='market-order',
|
assert(isinstance(job, beanstalkc.Job))
|
||||||
data={'orderID': orderID})
|
id = job.jid
|
||||||
|
evt = cls._fromJSON(job.body)
|
||||||
|
evt._id = int(id)
|
||||||
|
evt._conn = job.conn
|
||||||
|
return evt
|
||||||
|
|
||||||
def server_queue(server, users=[]):
|
def _toDict(self):
|
||||||
queueName = 'caminus-broadcast-%s'%server.id
|
ret = {
|
||||||
queue = beanstalkc.Connection(host=settings.CAMINUS_BEANSTALKD_HOST,
|
'_id': self._id,
|
||||||
|
'_type': self._type,
|
||||||
|
'_stamp': self._stamp,
|
||||||
|
}
|
||||||
|
ret.update(self._properties)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _fromDict(cls, data):
|
||||||
|
assert(isinstance(data, dict))
|
||||||
|
type = data['_type']
|
||||||
|
properties = {}
|
||||||
|
for prop in data:
|
||||||
|
if prop.startswith('_'):
|
||||||
|
continue
|
||||||
|
properties[prop] = data[prop]
|
||||||
|
evt = Event(type, **properties)
|
||||||
|
if '_id' in data:
|
||||||
|
evt._id = int(data['_id'])
|
||||||
|
return evt
|
||||||
|
|
||||||
|
def _toJSON(self):
|
||||||
|
return dumps(self._toDict())
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _fromJSON(cls, json):
|
||||||
|
assert(isinstance(json, str))
|
||||||
|
return cls._fromDict(loads(json))
|
||||||
|
|
||||||
|
class OptionalEventProperty(str):
|
||||||
|
def __new__(cls, loader=None, node=None):
|
||||||
|
if node is None:
|
||||||
|
# Used with OptionalEventProperty("some-prop-name"), such as with pickling
|
||||||
|
return str.__new__(cls, loader)
|
||||||
|
# Used with yaml loader
|
||||||
|
return str.__new__(cls, loader.construct_scalar(node))
|
||||||
|
|
||||||
|
class EventQueue(object):
|
||||||
|
def __init__(self, name):
|
||||||
|
self.name = name
|
||||||
|
self.queue = beanstalkc.Connection(host=settings.CAMINUS_BEANSTALKD_HOST,
|
||||||
port=settings.CAMINUS_BEANSTALKD_PORT)
|
port=settings.CAMINUS_BEANSTALKD_PORT)
|
||||||
queue.use(queueName)
|
self.queue.use(self.name)
|
||||||
queue.watch(queueName)
|
self.queue.watch(self.name)
|
||||||
if len(users) > 0:
|
|
||||||
for user in users:
|
|
||||||
queue.watch("caminus-user-%s"%user)
|
|
||||||
return queue
|
|
||||||
|
|
||||||
def send_server_event(server, event):
|
@classmethod
|
||||||
if settings.CAMINUS_USE_BEANSTALKD:
|
def route(self, evt):
|
||||||
queue = server_queue(server)
|
raise NotImplementedError
|
||||||
json = dumps(event, cls=EventEncoder)
|
|
||||||
queue.put(json)
|
|
||||||
|
|
||||||
def broadcast_server_event(event):
|
def sendEvent(self, evt):
|
||||||
|
assert(isinstance(evt, Event))
|
||||||
|
id = self.queue.put(evt._toJSON())
|
||||||
|
|
||||||
|
def getEvents(self, timeout=30):
|
||||||
|
ret = []
|
||||||
|
job = self.queue.reserve(timeout)
|
||||||
|
while job:
|
||||||
|
ret.append(job)
|
||||||
|
job = self.queue.reserve(timeout=0)
|
||||||
|
return map(lambda x: Event._fromBeanstalkJob(x), ret)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def flushAll(cls):
|
||||||
|
for q in cls.allQueues():
|
||||||
|
q.flush()
|
||||||
|
|
||||||
|
def flush(self):
|
||||||
|
job = self.queue.peek_ready()
|
||||||
|
while job:
|
||||||
|
job.delete()
|
||||||
|
job = self.queue.peek_ready()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def connection():
|
||||||
|
return beanstalkc.Connection(host=settings.CAMINUS_BEANSTALKD_HOST,
|
||||||
|
port=settings.CAMINUS_BEANSTALKD_PORT)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def allQueues(cls):
|
||||||
|
c = cls.connection()
|
||||||
|
ret = []
|
||||||
|
for c in c.tubes():
|
||||||
|
ret.append(EventQueue(c))
|
||||||
|
return ret
|
||||||
|
|
||||||
|
class MarketQueue(EventQueue):
|
||||||
|
def __init__(self):
|
||||||
|
super(MarketQueue, self).__init__('caminus-market')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def route(cls, evt):
|
||||||
|
cls().sendEvent(evt)
|
||||||
|
|
||||||
|
class ServerQueue(EventQueue):
|
||||||
|
def __init__(self, server):
|
||||||
|
super(ServerQueue, self).__init__('caminus-broadcast-%s'%server.id)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def route(cls, evt):
|
||||||
|
cls.broadcast(evt)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def broadcast(cls, evt):
|
||||||
for server in Server.objects.all():
|
for server in Server.objects.all():
|
||||||
send_server_event(server, event)
|
queue = ServerQueue(server)
|
||||||
|
queue.sendEvent(evt)
|
||||||
|
|
||||||
def server_broadcast(message, *args):
|
class WebQueue(EventQueue):
|
||||||
message = message%args
|
def __init__(self, id):
|
||||||
for server in Server.objects.all():
|
self.id = id
|
||||||
event = BroadcastEvent(message)
|
super(WebQueue, self).__init__('caminus-web-%s'%(id))
|
||||||
send_server_event(server, event)
|
if id != "0":
|
||||||
send_web_event(event)
|
activeCache = cache.get('minecraft-web-queues')
|
||||||
|
if activeCache is None:
|
||||||
|
activeCache = {}
|
||||||
|
activeCache[id] = time.time()
|
||||||
|
expired = []
|
||||||
|
for queueName, stamp in activeCache.iteritems():
|
||||||
|
if time.time()-stamp > 120:
|
||||||
|
expired.append(queueName)
|
||||||
|
for e in expired:
|
||||||
|
del activeCache[e]
|
||||||
|
cache.set('minecraft-web-queues', activeCache, 3600)
|
||||||
|
for e in expired:
|
||||||
|
q = WebQueue(e)
|
||||||
|
q.flush()
|
||||||
|
|
||||||
def user_message(user, message, *args):
|
@classmethod
|
||||||
player = user.minecraftprofile.mc_username
|
def route(cls, event):
|
||||||
player_message(player, message, *args)
|
cls.broadcast(event)
|
||||||
|
|
||||||
def player_message(playername, message, *args):
|
@classmethod
|
||||||
message = message%args
|
def broadcast(cls, event):
|
||||||
for server in Server.objects.all():
|
|
||||||
event = PlayerMessageEvent(playername, message)
|
|
||||||
send_server_event(server, event)
|
|
||||||
|
|
||||||
def web_queue(id):
|
|
||||||
queueName = 'caminus-web-%s'%id
|
|
||||||
queue = beanstalkc.Connection(host=settings.CAMINUS_BEANSTALKD_HOST,
|
|
||||||
port = settings.CAMINUS_BEANSTALKD_PORT)
|
|
||||||
queue.use(queueName)
|
|
||||||
queue.watch(queueName)
|
|
||||||
return queue
|
|
||||||
|
|
||||||
def market_queue():
|
|
||||||
queueName = 'caminus-market'
|
|
||||||
queue = beanstalkc.Connection(host=settings.CAMINUS_BEANSTALKD_HOST,
|
|
||||||
port = settings.CAMINUS_BEANSTALKD_PORT)
|
|
||||||
queue.use(queueName)
|
|
||||||
queue.watch(queueName)
|
|
||||||
return queue
|
|
||||||
|
|
||||||
def queue_market_event(event):
|
|
||||||
queue = market_queue()
|
|
||||||
json = dumps({'stamp': time.time(), 'event': event}, cls=EventEncoder)
|
|
||||||
queue.put(json)
|
|
||||||
|
|
||||||
def send_web_event(event):
|
|
||||||
latest = cache.get('minecraft-web-events')
|
latest = cache.get('minecraft-web-events')
|
||||||
if latest is None:
|
if latest is None:
|
||||||
latest = []
|
latest = []
|
||||||
latest.append(dumps(event, cls=EventEncoder))
|
latest.append(dumps(event._toJSON()))
|
||||||
while len(latest) > 10:
|
while len(latest) > 10:
|
||||||
latest.pop(0)
|
latest.pop(0)
|
||||||
cache.set('minecraft-web-events', latest, 86400);
|
cache.set('minecraft-web-events', latest, 86400)
|
||||||
if settings.CAMINUS_USE_BEANSTALKD:
|
|
||||||
queue = beanstalkc.Connection(host=settings.CAMINUS_BEANSTALKD_HOST,
|
|
||||||
port = settings.CAMINUS_BEANSTALKD_PORT)
|
|
||||||
json = dumps({'stamp': time.time(), 'event':event}, cls=EventEncoder)
|
|
||||||
for tube in queue.tubes():
|
|
||||||
if tube.startswith("caminus-web-"):
|
|
||||||
queue.use(tube)
|
|
||||||
pendingJob = queue.peek_ready()
|
|
||||||
if pendingJob:
|
|
||||||
pending = loads(pendingJob.body)
|
|
||||||
if time.time()-pending['stamp'] > 30:
|
|
||||||
pendingJob.delete()
|
|
||||||
queue.put(json)
|
|
||||||
|
|
||||||
def chat(playername, message):
|
for queue in cls.activeQueues():
|
||||||
evt = ChatEvent(playername, message)
|
queue.sendEvent(event)
|
||||||
send_web_event(evt)
|
|
||||||
|
@staticmethod
|
||||||
|
def activeQueues():
|
||||||
|
activeCache = cache.get('minecraft-web-queues')
|
||||||
|
if activeCache is None:
|
||||||
|
activeCache = {}
|
||||||
|
ret = []
|
||||||
|
for name in activeCache.iterkeys():
|
||||||
|
ret.append(WebQueue(name))
|
||||||
|
return ret
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def clearActiveQueues():
|
||||||
|
cache.set('minecraft-web-queues', None)
|
||||||
|
|
||||||
|
def getEvents(self, timeout=30):
|
||||||
|
if self.id == "0":
|
||||||
|
latestEvents = cache.get('minecraft-web-events')
|
||||||
|
if latestEvents is None:
|
||||||
|
latestEvents = []
|
||||||
|
ret = []
|
||||||
|
for e in latestEvents:
|
||||||
|
ret.append(loads(e))
|
||||||
|
return ret
|
||||||
|
else:
|
||||||
|
ret = super(WebQueue, self).getEvents(timeout)
|
||||||
|
for evt in ret:
|
||||||
|
evt._delete()
|
||||||
|
return ret
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ from django.conf import settings
|
|||||||
import appversion
|
import appversion
|
||||||
from minecraft.models import MinecraftProfile
|
from minecraft.models import MinecraftProfile
|
||||||
from local.models import Quote
|
from local.models import Quote
|
||||||
from minecraft.models import MOTD, Server, PlayerSession
|
from minecraft.models import MOTD, Server, PlayerSession, Item
|
||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from urllib2 import urlopen
|
from urllib2 import urlopen
|
||||||
import json
|
import json
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from models import cachePlayerList
|
from models import cachePlayerList
|
||||||
from events import server_queue, web_queue, chat, server_broadcast, send_web_event, QuitEvent, JoinEvent, PlayerDeathEvent, ServerHeartbeatEvent
|
from events import ServerQueue, WebQueue, Event, Dispatcher
|
||||||
from bounty.models import Bounty
|
from bounty.models import Bounty
|
||||||
from vault.models import VaultSlot
|
from vault.models import VaultSlot
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ class NewPlayerSessionHandler(BaseHandler):
|
|||||||
server = request.server
|
server = request.server
|
||||||
profile = MinecraftProfile.objects.get(mc_username__exact=playername)
|
profile = MinecraftProfile.objects.get(mc_username__exact=playername)
|
||||||
session = PlayerSession.objects.create(server=server, player=profile, ip=ip)
|
session = PlayerSession.objects.create(server=server, player=profile, ip=ip)
|
||||||
send_web_event(JoinEvent(playername))
|
WebQueue.broadcast(Event('join', player=playername))
|
||||||
return {'success': True, 'error': '', 'permissions': profile.serverPermissions(), 'sessionId': session.id}
|
return {'success': True, 'error': '', 'permissions': profile.serverPermissions(), 'sessionId': session.id}
|
||||||
else:
|
else:
|
||||||
return {'success': False, 'error': 'Your account is inactive.', 'permissions': []}
|
return {'success': False, 'error': 'Your account is inactive.', 'permissions': []}
|
||||||
@@ -57,7 +57,7 @@ class ClosePlayerSessionHandler(BaseHandler):
|
|||||||
for session in sessions:
|
for session in sessions:
|
||||||
session.end = datetime.now()
|
session.end = datetime.now()
|
||||||
session.save()
|
session.save()
|
||||||
send_web_event(QuitEvent(playername))
|
WebQueue.broadcast(Event('quit', player=playername))
|
||||||
return {'valid': True}
|
return {'valid': True}
|
||||||
|
|
||||||
class EconomyHandler(BaseHandler):
|
class EconomyHandler(BaseHandler):
|
||||||
@@ -88,20 +88,18 @@ class ServerEventHandler(BaseHandler):
|
|||||||
allowed_methods = ('GET', 'POST', 'PUT')
|
allowed_methods = ('GET', 'POST', 'PUT')
|
||||||
|
|
||||||
def read(self, request):
|
def read(self, request):
|
||||||
queue = server_queue(request.server)
|
queue = ServerQueue(request.server)
|
||||||
queue.watch('caminus-broadcast-%s'%request.server.id)
|
|
||||||
events = []
|
events = []
|
||||||
job = queue.reserve(timeout=30)
|
for e in queue.getEvents():
|
||||||
while job:
|
events.append(json.loads(e.body)['event'])
|
||||||
job.bury()
|
e.bury()
|
||||||
events.append({'id': job.jid, 'event': json.loads(job.body)})
|
print {'events': events}
|
||||||
job = queue.reserve(timeout=0)
|
|
||||||
return {'events': events, 'is-live': settings.CAMINUS_USE_BEANSTALKD}
|
return {'events': events, 'is-live': settings.CAMINUS_USE_BEANSTALKD}
|
||||||
|
|
||||||
def create(self, request):
|
def create(self, request):
|
||||||
queue = server_queue(request.server)
|
queue = ServerQueue(request.server)
|
||||||
try:
|
try:
|
||||||
queue.delete(int(request.POST['job']))
|
queue.deleteJob(int(request.POST['job']))
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
pass
|
pass
|
||||||
return {'result': 'success'}
|
return {'result': 'success'}
|
||||||
@@ -109,21 +107,7 @@ class ServerEventHandler(BaseHandler):
|
|||||||
def update(self, request):
|
def update(self, request):
|
||||||
events = json.loads(request.POST['events'])['events']
|
events = json.loads(request.POST['events'])['events']
|
||||||
for evt in events:
|
for evt in events:
|
||||||
if evt['type'] == 'chat':
|
Dispatcher.dispatch(Event.fromDict(evt)
|
||||||
chat(evt['payload']['sender'], evt['payload']['message'])
|
|
||||||
if evt['type'] == 'player-death':
|
|
||||||
send_web_event(PlayerDeathEvent(evt['payload']['player'],
|
|
||||||
evt['payload']['message']))
|
|
||||||
if evt['type'] == 'heartbeat':
|
|
||||||
cache.set('caminus-server-heartbeat-%s'%(request.server.id), evt['payload'], 86400)
|
|
||||||
send_web_event(ServerHeartbeatEvent(request.server, evt['payload']))
|
|
||||||
if evt['type'] == 'player-murder':
|
|
||||||
bounties = Bounty.objects.filter(target__mc_username=evt['payload']['player'])
|
|
||||||
killer = MinecraftProfile.objects.get(mc_username=evt['payload']['killer'])
|
|
||||||
for bounty in bounties:
|
|
||||||
bounty.close(killer)
|
|
||||||
if len(bounties) > 0:
|
|
||||||
server_broadcast("The bounty on %s has been collected."%(evt['payload']['player']))
|
|
||||||
return {'result': 'success'}
|
return {'result': 'success'}
|
||||||
|
|
||||||
class ChatHandler(BaseHandler):
|
class ChatHandler(BaseHandler):
|
||||||
@@ -131,8 +115,8 @@ class ChatHandler(BaseHandler):
|
|||||||
|
|
||||||
def create(self, request):
|
def create(self, request):
|
||||||
chat(request.user.minecraftprofile.mc_username, request.POST['message'])
|
chat(request.user.minecraftprofile.mc_username, request.POST['message'])
|
||||||
server_broadcast("<%s> %s"%(request.user.minecraftprofile.mc_username,
|
ServerQueue.broadcast(BroadcastEvent("<%s> %s",
|
||||||
request.POST['message']))
|
request.user.minecraftprofile.mc_username, request.POST['message']))
|
||||||
|
|
||||||
class PollHandler(BaseHandler):
|
class PollHandler(BaseHandler):
|
||||||
allowed_methods = ('GET',)
|
allowed_methods = ('GET',)
|
||||||
@@ -150,20 +134,8 @@ class PollHandler(BaseHandler):
|
|||||||
pollData['events'] = []
|
pollData['events'] = []
|
||||||
pollData['info'] = info
|
pollData['info'] = info
|
||||||
pollData['poll-id'] = timestamp
|
pollData['poll-id'] = timestamp
|
||||||
if timestamp == "0" and settings.CAMINUS_USE_BEANSTALKD:
|
eventQueue = WebQueue(timestamp)
|
||||||
pollData['poll-id'] = time.time()
|
pollData['events'] = eventQueue.getEvents()
|
||||||
latestEvents = cache.get('minecraft-web-events')
|
|
||||||
if not latestEvents:
|
|
||||||
latestEvents = []
|
|
||||||
for e in latestEvents:
|
|
||||||
pollData['events'].append(json.loads(e))
|
|
||||||
elif settings.CAMINUS_USE_BEANSTALKD:
|
|
||||||
eventQueue = web_queue(timestamp)
|
|
||||||
event = eventQueue.reserve(timeout=30)
|
|
||||||
if event:
|
|
||||||
eventData = json.loads(event.body)
|
|
||||||
pollData['events'].append(eventData['event'])
|
|
||||||
event.delete()
|
|
||||||
return pollData
|
return pollData
|
||||||
|
|
||||||
class VaultHandler(BaseHandler):
|
class VaultHandler(BaseHandler):
|
||||||
@@ -173,9 +145,17 @@ class VaultHandler(BaseHandler):
|
|||||||
player = MinecraftProfile.objects.get(mc_username__exact=playername)
|
player = MinecraftProfile.objects.get(mc_username__exact=playername)
|
||||||
items = []
|
items = []
|
||||||
for slot in player.vault_slots.all():
|
for slot in player.vault_slots.all():
|
||||||
items.append({'item': slot.item, 'quantity':
|
data = {}
|
||||||
slot.quantity, 'damage': slot.damage, 'data': slot.data,
|
data['quantity'] = slot.quantity
|
||||||
'position': slot.position})
|
data['position'] = slot.position
|
||||||
|
data['item'] = None
|
||||||
|
data['damage'] = 0
|
||||||
|
data['data'] = 0
|
||||||
|
if slot.item:
|
||||||
|
data['data'] = slot.item.data
|
||||||
|
data['item'] = slot.item.material
|
||||||
|
data['damage'] = slot.item.damage
|
||||||
|
items.append(data)
|
||||||
return {'items': items}
|
return {'items': items}
|
||||||
|
|
||||||
def update(self, request, playername):
|
def update(self, request, playername):
|
||||||
@@ -186,18 +166,13 @@ class VaultHandler(BaseHandler):
|
|||||||
updated = False
|
updated = False
|
||||||
slot,created = VaultSlot.objects.get_or_create(player=player,
|
slot,created = VaultSlot.objects.get_or_create(player=player,
|
||||||
position=stack['position'])
|
position=stack['position'])
|
||||||
if slot.item != stack['item']:
|
item = Item.get(stack['item'], stack['damage'], stack['data'])
|
||||||
slot.item = stack['item']
|
if slot.item != item:
|
||||||
|
slot.item = item
|
||||||
updated = True
|
updated = True
|
||||||
if slot.quantity != stack['quantity']:
|
if slot.quantity != stack['quantity']:
|
||||||
slot.quantity = stack['quantity']
|
slot.quantity = stack['quantity']
|
||||||
updated = True
|
updated = True
|
||||||
if slot.damage != stack['damage']:
|
|
||||||
slot.damage = stack['damage']
|
|
||||||
updated = True
|
|
||||||
if slot.data != stack['data']:
|
|
||||||
slot.data = stack['data']
|
|
||||||
updated = True
|
|
||||||
if updated:
|
if updated:
|
||||||
slot.save()
|
slot.save()
|
||||||
return {'success': True}
|
return {'success': True}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.core.cache import cache
|
||||||
from api import events
|
from api import events
|
||||||
from minecraft.models import Server
|
from minecraft.models import Server
|
||||||
|
|
||||||
@@ -8,17 +9,23 @@ class Command(BaseCommand):
|
|||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
servers = Server.objects.all()
|
servers = Server.objects.all()
|
||||||
for s in servers:
|
for s in servers:
|
||||||
queue = events.server_queue(s)
|
queue = events.ServerQueue(s)
|
||||||
stats = queue.stats()
|
stats = queue.queue.stats()
|
||||||
print s
|
print s
|
||||||
for k,v in stats.iteritems():
|
for k,v in stats.iteritems():
|
||||||
print "\t%s: %s"%(k, v)
|
print "\t%s: %s"%(k, v)
|
||||||
print "\tTubes:"
|
print "Tubes:"
|
||||||
for t in queue.tubes():
|
for t in queue.queue.tubes():
|
||||||
print "\t\t%s"%(t)
|
print "\t%s"%(t)
|
||||||
queue.use(t)
|
queue.queue.use(t)
|
||||||
next = queue.peek_ready()
|
next = queue.queue.peek_ready()
|
||||||
if next:
|
if next:
|
||||||
print "\t\t\tNext job: %s"%(next.body)
|
print "\t\tNext job: %s"%(next.body)
|
||||||
else:
|
else:
|
||||||
print "\t\t\tNo pending job."
|
print "\t\tNo pending job."
|
||||||
|
print "Web queues:"
|
||||||
|
webQueues = cache.get("minecraft-web-queues")
|
||||||
|
if webQueues is None:
|
||||||
|
webQueues = {}
|
||||||
|
for queue,stamp in webQueues.iteritems():
|
||||||
|
"\t%s: %d"%(queue, stamp)
|
||||||
|
|||||||
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):
|
def handle(self, *args, **options):
|
||||||
servers = Server.objects.all()
|
servers = Server.objects.all()
|
||||||
for s in servers:
|
for s in servers:
|
||||||
queue = events.server_queue(s)
|
queue = events.ServerQueue(s)
|
||||||
stats = queue.stats()
|
stats = queue.queue.stats()
|
||||||
for t in queue.tubes():
|
for t in queue.queue.tubes():
|
||||||
queue.use(t)
|
queue.queue.use(t)
|
||||||
job = queue.peek_ready()
|
job = queue.queue.peek_ready()
|
||||||
while job:
|
while job:
|
||||||
print "Deleting %s from %s: %s"%(job.jid, t, job.body)
|
print "Deleting %s from %s: %s"%(job.jid, t, job.body)
|
||||||
job.delete()
|
job.delete()
|
||||||
job = queue.peek_ready()
|
job = queue.queue.peek_ready()
|
||||||
|
|||||||
252
api/oldtests.py
Normal file
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'])
|
||||||
340
api/tests.py
340
api/tests.py
@@ -1,196 +1,216 @@
|
|||||||
from django.utils import unittest
|
from django.utils import unittest
|
||||||
import json
|
from django.core.cache import cache
|
||||||
from django.test.client import Client
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from minecraft.models import MinecraftProfile, Server, PlayerSession, MOTD, Ban
|
from minecraft.models import MinecraftProfile, Server, PlayerSession, MOTD, Ban
|
||||||
from local.models import Quote
|
|
||||||
import hashlib
|
|
||||||
import events
|
import events
|
||||||
from django.conf import settings
|
import json
|
||||||
|
|
||||||
class ServerPingTest(unittest.TestCase):
|
class QueueAssertMixin(object):
|
||||||
|
def assertQueueLength(self, queue, count):
|
||||||
|
self._queue_events = queue.getEvents(0)
|
||||||
|
self.assertEqual(len(self._queue_events), count)
|
||||||
|
|
||||||
|
class EventTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.client = Client()
|
events.EventQueue.flushAll()
|
||||||
self.user = User.objects.create_user('ValidUsername', 'test@example.com')
|
|
||||||
self.user.minecraftprofile.mc_username = "ValidUsername"
|
|
||||||
self.user.minecraftprofile.save()
|
|
||||||
self.server = Server.objects.create(hostname='localhost', secret='secret')
|
self.server = Server.objects.create(hostname='localhost', secret='secret')
|
||||||
tokenHash = hashlib.sha1()
|
|
||||||
tokenHash.update("%s%s%s"%('localhost', 0, 'secret'))
|
|
||||||
self.token = "%s$%s$%s"%('localhost', 0, tokenHash.hexdigest())
|
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.user.delete()
|
events.EventQueue.flushAll()
|
||||||
self.server.delete()
|
self.server.delete()
|
||||||
|
|
||||||
def testPing(self):
|
class ServerEventTest(EventTest, QueueAssertMixin):
|
||||||
resp = self.client.get('/api/server/whoami',
|
def testHeartbeatCache(self):
|
||||||
HTTP_AUTHORIZATION='X-Caminus %s'%(self.token))
|
oldBeat = cache.get('caminus-server-heartbeat-%s'%(self.server.id))
|
||||||
self.assertEqual(resp.status_code, 200)
|
self.assertIsNone(oldBeat)
|
||||||
resp = json.loads(resp.content)
|
events.Dispatcher.dispatch(events.Event('server-heartbeat', port=self.server.port,
|
||||||
self.assertEqual(resp["api-version"], 2)
|
name=self.server.hostname, worldTimes={'world': 0}))
|
||||||
self.assertTrue("api-version" in resp)
|
newBeat = cache.get('caminus-server-heartbeat-%s'%(self.server.id))
|
||||||
|
self.assertIsNotNone(oldBeat)
|
||||||
|
|
||||||
if settings.CAMINUS_USE_BEANSTALKD:
|
class DispatcherTest(EventTest, QueueAssertMixin):
|
||||||
class ServerEventTest(unittest.TestCase):
|
def testDispatch(self):
|
||||||
def setUp(self):
|
queue = events.ServerQueue(self.server)
|
||||||
self.client = Client()
|
self.assertQueueLength(queue, 0)
|
||||||
self.user = User.objects.create_user('ValidUsername', 'test@example.com')
|
events.Dispatcher.dispatch(events.Event('test'))
|
||||||
self.user.minecraftprofile.mc_username = "ValidUsername"
|
self.assertQueueLength(queue, 1)
|
||||||
self.user.minecraftprofile.save()
|
|
||||||
self.server = Server.objects.create(hostname='localhost', secret='secret')
|
|
||||||
tokenHash = hashlib.sha1()
|
|
||||||
tokenHash.update("%s%s%s"%('localhost', 0, 'secret'))
|
|
||||||
self.token = "%s$%s$%s"%('localhost', 0, tokenHash.hexdigest())
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
self.user.delete()
|
|
||||||
self.server.delete()
|
|
||||||
|
|
||||||
def testBroadcast(self):
|
def testBroadcast(self):
|
||||||
events.server_broadcast("Test message")
|
queue = events.ServerQueue(self.server)
|
||||||
response = json.loads(self.client.get('/api/server/events',
|
self.assertQueueLength(queue, 0)
|
||||||
HTTP_AUTHORIZATION='X-Caminus %s'%(self.token)).content)
|
events.Dispatcher.broadcast('test')
|
||||||
self.assertTrue(len(response['events']) > 0)
|
self.assertQueueLength(queue, 1)
|
||||||
response = json.loads(self.client.post('/api/server/events', {'job':response['events'][0]['id']},
|
|
||||||
HTTP_AUTHORIZATION='X-Caminus %s'%(self.token)).content)
|
|
||||||
self.assertEqual(response['result'], 'success')
|
|
||||||
|
|
||||||
def testUserMessage(self):
|
class WebQueueTest(unittest.TestCase):
|
||||||
events.user_message(self.user, "Test user message")
|
|
||||||
response = json.loads(self.client.get('/api/server/events',
|
|
||||||
HTTP_AUTHORIZATION='X-Caminus %s'%(self.token)).content)
|
|
||||||
self.assertTrue(len(response['events']) > 0)
|
|
||||||
response = json.loads(self.client.post('/api/server/events', {'job':response['events'][0]['id']},
|
|
||||||
HTTP_AUTHORIZATION='X-Caminus %s'%(self.token)).content)
|
|
||||||
self.assertEqual(response['result'], 'success')
|
|
||||||
|
|
||||||
class MOTDTest(unittest.TestCase):
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.client = Client()
|
events.EventQueue.flushAll()
|
||||||
self.server = Server.objects.create(hostname="localhost", secret="")
|
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.server.delete()
|
events.EventQueue.flushAll()
|
||||||
|
|
||||||
def testUnregisteredUser(self):
|
def testBroadcast(self):
|
||||||
response = json.loads(self.client.get('/api/motd/NewUser').content)
|
queue = events.WebQueue(1)
|
||||||
self.assertIsInstance(response['motd'], list)
|
events.WebQueue.broadcast(events.Event('test'))
|
||||||
|
webEvents = queue.getEvents()
|
||||||
|
self.assertEqual(len(webEvents), 1)
|
||||||
|
|
||||||
def testRandomMOTD(self):
|
def testHugeBroadcast(self):
|
||||||
m = MOTD.objects.create(server=self.server, text="testMOTD")
|
queues = []
|
||||||
q = Quote.objects.create(text="testQUOTE")
|
for i in range(0, 30):
|
||||||
response = json.loads(self.client.get('/api/motd/NewUser').content)
|
queues.append(events.WebQueue(i))
|
||||||
m.delete()
|
events.WebQueue.broadcast(events.Event('test'))
|
||||||
q.delete()
|
for queue in queues:
|
||||||
foundMOTD = False
|
eventList = queue.getEvents()
|
||||||
foundQuote = False
|
self.assertEqual(len(eventList), 1)
|
||||||
for line in response['motd']:
|
|
||||||
if line == "testMOTD":
|
|
||||||
foundMOTD = True
|
|
||||||
if line == '"testQUOTE"':
|
|
||||||
foundQuote = True
|
|
||||||
self.assertTrue(foundMOTD)
|
|
||||||
self.assertTrue(foundQuote)
|
|
||||||
|
|
||||||
class SessionTest(unittest.TestCase):
|
def testActiveQueues(self):
|
||||||
|
events.WebQueue.clearActiveQueues()
|
||||||
|
self.assertEqual(len(events.WebQueue.activeQueues()), 0)
|
||||||
|
queues = []
|
||||||
|
for i in range(0, 30):
|
||||||
|
queues.append(events.WebQueue(i))
|
||||||
|
self.assertEqual(len(events.WebQueue.activeQueues()), len(queues))
|
||||||
|
|
||||||
|
def testSingleUseEvent(self):
|
||||||
|
queue = events.WebQueue(1)
|
||||||
|
queue.sendEvent(events.Event('test'))
|
||||||
|
self.assertEqual(len(queue.getEvents(0)), 1)
|
||||||
|
self.assertEqual(len(queue.getEvents(0)), 0)
|
||||||
|
|
||||||
|
class EventQueueTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.client = Client()
|
self.queue = events.EventQueue('test')
|
||||||
self.user = User.objects.create_user('ValidUsername', 'test@example.com')
|
events.EventQueue.flushAll()
|
||||||
self.user.minecraftprofile.mc_username = "ValidUsername"
|
|
||||||
self.user.minecraftprofile.save()
|
|
||||||
self.server = Server.objects.create(hostname='localhost', secret='secret')
|
|
||||||
tokenHash = hashlib.sha1()
|
|
||||||
tokenHash.update("%s%s%s"%('localhost', 0, 'secret'))
|
|
||||||
self.token = "%s$%s$%s"%('localhost', 0, tokenHash.hexdigest())
|
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.user.delete()
|
events.EventQueue.flushAll()
|
||||||
self.server.delete()
|
|
||||||
|
|
||||||
def testBannedStart(self):
|
def testSend(self):
|
||||||
b = Ban.objects.create(player=self.user.minecraftprofile, banner=self.user, reason="test")
|
self.queue.sendEvent(events.Event('test'))
|
||||||
resp = self.client.post('/api/server/session/%s/new'%(self.user.minecraftprofile.mc_username), {'ip': '127.0.0.1'}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
|
|
||||||
b.delete()
|
|
||||||
self.assertEqual(resp.status_code, 200)
|
|
||||||
session = json.loads(resp.content)
|
|
||||||
self.assertFalse(session['success'])
|
|
||||||
|
|
||||||
def testBadUserStart(self):
|
def testReceive(self):
|
||||||
resp = self.client.post('/api/server/session/SomeUnknownUser/new', {'ip': '127.0.0.1'}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
|
sentEvent = events.Event('test')
|
||||||
self.assertEqual(resp.status_code, 200)
|
self.queue.sendEvent(sentEvent)
|
||||||
session = json.loads(resp.content)
|
eventList = self.queue.getEvents()
|
||||||
self.assertFalse(session['success'])
|
self.assertEqual(len(eventList), 1)
|
||||||
|
self.assertEqual(type(eventList[0]), events.Event)
|
||||||
|
self.assertNotEqual(eventList[0]._id, sentEvent._id)
|
||||||
|
self.assertEqual(eventList[0]._type, sentEvent._type)
|
||||||
|
self.assertNotEqual(eventList[0]._conn, sentEvent._conn)
|
||||||
|
|
||||||
def testInactiveStart(self):
|
def testFlush(self):
|
||||||
self.user.is_active = False
|
evt = events.Event('test')
|
||||||
self.user.save()
|
for i in range(0, 300):
|
||||||
resp = self.client.post('/api/server/session/%s/new'%(self.user.minecraftprofile.mc_username), {'ip': '127.0.0.1'}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
|
self.queue.sendEvent(evt)
|
||||||
self.user.is_active = True
|
self.assertEqual(len(self.queue.getEvents(0)), 300)
|
||||||
self.user.save()
|
self.queue.flush()
|
||||||
self.assertEqual(resp.status_code, 200)
|
self.assertEqual(len(self.queue.getEvents(0)), 0)
|
||||||
session = json.loads(resp.content)
|
|
||||||
self.assertFalse(session['success'])
|
|
||||||
|
|
||||||
def testSessionStart(self):
|
def testFlushAll(self):
|
||||||
resp = self.client.post('/api/server/session/%s/new'%(self.user.minecraftprofile.mc_username), {'ip': '127.0.0.1'}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
|
evt = events.Event('test')
|
||||||
self.assertEqual(resp.status_code, 200)
|
queues = []
|
||||||
session = json.loads(resp.content)
|
for i in range(0, 10):
|
||||||
self.assertTrue(session['success'])
|
queues.append(events.EventQueue('test-%d'%(i)))
|
||||||
|
for i in range(0, 10):
|
||||||
|
for queue in queues:
|
||||||
|
queue.sendEvent(evt)
|
||||||
|
for queue in queues:
|
||||||
|
self.assertEqual(len(queue.getEvents(0)), 10)
|
||||||
|
events.EventQueue.flushAll()
|
||||||
|
for queue in queues:
|
||||||
|
self.assertEqual(len(queue.getEvents(0)), 0)
|
||||||
|
|
||||||
def testSessionEnd(self):
|
|
||||||
resp = self.client.post('/api/server/session/%s/new'%(self.user.minecraftprofile.mc_username), {'ip': '127.0.0.1'}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
|
|
||||||
session = json.loads(resp.content)
|
|
||||||
resp = self.client.get('/api/server/session/%s/close'%(self.user.minecraftprofile.mc_username), HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
|
|
||||||
self.assertEqual(resp.status_code, 200)
|
|
||||||
|
|
||||||
class PollTest(unittest.TestCase):
|
class EventTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def testEqual(self):
|
||||||
self.client = Client()
|
e = events.Event('test')
|
||||||
|
e2 = events.Event('test')
|
||||||
|
self.assertEqual(e, e2)
|
||||||
|
|
||||||
def testAnonymousPoll(self):
|
def testNewEvent(self):
|
||||||
resp = self.client.get('/api/poll/0')
|
e = events.Event('test')
|
||||||
self.assertEqual(resp.status_code, 200)
|
self.assertEqual(e._id, -1)
|
||||||
data = json.loads(resp.content)
|
self.assertEqual(e._conn, None)
|
||||||
self.assertTrue('server-info' in data)
|
self.assertDictEqual(e._properties, {'data': None})
|
||||||
self.assertTrue('user-info' in data)
|
|
||||||
self.assertEqual(len(data['user-info']), 0)
|
|
||||||
|
|
||||||
class EconomyTest(unittest.TestCase):
|
def testValidTypes(self):
|
||||||
def setUp(self):
|
e = events.Event('quit', player="TestPlayer")
|
||||||
self.client = Client()
|
self.assertEqual(e.player, "TestPlayer")
|
||||||
self.user = User.objects.create_user('ValidUsername', 'test@example.com')
|
|
||||||
self.user.minecraftprofile.mc_username = "ValidUsername"
|
|
||||||
self.user.minecraftprofile.save()
|
|
||||||
self.user.minecraftprofile.currencyaccount.balance=42
|
|
||||||
self.user.minecraftprofile.currencyaccount.save()
|
|
||||||
self.server = Server.objects.create(hostname='localhost', secret='secret')
|
|
||||||
tokenHash = hashlib.sha1()
|
|
||||||
tokenHash.update("%s%s%s"%('localhost', 0, 'secret'))
|
|
||||||
self.token = "%s$%s$%s"%('localhost', 0, tokenHash.hexdigest())
|
|
||||||
|
|
||||||
def tearDown(self):
|
def testInvalidType(self):
|
||||||
self.user.delete()
|
with self.assertRaises(ValueError):
|
||||||
self.server.delete()
|
e = events.Event('invalid')
|
||||||
|
|
||||||
def testBalanceQuery(self):
|
def testMissingArgs(self):
|
||||||
resp = self.client.get('/api/server/economy/ValidUsername', HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
|
with self.assertRaises(KeyError):
|
||||||
data = json.loads(resp.content)
|
e = events.Event('quit')
|
||||||
self.assertEqual(data['balance'], 42)
|
|
||||||
|
|
||||||
def testDeposit(self):
|
def testExtraArgs(self):
|
||||||
resp = self.client.put('/api/server/economy/ValidUsername', {'delta': 100}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
|
e = events.Event('quit', player='TestPlayer', extra='extra')
|
||||||
data = json.loads(resp.content)
|
|
||||||
self.assertEqual(data['balance'], 142)
|
|
||||||
self.assertTrue(data['success'])
|
|
||||||
|
|
||||||
def testWithdraw(self):
|
class EventMarshallTest(unittest.TestCase):
|
||||||
resp = self.client.put('/api/server/economy/ValidUsername', {'delta': -40}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
|
def testToDict(self):
|
||||||
data = json.loads(resp.content)
|
self.assertDictEqual(events.Event('test')._toDict(), {
|
||||||
self.assertEqual(data['balance'], 2)
|
'_id': -1,
|
||||||
self.assertTrue(data['success'])
|
'_stamp': 0,
|
||||||
|
'_type': 'test',
|
||||||
|
'data': None,
|
||||||
|
})
|
||||||
|
|
||||||
def testOverdraw(self):
|
def testJSONIdentity(self):
|
||||||
resp = self.client.put('/api/server/economy/ValidUsername', {'delta': -400}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
|
evt = events.Event('test')
|
||||||
data = json.loads(resp.content)
|
self.assertEqual(events.Event._fromJSON(evt._toJSON()), evt)
|
||||||
self.assertFalse(data['success'])
|
|
||||||
|
def testDictIdentity(self):
|
||||||
|
evt = events.Event('test')
|
||||||
|
self.assertEqual(events.Event._fromDict(evt._toDict()), evt)
|
||||||
|
|
||||||
|
def testEmptyType(self):
|
||||||
|
desc = {}
|
||||||
|
data = events.Event._marshallType({}, desc, events.Event._datatypes(), 'marshall-test')
|
||||||
|
self.assertEqual(len(data), 0)
|
||||||
|
|
||||||
|
def testSimpleType(self):
|
||||||
|
desc = {'data': 'string'}
|
||||||
|
data = events.Event._marshallType({'data': 'value'}, desc,
|
||||||
|
events.Event._datatypes(), 'marshall-test')
|
||||||
|
self.assertEqual(len(data), 1)
|
||||||
|
self.assertTrue(isinstance(data['data'], str))
|
||||||
|
self.assertEqual(data['data'], 'value')
|
||||||
|
|
||||||
|
def testTypeMismatch(self):
|
||||||
|
desc = {'data': 'int'}
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
events.Event._marshallType({'data': 'this is not an integer'}, desc,
|
||||||
|
events.Event._datatypes(), 'marshall-test')
|
||||||
|
|
||||||
|
def testComplexType(self):
|
||||||
|
desc = {'data': 'complex-type'}
|
||||||
|
typeDesc = {'complex-type': {'value': 'string'}}
|
||||||
|
data = events.Event._marshallType({'data': {'value': 'test'}}, desc, typeDesc, 'marshall-test')
|
||||||
|
self.assertDictEqual(data, {'data': {'value': 'test'}})
|
||||||
|
|
||||||
|
def testComplexTypeMismatch(self):
|
||||||
|
desc = {'data': 'complex-type'}
|
||||||
|
typeDesc = {'complex-type': {'value': 'string'}}
|
||||||
|
with self.assertRaises(KeyError):
|
||||||
|
events.Event._marshallType({'data': 'this is just a string'}, desc,
|
||||||
|
typeDesc, 'marshall-test')
|
||||||
|
|
||||||
|
def testMissingOptionalAttribute(self):
|
||||||
|
desc = {'data': events.OptionalEventProperty('string')}
|
||||||
|
data = events.Event._marshallType({}, desc, events.Event._datatypes(),
|
||||||
|
'marshall-test')
|
||||||
|
self.assertDictEqual(data, {'data': None})
|
||||||
|
|
||||||
|
def testOptionalAttribute(self):
|
||||||
|
desc = {'data': events.OptionalEventProperty('string')}
|
||||||
|
data = events.Event._marshallType({'data': 'value'}, desc,
|
||||||
|
events.Event._datatypes(),
|
||||||
|
'marshall-test')
|
||||||
|
self.assertDictEqual(data, {'data': 'value'})
|
||||||
|
|
||||||
|
def testMissingOptionalComplextType(self):
|
||||||
|
desc = {'data': events.OptionalEventProperty('complex-type')}
|
||||||
|
typeDesc = {'complex-type': {'value': 'string'}}
|
||||||
|
data = events.Event._marshallType({}, desc, typeDesc, 'marshall-test')
|
||||||
|
self.assertDictEqual(data, {'data': None})
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
import badges.api
|
import badges.api
|
||||||
from caminus.api.events import user_message
|
from caminus.api.events import Dispatcher
|
||||||
from notification import models as notification
|
from notification import models as notification
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ class Award(models.Model):
|
|||||||
super(Award, self).save(*args, **kwargs)
|
super(Award, self).save(*args, **kwargs)
|
||||||
badges.api.badge_awarded.send_robust(sender=intern(str(self.badge.slug)), award=self)
|
badges.api.badge_awarded.send_robust(sender=intern(str(self.badge.slug)), award=self)
|
||||||
notification.send([self.user], "badge_awarded", {"award": self, 'notice_description': self.badge, 'notice_url': reverse('user_profile')})
|
notification.send([self.user], "badge_awarded", {"award": self, 'notice_description': self.badge, 'notice_url': reverse('user_profile')})
|
||||||
user_message(self.user, "You have been awarded the '%s' badge."%self.badge)
|
Dispatcher.user_message(self.user, "You have been awarded the '%s' badge."%self.badge)
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return "%s for %s"%(self.badge.__unicode__(), self.user.__unicode__())
|
return "%s for %s"%(self.badge.__unicode__(), self.user.__unicode__())
|
||||||
|
|||||||
@@ -18,3 +18,13 @@ class Bounty(models.Model):
|
|||||||
self.save()
|
self.save()
|
||||||
killer.currencyaccount.balance = F('balance') + self.price
|
killer.currencyaccount.balance = F('balance') + self.price
|
||||||
killer.currencyaccount.save()
|
killer.currencyaccount.save()
|
||||||
|
|
||||||
|
def handle_murder(sender, event, *args, **kwargs):
|
||||||
|
bounties = Bounty.objects.filter(target__mc_username=evt['payload']['player'])
|
||||||
|
killer = MinecraftProfile.objects.get(mc_username=evt['payload']['killer'])
|
||||||
|
for bounty in bounties:
|
||||||
|
bounty.close(killer)
|
||||||
|
if len(bounties) > 0:
|
||||||
|
Dispatcher.broadcast("The bounty on %s has been collected."%(evt['payload']['player']))
|
||||||
|
|
||||||
|
events.on_event.connect(handle_murder, sender=intern('player-murder'))
|
||||||
|
|||||||
83
bulkops.py
Normal file
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.urlresolvers import reverse
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from notification import models as notification
|
from notification import models as notification
|
||||||
from api.events import server_broadcast, user_message
|
from api.events import Dispatcher
|
||||||
|
|
||||||
def index(request):
|
def index(request):
|
||||||
forums = models.Forum.objects.filter(parent=None)
|
forums = models.Forum.objects.filter(parent=None)
|
||||||
@@ -45,7 +45,7 @@ def reply(request, topicID=None):
|
|||||||
if reply.parent.user != request.user:
|
if reply.parent.user != request.user:
|
||||||
notification.send([reply.parent.user], "forum_reply", {"reply": reply, 'notice_url': reverse('forums.views.post', kwargs={'id':reply.id}), 'notice_description': reply.topic().title})
|
notification.send([reply.parent.user], "forum_reply", {"reply": reply, 'notice_url': reverse('forums.views.post', kwargs={'id':reply.id}), 'notice_description': reply.topic().title})
|
||||||
messages.info(request, "Reply successful")
|
messages.info(request, "Reply successful")
|
||||||
user_message(reply.parent.user, "%s replied to your post in '%s'",
|
Dispatcher.user_message(reply.parent.user, "%s replied to your post in '%s'",
|
||||||
request.user, reply.topic().title)
|
request.user, reply.topic().title)
|
||||||
return HttpResponseRedirect(reverse('forums.views.post', kwargs={"id":reply.id}))
|
return HttpResponseRedirect(reverse('forums.views.post', kwargs={"id":reply.id}))
|
||||||
return render_to_response('forums/reply.html', {"parent":parentPost, "form":form}, context_instance = RequestContext(request))
|
return render_to_response('forums/reply.html', {"parent":parentPost, "form":form}, context_instance = RequestContext(request))
|
||||||
@@ -84,7 +84,7 @@ def newTopic(request, forumID=None):
|
|||||||
topic.rootPost = reply
|
topic.rootPost = reply
|
||||||
topic.save()
|
topic.save()
|
||||||
messages.info(request, "Posting successful")
|
messages.info(request, "Posting successful")
|
||||||
server_broadcast("New forum topic: %s", topic.title)
|
Dispatcher.broadcast("New forum topic: %s", topic.title)
|
||||||
return HttpResponseRedirect(reverse('forums.views.post', kwargs={'id': reply.id}))
|
return HttpResponseRedirect(reverse('forums.views.post', kwargs={'id': reply.id}))
|
||||||
return render_to_response('forums/newTopic.html', {"forum":parentForum, "replyForm":replyForm, "topicForm": topicForm}, context_instance = RequestContext(request))
|
return render_to_response('forums/newTopic.html', {"forum":parentForum, "replyForm":replyForm, "topicForm": topicForm}, context_instance = RequestContext(request))
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from django.contrib.auth import authenticate, login
|
|||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
import forms
|
import forms
|
||||||
import models
|
import models
|
||||||
from api.events import user_message
|
from api.events import Dispatcher
|
||||||
from forums.models import Forum
|
from forums.models import Forum
|
||||||
from minecraft.forms import ProfileForm
|
from minecraft.forms import ProfileForm
|
||||||
from minecraft.models import MinecraftProfile
|
from minecraft.models import MinecraftProfile
|
||||||
@@ -80,7 +80,7 @@ def register(request):
|
|||||||
profile.save()
|
profile.save()
|
||||||
user = authenticate(username=userForm.cleaned_data['username'], password=userForm.cleaned_data['password'])
|
user = authenticate(username=userForm.cleaned_data['username'], password=userForm.cleaned_data['password'])
|
||||||
notification.send_now([invite.creator], "invite_accepted", {"new_user": user})
|
notification.send_now([invite.creator], "invite_accepted", {"new_user": user})
|
||||||
user_message(invite.creator, "%s has accepted your invite."%(user.username))
|
Dispatcher.user_message(invite.creator, "%s has accepted your invite."%(user.username))
|
||||||
login(request, user)
|
login(request, user)
|
||||||
del request.session['profile-invite']
|
del request.session['profile-invite']
|
||||||
return HttpResponseRedirect(reverse('welcome'))
|
return HttpResponseRedirect(reverse('welcome'))
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from django.core.management.base import NoArgsCommand
|
from django.core.management.base import NoArgsCommand
|
||||||
from market.models import MarketOrder
|
from market.models import MarketOrder
|
||||||
import json
|
import json
|
||||||
from api.events import market_queue
|
from api.events import MarketQueue
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
class Command(NoArgsCommand):
|
class Command(NoArgsCommand):
|
||||||
@@ -11,9 +11,9 @@ class Command(NoArgsCommand):
|
|||||||
print "Processing currently open orders"
|
print "Processing currently open orders"
|
||||||
for order in MarketOrder.objects.filter(close_stamp__isnull=True):
|
for order in MarketOrder.objects.filter(close_stamp__isnull=True):
|
||||||
order.process()
|
order.process()
|
||||||
queue = market_queue()
|
queue = MarketQueue()
|
||||||
while True:
|
while True:
|
||||||
job = queue.reserve()
|
for job in queue.getEvents():
|
||||||
jobInfo = json.loads(job.body)
|
jobInfo = json.loads(job.body)
|
||||||
if jobInfo['event']['type'] == "market-order":
|
if jobInfo['event']['type'] == "market-order":
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from django.db import transaction
|
|||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from django.db.models.signals import post_save
|
from django.db.models.signals import post_save
|
||||||
from api.events import queue_market_event, MarketOrderEvent
|
from api.events import MarketQueue, Event
|
||||||
from vault.models import VaultSlot
|
from vault.models import VaultSlot
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from minecraft.items import ITEMS
|
from minecraft.items import ITEMS
|
||||||
@@ -68,8 +68,9 @@ class MarketOrder(models.Model):
|
|||||||
|
|
||||||
def enqueue_process(self):
|
def enqueue_process(self):
|
||||||
if settings.CAMINUS_USE_BEANSTALKD:
|
if settings.CAMINUS_USE_BEANSTALKD:
|
||||||
evt = MarketOrderEvent(self.id)
|
evt = Event('market-order', orderID=self.id)
|
||||||
queue_market_event(evt)
|
queue = MarketQueue()
|
||||||
|
queue.sendEvent(evt)
|
||||||
else:
|
else:
|
||||||
self.process()
|
self.process()
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,3 @@ Replace this with more appropriate tests for your application.
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
|
|
||||||
class SimpleTest(TestCase):
|
|
||||||
def test_basic_addition(self):
|
|
||||||
"""
|
|
||||||
Tests that 1 + 1 always equals 2.
|
|
||||||
"""
|
|
||||||
self.assertEqual(1 + 1, 2)
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import models
|
import models
|
||||||
|
|
||||||
def server_info(request):
|
def server_info(request):
|
||||||
|
try:
|
||||||
period = models.Server.objects.all()[0].day_period()
|
period = models.Server.objects.all()[0].day_period()
|
||||||
|
except IndexError, e:
|
||||||
|
period = "daytime"
|
||||||
return {'minecraft_servers': models.Server.objects.all(),
|
return {'minecraft_servers': models.Server.objects.all(),
|
||||||
'first_day_period': period}
|
'first_day_period': period}
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ class Item(models.Model):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get(cls, material, damage=0, data=0):
|
def get(cls, material, damage=0, data=0):
|
||||||
|
if damage is None:
|
||||||
|
damage = 0
|
||||||
|
if data is None:
|
||||||
|
data = 0
|
||||||
return cls.objects.get_or_create(material=material, damage=damage,
|
return cls.objects.get_or_create(material=material, damage=damage,
|
||||||
data=data)[0]
|
data=data)[0]
|
||||||
|
|
||||||
@@ -174,5 +178,6 @@ import api.events
|
|||||||
def kick_banned_user(sender, instance, created, **kwargs):
|
def kick_banned_user(sender, instance, created, **kwargs):
|
||||||
player = instance.player
|
player = instance.player
|
||||||
if player.isBanned():
|
if player.isBanned():
|
||||||
api.events.broadcast_server_event(api.events.KickEvent(player.mc_username, instance.reason))
|
api.events.ServerQueue.broadcast(api.events.Event('player-kick',
|
||||||
|
player=player.mc_username, message=instance.reason))
|
||||||
post_save.connect(kick_banned_user, sender=Ban)
|
post_save.connect(kick_banned_user, sender=Ban)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from django.core.urlresolvers import reverse
|
|||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from notification import models as notification
|
from notification import models as notification
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from api.events import user_message
|
from api.events import Dispatcher
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def create(request):
|
def create(request):
|
||||||
@@ -24,7 +24,7 @@ def create(request):
|
|||||||
adminUsers = User.objects.filter(is_staff=True)
|
adminUsers = User.objects.filter(is_staff=True)
|
||||||
notification.send(adminUsers, "petition_opened", {"petition": petition, 'notice_url': reverse('petition.views.view', kwargs={'id':petition.id}),'notice_description': petition.id})
|
notification.send(adminUsers, "petition_opened", {"petition": petition, 'notice_url': reverse('petition.views.view', kwargs={'id':petition.id}),'notice_description': petition.id})
|
||||||
for user in adminUsers:
|
for user in adminUsers:
|
||||||
user_message(user, "%s has opened a petition."%(request.user))
|
Dispatcher.user_message(user, "%s has opened a petition."%(request.user))
|
||||||
messages.info(request, "Petition created.")
|
messages.info(request, "Petition created.")
|
||||||
return HttpResponseRedirect(reverse('petition.views.view', kwargs={"id":petition.id}))
|
return HttpResponseRedirect(reverse('petition.views.view', kwargs={"id":petition.id}))
|
||||||
return render_to_response('petition/create.html', {'form':form}, context_instance = RequestContext(request))
|
return render_to_response('petition/create.html', {'form':form}, context_instance = RequestContext(request))
|
||||||
@@ -59,7 +59,7 @@ def comment(request, id):
|
|||||||
comment.save()
|
comment.save()
|
||||||
adminUsers = User.objects.filter(is_staff=True)
|
adminUsers = User.objects.filter(is_staff=True)
|
||||||
for user in adminUsers:
|
for user in adminUsers:
|
||||||
user_message(user, "%s has opened a petition."%(request.user))
|
Dispatcher.user_message(user, "%s has opened a petition."%(request.user))
|
||||||
notification.send(adminUsers, "petition_commented", {"petition": petition, 'notice_url': reverse('petition.views.view', kwargs={'id':petition.id}),'notice_description': petition.id, 'comment': comment})
|
notification.send(adminUsers, "petition_commented", {"petition": petition, 'notice_url': reverse('petition.views.view', kwargs={'id':petition.id}),'notice_description': petition.id, 'comment': comment})
|
||||||
if comment.author != petition.author:
|
if comment.author != petition.author:
|
||||||
notification.send([petition.author], "petition_commented", {"petition": petition, 'notice_url': reverse('petition.views.view', kwargs={'id':petition.id}),'notice_description': petition.id, 'comment': comment})
|
notification.send([petition.author], "petition_commented", {"petition": petition, 'notice_url': reverse('petition.views.view', kwargs={'id':petition.id}),'notice_description': petition.id, 'comment': comment})
|
||||||
|
|||||||
@@ -11,3 +11,4 @@ pydot==1.0.28
|
|||||||
beanstalkc
|
beanstalkc
|
||||||
stripe
|
stripe
|
||||||
django-devserver
|
django-devserver
|
||||||
|
PyYAML==3.10
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ if [ ! -f virtualenv/bin/activate ];then
|
|||||||
fi
|
fi
|
||||||
source virtualenv/bin/activate
|
source virtualenv/bin/activate
|
||||||
pip install -r pip-requirements
|
pip install -r pip-requirements
|
||||||
|
coverage erase
|
||||||
coverage run ./manage.py test $@
|
coverage run ./manage.py test $@
|
||||||
ret=$?
|
ret=$?
|
||||||
if [ $ret -eq 0 ];then
|
if [ $ret -eq 0 ];then
|
||||||
coverage report -m --include=\* --omit=\*/migrations/\*,settings.py,local_settings.py,manage.py
|
coverage report -m --include=\* --omit=\*/migrations/\*,settings.py,local_settings.py,manage.py,bulkops.py
|
||||||
fi
|
fi
|
||||||
exit $ret
|
exit $ret
|
||||||
|
|||||||
@@ -104,7 +104,6 @@ MIDDLEWARE_CLASSES = (
|
|||||||
'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware',
|
'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware',
|
||||||
'django.middleware.http.ConditionalGetMiddleware',
|
'django.middleware.http.ConditionalGetMiddleware',
|
||||||
'django.middleware.gzip.GZipMiddleware',
|
'django.middleware.gzip.GZipMiddleware',
|
||||||
'caminus.devtools.PrintExceptionMiddleware',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
ROOT_URLCONF = 'caminus.urls'
|
ROOT_URLCONF = 'caminus.urls'
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ div[class|="inventory-item"] .quantity {
|
|||||||
padding: 3px;
|
padding: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div[class|="inventory-item"] .damage.empty{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
div[class|="inventory-item"] .damage {
|
div[class|="inventory-item"] .damage {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ EventPoller.prototype.start = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
EventPoller.prototype.dispatchEvent = function(evt) {
|
EventPoller.prototype.dispatchEvent = function(evt) {
|
||||||
console.log(evt['type']);
|
|
||||||
if (evt['type'] in this.handlers) {
|
if (evt['type'] in this.handlers) {
|
||||||
this.handlers[evt['type']].forEach(function (callback, idx, handlers) {
|
this.handlers[evt['type']].forEach(function (callback, idx, handlers) {
|
||||||
callback(evt, evt['payload']);
|
callback(evt, evt['payload']);
|
||||||
@@ -50,6 +49,7 @@ EventPoller.prototype._failedPoll = function(data) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.id = 0;
|
this.id = 0;
|
||||||
|
window.setTimeout(this.poll.bind(this), 5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
EventPoller.prototype._successfulPoll = function(data) {
|
EventPoller.prototype._successfulPoll = function(data) {
|
||||||
@@ -78,6 +78,7 @@ EventPoller.prototype._successfulPoll = function(data) {
|
|||||||
|
|
||||||
var that = this;
|
var that = this;
|
||||||
$(data['events']).each(function(idx, evt) {
|
$(data['events']).each(function(idx, evt) {
|
||||||
|
console.log(evt['type']);
|
||||||
that.dispatchEvent(evt);
|
that.dispatchEvent(evt);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
78
static/js/vault.js
Normal file
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}}">
|
<div class="inventory-item-{{item.material}}" data-material="{{item.material}}"
|
||||||
{% if quantity > 0 %}
|
data-quantity="{{quantity}}" data-damage="{{item.damage}}">
|
||||||
<div class="quantity">{{quantity}}</div>
|
<div class="quantity">
|
||||||
|
{% if quantity > 0 %}{{quantity}}
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="name">{{item.name}}</div>
|
<div class="name">{{item.name}}</div>
|
||||||
{% if item.damage %}
|
<div class="damage {% if not item.damage %}empty{%endif%}">
|
||||||
<div class="damage">
|
|
||||||
<div class="current"
|
<div class="current"
|
||||||
style="width:{{item.damagePct}}%"></div>
|
style="width:{{item.damagePct}}%"></div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
{% extends "base_simple.html" %}
|
{% extends "base_simple.html" %}
|
||||||
|
{%load static %}
|
||||||
|
{% get_static_prefix as STATIC_PREFIX %}
|
||||||
|
|
||||||
{% block title %}Vault{% endblock %}
|
{% block title %}Vault{% endblock %}
|
||||||
{% block sectiontitle %}Your Vault{% endblock %}
|
{% block sectiontitle %}Your Vault{% endblock %}
|
||||||
|
|
||||||
|
{% block extrahead %}
|
||||||
|
<script type="text/javascript" language="javascript" src="{{STATIC_PREFIX}}/js/vault.js"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<table class="inventory">
|
<table class="inventory">
|
||||||
<tr>
|
<tr>
|
||||||
@@ -11,10 +17,8 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<td>
|
<td data-id="{{slot.id}}" data-position="{{slot.position}}">
|
||||||
{% if slot.item %}
|
|
||||||
{% include 'common/item.html' with quantity=slot.quantity item=slot.item %}
|
{% include 'common/item.html' with quantity=slot.quantity item=slot.item %}
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from minecraft.models import MinecraftProfile
|
from minecraft.models import MinecraftProfile
|
||||||
from django.db.models.signals import post_save, post_delete
|
from django.db.models.signals import post_save, post_delete
|
||||||
from api.events import VaultContentsEvent, broadcast_server_event
|
from api.events import Event, ServerQueue, WebQueue
|
||||||
from minecraft.models import Item
|
from minecraft.models import Item
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from caminus.bulkops import insert_many
|
||||||
|
|
||||||
class VaultError(Exception):
|
class VaultError(Exception):
|
||||||
pass
|
pass
|
||||||
@@ -59,43 +60,46 @@ class VaultSlot(models.Model):
|
|||||||
raise VaultError, "Insufficient available slots."
|
raise VaultError, "Insufficient available slots."
|
||||||
return slots
|
return slots
|
||||||
|
|
||||||
def send_vault_delete(sender, instance, *args, **kwargs):
|
|
||||||
slots = [
|
|
||||||
{
|
|
||||||
'item': None,
|
|
||||||
'quantity': -1,
|
|
||||||
'damage': None,
|
|
||||||
'data': None,
|
|
||||||
'position': instance.position
|
|
||||||
}
|
|
||||||
]
|
|
||||||
broadcast_server_event(VaultContentsEvent(instance.player.mc_username,
|
|
||||||
slots))
|
|
||||||
|
|
||||||
def send_vault_update(sender, instance, created, *args, **kwargs):
|
def send_vault_update(sender, instance, created, *args, **kwargs):
|
||||||
if created and instance.item:
|
if instance.item:
|
||||||
slots = [
|
slots = [
|
||||||
{
|
{
|
||||||
'item': instance.item.material,
|
'material': instance.item.material,
|
||||||
'quantity': instance.quantity,
|
'quantity': instance.quantity,
|
||||||
'damage': instance.item.damage,
|
'damage': instance.item.damage,
|
||||||
'data': instance.item.data,
|
'data': instance.item.data,
|
||||||
'position': instance.position
|
'position': instance.position,
|
||||||
|
'name': instance.item.name(),
|
||||||
|
'durability': instance.item.metadata['durability']
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
broadcast_server_event(VaultContentsEvent(instance.player.mc_username, slots))
|
else:
|
||||||
|
slots = [
|
||||||
|
{
|
||||||
|
'material': None,
|
||||||
|
'quantity': None,
|
||||||
|
'damage': None,
|
||||||
|
'data': None,
|
||||||
|
'position': instance.position,
|
||||||
|
'name': None,
|
||||||
|
'durability': None
|
||||||
|
}
|
||||||
|
]
|
||||||
|
evt = Event('vault-contents', player=instance.player.mc_username, items=slots)
|
||||||
|
ServerQueue.broadcast(evt)
|
||||||
|
WebQueue.broadcast(evt)
|
||||||
|
|
||||||
post_save.connect(send_vault_update, sender=VaultSlot, dispatch_uid='derp')
|
post_save.connect(send_vault_update, sender=VaultSlot, dispatch_uid='derp')
|
||||||
post_delete.connect(send_vault_delete, sender=VaultSlot, dispatch_uid='derp')
|
|
||||||
|
|
||||||
def create_initial_slots(sender, instance, created, *args, **kwargs):
|
def ensure_initial_slots(sender, instance, created, *args, **kwargs):
|
||||||
slots = instance.vault_slots.all()
|
slots = instance.vault_slots.all()
|
||||||
if len(slots) < settings.CAMINUS_VAULT_SLOTS:
|
if len(slots) < settings.CAMINUS_VAULT_SLOTS:
|
||||||
neededPositions = range(0, settings.CAMINUS_VAULT_SLOTS)
|
neededPositions = range(0, settings.CAMINUS_VAULT_SLOTS)
|
||||||
for s in slots:
|
for s in slots:
|
||||||
neededPositions.remove(s.position)
|
neededPositions.remove(s.position)
|
||||||
|
bulk = []
|
||||||
for pos in neededPositions:
|
for pos in neededPositions:
|
||||||
VaultSlot.objects.create(player=instance, position=pos)
|
bulk.append(VaultSlot(player=instance, position=pos))
|
||||||
|
insert_many(bulk)
|
||||||
post_save.connect(create_initial_slots, sender=MinecraftProfile)
|
|
||||||
|
|
||||||
|
post_save.connect(ensure_initial_slots, sender=MinecraftProfile)
|
||||||
|
|||||||
Reference in New Issue
Block a user