aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcel <cel@f/6sQ6d2CMxRUhLpspgGIulDxDCwYD7DzFzPNr7u5AU=.ed25519>2017-06-10 20:44:18 -1000
committercel <cel@f/6sQ6d2CMxRUhLpspgGIulDxDCwYD7DzFzPNr7u5AU=.ed25519>2017-06-10 21:24:29 -1000
commitcc9024cba630c57aff2e16dbbd04844e4ec61781 (patch)
tree26232baac0599d2a149a6cc4d3df76fd7937efd4
parentb6d136ab10136f9d881b239a338a4c0eea2e65f0 (diff)
parenta3dca9e424477c2745c59f482342a1580ed807f0 (diff)
downloadpatchfoo-cc9024cba630c57aff2e16dbbd04844e4ec61781.tar.gz
patchfoo-cc9024cba630c57aff2e16dbbd04844e4ec61781.zip
Merge branch 'master' into exif-rotation
-rw-r--r--lib/app.js71
-rw-r--r--lib/git.js143
-rw-r--r--lib/render-msg.js10
-rw-r--r--lib/render.js42
-rw-r--r--lib/serve.js164
-rw-r--r--lib/util.js1
-rw-r--r--package.json2
7 files changed, 382 insertions, 51 deletions
diff --git a/lib/app.js b/lib/app.js
index d86b4b9..d1f76f3 100644
--- a/lib/app.js
+++ b/lib/app.js
@@ -172,6 +172,17 @@ App.prototype.publish = function (content, cb) {
tryPublish(2)
}
+App.prototype.wantSizeBlob = function (id, cb) {
+ var blobs = this.sbot.blobs
+ blobs.size(id, function (err, size) {
+ if (size != null) return cb(null, size)
+ blobs.want(id, function (err) {
+ if (err) return cb(err)
+ blobs.size(id, cb)
+ })
+ })
+}
+
App.prototype.addBlob = function (cb) {
var done = multicb({pluck: 1, spread: true})
var hashCb = done()
@@ -378,6 +389,66 @@ App.prototype.createContactStreams = function (id) {
return new Contacts(this.sbot).createContactStreams(id)
}
+function compareVoted(a, b) {
+ return b.value - a.value
+}
+
+App.prototype.getVoted = function (_opts, cb) {
+ if (isNaN(_opts.limit)) return pull.error(new Error('missing limit'))
+ var self = this
+ var opts = {
+ type: 'vote',
+ limit: _opts.limit * 100,
+ reverse: !!_opts.reverse,
+ gt: _opts.gt || undefined,
+ lt: _opts.lt || undefined,
+ }
+
+ var votedObj = {}
+ var votedArray = []
+ var numItems = 0
+ var firstTimestamp, lastTimestamp
+ pull(
+ self.sbot.messagesByType(opts),
+ self.unboxMessages(),
+ pull.take(function () {
+ return numItems < _opts.limit
+ }),
+ pull.drain(function (msg) {
+ if (!firstTimestamp) firstTimestamp = msg.timestamp
+ lastTimestamp = msg.timestamp
+ var vote = msg.value.content.vote
+ if (!vote) return
+ var target = u.linkDest(vote)
+ var votes = votedObj[target]
+ if (!votes) {
+ numItems++
+ votes = {id: target, value: 0, feedsObj: {}, feeds: []}
+ votedObj[target] = votes
+ votedArray.push(votes)
+ }
+ if (msg.value.author in votes.feedsObj) {
+ if (!opts.reverse) return // leave latest vote value as-is
+ // remove old vote value
+ votes.value -= votes.feedsObj[msg.value.author]
+ } else {
+ votes.feeds.push(msg.value.author)
+ }
+ var value = vote.value > 0 ? 1 : vote.value < 0 ? -1 : 0
+ votes.feedsObj[msg.value.author] = value
+ votes.value += value
+ }, function (err) {
+ if (err) return cb(err)
+ var items = votedArray
+ if (opts.reverse) items.reverse()
+ items.sort(compareVoted)
+ cb(null, {items: items,
+ firstTimestamp: firstTimestamp,
+ lastTimestamp: lastTimestamp})
+ })
+ )
+}
+
App.prototype.createAboutStreams = function (id) {
return this.about.createAboutStreams(id)
}
diff --git a/lib/git.js b/lib/git.js
index 1360a6f..fa889ef 100644
--- a/lib/git.js
+++ b/lib/git.js
@@ -7,6 +7,9 @@ var packidx = require('pull-git-packidx-parser')
var Reader = require('pull-reader')
var toPull = require('stream-to-pull-stream')
var zlib = require('zlib')
+var looper = require('looper')
+var multicb = require('multicb')
+var kvdiff = require('pull-kvdiff')
var ObjectNotFoundError = u.customError('ObjectNotFoundError')
@@ -15,6 +18,7 @@ var types = {
commit: true,
tree: true,
}
+var emptyBlobHash = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
module.exports = Git
@@ -81,6 +85,7 @@ Git.prototype.openObject = function (opts, cb) {
}
Git.prototype.readObject = function (obj) {
+ if (obj.offset === obj.next) return pull.empty()
return pull(
this.app.readBlobSlice(obj.packLink, {start: obj.offset, end: obj.next}),
this.decodeObject({
@@ -99,6 +104,19 @@ Git.prototype._findObject = function (opts, cb) {
if (!opts.obj) return cb(new TypeError('missing object id'))
var self = this
var objId = opts.obj
+ if (objId === emptyBlobHash) {
+ // special case: the empty blob may be found anywhere
+ self.app.getMsgDecrypted(opts.headMsgId, function (err, msg) {
+ if (err) return cb(err)
+ return cb(null, {
+ offset: 0,
+ next: 0,
+ packLink: null,
+ idx: null,
+ msg: msg,
+ })
+ })
+ }
self.findObjectMsgs(opts, function (err, msgs) {
if (err) return cb(err)
if (msgs.length === 0)
@@ -501,15 +519,20 @@ Git.prototype.getTag = function (obj, cb) {
function readCString(reader, cb) {
var chars = []
- reader.read(1, function next(err, ch) {
+ var loop = looper(function () {
+ reader.read(1, next)
+ })
+ function next(err, ch) {
if (err) return cb(err)
if (ch[0] === 0) return cb(null, Buffer.concat(chars).toString('utf8'))
chars.push(ch)
- reader.read(1, next)
- })
+ loop()
+ }
+ loop()
}
Git.prototype.readTree = function (obj) {
+ var self = this
var reader = Reader()
reader(this.readObject(obj))
return function (abort, cb) {
@@ -524,9 +547,121 @@ Git.prototype.readTree = function (obj) {
cb(null, {
name: name,
mode: mode,
- hash: hash.toString('hex')
+ hash: hash.toString('hex'),
+ type: mode === 0040000 ? 'tree' :
+ mode === 0160000 ? 'commit' : 'blob',
})
})
})
}
}
+
+Git.prototype.readCommitChanges = function (commit) {
+ var self = this
+ return u.readNext(function (cb) {
+ var done = multicb({pluck: 1})
+ commit.parents.forEach(function (rev) {
+ var cb = done()
+ self.getObjectMsg({
+ obj: rev,
+ headMsgId: commit.msg.key,
+ type: 'commit',
+ }, function (err, msg) {
+ if (err) return cb(err)
+ self.openObject({
+ obj: rev,
+ msg: msg.key,
+ }, function (err, obj) {
+ if (err) return cb(err)
+ self.getCommit(obj, cb)
+ })
+ })
+ })
+ done()(null, commit)
+ done(function (err, commits) {
+ if (err) return cb(err)
+ var done = multicb({pluck: 1})
+ commits.forEach(function (commit) {
+ var cb = done()
+ if (!commit.tree) return cb(null, pull.empty())
+ self.getObjectMsg({
+ obj: commit.tree,
+ headMsgId: commit.msg.key,
+ type: 'tree',
+ }, function (err, msg) {
+ if (err) return cb(err)
+ self.openObject({
+ obj: commit.tree,
+ msg: commit.msg.key,
+ }, cb)
+ })
+ })
+ done(function (err, trees) {
+ if (err) return cb(err)
+ cb(null, self.diffTreesRecursive(trees))
+ })
+ })
+ })
+}
+
+Git.prototype.diffTrees = function (objs) {
+ var self = this
+ return pull(
+ kvdiff(objs.map(function (obj) {
+ return self.readTree(obj)
+ }), 'name'),
+ pull.map(function (item) {
+ var diff = item.diff || {}
+ var head = item.values[item.values.length-1]
+ var created = true
+ for (var k = 0; k < item.values.length-1; k++)
+ if (item.values[k]) created = false
+ return {
+ name: item.key,
+ hash: diff.hash,
+ mode: diff.mode,
+ type: item.values.map(function (val) { return val.type }),
+ deleted: !head,
+ created: created
+ }
+ })
+ )
+}
+
+Git.prototype.diffTreesRecursive = function (objs) {
+ var self = this
+ return pull(
+ self.diffTrees(objs),
+ paramap(function (item, cb) {
+ if (!item.type.some(function (t) { return t === 'tree' }))
+ return cb(null, [item])
+ var done = multicb({pluck: 1})
+ item.type.forEach(function (type, i) {
+ var cb = done()
+ if (type !== 'tree') return cb(null, pull.once(item))
+ var hash = item.hash[i]
+ self.getObjectMsg({
+ obj: hash,
+ headMsgId: objs[i].msg.key,
+ }, function (err, msg) {
+ if (err) return cb(err)
+ self.openObject({
+ obj: hash,
+ msg: msg.key,
+ }, cb)
+ })
+ })
+ done(function (err, objs) {
+ if (err) return cb(err)
+ cb(null, pull(
+ self.diffTreesRecursive(objs),
+ pull.map(function (f) {
+ f.name = item.name + '/' + f.name
+ return f
+ })
+ ))
+ })
+ }, 4),
+ pull.flatten()
+ )
+}
diff --git a/lib/render-msg.js b/lib/render-msg.js
index 48a5777..8346baf 100644
--- a/lib/render-msg.js
+++ b/lib/render-msg.js
@@ -536,7 +536,7 @@ RenderMsg.prototype.gitUpdate = function (cb) {
return h('li', h('a', {href: self.render.toUrl(path)},
h('code', String(commit.sha1).substr(0, 8))), ' ',
self.linkify(String(commit.title)),
- self.gitCommitBody(commit.body)
+ self.render.gitCommitBody(commit.body)
)
})) : '',
Array.isArray(self.c.tags) ?
@@ -559,14 +559,6 @@ RenderMsg.prototype.gitUpdate = function (cb) {
})
}
-RenderMsg.prototype.gitCommitBody = function (body) {
- if (!body) return ''
- var isMarkdown = !/^# Conflicts:$/m.test(body)
- return isMarkdown
- ? h('div', {innerHTML: this.render.markdown('\n' + body)})
- : h('pre', this.linkify('\n' + body))
-}
-
RenderMsg.prototype.gitPullRequest = function (cb) {
var self = this
var done = multicb({pluck: 1, spread: true})
diff --git a/lib/render.js b/lib/render.js
index 21cdbda..c1c7edf 100644
--- a/lib/render.js
+++ b/lib/render.js
@@ -104,6 +104,7 @@ Render.prototype.emoji = function (emoji) {
src: this.opts.emoji_base + emoji + '.png',
alt: name,
height: 17,
+ align: 'absmiddle',
title: name,
})
}
@@ -304,3 +305,44 @@ Render.prototype.renderFeeds = function (opts) {
self.renderMsg(msg, opts, cb)
}, 4)
}
+
+Render.prototype.gitCommitBody = function (body) {
+ if (!body) return ''
+ var isMarkdown = !/^# Conflicts:$/m.test(body)
+ return isMarkdown
+ ? h('div', {innerHTML: this.markdown('\n' + body)})
+ : h('pre', this.linkify('\n' + body))
+}
+
+Render.prototype.getName = function (id, cb) {
+ // TODO: consolidate the get name/link functions
+ var self = this
+ switch (id && id[0]) {
+ case '%':
+ return self.app.getMsgDecrypted(id, function (err, msg) {
+ if (err && err.name == 'NotFoundError')
+ return cb(null, String(id).substring(0, 8) + '…(missing)')
+ if (err) return fallback()
+ new RenderMsg(self, self.app, msg, {wrap: false}).title(cb)
+ })
+ case '@': // fallthrough
+ case '&':
+ return self.app.getAbout(id, function (err, about) {
+ if (err || !about || !about.name) return fallback()
+ cb(null, about.name)
+ })
+ default:
+ return cb(null, String(id))
+ }
+ function fallback() {
+ cb(null, String(id).substr(0, 8) + '…')
+ }
+}
+
+Render.prototype.getNameLink = function (id, cb) {
+ var self = this
+ self.getName(id, function (err, name) {
+ if (err) return cb(err)
+ cb(null, h('a', {href: self.toUrl(id)}, name))
+ })
+}
diff --git a/lib/serve.js b/lib/serve.js
index 25969c9..9228335 100644
--- a/lib/serve.js
+++ b/lib/serve.js
@@ -286,6 +286,7 @@ Serve.prototype.path = function (url) {
case '/live': return this.live(m[2])
case '/compose': return this.compose(m[2])
case '/emojis': return this.emojis(m[2])
+ case '/votes': return this.votes(m[2])
}
m = /^(\/?[^\/]*)(\/.*)?$/.exec(url)
switch (m[1]) {
@@ -528,6 +529,90 @@ Serve.prototype.compose = function (ext) {
})
}
+Serve.prototype.votes = function (path) {
+ if (path) return pull(
+ pull.once(u.renderError(new Error('Not implemented')).outerHTML),
+ this.wrapPage('#' + channel),
+ this.respondSink(404, {'Content-Type': ctype('html')})
+ )
+
+ var self = this
+ var q = self.query
+ var opts = {
+ reverse: !q.forwards,
+ limit: Number(q.limit) || 50,
+ }
+ var gt = Number(q.gt)
+ if (gt) opts.gt = gt
+ var lt = Number(q.lt)
+ if (lt) opts.lt = lt
+
+ self.app.getVoted(opts, function (err, voted) {
+ if (err) return pull(
+ pull.once(u.renderError(err).outerHTML),
+ self.wrapPage('#' + channel),
+ self.respondSink(500, {'Content-Type': ctype('html')})
+ )
+
+ pull(
+ ph('table', [
+ ph('thead', [
+ ph('tr', [
+ ph('td', {colspan: 2}, self.syncPager({
+ first: voted.firstTimestamp,
+ last: voted.lastTimestamp,
+ }))
+ ])
+ ]),
+ ph('tbody', pull(
+ pull.values(voted.items),
+ paramap(function (item, cb) {
+ cb(null, ph('tr', [
+ ph('td', [String(item.value)]),
+ ph('td', [
+ self.phIdLink(item.id),
+ pull.once(' dug by '),
+ self.renderIdsList()(pull.values(item.feeds))
+ ])
+ ]))
+ }, 8)
+ )),
+ ph('tfoot', {}, []),
+ ]),
+ self.wrapPage('votes'),
+ self.respondSink(200, {
+ 'Content-Type': ctype('html')
+ })
+ )
+ })
+}
+
+Serve.prototype.syncPager = function (opts) {
+ var q = this.query
+ var reverse = !q.forwards
+ var min = (reverse ? opts.last : opts.first) || Number(q.gt)
+ var max = (reverse ? opts.first : opts.last) || Number(q.lt)
+ var minDate = new Date(min)
+ var maxDate = new Date(max)
+ var qOlder = mergeOpts(q, {lt: min, gt: undefined, forwards: undefined})
+ var qNewer = mergeOpts(q, {gt: max, lt: undefined, forwards: 1})
+ var atNewest = reverse ? !q.lt : !max
+ var atOldest = reverse ? !min : !q.gt
+ if (atNewest && !reverse) qOlder.lt++
+ if (atOldest && reverse) qNewer.gt--
+ return h('div',
+ atOldest ? 'oldest' : [
+ h('a', {href: '?' + qs.stringify(qOlder)}, '<<'), ' ',
+ h('span', {title: minDate.toString()}, htime(minDate)), ' ',
+ ],
+ ' - ',
+ atNewest ? 'now' : [
+ h('span', {title: maxDate.toString()}, htime(maxDate)), ' ',
+ h('a', {href: '?' + qs.stringify(qNewer)}, '>>')
+ ]
+ ).outerHTML
+}
+
Serve.prototype.peers = function (ext) {
var self = this
if (self.data.action === 'connect') {
@@ -622,14 +707,6 @@ Serve.prototype.channels = function (ext) {
)
}
-Serve.prototype.phIdLink = function (id) {
- return pull(
- pull.once(id),
- pull.asyncMap(this.renderIdLink.bind(this)),
- pull.map(u.toHTML)
- )
-}
-
Serve.prototype.contacts = function (path) {
var self = this
var id = String(path).substr(1)
@@ -949,14 +1026,13 @@ Serve.prototype.blob = function (id) {
var self = this
var blobs = self.app.sbot.blobs
if (self.req.headers['if-none-match'] === id) return self.respond(304)
- var done = multicb({pluck: 1, spread: true})
- blobs.want(id, function (err, has) {
+ self.app.wantSizeBlob(id, function (err, size) {
if (err) {
if (/^invalid/.test(err.message)) return self.respond(400, err.message)
else return self.respond(500, err.message || err)
}
- if (!has) return self.respond(404, 'Not found')
+ var done = multicb({pluck: 1, spread: true})
var heresTheData = done()
var heresTheType = done().bind(self, null)
@@ -1186,6 +1262,7 @@ Serve.prototype.wrapPage = function (title, searchQ) {
h('a', {href: render.toUrl('/advsearch')}, 'search'), ' ',
h('a', {href: render.toUrl('/live')}, 'live'), ' ',
h('a', {href: render.toUrl('/compose')}, 'compose'), ' ',
+ h('a', {href: render.toUrl('/votes')}, 'votes'), ' ',
h('a', {href: render.toUrl('/emojis')}, 'emojis'), ' ',
render.idLink(self.app.sbot.id, done()), ' ',
h('input.search-input', {name: 'q', value: searchQ,
@@ -1206,25 +1283,18 @@ Serve.prototype.wrapPage = function (title, searchQ) {
)
}
-Serve.prototype.renderIdLink = function (id, cb) {
- var render = this.app.render
- var el = render.idLink(id, function (err) {
- if (err || !el) {
- el = h('a', {href: render.toUrl(id)}, id)
- }
- cb(null, el)
- })
+Serve.prototype.phIdLink = function (id) {
+ return pull(
+ pull.once(id),
+ this.renderIdsList()
+ )
}
Serve.prototype.friends = function (path) {
var self = this
pull(
self.app.sbot.friends.createFriendStream({hops: 1}),
- self.renderFriends(),
- pull.map(function (el) {
- return [el, ' ']
- }),
- pull.map(u.toHTML),
+ self.renderIdsList(),
u.hyperwrap(function (items, cb) {
cb(null, [
h('section',
@@ -1240,14 +1310,17 @@ Serve.prototype.friends = function (path) {
)
}
-Serve.prototype.renderFriends = function () {
+Serve.prototype.renderIdsList = function () {
var self = this
- return paramap(function (id, cb) {
- self.renderIdLink(id, function (err, el) {
- if (err) el = u.renderError(err, ext)
- cb(null, el)
- })
- }, 8)
+ return pull(
+ paramap(function (id, cb) {
+ self.app.render.getNameLink(id, cb)
+ }, 8),
+ pull.map(function (el) {
+ return [el, ' ']
+ }),
+ pull.map(u.toHTML)
+ )
}
var relationships = [
@@ -1461,7 +1534,23 @@ Serve.prototype.gitCommit = function (rev) {
pull.once(commit.tree),
self.gitObjectLinks(obj.msg.key, 'tree')
)]) : '',
- h('pre', self.app.render.linkify(commit.body)).outerHTML,
+ h('blockquote',
+ self.app.render.gitCommitBody(commit.body)).outerHTML,
+ ph('h4', 'files'),
+ ph('table', pull(
+ self.app.git.readCommitChanges(commit),
+ pull.map(function (file) {
+ return ph('tr', [
+ ph('td', ph('code', u.escapeHTML(file.name))),
+ // ph('td', ph('code', u.escapeHTML(JSON.stringify(file.msg)))),
+ ph('td', file.deleted ? 'deleted'
+ : file.created ? 'created'
+ : file.hash ? 'changed'
+ : file.mode ? 'mode changed'
+ : JSON.stringify(file))
+ ])
+ })
+ ))
]
]),
self.wrapPage('git commit ' + rev),
@@ -1594,20 +1683,19 @@ Serve.prototype.gitTree = function (rev) {
})
}, 8),
pull.map(function (item) {
- var type = item.mode === 0040000 ? 'tree' :
- item.mode === 0160000 ? 'commit' : 'blob'
if (!item.msg) return ph('tr', [
ph('td',
- u.escapeHTML(item.name) + (type === 'tree' ? '/' : '')),
+ u.escapeHTML(item.name) + (item.type === 'tree' ? '/' : '')),
+ ph('td', u.escapeHTML(item.hash)),
ph('td', 'missing')
])
- var path = '/git/' + type + '/' + item.hash
+ var path = '/git/' + item.type + '/' + item.hash
+ '?msg=' + encodeURIComponent(item.msg.key)
var fileDate = new Date(item.msg.value.timestamp)
return ph('tr', [
ph('td',
ph('a', {href: self.app.render.toUrl(path)},
- u.escapeHTML(item.name) + (type === 'tree' ? '/' : ''))),
+ u.escapeHTML(item.name) + (item.type === 'tree' ? '/' : ''))),
ph('td',
self.phIdLink(item.msg.value.author)),
ph('td',
@@ -1770,7 +1858,7 @@ Serve.prototype.askWantBlobsForm = function (links) {
if (!u.isRef(link.link)) return
return ph('tr', [
ph('td', ph('code', link.link)),
- ph('td', self.app.render.formatSize(link.size)),
+ !isNaN(link.size) ? ph('td', self.app.render.formatSize(link.size)) : '',
])
})),
ph('input', {type: 'hidden', name: 'action', value: 'want-blobs'}),
diff --git a/lib/util.js b/lib/util.js
index a5f14f5..e4cf415 100644
--- a/lib/util.js
+++ b/lib/util.js
@@ -153,6 +153,7 @@ u.customError = function (name) {
}
u.escapeHTML = function (html) {
+ if (!html) return ''
return html.toString('utf8')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
diff --git a/package.json b/package.json
index e70fa96..e39ff34 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,7 @@
"human-time": "^0.0.1",
"hyperscript": "^2.0.2",
"jpeg-autorotate": "^3.0.0",
+ "looper": "^4.0.0",
"mime-types": "^2.1.12",
"multicb": "^1.2.1",
"pull-cat": "^1.1.11",
@@ -19,6 +20,7 @@
"pull-hash": "^1.0.0",
"pull-hyperscript": "^0.2.2",
"pull-identify-filetype": "^1.1.0",
+ "pull-kvdiff": "^0.0.1",
"pull-paginate": "^1.0.0",
"pull-paramap": "^1.2.1",
"pull-reader": "^1.2.9",