Commit 01fc1dd4552fcb7a246dc2551b28a35e2b68d7f3
initial
Dominic Tarr committed on 10/30/2017, 10:31:21 AMFiles changed
.travis.yml | added |
LICENSE | added |
README.md | added |
index.js | added |
package.json | added |
store.js | added |
test/index.js | added |
LICENSE | ||
---|---|---|
@@ -1,0 +1,22 @@ | ||
1 … | +Copyright (c) 2017 'Dominic Tarr' | |
2 … | + | |
3 … | +Permission is hereby granted, free of charge, | |
4 … | +to any person obtaining a copy of this software and | |
5 … | +associated documentation files (the "Software"), to | |
6 … | +deal in the Software without restriction, including | |
7 … | +without limitation the rights to use, copy, modify, | |
8 … | +merge, publish, distribute, sublicense, and/or sell | |
9 … | +copies of the Software, and to permit persons to whom | |
10 … | +the Software is furnished to do so, | |
11 … | +subject to the following conditions: | |
12 … | + | |
13 … | +The above copyright notice and this permission notice | |
14 … | +shall be included in all copies or substantial portions of the Software. | |
15 … | + | |
16 … | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
17 … | +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES | |
18 … | +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
19 … | +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR | |
20 … | +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
21 … | +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
22 … | +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
index.js | ||
---|---|---|
@@ -1,0 +1,78 @@ | ||
1 … | +var pull = require('pull-stream') | |
2 … | +var GQ = require('gossip-query') | |
3 … | +var hash = require('ssb-keys/util').hash | |
4 … | + | |
5 … | +function getId(msg) { | |
6 … | + return '%'+hash(JSON.stringify(msg, null, 2)) | |
7 … | +} | |
8 … | + | |
9 … | +var Store = require('./store') | |
10 … | +var log = console.error | |
11 … | + | |
12 … | +console.error = function (m) { | |
13 … | + log(new Error('---------------').stack) | |
14 … | + log(m) | |
15 … | + | |
16 … | +} | |
17 … | + | |
18 … | +exports.name = 'ooo' | |
19 … | +exports.version = '1.0.0' | |
20 … | +exports.manifest = { | |
21 … | + stream: 'duplex', | |
22 … | + get: 'async' | |
23 … | +} | |
24 … | +exports.permissions = { | |
25 … | + anonymous: {allow: ['stream']} | |
26 … | +} | |
27 … | + | |
28 … | +var Flume = require('flumedb') | |
29 … | +var OffsetLog = require('flumelog-offset') | |
30 … | +var mkdirp = require('mkdirp') | |
31 … | +var ViewHashtable = require('flumeview-hashtable') | |
32 … | + | |
33 … | +exports.init = function (sbot, config) { | |
34 … | + var id = sbot.id | |
35 … | + | |
36 … | + store = Store(config) | |
37 … | + | |
38 … | + var gq = GQ({ | |
39 … | + check: function (key, cb) { | |
40 … | + store.keys.get(key, function (err, data) { | |
41 … | + if(data) cb(null, data.value) | |
42 … | + else | |
43 … | + sbot.get(key, function (err, msg) { | |
44 … | + cb(null, msg) | |
45 … | + }) | |
46 … | + }) | |
47 … | + }, | |
48 … | + process: function (id, msg, cb) { | |
49 … | + if(id !== getId(msg)) | |
50 … | + cb() | |
51 … | + else cb(null, msg) | |
52 … | + } | |
53 … | + }) | |
54 … | + | |
55 … | + sbot.on('rpc:connect', function (rpc, isClient) { | |
56 … | + console.log('CONNECT...', id.substring(0, 5), rpc.id.substring(0, 5)) | |
57 … | + if(isClient) { | |
58 … | + var stream = gq.createStream(rpc.id) | |
59 … | + pull(stream, rpc.ooo.stream(function () {}), stream) | |
60 … | + } | |
61 … | + }) | |
62 … | + | |
63 … | + return { | |
64 … | + stream: function () { | |
65 … | + //called by muxrpc, so remote id is set as this.id | |
66 … | + return gq.createStream(this.id) | |
67 … | + }, | |
68 … | + get: function (id, cb) { | |
69 … | + gq.query(id, function (err, msg) { | |
70 … | + if(err) return cb(err) | |
71 … | + store.add(msg, function (err, data) { | |
72 … | + cb(null, data) | |
73 … | + }) | |
74 … | + }) | |
75 … | + } | |
76 … | + } | |
77 … | +} | |
78 … | + |
package.json | ||
---|---|---|
@@ -1,0 +1,28 @@ | ||
1 … | +{ | |
2 … | + "name": "ssb-ooo", | |
3 … | + "description": "", | |
4 … | + "version": "1.0.0", | |
5 … | + "homepage": "https://github.com/dominictarr/ssb-ooo", | |
6 … | + "repository": { | |
7 … | + "type": "git", | |
8 … | + "url": "git://github.com/dominictarr/ssb-ooo.git" | |
9 … | + }, | |
10 … | + "dependencies": { | |
11 … | + "flumecodec": "0.0.1", | |
12 … | + "flumedb": "^0.4.2", | |
13 … | + "flumelog-offset": "^3.2.5", | |
14 … | + "flumeview-hashtable": "^1.0.2", | |
15 … | + "mkdirp": "^0.5.1", | |
16 … | + "pull-stream": "^3.6.1", | |
17 … | + "ssb-keys": "^7.0.12" | |
18 … | + }, | |
19 … | + "devDependencies": { | |
20 … | + "rimraf": "^2.6.1", | |
21 … | + "tape": "^4.8.0" | |
22 … | + }, | |
23 … | + "scripts": { | |
24 … | + "test": "set -e; for t in test/*.js; do node $t; done" | |
25 … | + }, | |
26 … | + "author": "'Dominic Tarr' <dominic.tarr@gmail.com> (dominictarr.com)", | |
27 … | + "license": "MIT" | |
28 … | +} |
store.js | |||
---|---|---|---|
@@ -1,0 +1,46 @@ | |||
1 … | +var Flume = require('flumedb') | ||
2 … | +var OffsetLog = require('flumelog-offset') | ||
3 … | +var mkdirp = require('mkdirp') | ||
4 … | +var ViewHashTable = require('flumeview-hashtable') | ||
5 … | + | ||
6 … | +var codec = require('flumecodec/json') | ||
7 … | +var path = require('path') | ||
8 … | +var hash = require('ssb-keys/util').hash | ||
9 … | + | ||
10 … | +function getId(msg) { | ||
11 … | + return '%'+hash(JSON.stringify(msg, null, 2)) | ||
12 … | +} | ||
13 … | + | ||
14 … | +module.exports = function (config) { | ||
15 … | + //we'll store out of order messages in their own log | ||
16 … | + //so that we don't interfere with the views on in in-order messages | ||
17 … | + //it does mean we'll download them again later if we follow | ||
18 … | + //this feed, but it makes everything simpler overall | ||
19 … | + //and the point of messages is to be small | ||
20 … | + //and the point of ooo messages is that there | ||
21 … | + //is not really that many. | ||
22 … | + | ||
23 … | + var log = OffsetLog( | ||
24 … | + path.join(config.path, 'ooo', 'log.offset'), | ||
25 … | + {blockSize:1024*16, codec:codec} | ||
26 … | + ) | ||
27 … | + var store = Flume(log) | ||
28 … | + .use('keys', ViewHashTable(2, function (key) { | ||
29 … | + var b = new Buffer(key.substring(1,7), 'base64').readUInt32BE(0) | ||
30 … | + return b | ||
31 … | + })) | ||
32 … | + | ||
33 … | + store.add = function (msg, cb) { | ||
34 … | + var data = { | ||
35 … | + key: getId(msg), | ||
36 … | + value: msg, | ||
37 … | + timestamp: Date.now() | ||
38 … | + } | ||
39 … | + store.append(data, function (err) { | ||
40 … | + if(err) cb(err) | ||
41 … | + else cb(null, data) | ||
42 … | + }) | ||
43 … | + } | ||
44 … | + | ||
45 … | + return store | ||
46 … | +} |
test/index.js | ||
---|---|---|
@@ -1,0 +1,89 @@ | ||
1 … | +var tape = require('tape') | |
2 … | +var ssbKeys = require('ssb-keys') | |
3 … | +var path = require('path') | |
4 … | +var rmrf = require('rimraf') | |
5 … | +var createSbot = require('scuttlebot') | |
6 … | + .use(require('../')) // | |
7 … | + | |
8 … | +// .use(require('ssb-friends')) | |
9 … | +// .use(require('../plugins/gossip')) | |
10 … | +// .use(require('../plugins/logging')) | |
11 … | + | |
12 … | +var alice = createSbot({ | |
13 … | + temp: 'ooo_a', | |
14 … | + timeout: 1000, | |
15 … | + port: 34597, | |
16 … | + keys: ssbKeys.generate() | |
17 … | +}) | |
18 … | +var bob = createSbot({ | |
19 … | + temp: 'ooo_b', | |
20 … | + timeout: 1000, | |
21 … | + port: 34598, | |
22 … | + host: 'localhost', timeout: 20001, | |
23 … | + replicate: {hops: 3, legacy: false}, | |
24 … | + keys: ssbKeys.generate() | |
25 … | +}) | |
26 … | + | |
27 … | +var carol_path = path.join('/tmp/test-ssb-ooo_carol/') | |
28 … | +require('rimraf').sync(carol_path) | |
29 … | + | |
30 … | +var carol = createSbot({ | |
31 … | + path: carol_path, | |
32 … | + timeout: 1000, | |
33 … | + port: 34599, | |
34 … | + keys: ssbKeys.generate() | |
35 … | +}) | |
36 … | + | |
37 … | +var m1, m2 | |
38 … | + | |
39 … | +tape('connect', function (t) { | |
40 … | + alice.connect(bob.getAddress(), function (err) { | |
41 … | + if(err) throw err | |
42 … | + }) | |
43 … | + carol.connect(bob.getAddress(), function (err) { | |
44 … | + if(err) throw err | |
45 … | + }) | |
46 … | + var start = Date.now() | |
47 … | + alice.publish({type: 'test', msg: 'hello'}, function (err, data) { | |
48 … | + if(err) throw err | |
49 … | + console.log(data) | |
50 … | + m1 = data | |
51 … | + carol.ooo.get(data.key, function (err, _data) { | |
52 … | + t.deepEqual(_data.value, data.value, 'received the message!') | |
53 … | + console.log('time', Date.now() - start) | |
54 … | + | |
55 … | + alice.publish({type: 'test2', msg: 'hello2'}, function (err, data) { | |
56 … | + m2 = data | |
57 … | + var start = Date.now() | |
58 … | + carol.ooo.get(data.key, function (err, _data) { | |
59 … | + if(err) throw err | |
60 … | + console.log('time2', Date.now() - start) | |
61 … | + t.deepEqual(_data.value, data.value, 'received the 2nd message!') | |
62 … | + alice.close() | |
63 … | + bob.close() | |
64 … | + carol.close(t.end) | |
65 … | + }) | |
66 … | + }) | |
67 … | + }) | |
68 … | + }) | |
69 … | +}) | |
70 … | + | |
71 … | + | |
72 … | +tape('reopen', function (t) { | |
73 … | + var carol = createSbot({ | |
74 … | + path: carol_path, | |
75 … | + timeout: 1000, | |
76 … | + port: 34599, | |
77 … | + keys: ssbKeys.generate() | |
78 … | + }) | |
79 … | + | |
80 … | + carol.ooo.get(m1.key, function (err, data) { | |
81 … | + t.deepEqual(data.value, m1.value) | |
82 … | + carol.ooo.get(m2.key, function (err, data) { | |
83 … | + t.deepEqual(data.value, m2.value) | |
84 … | + t.end() | |
85 … | + carol.close() | |
86 … | + }) | |
87 … | + }) | |
88 … | +}) | |
89 … | + |
Built with git-ssb-web