Remove the concept of keys; they can just be encoded into subject names.
This commit is contained in:
13
api.py
13
api.py
@@ -82,11 +82,10 @@ def SendMessage(google_user, client, instance_id, args):
|
|||||||
subject = args['subject']
|
subject = args['subject']
|
||||||
message = args['message']
|
message = args['message']
|
||||||
sender_message_id = args['sender_message_id']
|
sender_message_id = args['sender_message_id']
|
||||||
key = args.get('key', None)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
models.Subject.FindOrCreate(subject).SendMessage(
|
models.Subject.FindOrCreate(subject).SendMessage(
|
||||||
message, client.parent_key(), sender_message_id, key)
|
message, client.parent_key(), sender_message_id)
|
||||||
except models.DuplicateMessage:
|
except models.DuplicateMessage:
|
||||||
logging.exception('Duplicate message: %s', sender_message_id)
|
logging.exception('Duplicate message: %s', sender_message_id)
|
||||||
return {
|
return {
|
||||||
@@ -114,10 +113,9 @@ def Subscribe(google_user, client, instance_id, args):
|
|||||||
subject = models.Subject.FindOrCreate(args['subject'])
|
subject = models.Subject.FindOrCreate(args['subject'])
|
||||||
messages = args.get('messages', 0)
|
messages = args.get('messages', 0)
|
||||||
last_id = args.get('last_id', None)
|
last_id = args.get('last_id', None)
|
||||||
keys = args.get('keys', [])
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ret = {
|
return {
|
||||||
'result': 'ok',
|
'result': 'ok',
|
||||||
'events': models.Subscription.FindOrCreate(
|
'events': models.Subscription.FindOrCreate(
|
||||||
subject, client, instance, messages, last_id),
|
subject, client, instance, messages, last_id),
|
||||||
@@ -128,13 +126,6 @@ def Subscribe(google_user, client, instance_id, args):
|
|||||||
'result': 'access_denied',
|
'result': 'access_denied',
|
||||||
}
|
}
|
||||||
|
|
||||||
for key in keys:
|
|
||||||
message = subject.GetKey(key)
|
|
||||||
if message:
|
|
||||||
ret['events'].append(message.ToEvent())
|
|
||||||
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
def Unpin(google_user, client, instance_id, args):
|
def Unpin(google_user, client, instance_id, args):
|
||||||
instance = models.Instance.FromID(instance_id)
|
instance = models.Instance.FromID(instance_id)
|
||||||
|
|||||||
@@ -161,18 +161,6 @@ class Subject(db.Model):
|
|||||||
.order('id_'))
|
.order('id_'))
|
||||||
return list(query)
|
return list(query)
|
||||||
|
|
||||||
@db.transactional()
|
|
||||||
def GetKey(self, key):
|
|
||||||
messages = (
|
|
||||||
Message.all()
|
|
||||||
.ancestor(self)
|
|
||||||
.filter('key_ =', key)
|
|
||||||
.order('-id_')
|
|
||||||
.fetch(1))
|
|
||||||
if messages:
|
|
||||||
return messages[0]
|
|
||||||
return None
|
|
||||||
|
|
||||||
@db.transactional()
|
@db.transactional()
|
||||||
def GetPins(self):
|
def GetPins(self):
|
||||||
query = (
|
query = (
|
||||||
@@ -181,7 +169,7 @@ class Subject(db.Model):
|
|||||||
return list(query)
|
return list(query)
|
||||||
|
|
||||||
@db.transactional()
|
@db.transactional()
|
||||||
def PutMessage(self, message, sender, sender_message_id, key=None):
|
def PutMessage(self, message, sender, sender_message_id):
|
||||||
"""Internal helper for SendMessage().
|
"""Internal helper for SendMessage().
|
||||||
|
|
||||||
Unless/until channel.send_message becomes transactional, we have to finish
|
Unless/until channel.send_message becomes transactional, we have to finish
|
||||||
@@ -211,8 +199,7 @@ class Subject(db.Model):
|
|||||||
message=message,
|
message=message,
|
||||||
sender=sender,
|
sender=sender,
|
||||||
sender_message_id=sender_message_id,
|
sender_message_id=sender_message_id,
|
||||||
id_=message_id,
|
id_=message_id)
|
||||||
key_=key)
|
|
||||||
obj.put()
|
obj.put()
|
||||||
|
|
||||||
return (obj, list(Subscription.all().ancestor(subject)))
|
return (obj, list(Subscription.all().ancestor(subject)))
|
||||||
@@ -223,9 +210,9 @@ class Subject(db.Model):
|
|||||||
writable_only_by != sender):
|
writable_only_by != sender):
|
||||||
raise AccessDenied
|
raise AccessDenied
|
||||||
|
|
||||||
def SendMessage(self, message, sender, sender_message_id, key=None):
|
def SendMessage(self, message, sender, sender_message_id):
|
||||||
self.VerifyWritable(sender)
|
self.VerifyWritable(sender)
|
||||||
obj, subscriptions = self.PutMessage(message, sender, sender_message_id, key)
|
obj, subscriptions = self.PutMessage(message, sender, sender_message_id)
|
||||||
event = obj.ToEvent()
|
event = obj.ToEvent()
|
||||||
for subscription in subscriptions:
|
for subscription in subscriptions:
|
||||||
subscription.SendMessage(event)
|
subscription.SendMessage(event)
|
||||||
@@ -359,11 +346,9 @@ class Message(db.Model):
|
|||||||
sender_message_id = db.StringProperty(required=True)
|
sender_message_id = db.StringProperty(required=True)
|
||||||
# id is reserved
|
# id is reserved
|
||||||
id_ = db.IntegerProperty(required=True)
|
id_ = db.IntegerProperty(required=True)
|
||||||
# key and key_name are reserved
|
|
||||||
key_ = db.StringProperty()
|
|
||||||
|
|
||||||
def ToEvent(self):
|
def ToEvent(self):
|
||||||
ret = {
|
return {
|
||||||
'event_type': 'message',
|
'event_type': 'message',
|
||||||
'id': self.id_,
|
'id': self.id_,
|
||||||
'sender': str(Message.sender.get_value_for_datastore(self)),
|
'sender': str(Message.sender.get_value_for_datastore(self)),
|
||||||
@@ -371,9 +356,6 @@ class Message(db.Model):
|
|||||||
'created': self.created,
|
'created': self.created,
|
||||||
'message': self.message,
|
'message': self.message,
|
||||||
}
|
}
|
||||||
if self.key_:
|
|
||||||
ret['key'] = self.key_
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
class Pin(db.Model):
|
class Pin(db.Model):
|
||||||
|
|||||||
@@ -132,9 +132,8 @@ Cosmopolite.prototype.shutdown = function() {
|
|||||||
* @param {!*} subject Subject name or object
|
* @param {!*} subject Subject name or object
|
||||||
* @param {number=} messages Number of recent messages to request; 0 for none, -1 for all
|
* @param {number=} messages Number of recent messages to request; 0 for none, -1 for all
|
||||||
* @param {number=} last_id ID of last message received; fetch all messages since
|
* @param {number=} last_id ID of last message received; fetch all messages since
|
||||||
* @param {Array.<string>=} keys Key names to ensure we receive at least 1 message defining
|
|
||||||
*/
|
*/
|
||||||
Cosmopolite.prototype.subscribe = function(subject, messages, last_id, keys) {
|
Cosmopolite.prototype.subscribe = function(subject, messages, last_id) {
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
var canonicalSubject = this.canonicalSubject_(subject);
|
var canonicalSubject = this.canonicalSubject_(subject);
|
||||||
var subjectString = JSON.stringify(canonicalSubject);
|
var subjectString = JSON.stringify(canonicalSubject);
|
||||||
@@ -142,7 +141,6 @@ Cosmopolite.prototype.subscribe = function(subject, messages, last_id, keys) {
|
|||||||
this.subscriptions_[subjectString] = {
|
this.subscriptions_[subjectString] = {
|
||||||
'messages': [],
|
'messages': [],
|
||||||
'pins': [],
|
'pins': [],
|
||||||
'keys': {},
|
|
||||||
'state': this.SubscriptionState.PENDING,
|
'state': this.SubscriptionState.PENDING,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -156,9 +154,6 @@ Cosmopolite.prototype.subscribe = function(subject, messages, last_id, keys) {
|
|||||||
if (last_id != null) {
|
if (last_id != null) {
|
||||||
args['last_id'] = last_id;
|
args['last_id'] = last_id;
|
||||||
}
|
}
|
||||||
if (keys != null) {
|
|
||||||
args['keys'] = keys;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.sendRPC_('subscribe', args, function(response) {
|
this.sendRPC_('subscribe', args, function(response) {
|
||||||
// unsubscribe may have been called since we sent the RPC. That's racy
|
// unsubscribe may have been called since we sent the RPC. That's racy
|
||||||
@@ -201,18 +196,14 @@ Cosmopolite.prototype.unsubscribe = function(subject) {
|
|||||||
*
|
*
|
||||||
* @param {!string} subject Subject name
|
* @param {!string} subject Subject name
|
||||||
* @param {!*} message Message string or object
|
* @param {!*} message Message string or object
|
||||||
* @param {string=} key Key name to associate this message with
|
|
||||||
*/
|
*/
|
||||||
Cosmopolite.prototype.sendMessage = function(subject, message, key) {
|
Cosmopolite.prototype.sendMessage = function(subject, message) {
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
var args = {
|
var args = {
|
||||||
'subject': this.canonicalSubject_(subject),
|
'subject': this.canonicalSubject_(subject),
|
||||||
'message': JSON.stringify(message),
|
'message': JSON.stringify(message),
|
||||||
'sender_message_id': this.uuid_(),
|
'sender_message_id': this.uuid_(),
|
||||||
};
|
};
|
||||||
if (key) {
|
|
||||||
args['key'] = key;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No message left behind.
|
// No message left behind.
|
||||||
var messageQueue = JSON.parse(localStorage[this.messageQueueKey_]);
|
var messageQueue = JSON.parse(localStorage[this.messageQueueKey_]);
|
||||||
@@ -249,19 +240,6 @@ Cosmopolite.prototype.getPins = function(subject) {
|
|||||||
return this.subscriptions_[subjectString].pins;
|
return this.subscriptions_[subjectString].pins;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch the most recent message that defined a key
|
|
||||||
*
|
|
||||||
* @param {!string} subject Subject name
|
|
||||||
* @param {!string} key Key name
|
|
||||||
* @const
|
|
||||||
*/
|
|
||||||
Cosmopolite.prototype.getKeyMessage = function(subject, key) {
|
|
||||||
var canonicalSubject = this.canonicalSubject_(subject);
|
|
||||||
var subjectString = JSON.stringify(canonicalSubject);
|
|
||||||
return this.subscriptions_[subjectString].keys[key];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a Promise for our profile ID.
|
* Return a Promise for our profile ID.
|
||||||
*/
|
*/
|
||||||
@@ -835,9 +813,6 @@ Cosmopolite.prototype.onMessage_ = function(e) {
|
|||||||
}
|
}
|
||||||
subscription.messages.splice(insertAfter + 1, 0, e);
|
subscription.messages.splice(insertAfter + 1, 0, e);
|
||||||
|
|
||||||
if (e['key']) {
|
|
||||||
subscription.keys[e['key']] = e;
|
|
||||||
}
|
|
||||||
if ('onMessage' in this.callbacks_) {
|
if ('onMessage' in this.callbacks_) {
|
||||||
this.callbacks_['onMessage'](e);
|
this.callbacks_['onMessage'](e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,9 +24,6 @@ a {
|
|||||||
<div>
|
<div>
|
||||||
Message: <input type="text" id="message">
|
Message: <input type="text" id="message">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
Key: <input type="text" id="key">
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<input type="button" id="send" value="Send">
|
<input type="button" id="send" value="Send">
|
||||||
<input type="button" id="pin" value="Pin">
|
<input type="button" id="pin" value="Pin">
|
||||||
@@ -34,10 +31,6 @@ a {
|
|||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<div id="keys"></div>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<div id="pins"></div>
|
<div id="pins"></div>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
@@ -49,9 +42,6 @@ var subject = document.getElementById('subject');
|
|||||||
var subscriptions = document.getElementById('subscriptions');
|
var subscriptions = document.getElementById('subscriptions');
|
||||||
|
|
||||||
var message = document.getElementById('message');
|
var message = document.getElementById('message');
|
||||||
var key = document.getElementById('key');
|
|
||||||
|
|
||||||
var keys = document.getElementById('keys');
|
|
||||||
|
|
||||||
var pins = document.getElementById('pins');
|
var pins = document.getElementById('pins');
|
||||||
|
|
||||||
@@ -96,26 +86,6 @@ window.addEventListener('load', function() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
addMessage(message);
|
addMessage(message);
|
||||||
if (message['key']) {
|
|
||||||
var i;
|
|
||||||
for (i = 0; i < keys.childNodes.length; i++) {
|
|
||||||
var key = keys.childNodes[i];
|
|
||||||
if (key.message['key'] == message['key']) {
|
|
||||||
// Overwrite
|
|
||||||
key.replaceChild(
|
|
||||||
document.createTextNode(message['message']),
|
|
||||||
key.childNodes[1]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (i == keys.childNodes.length) {
|
|
||||||
var keyDiv = document.createElement('div');
|
|
||||||
keyDiv.appendChild(document.createTextNode(message['key'] + ' = '));
|
|
||||||
keyDiv.appendChild(document.createTextNode(message['message']));
|
|
||||||
keyDiv.message = message;
|
|
||||||
keys.appendChild(keyDiv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onPin: function(pin) {
|
onPin: function(pin) {
|
||||||
if (subscriptions.value != pin['subject']['name']) {
|
if (subscriptions.value != pin['subject']['name']) {
|
||||||
@@ -154,7 +124,7 @@ window.addEventListener('load', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('send').addEventListener('click', function() {
|
document.getElementById('send').addEventListener('click', function() {
|
||||||
debug.sendMessage(subscriptions.value, message.value, key.value);
|
debug.sendMessage(subscriptions.value, message.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('pin').addEventListener('click', function() {
|
document.getElementById('pin').addEventListener('click', function() {
|
||||||
|
|||||||
@@ -106,39 +106,6 @@ asyncTest('Message round trip', function() {
|
|||||||
cosmo.subscribe(subject, -1);
|
cosmo.subscribe(subject, -1);
|
||||||
});
|
});
|
||||||
|
|
||||||
asyncTest('Overwrite key', function() {
|
|
||||||
expect(8);
|
|
||||||
|
|
||||||
var subject = randstring();
|
|
||||||
var message1 = randstring();
|
|
||||||
var message2 = randstring();
|
|
||||||
var key = randstring();
|
|
||||||
|
|
||||||
var messages = 0;
|
|
||||||
|
|
||||||
var callbacks = {
|
|
||||||
'onMessage': function(e) {
|
|
||||||
messages++;
|
|
||||||
equal(e['subject']['name'], subject, 'subject matches');
|
|
||||||
equal(e['key'], key, 'key matches');
|
|
||||||
if (messages == 1) {
|
|
||||||
equal(e['message'], message1, 'message #1 matches');
|
|
||||||
equal(cosmo.getKeyMessage(subject, key)['message'], message1, 'message #1 matches by key')
|
|
||||||
cosmo.sendMessage(subject, message2, key);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
equal(e['message'], message2, 'message #2 matches');
|
|
||||||
equal(cosmo.getKeyMessage(subject, key)['message'], message2, 'message #2 matches by key')
|
|
||||||
cosmo.shutdown();
|
|
||||||
start();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
var cosmo = new Cosmopolite(callbacks, null, randstring());
|
|
||||||
cosmo.subscribe(subject, -1);
|
|
||||||
cosmo.sendMessage(subject, message1, key);
|
|
||||||
});
|
|
||||||
|
|
||||||
asyncTest('Complex object', function() {
|
asyncTest('Complex object', function() {
|
||||||
expect(2);
|
expect(2);
|
||||||
|
|
||||||
@@ -200,17 +167,15 @@ asyncTest('subscribe/unsubscribe Promise', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
asyncTest('Duplicate message suppression', function() {
|
asyncTest('Duplicate message suppression', function() {
|
||||||
expect(3);
|
expect(2);
|
||||||
|
|
||||||
var subject = randstring();
|
var subject = randstring();
|
||||||
var key = randstring();
|
|
||||||
var message1 = randstring();
|
var message1 = randstring();
|
||||||
var message2 = randstring();
|
var message2 = randstring();
|
||||||
|
|
||||||
var callbacks = {
|
var callbacks = {
|
||||||
'onMessage': function (msg) {
|
'onMessage': function (msg) {
|
||||||
equal(msg['subject']['name'], subject, 'subject matches');
|
equal(msg['subject']['name'], subject, 'subject matches');
|
||||||
equal(msg['key'], key, 'key matches');
|
|
||||||
equal(msg['message'], message1, 'message matches');
|
equal(msg['message'], message1, 'message matches');
|
||||||
cosmo.shutdown();
|
cosmo.shutdown();
|
||||||
start();
|
start();
|
||||||
@@ -224,9 +189,9 @@ asyncTest('Duplicate message suppression', function() {
|
|||||||
// chosen by fair dice roll.
|
// chosen by fair dice roll.
|
||||||
// guaranteed to be random.
|
// guaranteed to be random.
|
||||||
};
|
};
|
||||||
cosmo.sendMessage(subject, message1, key).then(function() {
|
cosmo.sendMessage(subject, message1).then(function() {
|
||||||
cosmo.sendMessage(subject, message2, key).then(function() {
|
cosmo.sendMessage(subject, message2).then(function() {
|
||||||
cosmo.subscribe(subject, 0, null, [key]);
|
cosmo.subscribe(subject, -1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -319,30 +284,25 @@ asyncTest('resubscribe', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
asyncTest('Message ordering', function() {
|
asyncTest('Message ordering', function() {
|
||||||
expect(5);
|
expect(3);
|
||||||
|
|
||||||
var subject = randstring();
|
var subject = randstring();
|
||||||
var messages = [ 'A', 'B', 'C', 'D', 'E', 'F' ];
|
var messages = [ 'A', 'B', 'C', 'D' ];
|
||||||
var keys = [ null, 'X', 'X', null, null, null ];
|
|
||||||
|
|
||||||
var cosmo = new Cosmopolite({}, null, randstring());
|
var cosmo = new Cosmopolite({}, null, randstring());
|
||||||
|
|
||||||
var sendNextMessage = function() {
|
var sendNextMessage = function() {
|
||||||
if (messages.length) {
|
if (messages.length) {
|
||||||
cosmo.sendMessage(subject, messages.shift(), keys.shift()).then(sendNextMessage);
|
cosmo.sendMessage(subject, messages.shift()).then(sendNextMessage);
|
||||||
} else {
|
} else {
|
||||||
cosmo.subscribe(subject, 1).then(function() {
|
cosmo.subscribe(subject, 1).then(function() {
|
||||||
cosmo.subscribe(subject, 0, null, ['X']).then(function() {
|
cosmo.subscribe(subject, 2).then(function() {
|
||||||
cosmo.subscribe(subject, 2).then(function() {
|
var fetched = cosmo.getMessages(subject);
|
||||||
var fetched = cosmo.getMessages(subject);
|
equal(fetched.length, 2, 'two messages');
|
||||||
equal(fetched.length, 3, 'three messages');
|
equal(fetched[0]['message'], 'C', 'message 0: C matches');
|
||||||
equal(fetched[0]['message'], 'C', 'message 0: C matches');
|
equal(fetched[1]['message'], 'D', 'message 1: D matches');
|
||||||
equal(fetched[1]['message'], 'E', 'message 1: E matches');
|
cosmo.shutdown();
|
||||||
equal(fetched[2]['message'], 'F', 'message 2: F matches');
|
start();
|
||||||
equal(cosmo.getKeyMessage(subject, 'X')['message'], 'C', 'key X matches');
|
|
||||||
cosmo.shutdown();
|
|
||||||
start();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user