| 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: |
f9559a83fc2d82b62dc5aed0a13e9802 |
| User & Date: | lexi on 2020-12-25 23:37:28 |
| Other Links: | manifest | tags |
|
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 | |
Added acl.t version [3939510d0a].
1 +-- vim: ft=terra
Modified backend/pgsql.t from [0ea1a47601] to [1740166796].
1 1 -- vim: ft=terra 2 +local pstring = lib.mem.ptr(int8) 3 +local binblob = lib.mem.ptr(uint8) 2 4 local queries = { 3 5 conf_get = { 4 6 params = {rawstring}, sql = [[ 5 7 select value from parsav_config 6 8 where key = $1::text limit 1 7 9 ]]; 8 10 }; ................................................................................ 20 22 delete from parsav_config where 21 23 key = $1::text 22 24 ]]; 23 25 }; 24 26 25 27 actor_fetch_uid = { 26 28 params = {uint64}, sql = [[ 27 - select 28 - id, nym, handle, origin, bio, 29 - avataruri, rank, quota, key, 30 - extract(epoch from knownsince)::bigint 29 + select a.id, a.nym, a.handle, a.origin, a.bio, 30 + a.avataruri, a.rank, a.quota, a.key, 31 + extract(epoch from a.knownsince)::bigint, 32 + coalesce(a.handle || '@' || s.domain, 33 + '@' || a.handle) as xid 31 34 32 - from parsav_actors 33 - where id = $1::bigint 35 + from parsav_actors as a 36 + left join parsav_servers as s 37 + on a.origin = s.id 38 + where a.id = $1::bigint 34 39 ]]; 35 40 }; 36 41 37 42 actor_fetch_xid = { 38 - params = {lib.mem.ptr(int8)}, sql = [[ 43 + params = {pstring}, sql = [[ 39 44 select a.id, a.nym, a.handle, a.origin, a.bio, 40 45 a.avataruri, a.rank, a.quota, a.key, 41 - extract(epoch from knownsince)::bigint, 46 + extract(epoch from a.knownsince)::bigint, 42 47 coalesce(a.handle || '@' || s.domain, 43 48 '@' || a.handle) as xid, 44 49 45 50 coalesce(s.domain, 46 51 (select value from parsav_config 47 52 where key='domain' limit 1)) as domain 48 53 ................................................................................ 55 60 (a.origin is null and 56 61 $1::text = a.handle or 57 62 $1::text = ('@' || a.handle)) 58 63 ]]; 59 64 }; 60 65 61 66 actor_auth_pw = { 62 - params = {lib.mem.ptr(int8),rawstring,lib.mem.ptr(int8),lib.store.inet}, sql = [[ 67 + params = {pstring,rawstring,pstring,lib.store.inet}, sql = [[ 63 68 select a.aid from parsav_auth as a 64 69 left join parsav_actors as u on u.id = a.uid 65 70 where (a.uid is null or u.handle = $1::text or ( 66 71 a.uid = 0 and a.name = $1::text 67 72 )) and 68 73 (a.kind = 'trust' or (a.kind = $2::text and a.cred = $3::bytea)) and 69 74 (a.netmask is null or a.netmask >> $4::inet) ................................................................................ 83 88 ]]; 84 89 }; 85 90 86 91 actor_enum = { 87 92 params = {}, sql = [[ 88 93 select a.id, a.nym, a.handle, a.origin, a.bio, 89 94 a.avataruri, a.rank, a.quota, a.key, 90 - extract(epoch from knownsince)::bigint, 95 + extract(epoch from a.knownsince)::bigint, 91 96 coalesce(a.handle || '@' || s.domain, 92 97 '@' || a.handle) as xid 93 98 from parsav_actors a 94 99 left join parsav_servers s on s.id = a.origin 95 100 ]]; 96 101 }; 97 102 ................................................................................ 136 141 ]]; -- cheat 137 142 }; 138 143 139 144 actor_session_fetch = { 140 145 params = {uint64, lib.store.inet}, sql = [[ 141 146 select a.id, a.nym, a.handle, a.origin, a.bio, 142 147 a.avataruri, a.rank, a.quota, a.key, 143 - extract(epoch from knownsince)::bigint, 148 + extract(epoch from a.knownsince)::bigint, 144 149 coalesce(a.handle || '@' || s.domain, 145 150 '@' || a.handle) as xid, 146 151 147 152 au.restrict, 148 153 array['post' ] <@ au.restrict as can_post, 149 154 array['edit' ] <@ au.restrict as can_edit, 150 155 array['acct' ] <@ au.restrict as can_acct, ................................................................................ 156 161 left join parsav_actors a on au.uid = a.id 157 162 left join parsav_servers s on a.origin = s.id 158 163 159 164 where au.aid = $1::bigint and au.blacklist = false and 160 165 (au.netmask is null or au.netmask >> $2::inet) 161 166 ]]; 162 167 }; 168 + 169 + post_create = { 170 + params = {uint64, rawstring, rawstring, rawstring}, sql = [[ 171 + insert into parsav_posts ( 172 + author, subject, acl, body, 173 + posted, discovered, 174 + circles, mentions 175 + ) values ( 176 + $1::bigint, case when $2::text = '' then null else $2::text end, 177 + $3::text, $4::text, 178 + now(), now(), array[]::bigint[], array[]::bigint[] 179 + ) returning id 180 + ]]; -- TODO array handling 181 + }; 182 + 183 + instance_timeline_fetch = { 184 + params = {uint64, uint64, uint64, uint64}, sql = [[ 185 + select true, 186 + p.id, p.author, p.subject, p.acl, p.body, 187 + extract(epoch from p.posted )::bigint, 188 + extract(epoch from p.discovered)::bigint, 189 + p.parent, null::text 190 + from parsav_posts as p 191 + inner join parsav_actors as a on p.author = a.id 192 + where 193 + ($1::bigint = 0 or p.posted <= to_timestamp($1::bigint)) and 194 + ($2::bigint = 0 or to_timestamp($2::bigint) < p.posted) and 195 + (a.origin is null) 196 + order by (p.posted, p.discovered) desc 197 + limit case when $3::bigint = 0 then null 198 + else $3::bigint end 199 + offset $4::bigint 200 + ]] 201 + }; 163 202 } 203 + --($5::bool = false or p.parent is null) and 164 204 165 205 local struct pqr { 166 206 sz: intptr 167 207 res: &lib.pq.PGresult 168 208 } 169 209 terra pqr:free() if self.sz > 0 then lib.pq.PQclear(self.res) end end 170 210 terra pqr:null(row: intptr, col: intptr) ................................................................................ 278 318 ;[pqt[lib.store.inet](false)]([args[i]], [&uint8](&ipbuf)) 279 319 in &ipbuf[0] end 280 320 dumpers[#dumpers+1] = `lib.io.fmt([tostring(i)..'. got inet\n']) 281 321 elseif ty.ptr_basetype == int8 or ty.ptr_basetype == uint8 then 282 322 counters[i] = `[args[i]].ct 283 323 casts[i] = `[&int8]([args[i]].ptr) 284 324 dumpers[#dumpers+1] = `lib.io.fmt([tostring(i)..'. got ptr %llu %.*s\n'], [args[i]].ct, [args[i]].ct, [args[i]].ptr) 325 + elseif ty.ptr_basetype == bool then 326 + counters[i] = `1 327 + casts[i] = `[&int8]([args[i]].ptr) 328 + -- dumpers[#dumpers+1] = `lib.io.fmt([tostring(i)..'. got bool = %hhu\n'], @[args[i]].ptr) 285 329 elseif ty:isintegral() then 286 330 counters[i] = ty.bytes 287 331 casts[i] = `[&int8](&[args[i]]) 288 332 dumpers[#dumpers+1] = `lib.io.fmt([tostring(i)..'. got int %llu\n'], [args[i]]) 289 333 fixers[#fixers + 1] = quote 290 - --lib.io.fmt('uid=%llu(%llx)\n',[args[i]],[args[i]]) 291 334 [args[i]] = lib.math.netswap(ty, [args[i]]) 292 335 end 293 336 end 294 337 end 295 338 296 339 terra q.exec(src: &lib.store.source, [args]) 297 340 var params = arrayof([&int8], [casts]) ................................................................................ 314 357 return pqr {0, nil} 315 358 else 316 359 return pqr {ct, res} 317 360 end 318 361 end 319 362 end 320 363 364 +local terra row_to_post(r: &pqr, row: intptr): lib.mem.ptr(lib.store.post) 365 + --lib.io.fmt("body ptr %p len %llu\n", r:string(row,5), r:len(row,5)) 366 + --lib.io.fmt("acl ptr %p len %llu\n", r:string(row,4), r:len(row,4)) 367 + var subj: rawstring, sblen: intptr 368 + if r:null(row,3) 369 + then subj = nil sblen = 0 370 + else subj = r:string(row,3) sblen = r:len(row,3)+1 371 + end 372 + var p = [ lib.str.encapsulate(lib.store.post, { 373 + subject = { `subj, `sblen }; 374 + acl = {`r:string(row,4), `r:len(row,4)+1}; 375 + body = {`r:string(row,5), `r:len(row,5)+1}; 376 + --convoheaduri = { `nil, `0 }; --FIXME 377 + }) ] 378 + p.ptr.id = r:int(uint64,row,1) 379 + p.ptr.author = r:int(uint64,row,2) 380 + p.ptr.posted = r:int(uint64,row,6) 381 + p.ptr.discovered = r:int(uint64,row,7) 382 + if r:null(row,8) 383 + then p.ptr.parent = 0 384 + else p.ptr.parent = r:int(uint64,row,8) 385 + end 386 + p.ptr.localpost = r:bool(row,0) 387 + 388 + return p 389 +end 321 390 local terra row_to_actor(r: &pqr, row: intptr): lib.mem.ptr(lib.store.actor) 322 391 var a: lib.mem.ptr(lib.store.actor) 323 - 324 - if r:cols() >= 9 then 325 - a = [ lib.str.encapsulate(lib.store.actor, { 326 - nym = {`r:string(row,1), `r:len(row,1)+1}; 327 - bio = {`r:string(row,4), `r:len(row,4)+1}; 328 - avatar = {`r:string(row,5), `r:len(row,5)+1}; 329 - handle = {`r:string(row, 2); `r:len(row,2) + 1}; 330 - xid = {`r:string(row, 10); `r:len(row,10) + 1}; 331 - }) ] 332 - else 333 - a = [ lib.str.encapsulate(lib.store.actor, { 334 - nym = {`r:string(row,1), `r:len(row,1)+1}; 335 - bio = {`r:string(row,4), `r:len(row,4)+1}; 336 - avatar = {`r:string(row,5), `r:len(row,5)+1}; 337 - handle = {`r:string(row, 2); `r:len(row,2) + 1}; 338 - }) ] 339 - a.ptr.xid = nil 392 + var av: rawstring, avlen: intptr 393 + var nym: rawstring, nymlen: intptr 394 + var bio: rawstring, biolen: intptr 395 + if r:null(row,5) then avlen = 0 av = nil else 396 + av = r:string(row,5) 397 + avlen = r:len(row,5)+1 398 + end 399 + if r:null(row,1) then nymlen = 0 nym = nil else 400 + nym = r:string(row,1) 401 + nymlen = r:len(row,1)+1 402 + end 403 + if r:null(row,4) then biolen = 0 bio = nil else 404 + bio = r:string(row,4) 405 + biolen = r:len(row,4)+1 340 406 end 407 + a = [ lib.str.encapsulate(lib.store.actor, { 408 + nym = {`nym, `nymlen}; 409 + bio = {`bio, `biolen}; 410 + avatar = {`av,`avlen}; 411 + handle = {`r:string(row, 2); `r:len(row,2) + 1}; 412 + xid = {`r:string(row, 10); `r:len(row,10) + 1}; 413 + }) ] 341 414 a.ptr.id = r:int(uint64, row, 0); 342 415 a.ptr.rights = lib.store.rights_default(); 343 416 a.ptr.rights.rank = r:int(uint16, row, 6); 344 417 a.ptr.rights.quota = r:int(uint32, row, 7); 345 418 a.ptr.knownsince = r:int(int64,row, 9); 346 419 if r:null(row,8) then 347 420 a.ptr.key.ct = 0 a.ptr.key.ptr = nil ................................................................................ 550 623 551 624 return au, a 552 625 end 553 626 554 627 ::fail:: return [lib.stat (lib.store.auth) ] { ok = false }, 555 628 [lib.mem.ptr(lib.store.actor)] { ptr = nil, ct = 0 } 556 629 end]; 630 + 631 + post_create = [terra( 632 + src: &lib.store.source, 633 + post: &lib.store.post 634 + ): uint64 635 + var r = queries.post_create.exec(src,post.author,post.subject,post.acl,post.body) 636 + if r.sz == 0 then return 0 end 637 + defer r:free() 638 + var id = r:int(uint64,0,0) 639 + return id 640 + end]; 641 + 642 + instance_timeline_fetch = [terra(src: &lib.store.source, rg: lib.store.range) 643 + var r = pqr { sz = 0 } 644 + if rg.mode == 0 then 645 + r = queries.instance_timeline_fetch.exec(src,rg.from_time,rg.to_time,0,0) 646 + elseif rg.mode == 1 then 647 + r = queries.instance_timeline_fetch.exec(src,rg.from_time,0,rg.to_idx,0) 648 + elseif rg.mode == 2 then 649 + r = queries.instance_timeline_fetch.exec(src,0,rg.to_time,0,rg.from_idx) 650 + elseif rg.mode == 3 then 651 + r = queries.instance_timeline_fetch.exec(src,0,0,rg.to_idx,rg.from_idx) 652 + end 653 + 654 + var ret: lib.mem.ptr(lib.mem.ptr(lib.store.post)) ret:init(r.sz) 655 + for i=0,r.sz do ret.ptr[i] = row_to_post(&r, i) end -- MUST FREE ALL 656 + 657 + return ret 658 + end]; 557 659 } 558 660 559 661 return b
Modified mem.t from [e41dd1991a] to [15de92d877].
14 14 m.heapa = macro(function(ty, sz) 15 15 local p = m.ptr(ty:astype()) 16 16 return `p { 17 17 ptr = [&ty:astype()](m.heapa_raw(sizeof(ty) * sz)); 18 18 ct = sz; 19 19 } 20 20 end) 21 + 22 +function m.cache(ty,sz) 23 + sz = sz or 32 24 + local struct c { 25 + store: ty[sz] 26 + top: intptr 27 + cur: intptr 28 + } 29 + c.name = string.format('cache<%s,%u>', tostring(ty), sz) 30 + terra c:insert(v: ty) 31 + if [ty.ptr_basetype ~= nil] then 32 + if self.cur < self.top then self.store[self.cur]:free() end 33 + end 34 + self.store[self.cur] = v 35 + self.top = lib.math.biggest(self.top, self.cur + 1) 36 + self.cur = (self.cur + 1) % sz 37 + return v 38 + end 39 + c.metamethods.__apply = terra(self: &c, idx: intptr) return &self.store[idx] end 40 + if ty.ptr_basetype then 41 + terra c:free() 42 + for i=0,self.top do self.store[i]:free() end 43 + end 44 + end 45 + return c 46 +end 21 47 22 48 local function mkptr(ty, dyn) 23 49 local t = terralib.types.newstruct(string.format('%s<%s>', dyn and 'ptr' or 'ref', ty)) 24 50 t.entries = { 25 51 {'ptr', &ty}; 26 52 {'ct', intptr}; 27 53 } 28 54 t.ptr_basetype = ty 29 55 local recurse = false 30 - if ty:isstruct() then 31 - if ty.methods.free then recurse = true end 32 - end 56 + --if ty:isstruct() then 57 + --if ty.methods.free then recurse = true end 58 + --end 33 59 t.metamethods.__not = macro(function(self) 34 60 return `self.ptr 35 61 end) 36 62 if dyn then 37 63 t.methods = { 38 64 free = terra(self: &t): bool 39 65 [recurse and quote ................................................................................ 43 69 m.heapf(self.ptr) 44 70 self.ct = 0 45 71 return true 46 72 end 47 73 return false 48 74 end; 49 75 init = terra(self: &t, newct: intptr): bool 76 + if newct == 0 then self.ct = 0 self.ptr = nil return false end 50 77 var nv = [&ty](m.heapa_raw(sizeof(ty) * newct)) 51 78 if nv ~= nil then 52 79 self.ptr = nv 53 80 self.ct = newct 54 81 return true 55 82 else return false end 56 83 end; ................................................................................ 105 132 {field = 'storage', type = m.ptr(ty)}; 106 133 {field = 'sz', type = intptr}; 107 134 {field = 'run', type = intptr}; 108 135 } 109 136 local terra biggest(a: intptr, b: intptr) 110 137 if a > b then return a else return b end 111 138 end 139 + terra v:init(run: intptr): bool 140 + if not self.storage:init(run) then return false end 141 + self.run = run 142 + self.sz = 0 143 + return true 144 + end; 112 145 terra v:assure(n: intptr) 113 146 if self.storage.ct < n then 114 147 self.storage:resize(biggest(n, self.storage.ct + self.run)) 115 148 end 116 149 end 117 - v.methods = { 118 - init = terra(self: &v, run: intptr): bool 119 - if not self.storage:init(run) then return false end 120 - self.run = run 121 - self.sz = 0 122 - return true 123 - end; 124 - new = terra(self: &v): &ty 125 - self:assure(self.sz + 1) 126 - self.sz = self.sz + 1 127 - return self.storage.ptr + (self.sz - 1) 128 - end; 129 - push = terra(self: &v, val: ty) 130 - self:assure(self.sz + 1) 131 - self.storage.ptr[self.sz] = val 132 - self.sz = self.sz + 1 133 - end; 134 - free = terra(self: &v) self.storage:free() end; 135 - last = terra(self: &v, idx: intptr): &ty 136 - if self.sz > idx then 137 - return self.storage.ptr + (self.sz - (idx+1)) 138 - else lib.bail('vector underrun!') end 139 - end; 140 - crush = terra(self: &v) 141 - self.storage:resize(self.sz) 142 - return self.storage 143 - end; 144 - } 150 + terra v:new(): &ty 151 + self:assure(self.sz + 1) 152 + self.sz = self.sz + 1 153 + return self.storage.ptr + (self.sz - 1) 154 + end; 155 + terra v:push(val: ty) 156 + self:assure(self.sz + 1) 157 + self.storage.ptr[self.sz] = val 158 + self.sz = self.sz + 1 159 + end; 160 + terra v:free() self.storage:free() end; 161 + terra v:last(idx: intptr): &ty 162 + if self.sz > idx then 163 + return self.storage.ptr + (self.sz - (idx+1)) 164 + else lib.bail('vector underrun!') end 165 + end; 166 + terra v:crush() 167 + self.storage:resize(self.sz) 168 + return self.storage 169 + end; 145 170 v.metamethods.__apply = terra(self: &v, idx: intptr): &ty -- no index?? 146 171 if self.sz > idx then 147 172 return self.storage.ptr + idx 148 173 else lib.bail('vector overrun!') end 149 174 end 150 175 return v 151 176 end) 152 177 153 178 return m
Modified parsav.t from [11ad9b3025] to [579976ae23].
90 90 if c == true then r = i else r = e end 91 91 in r end 92 92 end); 93 93 coalesce = macro(function(...) 94 94 local args = {...} 95 95 local ty = args[1].tree.type 96 96 local val = symbol(ty) 97 - local empty if ty.type == 'integer' 98 - then empty = `0 99 - else empty = `nil 100 - end 97 + local empty 98 + if ty.ptr_basetype then empty = `[ty]{ptr=nil,ct=0} 99 + elseif ty.type == 'integer' then empty = `0 100 + else empty = `nil end 101 101 local exp = quote val = [empty] end 102 102 103 103 for i=#args, 1, -1 do 104 104 local v = args[i] 105 - exp = quote 106 - if [v] ~= [empty] 107 - then val = v 108 - else [exp] 105 + if ty.ptr_basetype then 106 + exp = quote 107 + if [v].ptr ~= nil 108 + then val = v 109 + else [exp] 110 + end 111 + end 112 + else 113 + exp = quote 114 + if [v] ~= [empty] 115 + then val = v 116 + else [exp] 117 + end 109 118 end 110 119 end 111 120 end 112 121 113 122 local q = quote 114 123 var [val] 115 124 [exp] ................................................................................ 285 294 lib.b64 = lib.loadlib('mbedtls','mbedtls/base64.h') 286 295 lib.net = lib.loadlib('mongoose','mongoose.h') 287 296 lib.pq = lib.loadlib('libpq','libpq-fe.h') 288 297 289 298 lib.load { 290 299 'mem', 'math', 'str', 'file', 'crypt'; 291 300 'http', 'session', 'tpl', 'store'; 301 + 302 + 'smackdown'; -- md-alike parser 292 303 } 293 304 294 305 local be = {} 295 306 for _, b in pairs(config.backends) do 296 307 be[#be+1] = terralib.loadfile('backend/' .. b .. '.t')() 297 308 end 298 309 lib.store.backends = global(`array([be])) ................................................................................ 324 335 lib.load { 325 336 'srv'; 326 337 'render:nav'; 327 338 'render:login'; 328 339 'render:profile'; 329 340 'render:userpage'; 330 341 'render:compose'; 342 + 'render:tweet'; 343 + 'render:timeline'; 331 344 'route'; 332 345 } 333 346 334 347 do 335 348 local p = string.format('parsav: %s\nbuilt on %s\n', config.build.str, config.build.when) 336 349 terra version() lib.io.send(1, p, [#p]) end 337 350 end ................................................................................ 444 457 if bflag('dump-config','C') then 445 458 print(util.dump(config)) 446 459 os.exit(0) 447 460 end 448 461 449 462 local holler = print 450 463 local out = config.exe and 'parsav' or ('parsav.' .. config.outform) 451 -local linkargs = {'-O4'} 464 +local linkargs = {} 452 465 453 466 if bflag('quiet','q') then holler = function() end end 454 467 if bflag('asan','s') then linkargs[#linkargs+1] = '-fsanitize=address' end 455 468 if bflag('lsan','S') then linkargs[#linkargs+1] = '-fsanitize=leak' end 456 469 457 470 if config.posix then 458 471 linkargs[#linkargs+1] = '-pthread'
Modified render/compose.t from [7a7e8f43ac] to [0685338a7e].
9 9 acl = lib.trn(target == nil, 'all', 'mentioned'); -- TODO default acl setting? 10 10 handle = co.who.handle; 11 11 } 12 12 end 13 13 var cotxt = form:tostr() defer cotxt:free() 14 14 15 15 var doc = data.view.docskel { 16 - instance = co.srv.cfg.instance.ptr; 17 - title = 'compose'; 18 - body = cotxt.ptr; 19 - class = 'compose'; 20 - navlinks = co.navbar.ptr; 16 + instance = co.srv.cfg.instance; 17 + title = lib.str.plit 'compose'; 18 + body = cotxt; 19 + class = lib.str.plit 'compose'; 20 + navlinks = co.navbar; 21 21 } 22 22 23 23 var hdrs = array( 24 24 lib.http.header { 'Content-Type', 'text/html; charset=UTF-8' } 25 25 ) 26 26 doc:send(co.con,200,[lib.mem.ptr(lib.http.header)] {ct = 1, ptr = &hdrs[0]}) 27 27 end 28 28 29 29 return render_compose
Modified render/login.t from [0d69ec17a3] to [671b92715b].
1 1 -- vim: ft=terra 2 +local pstr = lib.mem.ptr(int8) 3 +local P = lib.str.plit 2 4 local terra 3 -login_form(co: &lib.srv.convo, user: &lib.store.actor, creds: &lib.store.credset, msg: &int8) 5 +login_form(co: &lib.srv.convo, user: &lib.store.actor, creds: &lib.store.credset, msg: pstr) 4 6 var doc = data.view.docskel { 5 - instance = co.srv.cfg.instance.ptr; 6 - title = 'instance logon'; 7 - class = 'login'; 8 - navlinks = co.navbar.ptr; 7 + instance = co.srv.cfg.instance; 8 + title = lib.str.plit 'instance logon'; 9 + class = lib.str.plit 'login'; 10 + navlinks = co.navbar; 9 11 } 10 12 11 13 if user == nil then 12 14 var form = data.view.login_username { 13 15 loginmsg = msg; 14 16 } 15 - if form.loginmsg == nil then 16 - form.loginmsg = 'identify yourself for access to this instance.' 17 + if form.loginmsg.ptr == nil then 18 + form.loginmsg = lib.str.plit 'identify yourself for access to this instance.' 17 19 end 18 - var formtxt = form:tostr() 19 - doc.body = formtxt.ptr 20 + doc.body = form:tostr() 20 21 elseif creds:sz() == 0 then 21 22 co:complain(403,'access denied','your host is not eligible to authenticate as this user') 22 23 return 23 24 elseif creds:sz() == 1 then 24 25 if creds.trust() then 25 26 -- TODO log in immediately 26 27 return ................................................................................ 27 28 end 28 29 29 30 var ch = data.view.login_challenge { 30 31 handle = user.handle; 31 32 name = lib.coalesce(user.nym, user.handle); 32 33 } 33 34 if creds.pw() then 34 - ch.challenge = 'enter the password associated with your account' 35 - ch.label = 'password' 36 - ch.method = 'pw' 35 + ch.challenge = P'enter the password associated with your account' 36 + ch.label = P'password' 37 + ch.method = P'pw' 37 38 elseif creds.otp() then 38 - ch.challenge = 'enter a valid one-time password for your account' 39 - ch.label = 'OTP code' 40 - ch.method = 'otp' 39 + ch.challenge = P'enter a valid one-time password for your account' 40 + ch.label = P'OTP code' 41 + ch.method = P'otp' 41 42 elseif creds.challenge() then 42 - ch.challenge = 'sign the challenge token: <code>...</code>' 43 - ch.label = 'digest' 44 - ch.method = 'challenge' 43 + ch.challenge = P'sign the challenge token: <code>...</code>' 44 + ch.label = P'digest' 45 + ch.method = P'challenge' 45 46 else 46 47 co:complain(500,'login failure','unknown login method') 47 48 return 48 49 end 49 50 50 - doc.body = ch:tostr().ptr 51 + doc.body = ch:tostr() 51 52 else 52 53 -- pick a method 53 54 end 54 55 55 56 var hdrs = array( 56 57 lib.http.header { 'Content-Type', 'text/html; charset=UTF-8' } 57 58 ) 58 59 doc:send(co.con,200,[lib.mem.ptr(lib.http.header)] {ct = 1, ptr = &hdrs[0]}) 59 - lib.mem.heapf(doc.body) 60 + doc.body:free() 60 61 end 61 62 62 63 return login_form
Modified render/nav.t from [2d4aa38bec] to [450b73f673].
3 3 render_nav(co: &lib.srv.convo) 4 4 var t: lib.str.acc t:init(64) 5 5 if co.who ~= nil or co.srv.cfg.pol_sec == lib.srv.secmode.public then 6 6 t:lpush('<a href="/">timeline</a>') 7 7 end 8 8 if co.who ~= nil then 9 9 t:lpush('<a href="/compose">compose</a> <a href="/'):push(co.who.xid,0) 10 - t:lpush('">profile</a> <a href="/conf">configure</a> <a href="/logout">log out</a>') 10 + t:lpush('">profile</a> <a href="/conf">configure</a> <a href="/help">help</a> <a href="/logout">log out</a>') 11 11 else 12 - t:lpush('<a href="/login">log in</a>') 12 + t:lpush('<a href="/help">help</a> <a href="/login">log in</a>') 13 13 end 14 14 return t:finalize() 15 15 end 16 16 return render_nav
Modified render/profile.t from [ecfc7ba460] to [30c7a0b6c6].
1 1 -- vim: ft=terra 2 +local pstr = lib.mem.ptr(int8) 3 +local terra cs(s: rawstring) 4 + return pstr { ptr = s, ct = lib.str.sz(s) } 5 +end 6 + 2 7 local terra 3 8 render_profile(co: &lib.srv.convo, actor: &lib.store.actor) 4 9 var aux: lib.str.acc 5 - var auxp: rawstring 10 + var auxp: pstr 6 11 if co.aid ~= 0 and co.who.id == actor.id then 7 - auxp = '<a href="/conf/profile">alter</a>' 12 + auxp = lib.str.plit '<a href="/conf/profile">alter</a>' 8 13 elseif co.aid ~= 0 then 9 14 aux:compose('<a href="/', actor.xid, '/follow">follow</a><a href="/', 10 15 actor.xid, '/chat">chat</a>') 11 16 if co.who.rights.powers:affect_users() then 12 17 aux:push('<a href="/',11):push(actor.xid,0):push('/ctl">control</a>',17) 13 18 end 14 - auxp = aux.buf 19 + auxp = aux:finalize() 15 20 else 16 21 aux:compose('<a href="/', actor.xid, '/follow">remote follow</a>') 17 22 end 18 23 var avistr: lib.str.acc if actor.origin == 0 then 19 24 avistr:compose('/avi/',actor.handle) 20 25 end 21 26 var timestr: int8[26] lib.osclock.ctime_r(&actor.knownsince, ×tr[0]) 22 27 23 28 var strfbuf: int8[28*4] 24 29 var stats = co.srv:actor_stats(actor.id) 25 - var sn_posts = lib.math.decstr_friendly(stats.posts, &strfbuf[ [strfbuf.type.N - 1] ]) 26 - var sn_follows = lib.math.decstr_friendly(stats.follows, sn_posts - 1) 27 - var sn_followers = lib.math.decstr_friendly(stats.followers, sn_follows - 1) 28 - var sn_mutuals = lib.math.decstr_friendly(stats.mutuals, sn_followers - 1) 30 + var sn_posts = cs(lib.math.decstr_friendly(stats.posts, &strfbuf[ [strfbuf.type.N - 1] ])) 31 + var sn_follows = cs(lib.math.decstr_friendly(stats.follows, sn_posts.ptr - 1)) 32 + var sn_followers = cs(lib.math.decstr_friendly(stats.followers, sn_follows.ptr - 1)) 33 + var sn_mutuals = cs(lib.math.decstr_friendly(stats.mutuals, sn_followers.ptr - 1)) 29 34 30 35 var profile = data.view.profile { 31 - nym = lib.coalesce(actor.nym, actor.handle); 32 - bio = lib.coalesce(actor.bio, "<em>tall, dark, and mysterious</em>"); 33 - xid = actor.xid; 34 - avatar = lib.trn(actor.origin == 0, avistr.buf, 35 - lib.coalesce(actor.avatar, '/s/default-avatar.webp')); 36 + nym = cs(lib.coalesce(actor.nym, actor.handle)); 37 + bio = cs(lib.coalesce(actor.bio, "<em>tall, dark, and mysterious</em>")); 38 + xid = cs(actor.xid); 39 + avatar = lib.trn(actor.origin == 0, pstr{ptr=avistr.buf,ct=avistr.sz}, 40 + cs(lib.coalesce(actor.avatar, '/s/default-avatar.webp'))); 36 41 37 42 nposts = sn_posts, nfollows = sn_follows; 38 43 nfollowers = sn_followers, nmutuals = sn_mutuals; 39 - tweetday = timestr; 40 - timephrase = lib.trn(actor.origin == 0, 'joined', 'known since'); 44 + tweetday = cs(timestr); 45 + timephrase = lib.trn(actor.origin == 0, lib.str.plit'joined', lib.str.plit'known since'); 41 46 42 47 auxbtn = auxp; 43 48 } 44 49 45 50 var ret = profile:tostr() 46 51 if actor.origin == 0 then avistr:free() end 47 - if not (co.aid ~= 0 and co.who.id == actor.id) then aux:free() end 52 + if not (co.aid ~= 0 and co.who.id == actor.id) then auxp:free() end 48 53 return ret 49 54 end 50 55 51 56 return render_profile
Added render/timeline.t version [e2a4558814].
1 +-- vim: ft=terra 2 +local modes = lib.enum {'follow','mutual','srvlocal','fediglobal','circle'} 3 +local terra 4 +render_timeline(co: &lib.srv.convo, modestr: lib.mem.ref(int8)) 5 + var mode = modes.srvlocal 6 + var circle: uint64 = 0 7 + -- if modestr:cmpl('local') then mode = modes.srvlocal 8 + -- elseif modestr:cmpl('mutual') then mode = modes.mutual 9 + -- elseif modestr:cmpl('global') then mode = modes.fediglobal 10 + -- elseif modestr:cmpl('circle') then mode = modes.circle 11 + -- end 12 + 13 + var stoptime = lib.osclock.time(nil) 14 + 15 + var posts = [lib.mem.vec(lib.mem.ptr(lib.store.post))] { 16 + sz = 0, run = 0 17 + } 18 + if mode == modes.follow then 19 + elseif mode == modes.srvlocal then 20 + posts = co.srv:instance_timeline_fetch(lib.store.range { 21 + mode = 1; -- T->I 22 + from_time = stoptime; 23 + to_idx = 64; 24 + }) 25 + elseif mode == modes.fediglobal then 26 + elseif mode == modes.circle then 27 + end 28 + 29 + var acc: lib.str.acc acc:init(1024) 30 + for i = 0, posts.sz do 31 + lib.render.tweet(co, posts(i).ptr, &acc) 32 + posts(i):free() 33 + end 34 + posts:free() 35 + 36 + var doc = data.view.docskel { 37 + instance = co.srv.cfg.instance; 38 + title = lib.str.plit'timeline'; 39 + body = acc:finalize(); 40 + class = lib.str.plit'timeline'; 41 + navlinks = co.navbar; 42 + } 43 + var hdrs = array( 44 + lib.http.header { 'Content-Type', 'text/html; charset=UTF-8' } 45 + ) 46 + doc:send(co.con,200,[lib.mem.ptr(lib.http.header)] {ct = 1, ptr = &hdrs[0]}) 47 + doc.body:free() 48 +end 49 +return render_timeline
Added render/tweet.t version [71eb9101d9].
1 +-- vim: ft=terra 2 +local pstr = lib.mem.ptr(int8) 3 +local terra cs(s: rawstring) 4 + return pstr { ptr = s, ct = lib.str.sz(s) } 5 +end 6 + 7 +local terra 8 +render_tweet(co: &lib.srv.convo, p: &lib.store.post, acc: &lib.str.acc) 9 + var author: &lib.store.actor 10 + for j = 0, co.actorcache.top do 11 + lib.io.fmt('scanning cache for author %llu (%llu/%llu)\n', p.author, j, co.actorcache.top) 12 + if p.author == co.actorcache(j).ptr.id then 13 + author = co.actorcache(j).ptr 14 + lib.io.fmt('cache hit on idx %llu, skipping db lookup\n', j) 15 + goto foundauth 16 + end 17 + end 18 + lib.io.fmt('cache miss, checking db for id %llu\n', p.author) 19 + author = co.actorcache:insert(co.srv:actor_fetch_uid(p.author)).ptr 20 + lib.io.fmt('got author %s\n', author.handle) 21 + 22 + ::foundauth:: 23 + var avistr: lib.str.acc if author.origin == 0 then 24 + avistr:compose('/avi/',author.handle) 25 + end 26 + var timestr: int8[26] lib.osclock.ctime_r(&p.posted, ×tr[0]) 27 + lib.io.fmt('got body %s\n', author.handle) 28 + 29 + var bhtml = lib.smackdown.html([lib.mem.ptr(int8)] {ptr=p.body,ct=0}) defer bhtml:free() 30 + 31 + var idbuf: int8[lib.math.shorthand.maxlen] 32 + var idlen = lib.math.shorthand.gen(p.id, idbuf) 33 + var permalink: lib.str.acc permalink:compose('/post/',{idbuf,idlen}) 34 + 35 + var tpl = data.view.tweet { 36 + text = bhtml; 37 + subject = cs(lib.coalesce(p.subject,'')); 38 + nym = cs(lib.coalesce(author.nym, author.handle)); 39 + xid = cs(author.xid); 40 + when = cs(×tr[0]); 41 + avatar = cs(lib.trn(author.origin == 0, avistr.buf, 42 + lib.coalesce(author.avatar, '/s/default-avatar.webp'))); 43 + acctlink = cs(author.xid); 44 + permalink = permalink:finalize(); 45 + } 46 + defer tpl.permalink:free() 47 + if acc ~= nil then tpl:append(acc) return [lib.mem.ptr(int8)]{ptr=nil,ct=0} end 48 + var txt = tpl:tostr() 49 + return txt 50 +end 51 +return render_tweet
Modified render/userpage.t from [cdf1e65d22] to [7b79f9904c].
1 1 -- vim: ft=terra 2 2 local terra 3 3 render_userpage(co: &lib.srv.convo, actor: &lib.store.actor) 4 - var ti: lib.str.acc defer ti:free() 4 + var ti: lib.str.acc 5 5 if co.aid ~= 0 and co.who.id == actor.id then 6 6 ti:compose('my profile') 7 7 else 8 8 ti:compose('profile :: ', actor.handle) 9 9 end 10 10 var pftxt = lib.render.profile(co,actor) defer pftxt:free() 11 11 12 12 var doc = data.view.docskel { 13 - instance = co.srv.cfg.instance.ptr; 14 - title = ti.buf; 15 - body = pftxt.ptr; 16 - class = 'profile'; 17 - navlinks = co.navbar.ptr; 13 + instance = co.srv.cfg.instance; 14 + title = ti:finalize(); 15 + body = pftxt; 16 + class = lib.str.plit 'profile'; 17 + navlinks = co.navbar; 18 18 } 19 19 20 20 var hdrs = array( 21 21 lib.http.header { 'Content-Type', 'text/html; charset=UTF-8' } 22 22 ) 23 23 doc:send(co.con,200,[lib.mem.ptr(lib.http.header)] {ct = 1, ptr = &hdrs[0]}) 24 + doc.title:free() 24 25 end 25 26 26 27 return render_userpage
Modified route.t from [d7d680b0a3] to [4d69f275c0].
1 1 -- vim: ft=terra 2 2 local r = lib.srv.route 3 3 local method = lib.http.method 4 +local pstring = lib.mem.ptr(int8) 5 +local rstring = lib.mem.ref(int8) 6 +local hpath = lib.mem.ptr(rstring) 4 7 local http = {} 5 8 6 9 terra http.actor_profile_xid(co: &lib.srv.convo, uri: lib.mem.ptr(int8), meth: method.t) 7 10 var handle = [lib.mem.ptr(int8)] { ptr = &uri.ptr[2], ct = 0 } 8 11 for i=2,uri.ct do 9 12 if uri.ptr[i] == @'/' or uri.ptr[i] == 0 then handle.ct = i - 2 break end 10 13 end ................................................................................ 57 60 58 61 lib.render.userpage(co, actor.ptr) 59 62 end 60 63 61 64 terra http.login_form(co: &lib.srv.convo, meth: method.t) 62 65 if meth == method.get then 63 66 -- request a username 64 - lib.render.login(co, nil, nil, nil) 67 + lib.render.login(co, nil, nil, lib.str.plit(nil)) 65 68 elseif meth == method.post then 66 69 var usn, usnl = co:postv('user') 67 - lib.dbg('got name ',{usn,usnl}) 68 - lib.io.fmt('name len %llu\n',usnl) 69 70 var am, aml = co:postv('authmethod') 70 71 var chrs, chrsl = co:postv('response') 71 72 var cs, authok = co.srv:actor_auth_how(co.peer, usn) 72 73 var act = co.srv:actor_fetch_xid([lib.mem.ptr(int8)] { 73 74 ptr = usn, ct = usnl 74 75 }) 75 76 if authok == false then 76 - lib.render.login(co, nil, nil, 'access denied') 77 + lib.render.login(co, nil, nil, lib.str.plit'access denied') 77 78 return 78 79 end 79 80 var fakeact = false 80 81 var fakeactor: lib.store.actor 81 82 if act.ptr == nil then 82 83 -- the user is known to us but has not yet claimed an 83 84 -- account on the server. create a template for the ................................................................................ 90 91 } 91 92 act.ct = 1 92 93 act.ptr = &fakeactor 93 94 act.ptr.rights = lib.store.rights_default() 94 95 end 95 96 if am == nil then 96 97 -- pick an auth method 97 - lib.render.login(co, act.ptr, &cs, nil) 98 + lib.render.login(co, act.ptr, &cs, lib.str.plit(nil)) 98 99 else var aid: uint64 = 0 99 100 lib.dbg('authentication attempt beginning') 100 101 -- attempt login with provided method 101 102 if lib.str.ncmp('pw', am, lib.math.biggest(2,aml)) == 0 and chrs ~= nil then 102 103 aid = co.srv:actor_auth_pw(co.peer, 103 104 [lib.mem.ptr(int8)]{ptr=usn,ct=usnl}, 104 105 [lib.mem.ptr(int8)]{ptr=chrs,ct=chrsl}) ................................................................................ 105 106 elseif lib.str.ncmp('otp', am, lib.math.biggest(2,aml)) == 0 and chrs ~= nil then 106 107 lib.dbg('using otp auth') 107 108 -- ··· -- 108 109 else 109 110 lib.dbg('invalid auth method') 110 111 end 111 112 112 - lib.io.fmt('login got aid = %llu\n', aid) 113 113 -- error out 114 114 if aid == 0 then 115 - lib.render.login(co, nil, nil, 'authentication failure') 115 + lib.render.login(co, nil, nil, lib.str.plit 'authentication failure') 116 116 else 117 117 var sesskey: int8[lib.session.maxlen + #lib.session.cookiename + #"=; Path=/" + 1] 118 118 do var p = &sesskey[0] 119 119 p = lib.str.ncpy(p, [lib.session.cookiename .. '='], [#lib.session.cookiename + 1]) 120 120 p = p + lib.session.cookie_gen(co.srv.cfg.secret, aid, lib.osclock.time(nil), p) 121 121 lib.dbg('sending cookie',&sesskey[0]) 122 122 p = lib.str.ncpy(p, '; Path=/', 9) ................................................................................ 134 134 terra http.post_compose(co: &lib.srv.convo, meth: method.t) 135 135 if meth == method.get then 136 136 lib.render.compose(co, nil) 137 137 elseif meth == method.post then 138 138 if co.who.rights.powers.post() == false then 139 139 co:complain(401,'insufficient privileges','you lack the <strong>post</strong> power and cannot perform this action') return 140 140 end 141 + var text, textlen = co:postv("post") 142 + var acl, acllen = co:postv("acl") 143 + var subj, subjlen = co:postv("subject") 144 + if text == nil or acl == nil then 145 + co:complain(405, 'invalid post', 'every post must have at least body text and an ACL') 146 + return 147 + end 148 + if subj == nil then subj = '' end 149 + 150 + var p = lib.store.post { 151 + author = co.who.id, acl = acl; 152 + body = text, subject = subj; 153 + } 154 + var newid = co.srv:post_create(&p) 141 155 156 + var idbuf: int8[lib.math.shorthand.maxlen] 157 + var idlen = lib.math.shorthand.gen(newid, idbuf) 158 + var redirto: lib.str.acc redirto:compose('/post/',{idbuf,idlen}) defer redirto:free() 159 + co:reroute(redirto.buf) 142 160 end 143 161 end 162 + 163 +terra http.timeline(co: &lib.srv.convo, mode: hpath) 164 + lib.render.timeline(co,lib.trn(mode.ptr == nil, rstring{ptr=nil}, mode.ptr[1])) 165 + return 166 +end 144 167 145 168 do local branches = quote end 146 169 local filename, flen = symbol(&int8), symbol(intptr) 147 170 local page = symbol(lib.http.page) 148 171 local send = label() 149 172 local storage = data.stmap 150 173 for i,e in ipairs(config.embeds) do local id,mime = e[1],e[2] ................................................................................ 180 203 co:reroute('/s/default-avatar.webp') 181 204 end 182 205 183 206 -- entry points 184 207 terra r.dispatch_http(co: &lib.srv.convo, uri: lib.mem.ptr(int8), meth: method.t) 185 208 lib.dbg('handling URI of form ', {uri.ptr,uri.ct}) 186 209 co.navbar = lib.render.nav(co) 210 + lib.dbg('got nav ', {co.navbar.ptr,co.navbar.ct}, "||", co.navbar.ptr) 187 211 -- some routes are non-hierarchical, and can be resolved with a simple strcmp 188 212 -- we run through those first before giving up and parsing the URI 189 213 if uri.ptr[0] ~= @'/' then 190 214 co:complain(404, 'what the hell', 'how did you do that') 191 215 return 192 216 elseif uri.ct == 1 then -- root 193 - lib.io.fmt('root directory, aid is %llu\n', co.aid) 194 217 if (co.srv.cfg.pol_sec == lib.srv.secmode.private or 195 218 co.srv.cfg.pol_sec == lib.srv.secmode.lockdown) and co.aid == 0 then 196 219 http.login_form(co, meth) 197 220 else 198 221 -- FIXME display home screen 222 + http.timeline(co, hpath {ptr=nil}) 199 223 goto notfound 200 224 end 201 225 return 202 226 elseif uri.ptr[1] == @'@' then 203 227 http.actor_profile_xid(co, uri, meth) 204 228 return 205 229 elseif uri.ptr[1] == @'s' and uri.ptr[2] == @'/' and uri.ct > 3 then ................................................................................ 225 249 else co:reroute_cookie('/','auth=; Path=/') 226 250 end 227 251 return 228 252 else -- hierarchical routes 229 253 var path = lib.http.hier(uri) defer path:free() 230 254 if path.ptr[0]:cmp(lib.str.lit('user')) then 231 255 http.actor_profile_uid(co, path, meth) 256 + elseif path.ptr[0]:cmp(lib.str.lit('tl')) then 257 + http.timeline(co, path) 232 258 else goto notfound end 233 259 return 234 260 end 235 261 236 262 ::wrongmeth:: co:complain(405, 'method not allowed', 'that method is not meaningful for this endpoint') do return end 237 263 ::notfound:: co:complain(404, 'not found', 'no such resource available') do return end 238 264 end
Modified schema.sql from [a3359b8b76] to [6bf306c986].
31 31 -- note that valid ids should always > 0, as 0 is reserved for null 32 32 -- on the client side, vastly simplifying code 33 33 drop table if exists parsav_servers cascade; 34 34 create table parsav_servers ( 35 35 id bigint primary key default (1+random()*(2^63-1))::bigint, 36 36 domain text not null, 37 37 key bytea, 38 + knownsince timestamp, 38 39 parsav boolean -- whether to use parsav protocol extensions 39 40 ); 40 41 41 42 drop table if exists parsav_actors cascade; 42 43 create table parsav_actors ( 43 44 id bigint primary key default (1+random()*(2^63-1))::bigint, 44 45 nym text, 45 46 handle text not null, -- nym [@handle@origin] 46 47 origin bigint references parsav_servers(id) 47 48 on delete cascade, -- null origin = local actor 49 + knownsince timestamp, 48 50 bio text, 49 51 avataruri text, -- null if local 50 52 rank smallint not null default 0, 51 53 quota integer not null default 1000, 52 54 key bytea, -- private if localactor; public if remote 53 - title text 55 + title text, 54 56 55 57 unique (handle,origin) 56 58 ); 57 59 58 60 drop table if exists parsav_rights cascade; 59 61 create table parsav_rights ( 60 62 key text, ................................................................................ 81 83 author bigint references parsav_actors(id) 82 84 on delete cascade, 83 85 subject text, 84 86 acl text not null default 'all', -- just store the script raw 🤷 85 87 body text, 86 88 posted timestamp not null, 87 89 discovered timestamp not null, 88 - scope smallint not null, 89 - convo bigint, 90 - parent bigint, 91 - circles bigint[], 92 - mentions bigint[] 90 + parent bigint not null default 0, 91 + circles bigint[], -- TODO at edit or creation, iterate through each circle 92 + mentions bigint[], -- a user has, check if it can see her post, and if so add 93 + 94 + convoheaduri text 95 + -- only used for tracking foreign conversations and tying them to post heads; 96 + -- local conversations are tracked directly and mapped to URIs based on the 97 + -- head's ID. null if native tweet or not the first tweet in convo 93 98 ); 94 99 95 100 drop table if exists parsav_conversations cascade; 96 -create table parsav_conversations ( 97 - id bigint primary key default (1+random()*(2^63-1))::bigint, 98 - uri text not null, 99 - discovered timestamp not null, 100 - head bigint references parsav_posts(id) 101 -); 102 101 103 102 drop table if exists parsav_rels cascade; 104 103 create table parsav_rels ( 105 104 relator bigint references parsav_actors(id) 106 105 on delete cascade, -- e.g. follower 107 106 relatee bigint references parsav_actors(id) 108 107 on delete cascade, -- e.g. followed ................................................................................ 142 141 ); 143 142 144 143 drop table if exists parsav_circles cascade; 145 144 create table parsav_circles ( 146 145 id bigint primary key default (1+random()*(2^63-1))::bigint, 147 146 owner bigint not null references parsav_actors(id), 148 147 name text not null, 149 - members bigint[] not null default array[], 148 + members bigint[] not null default array[]::bigint[], 150 149 151 150 unique (owner,name) 152 151 ); 153 152 154 153 drop table if exists parsav_rooms cascade; 155 154 create table parsav_rooms ( 156 155 id bigint primary key default (1+random()*(2^63-1))::bigint, ................................................................................ 162 161 163 162 drop table if exists parsav_room_members cascade; 164 163 create table parsav_room_members ( 165 164 room bigint references parsav_rooms(id), 166 165 member bigint references parsav_actors(id), 167 166 rank smallint not null default 0, 168 167 admin boolean not null default false, -- non-admins with rank can only moderate + invite 169 - title text -- admin-granted title like reddit flair 168 + title text, -- admin-granted title like reddit flair 169 + vouchedby bigint references parsav_actors(id) 170 170 ); 171 171 172 172 drop table if exists parsav_invites cascade; 173 173 create table parsav_invites ( 174 174 id bigint primary key default (1+random()*(2^63-1))::bigint, 175 175 -- when a user is created from an invite, the invite is deleted and the invite 176 176 -- ID becomes the user ID. privileges granted on the invite ID during the invite 177 177 -- process are thus inherited by the user 178 + issuer bigint references parsav_actors(id), 178 179 handle text, -- admin can lock invite to specific handle 179 180 rank smallint not null default 0, 180 181 quota integer not null default 1000 181 -}; 182 +); 182 183 183 184 drop table if exists parsav_interventions cascade; 184 185 create table parsav_interventions ( 185 186 id bigint primary key default (1+random()*(2^63-1))::bigint, 186 187 issuer bigint references parsav_actors(id) not null, 187 188 scope bigint, -- can be null or room for local actions 188 189 nature smallint not null, -- silence, suspend, disemvowel, etc 189 190 victim bigint not null, -- could potentially target group as well 190 191 expire timestamp -- auto-expires if set 191 192 ); 192 193 193 194 end;
Added smackdown.t version [896438e021].
1 +-- vim: ft=terra 2 +-- smackdown is parsav's terrible, terrible custom markdown-alike parser 3 + 4 +local m = {} 5 +local pstr = lib.mem.ptr(int8) 6 + 7 +local segt = { 8 + none = 0, para = 1, head = 2, listing = 3 9 +} 10 + 11 +local autolink_protos = { 12 + 'https', 'http', 'ftp', 'gopher', 'gemini', 'ircs', 'irc'; 13 + 'mailto', 'about', 'sshfs', 'afp', 'smb', 'data', 'file'; 14 + 'dav', 'git', 'svn', 'cvs', 'dns', 'finger', 'pop', 'imap'; 15 + 'pops', 'imaps', 'torrent', 'magnet', 'news', 'snews', 'nfs'; 16 + 'nntp', 'sms', 'tel', 'telnet', 'vnc', 'webcal', 'ws', 'wss'; 17 + 'xmpp'; 18 +} 19 + 20 +local struct state { 21 + segt: uint 22 + bqlvl: uint 23 + curpos: rawstring 24 + blockstart: rawstring 25 +} 26 + 27 +terra state:segend(ofs: uint) 28 +-- takes a string offset and returns true if it indexes th 29 +-- end of the current block 30 + var s = self.curpos + ofs 31 + if s[0] ~= @'\n' then return false end 32 + if self.segt == segt.head then return true end -- headers can only be 1 line 33 +-- if s[1] == '#' 34 + 35 +end 36 + 37 +local terra isws(c: int8) 38 + return c == @' ' or c == @'\n' or c == @'\t' or c == @'\r' 39 +end 40 + 41 +local terra scanline(l: rawstring, max: intptr, n: rawstring, nc: intptr) 42 + if l == nil then return nil end 43 + for i=0,max do 44 + for j=0,nc do 45 + if l[i+j] == @'\n' then return nil end 46 + if l[i+j] ~= n[j] then goto nexti end 47 + end 48 + do return l+i end 49 + ::nexti::end 50 +end 51 + 52 +local terra scanline_wordend(l: rawstring, max: intptr, n: rawstring, nc: intptr) 53 + var sl = scanline(l,max,n,nc) 54 + if sl == nil then return nil else sl = sl + nc end 55 + if sl >= l+max or isws(@sl) then return sl-nc end 56 + return nil 57 +end 58 + 59 +terra m.html(md: pstr) 60 + if md.ct == 0 then md.ct = lib.str.sz(md.ptr) end 61 + var styled: lib.str.acc styled:init(md.ct) 62 + 63 + do var i = 0 while i < md.ct do 64 + var wordstart = (i == 0 or isws(md.ptr[i-1])) 65 + var wordend = (i == md.ct - 1 or isws(md.ptr[i+1])) 66 + 67 + var here = md.ptr + i 68 + var rem = md.ct - i 69 + if @here == @'[' then 70 + var sep = scanline(here,rem, '](', 2) 71 + var term = scanline(sep+2,rem - ((sep+2)-here), ')', 1) 72 + if sep ~= nil and term ~= nil then 73 + styled:lpush('<a href="') 74 + :push(sep+2, term-(sep+2)) 75 + :lpush('" rel="nofollow">') 76 + :push(here+1,(sep-here) - 1) 77 + :lpush('</a>') 78 + i = (term - md.ptr) + 1 79 + goto skip 80 + else goto fallback end 81 + end 82 + 83 + if wordstart and rem >= 7 and lib.str.ncmp('***',here,3)==0 then 84 + var term = scanline_wordend(here+4,rem-4,'***',3) 85 + if term ~= nil then 86 + styled:lpush('<strong><em>') 87 + :push(here+3, (term-here) - 3) 88 + :lpush('</strong></em>') 89 + i = (term - md.ptr) + 3 90 + goto skip 91 + end 92 + end 93 + 94 + if wordstart and rem >= 5 and lib.str.ncmp('**',here,2)==0 then 95 + var term = scanline_wordend(here+3,rem-3,'**',2) 96 + if term ~= nil then 97 + styled:lpush('<strong>') 98 + :push(here+2, (term-here) - 2) 99 + :lpush('</strong>') 100 + i = (term - md.ptr) + 2 101 + goto skip 102 + end 103 + end 104 + 105 + if wordstart and rem >= 3 and @here == @'*' then 106 + var term = scanline_wordend(here+2,rem-2,'*',1) 107 + if term ~= nil then 108 + styled:lpush('<em>') 109 + :push(here+1, (term-here) - 1) 110 + :lpush('</em>') 111 + i = (term - md.ptr) + 1 112 + goto skip 113 + end 114 + end 115 + 116 + ::fallback::styled:push(here,1) -- :/ 117 + i = i + 1 118 + ::skip::end end 119 + 120 + -- we make two passes: the first detects and transforms inline elements, 121 + -- the second carries out block-level organization 122 + 123 + var html: lib.str.acc html:init(styled.sz) 124 + var s = state { 125 + segt = segt.none; 126 + bqlvl = 0; 127 + curpos = md.ptr; 128 + blockstart = nil; 129 + } 130 + while s.curpos < md.ptr + md.ct do 131 + s.curpos = s.curpos + 1 132 + end 133 + 134 + html:free() -- JUST FOR NOW 135 + return styled:finalize() 136 +end 137 + 138 +return m
Modified srv.t from [afa0417e30] to [d14bb4b6ce].
16 16 cfg: cfgcache 17 17 } 18 18 19 19 terra cfgcache:free() -- :/ 20 20 self.secret:free() 21 21 self.instance:free() 22 22 end 23 + 24 +terra srv:instance_timeline_fetch(r: lib.store.range): lib.mem.vec(lib.mem.ptr(lib.store.post)) 25 + var all: lib.mem.vec(lib.mem.ptr(lib.store.post)) all:init(64) 26 + for i=0,self.sources.ct do var src = self.sources.ptr + i 27 + if src.handle ~= nil and src.backend.instance_timeline_fetch ~= nil then 28 + var lst = src:instance_timeline_fetch(r) 29 + all:assure(all.sz + lst.ct) 30 + for j=0, lst.ct do all:push(lst.ptr[j]) end 31 + lst:free() 32 + end 33 + end 34 + return all 35 +end 23 36 24 37 srv.metamethods.__methodmissing = macro(function(meth, self, ...) 25 38 local primary, ptr, stat, simple, oid = 0,1,2,3,4 26 39 local tk, rt = primary 27 40 local expr = {...} 28 41 for _,f in pairs(lib.store.backend.entries) do 29 42 local fn = f.field or f[1] ................................................................................ 34 47 elseif rt.type == 'integer' then tk = oid 35 48 elseif rt.stat_basetype then tk = stat 36 49 elseif rt.ptr_basetype then tk = ptr end 37 50 break 38 51 end 39 52 end 40 53 54 + local r = symbol(rt) 41 55 if tk == primary then 42 - return `self.sources.ptr[0]:[meth]([expr]) 56 + return quote 57 + var [r] 58 + for i=0,self.sources.ct do var src = self.sources.ptr + i 59 + if src.handle ~= nil and src.backend.[meth] ~= nil then 60 + r = src:[meth]([expr]) 61 + goto success 62 + end 63 + end 64 + lib.bail(['no active backends provide critical capability ' .. meth .. '!']) 65 + ::success::; 66 + in r end 43 67 else local ok, empty 44 - local r = symbol(rt) 45 68 if tk == ptr then 46 69 ok = `r.ptr ~= nil 47 70 empty = `[rt]{ptr=nil,ct=0} 48 71 elseif tk == stat then 49 72 ok = `r.ok == true 50 73 empty = `[rt]{ok=false,error=1} 51 74 elseif tk == simple then ................................................................................ 54 77 elseif tk == oid then 55 78 ok = `r ~= 0 56 79 empty = `0 57 80 end 58 81 return quote 59 82 var [r] = empty 60 83 for i=0,self.sources.ct do var src = self.sources.ptr + i 61 - if src.handle ~= nil then 84 + if src.handle ~= nil and src.backend.[meth] ~= nil then 62 85 r = src:[meth]([expr]) 63 86 if [ok] then break 64 87 else r = empty end 65 88 end 66 89 end 67 90 in r end 68 91 end ................................................................................ 74 97 msg: &lib.net.mg_http_message 75 98 aid: uint64 -- 0 if logged out 76 99 who: &lib.store.actor -- who we're logged in as, if aid ~= 0 77 100 peer: lib.store.inet 78 101 reqtype: lib.http.mime.t -- negotiated content type 79 102 -- cache 80 103 navbar: lib.mem.ptr(int8) 104 + actorcache: lib.mem.cache(lib.mem.ptr(lib.store.actor),32) -- naive cache to avoid unnecessary queries 81 105 -- private 82 106 varbuf: lib.mem.ptr(int8) 83 107 vbofs: &int8 84 108 } 85 109 86 110 -- this is unfortunately necessary to work around a terra bug 87 111 -- it can't seem to handle forward-declarations of structs in C ................................................................................ 118 142 end 119 143 120 144 terra convo:reroute(dest: rawstring) self:reroute_cookie(dest,nil) end 121 145 122 146 terra convo:complain(code: uint16, title: rawstring, msg: rawstring) 123 147 var hdrs = array(lib.http.header { key = 'Content-Type', value = 'text/html; charset=UTF-8' }) 124 148 125 - var ti: lib.str.acc ti:compose('error :: ', title) defer ti:free() 126 - 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() 149 + var ti: lib.str.acc ti:compose('error :: ', title) 150 + var bo: lib.str.acc bo:compose('<div class="message"><img class="icon" src="/s/warn.webp"><h1>',title,'</h1><p>',msg,'</p></div>') 127 151 var body = data.view.docskel { 128 - instance = self.srv.cfg.instance.ptr; 129 - title = ti.buf; 130 - body = bo.buf; 131 - class = 'error'; 132 - navlinks = lib.coalesce(self.navbar.ptr, ''); 152 + instance = self.srv.cfg.instance; 153 + title = ti:finalize(); 154 + body = bo:finalize(); 155 + class = lib.str.plit 'error'; 156 + navlinks = lib.coalesce(self.navbar, [lib.mem.ptr(int8)]{ptr='',ct=0}); 133 157 } 134 158 135 - if body.body == nil then 136 - body.body = "i'm sorry, dave. i can't let you do that" 159 + if body.body.ptr == nil then 160 + body.body = lib.str.plit"i'm sorry, dave. i can't let you do that" 137 161 end 138 162 139 163 body:send(self.con, code, [lib.mem.ptr(lib.http.header)] { 140 164 ptr = &hdrs[0], ct = [hdrs.type.N] 141 165 }) 166 + 167 + body.title:free() 168 + body.body:free() 142 169 end 143 170 144 171 -- CALL ONLY ONCE PER VAR 145 172 terra convo:postv(name: rawstring) 146 173 if self.varbuf.ptr == nil then 147 174 self.varbuf = lib.mem.heapa(int8, self.msg.body.len + self.msg.query.len) 148 175 self.vbofs = self.varbuf.ptr 149 176 end 150 177 var o = lib.net.mg_http_get_var(&self.msg.body, name, self.vbofs, self.varbuf.ct - (self.vbofs - self.varbuf.ptr)) 151 178 if o > 0 then 152 179 var r = self.vbofs 153 - self.vbofs = self.vbofs + o 154 - return r, o 180 + self.vbofs = self.vbofs + o + 1 181 + @(self.vbofs - 1) = 0 182 + var norm = lib.str.normalize([lib.mem.ptr(int8)]{ptr = r, ct = o}) 183 + return norm.ptr, norm.ct 155 184 else return nil, 0 end 156 185 end 157 186 158 187 terra convo:getv(name: rawstring) 159 188 if self.varbuf.ptr == nil then 160 189 self.varbuf = lib.mem.heapa(int8, self.msg.query.len + self.msg.body.len) 161 190 self.vbofs = self.varbuf.ptr 162 191 end 163 192 var o = lib.net.mg_http_get_var(&self.msg.query, name, self.vbofs, self.varbuf.ct - (self.vbofs - self.varbuf.ptr)) 164 193 if o > 0 then 165 194 var r = self.vbofs 166 - self.vbofs = self.vbofs + o 167 - return r, o 195 + self.vbofs = self.vbofs + o + 1 196 + @(self.vbofs - 1) = 0 197 + var norm = lib.str.normalize([lib.mem.ptr(int8)]{ptr = r, ct = o}) 198 + return norm.ptr, norm.ct 168 199 else return nil, 0 end 169 200 end 170 201 171 202 local urimatch = macro(function(uri, ptn) 172 203 return `lib.net.mg_globmatch(ptn, [#ptn], uri.ptr, uri.ct+1) 173 204 end) 174 205 ................................................................................ 223 254 var msg = [&lib.net.mg_http_message](p) 224 255 var co = convo { 225 256 con = con, srv = server, msg = msg; 226 257 aid = 0, who = nil, peer = peer; 227 258 reqtype = lib.http.mime.none; 228 259 } co.varbuf.ptr = nil 229 260 co.navbar.ptr = nil 261 + co.actorcache.top = 0 262 + co.actorcache.cur = 0 230 263 231 264 -- first, check for an accept header. if it's there, we need to 232 265 -- iterate over the values and pick the highest-priority one 233 266 do var acc = lib.http.findheader(msg, 'Accept') 234 267 -- TODO handle q-value 235 268 if acc.ptr ~= nil then 236 269 var [mimevar] = [lib.mem.ref(int8)] { ptr = acc.ptr } ................................................................................ 344 377 else 345 378 co:complain(400,'unknown method','you have submitted an invalid http request') 346 379 end 347 380 348 381 if co.aid ~= 0 then lib.mem.heapf(co.who) end 349 382 if co.varbuf.ptr ~= nil then co.varbuf:free() end 350 383 if co.navbar.ptr ~= nil then co.navbar:free() end 384 + co.actorcache:free() 351 385 end 352 386 end 353 387 end; 354 388 } 355 389 356 390 local terra cfg(s: &srv, befile: rawstring) 357 391 lib.report('configuring backends from ', befile)
Modified static/style.scss from [7905a3444d] to [1dafd96ed6].
1 1 $color: hsl(323,100%,65%); 2 2 %sans { font-family: "Alegreya Sans", "Open Sans", sans-serif; } 3 3 %serif { font-family: Alegreya, GaramondNo8, "Garamond Premier Pro", "Adobe Garamond", Garamond, Junicode, serif; } 4 4 %teletype { font-family: "Inconsolata LGC", Inconsolata, monospace; font-size: 12pt !important; } 5 5 6 -@function tone($pct) { @return adjust-color($color, $lightness: $pct) } 6 +@function tone($pct, $alpha: 0) { @return adjust-color($color, $lightness: $pct, $alpha: $alpha) } 7 7 8 8 body { 9 9 @extend %sans; 10 - background-color: adjust-color($color, $lightness: -55%); 11 - color: adjust-color($color, $lightness: 25%); 10 + background-color: tone(-55%); 11 + color: tone(25%); 12 12 font-size: 14pt; 13 13 margin: 0; 14 14 padding: 0; 15 15 } 16 16 a[href] { 17 - color: adjust-color($color, $lightness: 10%); 17 + color: tone(10%); 18 + text-decoration-color: adjust-color($color, $lightness: 10%, $alpha: -0.5); 18 19 &:hover { 19 20 color: white; 20 - text-shadow: 0 0 15px adjust-color($color, $lightness: 20%); 21 + text-shadow: 0 0 15px tone(20%); 22 + text-decoration-color: adjust-color($color, $lightness: 10%, $alpha: -0.1); 21 23 } 22 24 } 23 25 a[href^="//"], 24 26 a[href^="http://"], 25 27 a[href^="https://"] { // external link 26 28 &:hover::after { 27 29 color: black; ................................................................................ 410 412 padding: 0.1in; 411 413 &:hover { font-weight: bold; } 412 414 } 413 415 } 414 416 415 417 code { 416 418 @extend %teletype; 417 - background: adjust-color($color, $lightness: -50%); 418 - border: 1px solid adjust-color($color, $lightness: -20%); 419 + background: tone(-50%); 420 + border: 1px solid tone(-20%); 419 421 padding: 2px 6px; 420 422 text-shadow: 2px 2px black; 421 423 } 424 + 425 +div.post { 426 + @extend %box; 427 + display: grid; 428 + grid-template-columns: 1in 1fr max-content; 429 + grid-template-rows: 1fr max-content; 430 + margin-bottom: 0.1in; 431 + >.avatar { 432 + grid-column: 1/2; grid-row: 1/2; 433 + width: 1in; 434 + } 435 + >a[href].username { 436 + display: block; 437 + grid-column: 1/3; 438 + grid-row: 2/3; 439 + text-align: left; 440 + text-decoration: none; 441 + padding: 0.1in; 442 + padding-left: 0.15in; 443 + >.nym { font-weight: bold; } 444 + color: tone(0%,-0.4); 445 + > span.nym { color: tone(10%) } 446 + > span.handle { color: tone(-5%) } 447 + background: linear-gradient(to right, tone(-55%), transparent); 448 + } 449 + >.content { 450 + grid-column: 2/4; grid-row: 1/2; 451 + padding: 0.2in; 452 + @extend %serif; 453 + font-size: 110%; 454 + } 455 + > a[href].permalink { 456 + display: block; 457 + grid-column: 3/4; grid-row: 2/3; 458 + font-size: 80%; 459 + text-align: right; 460 + padding: 0.1in; 461 + padding-right: 0.15in; 462 + font-style: italic; 463 + background: linear-gradient(to left, tone(-55%,-0.5), transparent); 464 + } 465 +} 466 + 467 +a[href].rawlink { 468 + @extend %teletype; 469 +}
Modified store.t from [5ad659a834] to [4782518865].
1 1 -- vim: ft=terra 2 2 local m = { 3 - timepoint = uint64; 3 + timepoint = int64; 4 4 scope = lib.enum { 5 5 'public', 'private', 'local'; 6 6 'personal', 'direct', 'circle'; 7 7 }; 8 8 notiftype = lib.enum { 9 9 'mention', 'like', 'rt', 'react' 10 10 }; ................................................................................ 62 62 struct m.actor { 63 63 id: uint64 64 64 nym: str 65 65 handle: str 66 66 origin: uint64 67 67 bio: str 68 68 avatar: str 69 - knownsince: int64 69 + knownsince: m.timepoint 70 70 rights: m.rights 71 71 key: lib.mem.ptr(uint8) 72 72 73 73 -- ephemera 74 74 xid: str 75 75 source: &m.source 76 76 } ................................................................................ 79 79 posts: intptr 80 80 follows: intptr 81 81 followers: intptr 82 82 mutuals: intptr 83 83 } 84 84 85 85 struct m.range { 86 - time: bool 86 + mode: uint8 -- 0 == I->I, 1 == T->I, 2 == I->T, 3 == T->T 87 87 union { 88 88 from_time: m.timepoint 89 89 from_idx: uint64 90 90 } 91 91 union { 92 92 to_time: m.timepoint 93 93 to_idx: uint64 ................................................................................ 95 95 } 96 96 97 97 struct m.post { 98 98 id: uint64 99 99 author: uint64 100 100 subject: str 101 101 body: str 102 + acl: str 102 103 posted: m.timepoint 103 104 discovered: m.timepoint 104 - scope: m.scope.t 105 105 mentions: lib.mem.ptr(uint64) 106 106 circles: lib.mem.ptr(uint64) --only meaningful if scope is set to circle 107 107 convo: uint64 108 108 parent: uint64 109 - 109 +-- ephemera 110 + localpost: bool 110 111 source: &m.source 111 112 } 112 113 113 114 local cnf = terralib.memoize(function(ty,rty) 114 115 rty = rty or ty 115 116 return struct { 116 117 enum: {&opaque, uint64, rawstring} -> intptr ................................................................................ 207 208 -- for determining session validity & caps 208 209 -- aid: uint64 209 210 -- origin: inet 210 211 211 212 actor_conf_str: cnf(rawstring, lib.mem.ptr(int8)) 212 213 actor_conf_int: cnf(intptr, lib.stat(intptr)) 213 214 214 - post_save: {&m.source, &m.post} -> bool 215 - post_create: {&m.source, &m.post} -> bool 215 + post_save: {&m.source, &m.post} -> {} 216 + post_create: {&m.source, &m.post} -> uint64 216 217 actor_post_fetch_uid: {&m.source, uint64, m.range} -> lib.mem.ptr(m.post) 217 218 convo_fetch_xid: {&m.source,rawstring} -> lib.mem.ptr(m.post) 218 219 convo_fetch_uid: {&m.source,uint64} -> lib.mem.ptr(m.post) 219 220 220 - actor_timeline_fetch_uid: {&m.source, uint64, m.range} -> lib.mem.ptr(m.post) 221 - instance_timeline_fetch: {&m.source, m.range} -> lib.mem.ptr(m.post) 221 + actor_timeline_fetch_uid: {&m.source, uint64, m.range} -> lib.mem.ptr(lib.mem.ptr(m.post)) 222 + instance_timeline_fetch: {&m.source, m.range} -> lib.mem.ptr(lib.mem.ptr(m.post)) 222 223 } 223 224 224 225 struct m.source { 225 226 backend: &m.backend 226 227 id: lib.mem.ptr(int8) 227 228 handle: &opaque 228 229 string: lib.mem.ptr(int8)
Modified str.t from [c8d105a016] to [c5ea2451a4].
1 1 -- vim: ft=terra 2 2 -- string.t: string classes 3 3 local util = dofile('common.lua') 4 +local pstr = lib.mem.ptr(int8) 5 +local pref = lib.mem.ref(int8) 4 6 5 7 local m = { 6 8 sz = terralib.externfunction('strlen', rawstring -> intptr); 7 9 cmp = terralib.externfunction('strcmp', {rawstring, rawstring} -> int); 8 10 ncmp = terralib.externfunction('strncmp', {rawstring, rawstring, intptr} -> int); 9 11 cpy = terralib.externfunction('stpcpy',{rawstring, rawstring} -> rawstring); 10 12 ncpy = terralib.externfunction('stpncpy',{rawstring, rawstring, intptr} -> rawstring); ................................................................................ 14 16 terralib.types.funcpointer({&rawstring,rawstring},{int},true)); 15 17 bfmt = terralib.externfunction('sprintf', 16 18 terralib.types.funcpointer({rawstring,rawstring},{int},true)); 17 19 span = terralib.externfunction('strspn',{rawstring, rawstring} -> rawstring); 18 20 } 19 21 20 22 do local strptr = (lib.mem.ptr(int8)) 23 + local strref = (lib.mem.ref(int8)) 21 24 local byteptr = (lib.mem.ptr(uint8)) 22 25 strptr.metamethods.__cast = function(from,to,e) 23 26 if from == &int8 then 24 27 return `strptr {ptr = e, ct = m.sz(e)} 25 28 elseif to == &int8 then 26 29 return e.ptr 27 30 end 28 31 end 29 32 30 33 terra strptr:cmp(other: strptr) 34 + if self.ptr == nil and other.ptr == nil then return true end 35 + if self.ptr == nil or other.ptr == nil then return false end 36 + 37 + var sz = lib.math.biggest(self.ct, other.ct) 38 + for i = 0, sz do 39 + if self.ptr[i] == 0 and other.ptr[i] == 0 then return true end 40 + if self.ptr[i] ~= other.ptr[i] then return false end 41 + end 42 + return true 43 + end 44 + terra strref:cmp(other: strref) 45 + if self.ptr == nil and other.ptr == nil then return true end 46 + if self.ptr == nil or other.ptr == nil then return false end 47 + 31 48 var sz = lib.math.biggest(self.ct, other.ct) 32 49 for i = 0, sz do 33 50 if self.ptr[i] == 0 and other.ptr[i] == 0 then return true end 34 51 if self.ptr[i] ~= other.ptr[i] then return false end 35 52 end 36 53 return true 37 54 end 55 + 56 + strptr.methods.cmpl = macro(function(self,other) 57 + return `self:cmp(strptr { ptr = [other:asvalue()], ct = [#(other:asvalue())] }) 58 + end) 59 + strref.methods.cmpl = macro(function(self,other) 60 + return `self:cmp(strref { ptr = [other:asvalue()], ct = [#(other:asvalue())] }) 61 + end) 38 62 39 63 terra byteptr:cmp(other: byteptr) 40 64 var sz = lib.math.biggest(self.ct, other.ct) 41 65 for i = 0, sz do 42 66 if self.ptr[i] == 0 and other.ptr[i] == 0 then return true end 43 67 if self.ptr[i] ~= other.ptr[i] then return false end 44 68 end 45 69 return true 46 70 end 47 71 end 72 + 73 +terra m.normalize(s: pstr) 74 + var c: rawstring = s.ptr 75 + var n: rawstring = s.ptr 76 + while n < s.ptr + s.ct do 77 + while @n == 0 or @n == @'\r' do 78 + n = n + 1 79 + if n > s.ptr + s.ct then 80 + c = c + 1 goto done 81 + end 82 + end 83 + @c = @n 84 + c = c + 1 85 + n = n + 1 86 + end ::done:: 87 + @c = 0 88 + return pstr { ptr = s.ptr, ct = c - s.ptr } 89 +end 48 90 49 91 struct m.acc { 50 92 buf: rawstring 51 93 sz: intptr 52 94 run: intptr 53 95 space: intptr 54 96 } ................................................................................ 70 112 --lib.dbg('freeing string accumulator') 71 113 if self.buf ~= nil and self.space > 0 then 72 114 lib.mem.heapf(self.buf) 73 115 end 74 116 end; 75 117 76 118 terra m.acc:crush() 77 - lib.dbg('crushing string accumulator') 119 + --lib.dbg('crushing string accumulator') 78 120 self.buf = [rawstring](lib.mem.heapr_raw(self.buf, self.sz)) 79 121 self.space = self.sz 80 122 return self 81 123 end; 82 124 83 125 terra m.acc:finalize() 84 - lib.dbg('finalizing string accumulator') 126 + --lib.dbg('finalizing string accumulator') 85 127 self:crush() 86 128 var pt: lib.mem.ptr(int8) 87 129 pt.ptr = self.buf 88 130 pt.ct = self.sz 89 131 self.buf = nil 90 132 self.sz = 0 91 133 return pt ................................................................................ 114 156 lib.mem.cpy(self.buf + self.sz, str, len) 115 157 self.sz = self.sz + len 116 158 self.buf[self.sz] = 0 117 159 return self 118 160 end; 119 161 120 162 m.lit = macro(function(str) 121 - return `[lib.mem.ref(int8)] {ptr = [str:asvalue()], ct = [#(str:asvalue())]} 163 + if str:asvalue() ~= nil then 164 + return `[lib.mem.ref(int8)] {ptr = [str:asvalue()], ct = [#(str:asvalue())]} 165 + else 166 + return `[lib.mem.ref(int8)] {ptr = nil, ct = 0} 167 + end 168 +end) 169 + 170 +m.plit = macro(function(str) 171 + if str:asvalue() ~= nil then 172 + return `[lib.mem.ptr(int8)] {ptr = [str:asvalue()], ct = [#(str:asvalue())]} 173 + else 174 + return `[lib.mem.ptr(int8)] {ptr = nil, ct = 0} 175 + end 122 176 end) 123 177 124 178 m.acc.methods.lpush = macro(function(self,str) 125 179 return `self:push([str:asvalue()], [#(str:asvalue())]) end) 126 180 m.acc.methods.ppush = terra(self: &m.acc, str: lib.mem.ptr(int8)) 127 181 self:push(str.ptr, str.ct) return self end; 128 182 m.acc.methods.merge = terra(self: &m.acc, str: lib.mem.ptr(int8)) ................................................................................ 212 266 local str,sz = v[1],v[2] 213 267 if type(sz) == 'number' then 214 268 memreq_const = memreq_const + sz 215 269 elseif type(sz:asvalue()) == 'number' then 216 270 memreq_const = memreq_const + sz:asvalue() 217 271 else memreq_exp = `[sz] + [memreq_exp] end 218 272 219 - cpy = quote [kp] ; [ptr] = [&int8](lib.mem.cpy([ptr], str, sz)) end 273 + cpy = quote [kp] ; 274 + --lib.io.fmt('encapsulating string %p → %p [%s] sz %llu\n', str, [ptr], str, sz) 275 + [ptr] = [&int8](lib.mem.cpy([ptr], str, sz)) 276 + --lib.io.fmt(' :: encapsulated string %p [%s]\n', box.obj.[k],box.obj.[k]) 277 + end 220 278 if ty.ptr_basetype then 221 279 cpy = quote [cpy]; [box].obj.[k].ct = sz end 222 280 end 223 281 isnull = `[str] == nil 224 282 else 225 283 memreq_exp = `(m.sz(v) + 1) + [memreq_exp] -- make room for NUL 226 284 isnull = `v == nil
Modified tpl.t from [3cd51c8b03] to [3e776df34f].
52 52 local rec = terralib.types.newstruct(string.format('template<%s>',tplspec.id or '')) 53 53 local symself = symbol(&rec) 54 54 do local kfac = {} 55 55 for afterseg,key in pairs(fields) do 56 56 if not kfac[key] then 57 57 rec.entries[#rec.entries + 1] = { 58 58 field = key; 59 - type = rawstring; 59 + type = lib.mem.ptr(int8); 60 60 } 61 61 end 62 62 kfac[key] = (kfac[key] or 0) + 1 63 63 end 64 64 for key, fac in pairs(kfac) do 65 65 tallyup[#tallyup + 1] = quote 66 - [runningtally] = [runningtally] + lib.str.sz([symself].[key])*fac 66 + [runningtally] = [runningtally] + ([symself].[key].ct)*fac 67 67 end 68 68 end 69 69 end 70 70 71 71 local copiers = {} 72 72 local senders = {} 73 73 local appenders = {} ................................................................................ 76 76 local accumulator = symbol(&lib.str.acc) 77 77 local destcon = symbol(&lib.net.mg_connection) 78 78 for idx, seg in ipairs(segs) do 79 79 copiers[#copiers+1] = quote [cpypos] = lib.mem.cpy([cpypos], [&opaque]([seg]), [#seg]) end 80 80 senders[#senders+1] = quote lib.net.mg_send([destcon], [seg], [#seg]) end 81 81 appenders[#appenders+1] = quote [accumulator]:push([seg], [#seg]) end 82 82 if fields[idx] then 83 + --local fsz = `lib.str.sz(symself.[fields[idx]]) 84 + local fval = `symself.[fields[idx]].ptr 85 + local fsz = `symself.[fields[idx]].ct 83 86 copiers[#copiers+1] = quote 84 - [cpypos] = lib.mem.cpy([cpypos], 85 - [&opaque](symself.[fields[idx]]), 86 - lib.str.sz(symself.[fields[idx]])) 87 + [cpypos] = lib.mem.cpy([cpypos], [&opaque]([fval]), [fsz]) 87 88 end 88 89 senders[#senders+1] = quote 89 - lib.net.mg_send([destcon], 90 - symself.[fields[idx]], 91 - lib.str.sz(symself.[fields[idx]])) 90 + lib.net.mg_send([destcon], [fval], [fsz]) 91 + end 92 + appenders[#appenders+1] = quote 93 + [accumulator]:push([fval], [fsz]) 92 94 end 93 95 end 94 96 end 95 97 96 98 local tid = tplspec.id or '<anonymous>' 97 99 rec.methods.tostr = terra([symself]) 98 100 lib.dbg(['compiling template ' .. tid])
Modified view/tweet.tpl from [4ad2e9eaa9] to [e1b2184d52].
1 1 <div class="post"> 2 - <div class="detail"> 3 - <a class="username" href="@link"> 4 - <span class="nym">@nym</span> <span class="handle">[@xid]</span> 5 - </div> 6 - <div class="when">@when</div> 7 - </div> 2 + <img class="avatar" src="@avatar"> 3 + <a class="username" href="/@acctlink"> 4 + <span class="nym">@nym</span> [<span class="handle">@xid</span>] 5 + </a> 8 6 <div class="content"> 9 - <img class="avatar" src="@avatar"> 7 + <div class="subject">@subject</div> 10 8 <div class="text">@text</div> 11 9 </div> 10 + <a class="permalink" href="@permalink">@when</a> 12 11 </div>