Files: 9a7e43ac498a13a9720df2ba9fdd94eafe23d04d / indexes / links.js
5276 bytesRaw
1 | var ref = require('ssb-ref') |
2 | var path = require('path') |
3 | var pull = require('pull-stream') |
4 | var ltgt = require('ltgt') |
5 | var ssbKeys = require('ssb-keys') |
6 | var paramap = require('pull-paramap') |
7 | var Format = require('../util').formatStream |
8 | var ViewLevel = require('flumeview-level') |
9 | |
10 | |
11 | |
12 | //53 bit integer |
13 | var MAX_INT = 0x1fffffffffffff |
14 | var u = require('../util') |
15 | |
16 | var mlib = require('ssb-msgs') |
17 | |
18 | function isString (s) { |
19 | return 'string' === typeof s |
20 | } |
21 | |
22 | module.exports = function (keys) { |
23 | |
24 | function indexMsg (localtime, id, msg) { |
25 | //DECRYPT the message, if possible |
26 | //to enable indexing. If external apis |
27 | //are not provided that may access indexes |
28 | //then this will not leak information. |
29 | //otherwise, we may need to figure something out. |
30 | |
31 | var content = (keys && isString(msg.content)) |
32 | ? ssbKeys.unbox(msg.content, keys) |
33 | : msg.content |
34 | |
35 | if(!content) return [] |
36 | |
37 | var a = [] |
38 | |
39 | if(isString(content.type)) |
40 | a.push(['type', content.type.toString().substring(0, 32), localtime]) |
41 | |
42 | mlib.indexLinks(content, function (obj, rel) { |
43 | a.push(['link', msg.author, rel, obj.link, msg.sequence, id]) |
44 | a.push(['_link', obj.link, rel, msg.author, msg.sequence, id]) |
45 | }) |
46 | |
47 | return a |
48 | } |
49 | |
50 | var createIndex = ViewLevel(1, function (data) { |
51 | return indexMsg(data.timestamp, data.key, data.value) |
52 | }) |
53 | |
54 | return function (log, name) { |
55 | var index = createIndex(log, name) |
56 | |
57 | index.methods = { |
58 | messagesByType: 'source', |
59 | links: 'source' |
60 | } |
61 | |
62 | index.messagesByType = function (opts) { |
63 | if(!opts) |
64 | throw new Error('must provide {type: string} to messagesByType') |
65 | |
66 | if(isString(opts)) |
67 | opts = {type: opts} |
68 | |
69 | opts = u.options(opts) |
70 | var keys = opts.keys !== false |
71 | var values = opts.values !== false |
72 | opts.values = true |
73 | |
74 | ltgt.toLtgt(opts, opts, function (value) { |
75 | return ['type', opts.type, value] |
76 | }, u.lo, u.hi) |
77 | |
78 | return pull( |
79 | index.read(opts), |
80 | Format(keys, values) |
81 | ) |
82 | } |
83 | |
84 | function format(opts, op, key, value) { |
85 | var meta = opts.meta !== false //default: true |
86 | var keys = opts.keys !== false //default: true |
87 | var vals = opts.values === true //default: false |
88 | if(!meta&&!keys&&!vals) |
89 | throw new Error('a stream without any values does not make sense') |
90 | if(!meta) return ( |
91 | keys && vals ? {key: op.key, value: value} |
92 | : keys ? op.key |
93 | : value |
94 | ) |
95 | else { |
96 | if(vals) op.value = value |
97 | if(!keys) delete op.key |
98 | delete op._value |
99 | return op |
100 | } |
101 | } |
102 | |
103 | function type(t) { return {feed: '@', msg: '%', blob: '&'}[t] || t } |
104 | |
105 | function linksOpts (opts) { |
106 | if(!opts) throw new Error('opts *must* be provided') |
107 | |
108 | if( !(opts.values === true) |
109 | && !(opts.meta !== false) |
110 | && !(opts.keys !== false) |
111 | ) |
112 | throw new Error('makes no sense to return stream without results' |
113 | + 'set at least one of {keys, values, meta} to true') |
114 | |
115 | var src = type(opts.source), dst = type(opts.dest), rel = opts.rel |
116 | |
117 | var back = dst && !src |
118 | var from = back ? dst : src, to = back ? src : dst |
119 | |
120 | function range(value, end, def) { |
121 | return !value ? def : /^[@%&]$/.test(value) ? value + end : value |
122 | } |
123 | function lo(value) { return range(value, "!", u.lo) } |
124 | function hi(value) { return range(value, "~", u.hi) } |
125 | |
126 | var index = back ? '_link' : 'link' |
127 | var gte = [index, lo(from), rel || u.lo, lo(to), u.lo, u.lo] |
128 | var lte = [index, hi(from), rel || u.hi, hi(to), u.hi, u.hi] |
129 | return { |
130 | gte: gte, lte: lte, reverse: opts.reverse, |
131 | back: back, rel: rel, source: src, dest: dst, |
132 | live: opts.live, sync: opts.sync, old: opts.old, |
133 | props: { |
134 | keys: opts.keys !== false, //default: true |
135 | meta: opts.meta !== false, //default: true |
136 | values: opts.values === true, //default: false |
137 | } |
138 | } |
139 | } |
140 | |
141 | function testLink (a, e) { //actual, expected |
142 | return e ? e.length === 1 ? a[0]==e[0] : a===e : true |
143 | } |
144 | |
145 | index.links = function (opts) { |
146 | opts = linksOpts(opts) |
147 | return pull( |
148 | index.read(opts), |
149 | pull.map(function (op) { |
150 | if(op.sync) return op |
151 | return { |
152 | source: op.key[opts.back?3:1], |
153 | rel: op.key[2], |
154 | dest: op.key[opts.back?1:3], |
155 | key: op.key[5], |
156 | _value: op.value.value, |
157 | //timestamp: op.value.timestamp |
158 | } |
159 | }), |
160 | // in case source and dest are known but not rel, |
161 | // this will scan all links from the source |
162 | // and filter out those to the dest. not efficient |
163 | // but probably a rare query. |
164 | pull.filter(function (data) { |
165 | if(data.sync) return true |
166 | if(opts.rel && opts.rel !== data.rel) return false |
167 | if(!testLink(data.dest, opts.dest)) return false |
168 | if(!testLink(data.source, opts.source)) return false |
169 | return true |
170 | }), |
171 | pull.map(function (op) { |
172 | if(op.sync) return op |
173 | return format(opts.props, op, op.key, op._value) |
174 | }) |
175 | ) |
176 | } |
177 | |
178 | return index |
179 | } |
180 | } |
181 | |
182 | |
183 | |
184 | |
185 | |
186 | |
187 | |
188 |
Built with git-ssb-web