about summary refs log tree commit diff stats
path: root/src/file.lua
blob: 81fe68d0457bddf4ce235eb30288397c8dd2719f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
-- 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