🔧 master: work in progress [might break]
This commit is contained in:
parent
f99c2e502f
commit
7276561d6b
9 changed files with 264 additions and 51 deletions
2
run.sh
2
run.sh
|
|
@ -1,4 +1,4 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
source ./.env
|
source ./.env
|
||||||
rebuild
|
rebuild
|
||||||
result/bin/xrforge.com -c 0 "$@"
|
DEBUG=1 result/bin/xrforge.com -c 0 "$@"
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ HidePath('/usr/share/ssl/')
|
||||||
app = require("soakbean") {
|
app = require("soakbean") {
|
||||||
bin = "./xrforge.com",
|
bin = "./xrforge.com",
|
||||||
bindir = arg[-1]:gsub("xrforge.com",""),
|
bindir = arg[-1]:gsub("xrforge.com",""),
|
||||||
cache = { items = {} },
|
cache = { items = {}, url = {} },
|
||||||
plugin = {},
|
plugin = {},
|
||||||
opts = {},
|
opts = {},
|
||||||
forkpids = {},
|
forkpids = {},
|
||||||
|
|
@ -38,8 +38,9 @@ app.opts = require('config')(app)
|
||||||
-- load system plugins
|
-- load system plugins
|
||||||
app.plugin.moderation = require('plugin/moderation')
|
app.plugin.moderation = require('plugin/moderation')
|
||||||
app.plugin.diskscan = require('plugin/diskscan')
|
app.plugin.diskscan = require('plugin/diskscan')
|
||||||
app.plugin.manyfold = require('plugin/manyfold')
|
app.plugin.xrfragments = require('plugin/xrfragments')
|
||||||
app.plugin.janusxr = require('plugin/janusxr')
|
app.plugin.janusxr = require('plugin/janusxr')
|
||||||
|
app.plugin.manyfold = require('plugin/manyfold')
|
||||||
app.plugin.git = require('plugin/git')
|
app.plugin.git = require('plugin/git')
|
||||||
app.plugin.clouddrive = require('plugin/clouddrive')
|
app.plugin.clouddrive = require('plugin/clouddrive')
|
||||||
app.plugin.webhook = require('plugin/webhook')
|
app.plugin.webhook = require('plugin/webhook')
|
||||||
|
|
@ -59,7 +60,8 @@ end)
|
||||||
app.plugin[1] = app.plugin.diskscan
|
app.plugin[1] = app.plugin.diskscan
|
||||||
app.plugin[2] = app.plugin.moderation
|
app.plugin[2] = app.plugin.moderation
|
||||||
app.plugin[3] = app.plugin.janusxr
|
app.plugin[3] = app.plugin.janusxr
|
||||||
app.plugin[4] = app.plugin.manyfold
|
app.plugin[4] = app.plugin.xrfragments
|
||||||
|
app.plugin[5] = app.plugin.manyfold
|
||||||
|
|
||||||
app.runcmd(app) -- run clicmds
|
app.runcmd(app) -- run clicmds
|
||||||
|
|
||||||
|
|
@ -86,21 +88,58 @@ end)
|
||||||
app --
|
app --
|
||||||
.use( require("middleware/httpauth")() )
|
.use( require("middleware/httpauth")() )
|
||||||
.use( function(req,res,next)
|
.use( function(req,res,next)
|
||||||
local diskpath = app.opts.plugin_diskscan_dir
|
local tile = require('page/partial/tile')
|
||||||
|
local rootpath = "/" .. app.opts.plugin_diskscan_dirname
|
||||||
local urlpath = GetPath()
|
local urlpath = GetPath()
|
||||||
print( urlpath .. " => " .. diskpath)
|
local isdir = urlpath:sub(-1) == "/"
|
||||||
foreach( app.cache.items, function(k,v)
|
local nslashes = util.countChar('/',urlpath)
|
||||||
print(v.path)
|
res.items = {}
|
||||||
--local itempath = v.path:gsub("^.","")
|
local match = function(a,b) return util.stripPath(a) == util.stripPath(b) end
|
||||||
--if itempath:sub(0,#diskpath) == diskpath then
|
if urlpath:match("^"..rootpath) then
|
||||||
-- local _, nslashes = itempath:gsub("/","")
|
if not app.cache.url[ urlpath ] then
|
||||||
-- local _, diskslashes = diskpath:gsub("/","")
|
foreach( app.cache.items, function(k,v)
|
||||||
-- if nslashes == diskslashes + 1 then
|
if v.parent then
|
||||||
-- print("slashes = " .. nslashes .. " => " .. diskslashes)
|
trace( "path = " .. util.stripPath(v.path) .. " " ..
|
||||||
-- table.insert(items,v)
|
"parent = " .. util.stripPath(v.parent.path) .. " " ..
|
||||||
-- end
|
"urlpath = " .. util.stripPath(urlpath)
|
||||||
--end
|
)
|
||||||
end)
|
if match(v.path, urlpath) then -- show parent folder if any
|
||||||
|
local parent = app.cache.items[ v.parent.id ]
|
||||||
|
if v.dir then
|
||||||
|
res.items[ parent.id ] = parent.portal or parent -- portal = symlinked item
|
||||||
|
print("222222222")
|
||||||
|
end
|
||||||
|
if match(parent.path, app.opts.plugin_diskscan_dir) then -- add rootfolder if needed
|
||||||
|
local root = app.cache.items[ app.opts.plugin_diskscan_dir ]
|
||||||
|
res.items[ root.id ] = root
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- add items from dir
|
||||||
|
if match(v.parent.path, urlpath) then
|
||||||
|
local parent = app.cache.items[ v.parent.id ]
|
||||||
|
res.items[ v.id ] = v
|
||||||
|
--if parent.parent then
|
||||||
|
-- res.items[ v.id ] = v
|
||||||
|
--else
|
||||||
|
-- res.items[ parent.id ] = parent
|
||||||
|
--end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
local data = util.merge({items=''},{ opts = app.opts })
|
||||||
|
foreach( res.items, function(k,v)
|
||||||
|
data.items = data.items .. tile( util.flatten(v,"item") )
|
||||||
|
end)
|
||||||
|
app.cache.url[ urlpath ] = util.template( LoadAsset("page/dir.html"), data)
|
||||||
|
end
|
||||||
|
res.status(200)
|
||||||
|
res.header('content-type','text/html; charset=utf-8')
|
||||||
|
res.body( app.cache.url[ urlpath ] )
|
||||||
|
next()
|
||||||
|
else
|
||||||
|
next()
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
.use( require("json").middleware() ) -- try plug'n'play json API middleware
|
.use( require("json").middleware() ) -- try plug'n'play json API middleware
|
||||||
.use( app.router( app.url ) ) -- try url router
|
.use( app.router( app.url ) ) -- try url router
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ return function(app)
|
||||||
name = "XRForge",
|
name = "XRForge",
|
||||||
baseurl="/",
|
baseurl="/",
|
||||||
title="Your XR Forge",
|
title="Your XR Forge",
|
||||||
subtitle="Local-first XR experiences for Phone, VRheadset & desktop.",
|
subtitle="Portable XR experiences for Phone, VRheadset & desktop.",
|
||||||
admin_title = "XRForge Admin",
|
admin_title = "XRForge Admin",
|
||||||
admin_subtitle = "Manage your XR experiences here",
|
admin_subtitle = "Manage your XR experiences here",
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,15 @@
|
||||||
local util = { }
|
local util = { }
|
||||||
|
|
||||||
|
util.fileExist = function(filename)
|
||||||
|
local file, err = io.open(filename, mode or "r")
|
||||||
|
local exist = false
|
||||||
|
if file then
|
||||||
|
exist = true
|
||||||
|
file:close()
|
||||||
|
end
|
||||||
|
return exist
|
||||||
|
end
|
||||||
|
|
||||||
util.readfile = function(filename, mode, silent)
|
util.readfile = function(filename, mode, silent)
|
||||||
local file, err = io.open(filename, mode or "r")
|
local file, err = io.open(filename, mode or "r")
|
||||||
if not file then
|
if not file then
|
||||||
|
|
@ -192,10 +202,47 @@ function trace(o)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- usage: result = filter({ {foo=1},{foo=2} }, function(k,v) return v.foo > 1 end) -- {{foo=2}}
|
||||||
|
-- result = filter({ {foo=1},{foo=2} }, {foo=1}) -- {{foo=1}}
|
||||||
|
function util.filter(t,t_or_func)
|
||||||
|
local result = {}
|
||||||
|
foreach(t, function(k,v)
|
||||||
|
if type(t_or_func) == "function" then
|
||||||
|
if t_or_func(k,v) then result[k] = v end
|
||||||
|
else
|
||||||
|
local match = 0
|
||||||
|
foreach( t_or_func, function(kk,vv)
|
||||||
|
if v[kk] == vv then result[k] = v end
|
||||||
|
end)
|
||||||
|
if match == util.count(t_or_func) then result[k] = v end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
function util.countChar(char, str)
|
||||||
|
local _, nchars = str:gsub(char,"")
|
||||||
|
return nchars
|
||||||
|
end
|
||||||
|
|
||||||
function print_r(t)
|
function print_r(t)
|
||||||
print( util.dump(t) )
|
print( util.dump(t) )
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function util.split(inputstr, sep)
|
||||||
|
if sep == nil then
|
||||||
|
sep = "%s"
|
||||||
|
end
|
||||||
|
local t = {}
|
||||||
|
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
|
||||||
|
table.insert(t, str)
|
||||||
|
end
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
function util.stripPath(str)
|
||||||
|
return str:gsub("^./",""):gsub("^/",""):gsub("/$","")
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
return util
|
return util
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ ${header}
|
||||||
<hr style="margin-top:0">
|
<hr style="margin-top:0">
|
||||||
|
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
${tiles}
|
${items}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
${footer}
|
${footer}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@
|
||||||
<td class="menu">
|
<td class="menu">
|
||||||
<div class="margin1L">
|
<div class="margin1L">
|
||||||
<!-- important: no spaces between anchor tags for older browsers without flexbox -->
|
<!-- important: no spaces between anchor tags for older browsers without flexbox -->
|
||||||
<a href="/dir">/myverse</a><a href="/about">/about</a>
|
<a href="/${opts.plugin_diskscan_dirname}">${opts.plugin_diskscan_dirname}</a><a href="/about">about</a>
|
||||||
<!--<a class="xl" href="/howto">Howto</a></div>-->
|
<!--<a class="xl" href="/howto">Howto</a></div>-->
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ local dir = require "opendirectory"
|
||||||
|
|
||||||
app.on('init', function()
|
app.on('init', function()
|
||||||
print("scanning experiences (this make take a while..)")
|
print("scanning experiences (this make take a while..)")
|
||||||
|
app.opts.plugin_diskscan_dirname = app.opts.plugin_diskscan_dir:gsub("^[.]?[/]?","")
|
||||||
app.plugin.diskscan.scan(app.opts.plugin_diskscan_dir)
|
app.plugin.diskscan.scan(app.opts.plugin_diskscan_dir)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
@ -27,46 +28,94 @@ return {
|
||||||
end,
|
end,
|
||||||
|
|
||||||
scan = function(scandir,parent,seen)
|
scan = function(scandir,parent,seen)
|
||||||
seen = seen or {}
|
local seen = seen or {}
|
||||||
local me = app.plugin.diskscan
|
local me = app.plugin.diskscan
|
||||||
|
-- ensure rootdir as experience
|
||||||
|
app.cache.items[ scandir ] = {
|
||||||
|
path = scandir,
|
||||||
|
url = util.stripPath(scandir),
|
||||||
|
name = scandir:gsub(".*/",""),
|
||||||
|
title = scandir:gsub(".*/",""),
|
||||||
|
file = { ino = scandir },
|
||||||
|
id = scandir
|
||||||
|
}
|
||||||
|
parent = parent or app.cache.items[scandir]
|
||||||
-- scan dirs
|
-- scan dirs
|
||||||
local dref, derr = dir.foreach( scandir, function(path,stat,state,name,kind,ino,off,attr,fullpath)
|
local scanFile = function(symlinksOnly)
|
||||||
|
return function(path,stat,state,name,kind,ino,off,attr,fullpath)
|
||||||
|
if seen[ino] then return end
|
||||||
|
|
||||||
local experience = {
|
local experience = {
|
||||||
path = fullpath:gsub("^.",""), -- strip .
|
path = fullpath,
|
||||||
name = name,
|
url = util.stripPath(fullpath),
|
||||||
title = name,
|
name = name,
|
||||||
tag = {},
|
title = name,
|
||||||
file = {stat=stat,state=state,kind=kind,ino=ino,off=off,attr=attr,fullpath=fullpath,name=name,path=path}
|
sidecarfile = {},
|
||||||
}
|
tag = {},
|
||||||
|
id = ino,
|
||||||
|
file = {stat=stat,state=state,kind=kind,ino=ino,off=off,attr=attr,fullpath=fullpath,name=name,path=path}
|
||||||
|
}
|
||||||
|
experience.parent = parent
|
||||||
|
|
||||||
-- found directory
|
if (symlinksOnly and kind == unix.DT_LNK)
|
||||||
if not seen[ino] and (kind == unix.DT_DIR or kind == unix.DT_LNK)
|
and not me.shouldHideFile(name) then
|
||||||
and not me.shouldHideFile(name) then
|
--seen[ino] = experience.file -- prevent recursion with symlinks
|
||||||
seen[ino] = true -- prevent recursion with symlinks
|
local stat, err = unix.stat(fullpath)
|
||||||
print("✅ ".. fullpath)
|
local refItem = seen[stat:ino()]
|
||||||
app.pub("scan_experience", nil, experience)
|
if refItem then
|
||||||
table.insert( app.cache.items, experience)
|
print("ℹ️ ".. fullpath)
|
||||||
me.scan(fullpath, dir,seen)
|
print("✅ └─ plugin/diskscan: detected circular symlink..creating portal")
|
||||||
|
experience.file = refItem.file
|
||||||
|
experience.portal = refItem
|
||||||
|
app.pub("scan_experience", nil, experience)
|
||||||
|
app.cache.items[ino] = experience
|
||||||
|
else
|
||||||
|
print(":(")
|
||||||
|
print("symlink: " .. fullpath)
|
||||||
|
print_r(experience)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if symlinksOnly then return end
|
||||||
|
|
||||||
|
-- found directory
|
||||||
|
if (kind == unix.DT_DIR and kind ~= unix.DT_LNK)
|
||||||
|
and not me.shouldHideFile(name) then
|
||||||
|
seen[ino] = experience -- prevent recursion with symlinks
|
||||||
|
print("✅ ".. fullpath)
|
||||||
|
experience.dir = true
|
||||||
|
app.pub("scan_experience", nil, experience)
|
||||||
|
app.cache.items[ino] = experience
|
||||||
|
me.scan(fullpath,experience,seen)
|
||||||
|
end
|
||||||
|
|
||||||
|
---- found file
|
||||||
|
if (kind ~= unix.DT_DIR and kind ~= unix.DT_LNK)
|
||||||
|
and not me.shouldHideFile(name) then
|
||||||
|
seen[ino] = experience.file -- prevent recursion with symlinks
|
||||||
|
trace("ℹ️ ".. fullpath)
|
||||||
|
experience.tag.file = true
|
||||||
|
app.pub("scan_experience_file", nil, experience)
|
||||||
|
local size = attr:size()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
---- found file
|
-- first we scan real files, afterwards symlinks (to deal with circular symlinks)
|
||||||
if not seen[ino] and (kind ~= unix.DT_DIR and kind ~= unix.DT_LNK)
|
local dref, derr;
|
||||||
and not me.shouldHideFile(name) then
|
print("--- no symlink")
|
||||||
seen[ino] = true -- prevent recursion with symlinks
|
dref, derr = dir.foreach( scandir, scanFile(false) )
|
||||||
print("ℹ️ ".. fullpath)
|
if not derr then dref:close() end
|
||||||
app.pub("scan_experience_file", nil, experience)
|
print("--- symlink")
|
||||||
local size = attr:size()
|
dref, derr = dir.foreach( scandir, scanFile(true) )
|
||||||
end
|
|
||||||
|
|
||||||
end)
|
|
||||||
if not derr then dref:close() end
|
if not derr then dref:close() end
|
||||||
|
|
||||||
|
-- cleanup file handles
|
||||||
foreach( app.cache.items, function(k,v)
|
foreach( app.cache.items, function(k,v)
|
||||||
-- remove file attributes (which are closed already)
|
-- remove file attributes (which are closed already)
|
||||||
v.file.stat = nil
|
v.file.stat = nil
|
||||||
v.file.attr = nil
|
v.file.attr = nil
|
||||||
v.file.state = nil
|
v.file.state = nil
|
||||||
end)
|
end)
|
||||||
|
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ local pattern_fireboxroom = "[Ff][Ii][Rr][Ee][Bb][Oo][Xx][Rr][Oo][Oo][Mm][ ]?.*>
|
||||||
local pattern_title = "<[Tt][Ii][Tt][Ll][Ee]>(.*)<"
|
local pattern_title = "<[Tt][Ii][Tt][Ll][Ee]>(.*)<"
|
||||||
|
|
||||||
app.on('scan_experience', function(exp)
|
app.on('scan_experience', function(exp)
|
||||||
print("dir: "..exp.file.fullpath)
|
|
||||||
--print_r(exp)
|
--print_r(exp)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
@ -20,7 +19,7 @@ app.on('scan_experience_file', function(exp)
|
||||||
exp.title = title:gsub("<.*","")
|
exp.title = title:gsub("<.*","")
|
||||||
end
|
end
|
||||||
app.pub("scan_experience_janusxr", nil, {exp=exp, item=item}) -- be nice to other plugins
|
app.pub("scan_experience_janusxr", nil, {exp=exp, item=item}) -- be nice to other plugins
|
||||||
table.insert( app.cache.items, exp)
|
app.cache.items[ exp.file.ino ] = exp
|
||||||
print("✅ └─ plugin/janusxr: " .. exp.file.name)
|
print("✅ └─ plugin/janusxr: " .. exp.file.name)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
|
|
||||||
79
src/plugin/xrfragments.lua
Normal file
79
src/plugin/xrfragments.lua
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
local json = require('json')
|
||||||
|
|
||||||
|
app.on('init', function()
|
||||||
|
end)
|
||||||
|
|
||||||
|
app.on('scan_experience_file', function(exp)
|
||||||
|
local me = app.plugin.xrfragments
|
||||||
|
foreach( util.split( app.opts.plugin_xrfragments_filetypes, ' '), function(k,ext)
|
||||||
|
if exp.path:lower():match("."..ext.."$") then
|
||||||
|
if ext == 'glb' then
|
||||||
|
-- detect XR Fragment exp https://xrfragment.org/#%F0%9F%93%9C%20level0%3A%20File
|
||||||
|
local tree = me.extract_glb_json( util.stripPath(exp.path) )
|
||||||
|
local hasHref = me.detectHrefInStruct(tree)
|
||||||
|
end
|
||||||
|
-- check sidecarfiles: https://xrfragment.org/#sidecar%20files
|
||||||
|
local sidecarExts = {"png","vtt","ogg","json"}
|
||||||
|
foreach( sidecarExts, function(k,sidecarExt)
|
||||||
|
if util.fileExist( exp.path:gsub("."..ext.."$", "."..sidecarExt) ) then
|
||||||
|
exp.sidecarfile[ sidecarExt ] = true
|
||||||
|
exp.view3D = true
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
local jsonfile = exp.path:gsub("."..ext.."$",".json")
|
||||||
|
-- scan for hrefs in json sidecarfile
|
||||||
|
if util.fileExist( jsonfile ) then
|
||||||
|
exp.sidecarfile.JSONXRF = me.detectHrefInStruct( json.decode( util.readfile(jsonfile) ) )
|
||||||
|
end
|
||||||
|
exp.tag.xrfragments = hasPNG or hasWebVTT or hasOGG or hasJSONref
|
||||||
|
if exp.tag.xrfragments then
|
||||||
|
print("✅ └─ plugin/xrfragments: detected xrfragments experience")
|
||||||
|
end
|
||||||
|
foreach(exp.sidecarfile, function(ext,v)
|
||||||
|
print("✅ └─ plugin/xrfragments: detected .".. ext:lower() .. " sidecarfile")
|
||||||
|
end)
|
||||||
|
-- finally insert if viewable
|
||||||
|
if exp.view3D then
|
||||||
|
app.cache.items[ exp.file.ino ] = exp
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- this registers the plugin UI /funcs
|
||||||
|
return {
|
||||||
|
name = "XR Fragments",
|
||||||
|
info = '<a href="https://xrfragment.org" target="_blank">deeplinking for 3D files</a>',
|
||||||
|
opts = {
|
||||||
|
{
|
||||||
|
key='app.opts.plugin_xrfragments_filetypes',
|
||||||
|
title='Filetypes',
|
||||||
|
info='Space-separated 3D filetypes, <a href="http://lua-users.org/wiki/PatternsTutorial" target="_blank">lua patterns</a> are allowed',
|
||||||
|
default="glb gltf obj dae",
|
||||||
|
values=''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
extract_glb_json = function(filename)
|
||||||
|
local f = assert(io.open(filename, "rb"))
|
||||||
|
-- Skip 12-byte header + 4-byte chunk length + 4-byte chunk type
|
||||||
|
f:seek("set", 12)
|
||||||
|
local length_data = f:read(4)
|
||||||
|
if not length_data then f:close(); return nil end
|
||||||
|
-- Unpack 32-bit little-endian integer for the JSON chunk length
|
||||||
|
local length = string.unpack("<I4", length_data)
|
||||||
|
f:seek("set", 20) -- Jump straight to the start of the JSON string
|
||||||
|
local json_str = f:read(length)
|
||||||
|
f:close()
|
||||||
|
return json.decode(json_str)
|
||||||
|
end,
|
||||||
|
|
||||||
|
detectHrefInStruct = function(t,parent,parentKey,result)
|
||||||
|
result = result or {detected=false}
|
||||||
|
foreach( t, function(k,v)
|
||||||
|
if k == 'href' then result.detected = true end
|
||||||
|
if type(v) == 'table' then app.plugin.xrfragments.detectHrefInStruct(v,t,k,result) end
|
||||||
|
end)
|
||||||
|
return result.detected
|
||||||
|
end
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue