teliva/src/file.lua

88 lines
2.6 KiB
Lua

-- primitive for reading files from a file system (or, later, network)
-- returns a channel or nil on error
-- read lines from the channel using :recv()
-- recv() on the channel will indicate end of file.
function start_reading(fs, filename)
local result = task.Channel:new()
local infile = io.open(filename)
if infile == nil then return nil end
task.spawn(reading_task, infile, result)
return result
end
function reading_task(infile, chanout)
for line in infile:lines() do
chanout:send(line)
end
chanout:send(nil) -- eof
end
-- primitive for writing files to a file system (or, later, network)
-- returns a channel or nil on error
-- write to the channel using :send()
-- indicate you're done writing by calling :close()
-- file will not be externally visible until :close()
function start_writing(fs, filename)
if filename == nil then
error('start_writing requires two arguments: a file-system (nil for real disk) and a filename')
end
local result = task.Channel:new()
local initial_filename = temporary_filename_in_same_volume(filename)
local outfile = io.open(initial_filename, 'w')
if outfile == nil then return nil end
result.close = function()
result:send(nil) -- end of file
outfile:close()
os.rename(initial_filename, filename)
end
task.spawn(writing_task, outfile, result)
return result
end
function temporary_filename_in_same_volume(filename)
-- opening in same directory will hopefully keep it on the same volume,
-- so that a future rename works
local i = 1
while true do
temporary_filename = 'teliva_tmp_'..filename..'_'..i
if io.open(temporary_filename) == nil then
-- file doesn't exist yet; create a placeholder and return it
local handle = io.open(temporary_filename, 'w')
if handle == nil then
error("this is unexpected; I can't create temporary files..")
end
handle:close()
return temporary_filename
end
i = i+1
end
end
function writing_task(outfile, chanin)
while true do
local line = chanin:recv()
if line == nil then break end -- end of file
outfile:write(line)
end
end
-- start_reading reads line by line by default
-- this helper permits character-by-character reading
function character_by_character(chanin, buffer_size)
local chanout = task.Channel:new(buffer_size or 50)
task.spawn(character_splitting_task, chanin, chanout)
return chanout
end
function character_splitting_task(chanin, chanout)
while true do
local line = chanin:recv()
if line == nil then break end
local linesz = line:len()
for i=1,linesz do
chanout:send(line:sub(i, i))
end
end
chanout:send(nil) -- end of file
end