parsav  Check-in [59e1d7d56a]

Overview
Comment:iterating
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 59e1d7d56aea6174c52dbedafd74e94bef506690949dd0a3728404456fdbb120
User & Date: lexi on 2020-12-16 08:46:02
Other Links: manifest | tags
Context
2020-12-16
08:46
add nix build file check-in: df4ae251ef user: lexi tags: trunk
08:46
iterating check-in: 59e1d7d56a user: lexi tags: trunk
2020-12-14
14:40
more boilerplate, add template framework check-in: 6f17de4767 user: lexi tags: trunk
Changes

Added backend/pgsql.t version [0360541ecf].























































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
-- vim: ft=terra
local queries = {
	conf_get = {
		params = {rawstring}, sql = [[
			select value from parsav_config
				where key = $1::text limit 1
		]];
	};

	conf_set = {
		params = {rawstring,rawstring}, sql = [[
			insert into parsav_config (key, value)
				values ($1::text, $2::text)
				on conflict (key) do update set value = $2::text
		]];
	};

	conf_reset = {
		params = {rawstring}, sql = [[
			delete from parsav_config where
				key = $1::text 
		]];
	};

	actor_fetch_uid = {
		params = {uint64}, sql = [[
			select
				id, nym, handle, origin,
				bio, rank, quota, key
			from parsav_actors
				where id = $1::bigint
		]];
	};

	actor_fetch_xid = {
		params = {rawstring}, sql = [[
			select a.id, a.nym, a.handle, a.origin,
			       a.bio, a.rank, a.quota, a.key,

				coalesce(s.domain,
				        (select value from parsav_config
							where key='domain' limit 1)) as domain

			from      parsav_actors  as a
			left join parsav_servers as s
				on a.origin = s.id

			where $1::text = (a.handle || '@' || domain) or
			      $1::text = ('@' || a.handle || '@' || domain) or
				  (a.origin is null and $1::text = ('@' || a.handle))
		]];
	};
}

local struct pqr {
	sz: intptr
	res: &lib.pq.PGresult
}
terra pqr:free() if self.sz > 0 then lib.pq.PQclear(self.res) end end
terra pqr:null(row: intptr, col: intptr)
	return (lib.pq.PQgetisnull(self.res, row, col) == 1)
end
terra pqr:string(row: intptr, col: intptr)
	var v = lib.pq.PQgetvalue(self.res, row, col)
	var r: lib.mem.ptr(int8)
	r.ct = lib.str.sz(v)
	r.ptr = lib.str.ndup(v, r.ct)
	return r
end
pqr.methods.int = macro(function(self, ty, row, col)
	return quote
		var i: ty:astype()
		var v = lib.pq.PQgetvalue(self.res, row, col)
		lib.math.netswap_ip(ty, v, &i)
	in i end
end)

local con = symbol(&lib.pq.PGconn)
local prep = {}
for k,q in pairs(queries) do
	local qt = (q.sql):gsub('%s+',' '):gsub('^%s*(.-)%s*$','%1')
	local stmt = 'parsavpg_' .. k
	prep[#prep + 1] = quote
		var res = lib.pq.PQprepare([con], stmt, qt, [#q.params], nil)
		defer lib.pq.PQclear(res)
		if res == nil or lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_COMMAND_OK then
			if res == nil then
				lib.bail('grievous error occurred preparing ',k,' statement')
			end
			lib.bail('could not prepare PGSQL statement ',k,': ',lib.pq.PQresultErrorMessage(res))
		end
		lib.dbg('prepared PGSQL statement ',k) 
	end

	local args, casts, counters, fixers, ft, yield = {}, {}, {}, {}, {}, {}
	for i, ty in ipairs(q.params) do
		args[i] = symbol(ty)
		ft[i] = `1
		if ty == rawstring then
			counters[i] = `lib.trn([args[i]] == nil, 0, lib.str.sz([args[i]]))
			casts[i] = `[&int8]([args[i]])
		elseif ty:isintegral() then
			counters[i] = ty.bytes
			casts[i] = `[&int8](&[args[i]])
			fixers[#fixers + 1] = quote
				--lib.io.fmt('uid=%llu(%llx)\n',[args[i]],[args[i]])
				[args[i]] = lib.math.netswap(ty, [args[i]])
			end
		end
	end

	q.exec = terra(src: &lib.store.source, [args])
		var params = arrayof([&int8], [casts])
		var params_sz = arrayof(int, [counters])
		var params_ft = arrayof(int, [ft])
		[fixers]
		var res = lib.pq.PQexecPrepared([&lib.pq.PGconn](src.handle), stmt,
			[#args], params, params_sz, params_ft, 1)
		if res == nil then
			lib.bail(['grievous error occurred executing '..k..' against database'])
		elseif lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_TUPLES_OK then
			lib.bail(['PGSQL database procedure '..k..' failed\n'],
			lib.pq.PQresultErrorMessage(res))
		end

		var ct = lib.pq.PQntuples(res)
		if ct == 0 then
			lib.pq.PQclear(res)
			return pqr {0, nil}
		else
			return pqr {ct, res}
		end
	end
end

local terra row_to_actor(r: &pqr, row: intptr): lib.store.actor
	var a = lib.store.actor {
		id = r:int(uint64, row, 0);
		nym = r:string(row, 1);
		handle = r:string(row, 2);
		bio = r:string(row, 4);
		key = r:string(row, 7);
		rights = lib.store.rights_default();
	}
	a.rights.rank = r:int(uint16, 0, 5);
	a.rights.quota = r:int(uint32, 0, 6);
	if r:null(0,3) then a.origin = 0
	else a.origin = r:int(uint64,0,3) end
	return a
end

local b = `lib.store.backend {
	id = "pgsql";
	open = [terra(src: &lib.store.source): &opaque
		lib.report('connecting to postgres database: ', src.string.ptr)
		var [con] = lib.pq.PQconnectdb(src.string.ptr)
		if lib.pq.PQstatus(con) ~= lib.pq.CONNECTION_OK then
			lib.warn('postgres backend connection failed')
			lib.pq.PQfinish(con)
			return nil
		end
		var res = lib.pq.PQexec(con, [[
			select pg_catalog.set_config('search_path', 'public', false)
		]])
		if res ~= nil then defer lib.pq.PQclear(res) end
		if res == nil or lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_TUPLES_OK then
			lib.warn('failed to secure postgres connection')
			lib.pq.PQfinish(con)
			return nil
		end

		[prep]
		return con
	end];
	close = [terra(src: &lib.store.source) lib.pq.PQfinish([&lib.pq.PGconn](src.handle)) end];

	conf_get = [terra(src: &lib.store.source, key: rawstring)
		var r = queries.conf_get.exec(src, key)
		if r.sz == 0 then return [lib.mem.ptr(int8)] { ptr = nil, ct = 0 } else
			defer r:free()
			return r:string(0,0)
		end
	end];
	conf_set = [terra(src: &lib.store.source, key: rawstring, val: rawstring)
		queries.conf_set.exec(src, key, val):free() end];
	conf_reset = [terra(src: &lib.store.source, key: rawstring)
		queries.conf_reset.exec(src, key):free() end];
	
	actor_fetch_uid = [terra(src: &lib.store.source, uid: uint64)
		var r = queries.actor_fetch_uid.exec(src, uid)
		if r.sz == 0 then
			return [lib.stat(lib.store.actor)] { ok = false, error = 1}
		else
			defer r:free()
			var a = [lib.stat(lib.store.actor)] { ok = true }
			a.val = row_to_actor(&r, 0)
			a.val.source = src
			return a
		end
	end];
}

return b

Modified cmdparse.t from [8abc7a6fc7] to [c7f162fae3].


1
2
3
4
5
6
7

return function(tbl)
	local options = terralib.types.newstruct('options') do
		local flags = '' for _,d in pairs(tbl) do flags = flags .. d[1] end
		local helpstr = 'usage: parsav [-' .. flags .. '] [<arg>...]\n'
		options.entries = {
			{field = 'arglist', type = lib.mem.ptr(rawstring)}
		}
>







1
2
3
4
5
6
7
8
-- vim: ft=terra
return function(tbl)
	local options = terralib.types.newstruct('options') do
		local flags = '' for _,d in pairs(tbl) do flags = flags .. d[1] end
		local helpstr = 'usage: parsav [-' .. flags .. '] [<arg>...]\n'
		options.entries = {
			{field = 'arglist', type = lib.mem.ptr(rawstring)}
		}

Modified config.lua from [85408f7c8a] to [60797f7bc4].

19
20
21
22
23
24
25

26
27
28
29
30
31
32
	dist      = default('parsav_dist', coalesce(
		os.getenv('NIX_PATH')  and 'nixos',
		os.getenv('NIX_STORE') and 'nixos',
	''));
	tgttrip   = default('parsav_arch_triple'); -- target triple, used in xcomp
	tgtcpu    = default('parsav_arch_cpu'); -- target cpu, used in xcomp
	tgthf     = u.tobool(default('parsav_arch_armhf',true)); 

	build     = {
		id = u.rndstr(6);
		release = u.ingest('release');
		when = os.date();
	};
	feat = {};
}







>







19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
	dist      = default('parsav_dist', coalesce(
		os.getenv('NIX_PATH')  and 'nixos',
		os.getenv('NIX_STORE') and 'nixos',
	''));
	tgttrip   = default('parsav_arch_triple'); -- target triple, used in xcomp
	tgtcpu    = default('parsav_arch_cpu'); -- target cpu, used in xcomp
	tgthf     = u.tobool(default('parsav_arch_armhf',true)); 
	endian    = default('parsav_arch_endian', 'little');
	build     = {
		id = u.rndstr(6);
		release = u.ingest('release');
		when = os.date();
	};
	feat = {};
}

Added file.t version [fc7770c3f7].











































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
-- vim: ft=terra
-- TODO: add support for windows IO calls
local handle_type = int
local posix = terralib.includec 'fcntl.h'
local unistd = terralib.includec 'unistd.h'

struct file {
	handle: handle_type
	read: bool
	write: bool
}

file.mode = { read = 0, write = 1, rw = 2 }
file.seek = { abs = 0, ofs = 1, eof = 2 }

file.methods = {
	open = terra(path: rawstring, mode: uint8)
		var f: file
		var flag: int
		if mode == [file.mode.rw] then
			flag = posix.O_RDWR
			f.read = true f.write = true
		elseif mode == [file.mode.read] then
			flag = posix.O_RDONLY
			f.read = true f.write = false
		elseif mode == [file.mode.read] then
			flag = posix.O_WRONLY
			f.read = false f.write = true
		else lib.bail('invalid file mode') end
		lib.dbg('opening file ', path)
		f.handle = posix.open(path, flag) 

		var r: lib.stat(file)
		if f.handle == -1 then
			r.ok = false
			r.error = 1 -- TODO get errno somehow?
		else
			r.ok = true
			r.val = f
		end
		return r
	end;
	close = terra(self: &file)
		unistd.close(self.handle)
		self.handle = -1
		self.read = false
		self.write = false
	end;
	read = terra(self: &file, dest: rawstring, sz: intptr): ptrdiff
		return unistd.read(self.handle,dest,sz)
	end;
	write = terra(self: &file, data: &opaque, sz: intptr): ptrdiff
		return unistd.write(self.handle,data,sz)
	end;
	seek = terra(self: &file, ofs: ptrdiff, wh: int)
		var whence: int
		if wh == [file.seek.abs] then
			whence = unistd.SEEK_SET
		elseif wh == [file.seek.ofs] then
			whence = unistd.SEEK_CUR
		elseif wh == [file.seek.eof] then
			whence = unistd.SEEK_END
		else lib.bail('invalid seek mode') end
	
		return unistd.lseek(self.handle, ofs, whence)
	end;
}

return file

Modified makefile from [0f98e9b572] to [40c8adaeb1].

18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
..
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
	lib/mbedtls/library/libmbedcrypto.a \
	lib/mbedtls/library/libmbedx509.a
dep.mongoose: lib/mongoose/libmongoose.a
dep.json-c: lib/json-c/libjson-c.a

lib:
	mkdir $@
# parsav is designed to be fronted by a real web
# server like nginx if SSL is to be used
# generate a shim static library so mongoose cooperates
# with the build apparatus
lib/mongoose/libmongoose.a: lib/mongoose lib/mongoose/mongoose.c lib/mongoose/mongoose.h
	$(CC) -c $</mongoose.c -o lib/mongoose/mongoose.o \
		-DMG_ENABLE_THREADS \
		-DMG_ENABLE_IPV6 \
		-DMG_ENABLE_HTTP_WEBDAV \
		-DMG_ENABLE_HTTP_WEBSOCKET=0
	ar rcs $@ lib/mongoose/*.o
................................................................................
lib/json-c/libjson-c.a: lib/json-c/Makefile
	$(MAKE) -C lib/json-c
lib/mbedtls/library/%.a: lib/mbedtls 
	$(MAKE) -C lib/mbedtls/library $*.a

ifeq ($(dl), git)
lib/mongoose: lib
	cd lib && git clone https://github.com/cesanta/mongoose
lib/mbedtls: lib
	cd lib && git clone https://github.com/ARMmbed/mbedtls.git
lib/json-c: lib
	cd lib && git clone https://github.com/json-c/json-c.git
else
lib/%: lib/%.tar.gz
	cd lib && tar zxf $*.tar.gz







|
|
|
|







 







|







18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
..
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
	lib/mbedtls/library/libmbedcrypto.a \
	lib/mbedtls/library/libmbedx509.a
dep.mongoose: lib/mongoose/libmongoose.a
dep.json-c: lib/json-c/libjson-c.a

lib:
	mkdir $@
# generate a shim static library so mongoose cooperates
# with the build apparatus. note that parsav is designed
# to be fronted by a real web server like nginx if SSL
# is to be used, so we don't turn on SSL in mongoose
lib/mongoose/libmongoose.a: lib/mongoose lib/mongoose/mongoose.c lib/mongoose/mongoose.h
	$(CC) -c $</mongoose.c -o lib/mongoose/mongoose.o \
		-DMG_ENABLE_THREADS \
		-DMG_ENABLE_IPV6 \
		-DMG_ENABLE_HTTP_WEBDAV \
		-DMG_ENABLE_HTTP_WEBSOCKET=0
	ar rcs $@ lib/mongoose/*.o
................................................................................
lib/json-c/libjson-c.a: lib/json-c/Makefile
	$(MAKE) -C lib/json-c
lib/mbedtls/library/%.a: lib/mbedtls 
	$(MAKE) -C lib/mbedtls/library $*.a

ifeq ($(dl), git)
lib/mongoose: lib
	cd lib && git clone https://github.com/cesanta/mongoose.git
lib/mbedtls: lib
	cd lib && git clone https://github.com/ARMmbed/mbedtls.git
lib/json-c: lib
	cd lib && git clone https://github.com/json-c/json-c.git
else
lib/%: lib/%.tar.gz
	cd lib && tar zxf $*.tar.gz

Added math.t version [fe958b6645].













































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
-- vim: ft=terra
local m = {
	shorthand = {maxlen = 14}
}

-- swap in place -- faster on little endian
m.netswap_ip = macro(function(ty, src, dest)
	if ty:astype().type ~= 'integer' then error('bad type') end
	local bytes = ty:astype().bytes
	src = `[&uint8](src)
	dest = `[&uint8](dest)
	if config.endian == 'little' then
		return quote for i = 0, bytes do dest[i] = src[bytes - (i+1)] end end
	elseif config.endian == 'big' then
		return quote for i = 0, bytes do dest[i] = src[i] end end
	else error('unknown endianness '..config.endian) end
end)

-- swap out of place -- safer, more flexible, and optimized to an intrinsic call; trivial on big endian
m.netswap = macro(function(tyq, src)
	if config.endian == 'little' then
		local ty = tyq:astype()
		local a,b = symbol(ty), symbol(ty)
		local bytes = ty.bytes
		local steps = {}
		for i=0,bytes-1 do
			steps[#steps + 1] = quote
				b = b << 8
				b = b or (a and 0xff)
				a = a >> 8
			end
		end
		return quote
			var [a] = src
			var [b] = 0
			[steps]
		in b end
	elseif config.endian == 'big' then return `src
	else error('unknown endianness '..config.endian) end
end)

terra m.shorthand.cval(character: int8): {uint8, bool}
	var ch = [uint8](character)

	if ch >= 0x30 and ch <= 0x39 then
		ch = 00 + (ch - 0x30)
	elseif ch == 0x2d then ch = 10
	elseif ch >= 0x41 and ch <= 0x5a then
		ch = 11 + (ch - 0x41)
	elseif ch == 0x3a then ch = 37
	elseif ch >= 0x61 and ch <= 0x7a then
		ch = 38 + (ch - 0x61)
	else return 0, false end

	return ch, true 
end

terra m.shorthand.gen(val: uint64, dest: rawstring): ptrdiff
	var lst = "0123456789-ABCDEFGHIJKLMNOPQRSTUVWXYZ:abcdefghijklmnopqrstuvwxyz"
	var buf: int8[m.shorthand.maxlen]
	var ptr = [&int8](buf)
	while val ~= 0 do
		var v = val % 64
		@ptr = lst[v]
		ptr = ptr + 1
		val = val / 64
	end
	var len = ptr - buf
	for i = 0, len do
		dest[i] = buf[len - (i+1)]
	end
	dest[len] = 0
	return len
end

terra m.shorthand.parse(s: rawstring, len: intptr): {uint64, bool}
	var val: uint64 = 0
	for i = 0, len do
		var v, ok = m.shorthand.cval(s[i])
		if ok == false then return 0, false end
		val = (val * 64) + v
	end
	return val, true
end

return m

Modified parsav.t from [889180c92d] to [ce122c09dc].

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
..
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
...
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
...
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
...
217
218
219
220
221
222
223



224
225
226
227
228
229
230
231
232
233
234

235
236
237
238
239
240
241
242
243
244

245

246
247
248
249
250
251
252
253
254
				code[#code+1] = quote var n = v in
					lib.io.send(2, n, lib.str.sz(n)) end
			end
		end
		code[#code+1] = `lib.io.send(2, '\n', 1)
		return code
	end;







	proc = {
		exit = terralib.externfunction('exit', int -> {});
		getenv = terralib.externfunction('getenv', rawstring -> rawstring);
	};
	io = {
		open = terralib.externfunction('open', {rawstring, int} -> int);
		close = terralib.externfunction('close',  {int} -> int);
		send = terralib.externfunction('write', {int, rawstring, intptr} -> ptrdiff);
		recv = terralib.externfunction('read',  {int, rawstring, intptr} -> ptrdiff);
		say = macro(function(msg) return `lib.io.send(2, msg, [#(msg:asvalue())]) end);
		fmt = terralib.externfunction('printf',
			terralib.types.funcpointer({rawstring},{int},true));
	};
	str = {
		sz = terralib.externfunction('strlen', rawstring -> intptr);
		cmp = terralib.externfunction('strcmp', {rawstring, rawstring} -> int);

		cpy = terralib.externfunction('stpcpy',{rawstring, rawstring} -> rawstring);
		ncpy = terralib.externfunction('stpncpy',{rawstring, rawstring, intptr} -> rawstring);

		fmt = terralib.externfunction('asprintf',
			terralib.types.funcpointer({&rawstring},{int},true));
	};






	mem = {
		zero = macro(function(r)
			return quote
				for i = 0, [r.tree.type.N] do r[i] = 0 end
			end
		end);
		heapa_raw = terralib.externfunction('malloc', intptr -> &opaque);
................................................................................
				ct = sz;
			}
		end)
	};
}

local noise = global(uint8,1)







local defrep = function(level,n,code)
	return macro(function(...)
		local q = lib.emit("\27["..code..";1m(parsav "..n..")\27[m ", ...)

		return quote
			if noise >= level then [q] end
		end
	end);
end
lib.dbg = defrep(3,'debug', '32')
lib.report = defrep(2,'info', '35')
lib.warn = defrep(1,'warn', '33')
lib.bail = macro(function(...)
	local q = lib.emit("\27[31;1m(parsav fatal)\27[m ", ...)
	return quote
		[q]
		lib.proc.exit(1)
	end
end);























lib.mem.ptr = terralib.memoize(function(ty)
	local t = terralib.types.newstruct(string.format('ptr<%s>', ty))
	t.entries = {
		{'ptr', &ty};
		{'ct', intptr};
	}

	local recurse = false
	if ty:isstruct() then
		if ty.methods.free then recurse = true end
	end
	t.methods = {
		free = terra(self: &t): bool
			[recurse and quote
................................................................................
				self.ct = newct
				return true
			else return false end
		end;
	}
	return t
end)



















































lib.err = lib.loadlib('mbedtls','mbedtls/error.h')
lib.rsa = lib.loadlib('mbedtls','mbedtls/rsa.h')
lib.pk = lib.loadlib('mbedtls','mbedtls/pk.h')
lib.md = lib.loadlib('mbedtls','mbedtls/md.h')
lib.b64 = lib.loadlib('mbedtls','mbedtls/base64.h')
lib.net = lib.loadlib('mongoose','mongoose.h')
lib.pq = lib.loadlib('libpq','libpq-fe.h')


lib.crypt = terralib.loadfile('crypt.t')()
lib.http = terralib.loadfile('http.t')()
lib.tpl = terralib.loadfile('tpl.t')()
lib.string = terralib.loadfile('string.t')()
lib.store = terralib.loadfile('store.t')()







lib.cmdparse = terralib.loadfile('cmdparse.t')()


do local collate = function(path,f, ...)
	return loadfile(path..'/'..f..'.lua')(path, ...)
end
data = {
	view = collate('view','load');
} end
................................................................................
		lib.crypt.pem(pub, &kp, buf)
		lib.io.send(1, msg, [#msg])
		lib.io.send(1, [rawstring](&buf), lib.str.sz([rawstring](&buf)))
		lib.io.send(1, '\n', 1)
	end
end)

local handle = {
	http = terra(con: &lib.net.mg_connection, event: int, p: &opaque, ext: &opaque)
		switch event do
			case lib.net.MG_EV_HTTP_MSG then
				lib.dbg('routing HTTP request')
				var msg = [&lib.net.mg_http_message](p)

			end
		end
	end;
}
do
	local p = string.format('parsav: %s\nbuilt on %s\n', config.build.str, config.build.when)
	terra version() lib.io.send(1, p, [#p]) end
end
terra noise_init()
	var n = lib.proc.getenv('parsav_noise')
	if n ~= nil then
................................................................................
	help = {'h', 'display this list'}
}

terra entry(argc: int, argv: &rawstring): int
	noise_init()
	[lib.init]




	var mode: options
	mode:parse(argc,argv)
	if mode.version then
		version()
		return 0
	end
	if mode.help then
		lib.io.send(1,  [options.helptxt], [#options.helptxt])
		return 0
	end


	var bind = lib.proc.getenv('parsav_bind')
	if bind == nil then bind = '[::]:10917' end

	var nm: lib.net.mg_mgr
	lib.net.mg_mgr_init(&nm)

	var nmc = lib.net.mg_http_listen(&nm, bind, handle.http, nil)

	while true do
		lib.net.mg_mgr_poll(&nm,1000)

	end


	lib.net.mg_mgr_free(&nm)
	return 0
end

local bflag = function(long,short)
	if short and util.has(buildopts, short) then return true end
	if long and util.has(buildopts, long) then return true end
	return false







>
>
>
>
>
>
>





<
<









>


>



>
>
>
>
>
>







 







>
>
>
>
>
>
>


<
>









|





>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>






>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>








>
>





>
>
>
>
>
>
>

>







 







<
<
<
<
<
<
<
<
<
<
<







 







>
>
>










<
>
|
|
<
<
<
<
<
<

<
>

>

<







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
..
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
...
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
...
285
286
287
288
289
290
291











292
293
294
295
296
297
298
...
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329

330
331
332






333

334
335
336
337

338
339
340
341
342
343
344
				code[#code+1] = quote var n = v in
					lib.io.send(2, n, lib.str.sz(n)) end
			end
		end
		code[#code+1] = `lib.io.send(2, '\n', 1)
		return code
	end;
	trn = macro(function(cond, i, e)
		return quote
			var c: bool = [cond]
			var r: i.tree.type
			if c == true then r = i else r = e end
		in r end
	end);
	proc = {
		exit = terralib.externfunction('exit', int -> {});
		getenv = terralib.externfunction('getenv', rawstring -> rawstring);
	};
	io = {


		send = terralib.externfunction('write', {int, rawstring, intptr} -> ptrdiff);
		recv = terralib.externfunction('read',  {int, rawstring, intptr} -> ptrdiff);
		say = macro(function(msg) return `lib.io.send(2, msg, [#(msg:asvalue())]) end);
		fmt = terralib.externfunction('printf',
			terralib.types.funcpointer({rawstring},{int},true));
	};
	str = {
		sz = terralib.externfunction('strlen', rawstring -> intptr);
		cmp = terralib.externfunction('strcmp', {rawstring, rawstring} -> int);
		ncmp = terralib.externfunction('strncmp', {rawstring, rawstring, intptr} -> int);
		cpy = terralib.externfunction('stpcpy',{rawstring, rawstring} -> rawstring);
		ncpy = terralib.externfunction('stpncpy',{rawstring, rawstring, intptr} -> rawstring);
		ndup = terralib.externfunction('strndup',{rawstring, intptr} -> rawstring);
		fmt = terralib.externfunction('asprintf',
			terralib.types.funcpointer({&rawstring},{int},true));
	};
	copy = function(tbl)
		local new = {}
		for k,v in pairs(tbl) do new[k] = v end
		setmetatable(new, getmetatable(tbl))
		return new
	end;
	mem = {
		zero = macro(function(r)
			return quote
				for i = 0, [r.tree.type.N] do r[i] = 0 end
			end
		end);
		heapa_raw = terralib.externfunction('malloc', intptr -> &opaque);
................................................................................
				ct = sz;
			}
		end)
	};
}

local noise = global(uint8,1)
local noise_header = function(code,txt,mod)
	if mod then
		return string.format('\27[%s;1m(parsav::%s %s)\27[m ', code,mod,txt)
	else
		return string.format('\27[%s;1m(parsav %s)\27[m ', code,txt)
	end
end
local defrep = function(level,n,code)
	return macro(function(...)

		local q = lib.emit(noise_header(code,n), ...)
		return quote
			if noise >= level then [q] end
		end
	end);
end
lib.dbg = defrep(3,'debug', '32')
lib.report = defrep(2,'info', '35')
lib.warn = defrep(1,'warn', '33')
lib.bail = macro(function(...)
	local q = lib.emit(noise_header('31','fatal'), ...)
	return quote
		[q]
		lib.proc.exit(1)
	end
end);
lib.stat = terralib.memoize(function(ty)
	local n = struct {
		ok: bool
		union {
			error: uint8
			val: ty
		}
	}
	n.name = string.format("stat<%s>", ty.name)
	n.stat_basetype = ty
	return n
end)
lib.enum = function(tbl)
	local ty = uint8
	if #tbl >= 2^32 then ty = uint64 -- hey, can't be too safe
	elseif #tbl >= 2^16 then ty = uint32
	elseif #tbl >= 2^8 then ty = uint16 end
	local o = { t = ty }
	for i, name in ipairs(tbl) do
		o[name] = i
	end
	return o
end
lib.mem.ptr = terralib.memoize(function(ty)
	local t = terralib.types.newstruct(string.format('ptr<%s>', ty))
	t.entries = {
		{'ptr', &ty};
		{'ct', intptr};
	}
	t.ptr_basetype = ty
	local recurse = false
	if ty:isstruct() then
		if ty.methods.free then recurse = true end
	end
	t.methods = {
		free = terra(self: &t): bool
			[recurse and quote
................................................................................
				self.ct = newct
				return true
			else return false end
		end;
	}
	return t
end)
lib.mem.vec = terralib.memoize(function(ty)
	local v = terralib.types.newstruct(string.format('vec<%s>', ty.name))
	v.entries = {
		{field = 'storage', type = lib.mem.ptr(ty)};
		{field = 'sz', type = intptr};
		{field = 'run', type = intptr};
	}
	local terra biggest(a: intptr, b: intptr)
		if a > b then return a else return b end
	end
	terra v:assure(n: intptr)
		if self.storage.ct < n then
			self.storage:resize(biggest(n, self.storage.ct + self.run))
		end
	end
	v.methods = {
		init = terra(self: &v, run: intptr): bool
			if not self.storage:init(run) then return false end
			self.run = run
			self.sz = 0
			return true
		end;
		new = terra(self: &v): &ty
			self:assure(self.sz + 1)
			self.sz = self.sz + 1
			return self.storage.ptr + (self.sz - 1)
		end;
		push = terra(self: &v, val: ty)
			self:assure(self.sz + 1)
			self.storage.ptr[self.sz] = val
			self.sz = self.sz + 1
		end;
		free = terra(self: &v) self.storage:free() end;
		last = terra(self: &v, idx: intptr): &ty
			if self.sz > idx then
				return self.storage.ptr + (self.sz - (idx+1))
			else lib.bail('vector underrun!') end
		end;
		crush = terra(self: &v)
			self.storage:resize(self.sz)
			return self.storage
		end;
	}
	v.metamethods.__apply = terra(self: &v, idx: intptr): &ty -- no index??
		if self.sz > idx then
			return self.storage.ptr + idx
		else lib.bail('vector overrun!') end
	end
	return v 
end)

lib.err = lib.loadlib('mbedtls','mbedtls/error.h')
lib.rsa = lib.loadlib('mbedtls','mbedtls/rsa.h')
lib.pk = lib.loadlib('mbedtls','mbedtls/pk.h')
lib.md = lib.loadlib('mbedtls','mbedtls/md.h')
lib.b64 = lib.loadlib('mbedtls','mbedtls/base64.h')
lib.net = lib.loadlib('mongoose','mongoose.h')
lib.pq = lib.loadlib('libpq','libpq-fe.h')
lib.file = terralib.loadfile('file.t')()
lib.math = terralib.loadfile('math.t')()
lib.crypt = terralib.loadfile('crypt.t')()
lib.http = terralib.loadfile('http.t')()
lib.tpl = terralib.loadfile('tpl.t')()
lib.string = terralib.loadfile('string.t')()
lib.store = terralib.loadfile('store.t')()

local be = {}
for _, b in pairs { 'pgsql' } do
	be[#be+1] = terralib.loadfile('backend/' .. b .. '.t')()
end
lib.store.backends = global(`array([be]))

lib.cmdparse = terralib.loadfile('cmdparse.t')()
lib.srv = terralib.loadfile('srv.t')()

do local collate = function(path,f, ...)
	return loadfile(path..'/'..f..'.lua')(path, ...)
end
data = {
	view = collate('view','load');
} end
................................................................................
		lib.crypt.pem(pub, &kp, buf)
		lib.io.send(1, msg, [#msg])
		lib.io.send(1, [rawstring](&buf), lib.str.sz([rawstring](&buf)))
		lib.io.send(1, '\n', 1)
	end
end)












do
	local p = string.format('parsav: %s\nbuilt on %s\n', config.build.str, config.build.when)
	terra version() lib.io.send(1, p, [#p]) end
end
terra noise_init()
	var n = lib.proc.getenv('parsav_noise')
	if n ~= nil then
................................................................................
	help = {'h', 'display this list'}
}

terra entry(argc: int, argv: &rawstring): int
	noise_init()
	[lib.init]

	-- shut mongoose the fuck up
	lib.net.mg_log_set_callback([terra(msg: &opaque, sz: int, u: &opaque) end], nil)

	var mode: options
	mode:parse(argc,argv)
	if mode.version then
		version()
		return 0
	end
	if mode.help then
		lib.io.send(1,  [options.helptxt], [#options.helptxt])
		return 0
	end

	var srv: lib.srv
	srv:start('backend.conf')
	lib.report('listening for requests')






	while true do

		srv:poll()
	end
	srv:shutdown()


	return 0
end

local bflag = function(long,short)
	if short and util.has(buildopts, short) then return true end
	if long and util.has(buildopts, long) then return true end
	return false

Added schema.sql version [2da83887bf].











































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
\prompt 'domain name: ' domain
\prompt 'bind to socket: ' bind
\qecho 'by default, parsav tracks rights on its own. you can override this later by replacing the rights table with a view, but you''ll then need to set appropriate rules on the view to allow administrators to modify rights from the web UI, or set the rights-readonly flag in the config table to true. for now, enter the name of an actor who will be granted full rights when she logs in.'
\prompt 'admin actor: ' admin
\qecho 'you will need to create an authentication view mapping your user database to something parsav can understand; see auth.sql for an example. enter the name of the view to use.'
\prompt 'auth view: ' auth

begin;

drop table if exists parsav_config;
create table if not exists parsav_config (
	key text primary key,
	value text
);

insert into parsav_config (key,value) values
	('bind',:'bind'),
	('domain',:'domain'),
	('auth-source',:'auth'),
	('administrator',:'admin');

-- note that valid ids should always > 0, as 0 is reserved for null
-- on the client side, vastly simplifying code
drop table if exists parsav_servers cascade;
create table parsav_servers (
	id bigint primary key default (1+random()*(2^63-1))::bigint,
	domain text not null,
	key bytea
);
drop table if exists parsav_actors cascade;
create table parsav_actors (
	id bigint primary key default (1+random()*(2^63-1))::bigint,
	nym text,
	handle text not null, -- nym [@handle@origin] 
	origin bigint references parsav_servers(id)
		on delete cascade, -- null origin = local actor
	bio text,
	rank smallint not null default 0,
	quota integer not null default 1000,
	key bytea, -- private if localactor; public if remote
	
	unique (handle,origin)
);

drop table if exists parsav_rights cascade;
create table parsav_rights (
	key text,
	actor bigint references parsav_actors(id)
		on delete cascade,
	allow boolean,

	primary key (key,actor)
);

insert into parsav_actors (handle,rank,quota) values (:'admin',1,0);
insert into parsav_rights (actor,key,allow)
	select (select id from parsav_actors where handle=:'admin'), a.column1, a.column2 from (values
		('ban',true),
		('config',true),
		('censor',true),
		('suspend',true),
		('rebrand',true)
	) as a;

drop table if exists parsav_posts cascade;
create table parsav_posts (
	id bigint primary key default (1+random()*(2^63-1))::bigint,
	author bigint references parsav_actors(id)
		on delete cascade,
	subject text,
	body text,
	posted timestamp not null,
	discovered timestamp not null,
	scope smallint not null,
	convo bigint, parent bigint,
	circles bigint[], mentions bigint[]
);

drop table if exists parsav_conversations cascade;
create table parsav_conversations (
	id bigint primary key default (1+random()*(2^63-1))::bigint,
	uri text not null,
	discovered timestamp not null,
	head bigint references parsav_posts(id)
);

drop table if exists parsav_rels cascade;
create table parsav_rels (
	relator bigint references parsav_actors(id)
		on delete cascade, -- e.g. follower
	relatee bigint references parsav_actors(id)
		on delete cascade, -- e.g. follower
	kind smallint, -- e.g. follow, block, mute

	primary key (relator, relatee, kind)
);

drop table if exists parsav_acts cascade;
create table parsav_acts (
	id bigint primary key default (1+random()*(2^63-1))::bigint,
	kind text not null, -- like, react, so on
	time timestamp not null,
	actor bigint references parsav_actors(id)
		on delete cascade,
	subject bigint -- may be post or act, depending on kind
);

drop table if exists parsav_log cascade;
create table parsav_log (
	-- accesses are tracked for security & sending delete acts
	id bigint primary key default (1+random()*(2^63-1))::bigint,
	time timestamp not null,
	actor bigint references parsav_actors(id)
		on delete cascade,
	post bigint not null
);
end;

Added srv.t version [350801ad24].















































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
174
175
176
177
178
179
180
181
182
183
-- vim: ft=terra
local util = dofile 'common.lua'
local struct srv {
	sources: lib.mem.ptr(lib.store.source)
	webmgr: lib.net.mg_mgr
	webcon: &lib.net.mg_connection
}

local handle = {
	http = terra(con: &lib.net.mg_connection, event: int, p: &opaque, ext: &opaque)
		switch event do
			case lib.net.MG_EV_HTTP_MSG then
				lib.dbg('routing HTTP request')
				var msg = [&lib.net.mg_http_message](p)

			end
		end
	end;
}
local char = macro(function(ch) return `[string.byte(ch:asvalue())] end)
local terra cfg(s: &srv, befile: rawstring)
	lib.report('configuring backends from ', befile)

	var fr = lib.file.open(befile, [lib.file.mode.read])
	if fr.ok == false then
		lib.bail('could not open configuration file ', befile)
	end

	var f = fr.val
	var c: lib.mem.vec(lib.store.source) c:init(8)
	var text: lib.string.acc text:init(64)
	do var buf: int8[64]
		while true do
			var ct = f:read(buf, [buf.type.N])
			if ct == 0 then break end
			text:push(buf, ct)
		end
	end
	f:close()

	var cur = text.buf
	var segs: tuple(&int8, &int8)[3] = array(
		{[&int8](0),[&int8](0)},
		{[&int8](0),[&int8](0)},
		{[&int8](0),[&int8](0)}
	)
	var segdup = [terra(s: {rawstring, rawstring})
		var sz = s._1 - s._0
		var str = s._0
		return [lib.mem.ptr(int8)] {
			ptr = lib.str.ndup(str, sz);
			ct = sz;
		}
	end]
	var fld = 0
	while (cur - text.buf) < text.sz do
		if segs[fld]._0 == nil then
			if not (@cur == char(' ') or @cur == char('\t') or @cur == char('\n')) then
				segs[fld] = {cur, nil}
			end
		else
			if fld < 2 and @cur == char(' ') or @cur == char('\t') then
				segs[fld]._1 = cur
				fld = fld + 1
				segs[fld] = {nil, nil}
			elseif @cur == char('\n') or cur == text.buf + (text.sz-1) then
				if fld < 2 then lib.bail('incomplete backend line in ', befile) else
					segs[fld]._1 = cur
					var src = c:new()
					src.id = segdup(segs[0])
					src.string = segdup(segs[2])
					src.backend = nil
					for i = 0,[lib.store.backends.type.N] do
						if lib.str.ncmp(segs[1]._0, lib.store.backends[i].id, segs[1]._1 - segs[1]._0) == 0 then
							src.backend = &lib.store.backends[i]
							break
						end
					end
					if src.backend == nil then
						lib.bail('unknown backend in ', befile)
					end
					src.handle = nil
					fld = 0
					segs[0] = {nil, nil}
				end
			end
		end
		cur = cur + 1
	end
	text:free()

	s.sources = c:crush()
end

--srv.methods.conf_set = terra(self: &srv, key: rawstring, val:rawstring)
--	self.sources.ptr[0]:conf_set(key, val)
--end

srv.metamethods.__methodmissing = macro(function(meth, self, ...)
	local primary, ptr, stat, simple = 0,1,2,3
	local tk, rt = primary
	local expr = {...}
	for _,f in pairs(lib.store.backend.entries) do
		local fn = f.field or f[1]
		local ft = f.type or f[2]
		if fn == meth then
			rt = ft.type.returntype
			if rt == bool then tk = simple
			elseif rt.stat_basetype then tk = stat
			elseif rt.ptr_basetype then tk = ptr end
			break
		end
	end
	
	if tk == primary then
		return `self.sources.ptr[0]:[meth]([expr])
	else local ok, empty
		local r = symbol(rt)
		if tk == ptr then
			ok = `r.ptr ~= nil
			empty = `[rt]{ptr=nil,ct=0}
		elseif tk == stat then
			ok = `r.ok ~= false
			empty = `[rt]{ok=false,error=1}
		elseif tk == simple then
			ok = `r == true
			empty = `false
		end
		return quote
			var [r] = empty
			for i=0,self.sources.ct do var src = self.sources.ptr + i
				if src.handle ~= nil then
					r = src:[meth]([expr])
					if [ok] then break
						else r = empty end
				end
			end
		in r end
	end
end)

srv.methods.start = terra(self: &srv, befile: rawstring)
	cfg(self, befile)
	var success = false
	for i=0,self.sources.ct do var src = self.sources.ptr + i
		lib.report('opening data source ', src.id.ptr, '(', src.backend.id, ')')
		src.handle = src.backend.open(src)
		if src.handle ~= nil then success = true end
	end
	if not success then
		lib.bail('could not connect to any data sources!')
	end

	var dbbind = self:conf_get('bind')
	var envbind = lib.proc.getenv('parsav_bind')
	var bind: rawstring
	if envbind ~= nil then
		bind = envbind
	elseif dbbind.ptr ~= nil then
		bind = dbbind.ptr
	else bind = '[::]:10917' end

	lib.report('binding to ', bind)
	lib.net.mg_mgr_init(&self.webmgr)
	self.webcon = lib.net.mg_http_listen(&self.webmgr, bind, handle.http, nil)
	dbbind:free()

end

srv.methods.poll = terra(self: &srv)
	lib.net.mg_mgr_poll(&self.webmgr,1000)
end

srv.methods.shutdown = terra(self: &srv)
	lib.net.mg_mgr_free(&self.webmgr)
	for i=0,self.sources.ct do var src = self.sources.ptr + i
		lib.report('closing data source ', src.id.ptr, '(', src.backend.id, ')')
		src:close()
	end
	self.sources:free()
end

return srv

Modified store.t from [a0814e5c61] to [ed1d490f12].

1
2
3

4
5

6


7



8




9
10




































11
12




13










14



15

























































































-- vim: ft=terra
local m = {}


local backend = {
	pgsql = {

	};


}








struct m.user {
	uid: rawstring




































	nym: rawstring
	handle: rawstring















	localuser: bool



}


























































































|
<
>
|
|
>

>
>
|
>
>
>
|
>
>
>
>
|
<
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
>
>
>
>

>
>
>
>
>
>
>
>
>
>
|
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
-- vim: ft=terra
local m = {

	timepoint = uint64;
	scope = lib.enum {
		'public', 'private', 'local';
		'personal', 'direct', 'circle';
	};
	notiftype = lib.enum {
		'mention', 'like', 'rt', 'react'
	};
	relation = lib.enum {
		'follow', 'mute', 'block'
	};
}

local str = lib.mem.ptr(int8)
str:complete()

struct m.source


struct m.rights {
	rank: uint16 -- lower = more powerful except 0 = regular user
	-- creating staff automatically assigns rank immediately below you
	quota: uint32 -- # of allowed tweets per day; 0 = no limit
	
	-- user powers -- default on
	login: bool
	visible: bool
	post: bool
	shout: bool
	propagate: bool
	upload: bool

	-- admin powers -- default off
	ban: bool
	config: bool
	censor: bool
	suspend: bool
	rebrand: bool -- modify site's brand identity
}

terra m.rights_default()
	return m.rights {
		rank = 0, quota = 1000;
		
		login = true, visible = true, post = true;
		shout = true, propagate = true, upload = true;

		ban = false, config = false, censor = false;
		suspend = false, rebrand = false;
	}
end

struct m.actor {
	id: uint64
	nym: str
	handle: str
	origin: uint64
	bio: str
	rights: m.rights
	key: str

	source: &m.source
}
terra m.actor:free()
	self.nym:free()
	self.handle:free()
	self.bio:free()
	self.key:free()
end

struct m.range {
	time: bool
	union {
		from_time: m.timepoint
		from_idx: uint64
	}
	union {
		to_time: m.timepoint
		to_idx: uint64
	}
}

struct m.post {
	id: uint64
	author: uint64
	subject: str
	body: str
	posted: m.timepoint
	discovered: m.timepoint
	scope: m.scope.t
	mentions: lib.mem.ptr(uint64)
	circles: lib.mem.ptr(uint64) --only meaningful if scope is set to circle
	convo: uint64
	parent: uint64

	source: &m.source
}

local cnf = terralib.memoize(function(ty,rty)
	rty = rty or ty
	return struct {
		enum: {&opaque, uint64, rawstring} -> intptr
		get: {&opaque, uint64, rawstring} -> rty
		set: {&opaque, uint64, rawstring, ty} -> {}
		reset: {&opaque, uint64, rawstring} -> {}
	}
end)

struct m.notif {
	kind: m.notiftype.t
	when: uint64
	union {
		post: uint64
		reaction: int8[8]
	}
}

-- backends only handle content on the local server
struct m.backend { id: rawstring
	open: &m.source -> &opaque
	close: &m.source -> {}

	conf_get: {&m.source, rawstring} -> lib.mem.ptr(int8)
	conf_set: {&m.source, rawstring, rawstring} -> {}
	conf_reset: {&m.source, rawstring} -> {}

	actor_save: {&m.source, m.actor} -> bool
	actor_create: {&m.source, m.actor} -> bool
	actor_fetch_xid: {&m.source, rawstring} -> lib.stat(m.actor)
	actor_fetch_uid: {&m.source, uint64} -> lib.stat(m.actor)
	actor_notif_fetch_uid: {&m.source, uint64} -> lib.mem.ptr(m.notif)
	actor_auth: {&m.source, rawstring, rawstring} -> lib.stat(m.actor)
	actor_enum: {&m.source} -> lib.mem.ptr(m.actor)
	actor_enum_local: {&m.source} -> lib.mem.ptr(m.actor)

	actor_conf_str: cnf(rawstring, lib.mem.ptr(int8))
	actor_conf_int: cnf(intptr, lib.stat(intptr))

	post_save: {&m.source, &m.post} -> bool
	post_create: {&m.source, &m.post} -> bool
	actor_post_fetch_uid: {&m.source, uint64, m.range} -> lib.mem.ptr(m.post)
	convo_fetch_xid: {&m.source,rawstring} -> lib.mem.ptr(m.post)
	convo_fetch_uid: {&m.source,uint64} -> lib.mem.ptr(m.post)

	actor_timeline_fetch_uid: {&m.source, uint64, m.range} -> lib.mem.ptr(m.post)
	instance_timeline_fetch: {&m.source, m.range} -> lib.mem.ptr(m.post)
}

struct m.source {
	backend: &m.backend
	id: lib.mem.ptr(int8)
	handle: &opaque
	string: lib.mem.ptr(int8)
}
terra m.source:free()
	self.id:free()
	self.string:free()
end
m.source.metamethods.__methodmissing = macro(function(meth, obj, ...)
	local q = {...}
	-- syntax sugar to forward unrecognized calls onto the backend
	return `obj.backend.[meth](&obj, [q])
end)

return m