Support public flag for StateEntry

This commit is contained in:
Ian Gulliver
2014-05-01 11:33:29 -07:00
parent 006eb03483
commit 64f989e3ca
5 changed files with 59 additions and 45 deletions

29
api.py
View File

@@ -51,6 +51,7 @@ class SetValue(webapp2.RequestHandler):
def post(self): def post(self):
entry_key = self.request.get('key') entry_key = self.request.get('key')
entry_value = self.request.get('value') entry_value = self.request.get('value')
public = (self.request.get('public') == 'true')
entries = (models.StateEntry.all() entries = (models.StateEntry.all()
.ancestor(self.client.parent_key()) .ancestor(self.client.parent_key())
@@ -59,11 +60,13 @@ class SetValue(webapp2.RequestHandler):
if entries: if entries:
entry = entries[0] entry = entries[0]
entry.entry_value = entry_value entry.entry_value = entry_value
entry.public = public
else: else:
entry = models.StateEntry( entry = models.StateEntry(
parent=self.client.parent_key(), parent=self.client.parent_key(),
entry_key=entry_key, entry_key=entry_key,
entry_value=entry_value) entry_value=entry_value,
public=public)
entry.put() entry.put()
msg = entry.ToMessage() msg = entry.ToMessage()
@@ -75,29 +78,6 @@ class SetValue(webapp2.RequestHandler):
return {} return {}
class GetValue(webapp2.RequestHandler):
@utils.chaos_monkey
@utils.returns_json
@utils.local_namespace
@security.google_user_xsrf_protection
@security.weak_security_checks
@session.session_required
@db.transactional()
def post(self):
entry_key = self.request.get('key')
entries = (models.StateEntry.all()
.ancestor(self.client.parent_key())
.filter('entry_key =', entry_key)
.fetch(1))
if entries:
return {
'value': entries[0].entry_value
}
return {}
class CreateChannel(webapp2.RequestHandler): class CreateChannel(webapp2.RequestHandler):
@utils.chaos_monkey @utils.chaos_monkey
@utils.returns_json @utils.returns_json
@@ -119,6 +99,5 @@ class CreateChannel(webapp2.RequestHandler):
app = webapp2.WSGIApplication([ app = webapp2.WSGIApplication([
(config.URL_PREFIX + '/api/createChannel', CreateChannel), (config.URL_PREFIX + '/api/createChannel', CreateChannel),
(config.URL_PREFIX + '/api/getUser', GetUser), (config.URL_PREFIX + '/api/getUser', GetUser),
(config.URL_PREFIX + '/api/getValue', GetValue),
(config.URL_PREFIX + '/api/setValue', SetValue), (config.URL_PREFIX + '/api/setValue', SetValue),
]) ])

View File

@@ -19,6 +19,8 @@ import logging
from google.appengine.api import channel from google.appengine.api import channel
from google.appengine.ext import db from google.appengine.ext import db
import utils
# Profile # Profile
# ↳ Client # ↳ Client
@@ -94,7 +96,7 @@ class Client(db.Model):
return cls.FromProfile(profile) return cls.FromProfile(profile)
def SendMessage(self, msg): def SendMessage(self, msg):
channel.send_message(str(self.key()), json.dumps(msg)) channel.send_message(str(self.key()), json.dumps(msg, default=utils.EncodeJSON))
class StateEntry(db.Model): class StateEntry(db.Model):
@@ -103,12 +105,15 @@ class StateEntry(db.Model):
last_set = db.DateTimeProperty(required=True, auto_now=True) last_set = db.DateTimeProperty(required=True, auto_now=True)
entry_key = db.StringProperty(required=True) entry_key = db.StringProperty(required=True)
entry_value = db.StringProperty() entry_value = db.StringProperty()
public = db.BooleanProperty(required=True, default=False)
def ToMessage(self): def ToMessage(self):
return { return {
'message_type': 'state', 'message_type': 'state',
'key': self.entry_key, 'key': self.entry_key,
'value': self.entry_value, 'value': self.entry_value,
'last_set': self.last_set,
'public': self.public,
} }

View File

@@ -12,9 +12,11 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import datetime
import functools import functools
import json import json
import random import random
import time
from google.appengine.api import namespace_manager from google.appengine.api import namespace_manager
@@ -27,7 +29,7 @@ def returns_json(handler):
@functools.wraps(handler) @functools.wraps(handler)
def SerializeResult(self): def SerializeResult(self):
json.dump(handler(self), self.response.out) json.dump(handler(self), self.response.out, default=EncodeJSON)
return SerializeResult return SerializeResult
@@ -54,3 +56,9 @@ def local_namespace(handler):
return handler(self) return handler(self)
return SetNamespace return SetNamespace
def EncodeJSON(o):
if isinstance(o, datetime.datetime):
return time.mktime(o.timetuple())
return json.JSONEncoder.default(o)

View File

@@ -144,10 +144,11 @@ cosmopolite.Client.prototype.getUser_ = function() {
}); });
}; };
cosmopolite.Client.prototype.setValue = function(key, value) { cosmopolite.Client.prototype.setValue = function(key, value, is_public) {
this.sendRPC_('setValue', { this.sendRPC_('setValue', {
'key': key, 'key': key,
'value': value, 'value': value,
'public': is_public,
}) })
// Provide immediate feedback without waiting for a round trip. // Provide immediate feedback without waiting for a round trip.
// We'll also get a response from the server, so this should be eventually // We'll also get a response from the server, so this should be eventually
@@ -200,14 +201,20 @@ cosmopolite.Client.prototype.onServerMessage_ = function(msg) {
switch (msg.message_type) { switch (msg.message_type) {
case 'state': case 'state':
var key = msg['key']; var key = msg['key'];
var value = msg['value']; if (this.stateCache_[key] &&
if (this.stateCache_[key] == value) { this.stateCache_[key]['value'] == msg['value'] &&
this.stateCache_[key]['last_set'] == msg['last_set'] &&
this.stateCache_[key]['public'] == msg['public']) {
// Duplicate message. // Duplicate message.
break; break;
} }
this.stateCache_[key] = value; this.stateCache_[key] = {
'value': msg['value'],
'last_set': msg['last_set'],
'public': msg['public'],
}
if ('onStateChange' in this.callbacks_) { if ('onStateChange' in this.callbacks_) {
this.callbacks_['onStateChange'](key, value); this.callbacks_['onStateChange'](key, this.stateCache_[key]);
} }
break; break;
default: default:

View File

@@ -11,23 +11,35 @@ a {
<div>Google user: <span id="google_user"></span></div> <div>Google user: <span id="google_user"></span></div>
<div> <div>
Key: <div>
<select id="keys"></select> Key:
<input type="button" id="set" value="Set"> <select id="keys"></select>
<input type="button" id="add" value="Add"> <input type="button" id="set" value="Set">
<br /> <input type="button" id="add" value="Add">
<textarea id="value" rows="10" cols="40"></textarea> </div>
<div>
Last set:
<span id="last_set"></span>
</div>
<div>
<input type="checkbox" id="public"> Public
</div>
<div>
<textarea id="value" rows="10" cols="40"></textarea>
</div>
</div> </div>
<script> <script>
var keys = document.getElementById('keys'); var keys = document.getElementById('keys');
var value = document.getElementById('value'); var value = document.getElementById('value');
var last_set = document.getElementById('last_set');
var is_public = document.getElementById('public');
var selectKey = function(new_key) { var selectKey = function(new_key) {
keys.value = new_key; keys.value = new_key;
keys.dispatchEvent(new CustomEvent('change')); keys.dispatchEvent(new CustomEvent('change'));
}; };
var addKey = function(new_key, new_value, index) { var addKey = function(new_key, index) {
var option = document.createElement('option'); var option = document.createElement('option');
option.text = new_key; option.text = new_key;
keys.options.add(option, index); keys.options.add(option, index);
@@ -55,19 +67,19 @@ window.addEventListener('load', function() {
return; return;
}; };
if (keys.options[i].value > new_key) { if (keys.options[i].value > new_key) {
addKey(new_key, new_value, i); addKey(new_key, i);
return; return;
}; };
}; };
// Sorts at the end // Sorts at the end
addKey(new_key, new_value, keys.options.length); addKey(new_key, keys.options.length);
}, },
}; };
var debug = new cosmopolite.Client(callbacks); var debug = new cosmopolite.Client(callbacks);
document.getElementById('set').addEventListener('click', function() { document.getElementById('set').addEventListener('click', function() {
debug.setValue(keys.value, value.value); debug.setValue(keys.value, value.value, is_public.checked);
}); });
document.getElementById('add').addEventListener('click', function() { document.getElementById('add').addEventListener('click', function() {
@@ -80,7 +92,10 @@ window.addEventListener('load', function() {
}); });
document.getElementById('keys').addEventListener('change', function() { document.getElementById('keys').addEventListener('change', function() {
value.value = debug.getValue(keys.value); var value_obj = debug.getValue(keys.value);
value.value = value_obj.value;
last_set.innerHTML = (new Date(value_obj.last_set * 1000)).toString();
is_public.checked = value_obj['public'];
}); });
}); });
</script> </script>