aboutsummaryrefslogtreecommitdiff
path: root/lib/about.js
diff options
context:
space:
mode:
Diffstat (limited to 'lib/about.js')
-rw-r--r--lib/about.js271
1 files changed, 271 insertions, 0 deletions
diff --git a/lib/about.js b/lib/about.js
index 1e41691..c905d61 100644
--- a/lib/about.js
+++ b/lib/about.js
@@ -81,6 +81,277 @@ function computeTopAbout(aboutByFeed) {
}
About.prototype.get = function (dest, cb) {
+ if (dest[0] === '@') return this.getSocial(dest, cb)
+ return this.getCausal(dest, cb)
+}
+
+function copy(obj) {
+ if (obj === null || typeof obj !== 'object') return obj
+ var o = {}
+ for (var k in obj) o[k] = obj[k]
+ return o
+}
+
+function premapAbout(msg) {
+ var value = {
+ about: {},
+ mentions: {},
+ branches: {},
+ sources: {},
+ ts: msg.ts
+ }
+ var c = msg.value.content
+ if (!c) return value
+
+ if (c.branch) {
+ msg.branches.map(function (id) {
+ value.branches[id] = true
+ })
+ }
+
+ if (!c.about && c.root) return value
+ value.about = {}
+ var author = msg.value.author
+ var source = {id: msg.key, seq: msg.value.sequence}
+ for (var k in c) switch(k) {
+ case 'type':
+ case 'about':
+ case 'branch':
+ break
+ case 'recps':
+ // get recps from root message only
+ if (!c.root && !c.about) {
+ value.recps = {}
+ u.toLinkArray(c.recps).forEach(function (link) {
+ value.recps[link.link] = link
+ })
+ }
+ break
+ case 'mentions':
+ value.mentions = {}
+ u.toLinkArray(c.mentions).map(function (link) {
+ value.mentions[link.link] = link
+ })
+ break
+ case 'image':
+ var link = u.toLink(c.image)
+ if (!link) break
+ value.about.image = link.link
+ value.about.imageLink = link
+ var sources = value.sources.image || (value.sources.image = [])
+ sources[author] = source
+ break
+ case 'attendee':
+ var attendee = u.linkDest(c.attendee)
+ if (attendee && attendee === author) {
+ // TODO: allow users adding other users as attendees?
+ var attendeeLink = copy(u.toLink(c.attendee))
+ attendeeLink.source = msg.key
+ value.attending = {}
+ value.attending[attendee] = attendeeLink.remove ? null : attendeeLink }
+ break
+ default:
+ // TODO: handle arrays
+ value.about[k] = c[k]
+ var sources = value.sources[k] || (value.sources[k] = {})
+ sources[author] = source
+ }
+ return value
+}
+
+function reduceAbout(values, lastValue) {
+ var newValue = {
+ about: {},
+ mentions: {},
+ branches: {},
+ sources: {}
+ }
+ values.sort(compareByTs).concat(lastValue).forEach(function (value) {
+ if (!value) return
+ if (value.ts) {
+ if (!newValue.ts || value.ts > newValue.ts) {
+ newValue.ts = value.ts
+ }
+ }
+ if (value.attending) {
+ var attending = newValue.attending || (newValue.attending = {})
+ for (var k in value.attending) {
+ attending[k] = value.attending[k]
+ }
+ }
+ if (value.mentions) for (var k in value.mentions) {
+ newValue.mentions[k] = value.mentions[k]
+ }
+ if (value.branches) for (var k in value.branches) {
+ newValue.branches[k] = value.branches[k]
+ }
+ if (value.recps) {
+ // note: zero recps is still private. truthy recps indicates private
+ var recps = newValue.recps || (newValue.recps = {})
+ for (var k in value.recps) {
+ newValue.recps[k] = value.recps[k]
+ }
+ }
+ if (value.about) for (var k in value.about) {
+ // TODO: use merge heuristics
+ newValue.about[k] = value.about[k]
+ if (lastValue && lastValue.about[k]) {
+ if (value === lastValue) {
+ newValue.sources[k] = lastValue.sources[k]
+ } else {
+ // message setting a property resets the property's sources from branches
+ }
+ } else {
+ var newSources = newValue.sources[k] || (newValue.sources[k] = {})
+ var sources = value.sources[k]
+ for (var feed in sources) {
+ if (newSources[feed] && newSources[feed].seq > sources[feed].seq) {
+ // assume causal order in user's own feed.
+ // this condition shouldn't be reached if messages are in feed order
+ console.error('skip', k, feed, sources[feed].id, newSources[feed].id)
+ continue
+ }
+ newSources[feed] = sources[feed]
+ }
+ }
+ }
+ })
+ return newValue
+}
+
+function postmapAbout(value) {
+ var about = {
+ ts: value.ts,
+ _sources: {}
+ }
+ for (var k in value.sources) {
+ var propSources = about._sources[k] = []
+ for (var feed in value.sources[k]) {
+ propSources.push(value.sources[k][feed].id)
+ }
+ }
+ if (value.mentions) {
+ about.mentions = []
+ for (var k in value.mentions) {
+ about.mentions.push(value.mentions[k])
+ }
+ }
+ if (value.attending) {
+ about.attendee = []
+ for (var k in value.attending) {
+ var link = value.attending[k]
+ if (link) about.attendee.push(link)
+ }
+ }
+ if (value.branches) about.branch = Object.keys(value.branches)
+ if (value.recps) {
+ about.recps = []
+ for (var k in value.recps) {
+ about.recps.push(value.recps[k])
+ }
+ }
+ if (value.about) for (var k in value.about) {
+ about[k] = value.about[k]
+ }
+ return about
+}
+
+function compareByTs(a, b) {
+ return a.ts - b.ts
+}
+
+About.prototype.getCausal = function (dest, cb) {
+ var self = this
+ var backlinks = {}
+ var seen = {}
+ var queue = []
+ var aboutAtMsgs = {}
+ var now = Date.now()
+ function enqueue(msg) {
+ if (!seen[msg.key]) {
+ seen[msg.key] = true
+ queue.push(msg)
+ }
+ }
+ function isMsgIdDone(id) {
+ return !!aboutAtMsgs[id]
+ }
+ function isMsgReady(msg) {
+ return msg.branches.every(isMsgIdDone)
+ }
+ function dequeue() {
+ var msg = queue.filter(isMsgReady).sort(compareByTs)[0]
+ if (!msg) return console.error('thread error'), queue.shift()
+ var i = queue.indexOf(msg)
+ queue.splice(i, 1)
+ return msg
+ }
+ pull(
+ cat([
+ dest[0] === '%' && self.app.pullGetMsg(dest),
+ self.app.sbot.links({
+ rel: 'about',
+ dest: dest,
+ values: true,
+ private: true,
+ meta: false
+ }),
+ self.app.sbot.links({
+ rel: 'root',
+ dest: dest,
+ values: true,
+ private: true,
+ meta: false
+ })
+ ]),
+ pull.unique('key'),
+ self.app.unboxMessages(),
+ pull.drain(function (msg) {
+ var c = msg.value.content
+ if (!c) return
+ msg = {
+ key: msg.key,
+ ts: Math.min(now,
+ Number(msg.timestamp) || Infinity,
+ Number(msg.value.timestamp || c.timestamp) || Infinity),
+ value: msg.value,
+ branches: u.toLinkArray(c.branch).map(u.linkDest)
+ }
+ if (!msg.branches.length) {
+ enqueue(msg)
+ } else msg.branches.forEach(function (id) {
+ var linksToMsg = backlinks[id] || (backlinks[id] = [])
+ linksToMsg.push(msg)
+ })
+ }, function (err) {
+ if (err) return cb(err)
+ while (queue.length) {
+ var msg = dequeue()
+ var abouts = msg.branches.map(function (id) { return aboutAtMsgs[id] })
+ aboutAtMsgs[msg.key] = reduceAbout(abouts, premapAbout(msg))
+ var linksToMsg = backlinks[msg.key]
+ if (linksToMsg) linksToMsg.forEach(enqueue)
+ }
+ var headAbouts = []
+ var headIds = []
+ for (var id in aboutAtMsgs) {
+ if (backlinks[id]) continue
+ headIds.push(id)
+ headAbouts.push(aboutAtMsgs[id])
+ }
+ headAbouts.sort(compareByTs)
+ var about = postmapAbout(reduceAbout(headAbouts))
+ about.branch = headIds
+ cb(null, about)
+ })
+ )
+}
+
+function getValue(obj) {
+ return obj.value
+}
+
+About.prototype.getSocial = function (dest, cb) {
var self = this
var myAbout = []
var aboutByFeed = {}