lzwfs
This commit is contained in:
6
lzwfs/.package
Normal file
6
lzwfs/.package
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
title = 'Disk compression',
|
||||
repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/lzwfs',
|
||||
description = [[ Disk compression ]],
|
||||
license = 'MIT',
|
||||
}
|
||||
27
lzwfs/autorun/install.lua
Normal file
27
lzwfs/autorun/install.lua
Normal file
@@ -0,0 +1,27 @@
|
||||
local Config = require('opus.config')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local config = Config.load('lzwfs', {
|
||||
enabled = false,
|
||||
installed = false,
|
||||
filters = {
|
||||
'/packages',
|
||||
'/sys',
|
||||
'/usr/config',
|
||||
}
|
||||
})
|
||||
|
||||
if not config.installed then
|
||||
-- insert lzwfs into boot startup
|
||||
local boot = Util.readTable('.startup.boot')
|
||||
table.insert(boot.preload, 1, '/packages/lzwfs/startup.lua')
|
||||
Util.writeTable('.startup.boot', boot)
|
||||
|
||||
-- update config
|
||||
config.installed = true
|
||||
Config.update('lzwfs', config)
|
||||
|
||||
print('Installing lzwfs - rebooting in 3 seconds')
|
||||
os.sleep(3)
|
||||
os.reboot()
|
||||
end
|
||||
1
lzwfs/etc/fstab
Normal file
1
lzwfs/etc/fstab
Normal file
@@ -0,0 +1 @@
|
||||
sys/apps/system/lzwfs.lua linkfs packages/lzwfs/system/lzwfs.lua
|
||||
248
lzwfs/lzwfs.lua
Normal file
248
lzwfs/lzwfs.lua
Normal file
@@ -0,0 +1,248 @@
|
||||
-- see: https://github.com/Rochet2/lualzw
|
||||
-- MIT License - Copyright (c) 2016 Rochet2
|
||||
|
||||
-- Transparent file system compression for non-binary files using lzw
|
||||
|
||||
-- Files that are compressed will have the first bytes in file set to 'LZWC'.
|
||||
-- If a file does not benefit from compression, the contents will not be altered.
|
||||
|
||||
local char = string.char
|
||||
local type = type
|
||||
local sub = string.sub
|
||||
local tconcat = table.concat
|
||||
local tinsert = table.insert
|
||||
|
||||
local fs = _G.fs
|
||||
|
||||
local SIGC = 'LZWC'
|
||||
|
||||
local basedictcompress = {}
|
||||
local basedictdecompress = {}
|
||||
for i = 0, 255 do
|
||||
local ic, iic = char(i), char(i, 0)
|
||||
basedictcompress[ic] = iic
|
||||
basedictdecompress[iic] = ic
|
||||
end
|
||||
|
||||
local native = { open = fs.open }
|
||||
local enabled = false
|
||||
local filters = { }
|
||||
|
||||
local function dictAddA(str, dict, a, b)
|
||||
if a >= 256 then
|
||||
a, b = 0, b+1
|
||||
if b >= 256 then
|
||||
dict = {}
|
||||
b = 1
|
||||
end
|
||||
end
|
||||
dict[str] = char(a,b)
|
||||
a = a+1
|
||||
return dict, a, b
|
||||
end
|
||||
|
||||
local function compress(input)
|
||||
if type(input) ~= "string" then
|
||||
error ("string expected, got "..type(input))
|
||||
end
|
||||
local len = #input
|
||||
if len <= 1 then
|
||||
return input
|
||||
end
|
||||
|
||||
local dict = {}
|
||||
local a, b = 0, 1
|
||||
|
||||
local result = { SIGC }
|
||||
local resultlen = 1
|
||||
local n = 2
|
||||
local word = ""
|
||||
for i = 1, len do
|
||||
local c = sub(input, i, i)
|
||||
local wc = word..c
|
||||
if not (basedictcompress[wc] or dict[wc]) then
|
||||
local write = basedictcompress[word] or dict[word]
|
||||
if not write then
|
||||
error "algorithm error, could not fetch word"
|
||||
end
|
||||
result[n] = write
|
||||
resultlen = resultlen + #write
|
||||
n = n+1
|
||||
if len <= resultlen then
|
||||
return input
|
||||
end
|
||||
dict, a, b = dictAddA(wc, dict, a, b)
|
||||
word = c
|
||||
else
|
||||
word = wc
|
||||
end
|
||||
end
|
||||
result[n] = basedictcompress[word] or dict[word]
|
||||
resultlen = resultlen+#result[n]
|
||||
if len <= resultlen then
|
||||
return input
|
||||
end
|
||||
return tconcat(result)
|
||||
end
|
||||
|
||||
local function dictAddB(str, dict, a, b)
|
||||
if a >= 256 then
|
||||
a, b = 0, b+1
|
||||
if b >= 256 then
|
||||
dict = {}
|
||||
b = 1
|
||||
end
|
||||
end
|
||||
dict[char(a,b)] = str
|
||||
a = a+1
|
||||
return dict, a, b
|
||||
end
|
||||
|
||||
local function decompress(input)
|
||||
if type(input) ~= "string" then
|
||||
error( "string expected, got "..type(input))
|
||||
end
|
||||
|
||||
if #input <= 1 then
|
||||
return input
|
||||
end
|
||||
|
||||
local control = sub(input, 1, 4)
|
||||
if control ~= SIGC then
|
||||
return input
|
||||
end
|
||||
input = sub(input, 5)
|
||||
local len = #input
|
||||
|
||||
if len < 2 then
|
||||
error("invalid input - not a compressed string")
|
||||
end
|
||||
|
||||
local dict = {}
|
||||
local a, b = 0, 1
|
||||
|
||||
local result = {}
|
||||
local n = 1
|
||||
local last = sub(input, 1, 2)
|
||||
result[n] = basedictdecompress[last] or dict[last]
|
||||
n = n+1
|
||||
for i = 3, len, 2 do
|
||||
local code = sub(input, i, i+1)
|
||||
local lastStr = basedictdecompress[last] or dict[last]
|
||||
if not lastStr then
|
||||
error( "could not find last from dict. Invalid input?")
|
||||
end
|
||||
local toAdd = basedictdecompress[code] or dict[code]
|
||||
if toAdd then
|
||||
result[n] = toAdd
|
||||
n = n+1
|
||||
dict, a, b = dictAddB(lastStr..sub(toAdd, 1, 1), dict, a, b)
|
||||
else
|
||||
local tmp = lastStr..sub(lastStr, 1, 1)
|
||||
result[n] = tmp
|
||||
n = n+1
|
||||
dict, a, b = dictAddB(tmp, dict, a, b)
|
||||
end
|
||||
last = code
|
||||
end
|
||||
return tconcat(result)
|
||||
end
|
||||
|
||||
local function split(str, pattern)
|
||||
pattern = pattern or "(.-)\n"
|
||||
local t = {}
|
||||
local function helper(line) tinsert(t, line) return "" end
|
||||
helper((str:gsub(pattern, helper)))
|
||||
return t
|
||||
end
|
||||
|
||||
local function matchesFilter(fname)
|
||||
for _, filter in pairs(filters) do
|
||||
if fname:match(filter) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function fs.open(fname, flags)
|
||||
if not enabled then
|
||||
return native.open(fname, flags)
|
||||
end
|
||||
|
||||
if flags == 'r' then
|
||||
local f, err = native.open(fname, 'rb')
|
||||
if not f then
|
||||
return f, err
|
||||
end
|
||||
|
||||
local ctr = 0
|
||||
local lines
|
||||
return {
|
||||
readLine = function()
|
||||
if not lines then
|
||||
lines = split(decompress(f.readAll()))
|
||||
end
|
||||
ctr = ctr + 1
|
||||
return lines[ctr]
|
||||
end,
|
||||
readAll = function()
|
||||
return decompress(f.readAll())
|
||||
end,
|
||||
close = function()
|
||||
f.close()
|
||||
end,
|
||||
}
|
||||
elseif flags == 'w' or flags == 'a' then
|
||||
if not matchesFilter(fs.combine(fname, '')) or true then
|
||||
return native.open(fname, flags)
|
||||
end
|
||||
|
||||
local c = { }
|
||||
|
||||
if flags == 'a' then
|
||||
local f = fs.open(fname, 'r')
|
||||
if f then
|
||||
tinsert(c, f.readAll())
|
||||
f.close()
|
||||
end
|
||||
end
|
||||
|
||||
local f, err = native.open(fname, 'wb')
|
||||
if not f then
|
||||
return f, err
|
||||
end
|
||||
|
||||
return {
|
||||
write = function(str)
|
||||
tinsert(c, str)
|
||||
end,
|
||||
writeLine = function(str)
|
||||
tinsert(c, str)
|
||||
tinsert(c, '\n')
|
||||
end,
|
||||
flush = function()
|
||||
-- this isn't gonna work...
|
||||
-- f.write(compress(tconcat(c)))
|
||||
f.flush();
|
||||
end,
|
||||
close = function()
|
||||
f.write(compress(tconcat(c)))
|
||||
f.close()
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
return native.open(fname, flags)
|
||||
end
|
||||
|
||||
function fs.option(category, action, option)
|
||||
if category == 'compression' then
|
||||
if action == 'enabled' then
|
||||
enabled = option
|
||||
elseif action == 'filters' then
|
||||
filters = option
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
print('lzwfs started')
|
||||
18
lzwfs/startup.lua
Normal file
18
lzwfs/startup.lua
Normal file
@@ -0,0 +1,18 @@
|
||||
local fs = _G.fs
|
||||
local textutils = _G.textutils
|
||||
|
||||
local CONFIG = 'usr/config/lzwfs'
|
||||
|
||||
local config = { }
|
||||
|
||||
if fs.exists(CONFIG) then
|
||||
local f = fs.open(CONFIG, 'r')
|
||||
if f then
|
||||
config = textutils.unserialize(f.readAll())
|
||||
f.close()
|
||||
end
|
||||
end
|
||||
|
||||
os.run(_ENV, '/packages/lzwfs/lzwfs.lua')
|
||||
fs.option('compression', 'filters', config.filters)
|
||||
fs.option('compression', 'enable', config.enabled)
|
||||
150
lzwfs/system/lzwfs.lua
Normal file
150
lzwfs/system/lzwfs.lua
Normal file
@@ -0,0 +1,150 @@
|
||||
local Config = require('opus.config')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local fs = _G.fs
|
||||
|
||||
local config = Config.load('lzwfs', {
|
||||
enabled = false,
|
||||
filters = {
|
||||
'/packages',
|
||||
'/sys',
|
||||
'/usr/config',
|
||||
}
|
||||
})
|
||||
|
||||
local tab = UI.Tab {
|
||||
tabTitle = 'Compression',
|
||||
description = 'Disk compression',
|
||||
label1 = UI.Text {
|
||||
x = 2, y = 2,
|
||||
value = 'Enable compression',
|
||||
},
|
||||
checkbox = UI.Checkbox {
|
||||
x = 20, y = 2,
|
||||
value = config.enabled
|
||||
},
|
||||
entry = UI.TextEntry {
|
||||
x = 2, y = 4, ex = -2,
|
||||
limit = 256,
|
||||
shadowText = 'enter new path',
|
||||
accelerators = {
|
||||
enter = 'add_path',
|
||||
},
|
||||
help = 'add a new path',
|
||||
},
|
||||
grid = UI.Grid {
|
||||
x = 2, ex = -2, y = 6, ey = -5,
|
||||
disableHeader = true,
|
||||
columns = { { key = 'value' } },
|
||||
autospace = true,
|
||||
sortColumn = 'index',
|
||||
help = 'double-click to remove',
|
||||
accelerators = {
|
||||
delete = 'remove',
|
||||
},
|
||||
},
|
||||
button = UI.Button {
|
||||
x = -9, ex = -2, y = -3,
|
||||
text = 'Apply',
|
||||
event = 'apply',
|
||||
},
|
||||
statusBar = UI.StatusBar { },
|
||||
}
|
||||
|
||||
function tab:enable()
|
||||
self.grid.values = { }
|
||||
for k,v in ipairs(config.filters or { }) do
|
||||
table.insert(self.grid.values, { index = k, value = v })
|
||||
end
|
||||
self.grid:update()
|
||||
UI.Tab.enable(self)
|
||||
end
|
||||
|
||||
local function rewriteFiles(p)
|
||||
if type(p) == 'table' then
|
||||
for _, path in pairs(p) do
|
||||
rewriteFiles(path)
|
||||
end
|
||||
else
|
||||
local function recurse(path)
|
||||
_G._syslog('rewriting: ' .. path)
|
||||
if fs.isDir(path) then
|
||||
for _, v in pairs(fs.list(path)) do
|
||||
recurse(fs.combine(path, v))
|
||||
end
|
||||
else
|
||||
local c = Util.readFile(path)
|
||||
Util.writeFile(path, c)
|
||||
end
|
||||
end
|
||||
|
||||
recurse(fs.combine(p, ''))
|
||||
end
|
||||
end
|
||||
|
||||
function tab:eventHandler(event)
|
||||
if event.type == 'add_path' then
|
||||
table.insert(self.grid.values, {
|
||||
index = #self.grid.values + 1,
|
||||
value = self.entry.value,
|
||||
})
|
||||
self.entry:reset()
|
||||
self.entry:draw()
|
||||
self.grid:update()
|
||||
self.grid:draw()
|
||||
return true
|
||||
|
||||
elseif event.type == 'grid_select' or event.type == 'remove' then
|
||||
local selected = self.grid:getSelected()
|
||||
if selected then
|
||||
table.remove(self.grid.values, selected.index)
|
||||
self.grid:update()
|
||||
self.grid:draw()
|
||||
end
|
||||
|
||||
elseif event.type == 'focus_change' then
|
||||
self.statusBar:setStatus(event.focused.help)
|
||||
|
||||
elseif event.type == 'apply' then
|
||||
local filters = { }
|
||||
|
||||
for _, v in pairs(self.grid.values) do
|
||||
table.insert(filters, v.value)
|
||||
end
|
||||
|
||||
if self.checkbox.value ~= config.enabled then
|
||||
if not self.checkbox.value then
|
||||
fs.option('compression', 'filters', { })
|
||||
self:rewriteFiles(config.filters)
|
||||
fs.option('compression', 'enabled', false)
|
||||
else
|
||||
fs.option('compression', 'enabled', true)
|
||||
fs.option('compression', 'filters', filters)
|
||||
rewriteFiles(filters)
|
||||
end
|
||||
elseif self.checkbox.value then
|
||||
fs.option('compression', 'filters', filters)
|
||||
for _,v in pairs(filters) do
|
||||
if not Util.find(config.filters, v) then
|
||||
rewriteFiles(v) -- uncompress paths not in current filter
|
||||
end
|
||||
end
|
||||
|
||||
for _,v in pairs(config.filters) do
|
||||
if not Util.find(filters, v) then
|
||||
rewriteFiles(v) -- compress new filters
|
||||
end
|
||||
end
|
||||
end
|
||||
config.filters = filters
|
||||
config.enabled = self.checkbox.value
|
||||
Config.update('lzwfs', config)
|
||||
|
||||
self:emit({ type = 'success_message', message = 'Settings updated' })
|
||||
end
|
||||
|
||||
return UI.Tab.eventHandler(self, event)
|
||||
end
|
||||
|
||||
return tab
|
||||
Reference in New Issue
Block a user