parsav  Check-in [f9559a83fc]

Overview
Comment:iteration and important api adjustments
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: f9559a83fc2d82b62dc5aed0a13e9802bcccda3ccb9457b98a28439d31d425cb
User & Date: lexi on 2020-12-25 23:37:28
Other Links: manifest | tags
Context
2020-12-27
02:31
permissions work now check-in: bbfea467bf user: lexi tags: trunk
2020-12-25
23:37
iteration and important api adjustments check-in: f9559a83fc user: lexi tags: trunk
03:59
big ol iteration check-in: 5b3a03ad34 user: lexi tags: trunk
Changes

Added acl.t version [3939510d0a].



>
1
-- vim: ft=terra

Modified backend/pgsql.t from [0ea1a47601] to [1740166796].

1


2
3
4
5
6
7
8
..
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
..
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
..
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
...
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
...
156
157
158
159
160
161
162
163



































164
165
166
167
168
169
170
...
278
279
280
281
282
283
284




285
286
287
288
289
290
291
292
293
294
295
296
297
...
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
345
346
347
...
550
551
552
553
554
555
556
557










558



















559
-- vim: ft=terra


local queries = {
	conf_get = {
		params = {rawstring}, sql = [[
			select value from parsav_config
				where key = $1::text limit 1
		]];
	};
................................................................................
			delete from parsav_config where
				key = $1::text 
		]];
	};

	actor_fetch_uid = {
		params = {uint64}, sql = [[
			select
				id, nym, handle, origin, bio,
				avataruri, rank, quota, key,
				extract(epoch from knownsince)::bigint



			from parsav_actors


				where id = $1::bigint
		]];
	};

	actor_fetch_xid = {
		params = {lib.mem.ptr(int8)}, sql = [[
			select a.id, a.nym, a.handle, a.origin, a.bio,
			       a.avataruri, a.rank, a.quota, a.key, 
			       extract(epoch from knownsince)::bigint,
				   coalesce(a.handle || '@' || s.domain,
				            '@' || a.handle) as xid,

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

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

	actor_auth_pw = {
		params = {lib.mem.ptr(int8),rawstring,lib.mem.ptr(int8),lib.store.inet}, sql = [[
			select a.aid from parsav_auth as a
				left join parsav_actors as u on u.id = a.uid
			where (a.uid is null or u.handle = $1::text or (
					a.uid = 0 and a.name = $1::text
				)) and
				(a.kind = 'trust' or (a.kind = $2::text and a.cred = $3::bytea)) and
				(a.netmask is null or a.netmask >> $4::inet)
................................................................................
		]];
	};

	actor_enum = {
		params = {}, sql = [[
			select a.id, a.nym, a.handle, a.origin, a.bio,
			       a.avataruri, a.rank, a.quota, a.key,
			       extract(epoch from knownsince)::bigint,
				   coalesce(a.handle || '@' || s.domain,
				            '@' || a.handle) as xid
			from parsav_actors a
			left join parsav_servers s on s.id = a.origin
		]];
	};

................................................................................
		]]; -- cheat
	};

	actor_session_fetch = {
		params = {uint64, lib.store.inet}, sql = [[
			select a.id, a.nym, a.handle, a.origin, a.bio,
			       a.avataruri, a.rank, a.quota, a.key,
			       extract(epoch from knownsince)::bigint,
				   coalesce(a.handle || '@' || s.domain,
				            '@' || a.handle) as xid,

			       au.restrict,
						array['post'  ] <@ au.restrict as can_post,
						array['edit'  ] <@ au.restrict as can_edit,
						array['acct'  ] <@ au.restrict as can_acct,
................................................................................
			left join parsav_actors a     on au.uid = a.id
			left join parsav_servers s    on a.origin = s.id

			where au.aid = $1::bigint and au.blacklist = false and
				(au.netmask is null or au.netmask >> $2::inet)
		]];
	};
}




































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)
................................................................................
				;[pqt[lib.store.inet](false)]([args[i]], [&uint8](&ipbuf))
			in &ipbuf[0] end
			dumpers[#dumpers+1] = `lib.io.fmt([tostring(i)..'. got inet\n'])
		elseif ty.ptr_basetype == int8 or ty.ptr_basetype == uint8 then
			counters[i] = `[args[i]].ct
			casts[i] = `[&int8]([args[i]].ptr)
			dumpers[#dumpers+1] = `lib.io.fmt([tostring(i)..'. got ptr %llu %.*s\n'], [args[i]].ct, [args[i]].ct, [args[i]].ptr)




		elseif ty:isintegral() then
			counters[i] = ty.bytes
			casts[i] = `[&int8](&[args[i]])
			dumpers[#dumpers+1] = `lib.io.fmt([tostring(i)..'. got int %llu\n'], [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

	terra q.exec(src: &lib.store.source, [args])
		var params = arrayof([&int8], [casts])
................................................................................
			return pqr {0, nil}
		else
			return pqr {ct, res}
		end
	end
end

local terra row_to_actor(r: &pqr, row: intptr): lib.mem.ptr(lib.store.actor)
	var a: lib.mem.ptr(lib.store.actor)


	if r:cols() >= 9 then 




		a = [ lib.str.encapsulate(lib.store.actor, {
			nym = {`r:string(row,1), `r:len(row,1)+1};

			bio = {`r:string(row,4), `r:len(row,4)+1};
			avatar = {`r:string(row,5), `r:len(row,5)+1};
			handle = {`r:string(row, 2); `r:len(row,2) + 1};
			xid = {`r:string(row, 10); `r:len(row,10) + 1};

		}) ]
	else













		a = [ lib.str.encapsulate(lib.store.actor, {




			nym = {`r:string(row,1), `r:len(row,1)+1};



			bio = {`r:string(row,4), `r:len(row,4)+1};



			avatar = {`r:string(row,5), `r:len(row,5)+1};






			handle = {`r:string(row, 2); `r:len(row,2) + 1};

		}) ]
		a.ptr.xid = nil
	end
	a.ptr.id = r:int(uint64, row, 0);
	a.ptr.rights = lib.store.rights_default();
	a.ptr.rights.rank = r:int(uint16, row, 6);
	a.ptr.rights.quota = r:int(uint32, row, 7);
	a.ptr.knownsince = r:int(int64,row, 9);
	if r:null(row,8) then
		a.ptr.key.ct = 0 a.ptr.key.ptr = nil
................................................................................

			return au, a
		end

		::fail:: return [lib.stat   (lib.store.auth) ] { ok = false        },
			            [lib.mem.ptr(lib.store.actor)] { ptr = nil, ct = 0 }
	end];
}






























return b

>
>







 







|
|
<
|
>
>

|
>
>
|




|


|







 







|







 







|







 







|







 







|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>
>
>
>





<







 







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







 







|
>
>
>
>
>
>
>
>
>
>

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

1
2
3
4
5
6
7
8
9
10
..
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
..
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
..
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
...
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
...
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
204
205
206
207
208
209
210
...
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333

334
335
336
337
338
339
340
...
357
358
359
360
361
362
363
364
365

366
367
368
369
370
371
372

373
374
375


376
377

378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413


414
415
416
417
418
419
420
...
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
-- vim: ft=terra
local pstring = lib.mem.ptr(int8)
local binblob = lib.mem.ptr(uint8)
local queries = {
	conf_get = {
		params = {rawstring}, sql = [[
			select value from parsav_config
				where key = $1::text limit 1
		]];
	};
................................................................................
			delete from parsav_config where
				key = $1::text 
		]];
	};

	actor_fetch_uid = {
		params = {uint64}, sql = [[
			select a.id, a.nym, a.handle, a.origin, a.bio,
			       a.avataruri, a.rank, a.quota, a.key, 

			       extract(epoch from a.knownsince)::bigint,
				   coalesce(a.handle || '@' || s.domain,
				            '@' || a.handle) as xid

			from      parsav_actors  as a
			left join parsav_servers as s
				on a.origin = s.id
			where a.id = $1::bigint
		]];
	};

	actor_fetch_xid = {
		params = {pstring}, sql = [[
			select a.id, a.nym, a.handle, a.origin, a.bio,
			       a.avataruri, a.rank, a.quota, a.key, 
			       extract(epoch from a.knownsince)::bigint,
				   coalesce(a.handle || '@' || s.domain,
				            '@' || a.handle) as xid,

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

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

	actor_auth_pw = {
		params = {pstring,rawstring,pstring,lib.store.inet}, sql = [[
			select a.aid from parsav_auth as a
				left join parsav_actors as u on u.id = a.uid
			where (a.uid is null or u.handle = $1::text or (
					a.uid = 0 and a.name = $1::text
				)) and
				(a.kind = 'trust' or (a.kind = $2::text and a.cred = $3::bytea)) and
				(a.netmask is null or a.netmask >> $4::inet)
................................................................................
		]];
	};

	actor_enum = {
		params = {}, sql = [[
			select a.id, a.nym, a.handle, a.origin, a.bio,
			       a.avataruri, a.rank, a.quota, a.key,
			       extract(epoch from a.knownsince)::bigint,
				   coalesce(a.handle || '@' || s.domain,
				            '@' || a.handle) as xid
			from parsav_actors a
			left join parsav_servers s on s.id = a.origin
		]];
	};

................................................................................
		]]; -- cheat
	};

	actor_session_fetch = {
		params = {uint64, lib.store.inet}, sql = [[
			select a.id, a.nym, a.handle, a.origin, a.bio,
			       a.avataruri, a.rank, a.quota, a.key,
			       extract(epoch from a.knownsince)::bigint,
				   coalesce(a.handle || '@' || s.domain,
				            '@' || a.handle) as xid,

			       au.restrict,
						array['post'  ] <@ au.restrict as can_post,
						array['edit'  ] <@ au.restrict as can_edit,
						array['acct'  ] <@ au.restrict as can_acct,
................................................................................
			left join parsav_actors a     on au.uid = a.id
			left join parsav_servers s    on a.origin = s.id

			where au.aid = $1::bigint and au.blacklist = false and
				(au.netmask is null or au.netmask >> $2::inet)
		]];
	};

	post_create = {
		params = {uint64, rawstring, rawstring, rawstring}, sql = [[
			insert into parsav_posts (
				author, subject, acl, body,
				posted, discovered,
				circles, mentions
			) values (
				$1::bigint, case when $2::text = '' then null else $2::text end,
				$3::text, $4::text, 
				now(), now(), array[]::bigint[], array[]::bigint[]
			) returning id
		]]; -- TODO array handling
	};

	instance_timeline_fetch = {
		params = {uint64, uint64, uint64, uint64}, sql = [[
			select true,
				p.id, p.author, p.subject, p.acl, p.body,
				extract(epoch from p.posted    )::bigint,
				extract(epoch from p.discovered)::bigint,
				p.parent, null::text
			from parsav_posts as p
				inner join parsav_actors as a on p.author = a.id
			where
				($1::bigint = 0 or p.posted <= to_timestamp($1::bigint)) and
				($2::bigint = 0 or to_timestamp($2::bigint) < p.posted) and
				(a.origin is null)
			order by (p.posted, p.discovered) desc
			limit case when $3::bigint = 0 then null
			           else $3::bigint end
			offset $4::bigint
		]]
	};
}
				--($5::bool = false or p.parent is null) and

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)
................................................................................
				;[pqt[lib.store.inet](false)]([args[i]], [&uint8](&ipbuf))
			in &ipbuf[0] end
			dumpers[#dumpers+1] = `lib.io.fmt([tostring(i)..'. got inet\n'])
		elseif ty.ptr_basetype == int8 or ty.ptr_basetype == uint8 then
			counters[i] = `[args[i]].ct
			casts[i] = `[&int8]([args[i]].ptr)
			dumpers[#dumpers+1] = `lib.io.fmt([tostring(i)..'. got ptr %llu %.*s\n'], [args[i]].ct, [args[i]].ct, [args[i]].ptr)
		elseif ty.ptr_basetype == bool then
			counters[i] = `1
			casts[i] = `[&int8]([args[i]].ptr)
			-- dumpers[#dumpers+1] = `lib.io.fmt([tostring(i)..'. got bool = %hhu\n'], @[args[i]].ptr)
		elseif ty:isintegral() then
			counters[i] = ty.bytes
			casts[i] = `[&int8](&[args[i]])
			dumpers[#dumpers+1] = `lib.io.fmt([tostring(i)..'. got int %llu\n'], [args[i]])
			fixers[#fixers + 1] = quote

				[args[i]] = lib.math.netswap(ty, [args[i]])
			end
		end
	end

	terra q.exec(src: &lib.store.source, [args])
		var params = arrayof([&int8], [casts])
................................................................................
			return pqr {0, nil}
		else
			return pqr {ct, res}
		end
	end
end

local terra row_to_post(r: &pqr, row: intptr): lib.mem.ptr(lib.store.post)
	--lib.io.fmt("body ptr %p  len %llu\n", r:string(row,5), r:len(row,5))

	--lib.io.fmt("acl ptr %p  len %llu\n", r:string(row,4), r:len(row,4))
	var subj: rawstring, sblen: intptr
	if r:null(row,3)
		then subj = nil sblen = 0
		else subj = r:string(row,3) sblen = r:len(row,3)+1
	end
	var p = [ lib.str.encapsulate(lib.store.post, {

		subject = { `subj, `sblen };
		acl = {`r:string(row,4), `r:len(row,4)+1};
		body = {`r:string(row,5), `r:len(row,5)+1};


		--convoheaduri = { `nil, `0 }; --FIXME
	}) ]

	p.ptr.id = r:int(uint64,row,1)
	p.ptr.author = r:int(uint64,row,2)
	p.ptr.posted = r:int(uint64,row,6)
	p.ptr.discovered = r:int(uint64,row,7)
	if r:null(row,8)
		then p.ptr.parent = 0
		else p.ptr.parent = r:int(uint64,row,8)
	end 
	p.ptr.localpost = r:bool(row,0)

	return p
end
local terra row_to_actor(r: &pqr, row: intptr): lib.mem.ptr(lib.store.actor)
	var a: lib.mem.ptr(lib.store.actor)
	var av: rawstring, avlen: intptr
	var nym: rawstring, nymlen: intptr
	var bio: rawstring, biolen: intptr
	if r:null(row,5) then avlen = 0 av = nil else
		av = r:string(row,5)
		avlen = r:len(row,5)+1
	end
	if r:null(row,1) then nymlen = 0 nym = nil else
		nym = r:string(row,1)
		nymlen = r:len(row,1)+1
	end
	if r:null(row,4) then biolen = 0 bio = nil else
		bio = r:string(row,4)
		biolen = r:len(row,4)+1
	end
	a = [ lib.str.encapsulate(lib.store.actor, {
		nym = {`nym, `nymlen};
		bio = {`bio, `biolen};
		avatar = {`av,`avlen};
		handle = {`r:string(row, 2); `r:len(row,2) + 1};
		xid = {`r:string(row, 10); `r:len(row,10) + 1};
	}) ]


	a.ptr.id = r:int(uint64, row, 0);
	a.ptr.rights = lib.store.rights_default();
	a.ptr.rights.rank = r:int(uint16, row, 6);
	a.ptr.rights.quota = r:int(uint32, row, 7);
	a.ptr.knownsince = r:int(int64,row, 9);
	if r:null(row,8) then
		a.ptr.key.ct = 0 a.ptr.key.ptr = nil
................................................................................

			return au, a
		end

		::fail:: return [lib.stat   (lib.store.auth) ] { ok = false        },
			            [lib.mem.ptr(lib.store.actor)] { ptr = nil, ct = 0 }
	end];

	post_create = [terra(
		src: &lib.store.source,
		post: &lib.store.post
	): uint64
		var r = queries.post_create.exec(src,post.author,post.subject,post.acl,post.body) 
		if r.sz == 0 then return 0 end
		defer r:free()
		var id = r:int(uint64,0,0)
		return id
	end];

	instance_timeline_fetch = [terra(src: &lib.store.source, rg: lib.store.range)
		var r = pqr { sz = 0 }
		if rg.mode == 0 then
			r = queries.instance_timeline_fetch.exec(src,rg.from_time,rg.to_time,0,0)
		elseif rg.mode == 1 then
			r = queries.instance_timeline_fetch.exec(src,rg.from_time,0,rg.to_idx,0)
		elseif rg.mode == 2 then
			r = queries.instance_timeline_fetch.exec(src,0,rg.to_time,0,rg.from_idx)
		elseif rg.mode == 3 then
			r = queries.instance_timeline_fetch.exec(src,0,0,rg.to_idx,rg.from_idx)
		end
		
		var ret: lib.mem.ptr(lib.mem.ptr(lib.store.post)) ret:init(r.sz)
		for i=0,r.sz do ret.ptr[i] = row_to_post(&r, i) end -- MUST FREE ALL

		return ret
	end];
}

return b

Modified mem.t from [e41dd1991a] to [15de92d877].

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
..
43
44
45
46
47
48
49

50
51
52
53
54
55
56
...
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
m.heapa = macro(function(ty, sz)
	local p = m.ptr(ty:astype())
	return `p {
		ptr = [&ty:astype()](m.heapa_raw(sizeof(ty) * sz));
		ct = sz;
	}
end)



























local function mkptr(ty, dyn)
	local t = terralib.types.newstruct(string.format('%s<%s>', dyn and 'ptr' or 'ref', 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.metamethods.__not = macro(function(self)
		return `self.ptr
	end)
	if dyn then
		t.methods = {
			free = terra(self: &t): bool
				[recurse and quote
................................................................................
					m.heapf(self.ptr)
					self.ct = 0
					return true
				end
				return false
			end;
			init = terra(self: &t, newct: intptr): bool

				var nv = [&ty](m.heapa_raw(sizeof(ty) * newct))
				if nv ~= nil then
					self.ptr = nv
					self.ct = newct
					return true
				else return false end
			end;
................................................................................
		{field = 'storage', type = m.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)

return m







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









|
|
|







 







>







 







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









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
..
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
...
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
m.heapa = macro(function(ty, sz)
	local p = m.ptr(ty:astype())
	return `p {
		ptr = [&ty:astype()](m.heapa_raw(sizeof(ty) * sz));
		ct = sz;
	}
end)

function m.cache(ty,sz)
	sz = sz or 32
	local struct c {
		store: ty[sz]
		top: intptr
		cur: intptr
	}
	c.name = string.format('cache<%s,%u>', tostring(ty), sz)
	terra c:insert(v: ty)
		if [ty.ptr_basetype ~= nil] then
			if self.cur < self.top then self.store[self.cur]:free() end
		end
		self.store[self.cur] = v
		self.top = lib.math.biggest(self.top, self.cur + 1)
		self.cur = (self.cur + 1) % sz
		return v
	end
	c.metamethods.__apply = terra(self: &c, idx: intptr) return &self.store[idx] end
	if ty.ptr_basetype then
		terra c:free()
			for i=0,self.top do self.store[i]:free() end
		end
	end
	return c
end

local function mkptr(ty, dyn)
	local t = terralib.types.newstruct(string.format('%s<%s>', dyn and 'ptr' or 'ref', 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.metamethods.__not = macro(function(self)
		return `self.ptr
	end)
	if dyn then
		t.methods = {
			free = terra(self: &t): bool
				[recurse and quote
................................................................................
					m.heapf(self.ptr)
					self.ct = 0
					return true
				end
				return false
			end;
			init = terra(self: &t, newct: intptr): bool
				if newct == 0 then self.ct = 0 self.ptr = nil return false end
				var nv = [&ty](m.heapa_raw(sizeof(ty) * newct))
				if nv ~= nil then
					self.ptr = nv
					self.ct = newct
					return true
				else return false end
			end;
................................................................................
		{field = 'storage', type = m.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:init(run: intptr): bool
		if not self.storage:init(run) then return false end
		self.run = run
		self.sz = 0
		return true
	end;

	terra v:assure(n: intptr)
		if self.storage.ct < n then
			self.storage:resize(biggest(n, self.storage.ct + self.run))
		end
	end
	terra v:new(): &ty
		self:assure(self.sz + 1)
		self.sz = self.sz + 1
		return self.storage.ptr + (self.sz - 1)
	end;

	terra v:push(val: ty)
		self:assure(self.sz + 1)
		self.storage.ptr[self.sz] = val
		self.sz = self.sz + 1
	end;
	terra v:free() self.storage:free() end;
	terra v:last(idx: intptr): &ty
		if self.sz > idx then
			return self.storage.ptr + (self.sz - (idx+1))
		else lib.bail('vector underrun!') end
	end;

	terra v:crush()
		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)

return m

Modified parsav.t from [11ad9b3025] to [579976ae23].

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
...
285
286
287
288
289
290
291


292
293
294
295
296
297
298
...
324
325
326
327
328
329
330


331
332
333
334
335
336
337
...
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
			if c == true then r = i else r = e end
		in r end
	end);
	coalesce = macro(function(...)
		local args = {...}
		local ty = args[1].tree.type
		local val = symbol(ty)
		local empty if ty.type == 'integer'

			then empty = `0
			else empty = `nil
		end
		local exp = quote val = [empty] end

		for i=#args, 1, -1 do
			local v = args[i]

			exp = quote







				if [v] ~= [empty]
					then val = v
					else [exp]

				end
			end
		end

		local q = quote
			var [val]
			[exp]
................................................................................
lib.b64 = lib.loadlib('mbedtls','mbedtls/base64.h')
lib.net = lib.loadlib('mongoose','mongoose.h')
lib.pq = lib.loadlib('libpq','libpq-fe.h')

lib.load {
	'mem',  'math', 'str', 'file', 'crypt';
	'http', 'session', 'tpl', 'store';


}

local be = {}
for _, b in pairs(config.backends) do
	be[#be+1] = terralib.loadfile('backend/' .. b .. '.t')()
end
lib.store.backends = global(`array([be]))
................................................................................
lib.load {
	'srv';
	'render:nav';
	'render:login';
	'render:profile';
	'render:userpage';
	'render:compose';


	'route';
}

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
................................................................................
if bflag('dump-config','C') then
	print(util.dump(config))
	os.exit(0)
end

local holler = print
local out = config.exe and 'parsav' or ('parsav.' .. config.outform)
local linkargs = {'-O4'}

if bflag('quiet','q') then holler = function() end end
if bflag('asan','s') then linkargs[#linkargs+1] = '-fsanitize=address' end
if bflag('lsan','S') then linkargs[#linkargs+1] = '-fsanitize=leak' end

if config.posix then
	linkargs[#linkargs+1] = '-pthread'







|
>
|
|
<




>
|
>
>
>
>
>
>
>
|
|
|
>







 







>
>







 







>
>







 







|







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
...
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
...
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
...
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
			if c == true then r = i else r = e end
		in r end
	end);
	coalesce = macro(function(...)
		local args = {...}
		local ty = args[1].tree.type
		local val = symbol(ty)
		local empty
		if ty.ptr_basetype then empty = `[ty]{ptr=nil,ct=0}
		elseif ty.type == 'integer' then empty = `0
		else empty = `nil end

		local exp = quote val = [empty] end

		for i=#args, 1, -1 do
			local v = args[i]
			if ty.ptr_basetype then
				exp = quote
					if [v].ptr ~= nil
						then val = v
						else [exp]
					end
				end
			else
				exp = quote
					if [v] ~= [empty]
						then val = v
						else [exp]
					end
				end
			end
		end

		local q = quote
			var [val]
			[exp]
................................................................................
lib.b64 = lib.loadlib('mbedtls','mbedtls/base64.h')
lib.net = lib.loadlib('mongoose','mongoose.h')
lib.pq = lib.loadlib('libpq','libpq-fe.h')

lib.load {
	'mem',  'math', 'str', 'file', 'crypt';
	'http', 'session', 'tpl', 'store';

	'smackdown'; -- md-alike parser
}

local be = {}
for _, b in pairs(config.backends) do
	be[#be+1] = terralib.loadfile('backend/' .. b .. '.t')()
end
lib.store.backends = global(`array([be]))
................................................................................
lib.load {
	'srv';
	'render:nav';
	'render:login';
	'render:profile';
	'render:userpage';
	'render:compose';
	'render:tweet';
	'render:timeline';
	'route';
}

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
................................................................................
if bflag('dump-config','C') then
	print(util.dump(config))
	os.exit(0)
end

local holler = print
local out = config.exe and 'parsav' or ('parsav.' .. config.outform)
local linkargs = {}

if bflag('quiet','q') then holler = function() end end
if bflag('asan','s') then linkargs[#linkargs+1] = '-fsanitize=address' end
if bflag('lsan','S') then linkargs[#linkargs+1] = '-fsanitize=leak' end

if config.posix then
	linkargs[#linkargs+1] = '-pthread'

Modified render/compose.t from [7a7e8f43ac] to [0685338a7e].

9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
			acl = lib.trn(target == nil, 'all', 'mentioned'); -- TODO default acl setting?
			handle = co.who.handle;
		}
	end
	var cotxt = form:tostr() defer cotxt:free()

	var doc = data.view.docskel {
		instance = co.srv.cfg.instance.ptr;
		title = 'compose';
		body = cotxt.ptr;
		class = 'compose';
		navlinks = co.navbar.ptr;
	}

	var hdrs = array(
		lib.http.header { 'Content-Type', 'text/html; charset=UTF-8' }
	)
	doc:send(co.con,200,[lib.mem.ptr(lib.http.header)] {ct = 1, ptr = &hdrs[0]})
end

return render_compose







|
|
|
|
|









9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
			acl = lib.trn(target == nil, 'all', 'mentioned'); -- TODO default acl setting?
			handle = co.who.handle;
		}
	end
	var cotxt = form:tostr() defer cotxt:free()

	var doc = data.view.docskel {
		instance = co.srv.cfg.instance;
		title = lib.str.plit 'compose';
		body = cotxt;
		class = lib.str.plit 'compose';
		navlinks = co.navbar;
	}

	var hdrs = array(
		lib.http.header { 'Content-Type', 'text/html; charset=UTF-8' }
	)
	doc:send(co.con,200,[lib.mem.ptr(lib.http.header)] {ct = 1, ptr = &hdrs[0]})
end

return render_compose

Modified render/login.t from [0d69ec17a3] to [671b92715b].

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


local terra 
login_form(co: &lib.srv.convo, user: &lib.store.actor, creds: &lib.store.credset, msg: &int8)
	var doc = data.view.docskel {
		instance = co.srv.cfg.instance.ptr;
		title = 'instance logon';
		class = 'login';
		navlinks = co.navbar.ptr;
	}

	if user == nil then
		var form = data.view.login_username {
			loginmsg = msg;
		}
		if form.loginmsg == nil then
			form.loginmsg = 'identify yourself for access to this instance.'
		end
		var formtxt = form:tostr()
		doc.body = formtxt.ptr
	elseif creds:sz() == 0 then
		co:complain(403,'access denied','your host is not eligible to authenticate as this user')
		return
	elseif creds:sz() == 1 then
		if creds.trust() then
			-- TODO log in immediately
			return
................................................................................
		end

		var ch = data.view.login_challenge {
			handle = user.handle;
			name = lib.coalesce(user.nym, user.handle);
		}
		if creds.pw() then
			ch.challenge = 'enter the password associated with your account'
			ch.label = 'password'
			ch.method = 'pw'
		elseif creds.otp() then
			ch.challenge = 'enter a valid one-time password for your account'
			ch.label = 'OTP code'
			ch.method = 'otp'
		elseif creds.challenge() then
			ch.challenge = 'sign the challenge token: <code>...</code>'
			ch.label = 'digest'
			ch.method = 'challenge'
		else
			co:complain(500,'login failure','unknown login method')
			return
		end

		doc.body = ch:tostr().ptr
	else
		-- pick a method
	end

	var hdrs = array(
		lib.http.header { 'Content-Type', 'text/html; charset=UTF-8' }
	)
	doc:send(co.con,200,[lib.mem.ptr(lib.http.header)] {ct = 1, ptr = &hdrs[0]})
	lib.mem.heapf(doc.body)
end

return login_form

>
>

|

|
|
|
|






|
|

<
|







 







|
|
|

|
|
|

|
|
|





|








|



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
-- vim: ft=terra
local pstr = lib.mem.ptr(int8)
local P = lib.str.plit
local terra 
login_form(co: &lib.srv.convo, user: &lib.store.actor, creds: &lib.store.credset, msg: pstr)
	var doc = data.view.docskel {
		instance = co.srv.cfg.instance;
		title = lib.str.plit 'instance logon';
		class = lib.str.plit 'login';
		navlinks = co.navbar;
	}

	if user == nil then
		var form = data.view.login_username {
			loginmsg = msg;
		}
		if form.loginmsg.ptr == nil then
			form.loginmsg = lib.str.plit 'identify yourself for access to this instance.'
		end

		doc.body = form:tostr()
	elseif creds:sz() == 0 then
		co:complain(403,'access denied','your host is not eligible to authenticate as this user')
		return
	elseif creds:sz() == 1 then
		if creds.trust() then
			-- TODO log in immediately
			return
................................................................................
		end

		var ch = data.view.login_challenge {
			handle = user.handle;
			name = lib.coalesce(user.nym, user.handle);
		}
		if creds.pw() then
			ch.challenge = P'enter the password associated with your account'
			ch.label = P'password'
			ch.method = P'pw'
		elseif creds.otp() then
			ch.challenge = P'enter a valid one-time password for your account'
			ch.label = P'OTP code'
			ch.method = P'otp'
		elseif creds.challenge() then
			ch.challenge = P'sign the challenge token: <code>...</code>'
			ch.label = P'digest'
			ch.method = P'challenge'
		else
			co:complain(500,'login failure','unknown login method')
			return
		end

		doc.body = ch:tostr()
	else
		-- pick a method
	end

	var hdrs = array(
		lib.http.header { 'Content-Type', 'text/html; charset=UTF-8' }
	)
	doc:send(co.con,200,[lib.mem.ptr(lib.http.header)] {ct = 1, ptr = &hdrs[0]})
	doc.body:free()
end

return login_form

Modified render/nav.t from [2d4aa38bec] to [450b73f673].

3
4
5
6
7
8
9
10
11
12
13
14
15
16
render_nav(co: &lib.srv.convo)
	var t: lib.str.acc t:init(64)
	if co.who ~= nil or co.srv.cfg.pol_sec == lib.srv.secmode.public then
		t:lpush('<a href="/">timeline</a>')
	end
	if co.who ~= nil then
		t:lpush('<a href="/compose">compose</a> <a href="/'):push(co.who.xid,0)
		t:lpush('">profile</a> <a href="/conf">configure</a> <a href="/logout">log out</a>')
	else
		t:lpush('<a href="/login">log in</a>')
	end
	return t:finalize()
end
return render_nav







|

|




3
4
5
6
7
8
9
10
11
12
13
14
15
16
render_nav(co: &lib.srv.convo)
	var t: lib.str.acc t:init(64)
	if co.who ~= nil or co.srv.cfg.pol_sec == lib.srv.secmode.public then
		t:lpush('<a href="/">timeline</a>')
	end
	if co.who ~= nil then
		t:lpush('<a href="/compose">compose</a> <a href="/'):push(co.who.xid,0)
		t:lpush('">profile</a> <a href="/conf">configure</a> <a href="/help">help</a> <a href="/logout">log out</a>')
	else
		t:lpush('<a href="/help">help</a> <a href="/login">log in</a>')
	end
	return t:finalize()
end
return render_nav

Modified render/profile.t from [ecfc7ba460] to [30c7a0b6c6].

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





local terra 
render_profile(co: &lib.srv.convo, actor: &lib.store.actor)
	var aux: lib.str.acc
	var auxp: rawstring
	if co.aid ~= 0 and co.who.id == actor.id then
		auxp = '<a href="/conf/profile">alter</a>'
	elseif co.aid ~= 0 then
		aux:compose('<a href="/', actor.xid, '/follow">follow</a><a href="/',
			actor.xid, '/chat">chat</a>')
		if co.who.rights.powers:affect_users() then
			aux:push('<a href="/',11):push(actor.xid,0):push('/ctl">control</a>',17)
		end
		auxp = aux.buf
	else
		aux:compose('<a href="/', actor.xid, '/follow">remote follow</a>')
	end
	var avistr: lib.str.acc if actor.origin == 0 then
		avistr:compose('/avi/',actor.handle)
	end
	var timestr: int8[26] lib.osclock.ctime_r(&actor.knownsince, &timestr[0])

	var strfbuf: int8[28*4]
	var stats = co.srv:actor_stats(actor.id)
		var sn_posts = lib.math.decstr_friendly(stats.posts, &strfbuf[ [strfbuf.type.N - 1] ])
		var sn_follows = lib.math.decstr_friendly(stats.follows, sn_posts - 1)
		var sn_followers = lib.math.decstr_friendly(stats.followers, sn_follows - 1)
		var sn_mutuals = lib.math.decstr_friendly(stats.mutuals, sn_followers - 1)
	
	var profile = data.view.profile {
		nym = lib.coalesce(actor.nym, actor.handle);
		bio = lib.coalesce(actor.bio, "<em>tall, dark, and mysterious</em>");
		xid = actor.xid;
		avatar = lib.trn(actor.origin == 0, avistr.buf,
			lib.coalesce(actor.avatar, '/s/default-avatar.webp'));

		nposts = sn_posts, nfollows = sn_follows;
		nfollowers = sn_followers, nmutuals = sn_mutuals;
		tweetday = timestr;
		timephrase = lib.trn(actor.origin == 0, 'joined', 'known since');

		auxbtn = auxp;
	}

	var ret = profile:tostr()
	if actor.origin == 0 then avistr:free() end
	if not (co.aid ~= 0 and co.who.id == actor.id) then aux:free() end
	return ret
end

return render_profile

>
>
>
>
>



|

|






|










|
|
|
|


|
|
|
|
|



|
|






|




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
-- vim: ft=terra
local pstr = lib.mem.ptr(int8)
local terra cs(s: rawstring)
	return pstr { ptr = s, ct = lib.str.sz(s) }
end

local terra 
render_profile(co: &lib.srv.convo, actor: &lib.store.actor)
	var aux: lib.str.acc
	var auxp: pstr
	if co.aid ~= 0 and co.who.id == actor.id then
		auxp = lib.str.plit '<a href="/conf/profile">alter</a>'
	elseif co.aid ~= 0 then
		aux:compose('<a href="/', actor.xid, '/follow">follow</a><a href="/',
			actor.xid, '/chat">chat</a>')
		if co.who.rights.powers:affect_users() then
			aux:push('<a href="/',11):push(actor.xid,0):push('/ctl">control</a>',17)
		end
		auxp = aux:finalize()
	else
		aux:compose('<a href="/', actor.xid, '/follow">remote follow</a>')
	end
	var avistr: lib.str.acc if actor.origin == 0 then
		avistr:compose('/avi/',actor.handle)
	end
	var timestr: int8[26] lib.osclock.ctime_r(&actor.knownsince, &timestr[0])

	var strfbuf: int8[28*4]
	var stats = co.srv:actor_stats(actor.id)
		var sn_posts     = cs(lib.math.decstr_friendly(stats.posts, &strfbuf[ [strfbuf.type.N - 1] ]))
		var sn_follows   = cs(lib.math.decstr_friendly(stats.follows, sn_posts.ptr - 1))
		var sn_followers = cs(lib.math.decstr_friendly(stats.followers, sn_follows.ptr - 1))
		var sn_mutuals   = cs(lib.math.decstr_friendly(stats.mutuals, sn_followers.ptr - 1))
	
	var profile = data.view.profile {
		nym = cs(lib.coalesce(actor.nym, actor.handle));
		bio = cs(lib.coalesce(actor.bio, "<em>tall, dark, and mysterious</em>"));
		xid = cs(actor.xid);
		avatar = lib.trn(actor.origin == 0, pstr{ptr=avistr.buf,ct=avistr.sz},
			cs(lib.coalesce(actor.avatar, '/s/default-avatar.webp')));

		nposts = sn_posts, nfollows = sn_follows;
		nfollowers = sn_followers, nmutuals = sn_mutuals;
		tweetday = cs(timestr);
		timephrase = lib.trn(actor.origin == 0, lib.str.plit'joined', lib.str.plit'known since');

		auxbtn = auxp;
	}

	var ret = profile:tostr()
	if actor.origin == 0 then avistr:free() end
	if not (co.aid ~= 0 and co.who.id == actor.id) then auxp:free() end
	return ret
end

return render_profile

Added render/timeline.t version [e2a4558814].



































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
-- vim: ft=terra
local modes = lib.enum {'follow','mutual','srvlocal','fediglobal','circle'}
local terra 
render_timeline(co: &lib.srv.convo, modestr: lib.mem.ref(int8))
	var mode = modes.srvlocal
	var circle: uint64 = 0
	-- if     modestr:cmpl('local') then mode = modes.srvlocal
	-- elseif modestr:cmpl('mutual') then mode = modes.mutual
	-- elseif modestr:cmpl('global') then mode = modes.fediglobal
	-- elseif modestr:cmpl('circle') then mode = modes.circle
	-- end

	var stoptime = lib.osclock.time(nil)

	var posts = [lib.mem.vec(lib.mem.ptr(lib.store.post))] { 
		sz = 0, run = 0
	}
	if mode == modes.follow then
	elseif mode == modes.srvlocal then
		posts = co.srv:instance_timeline_fetch(lib.store.range {
			mode = 1; -- T->I
			from_time = stoptime;
			to_idx = 64;
		})
	elseif mode == modes.fediglobal then
	elseif mode == modes.circle then
	end

	var acc: lib.str.acc acc:init(1024)
	for i = 0, posts.sz do
		lib.render.tweet(co, posts(i).ptr, &acc)
		posts(i):free()
	end
	posts:free()

	var doc = data.view.docskel {
		instance = co.srv.cfg.instance;
		title = lib.str.plit'timeline';
		body = acc:finalize();
		class = lib.str.plit'timeline';
		navlinks = co.navbar;
	}
	var hdrs = array(
		lib.http.header { 'Content-Type', 'text/html; charset=UTF-8' }
	)
	doc:send(co.con,200,[lib.mem.ptr(lib.http.header)] {ct = 1, ptr = &hdrs[0]})
	doc.body:free()
end
return render_timeline

Added render/tweet.t version [71eb9101d9].







































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
-- vim: ft=terra
local pstr = lib.mem.ptr(int8)
local terra cs(s: rawstring)
	return pstr { ptr = s, ct = lib.str.sz(s) }
end

local terra 
render_tweet(co: &lib.srv.convo, p: &lib.store.post, acc: &lib.str.acc)
	var author: &lib.store.actor
	for j = 0, co.actorcache.top do
		lib.io.fmt('scanning cache for author %llu (%llu/%llu)\n', p.author, j, co.actorcache.top)
		if p.author == co.actorcache(j).ptr.id then
			author = co.actorcache(j).ptr
			lib.io.fmt('cache hit on idx %llu, skipping db lookup\n', j) 
			goto foundauth
		end
	end
	lib.io.fmt('cache miss, checking db for id %llu\n', p.author) 
	author = co.actorcache:insert(co.srv:actor_fetch_uid(p.author)).ptr
	lib.io.fmt('got author %s\n', author.handle) 

	::foundauth::
	var avistr: lib.str.acc if author.origin == 0 then
		avistr:compose('/avi/',author.handle)
	end
	var timestr: int8[26] lib.osclock.ctime_r(&p.posted, &timestr[0])
	lib.io.fmt('got body %s\n', author.handle) 

	var bhtml = lib.smackdown.html([lib.mem.ptr(int8)] {ptr=p.body,ct=0}) defer bhtml:free()

	var idbuf: int8[lib.math.shorthand.maxlen]
	var idlen = lib.math.shorthand.gen(p.id, idbuf)
	var permalink: lib.str.acc permalink:compose('/post/',{idbuf,idlen})

	var tpl = data.view.tweet {
		text = bhtml;
		subject = cs(lib.coalesce(p.subject,''));
		nym = cs(lib.coalesce(author.nym, author.handle));
		xid = cs(author.xid);
		when = cs(&timestr[0]);
		avatar = cs(lib.trn(author.origin == 0, avistr.buf,
			lib.coalesce(author.avatar, '/s/default-avatar.webp')));
		acctlink = cs(author.xid);
		permalink = permalink:finalize();
	}
	defer tpl.permalink:free()
	if acc ~= nil then tpl:append(acc) return [lib.mem.ptr(int8)]{ptr=nil,ct=0} end
	var txt = tpl:tostr()
	return txt
end
return render_tweet

Modified render/userpage.t from [cdf1e65d22] to [7b79f9904c].

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
-- vim: ft=terra
local terra 
render_userpage(co: &lib.srv.convo, actor: &lib.store.actor)
	var ti: lib.str.acc defer ti:free()
	if co.aid ~= 0 and co.who.id == actor.id then
		ti:compose('my profile')
	else
		ti:compose('profile :: ', actor.handle)
	end
	var pftxt = lib.render.profile(co,actor) defer pftxt:free()

	var doc = data.view.docskel {
		instance = co.srv.cfg.instance.ptr;
		title = ti.buf;
		body = pftxt.ptr;
		class = 'profile';
		navlinks = co.navbar.ptr;
	}

	var hdrs = array(
		lib.http.header { 'Content-Type', 'text/html; charset=UTF-8' }
	)
	doc:send(co.con,200,[lib.mem.ptr(lib.http.header)] {ct = 1, ptr = &hdrs[0]})

end

return render_userpage



|








|
|
|
|
|






>



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
-- vim: ft=terra
local terra 
render_userpage(co: &lib.srv.convo, actor: &lib.store.actor)
	var ti: lib.str.acc 
	if co.aid ~= 0 and co.who.id == actor.id then
		ti:compose('my profile')
	else
		ti:compose('profile :: ', actor.handle)
	end
	var pftxt = lib.render.profile(co,actor) defer pftxt:free()

	var doc = data.view.docskel {
		instance = co.srv.cfg.instance;
		title = ti:finalize();
		body = pftxt;
		class = lib.str.plit 'profile';
		navlinks = co.navbar;
	}

	var hdrs = array(
		lib.http.header { 'Content-Type', 'text/html; charset=UTF-8' }
	)
	doc:send(co.con,200,[lib.mem.ptr(lib.http.header)] {ct = 1, ptr = &hdrs[0]})
	doc.title:free()
end

return render_userpage

Modified route.t from [d7d680b0a3] to [4d69f275c0].

1
2
3



4
5
6
7
8
9
10
..
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
..
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
...
134
135
136
137
138
139
140








141










142
143





144
145
146
147
148
149
150
...
180
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
...
225
226
227
228
229
230
231


232
233
234
235
236
237
238
-- vim: ft=terra
local r = lib.srv.route
local method = lib.http.method



local http = {}

terra http.actor_profile_xid(co: &lib.srv.convo, uri: lib.mem.ptr(int8), meth: method.t)
	var handle = [lib.mem.ptr(int8)] { ptr = &uri.ptr[2], ct = 0 }
	for i=2,uri.ct do
		if uri.ptr[i] == @'/' or uri.ptr[i] == 0 then handle.ct = i - 2 break end
	end
................................................................................

	lib.render.userpage(co, actor.ptr)
end

terra http.login_form(co: &lib.srv.convo, meth: method.t)
	if meth == method.get then
		-- request a username
		lib.render.login(co, nil, nil, nil)
	elseif meth == method.post then
		var usn, usnl = co:postv('user')
		lib.dbg('got name ',{usn,usnl})
		lib.io.fmt('name len %llu\n',usnl)
		var am, aml = co:postv('authmethod')
		var chrs, chrsl = co:postv('response')
		var cs, authok = co.srv:actor_auth_how(co.peer, usn)
		var act = co.srv:actor_fetch_xid([lib.mem.ptr(int8)] {
			ptr = usn, ct = usnl
		})
		if authok == false then
			lib.render.login(co, nil, nil, 'access denied')
			return
		end
		var fakeact = false
		var fakeactor: lib.store.actor
		if act.ptr == nil then
			-- the user is known to us but has not yet claimed an
			-- account on the server. create a template for the
................................................................................
			}
			act.ct = 1
			act.ptr = &fakeactor
			act.ptr.rights = lib.store.rights_default()
		end
		if am == nil then
			-- pick an auth method
			lib.render.login(co, act.ptr, &cs, nil)
		else var aid: uint64 = 0
			lib.dbg('authentication attempt beginning')
			-- attempt login with provided method
			if lib.str.ncmp('pw', am, lib.math.biggest(2,aml)) == 0 and chrs ~= nil then
				aid = co.srv:actor_auth_pw(co.peer,
					[lib.mem.ptr(int8)]{ptr=usn,ct=usnl},
					[lib.mem.ptr(int8)]{ptr=chrs,ct=chrsl})
................................................................................
			elseif lib.str.ncmp('otp', am, lib.math.biggest(2,aml)) == 0 and chrs ~= nil then
				lib.dbg('using otp auth')
				-- ··· --
			else
				lib.dbg('invalid auth method')
			end

			lib.io.fmt('login got aid = %llu\n', aid)
			-- error out
			if aid == 0 then
				lib.render.login(co, nil, nil, 'authentication failure')
			else
				var sesskey: int8[lib.session.maxlen + #lib.session.cookiename + #"=; Path=/" + 1]
				do var p = &sesskey[0]
					p = lib.str.ncpy(p, [lib.session.cookiename .. '='], [#lib.session.cookiename + 1])
					p = p + lib.session.cookie_gen(co.srv.cfg.secret, aid, lib.osclock.time(nil), p)
					lib.dbg('sending cookie',&sesskey[0])
					p = lib.str.ncpy(p, '; Path=/', 9)
................................................................................
terra http.post_compose(co: &lib.srv.convo, meth: method.t)
	if meth == method.get then
		lib.render.compose(co, nil)
	elseif meth == method.post then
		if co.who.rights.powers.post() == false then
			co:complain(401,'insufficient privileges','you lack the <strong>post</strong> power and cannot perform this action') return
		end



















	end
end






do local branches = quote end
	local filename, flen = symbol(&int8), symbol(intptr)
	local page = symbol(lib.http.page)
	local send = label()
	local storage = data.stmap
	for i,e in ipairs(config.embeds) do local id,mime = e[1],e[2]
................................................................................
	co:reroute('/s/default-avatar.webp')
end

-- entry points
terra r.dispatch_http(co: &lib.srv.convo, uri: lib.mem.ptr(int8), meth: method.t)
	lib.dbg('handling URI of form ', {uri.ptr,uri.ct})
	co.navbar = lib.render.nav(co)

	-- some routes are non-hierarchical, and can be resolved with a simple strcmp
	-- we run through those first before giving up and parsing the URI
	if uri.ptr[0] ~= @'/' then
		co:complain(404, 'what the hell', 'how did you do that')
		return
	elseif uri.ct == 1 then -- root
		lib.io.fmt('root directory, aid is %llu\n', co.aid)
		if (co.srv.cfg.pol_sec == lib.srv.secmode.private or
		   co.srv.cfg.pol_sec == lib.srv.secmode.lockdown) and co.aid == 0 then
		   http.login_form(co, meth)
		else
			-- FIXME display home screen

			goto notfound
		end
		return
	elseif uri.ptr[1] == @'@' then
		http.actor_profile_xid(co, uri, meth)
		return
	elseif uri.ptr[1] == @'s' and uri.ptr[2] == @'/' and uri.ct > 3 then
................................................................................
			else co:reroute_cookie('/','auth=; Path=/')
		end
		return
	else -- hierarchical routes
		var path = lib.http.hier(uri) defer path:free()
		if path.ptr[0]:cmp(lib.str.lit('user')) then
			http.actor_profile_uid(co, path, meth)


		else goto notfound end
		return
	end

	::wrongmeth:: co:complain(405, 'method not allowed', 'that method is not meaningful for this endpoint') do return end
	::notfound:: co:complain(404, 'not found', 'no such resource available') do return end
end



>
>
>







 







|


<
<







|







 







|







 







<


|







 







>
>
>
>
>
>
>
>

>
>
>
>
>
>
>
>
>
>


>
>
>
>
>







 







>






<





>







 







>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
..
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
..
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
...
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
...
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
...
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
-- vim: ft=terra
local r = lib.srv.route
local method = lib.http.method
local pstring = lib.mem.ptr(int8)
local rstring = lib.mem.ref(int8)
local hpath = lib.mem.ptr(rstring)
local http = {}

terra http.actor_profile_xid(co: &lib.srv.convo, uri: lib.mem.ptr(int8), meth: method.t)
	var handle = [lib.mem.ptr(int8)] { ptr = &uri.ptr[2], ct = 0 }
	for i=2,uri.ct do
		if uri.ptr[i] == @'/' or uri.ptr[i] == 0 then handle.ct = i - 2 break end
	end
................................................................................

	lib.render.userpage(co, actor.ptr)
end

terra http.login_form(co: &lib.srv.convo, meth: method.t)
	if meth == method.get then
		-- request a username
		lib.render.login(co, nil, nil, lib.str.plit(nil))
	elseif meth == method.post then
		var usn, usnl = co:postv('user')


		var am, aml = co:postv('authmethod')
		var chrs, chrsl = co:postv('response')
		var cs, authok = co.srv:actor_auth_how(co.peer, usn)
		var act = co.srv:actor_fetch_xid([lib.mem.ptr(int8)] {
			ptr = usn, ct = usnl
		})
		if authok == false then
			lib.render.login(co, nil, nil, lib.str.plit'access denied')
			return
		end
		var fakeact = false
		var fakeactor: lib.store.actor
		if act.ptr == nil then
			-- the user is known to us but has not yet claimed an
			-- account on the server. create a template for the
................................................................................
			}
			act.ct = 1
			act.ptr = &fakeactor
			act.ptr.rights = lib.store.rights_default()
		end
		if am == nil then
			-- pick an auth method
			lib.render.login(co, act.ptr, &cs, lib.str.plit(nil))
		else var aid: uint64 = 0
			lib.dbg('authentication attempt beginning')
			-- attempt login with provided method
			if lib.str.ncmp('pw', am, lib.math.biggest(2,aml)) == 0 and chrs ~= nil then
				aid = co.srv:actor_auth_pw(co.peer,
					[lib.mem.ptr(int8)]{ptr=usn,ct=usnl},
					[lib.mem.ptr(int8)]{ptr=chrs,ct=chrsl})
................................................................................
			elseif lib.str.ncmp('otp', am, lib.math.biggest(2,aml)) == 0 and chrs ~= nil then
				lib.dbg('using otp auth')
				-- ··· --
			else
				lib.dbg('invalid auth method')
			end


			-- error out
			if aid == 0 then
				lib.render.login(co, nil, nil, lib.str.plit 'authentication failure')
			else
				var sesskey: int8[lib.session.maxlen + #lib.session.cookiename + #"=; Path=/" + 1]
				do var p = &sesskey[0]
					p = lib.str.ncpy(p, [lib.session.cookiename .. '='], [#lib.session.cookiename + 1])
					p = p + lib.session.cookie_gen(co.srv.cfg.secret, aid, lib.osclock.time(nil), p)
					lib.dbg('sending cookie',&sesskey[0])
					p = lib.str.ncpy(p, '; Path=/', 9)
................................................................................
terra http.post_compose(co: &lib.srv.convo, meth: method.t)
	if meth == method.get then
		lib.render.compose(co, nil)
	elseif meth == method.post then
		if co.who.rights.powers.post() == false then
			co:complain(401,'insufficient privileges','you lack the <strong>post</strong> power and cannot perform this action') return
		end
		var text, textlen = co:postv("post")
		var acl, acllen = co:postv("acl")
		var subj, subjlen = co:postv("subject")
		if text == nil or acl == nil then
			co:complain(405, 'invalid post', 'every post must have at least body text and an ACL')
			return
		end
		if subj == nil then subj = '' end

		var p = lib.store.post {
			author = co.who.id, acl = acl;
			body = text, subject = subj;
		}
		var newid = co.srv:post_create(&p)

		var idbuf: int8[lib.math.shorthand.maxlen]
		var idlen = lib.math.shorthand.gen(newid, idbuf)
		var redirto: lib.str.acc redirto:compose('/post/',{idbuf,idlen}) defer redirto:free()
		co:reroute(redirto.buf)
	end
end

terra http.timeline(co: &lib.srv.convo, mode: hpath)
	lib.render.timeline(co,lib.trn(mode.ptr == nil, rstring{ptr=nil}, mode.ptr[1]))
	return
end

do local branches = quote end
	local filename, flen = symbol(&int8), symbol(intptr)
	local page = symbol(lib.http.page)
	local send = label()
	local storage = data.stmap
	for i,e in ipairs(config.embeds) do local id,mime = e[1],e[2]
................................................................................
	co:reroute('/s/default-avatar.webp')
end

-- entry points
terra r.dispatch_http(co: &lib.srv.convo, uri: lib.mem.ptr(int8), meth: method.t)
	lib.dbg('handling URI of form ', {uri.ptr,uri.ct})
	co.navbar = lib.render.nav(co)
	lib.dbg('got nav ', {co.navbar.ptr,co.navbar.ct}, "||", co.navbar.ptr)
	-- some routes are non-hierarchical, and can be resolved with a simple strcmp
	-- we run through those first before giving up and parsing the URI
	if uri.ptr[0] ~= @'/' then
		co:complain(404, 'what the hell', 'how did you do that')
		return
	elseif uri.ct == 1 then -- root

		if (co.srv.cfg.pol_sec == lib.srv.secmode.private or
		   co.srv.cfg.pol_sec == lib.srv.secmode.lockdown) and co.aid == 0 then
		   http.login_form(co, meth)
		else
			-- FIXME display home screen
			http.timeline(co, hpath {ptr=nil})
			goto notfound
		end
		return
	elseif uri.ptr[1] == @'@' then
		http.actor_profile_xid(co, uri, meth)
		return
	elseif uri.ptr[1] == @'s' and uri.ptr[2] == @'/' and uri.ct > 3 then
................................................................................
			else co:reroute_cookie('/','auth=; Path=/')
		end
		return
	else -- hierarchical routes
		var path = lib.http.hier(uri) defer path:free()
		if path.ptr[0]:cmp(lib.str.lit('user')) then
			http.actor_profile_uid(co, path, meth)
		elseif path.ptr[0]:cmp(lib.str.lit('tl')) then
			http.timeline(co, path)
		else goto notfound end
		return
	end

	::wrongmeth:: co:complain(405, 'method not allowed', 'that method is not meaningful for this endpoint') do return end
	::notfound:: co:complain(404, 'not found', 'no such resource available') do return end
end

Modified schema.sql from [a3359b8b76] to [6bf306c986].

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
..
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
...
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
...
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
-- 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,

	parsav boolean -- whether to use parsav protocol extensions
);

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,
	avataruri text, -- null if local
	rank      smallint not null default 0,
	quota     integer not null default 1000,
	key       bytea, -- private if localactor; public if remote
	title     text
	
	unique (handle,origin)
);

drop table if exists parsav_rights cascade;
create table parsav_rights (
	key text,
................................................................................
	author     bigint references parsav_actors(id)
		on delete cascade,
	subject    text,
	acl        text not null default 'all', -- just store the script raw 🤷
	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. followed
................................................................................
);

drop table if exists parsav_circles cascade;
create table parsav_circles (
	id          bigint primary key default (1+random()*(2^63-1))::bigint,
	owner       bigint not null references parsav_actors(id),
	name        text not null,
	members     bigint[] not null default array[],

	unique (owner,name)
);

drop table if exists parsav_rooms cascade;
create table parsav_rooms (
	id          bigint primary key default (1+random()*(2^63-1))::bigint,
................................................................................

drop table if exists parsav_room_members cascade;
create table parsav_room_members (
	room   bigint references parsav_rooms(id),
	member bigint references parsav_actors(id),
	rank   smallint not null default 0,
	admin  boolean not null default false, -- non-admins with rank can only moderate + invite
	title  text -- admin-granted title like reddit flair

);

drop table if exists parsav_invites cascade;
create table parsav_invites (
	id          bigint primary key default (1+random()*(2^63-1))::bigint,
	-- when a user is created from an invite, the invite is deleted and the invite
	-- ID becomes the user ID. privileges granted on the invite ID during the invite
	-- process are thus inherited by the user

	handle text, -- admin can lock invite to specific handle
	rank   smallint not null default 0,
	quota  integer not null  default 1000
};

drop table if exists parsav_interventions cascade;
create table parsav_interventions (
	id     bigint primary key default (1+random()*(2^63-1))::bigint,
	issuer bigint references parsav_actors(id) not null,
	scope  bigint, -- can be null or room for local actions
	nature smallint not null, -- silence, suspend, disemvowel, etc
	victim bigint not null, -- could potentially target group as well
	expire timestamp -- auto-expires if set
);

end;







>










>





|







 







<
<
|
|
|
>
>
>
>
>



<
<
<
<
<
<







 







|







 







|
>








>



|












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
..
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
...
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
...
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
-- 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,
	knownsince timestamp,
	parsav boolean -- whether to use parsav protocol extensions
);

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
	knownsince timestamp,
	bio       text,
	avataruri text, -- null if local
	rank      smallint not null default 0,
	quota     integer not null default 1000,
	key       bytea, -- private if localactor; public if remote
	title     text,
	
	unique (handle,origin)
);

drop table if exists parsav_rights cascade;
create table parsav_rights (
	key text,
................................................................................
	author     bigint references parsav_actors(id)
		on delete cascade,
	subject    text,
	acl        text not null default 'all', -- just store the script raw 🤷
	body       text,
	posted     timestamp not null,
	discovered timestamp not null,


	parent     bigint not null default 0,
	circles    bigint[], -- TODO at edit or creation, iterate through each circle
	mentions   bigint[], -- a user has, check if it can see her post, and if so add

	convoheaduri text
	-- only used for tracking foreign conversations and tying them to post heads;
	-- local conversations are tracked directly and mapped to URIs based on the
	-- head's ID. null if native tweet or not the first tweet in convo
);

drop table if exists parsav_conversations cascade;







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. followed
................................................................................
);

drop table if exists parsav_circles cascade;
create table parsav_circles (
	id          bigint primary key default (1+random()*(2^63-1))::bigint,
	owner       bigint not null references parsav_actors(id),
	name        text not null,
	members     bigint[] not null default array[]::bigint[],

	unique (owner,name)
);

drop table if exists parsav_rooms cascade;
create table parsav_rooms (
	id          bigint primary key default (1+random()*(2^63-1))::bigint,
................................................................................

drop table if exists parsav_room_members cascade;
create table parsav_room_members (
	room   bigint references parsav_rooms(id),
	member bigint references parsav_actors(id),
	rank   smallint not null default 0,
	admin  boolean not null default false, -- non-admins with rank can only moderate + invite
	title  text, -- admin-granted title like reddit flair
	vouchedby bigint references parsav_actors(id)
);

drop table if exists parsav_invites cascade;
create table parsav_invites (
	id          bigint primary key default (1+random()*(2^63-1))::bigint,
	-- when a user is created from an invite, the invite is deleted and the invite
	-- ID becomes the user ID. privileges granted on the invite ID during the invite
	-- process are thus inherited by the user
	issuer bigint references parsav_actors(id),
	handle text, -- admin can lock invite to specific handle
	rank   smallint not null default 0,
	quota  integer not null  default 1000
);

drop table if exists parsav_interventions cascade;
create table parsav_interventions (
	id     bigint primary key default (1+random()*(2^63-1))::bigint,
	issuer bigint references parsav_actors(id) not null,
	scope  bigint, -- can be null or room for local actions
	nature smallint not null, -- silence, suspend, disemvowel, etc
	victim bigint not null, -- could potentially target group as well
	expire timestamp -- auto-expires if set
);

end;

Added smackdown.t version [896438e021].





















































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
-- vim: ft=terra
-- smackdown is parsav's terrible, terrible custom markdown-alike parser

local m = {}
local pstr = lib.mem.ptr(int8)

local segt = { 
	none = 0, para = 1, head = 2, listing = 3
}

local autolink_protos = {
	'https', 'http', 'ftp', 'gopher', 'gemini', 'ircs', 'irc';
	'mailto', 'about', 'sshfs', 'afp', 'smb', 'data', 'file';
	'dav', 'git', 'svn', 'cvs', 'dns', 'finger', 'pop', 'imap';
	'pops', 'imaps', 'torrent', 'magnet', 'news', 'snews', 'nfs';
	'nntp', 'sms', 'tel', 'telnet', 'vnc', 'webcal', 'ws', 'wss';
	'xmpp';
}

local struct state {
	segt: uint
	bqlvl: uint
	curpos: rawstring
	blockstart: rawstring
}

terra state:segend(ofs: uint)
-- takes a string offset and returns true if it indexes th
-- end of the current block
	var s = self.curpos + ofs
	if s[0] ~= @'\n' then return false end
	if self.segt == segt.head then return true end -- headers can only be 1 line
-- 	if s[1] == '#'

end

local terra isws(c: int8)
	return c == @' ' or c == @'\n' or c == @'\t' or c == @'\r'
end

local terra scanline(l: rawstring, max: intptr, n: rawstring, nc: intptr)
	if l == nil then return nil end
	for i=0,max do
		for j=0,nc do
			if l[i+j] == @'\n' then return nil end
			if l[i+j] ~= n[j] then goto nexti end
		end
		do return l+i end
	::nexti::end
end

local terra scanline_wordend(l: rawstring, max: intptr, n: rawstring, nc: intptr)
	var sl = scanline(l,max,n,nc)
	if sl == nil then return nil else sl = sl + nc end
	if sl >= l+max or isws(@sl) then return sl-nc end
	return nil
end

terra m.html(md: pstr)
	if md.ct == 0 then md.ct = lib.str.sz(md.ptr) end
	var styled: lib.str.acc styled:init(md.ct)

	do var i = 0 while i < md.ct do
		var wordstart = (i == 0 or isws(md.ptr[i-1]))
		var wordend = (i == md.ct - 1 or isws(md.ptr[i+1]))

		var here = md.ptr + i
		var rem = md.ct - i
		if @here == @'[' then
			var sep = scanline(here,rem, '](', 2)
			var term = scanline(sep+2,rem - ((sep+2)-here), ')', 1)
			if sep ~= nil and term ~= nil then
				styled:lpush('<a href="')
					:push(sep+2, term-(sep+2))
					:lpush('" rel="nofollow">')
					:push(here+1,(sep-here) - 1)
					:lpush('</a>')
				i = (term - md.ptr) + 1
				goto skip
			else goto fallback end
		end

		if wordstart and rem >= 7 and lib.str.ncmp('***',here,3)==0 then
			var term = scanline_wordend(here+4,rem-4,'***',3)
			if term ~= nil then
				styled:lpush('<strong><em>')
					:push(here+3, (term-here) - 3)
					:lpush('</strong></em>')
				i = (term - md.ptr) + 3
				goto skip
			end
		end

		if wordstart and rem >= 5 and lib.str.ncmp('**',here,2)==0 then
			var term = scanline_wordend(here+3,rem-3,'**',2)
			if term ~= nil then
				styled:lpush('<strong>')
					:push(here+2, (term-here) - 2)
					:lpush('</strong>')
				i = (term - md.ptr) + 2
				goto skip
			end
		end

		if wordstart and rem >= 3 and @here == @'*' then
			var term = scanline_wordend(here+2,rem-2,'*',1)
			if term ~= nil then
				styled:lpush('<em>')
					:push(here+1, (term-here) - 1)
					:lpush('</em>')
				i = (term - md.ptr) + 1
				goto skip
			end
		end

		::fallback::styled:push(here,1) -- :/
		i = i + 1
	::skip::end end

	-- we make two passes: the first detects and transforms inline elements,
	-- the second carries out block-level organization

	var html: lib.str.acc html:init(styled.sz)
	var s = state {
		segt = segt.none;
		bqlvl = 0;
		curpos = md.ptr;
		blockstart = nil;
	}
	while s.curpos < md.ptr + md.ct do
		s.curpos = s.curpos + 1
	end 

		html:free() -- JUST FOR NOW
	return styled:finalize()
end

return m

Modified srv.t from [afa0417e30] to [d14bb4b6ce].

16
17
18
19
20
21
22













23
24
25
26
27
28
29
..
34
35
36
37
38
39
40

41




42






43
44
45
46
47
48
49
50
51
..
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
..
74
75
76
77
78
79
80

81
82
83
84
85
86
87
...
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
...
223
224
225
226
227
228
229


230
231
232
233
234
235
236
...
344
345
346
347
348
349
350

351
352
353
354
355
356
357
	cfg: cfgcache
}

terra cfgcache:free() -- :/
	self.secret:free()
	self.instance:free()
end














srv.metamethods.__methodmissing = macro(function(meth, self, ...)
	local primary, ptr, stat, simple, oid = 0,1,2,3,4
	local tk, rt = primary
	local expr = {...}
	for _,f in pairs(lib.store.backend.entries) do
		local fn = f.field or f[1]
................................................................................
			elseif rt.type == 'integer' then tk = oid
			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 == true
			empty = `[rt]{ok=false,error=1}
		elseif tk == simple then
................................................................................
		elseif tk == oid then
			ok = `r ~= 0
			empty = `0
		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
................................................................................
	msg: &lib.net.mg_http_message
	aid: uint64 -- 0 if logged out
	who: &lib.store.actor -- who we're logged in as, if aid ~= 0
	peer: lib.store.inet
	reqtype: lib.http.mime.t -- negotiated content type
-- cache
	navbar: lib.mem.ptr(int8)

-- private
	varbuf: lib.mem.ptr(int8)
	vbofs: &int8
}

-- this is unfortunately necessary to work around a terra bug
-- it can't seem to handle forward-declarations of structs in C
................................................................................
end

terra convo:reroute(dest: rawstring) self:reroute_cookie(dest,nil) end
 
terra convo:complain(code: uint16, title: rawstring, msg: rawstring)
	var hdrs = array(lib.http.header { key = 'Content-Type', value = 'text/html; charset=UTF-8' })

	var ti: lib.str.acc ti:compose('error :: ', title) defer ti:free()
	var bo: lib.str.acc bo:compose('<div class="message"><img class="icon" src="/s/warn.webp"><h1>error</h1><p>',msg,'</p></div>') defer bo:free()
	var body = data.view.docskel {
		instance = self.srv.cfg.instance.ptr;
		title = ti.buf;
		body = bo.buf;
		class = 'error';
		navlinks = lib.coalesce(self.navbar.ptr, '');
	}

	if body.body == nil then
		body.body = "i'm sorry, dave. i can't let you do that"
	end

	body:send(self.con, code, [lib.mem.ptr(lib.http.header)] {
		ptr = &hdrs[0], ct = [hdrs.type.N]
	})



end

-- CALL ONLY ONCE PER VAR
terra convo:postv(name: rawstring)
	if self.varbuf.ptr == nil then
		self.varbuf = lib.mem.heapa(int8, self.msg.body.len + self.msg.query.len)
		self.vbofs = self.varbuf.ptr
	end
	var o = lib.net.mg_http_get_var(&self.msg.body, name, self.vbofs, self.varbuf.ct - (self.vbofs - self.varbuf.ptr))
	if o > 0 then
		var r = self.vbofs
		self.vbofs = self.vbofs + o


		return r, o
	else return nil, 0 end
end

terra convo:getv(name: rawstring)
	if self.varbuf.ptr == nil then
		self.varbuf = lib.mem.heapa(int8, self.msg.query.len + self.msg.body.len)
		self.vbofs = self.varbuf.ptr
	end
	var o = lib.net.mg_http_get_var(&self.msg.query, name, self.vbofs, self.varbuf.ct - (self.vbofs - self.varbuf.ptr))
	if o > 0 then
		var r = self.vbofs
		self.vbofs = self.vbofs + o


		return r, o
	else return nil, 0 end
end

local urimatch = macro(function(uri, ptn)
	return `lib.net.mg_globmatch(ptn, [#ptn], uri.ptr, uri.ct+1)
end)

................................................................................
				var msg = [&lib.net.mg_http_message](p)
				var co = convo {
					con = con, srv = server, msg = msg;
					aid = 0, who = nil, peer = peer;
					reqtype = lib.http.mime.none;
				} co.varbuf.ptr = nil
				  co.navbar.ptr = nil



				-- first, check for an accept header. if it's there, we need to
				-- iterate over the values and pick the highest-priority one
				do var acc = lib.http.findheader(msg, 'Accept')
					-- TODO handle q-value
					if acc.ptr ~= nil then
						var [mimevar] = [lib.mem.ref(int8)] { ptr = acc.ptr }
................................................................................
				else
					co:complain(400,'unknown method','you have submitted an invalid http request')
				end

				if co.aid ~= 0 then lib.mem.heapf(co.who) end
				if co.varbuf.ptr ~= nil then co.varbuf:free() end
				if co.navbar.ptr ~= nil then co.navbar:free() end

			end
		end
	end;
}

local terra cfg(s: &srv, befile: rawstring)
	lib.report('configuring backends from ', befile)







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







 







>

>
>
>
>
|
>
>
>
>
>
>

<







 







|







 







>







 







|
|

|
|
|
|
|


|
|





>
>
>











|
>
>
|











|
>
>
|







 







>
>







 







>







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
..
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
..
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
..
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
...
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
204
205
...
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
...
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
	cfg: cfgcache
}

terra cfgcache:free() -- :/
	self.secret:free()
	self.instance:free()
end

terra srv:instance_timeline_fetch(r: lib.store.range): lib.mem.vec(lib.mem.ptr(lib.store.post))
	var all: lib.mem.vec(lib.mem.ptr(lib.store.post)) all:init(64)
	for i=0,self.sources.ct do var src = self.sources.ptr + i
		if src.handle ~= nil and src.backend.instance_timeline_fetch ~= nil then
			var lst = src:instance_timeline_fetch(r)
			all:assure(all.sz + lst.ct)
			for j=0, lst.ct do all:push(lst.ptr[j]) end
			lst:free()
		end
	end
	return all
end

srv.metamethods.__methodmissing = macro(function(meth, self, ...)
	local primary, ptr, stat, simple, oid = 0,1,2,3,4
	local tk, rt = primary
	local expr = {...}
	for _,f in pairs(lib.store.backend.entries) do
		local fn = f.field or f[1]
................................................................................
			elseif rt.type == 'integer' then tk = oid
			elseif rt.stat_basetype then tk = stat
			elseif rt.ptr_basetype then tk = ptr end
			break
		end
	end
	
	local r = symbol(rt)
	if tk == primary then
		return quote
			var [r]
			for i=0,self.sources.ct do var src = self.sources.ptr + i
				if src.handle ~= nil and src.backend.[meth] ~= nil then
					r = src:[meth]([expr])
					goto success
				end
			end
			lib.bail(['no active backends provide critical capability ' .. meth .. '!'])
			::success::;
		in r end
	else local ok, empty

		if tk == ptr then
			ok = `r.ptr ~= nil
			empty = `[rt]{ptr=nil,ct=0}
		elseif tk == stat then
			ok = `r.ok == true
			empty = `[rt]{ok=false,error=1}
		elseif tk == simple then
................................................................................
		elseif tk == oid then
			ok = `r ~= 0
			empty = `0
		end
		return quote
			var [r] = empty
			for i=0,self.sources.ct do var src = self.sources.ptr + i
				if src.handle ~= nil and src.backend.[meth] ~= nil then
					r = src:[meth]([expr])
					if [ok] then break
						else r = empty end
				end
			end
		in r end
	end
................................................................................
	msg: &lib.net.mg_http_message
	aid: uint64 -- 0 if logged out
	who: &lib.store.actor -- who we're logged in as, if aid ~= 0
	peer: lib.store.inet
	reqtype: lib.http.mime.t -- negotiated content type
-- cache
	navbar: lib.mem.ptr(int8)
	actorcache: lib.mem.cache(lib.mem.ptr(lib.store.actor),32) -- naive cache to avoid unnecessary queries
-- private
	varbuf: lib.mem.ptr(int8)
	vbofs: &int8
}

-- this is unfortunately necessary to work around a terra bug
-- it can't seem to handle forward-declarations of structs in C
................................................................................
end

terra convo:reroute(dest: rawstring) self:reroute_cookie(dest,nil) end
 
terra convo:complain(code: uint16, title: rawstring, msg: rawstring)
	var hdrs = array(lib.http.header { key = 'Content-Type', value = 'text/html; charset=UTF-8' })

	var ti: lib.str.acc ti:compose('error :: ', title)
	var bo: lib.str.acc bo:compose('<div class="message"><img class="icon" src="/s/warn.webp"><h1>',title,'</h1><p>',msg,'</p></div>')
	var body = data.view.docskel {
		instance = self.srv.cfg.instance;
		title = ti:finalize();
		body = bo:finalize();
		class = lib.str.plit 'error';
		navlinks = lib.coalesce(self.navbar, [lib.mem.ptr(int8)]{ptr='',ct=0});
	}

	if body.body.ptr == nil then
		body.body = lib.str.plit"i'm sorry, dave. i can't let you do that"
	end

	body:send(self.con, code, [lib.mem.ptr(lib.http.header)] {
		ptr = &hdrs[0], ct = [hdrs.type.N]
	})

	body.title:free()
	body.body:free()
end

-- CALL ONLY ONCE PER VAR
terra convo:postv(name: rawstring)
	if self.varbuf.ptr == nil then
		self.varbuf = lib.mem.heapa(int8, self.msg.body.len + self.msg.query.len)
		self.vbofs = self.varbuf.ptr
	end
	var o = lib.net.mg_http_get_var(&self.msg.body, name, self.vbofs, self.varbuf.ct - (self.vbofs - self.varbuf.ptr))
	if o > 0 then
		var r = self.vbofs
		self.vbofs = self.vbofs + o + 1
		@(self.vbofs - 1) = 0
		var norm = lib.str.normalize([lib.mem.ptr(int8)]{ptr = r, ct = o})
		return norm.ptr, norm.ct
	else return nil, 0 end
end

terra convo:getv(name: rawstring)
	if self.varbuf.ptr == nil then
		self.varbuf = lib.mem.heapa(int8, self.msg.query.len + self.msg.body.len)
		self.vbofs = self.varbuf.ptr
	end
	var o = lib.net.mg_http_get_var(&self.msg.query, name, self.vbofs, self.varbuf.ct - (self.vbofs - self.varbuf.ptr))
	if o > 0 then
		var r = self.vbofs
		self.vbofs = self.vbofs + o + 1
		@(self.vbofs - 1) = 0
		var norm = lib.str.normalize([lib.mem.ptr(int8)]{ptr = r, ct = o})
		return norm.ptr, norm.ct
	else return nil, 0 end
end

local urimatch = macro(function(uri, ptn)
	return `lib.net.mg_globmatch(ptn, [#ptn], uri.ptr, uri.ct+1)
end)

................................................................................
				var msg = [&lib.net.mg_http_message](p)
				var co = convo {
					con = con, srv = server, msg = msg;
					aid = 0, who = nil, peer = peer;
					reqtype = lib.http.mime.none;
				} co.varbuf.ptr = nil
				  co.navbar.ptr = nil
				  co.actorcache.top = 0
				  co.actorcache.cur = 0

				-- first, check for an accept header. if it's there, we need to
				-- iterate over the values and pick the highest-priority one
				do var acc = lib.http.findheader(msg, 'Accept')
					-- TODO handle q-value
					if acc.ptr ~= nil then
						var [mimevar] = [lib.mem.ref(int8)] { ptr = acc.ptr }
................................................................................
				else
					co:complain(400,'unknown method','you have submitted an invalid http request')
				end

				if co.aid ~= 0 then lib.mem.heapf(co.who) end
				if co.varbuf.ptr ~= nil then co.varbuf:free() end
				if co.navbar.ptr ~= nil then co.navbar:free() end
				co.actorcache:free()
			end
		end
	end;
}

local terra cfg(s: &srv, befile: rawstring)
	lib.report('configuring backends from ', befile)

Modified static/style.scss from [7905a3444d] to [1dafd96ed6].

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
...
410
411
412
413
414
415
416
417
418
419
420
421














































$color: hsl(323,100%,65%);
%sans { font-family: "Alegreya Sans", "Open Sans", sans-serif; }
%serif { font-family: Alegreya, GaramondNo8, "Garamond Premier Pro", "Adobe Garamond", Garamond, Junicode, serif; }
%teletype { font-family: "Inconsolata LGC", Inconsolata, monospace; font-size: 12pt !important; }

@function tone($pct) { @return adjust-color($color, $lightness: $pct) }

body {
	@extend %sans;
	background-color: adjust-color($color, $lightness: -55%);
	color: adjust-color($color, $lightness: 25%);
	font-size: 14pt;
	margin: 0;
	padding: 0;
}
a[href] {

	color: adjust-color($color, $lightness: 10%);
	&:hover {
		color: white;

		text-shadow: 0 0 15px adjust-color($color, $lightness: 20%);
	}
}
a[href^="//"],
a[href^="http://"],
a[href^="https://"] { // external link
	&:hover::after {
		color: black;
................................................................................
		padding: 0.1in;
		&:hover { font-weight: bold; }
	}
}

code {
	@extend %teletype;
	background: adjust-color($color, $lightness: -50%);
	border: 1px solid adjust-color($color, $lightness: -20%);
	padding: 2px 6px;
	text-shadow: 2px 2px black;
}



















































|



|
|





>
|


>
|







 







|
|



>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
...
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
$color: hsl(323,100%,65%);
%sans { font-family: "Alegreya Sans", "Open Sans", sans-serif; }
%serif { font-family: Alegreya, GaramondNo8, "Garamond Premier Pro", "Adobe Garamond", Garamond, Junicode, serif; }
%teletype { font-family: "Inconsolata LGC", Inconsolata, monospace; font-size: 12pt !important; }

@function tone($pct, $alpha: 0) { @return adjust-color($color, $lightness: $pct, $alpha: $alpha) }

body {
	@extend %sans;
	background-color: tone(-55%);
	color: tone(25%);
	font-size: 14pt;
	margin: 0;
	padding: 0;
}
a[href] {
	color: tone(10%);
	text-decoration-color: adjust-color($color, $lightness: 10%, $alpha: -0.5);
	&:hover {
		color: white;
		text-shadow: 0 0 15px tone(20%);
		text-decoration-color: adjust-color($color, $lightness: 10%, $alpha: -0.1);
	}
}
a[href^="//"],
a[href^="http://"],
a[href^="https://"] { // external link
	&:hover::after {
		color: black;
................................................................................
		padding: 0.1in;
		&:hover { font-weight: bold; }
	}
}

code {
	@extend %teletype;
	background: tone(-50%);
	border: 1px solid tone(-20%);
	padding: 2px 6px;
	text-shadow: 2px 2px black;
}

div.post {
	@extend %box;
	display: grid;
	grid-template-columns: 1in 1fr max-content;
	grid-template-rows: 1fr max-content;
	margin-bottom: 0.1in;
	>.avatar {
		grid-column: 1/2; grid-row: 1/2;
		width: 1in;
	}
	>a[href].username {
		display: block;
		grid-column: 1/3;
		grid-row: 2/3;
		text-align: left;
		text-decoration: none;
		padding: 0.1in;
		padding-left: 0.15in;
		>.nym { font-weight: bold; }
		color: tone(0%,-0.4);
		> span.nym { color: tone(10%) }
		> span.handle { color: tone(-5%) }
		background: linear-gradient(to right, tone(-55%), transparent);
	}
	>.content {
		grid-column: 2/4; grid-row: 1/2;
		padding: 0.2in;
		@extend %serif;
		font-size: 110%;
	}
	> a[href].permalink {
		display: block;
		grid-column: 3/4; grid-row: 2/3;
		font-size: 80%;
		text-align: right;
		padding: 0.1in;
		padding-right: 0.15in;
		font-style: italic;
		background: linear-gradient(to left, tone(-55%,-0.5), transparent);
	}
}

a[href].rawlink {
	@extend %teletype;
}

Modified store.t from [5ad659a834] to [4782518865].

1
2
3
4
5
6
7
8
9
10
..
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
..
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
..
95
96
97
98
99
100
101

102
103
104
105
106
107
108
109

110
111
112
113
114
115
116
...
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
-- vim: ft=terra
local m = {
	timepoint = uint64;
	scope = lib.enum {
		'public', 'private', 'local';
		'personal', 'direct', 'circle';
	};
	notiftype = lib.enum {
		'mention', 'like', 'rt', 'react'
	};
................................................................................
struct m.actor {
	id: uint64
	nym: str
	handle: str
	origin: uint64
	bio: str
	avatar: str
	knownsince: int64
	rights: m.rights
	key: lib.mem.ptr(uint8)

-- ephemera
	xid: str
	source: &m.source
}
................................................................................
	posts: intptr
	follows: intptr
	followers: intptr
	mutuals: intptr
}

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
................................................................................
		-- for determining session validity & caps
			-- aid:    uint64
			-- origin: inet

	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)


|







 







|







 







|







 







>


<




|
>







 







|
|




|
|







1
2
3
4
5
6
7
8
9
10
..
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
..
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
..
95
96
97
98
99
100
101
102
103
104

105
106
107
108
109
110
111
112
113
114
115
116
117
...
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
-- vim: ft=terra
local m = {
	timepoint = int64;
	scope = lib.enum {
		'public', 'private', 'local';
		'personal', 'direct', 'circle';
	};
	notiftype = lib.enum {
		'mention', 'like', 'rt', 'react'
	};
................................................................................
struct m.actor {
	id: uint64
	nym: str
	handle: str
	origin: uint64
	bio: str
	avatar: str
	knownsince: m.timepoint
	rights: m.rights
	key: lib.mem.ptr(uint8)

-- ephemera
	xid: str
	source: &m.source
}
................................................................................
	posts: intptr
	follows: intptr
	followers: intptr
	mutuals: intptr
}

struct m.range {
	mode: uint8 -- 0 == I->I, 1 == T->I, 2 == I->T, 3 == T->T
	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
	acl: str
	posted: m.timepoint
	discovered: m.timepoint

	mentions: lib.mem.ptr(uint64)
	circles: lib.mem.ptr(uint64) --only meaningful if scope is set to circle
	convo: uint64
	parent: uint64
-- ephemera
	localpost: bool
	source: &m.source
}

local cnf = terralib.memoize(function(ty,rty)
	rty = rty or ty
	return struct {
		enum: {&opaque, uint64, rawstring} -> intptr
................................................................................
		-- for determining session validity & caps
			-- aid:    uint64
			-- origin: inet

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

	post_save: {&m.source, &m.post} -> {}
	post_create: {&m.source, &m.post} -> uint64
	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(lib.mem.ptr(m.post))
	instance_timeline_fetch: {&m.source, m.range} -> lib.mem.ptr(lib.mem.ptr(m.post))
}

struct m.source {
	backend: &m.backend
	id: lib.mem.ptr(int8)
	handle: &opaque
	string: lib.mem.ptr(int8)

Modified str.t from [c8d105a016] to [c5ea2451a4].

1
2
3


4
5
6
7
8
9
10
..
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
..
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
...
114
115
116
117
118
119
120

121











122
123
124
125
126
127
128
...
212
213
214
215
216
217
218


219


220
221
222
223
224
225
226
-- vim: ft=terra
-- string.t: string classes
local util = dofile('common.lua')



local m = {
	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);
................................................................................
		terralib.types.funcpointer({&rawstring,rawstring},{int},true));
	bfmt = terralib.externfunction('sprintf',
		terralib.types.funcpointer({rawstring,rawstring},{int},true));
	span = terralib.externfunction('strspn',{rawstring, rawstring} -> rawstring);
}

do local strptr = (lib.mem.ptr(int8))

	local byteptr = (lib.mem.ptr(uint8))
	strptr.metamethods.__cast = function(from,to,e)
		if from == &int8 then
			return `strptr {ptr = e, ct = m.sz(e)}
		elseif to == &int8 then
			return e.ptr
		end
	end

	terra strptr:cmp(other: strptr)














		var sz = lib.math.biggest(self.ct, other.ct)
		for i = 0, sz do
			if self.ptr[i] == 0 and other.ptr[i] == 0 then return true end
			if self.ptr[i] ~= other.ptr[i] then return false end
		end
		return true
	end








	terra byteptr:cmp(other: byteptr)
		var sz = lib.math.biggest(self.ct, other.ct)
		for i = 0, sz do
			if self.ptr[i] == 0 and other.ptr[i] == 0 then return true end
			if self.ptr[i] ~= other.ptr[i] then return false end
		end
		return true
	end
end



















struct m.acc {
	buf: rawstring
	sz: intptr
	run: intptr
	space: intptr
}
................................................................................
	--lib.dbg('freeing string accumulator')
	if self.buf ~= nil and self.space > 0 then
		lib.mem.heapf(self.buf)
	end
end;

terra m.acc:crush()
	lib.dbg('crushing string accumulator')
	self.buf = [rawstring](lib.mem.heapr_raw(self.buf, self.sz))
	self.space = self.sz
	return self
end;

terra m.acc:finalize()
	lib.dbg('finalizing string accumulator')
	self:crush()
	var pt: lib.mem.ptr(int8)
	pt.ptr = self.buf
	pt.ct = self.sz
	self.buf = nil
	self.sz = 0
	return pt
................................................................................
	lib.mem.cpy(self.buf + self.sz, str, len)
	self.sz = self.sz + len
	self.buf[self.sz] = 0
	return self
end;

m.lit = macro(function(str)

	return `[lib.mem.ref(int8)] {ptr = [str:asvalue()], ct = [#(str:asvalue())]}











end)

m.acc.methods.lpush = macro(function(self,str)
	return `self:push([str:asvalue()], [#(str:asvalue())]) end)
m.acc.methods.ppush = terra(self: &m.acc, str: lib.mem.ptr(int8))
	self:push(str.ptr, str.ct)            return self end;
m.acc.methods.merge = terra(self: &m.acc, str: lib.mem.ptr(int8))
................................................................................
			local str,sz = v[1],v[2]
			if type(sz) == 'number' then
				memreq_const = memreq_const + sz
			elseif type(sz:asvalue()) == 'number' then
				memreq_const = memreq_const + sz:asvalue()
			else memreq_exp = `[sz] + [memreq_exp] end



			cpy = quote [kp] ; [ptr] = [&int8](lib.mem.cpy([ptr], str, sz)) end


			if ty.ptr_basetype then
				cpy = quote [cpy]; [box].obj.[k].ct = sz end
			end
			isnull = `[str] == nil
		else
			memreq_exp = `(m.sz(v) + 1) + [memreq_exp] -- make room for NUL
			isnull = `v == nil



>
>







 







>










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







>
>
>
>
>
>
>










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







 







|






|







 







>
|
>
>
>
>
>
>
>
>
>
>
>







 







>
>
|
>
>







1
2
3
4
5
6
7
8
9
10
11
12
..
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
...
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
...
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
...
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
-- vim: ft=terra
-- string.t: string classes
local util = dofile('common.lua')
local pstr = lib.mem.ptr(int8)
local pref = lib.mem.ref(int8)

local m = {
	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);
................................................................................
		terralib.types.funcpointer({&rawstring,rawstring},{int},true));
	bfmt = terralib.externfunction('sprintf',
		terralib.types.funcpointer({rawstring,rawstring},{int},true));
	span = terralib.externfunction('strspn',{rawstring, rawstring} -> rawstring);
}

do local strptr = (lib.mem.ptr(int8))
	local strref = (lib.mem.ref(int8))
	local byteptr = (lib.mem.ptr(uint8))
	strptr.metamethods.__cast = function(from,to,e)
		if from == &int8 then
			return `strptr {ptr = e, ct = m.sz(e)}
		elseif to == &int8 then
			return e.ptr
		end
	end

	terra strptr:cmp(other: strptr)
		if self.ptr == nil and other.ptr == nil then return true end
		if self.ptr == nil or other.ptr == nil then return false end

		var sz = lib.math.biggest(self.ct, other.ct)
		for i = 0, sz do
			if self.ptr[i] == 0 and other.ptr[i] == 0 then return true end
			if self.ptr[i] ~= other.ptr[i] then return false end
		end
		return true
	end
	terra strref:cmp(other: strref)
		if self.ptr == nil and other.ptr == nil then return true end
		if self.ptr == nil or other.ptr == nil then return false end

		var sz = lib.math.biggest(self.ct, other.ct)
		for i = 0, sz do
			if self.ptr[i] == 0 and other.ptr[i] == 0 then return true end
			if self.ptr[i] ~= other.ptr[i] then return false end
		end
		return true
	end

	strptr.methods.cmpl = macro(function(self,other)
		return `self:cmp(strptr { ptr = [other:asvalue()], ct = [#(other:asvalue())] })
	end)
	strref.methods.cmpl = macro(function(self,other)
		return `self:cmp(strref { ptr = [other:asvalue()], ct = [#(other:asvalue())] })
	end)

	terra byteptr:cmp(other: byteptr)
		var sz = lib.math.biggest(self.ct, other.ct)
		for i = 0, sz do
			if self.ptr[i] == 0 and other.ptr[i] == 0 then return true end
			if self.ptr[i] ~= other.ptr[i] then return false end
		end
		return true
	end
end

terra m.normalize(s: pstr)
	var c: rawstring = s.ptr
	var n: rawstring = s.ptr
	while n < s.ptr + s.ct do
		while @n == 0 or @n == @'\r' do
			n = n + 1
			if n > s.ptr + s.ct then
				c = c + 1 goto done
			end
		end
		@c = @n
		c = c + 1
		n = n + 1
	end ::done::
	@c = 0
	return pstr { ptr = s.ptr, ct = c - s.ptr }
end

struct m.acc {
	buf: rawstring
	sz: intptr
	run: intptr
	space: intptr
}
................................................................................
	--lib.dbg('freeing string accumulator')
	if self.buf ~= nil and self.space > 0 then
		lib.mem.heapf(self.buf)
	end
end;

terra m.acc:crush()
	--lib.dbg('crushing string accumulator')
	self.buf = [rawstring](lib.mem.heapr_raw(self.buf, self.sz))
	self.space = self.sz
	return self
end;

terra m.acc:finalize()
	--lib.dbg('finalizing string accumulator')
	self:crush()
	var pt: lib.mem.ptr(int8)
	pt.ptr = self.buf
	pt.ct = self.sz
	self.buf = nil
	self.sz = 0
	return pt
................................................................................
	lib.mem.cpy(self.buf + self.sz, str, len)
	self.sz = self.sz + len
	self.buf[self.sz] = 0
	return self
end;

m.lit = macro(function(str)
	if str:asvalue() ~= nil then
		return `[lib.mem.ref(int8)] {ptr = [str:asvalue()], ct = [#(str:asvalue())]}
	else
		return `[lib.mem.ref(int8)] {ptr = nil, ct = 0}
	end
end)

m.plit = macro(function(str)
	if str:asvalue() ~= nil then
		return `[lib.mem.ptr(int8)] {ptr = [str:asvalue()], ct = [#(str:asvalue())]}
	else
		return `[lib.mem.ptr(int8)] {ptr = nil, ct = 0}
	end
end)

m.acc.methods.lpush = macro(function(self,str)
	return `self:push([str:asvalue()], [#(str:asvalue())]) end)
m.acc.methods.ppush = terra(self: &m.acc, str: lib.mem.ptr(int8))
	self:push(str.ptr, str.ct)            return self end;
m.acc.methods.merge = terra(self: &m.acc, str: lib.mem.ptr(int8))
................................................................................
			local str,sz = v[1],v[2]
			if type(sz) == 'number' then
				memreq_const = memreq_const + sz
			elseif type(sz:asvalue()) == 'number' then
				memreq_const = memreq_const + sz:asvalue()
			else memreq_exp = `[sz] + [memreq_exp] end

			cpy = quote [kp] ;
				--lib.io.fmt('encapsulating string %p → %p [%s] sz %llu\n', str, [ptr], str, sz)
				[ptr] = [&int8](lib.mem.cpy([ptr], str, sz))
				--lib.io.fmt(' :: encapsulated string %p [%s]\n', box.obj.[k],box.obj.[k])
			end
			if ty.ptr_basetype then
				cpy = quote [cpy]; [box].obj.[k].ct = sz end
			end
			isnull = `[str] == nil
		else
			memreq_exp = `(m.sz(v) + 1) + [memreq_exp] -- make room for NUL
			isnull = `v == nil

Modified tpl.t from [3cd51c8b03] to [3e776df34f].

52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
..
76
77
78
79
80
81
82



83
84
85
86
87
88
89
90
91


92
93
94
95
96
97
98
	local rec = terralib.types.newstruct(string.format('template<%s>',tplspec.id or ''))
	local symself = symbol(&rec)
	do local kfac = {}
		for afterseg,key in pairs(fields) do
			if not kfac[key] then
				rec.entries[#rec.entries + 1] = {
					field = key;
					type = rawstring;
				}
			end
			kfac[key] = (kfac[key] or 0) + 1
		end
		for key, fac in pairs(kfac) do
			tallyup[#tallyup + 1] = quote
				[runningtally] = [runningtally] + lib.str.sz([symself].[key])*fac
			end
		end
	end

	local copiers = {}
	local senders = {}
	local appenders = {}
................................................................................
	local accumulator = symbol(&lib.str.acc)
	local destcon = symbol(&lib.net.mg_connection)
	for idx, seg in ipairs(segs) do
		copiers[#copiers+1] = quote [cpypos] = lib.mem.cpy([cpypos], [&opaque]([seg]), [#seg]) end
		senders[#senders+1] = quote lib.net.mg_send([destcon], [seg], [#seg]) end
		appenders[#appenders+1] = quote [accumulator]:push([seg], [#seg]) end
		if fields[idx] then



			copiers[#copiers+1] = quote
				[cpypos] = lib.mem.cpy([cpypos],
					[&opaque](symself.[fields[idx]]),
					lib.str.sz(symself.[fields[idx]]))
			end
			senders[#senders+1] = quote
				lib.net.mg_send([destcon],
					symself.[fields[idx]],
					lib.str.sz(symself.[fields[idx]]))


			end
		end
	end

	local tid = tplspec.id or '<anonymous>'
	rec.methods.tostr = terra([symself])
		lib.dbg(['compiling template ' .. tid])







|






|







 







>
>
>

|
<
<


|
<
|
>
>







52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
..
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
	local rec = terralib.types.newstruct(string.format('template<%s>',tplspec.id or ''))
	local symself = symbol(&rec)
	do local kfac = {}
		for afterseg,key in pairs(fields) do
			if not kfac[key] then
				rec.entries[#rec.entries + 1] = {
					field = key;
					type = lib.mem.ptr(int8);
				}
			end
			kfac[key] = (kfac[key] or 0) + 1
		end
		for key, fac in pairs(kfac) do
			tallyup[#tallyup + 1] = quote
				[runningtally] = [runningtally] + ([symself].[key].ct)*fac
			end
		end
	end

	local copiers = {}
	local senders = {}
	local appenders = {}
................................................................................
	local accumulator = symbol(&lib.str.acc)
	local destcon = symbol(&lib.net.mg_connection)
	for idx, seg in ipairs(segs) do
		copiers[#copiers+1] = quote [cpypos] = lib.mem.cpy([cpypos], [&opaque]([seg]), [#seg]) end
		senders[#senders+1] = quote lib.net.mg_send([destcon], [seg], [#seg]) end
		appenders[#appenders+1] = quote [accumulator]:push([seg], [#seg]) end
		if fields[idx] then
			--local fsz = `lib.str.sz(symself.[fields[idx]])
			local fval = `symself.[fields[idx]].ptr
			local fsz = `symself.[fields[idx]].ct
			copiers[#copiers+1] = quote
				[cpypos] = lib.mem.cpy([cpypos], [&opaque]([fval]), [fsz])


			end
			senders[#senders+1] = quote
				lib.net.mg_send([destcon], [fval], [fsz])

			end
			appenders[#appenders+1] = quote
				[accumulator]:push([fval], [fsz])
			end
		end
	end

	local tid = tplspec.id or '<anonymous>'
	rec.methods.tostr = terra([symself])
		lib.dbg(['compiling template ' .. tid])

Modified view/tweet.tpl from [4ad2e9eaa9] to [e1b2184d52].

1
2
3
4
5
6
7
8
9

10
11

12
<div class="post">
	<div class="detail">
		<a class="username" href="@link">
			<span class="nym">@nym</span> <span class="handle">[@xid]</span>
		</div>
		<div class="when">@when</div>
	</div>
	<div class="content">
		<img class="avatar" src="@avatar">

		<div class="text">@text</div>
	</div>

</div>

|
|
|
|
<
<

<
>


>

1
2
3
4
5


6

7
8
9
10
11
<div class="post">
	<img class="avatar" src="@avatar">
	<a class="username" href="/@acctlink">
		<span class="nym">@nym</span> [<span class="handle">@xid</span>]
	</a>


	<div class="content">

		<div class="subject">@subject</div>
		<div class="text">@text</div>
	</div>
	<a class="permalink" href="@permalink">@when</a>
</div>