update.lua

289 lines · 8.7 KB

Open raw

Bulk updater for CC-Tweaked scripts

Usage: run: update

Copy & run

wget https://perlytiara.github.io/turtles.tips/raw/programs/perlytiara/BigBaemingGamers/update.lua
1-- Bulk updater for CC-Tweaked scripts
2-- Overwrites local scripts with the latest from GitHub
3-- Usage: run: update
4
5local BASE = 'https://raw.githubusercontent.com/perlytiara/CC-Tweaked-TurtsAndComputers/refs/heads/main/'
6
7local manifest = {
8 ["programs/perlytiara/BigBaemingGamers"] = {
9 -- client
10 "client/client-bomb.lua",
11 "client/client-dig.lua",
12 "client/client-quarry.lua",
13 "client/client.lua",
14 -- farm
15 "farm/cactus-farm.lua",
16 "farm/harvest_0.lua",
17 "farm/harvest_1.lua",
18 -- phone
19 "phone/phone-app-mine.lua",
20 "phone/phone-bombing.lua",
21 -- server
22 "server/server-phone.lua",
23 -- tasks
24 "tasks/quarry-miner.lua",
25 "tasks/simple-quarry.lua",
26 -- tnt
27 "tnt/tnt-deployer.lua",
28 "tnt/tnt-igniter.lua",
29 -- update helpers
30 "update/update_bamboo.lua",
31 "update/update_dispense.lua",
32 "update/update_lift.lua",
33 "update/update_pos.lua",
34 -- utils
35 "utils/block-placer.lua",
36 "utils/detectids.lua",
37 "utils/item-counter.lua",
38 "utils/lava-refueler.lua",
39 "utils/ore-keeper.lua",
40 "utils/ractor.lua",
41 "utils/refuel-test.lua",
42 "utils/upward-quarry-test.lua",
43 -- loader and updater aliases
44 "@load.lua",
45 "@update.lua",
46 "load.lua",
47 "update.lua",
48 },
49 ["programs/perlytiara/BigBaemingGamers/@gps"] = {
50 -- @gps scripts
51 "@gps/gps.lua",
52 "@gps/gps-host.lua",
53 },
54}
55
56local function ensureDir(path)
57 if not fs.exists(path) then
58 fs.makeDir(path)
59 end
60end
61
62local function ensureParentDir(localPath)
63 local parent = string.match(localPath, "(.+)/[^/]+$")
64 if parent then ensureDir(parent) end
65end
66
67local function fetch(remotePath)
68 local url = BASE .. remotePath .. '?breaker=' .. tostring(math.random(0, 999999))
69 local res, err = http.get(url)
70 if not res then
71 print('ERROR: http.get failed for ' .. remotePath .. ' - ' .. tostring(err))
72 return false, nil
73 end
74 local code = res.readAll()
75 res.close()
76 return true, code
77end
78
79local function writeFile(path, contents)
80 local handle = fs.open(path, 'w')
81 handle.write(contents)
82 handle.close()
83end
84
85local function readFile(path)
86 if not fs.exists(path) then return '' end
87 local h = fs.open(path, 'r')
88 local c = h.readAll()
89 h.close()
90 return c or ''
91end
92
93local function bytesDiff(a, b)
94 return string.len(b) - string.len(a)
95end
96
97local function progressBar(current, total, label)
98 local width = 28
99 local filled = math.floor((current / total) * width)
100 local bar = string.rep('#', filled) .. string.rep('-', width - filled)
101 print(string.format('[%s] %d/%d %s', bar, current, total, label or ''))
102end
103
104local function removeStale(localDir, keepSet)
105 if not fs.exists(localDir) then return end
106 local function walk(dir, relPrefix)
107 local items = fs.list(dir)
108 for i = 1, #items, 1 do
109 local name = items[i]
110 local full = dir .. '/' .. name
111 local relPath = (relPrefix ~= '' and (relPrefix .. '/' .. name)) or name
112 if fs.isDir(full) then
113 walk(full, relPath)
114 -- remove empty dir
115 if #fs.list(full) == 0 then fs.delete(full) end
116 else
117 -- Do not delete the loader/updater themselves at root
118 if relPath ~= 'load.lua' and relPath ~= 'update.lua' and relPath ~= '@load.lua' and relPath ~= '@update.lua' and not keepSet[relPath] then
119 fs.delete(full)
120 print('Removed stale: ' .. full)
121 end
122 end
123 end
124 end
125 walk(localDir, '')
126end
127
128local function updateDir(dir, files)
129 local updated, same = 0, 0
130 local localDir = '/' .. dir
131 ensureDir(localDir)
132
133 local keep = {}
134 for i = 1, #files, 1 do keep[files[i]] = true end
135
136 local total = #files
137 for i = 1, #files, 1 do
138 local fname = files[i]
139 local remotePath = dir .. '/' .. fname
140 local localPath = localDir .. '/' .. fname
141 local ok, code = fetch(remotePath)
142 if ok and code then
143 local old = readFile(localPath)
144 if old == code then
145 same = same + 1
146 else
147 ensureParentDir(localPath)
148 writeFile(localPath, code)
149 local diff = bytesDiff(old, code)
150 local change = (diff >= 0 and (tostring(math.abs(diff)) .. ' bytes added')) or (tostring(math.abs(diff)) .. ' bytes removed')
151 print('Updated: ' .. localPath .. ' (' .. change .. ')')
152 updated = updated + 1
153 end
154 end
155 progressBar(i, total, fname)
156 sleep(0)
157 end
158
159 removeStale(localDir, keep)
160 print(string.format('Done. Updated %d, unchanged %d.', updated, same))
161end
162
163local function promptSelect(options)
164 for i = 1, #options, 1 do
165 print(string.format('%d) %s', i, options[i]))
166 end
167 io.write('Select number: ')
168 local choice = read()
169 local idx = tonumber(choice)
170 if idx and idx >= 1 and idx <= #options then
171 return idx
172 end
173 return nil
174end
175
176local function showMenu()
177 print('== Update Menu ==')
178 print('1) Auto update all (clean stale)')
179 print('2) Update a single file')
180 print('3) Remove a local file')
181 print('4) Exit')
182 io.write('Choose: ')
183 local ch = read()
184 return tonumber(ch)
185end
186
187local function updateAll()
188 for dir, files in pairs(manifest) do
189 updateDir(dir, files)
190 end
191end
192
193local function updateSingle()
194 local dirs = {}
195 for d, _ in pairs(manifest) do table.insert(dirs, d) end
196 print('Select directory:')
197 local dirIdx = promptSelect(dirs)
198 if not dirIdx then print('Invalid selection') return end
199 local dir = dirs[dirIdx]
200 local files = manifest[dir]
201 print('Select file to update:')
202 local idx = promptSelect(files)
203 if not idx then print('Invalid selection') return end
204 updateDir(dir, { files[idx] })
205end
206
207local function removeOne()
208 local dirs = {}
209 for d, _ in pairs(manifest) do table.insert(dirs, d) end
210 print('Select directory:')
211 local dirIdx = promptSelect(dirs)
212 if not dirIdx then print('Invalid selection') return end
213 local dir = dirs[dirIdx]
214 local files = manifest[dir]
215 print('Select file to remove locally:')
216 local idx = promptSelect(files)
217 if not idx then print('Invalid selection') return end
218 local localDir = '/' .. dir
219 local localPath = localDir .. '/' .. files[idx]
220 if fs.exists(localPath) then
221 fs.delete(localPath)
222 print('Removed: ' .. localPath)
223 else
224 print('Not found: ' .. localPath)
225 end
226end
227
228if not http then
229 print('ERROR: http API not available. Enable in mod config.')
230else
231 if #arg >= 1 then
232 -- CLI usage:
233 -- update all | update <filename> | rm <filename>
234 local cmd = tostring(arg[1])
235 if cmd == 'all' then
236 updateAll()
237 elseif cmd == 'rm' and arg[2] then
238 local target = tostring(arg[2])
239 local found = false
240 local foundDir = nil
241 for dir, files in pairs(manifest) do
242 for i = 1, #files, 1 do
243 if files[i] == target then
244 found = true
245 foundDir = dir
246 break
247 end
248 end
249 if found then break end
250 end
251 if found and foundDir then
252 local localDir = '/' .. foundDir
253 local localPath = localDir .. '/' .. target
254 if fs.exists(localPath) then fs.delete(localPath) print('Removed: ' .. localPath) else print('Not found: ' .. localPath) end
255 else
256 print('Unknown file: ' .. target)
257 end
258 else
259 -- assume single-file update by name
260 local target = cmd
261 local found = false
262 local foundDir = nil
263 for dir, files in pairs(manifest) do
264 for i = 1, #files, 1 do
265 if files[i] == target then
266 found = true
267 foundDir = dir
268 break
269 end
270 end
271 if found then break end
272 end
273 if found and foundDir then updateDir(foundDir, { target }) else print('Unknown file: ' .. target) end
274 end
275 else
276 while true do
277 local ch = showMenu()
278 if ch == 1 then
279 updateAll()
280 elseif ch == 2 then
281 updateSingle()
282 elseif ch == 3 then
283 removeOne()
284 else
285 break
286 end
287 end
288 end
289end