-
Notifications
You must be signed in to change notification settings - Fork 0
/
utilExt.lua
223 lines (176 loc) · 7.04 KB
/
utilExt.lua
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
-- Miscellaneous utility extensions. All functions are put into the util object.
print("Loading nornsLib/utilExt.lua")
-- For logging
local log = require "nornsLib/loggingExt"
-- Returns size of table. Intended to be used for arrays that are sparse, where
-- every index isn't defined. For such tables #tbl only returns number of elements
-- until a nil is found, which of course won't return the true number of elements
-- if it is only sparsely populated.
function util.get_table_size(tbl)
local count = 0
for _, _ in pairs(tbl) do
count = count + 1
end
return count
end
-- Like os.execute() but returns the result string from the command. And different
-- from util.os_capture() by having a more clear name, and by only filtering out
-- last character if a newline. This means it works well for both shell commands
-- like 'date' and also accessing APIs that can provide binary data, such as using
-- curl to get an binary file.
function util.execute_command(command)
-- Execute command and get result
local handle = io.popen(command)
local result = handle:read("*a")
handle:close()
-- trim off trailing \n if there is one
if string.sub(result, -1, -1) == "\n" then
result = string.sub(result, 1, -2)
end
return result
end
-- Sleeps specified fraction number of seconds. Implemented by doing a system call.
-- Note that this will lock out the UI for the specified amount of time, so should
-- be used judiciously.
function util.sleep(seconds)
os.execute("sleep "..seconds)
end
-- Retuns epoch time string with with nanosecond precision, by doing a system
-- call. Note that because the number of characters one cannot just convert this
-- to a number via tonumber() because would then lose resolution. And yes, it
-- is doubtful that nono second resolution will be useful since doing a system
-- call, which takes a while. Therefore util.time() will usually be sufficient.
function util.epochtime_str()
return util.execute_command("date +%s.%N")
end
-- For getting just the filename from full directory path. Returns what is after
-- the last slash of the full filename. If full filename doesn't have any slashes
-- then full_filename is returned.
function util.get_filename(full_filename)
local last_slash = (full_filename:reverse()):find("/")
if last_slash == nil then
return full_filename
else
return full_filename:sub(-last_slash+1)
end
end
-- For finding the directory of a file. Useful for creating file in a directory that
-- doesn't already exist
function util.get_dir(full_filename)
local last_slash = (full_filename:reverse()):find("/")
return (full_filename:sub(1, -last_slash))
end
-- If dir doesn't already exist, creates directory for a file that is about
-- to be written. Different from util.make_dir() in that make_dir_for_file()
-- can take in file name and determine the directory from it.
function util.make_dir_for_file(full_filename)
-- Determine directory that needs to exist
local dir = util.get_dir(full_filename)
-- If directory already exists then don't need to create it
if util.file_exists(dir) then return end
-- Directory didn't exist so create it
os.execute("mkdir "..dir)
end
-- From https://gist.github.com/liukun/f9ce7d6d14fa45fe9b924a3eed5c3d99
local function char_to_hex(c)
return string.format("%%%02X", string.byte(c))
end
local function hex_to_char(x)
return string.char(tonumber(x, 16))
end
-- For encoding a url that has special characters.
function util.urlencode(url)
if url == nil then
return
end
url = url:gsub("\n", "\r\n")
url = url:gsub("([^%w ])", char_to_hex)
url = url:gsub(" ", "+")
return url
end
-- For decoding a url with special characters
function util.urldecode(url)
if url == nil then
return
end
url = url:gsub("+", " ")
url = url:gsub("%%(%x%x)", hex_to_char)
return url
end
-------------------- For waiting for file to be loaded -----------------------
-- Modifying standard _norns.metro() so that it also passes in the metro timer
-- to the callback function. This way the callback function can do things like
-- release the timer.
_norns.metro = function(idx, stage)
local m = metro.metros[idx] -- Lower case metro because being accessed outside of metro.lua
if m then
if m.event then
m.event(stage, m)
end
if m.count > -1 then
if (stage > m.count) then
m.is_running = false
end
end
end
end
-- Called every clock tick when waiting for a file to be ready
local function _wait_for_file_callback(stage, mtro)
local filename = mtro._file
-- Can get extra ticks after already called the callback. If so, just
-- ignore since already done.
if mtro._done then return end
-- See if file exists and has stopped growing
if util.file_exists(filename) then
current_size = util.file_size(filename)
if current_size == mtro._prev_file_size then
-- File exists and is no longer changing size. Done so wrap things up
log.debug("File fully loaded so calling callback. "..
util.get_filename(filename).." size="..current_size)
-- Done waiting so done with timer
mtro:stop()
metro.free(mtro.id)
-- Even though stopped timer it turns out that might still get a few more ticks.
-- Therefore mark the metro as being done with it.
mtro["_done"] = true
-- Call the callback
mtro._file_available_callback(filename)
else
-- File still changing size so not ready yet
log.debug("File still changing size so waiting. ".. util.get_filename(filename) .." size=" .. current_size)
mtro._prev_file_size = current_size
end
else
-- File doesn't even exist yet
mtro._prev_file_size = 0
--log.debug("Waiting for file to exist ".. util.get_filename(filename))
end
-- If exceeded allowable counts then give up. Free the timer
if mtro.count > -1 and stage >= mtro.count then
log.debug("Exceeded count so giving up waiting for file=" .. filename)
metro.free(mtro.id)
end
end
-- Waits until the file specified exists and is not changing in size. At that
-- point the callback is called. Uses an available metro timer, and frees it
-- once done. Recommend a tick_time of 0.1 to 0.2 seconds.
-- max_time specifies how long should wait. Must be at least 1.0 second.
function util.wait(full_filename, file_available_callback, tick_time, max_time)
-- If file already exists and is not empty then call the callback immediately
if util.file_exists(full_filename) and util.file_size(full_filename) > 0 then
log.debug("File already available so calling callback. file="..full_filename)
file_available_callback(full_filename)
return
end
local count = max_time / tick_time
wait_metro = metro.init(_wait_for_file_callback, tick_time, count)
-- Add filename to be waited for to the metro object
wait_metro["_file"] = full_filename
-- Store the callback in the metro
wait_metro["_file_available_callback"] = file_available_callback
-- Init _prev_file_size
wait_metro["_prev_file_size"] = 0
wait_metro["_done"] = false
-- And start that timer!
wait_metro:start()
end