diff --git a/src/wormhole/server/db-schemas/v2.sql b/src/wormhole/server/db-schemas/v2.sql index 6659fc6..a3a3485 100644 --- a/src/wormhole/server/db-schemas/v2.sql +++ b/src/wormhole/server/db-schemas/v2.sql @@ -17,7 +17,11 @@ CREATE TABLE `nameplates` `id` VARCHAR, `mailbox_id` VARCHAR, -- really a foreign key `side1` VARCHAR, -- side name, or NULL - `side2` VARCHAR -- side name, or NULL + `side2` VARCHAR, -- side name, or NULL + -- timing data + `started` INTEGER, -- time when nameplace was opened + `second` INTEGER, -- time when second side opened + `closed` INTEGER -- time when closed ); CREATE INDEX `nameplates_idx` ON `nameplates` (`app_id`, `id`); CREATE INDEX `nameplates_mailbox_idx` ON `nameplates` (`app_id`, `mailbox_id`); @@ -30,10 +34,6 @@ CREATE TABLE `mailboxes` `id` VARCHAR, `side1` VARCHAR -- side name, or NULL `side2` VARCHAR -- side name, or NULL - -- timing data for the (optional) linked nameplate - `nameplate_started` INTEGER, -- time when related nameplace was opened - `nameplate_second` INTEGER, -- time when second side opened - `nameplate_closed` INTEGER, -- time when closed -- timing data for the mailbox itself `started` INTEGER, -- time when opened `second` INTEGER, -- time when second side opened diff --git a/src/wormhole/server/rendezvous.py b/src/wormhole/server/rendezvous.py index 1007579..6571f09 100644 --- a/src/wormhole/server/rendezvous.py +++ b/src/wormhole/server/rendezvous.py @@ -17,7 +17,7 @@ RELEASE = u"_release" def get_sides(row): return set([s for s in [row["side1"], row["side2"]] if s]) -def make_sides(side1, side2): +def make_sides(sides): return list(sides) + [None] * (2 - len(sides)) def generate_mailbox_id(): return base64.b32encode(os.urandom(8)).lower().strip("=") @@ -270,7 +270,7 @@ class AppNamespace: " WHERE `app_id`=?", (self._app_id,)) return set([row["id"] for row in c.fetchall()]) - def find_available_nameplate_id(self): + def _find_available_nameplate_id(self): claimed = self.get_nameplate_ids() for size in range(1,4): # stick to 1-999 for now available = set() @@ -288,6 +288,12 @@ class AppNamespace: return id raise ValueError("unable to find a free nameplate-id") + def allocate_nameplate(self, side, when): + nameplate_id = self._find_available_nameplate_id() + mailbox_id = self.claim_nameplate(self, nameplate_id, side, when) + del mailbox_id # ignored, they'll learn it from claim() + return nameplate_id + def _get_mailbox_id(self, nameplate_id): row = self._db.execute("SELECT `mailbox_id` FROM `nameplates`" " WHERE `app_id`=? AND `id`=?", @@ -295,36 +301,81 @@ class AppNamespace: return row["mailbox_id"] def claim_nameplate(self, nameplate_id, side, when): + # when we're done: + # * there will be one row for the nameplate + # * side1 or side2 will be populated + # * started or second will be populated + # * a mailbox id will be created, but not a mailbox row + # (ids are randomly unique, so we can defer creation until 'open') assert isinstance(nameplate_id, type(u"")), type(nameplate_id) + assert isinstance(side, type(u"")), type(side) db = self._db - rows = db.execute("SELECT * FROM `nameplates`" - " WHERE `app_id`=? AND `id`=?", - (self._app_id, nameplate_id)) - if rows: - mailbox_id = rows[0]["mailbox_id"] + row = db.execute("SELECT * FROM `nameplates`" + " WHERE `app_id`=? AND `id`=?", + (self._app_id, nameplate_id)).fetchone() + if row: + mailbox_id = row["mailbox_id"] + sides = [row["side1"], row["sides2"]] + if side not in sides: + if sides[0] and sides[1]: + raise XXXERROR("crowded") + sides[1] = side + db.execute("UPDATE `nameplates` SET " + "`side1`=?, `side2`=?, `mailbox_id`=?, `second`=?" + " WHERE `app_id`=? AND `id`=?", + (sides[0], sides[1], mailbox_id, when, + self._app_id, nameplate_id)) else: if self._log_requests: log.msg("creating nameplate#%s for app_id %s" % (nameplate_id, self._app_id)) - mailbox_id = UUID() - db.execute("INSERT INTO `mailboxes`" - " (`app_id`, `id`)" - " VALUES(?,?)", - (self._app_id, mailbox_id)) + mailbox_id = generate_mailbox_id() db.execute("INSERT INTO `nameplates`" - " (`app_id`, `id`, `mailbox_id`, `side1`, `side2`)" + " (`app_id`, `id`, `mailbox_id`, `side1`, `started`)" " VALUES(?,?,?,?,?)", - (self._app_id, nameplate_id, mailbox_id, None, None)) + (self._app_id, nameplate_id, mailbox_id, side, when)) + db.commit() + return mailbox_id - nameplate = Nameplate(self._app_id, self._db, nameplate_id, mailbox_id) - nameplate.claim(side, when) - return nameplate + def release_nameplate(self, nameplate_id, side, when): + # when we're done: + # * in the nameplate row, side1 or side2 will be removed + # * if the nameplate is now unused: + # * mailbox.nameplate_closed will be populated + # * the nameplate row will be removed + assert isinstance(nameplate_id, type(u"")), type(nameplate_id) + assert isinstance(side, type(u"")), type(side) + db = self._db + row = db.execute("SELECT * FROM `nameplates`" + " WHERE `app_id`=? AND `id`=?", + (self._app_id, nameplate_id)).fetchone() + if not row: + return + sides = get_sides(row) + if side not in sides: + return + sides.discard(side) + if sides: + s12 = make_sides(sides) + db.execute("UPDATE `nameplates` SET `side1`=?, `side2`=?" + " WHERE `app_id`=? AND `id`=?", + (s12[0], s12[1], self._app_id, nameplate_id)) + else: + db.execute("DELETE FROM `nameplates`" + " WHERE `app_id`=? AND `id`=?", + (self._app_id, nameplate_id)) + self._summarize_nameplate(row) - def claim_channel(self, channelid, side): + def open_mailbox(self, channelid, side): assert isinstance(channelid, type(u"")), type(channelid) channel = self.get_channel(channelid) channel.claim(side) return channel + # some of this overlaps with open() on a new mailbox + db.execute("INSERT INTO `mailboxes`" + " (`app_id`, `id`, `nameplate_started`, `started`)" + " VALUES(?,?,?,?)", + (self._app_id, mailbox_id, when, when)) def get_channel(self, channelid): assert isinstance(channelid, type(u"")) diff --git a/src/wormhole/server/rendezvous_websocket.py b/src/wormhole/server/rendezvous_websocket.py index b2bf3f2..612b1f3 100644 --- a/src/wormhole/server/rendezvous_websocket.py +++ b/src/wormhole/server/rendezvous_websocket.py @@ -155,31 +155,28 @@ class WebSocketRendezvous(websocket.WebSocketServerProtocol): def handle_allocate(self, server_rx): if self._did_allocate: raise Error("You already allocated one channel, don't be greedy") - nameplate_id = self._app.find_available_nameplate_id() + nameplate_id = self._app.allocate_nameplate(self._side, server_rx) assert isinstance(nameplate_id, type(u"")) self._did_allocate = True - self._nameplate = self._app.claim_nameplate(nameplate_id, self._side, - server_rx) self.send("nameplate", nameplate=nameplate_id) def handle_claim(self, msg, server_rx): if "nameplate" not in msg: raise Error("claim requires 'nameplate'") nameplate_id = msg["nameplate"] + self._nameplate_id = nameplate_id assert isinstance(nameplate_id, type(u"")), type(nameplate) - if self._nameplate and self._nameplate.get_id() != nameplate_id: - raise Error("claimed nameplate doesn't match allocated nameplate") - self._nameplate = self._app.claim_nameplate(nameplate_id, self._side, - server_rx) - mailbox_id = self._nameplate.get_mailbox_id() + mailbox_id = self._app.claim_nameplate(nameplate_id, self._side, + server_rx) self.send("mailbox", mailbox=mailbox_id) def handle_release(self, server_rx): - if not self._nameplate: + if not self._nameplate_id: raise Error("must claim a nameplate before releasing it") - deleted = self._nameplate.release(self._side, server_rx) - self._nameplate = None + deleted = self._app.release_nameplate(self._nameplate_id, + self._side, server_rx) + self._nameplate_id = None def handle_open(self, msg):