diskscan: tamed the recursive symlink beast scanner

This commit is contained in:
Leon van Kammen 2026-07-28 17:55:51 +02:00
parent 7276561d6b
commit 6a14efeba6
3 changed files with 87 additions and 80 deletions

View file

@ -96,6 +96,7 @@ app --
res.items = {} res.items = {}
local match = function(a,b) return util.stripPath(a) == util.stripPath(b) end local match = function(a,b) return util.stripPath(a) == util.stripPath(b) end
if urlpath:match("^"..rootpath) then if urlpath:match("^"..rootpath) then
local redirect = false
if not app.cache.url[ urlpath ] then if not app.cache.url[ urlpath ] then
foreach( app.cache.items, function(k,v) foreach( app.cache.items, function(k,v)
if v.parent then if v.parent then
@ -105,9 +106,8 @@ app --
) )
if match(v.path, urlpath) then -- show parent folder if any if match(v.path, urlpath) then -- show parent folder if any
local parent = app.cache.items[ v.parent.id ] local parent = app.cache.items[ v.parent.id ]
if v.dir then if v.portal then
res.items[ parent.id ] = parent.portal or parent -- portal = symlinked item redirect = v.portal.url
print("222222222")
end end
if match(parent.path, app.opts.plugin_diskscan_dir) then -- add rootfolder if needed if match(parent.path, app.opts.plugin_diskscan_dir) then -- add rootfolder if needed
local root = app.cache.items[ app.opts.plugin_diskscan_dir ] local root = app.cache.items[ app.opts.plugin_diskscan_dir ]
@ -133,10 +133,18 @@ app --
end) end)
app.cache.url[ urlpath ] = util.template( LoadAsset("page/dir.html"), data) app.cache.url[ urlpath ] = util.template( LoadAsset("page/dir.html"), data)
end end
res.status(200) if redirect then
res.header('content-type','text/html; charset=utf-8') print("REDIRECT! " .. redirect )
res.body( app.cache.url[ urlpath ] ) res.status(303)
next() res.header('Location', redirect )
print_r(res)
next()
else
res.status(200)
res.header('content-type','text/html; charset=utf-8')
res.body( app.cache.url[ urlpath ] )
next()
end
else else
next() next()
end end

View file

@ -118,7 +118,7 @@ sb = {
res._header[k] = v res._header[k] = v
sb.pub('res.header',{key=k,value=v}) sb.pub('res.header',{key=k,value=v})
end end
res.header("content-type","text/html") --res.header("content-type","text/html")
-- run middleware -- run middleware
next = function() next = function()
k = k+1 k = k+1
@ -150,7 +150,7 @@ sb = {
response = function() response = function()
return function(req,res,next) return function(req,res,next)
if res._body ~= nil and res._status ~= nil and res._header['content-type'] ~= nil then if res._status ~= nil then
SetStatus(res._status) SetStatus(res._status)
for k,v in pairs(res._header) do for k,v in pairs(res._header) do
if k == "content-type" then v = v .. "; charset=" .. sb.charset end if k == "content-type" then v = v .. "; charset=" .. sb.charset end

View file

@ -27,88 +27,33 @@ return {
or name == "result" -- repository or name == "result" -- repository
end, end,
scan = function(scandir,parent,seen) scan = function(scandir)
local seen = seen or {}
local me = app.plugin.diskscan local me = app.plugin.diskscan
local seen = {symlinks={}}
-- ensure rootdir as experience -- ensure rootdir as experience
app.cache.items[ scandir ] = { app.cache.items[ scandir ] = {
path = scandir, path = scandir,
url = util.stripPath(scandir), url = "/" .. util.stripPath(scandir),
name = scandir:gsub(".*/",""), name = scandir:gsub(".*/",""),
title = scandir:gsub(".*/",""), title = scandir:gsub(".*/",""),
file = { ino = scandir }, file = { ino = scandir },
id = scandir id = scandir
} }
parent = parent or app.cache.items[scandir] me.scanDir(scandir, nil, seen )
-- scan dirs -- deal with (circular) symlinks
local scanFile = function(symlinksOnly) foreach( seen.symlinks, function(ino,experience)
return function(path,stat,state,name,kind,ino,off,attr,fullpath) if not me.shouldHideFile(experience.name) then
if seen[ino] then return end local refItem = seen[experience.id_target]
if refItem then
local experience = { print(" ".. experience.path )
path = fullpath, print("✅ └─ plugin/diskscan: detected symlink..creating portal")
url = util.stripPath(fullpath), experience.file = refItem.file
name = name, experience.portal = refItem
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
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.pub("scan_experience", nil, experience)
app.cache.items[ino] = experience app.cache.items[experience.id] = 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
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 -- 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)
@ -116,6 +61,60 @@ return {
v.file.attr = nil v.file.attr = nil
v.file.state = nil v.file.state = nil
end) end)
end,
scanDir = function(scandir,parent,seen)
local me = app.plugin.diskscan
parent = parent or app.cache.items[scandir]
-- scan dirs
local scanFile = function(path,stat,state,name,kind,ino,off,attr,fullpath)
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
if seen[ino] then return end -- dont process dirs/files twice
if (kind == unix.DT_LNK) then
local stat, err = unix.stat(fullpath)
experience.id_target = stat:ino()
seen.symlinks[ ino ] = experience
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 .. " " ..ino)
experience.dir = true
app.pub("scan_experience", nil, experience)
app.cache.items[ino] = experience
me.scanDir(fullpath,experience,seen,symlinks)
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
local dref, derr;
dref, derr = dir.foreach( scandir, scanFile )
if not derr then dref:close() end
end end
} }