diff --git a/run.sh b/run.sh
index d1343fc..c33553c 100755
--- a/run.sh
+++ b/run.sh
@@ -1,4 +1,4 @@
#!/bin/sh
source ./.env
rebuild
-result/bin/xrforge.com -c 0 "$@"
+DEBUG=1 result/bin/xrforge.com -c 0 "$@"
diff --git a/src/.init.lua b/src/.init.lua
index 08a569e..3a10783 100644
--- a/src/.init.lua
+++ b/src/.init.lua
@@ -15,7 +15,7 @@ HidePath('/usr/share/ssl/')
app = require("soakbean") {
bin = "./xrforge.com",
bindir = arg[-1]:gsub("xrforge.com",""),
- cache = { items = {} },
+ cache = { items = {}, url = {} },
plugin = {},
opts = {},
forkpids = {},
@@ -38,8 +38,9 @@ app.opts = require('config')(app)
-- load system plugins
app.plugin.moderation = require('plugin/moderation')
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.manyfold = require('plugin/manyfold')
app.plugin.git = require('plugin/git')
app.plugin.clouddrive = require('plugin/clouddrive')
app.plugin.webhook = require('plugin/webhook')
@@ -59,7 +60,8 @@ end)
app.plugin[1] = app.plugin.diskscan
app.plugin[2] = app.plugin.moderation
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
@@ -86,21 +88,58 @@ end)
app --
.use( require("middleware/httpauth")() )
.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()
- print( urlpath .. " => " .. diskpath)
- foreach( app.cache.items, function(k,v)
- print(v.path)
- --local itempath = v.path:gsub("^.","")
- --if itempath:sub(0,#diskpath) == diskpath then
- -- local _, nslashes = itempath:gsub("/","")
- -- local _, diskslashes = diskpath:gsub("/","")
- -- if nslashes == diskslashes + 1 then
- -- print("slashes = " .. nslashes .. " => " .. diskslashes)
- -- table.insert(items,v)
- -- end
- --end
- end)
+ local isdir = urlpath:sub(-1) == "/"
+ local nslashes = util.countChar('/',urlpath)
+ res.items = {}
+ local match = function(a,b) return util.stripPath(a) == util.stripPath(b) end
+ if urlpath:match("^"..rootpath) then
+ if not app.cache.url[ urlpath ] then
+ foreach( app.cache.items, function(k,v)
+ if v.parent then
+ trace( "path = " .. util.stripPath(v.path) .. " " ..
+ "parent = " .. util.stripPath(v.parent.path) .. " " ..
+ "urlpath = " .. util.stripPath(urlpath)
+ )
+ 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)
.use( require("json").middleware() ) -- try plug'n'play json API middleware
.use( app.router( app.url ) ) -- try url router
diff --git a/src/.lua/config.lua b/src/.lua/config.lua
index a643a7f..12a8140 100644
--- a/src/.lua/config.lua
+++ b/src/.lua/config.lua
@@ -6,7 +6,7 @@ return function(app)
name = "XRForge",
baseurl="/",
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_subtitle = "Manage your XR experiences here",
}
diff --git a/src/.lua/util.lua b/src/.lua/util.lua
index 34f77b7..4cd661a 100644
--- a/src/.lua/util.lua
+++ b/src/.lua/util.lua
@@ -1,5 +1,15 @@
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)
local file, err = io.open(filename, mode or "r")
if not file then
@@ -192,10 +202,47 @@ function trace(o)
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)
print( util.dump(t) )
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
diff --git a/src/page/dir.html b/src/page/dir.html
index e200a00..4f84333 100644
--- a/src/page/dir.html
+++ b/src/page/dir.html
@@ -7,7 +7,7 @@ ${header}
- ${tiles}
+ ${items}
${footer}
diff --git a/src/page/header.html b/src/page/header.html
index 6654ab7..cc3f0e5 100644
--- a/src/page/header.html
+++ b/src/page/header.html
@@ -26,7 +26,7 @@
diff --git a/src/plugin/diskscan.lua b/src/plugin/diskscan.lua
index 35bb1df..3860293 100644
--- a/src/plugin/diskscan.lua
+++ b/src/plugin/diskscan.lua
@@ -2,6 +2,7 @@ local dir = require "opendirectory"
app.on('init', function()
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)
end)
@@ -27,46 +28,94 @@ return {
end,
scan = function(scandir,parent,seen)
- seen = seen or {}
+ local seen = seen or {}
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
- 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 = {
- path = fullpath:gsub("^.",""), -- strip .
- name = name,
- title = name,
- tag = {},
- file = {stat=stat,state=state,kind=kind,ino=ino,off=off,attr=attr,fullpath=fullpath,name=name,path=path}
- }
+ local experience = {
+ path = fullpath,
+ url = util.stripPath(fullpath),
+ name = name,
+ title = name,
+ 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 not seen[ino] and (kind == unix.DT_DIR or kind == unix.DT_LNK)
- and not me.shouldHideFile(name) then
- seen[ino] = true -- prevent recursion with symlinks
- print("✅ ".. fullpath)
- app.pub("scan_experience", nil, experience)
- table.insert( app.cache.items, experience)
- me.scan(fullpath, dir,seen)
+ if (symlinksOnly and kind == unix.DT_LNK)
+ and not me.shouldHideFile(name) then
+ --seen[ino] = experience.file -- prevent recursion with symlinks
+ local stat, err = unix.stat(fullpath)
+ local refItem = seen[stat:ino()]
+ if refItem then
+ print("ℹ️ ".. fullpath)
+ 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
-
- ---- found file
- if not seen[ino] and (kind ~= unix.DT_DIR and kind ~= unix.DT_LNK)
- and not me.shouldHideFile(name) then
- seen[ino] = true -- prevent recursion with symlinks
- print("ℹ️ ".. fullpath)
- app.pub("scan_experience_file", nil, experience)
- local size = attr:size()
- end
-
- end)
+ end
+ -- first we scan real files, afterwards symlinks (to deal with circular symlinks)
+ local dref, derr;
+ print("--- no symlink")
+ dref, derr = dir.foreach( scandir, scanFile(false) )
+ if not derr then dref:close() end
+ print("--- symlink")
+ dref, derr = dir.foreach( scandir, scanFile(true) )
if not derr then dref:close() end
+ -- cleanup file handles
foreach( app.cache.items, function(k,v)
-- remove file attributes (which are closed already)
v.file.stat = nil
v.file.attr = nil
v.file.state = nil
end)
+
end
}
diff --git a/src/plugin/janusxr.lua b/src/plugin/janusxr.lua
index 5400501..c876d71 100644
--- a/src/plugin/janusxr.lua
+++ b/src/plugin/janusxr.lua
@@ -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]>(.*)<"
app.on('scan_experience', function(exp)
- print("dir: "..exp.file.fullpath)
--print_r(exp)
end)
@@ -20,7 +19,7 @@ app.on('scan_experience_file', function(exp)
exp.title = title:gsub("<.*","")
end
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)
end
else
diff --git a/src/plugin/xrfragments.lua b/src/plugin/xrfragments.lua
new file mode 100644
index 0000000..7436602
--- /dev/null
+++ b/src/plugin/xrfragments.lua
@@ -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 = 'deeplinking for 3D files',
+ opts = {
+ {
+ key='app.opts.plugin_xrfragments_filetypes',
+ title='Filetypes',
+ info='Space-separated 3D filetypes, lua patterns 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("