From 16a3027dc258d934ce38f4ab143c41246a5c1862 Mon Sep 17 00:00:00 2001 From: cel Date: Tue, 9 May 2017 15:17:00 -1000 Subject: Add another way to lookup names --- lib/render.js | 33 +++++++++++++++++++++++++++++++++ lib/serve.js | 46 +++++++++++++++++----------------------------- 2 files changed, 50 insertions(+), 29 deletions(-) diff --git a/lib/render.js b/lib/render.js index aa2a6da..ea94329 100644 --- a/lib/render.js +++ b/lib/render.js @@ -257,3 +257,36 @@ Render.prototype.renderFeeds = function (opts) { self.renderMsg(msg, opts, cb) }, 4) } + +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 defac87..002c520 100644 --- a/lib/serve.js +++ b/lib/serve.js @@ -583,18 +583,10 @@ Serve.prototype.contacts = function (path) { ) } - function idLink(id) { - return pull( - pull.once(id), - pull.asyncMap(self.renderIdLink.bind(self)), - pull.map(u.toHTML) - ) - } - pull( cat([ ph('section', {}, [ - ph('h3', {}, ['Contacts: ', idLink(id)]), + ph('h3', {}, ['Contacts: ', self.pullIdLink(id)]), ph('h4', {}, 'Friends'), renderFriendsList()(contacts.friends), ph('h4', {}, 'Follows'), @@ -1028,25 +1020,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.pullIdLink = 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', @@ -1062,14 +1047,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 = [ -- cgit v1.2.3 From d8f6c6a19274cf53d9967d3597dd582ee7233ca3 Mon Sep 17 00:00:00 2001 From: cel Date: Tue, 9 May 2017 15:17:46 -1000 Subject: Add votes page --- lib/app.js | 60 ++++++++++++++++++++++++++++++++++++++++++ lib/serve.js | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+) diff --git a/lib/app.js b/lib/app.js index b57e5a6..e503443 100644 --- a/lib/app.js +++ b/lib/app.js @@ -285,3 +285,63 @@ App.prototype.streamChannels = function (opts) { 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}) + }) + ) +} diff --git a/lib/serve.js b/lib/serve.js index 002c520..2f6caab 100644 --- a/lib/serve.js +++ b/lib/serve.js @@ -256,6 +256,7 @@ Serve.prototype.path = function (url) { case '/friends': return this.friends(m[2]) case '/live': return this.live(m[2]) case '/compose': return this.compose(m[2]) + case '/votes': return this.votes(m[2]) } m = /^(\/?[^\/]*)(\/.*)?$/.exec(url) switch (m[1]) { @@ -496,6 +497,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.pullIdLink(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') { @@ -1002,6 +1087,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'), ' ', render.idLink(self.app.sbot.id, done()), ' ', h('input.search-input', {name: 'q', value: searchQ, placeholder: 'search'}) -- cgit v1.2.3 From 2aa94754c9a09e5ea707a30c6e937b7bfb3dd05a Mon Sep 17 00:00:00 2001 From: cel Date: Fri, 2 Jun 2017 18:34:51 -1000 Subject: Use existing function for rendering git commit bodies --- lib/render-msg.js | 10 +--------- lib/render.js | 8 ++++++++ lib/serve.js | 3 ++- 3 files changed, 11 insertions(+), 10 deletions(-) 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..d28f4b4 100644 --- a/lib/render.js +++ b/lib/render.js @@ -304,3 +304,11 @@ 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)) +} diff --git a/lib/serve.js b/lib/serve.js index 9d5eb82..d2510e1 100644 --- a/lib/serve.js +++ b/lib/serve.js @@ -1438,7 +1438,8 @@ 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, ] ]), self.wrapPage('git commit ' + rev), -- cgit v1.2.3 From 81e06ce58a3a4a09b7c6c2837e8076ecec6e38e3 Mon Sep 17 00:00:00 2001 From: cel Date: Sat, 3 Jun 2017 12:16:56 -1000 Subject: Protect against too much recursion --- lib/git.js | 11 ++++++++--- package.json | 1 + 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/git.js b/lib/git.js index 1360a6f..d4a071e 100644 --- a/lib/git.js +++ b/lib/git.js @@ -7,6 +7,7 @@ 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 ObjectNotFoundError = u.customError('ObjectNotFoundError') @@ -501,12 +502,16 @@ 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) { diff --git a/package.json b/package.json index 465e93f..42311a4 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "human-time": "^0.0.1", "hyperscript": "^2.0.2", "hashlru": "^2.1.0", + "looper": "^4.0.0", "mime-types": "^2.1.12", "multicb": "^1.2.1", "pull-cat": "^1.1.11", -- cgit v1.2.3 From 4d96b6e0f7b726a5d96742e51fcb6c05b1d3f85f Mon Sep 17 00:00:00 2001 From: cel Date: Sat, 3 Jun 2017 11:53:21 -1000 Subject: Show commit files changed --- lib/git.js | 132 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- lib/render.js | 1 + lib/serve.js | 26 +++++++++--- lib/util.js | 1 + package.json | 1 + 5 files changed, 154 insertions(+), 7 deletions(-) diff --git a/lib/git.js b/lib/git.js index d4a071e..fa889ef 100644 --- a/lib/git.js +++ b/lib/git.js @@ -8,6 +8,8 @@ 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') @@ -16,6 +18,7 @@ var types = { commit: true, tree: true, } +var emptyBlobHash = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391' module.exports = Git @@ -82,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({ @@ -100,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) @@ -515,6 +532,7 @@ function readCString(reader, cb) { } Git.prototype.readTree = function (obj) { + var self = this var reader = Reader() reader(this.readObject(obj)) return function (abort, cb) { @@ -529,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.js b/lib/render.js index d28f4b4..880470a 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, }) } diff --git a/lib/serve.js b/lib/serve.js index d2510e1..59a09d5 100644 --- a/lib/serve.js +++ b/lib/serve.js @@ -1440,6 +1440,21 @@ Serve.prototype.gitCommit = function (rev) { )]) : '', 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), @@ -1572,20 +1587,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', @@ -1748,7 +1762,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, '>') diff --git a/package.json b/package.json index 42311a4..816c115 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,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", -- cgit v1.2.3 From e9b1ee71a12fcd6c15a2f0b06671bc819da98303 Mon Sep 17 00:00:00 2001 From: cel Date: Tue, 6 Jun 2017 20:50:08 -1000 Subject: Don't call blobs.want if we already have the blob --- lib/app.js | 11 +++++++++++ lib/serve.js | 12 ++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/app.js b/lib/app.js index d86b4b9..b9e1924 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() diff --git a/lib/serve.js b/lib/serve.js index 59a09d5..1fe9b37 100644 --- a/lib/serve.js +++ b/lib/serve.js @@ -947,22 +947,18 @@ 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') - blobs.size(id, done()) pull( blobs.get(id), pull.map(Buffer), - ident(done().bind(self, null)), + ident(gotType), self.respondSink() ) - done(function (err, size, type) { - if (err) console.trace(err) + function gotType(type) { type = type && mime.lookup(type) if (type) self.res.setHeader('Content-Type', type) if (typeof size === 'number') self.res.setHeader('Content-Length', size) @@ -971,7 +967,7 @@ Serve.prototype.blob = function (id) { self.res.setHeader('Cache-Control', 'public, max-age=315360000') self.res.setHeader('etag', id) self.res.writeHead(200) - }) + } }) } -- cgit v1.2.3