From d7c541742f5e6ec07dbcc8e1346efde9d807437e Mon Sep 17 00:00:00 2001 From: William Wilgus Date: Sat, 4 May 2024 08:41:16 -0400 Subject: Allow first level folders in plugin menu add sorting directories as files move picross files to a hidden folder use directory for lua_scripts, sgt_puzzles make plugin browser able to handle 1st level directories Change-Id: I30852d71dc992c378d5790756e94f06f5a2e9bef --- apps/plugins/lua/include_lua/fileviewers.lua | 468 +++++++++++++++++++++++++++ 1 file changed, 468 insertions(+) create mode 100755 apps/plugins/lua/include_lua/fileviewers.lua (limited to 'apps/plugins/lua/include_lua/fileviewers.lua') diff --git a/apps/plugins/lua/include_lua/fileviewers.lua b/apps/plugins/lua/include_lua/fileviewers.lua new file mode 100755 index 0000000000..c686f3eeda --- /dev/null +++ b/apps/plugins/lua/include_lua/fileviewers.lua @@ -0,0 +1,468 @@ +--[[ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2017 William Wilgus + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +]] +if ... == nil then rb.splash(rb.HZ * 3, "use 'require'") end +require("printtable") +local _clr = require("color") +local _lcd = require("lcd") +local _print = require("print") +local _timer = require("timer") + +require("actions") +local CANCEL_BUTTON = rb.actions.PLA_CANCEL +-------------------------------------------------------------------------------- +-- builds an index of byte position of every line at each bufsz increment +-- in filename; bufsz == 1 would be every line; saves to filename.ext.idx_ext +-- lnbyte should be nil for text files and number of bytes per line for binary +local function build_file_index(filename, idx_ext, bufsz, lnbyte) + + if not filename then return end + local file = io.open('/' .. filename, "r") --read + if not file then _lcd:splashf(100, "Can't open %s", filename) return end + local fsz = file:seek("end") + local fsz_kb = fsz / 1024 + local count + local ltable = {0} --first index is the beginning of the file + local timer = _timer() + local fread + _lcd:splashf(100, "Indexing file %d Kb", (fsz / 1024)) + + if lnbyte then + fread = function(f) return f:read(lnbyte) end + else + lnbyte = -1 + fread = function(f) return f:read("*l") end + end + + file:seek("set", 0) + for i = 1, fsz do + if i % bufsz == 0 then + local loc = file:seek() + ltable[#ltable + 1] = loc + _lcd:splashf(1, "Parsing %d of %d Kb", loc / 1024, fsz_kb) + end + if rb.get_plugin_action(0) == CANCEL_BUTTON then + return + end + if not fread(file) then + count = i + break + end + end + + local fileidx = io.open('/' .. filename .. idx_ext, "w+") -- write/erase + if fileidx then + fileidx:write(fsz .. "\n") + fileidx:write(count .. "\n") + fileidx:write(bufsz .. "\n") + fileidx:write(lnbyte .. "\n") + fileidx:write(table.concat(ltable, "\n")) + fileidx:close() + _lcd:splashf(100, "Finished in %d seconds", timer.stop() / rb.HZ) + collectgarbage("collect") + else + error("unable to save index file") + end +end -- build_file_index +-------------------------------------------------------------------------------- + +--- returns size of original file, total lines buffersize, and table filled +-- with line offsets in index file -> filename +local function load_index_file(filename) + local filesz, count, bufsz, lnbyte + local ltable + local fileidx = io.open('/' .. filename, "r") --read + if fileidx then + local idx = -3 + ltable = {} + fileidx:seek("set", 0) + for line in fileidx:lines() do + if idx == -3 then + filesz = tonumber(line) + elseif idx == -2 then + count = tonumber(line) + elseif idx == -1 then + bufsz = tonumber(line) + elseif idx == 0 then + lnbyte = tonumber(line) + else + ltable[idx] = tonumber(line) + end + idx = idx + 1 + end + fileidx:close() + end + return lnbyte, filesz, count, bufsz, ltable +end -- load_index_file +-------------------------------------------------------------------------------- + +-- creates a fixed index with fixed line lengths, perfect for viewing hex files +-- not so great for reading text files but works as a fallback +local function load_fixed_index(bytesperline, filesz, bufsz) + local lnbyte = bytesperline + local count = (filesz + lnbyte - 1) / lnbyte + 1 + local idx_t = {} -- build index + for i = 0, filesz, bufsz do + idx_t[#idx_t + 1] = lnbyte * i + end + return lnbyte, filesz, count, bufsz, idx_t +end -- load_fixed_index +-------------------------------------------------------------------------------- + +-- uses print_table to display a whole file +function print_file(filename, maxlinelen, settings) + + if not filename then return end + local file = io.open('/' .. filename or "", "r") --read + if not file then _lcd:splashf(100, "Can't open %s", filename) return end + maxlinelen = 33 + local hstr = filename + local ftable = {} + table.insert(ftable, 1, hstr) + + local tline = #ftable + 1 + local remln = maxlinelen + local posln = 1 + + for line in file:lines() do + if line then + if maxlinelen then + if line == "" then + ftable[tline] = ftable[tline] or "" + tline = tline + 1 + remln = maxlinelen + else + line = line:match("%w.+") or "" + end + local linelen = line:len() + while linelen > 0 do + + local fsp = line:find("%s", posln + remln - 5) or 0x0 + fsp = fsp - (posln + remln) + if fsp >= 0 then + local fspr = fsp + fsp = line:find("%s", posln + remln) or linelen + fsp = fsp - (posln + remln) + if math.abs(fspr) < fsp then fsp = fspr end + end + if fsp > 5 or fsp < -5 then fsp = 0 end + + local str = line:sub(posln, posln + remln + fsp) + local slen = str:len() + ftable[tline] = ftable[tline] or "" + ftable[tline] = ftable[tline] .. str + linelen = linelen - slen + if linelen > 0 then + tline = tline + 1 + posln = posln + slen + remln = maxlinelen + --loop continues + else + ftable[tline] = ftable[tline] .. " " + remln = maxlinelen - slen + posln = 1 + --loop ends + end + + end + else + ftable[#ftable + 1] = line + end + + + end + end + + file:close() + + _lcd:clear() + _print.clear() + + if not settings then + settings = {} + settings.justify = "center" + settings.wrap = true + settings.msel = true + end + settings.hasheader = true + settings.co_routine = nil + settings.ovfl = "manual" + + local sel = + print_table(ftable, #ftable, settings) + + _lcd:splashf(rb.HZ * 2, "%d items {%s}", #sel, table.concat(sel, ", ")) + ftable = nil +end -- print_file +-------------------------------------------------------------------------------- + +-- uses print_table to display a portion of a file +function print_file_increment(filename, settings) + + if not filename then return end + local file = io.open('/' .. filename, "r") --read + if not file then _lcd:splashf(100, "Can't open %s", filename) return end + local fsz = file:seek("end") + local bsz = 1023 + --if small file do it the easier way and load whole file to table + if fsz < 60 * 1024 then + file:close() + print_file(filename, settings) + return + end + + local ext = ".idx" + local lnbyte, filesz, count, bufsz, idx_t = load_index_file(filename .. ext) + + if not idx_t or fsz ~= filesz then -- build file index + build_file_index(filename, ext, bsz) + lnbyte, filesz, count, bufsz, idx_t = load_index_file(filename .. ext) + end + + -- if invalid or user canceled creation fallback to a fixed index + if not idx_t or fsz ~= filesz or count <= 0 then + _lcd:splashf(rb.HZ * 5, "Unable to read file index %s", filename .. ext) + lnbyte, filesz, count, bufsz, idx_t = load_fixed_index(32, fsz, bsz) + end + + if not idx_t or fsz ~= filesz or count <= 0 then + _lcd:splashf(rb.HZ * 5, "Unable to load file %s", filename) + return + end + + local hstr = filename + local file_t = setmetatable({},{__mode = "kv"}) --weak keys and values + -- this allows them to be garbage collected as space is needed + -- rebuilds when needed + local ovf = 0 + local lpos = 1 + local timer = _timer() + file:seek("set", 0) + + function print_co() + while true do + collectgarbage("step") + file_t[1] = hstr --position 1 is ALWAYS header/title + + for i = 1, bufsz + ovf do + file_t[lpos + i] = file:read ("*l") + end + ovf = 0 + lpos = lpos + bufsz + + local bpos = coroutine.yield() + + if bpos <= lpos then -- roll over or scroll up + bpos = (bpos - bufsz) + bpos % bufsz + timer:check(true) + end + + lpos = bpos - bpos % bufsz + + if lpos < 1 then + lpos = 1 + elseif lpos > count - bufsz then -- partial fill + ovf = count - bufsz - lpos + end + --get position in file of the nearest indexed line + file:seek("set", idx_t[bpos / bufsz + 1]) + + -- on really large files if it has been more than 10 minutes + -- since the user scrolled up the screen wipe out the prior + -- items to free memory + if lpos % 5000 == 0 and timer:check() > rb.HZ * 600 then + for i = 1, lpos - 100 do + file_t[i] = nil + end + end + + end + end + + co = coroutine.create(print_co) + _lcd:clear() + _print.clear() + + if not settings then + settings = {} + settings.justify = "center" + settings.wrap = true + end + settings.hasheader = true + settings.co_routine = co + settings.msel = false + settings.ovfl = "manual" + + table.insert(file_t, 1, hstr) --position 1 is header/title + local sel = + print_table(file_t, count, settings) + file:close() + idx_t = nil + file_t = nil + return sel +end --print_file_increment +-------------------------------------------------------------------------------- +function print_file_hex(filename, bytesperline, settings) + + if not filename then return end + local file = io.open('/' .. filename, "r") --read + if not file then _lcd:splashf(100, "Can't open %s", filename) return end + local hstr = filename + local bpl = bytesperline + local fsz = file:seek("end") +--[[ + local filesz = file:seek("end") + local bufsz = 1023 + local lnbyte = bytesperline + local count = (filesz + lnbyte - 1) / lnbyte + 1 + + local idx_t = {} -- build index + for i = 0, filesz, bufsz do + idx_t[#idx_t + 1] = lnbyte * i + end]] + + local lnbyte, filesz, count, bufsz, idx_t = load_fixed_index(bpl, fsz, 1023) + + local file_t = setmetatable({},{__mode = "kv"}) --weak keys and values + -- this allows them to be garbage collected as space is needed + -- rebuilds when needed + local ovf = 0 + local lpos = 1 + local timer = _timer() + file:seek("set", 0) + + function hex_co() + while true do + collectgarbage("step") + file_t[1] = hstr --position 1 is ALWAYS header/title + + for i = 1, bufsz + ovf do + local pos = file:seek() + local s = file:read (lnbyte) + if not s then -- EOF + file_t[lpos + i] = "" + break; + end + local s_len = s:len() + + if s_len > 0 then + local fmt = "0x%04X: " .. string.rep("%02X ", s_len) + local schrs = " " .. s:gsub("(%c)", " . ") + file_t[lpos + i] = string.format(fmt, pos, s:byte(1, s_len)) .. + schrs + else + file_t[lpos + i] = string.format("0x%04X: ", pos) + end + end + ovf = 0 + lpos = lpos + bufsz + + local bpos = coroutine.yield() + + if bpos < lpos then -- roll over or scroll up + bpos = (bpos - bufsz) + bpos % bufsz + timer:check(true) + end + + lpos = bpos - bpos % bufsz + + if lpos < 1 then + lpos = 1 + elseif lpos > count - bufsz then -- partial fill + ovf = count - bufsz - lpos + end + --get position in file of the nearest indexed line + file:seek("set", idx_t[bpos / bufsz + 1]) + + -- on really large files if it has been more than 10 minutes + -- since the user scrolled up the screen wipe out the prior + -- items to free memory + if lpos % 10000 == 0 and timer:check() > rb.HZ * 600 then + for i = 1, lpos - 100 do + file_t[i] = nil + end + end + + end + end + + co = coroutine.create(hex_co) + + local function repl(char) + local ret = "" + if char:sub(1,2) == "0x" then + return string.format("%dd:", tonumber(char:sub(3, -2), 16)) + else + return string.format("%03d ", tonumber(char, 16)) + end + end + + + _lcd:clear() + _print.clear() + + local sel, start, vcur = 1 + table.insert(file_t, 1, hstr) --position 1 is header/title + + if not settings then + settings = {} + settings.justify = "left" + settings.wrap = true + settings.msel = false + settings.hfgc = _clr.set( 0, 000, 000, 000) + settings.hbgc = _clr.set(-1, 255, 255, 255) + settings.ifgc = _clr.set(-1, 255, 255, 255) + settings.ibgc = _clr.set( 0, 000, 000, 000) + settings.iselc = _clr.set( 1, 000, 200, 100) + end + + settings.hasheader = true + settings.co_routine = co + settings.start = start + settings.curpos = vcur + settings.ovfl = "manual" + + while sel > 0 do + settings.start = start + settings.curpos = vcur + + sel, start, vcur = print_table(file_t, count, settings) + + if sel > 1 and file_t[sel] then -- flips between hex and decimal + local s = file_t[sel] + if s:sub(-1) == "\b" then + file_t[sel] = nil + ovf = -(bufsz - 1) + coroutine.resume(co, sel) --rebuild this item + else + s = s:gsub("(0x%x+:)", repl) .. "\b" + file_t[sel] = s:gsub("(%x%x%s)", repl) .. "\b" + end + end + end + + file:close() + idx_t = nil + file_t = nil + return sel +end -- print_file_hex +-------------------------------------------------------------------------------- -- cgit v1.2.3