-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
173 lines (165 loc) · 4.95 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
var from = require('from2')
var varint = require('varint')
var nextTick = process.nextTick
module.exports = Sparse
function Sparse(opts) {
if (!(this instanceof Sparse)) return new Sparse(opts)
if (!opts) opts = {}
this._read = opts.read
}
Sparse.prototype.search = function (q) {
q = q.toLowerCase()
var self = this
var riset = new Set, rfqueue = [], rfset = new Set
var lqueue = [], queue = []
var readNext = null, readSize = -1, meta = null
self._getMeta(function (err, m) {
meta = m
for (var i = 0; i < meta.lookup.length; i++) {
if (meta.lookup[i] >= q) break
}
// todo: check for duplicate lookup names
lqueue.push('l'+i)
if (readNext) read(readSize, readNext)
readNext = null
})
return from.obj(read)
function read(size, next) {
if (!meta) {
readSize = size
readNext = next
return
}
if (queue.length > 0) {
return next(null, queue.shift())
} else if (rfqueue.length > 0) {
var rfile = rfqueue.shift()
self._read(rfile, function (err, buf) {
if (err) return next(err)
rfset.delete(rfile)
var offset = 0
while (offset < buf.length) {
var flen = varint.decode(buf, offset)
offset += varint.decode.bytes
var id = varint.decode(buf, offset)
if (riset.has(id)) {
riset.delete(id)
var payload = buf.slice(offset, offset+flen)
var r = parsePayload(payload)
queue.push(r)
}
offset += flen
}
read(size, next)
})
} else if (lqueue.length > 0) {
var lfile = lqueue.shift()
self._read(lfile, function (err, buf) {
if (err) return next(err)
var offset = 0
while (offset < buf.length) {
var klen = varint.decode(buf, offset)
offset += varint.decode.bytes
var key = buf.slice(offset, offset+klen).toString()
offset += klen
var id = varint.decode(buf, offset)
offset += varint.decode.bytes
if (key.startsWith(q)) {
riset.add(id)
var rfile = getRecordFile(meta, id)
if (!rfset.has(rfile)) rfqueue.push(rfile)
rfset.add(rfile)
}
}
read(size, next)
})
} else {
next(null, null)
}
}
}
Sparse.prototype.getRecord = function (id, cb) {
if (!cb) cb = noop
var self = this
self._getMeta(function (err, meta) {
if (err) return cb(err)
var rfile = getRecordFile(meta, id)
self._read(rfile, function (err, buf) {
if (err) return cb(err)
var offset = 0
while (offset < buf.length) {
var flen = varint.decode(buf, offset)
offset += varint.decode.bytes
var fid = varint.decode(buf, offset)
if (id === fid) {
var payload = buf.slice(offset, offset+flen)
return cb(null, parsePayload(payload))
}
offset += flen
}
cb(null, null)
})
})
}
Sparse.prototype._getMeta = function (cb) {
var self = this
if (self._meta) return nextTick(cb, null, self._meta)
self._read('meta.json', function (err, src) {
if (err) return cb(err)
self._meta = JSON.parse(src.toString())
cb(null, self._meta)
})
}
function noop() {}
function getRecordFile(meta, id) {
for (var i = 0; i < meta.record.length; i++) {
if (meta.record[i] >= id) break
}
return 'r' + i
}
function parsePayload(buf) {
var offset = 0
var id = varint.decode(buf, offset)
offset += varint.decode.bytes
var nameLen = varint.decode(buf, offset)
offset += varint.decode.bytes
var name = buf.slice(offset, offset+nameLen).toString()
offset += nameLen
var longitude = buf.readFloatBE(offset)
offset += 4
var latitude = buf.readFloatBE(offset)
offset += 4
var cc1Len = varint.decode(buf, offset)
offset += varint.decode.bytes
var countryCode = buf.slice(offset, offset+cc1Len).toString()
offset += cc1Len
var cc2Len = varint.decode(buf, offset)
offset += varint.decode.bytes
var cc2 = buf.slice(offset, offset+cc2Len).toString()
offset += cc2Len
var admin1Len = varint.decode(buf, offset)
offset += varint.decode.bytes
var admin1 = buf.slice(offset, offset+admin1Len).toString()
offset += admin1Len
var admin2Len = varint.decode(buf, offset)
offset += varint.decode.bytes
var admin2 = buf.slice(offset, offset+admin2Len).toString()
offset += admin2Len
var admin3Len = varint.decode(buf, offset)
offset += varint.decode.bytes
var admin3 = buf.slice(offset, offset+admin3Len).toString()
offset += admin3Len
var admin4Len = varint.decode(buf, offset)
offset += varint.decode.bytes
var admin4 = buf.slice(offset, offset+admin4Len).toString()
offset += admin4Len
var population = varint.decode(buf, offset)
offset += varint.decode.bytes
var elevation = varint.decode(buf, offset)
offset += varint.decode.bytes
return {
id, name, longitude, latitude,
countryCode, cc2, admin1, admin2, admin3, admin4,
population, elevation,
}
}