Index: backend/pgsql.t ================================================================== --- backend/pgsql.t +++ backend/pgsql.t @@ -1589,23 +1589,38 @@ var idbuf: int8[lib.math.shorthand.maxlen] var idlen = lib.math.shorthand.gen(id, &idbuf[0]) var desc = res:_string(i,2) var folder = res:_string(i,3) var mime = res:_string(i,4) - var url = lib.str.acc{}:init(48):lpush('/media/a/'):push(&idbuf[0],idlen):finalize() defer url:free() m.ptr[i] = [ lib.str.encapsulate(lib.store.artifact, { desc = {`desc.ptr, `desc.ct + 1}; folder = {`folder.ptr, `folder.ct + 1}; mime = {`mime.ptr, `mime.ct + 1}; - url = {`url.ptr, `url.ct + 1}; + url = {`&idbuf[0], `idlen + 1}; }) ] m(i).ptr.rid = id m(i).ptr.owner = uid end return m else return [lib.mem.lstptr(lib.store.artifact)].null() end end]; + + artifact_load = [terra( + src: &lib.store.source, + rid: uint64 + ): {binblob, pstring} + var r = queries.artifact_load.exec(src,rid) + if r.sz == 0 then return binblob.null(), pstring.null() end + + var mime = r:String(0,1) + var mbin = r:bin(0,0) + var bin = lib.mem.heapa(uint8,mbin.ct) + lib.mem.cpy(bin.ptr, mbin.ptr, bin.ct) + + r:free() + return bin, mime + end]; post_attach_ctl = [terra( src: &lib.store.source, post: uint64, artifact: uint64, Index: math.t ================================================================== --- math.t +++ math.t @@ -226,38 +226,40 @@ if f(i) == @',' then goto skip end if f(i) >= 0x30 and f(i) <= 0x39 then sz = sz * 10 sz = sz + f(i) - 0x30 else - if i+1 == f.ct or f(i) == 0 then return sz, true end - if i+2 == f.ct or f(i+1) == 0 then + if i+0 == f.ct or f(i) == 0 then return sz, true end + if i+1 == f.ct or f(i+1) == 0 then if f(i) == @'b' then return sz/8, true end -- bits else var s: intptr = 0 - if i+3 == f.ct or f(i+2) == 0 then + if i+2 == f.ct or f(i+2) == 0 then s = i + 1 - elseif (i+4 == f.ct or f(i+3) == 0) and f(i+1) == @'i' then + elseif (i+3 == f.ct or f(i+3) == 0) and f(i+1) == @'i' then -- grudgingly tolerate ~mebibits~ and its ilk, without -- affecting the result in any way s = i + 2 else return 0, false end if f(s) == @'b' then sz = sz/8 -- bits elseif f(s) ~= @'B' then return 0, false end -- wth end var c = f(i) - if c >= @'A' and c <= @'Z' then c = c - 0x20 end + if c >= @'A' and c <= @'Z' then c = c + 0x20 end switch c do -- normal char literal syntax doesn't work here, leads to llvm error (!!) case [uint8]([string.byte('k')]) then return sz * [1024ULL ^ 1], true end case [uint8]([string.byte('m')]) then return sz * [1024ULL ^ 2], true end case [uint8]([string.byte('g')]) then return sz * [1024ULL ^ 3], true end case [uint8]([string.byte('t')]) then return sz * [1024ULL ^ 4], true end - case [uint8]([string.byte('e')]) then return sz * [1024ULL ^ 5], true end - case [uint8]([string.byte('y')]) then return sz * [1024ULL ^ 6], true end - else return sz, true + case [uint8]([string.byte('p')]) then return sz * [1024ULL ^ 5], true end + case [uint8]([string.byte('e')]) then return sz * [1024ULL ^ 6], true end + case [uint8]([string.byte('z')]) then return sz * [1024ULL ^ 7], true end + case [uint8]([string.byte('y')]) then return sz * [1024ULL ^ 8], true end + else return 0, false end end ::skip::end return sz, true end return m Index: render/media-gallery.t ================================================================== --- render/media-gallery.t +++ render/media-gallery.t @@ -3,64 +3,72 @@ local P = lib.str.plit local terra cs(s: rawstring) return pstr { ptr = s, ct = lib.str.sz(s) } end -local show_all,show_new,show_files,show_vid,show_img=1,2,3,4,5 +local show_all,show_new,show_unfiled,show_files,show_vid,show_img=1,2,3,4,5,6 local terra render_media_gallery(co: &lib.srv.convo, path: lib.mem.ptr(lib.mem.ref(int8)), uid: uint64, acc: &lib.str.acc) -- note that when calling this function, path must be adjusted so that path(0) -- eq "media" var owner = false if co.aid ~= 0 and co.who.id == uid then owner = true end var ou = co.srv:actor_fetch_uid(uid) if not ou then goto e404 end + + var mode: uint8 = show_new + var folder: pstr + if path.ct == 2 then + if path(1):cmp(lib.str.lit'unfiled') then + mode=show_unfiled + elseif path(1):cmp(lib.str.lit'all') then + mode=show_all + else goto e404 end + elseif path.ct == 3 and path(1):cmp(lib.str.lit'kind') then + end + + if mode == show_new then + folder = lib.str.plit'' + elseif mode == show_all then + folder = pstr.null() + elseif mode == show_unfiled then + folder = lib.str.plit'' -- TODO + -- else get folder from query str + end var view = data.view.media_gallery { menu = pstr{'',0}; folders = pstr{'',0}; directory = pstr{'',0}; images = pstr{'',0}; + pfx = pstr{'',0}; } + if not owner then + var pa: lib.str.acc pa:init(32) + pa:lpush('/') + if ou(0).origin ~= 0 then pa:lpush('@') end + pa:push(ou(0).xid,0) + view.pfx = pa:finalize() + end if owner then view.menu = P'upload
' end - var mode: uint8 = show_new - var folder: pstr - if mode == show_new then - folder = lib.str.plit'' - elseif mode == show_all then - folder = pstr.null() - -- else get folder from query str - end var md = co.srv:artifact_enum_uid(uid, folder) var gallery: lib.str.acc gallery:init(256) var files: lib.str.acc files:init(256) for i=0,md.ct do if lib.str.ncmp(md(i)(0).mime, 'image/', 6) == 0 then - gallery:lpush('
') :push(md(i)(0).desc,0) + gallery:lpush('
'):push(md(i)(0).desc,0) :lpush('
') else - files:lpush(''):push(md(i)(0).desc,0) + files:lpush(''):push(md(i)(0).desc,0) :lpush(' '):push(md(i)(0).mime,0) :lpush('') end md(i):free() end @@ -69,23 +77,27 @@ view.directory = files:finalize() if acc ~= nil then view:append(acc) else + lib.dbg('emitting page') var pg = view:tostr() defer pg:free() + lib.dbg('compiled page') co:stdpage([lib.srv.convo.page] { title = P'media'; class = P'media manager'; cache = false; body = pg; }) + lib.dbg('sent page') end view.images:free() view.directory:free() + if not owner then view.pfx:free() end if md:ref() then md:free() end do return end ::e404:: co:complain(404,'media not found','no such media exists on this server') end return render_media_gallery Index: route.t ================================================================== --- route.t +++ route.t @@ -406,65 +406,63 @@ terra http.media_manager(co: &lib.srv.convo, path: hpath, meth: method.t) if meth == method.post then goto badop end - if path.ct == 1 or (path.ct >= 3 and path(1):cmp(lib.str.lit'a')) then - if meth == method.post then goto badop end - lib.render.media_gallery(co,path,co.who.id,nil) - elseif path.ct == 2 then - if path(1):cmp(lib.str.lit'upload') and co.who.rights.powers.artifact() then - if meth == method.get then - var view = data.view.media_upload { - folders = '' - } - var pg = view:tostr() defer pg:free() - co:stdpage([lib.srv.convo.page] { - title = lib.str.plit'media :: upload'; - class = lib.str.plit'media upload'; - cache = false; body = pg; - }) - elseif meth == method.post_file then - var desc = pstring.null() - var folder = pstring.null() - var mime = pstring.null() - var name = pstring.null() - var body = binblob.null() - for i=0, co.uploads.sz do var up = co.uploads.storage.ptr + i - if up.body.ct > 0 then - if up.field:cmp(lib.str.plit'desc') then - desc = up.body - elseif up.field:cmp(lib.str.plit'folder') then - folder = up.body - elseif up.field:cmp(lib.str.plit'file') then - mime = up.ctype - body = binblob {ptr = [&uint8](up.body.ptr), ct = up.body.ct} - name = up.filename - end + if path.ct == 2 and path(1):cmp(lib.str.lit'upload') and co.who.rights.powers.artifact() then + if meth == method.get then + var view = data.view.media_upload { + folders = '' + } + var pg = view:tostr() defer pg:free() + co:stdpage([lib.srv.convo.page] { + title = lib.str.plit'media :: upload'; + class = lib.str.plit'media upload'; + cache = false; body = pg; + }) + elseif meth == method.post_file then + var desc = pstring.null() + var folder = pstring.null() + var mime = pstring.null() + var name = pstring.null() + var body = binblob.null() + for i=0, co.uploads.sz do var up = co.uploads.storage.ptr + i + if up.body.ct > 0 then + if up.field:cmp(lib.str.plit'desc') then + desc = up.body + elseif up.field:cmp(lib.str.plit'folder') then + folder = up.body + elseif up.field:cmp(lib.str.plit'file') then + mime = up.ctype + body = binblob {ptr = [&uint8](up.body.ptr), ct = up.body.ct} + name = up.filename end end - if not body then goto badop end - if body.ct > co.srv.cfg.maxupsz then - co:complain(403, 'file too long', "the file you have attempted to upload exceeds the maximum length permitted by this server's upload policy. if it is an image or video, try compressing it at a lower quality setting or resolution") - return - end - var id = co.srv:artifact_instantiate(body,mime) - if id == 0 then - co:complain(500,'upload failed','artifact rejected. either the server is running out of space or this file is banned from the server') - return - end - co.srv:artifact_expropriate(co.who.id,id,desc,folder) + end + if not body then goto badop end + if body.ct > co.srv.cfg.maxupsz then + co:complain(403, 'file too long', "the file you have attempted to upload exceeds the maximum length permitted by this server's upload policy. if it is an image or video, try compressing it at a lower quality setting or resolution") + return + end + var id = co.srv:artifact_instantiate(body,mime) + if id == 0 then + co:complain(500,'upload failed','artifact rejected. either the server is running out of space or this file is banned from the server') + return + end + co.srv:artifact_expropriate(co.who.id,id,desc,folder) + + var idbuf: int8[lib.math.shorthand.maxlen] + var idlen = lib.math.shorthand.gen(id,&idbuf[0]) - var idbuf: int8[lib.math.shorthand.maxlen] - var idlen = lib.math.shorthand.gen(id,&idbuf[0]) - - var url = lib.str.acc{}:compose('/media/a/',pstring{&idbuf[0],idlen}):finalize() - co:reroute(url.ptr) - url:free() - else goto badop end - end - else goto e404 end + var url = lib.str.acc{}:compose('/media/a/',pstring{&idbuf[0],idlen}):finalize() + co:reroute(url.ptr) + url:free() + else goto badop end + else + if meth == method.post then goto badop end + lib.render.media_gallery(co,path,co.who.id,nil) + end do return end ::badop:: do co:complain(405, 'invalid operation', 'the operation you have attempted on this post is not meaningful') return end ::e404:: do co:complain(404, 'artifact not found', 'no such artifact has been uploaded by this user') return end end @@ -507,10 +505,24 @@ terra http.local_avatar(co: &lib.srv.convo, handle: lib.mem.ptr(int8)) -- TODO retrieve user avatars co:reroute('/s/default-avatar.webp') end + +terra http.file_serve_raw(co: &lib.srv.convo, id: lib.mem.ptr(int8)) + var id, idok = lib.math.shorthand.parse(id.ptr, id.ct) + if not idok then goto e404 end + var data, mime = co.srv:artifact_load(id) + if not data then goto e404 end + do defer data:free() defer mime:free() + lib.net.mg_printf(co.con, 'HTTP/1.1 200 OK\r\nContent-Type: %.*s\r\nContent-Length: %llu\r\n\r\n', mime.ct, mime.ptr, data.ct + 2) + lib.net.mg_send(co.con, data.ptr, data.ct) + lib.net.mg_send(co.con, '\r\n', 2) + return end + + ::e404:: do co:complain(404, 'artifact not found', 'no such artifact has been uploaded to this instance') return end +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) @@ -528,10 +540,12 @@ elseif uri.ptr[1] == @'s' and uri.ptr[2] == @'/' and uri.ct > 3 then if not meth_get(meth) then goto wrongmeth end if not http.static_content(co, uri.ptr + 3, uri.ct - 3) then goto notfound end elseif lib.str.ncmp('/avi/', uri.ptr, 5) == 0 then http.local_avatar(co, [lib.mem.ptr(int8)] {ptr = uri.ptr + 5, ct = uri.ct - 5}) + elseif lib.str.ncmp('/file/', uri.ptr, 6) == 0 then + http.file_serve_raw(co, [lib.mem.ptr(int8)] {ptr = uri.ptr + 6, ct = uri.ct - 6}) elseif uri:cmp(lib.str.plit '/notices') then if co.aid == 0 then co:reroute('/login') return end http.user_notices(co,meth) elseif uri:cmp(lib.str.plit '/compose') then if co.aid == 0 then co:reroute('/login') return end @@ -553,10 +567,11 @@ elseif path.ct > 1 and path(0):cmp(lib.str.lit('post')) then http.tweet_page(co, path, meth) elseif path(0):cmp(lib.str.lit('tl')) then http.timeline(co, path) elseif path(0):cmp(lib.str.lit('media')) then + if co.aid == 0 then goto unauth end http.media_manager(co, path, meth) elseif path(0):cmp(lib.str.lit('doc')) then if not meth_get(meth) then goto wrongmeth end http.documentation(co, path) elseif path(0):cmp(lib.str.lit('conf')) then Index: srv.t ================================================================== --- srv.t +++ srv.t @@ -528,19 +528,17 @@ -- form-data encoded POST request so we can handle file uploads co.uploads.sz = 0 co.uploads.run = 0 if co.method == [lib.http.method.post] then var ctt = lib.http.findheader(msg, 'Content-Type') if ctt ~= nil then - lib.dbg('found content type', {ctt.ptr,ctt.ct}) if lib.str.ncmp(ctt.ptr,'multipart/form-data;',20) == 0 then var p = lib.str.ffw(ctt.ptr + 20,ctt.ct-20) if lib.str.ncmp(p,'boundary=',9) ~= 0 then co:complain(400,'bad request','unrecognized content-type') goto fail end var boundary = pstring {ptr=p+9,ct=ctt.ct - ((p - ctt.ptr) + 9)} - lib.dbg('got boundary ',{boundary.ptr,boundary.ct}) co.method = lib.http.method.post_file co.uploads:init(8) var bsr = (lib.str.acc{}):compose('\r\n--',boundary,'\r\n'):finalize() @@ -563,11 +561,10 @@ lsr:free() end for i=0,upmap.ct do var hdrbrk = lib.str.find(upmap(i), lib.str.plit'\r\n\r\n') if hdrbrk:ref() then - lib.dbg('got new entry') var hdrtxt = pstring {upmap(i).ptr,upmap(i).ct - hdrbrk.ct} var hdrs = lib.str.splitmap(hdrtxt, '\r\n',6) var ctt = pstring.null() var ctd = pstring.null() for j=0, hdrs.ct do @@ -583,11 +580,10 @@ end end if ctd:ref() then var ctdvals = lib.str.splitmap(ctd, ';', 4) defer ctdvals:free() if ctdvals(0):cmp(lib.str.plit'form-data') and ctdvals.ct > 1 then - lib.dbg('found form data') var fld = pstring.null() var file = pstring.null() for j=1, ctdvals.ct do var v = ctdvals(j):ffw() var x = lib.str.find(v,lib.str.plit'=') if x:ref() then @@ -778,11 +774,10 @@ end return 0 end ---9twh8y94i5c1qqr7hxu20fyd terra cfgcache.methods.load :: {&cfgcache} -> {} terra cfgcache:init(o: &srv) self.overlord = o self:load() end Index: static/live.js ================================================================== --- static/live.js +++ static/live.js @@ -119,11 +119,10 @@ let url = null; if (lede == null) {url = purl;} else { url = lede.querySelector('a[href].del'). attributes.getNamedItem('href').value; } - console.log('post',post,'lede',lede,url); if (last == null) { newmap.first = url; } else { newmap.map.get(last).next = url; } newmap.map.set(url, {me: post, go: purl, prev: last, next: null}) Index: static/style.scss ================================================================== --- static/style.scss +++ static/style.scss @@ -1020,30 +1020,39 @@ padding: 0.2in; display: flex; flex-wrap: wrap; } .gallery { - grid-row: 1/2; grid-column: 2/3; - margin-left: 0.1in; + grid-row: 2/3; grid-column: 1/3; + margin-top: 0.1in; flex-flow: row; + padding: 0 0.1in; > a[href].thumb { + background: linear-gradient(to top, tone(10%,-0.8), tone(10%,-0.94) 30%, transparent); + border-radius: 4px; + border-bottom: 1px solid tone(15%, -0.5); display: block; width: 1.5in; padding: 0.1in; height: max-content; + margin: 0.1in; > img { width: 1.5in; height: 1.5in; + object-fit: contain; + object-position: center; + outline: none; } > .caption { text-align: center; font-size: 80%; + text-shadow: 1px 1px 0 black; } } } .dir { - grid-row: 2/3; grid-column: 1/3; - margin-top: 0.1in; + grid-row: 1/2; grid-column: 2/3; + margin-left: 0.1in; flex-flow: column; flex-grow: 1; > a[href].file { padding: 0.1in 0.15in; text-decoration: none; Index: view/media-gallery.tpl ================================================================== --- view/media-gallery.tpl +++ view/media-gallery.tpl @@ -1,19 +1,19 @@ @menu - new uploads - unfiled + new uploads + unfiled
@folders - all uploads - all images - all videos - all text files - all others + all uploads + all images + all videos + all text files + all others
@directory