update.lua
251 lines · 7.4 KB
Bulk updater for CC-Tweaked scripts
Usage: run: update
Copy & run
wget https://perlytiara.github.io/turtles.tips/raw/programs/BigGamingGamers/update.lua
| 1 | -- Bulk updater for CC-Tweaked scripts |
| 2 | -- Overwrites local scripts with the latest from GitHub |
| 3 | -- Usage: run: update |
| 4 | |
| 5 | local BASE = 'https://raw.githubusercontent.com/perlytiara/CC-Tweaked-TurtsAndComputers/refs/heads/main/' |
| 6 | |
| 7 | local 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 | }, |
| 44 | } |
| 45 | |
| 46 | local function ensureDir(path) |
| 47 | if not fs.exists(path) then |
| 48 | fs.makeDir(path) |
| 49 | end |
| 50 | end |
| 51 | |
| 52 | local function ensureParentDir(localPath) |
| 53 | local parent = string.match(localPath, "(.+)/[^/]+$") |
| 54 | if parent then ensureDir(parent) end |
| 55 | end |
| 56 | |
| 57 | local function fetch(remotePath) |
| 58 | local url = BASE .. remotePath .. '?breaker=' .. tostring(math.random(0, 999999)) |
| 59 | local res, err = http.get(url) |
| 60 | if not res then |
| 61 | print('ERROR: http.get failed for ' .. remotePath .. ' - ' .. tostring(err)) |
| 62 | return false, nil |
| 63 | end |
| 64 | local code = res.readAll() |
| 65 | res.close() |
| 66 | return true, code |
| 67 | end |
| 68 | |
| 69 | local function writeFile(path, contents) |
| 70 | local handle = fs.open(path, 'w') |
| 71 | handle.write(contents) |
| 72 | handle.close() |
| 73 | end |
| 74 | |
| 75 | local function readFile(path) |
| 76 | if not fs.exists(path) then return '' end |
| 77 | local h = fs.open(path, 'r') |
| 78 | local c = h.readAll() |
| 79 | h.close() |
| 80 | return c or '' |
| 81 | end |
| 82 | |
| 83 | local function bytesDiff(a, b) |
| 84 | return string.len(b) - string.len(a) |
| 85 | end |
| 86 | |
| 87 | local function progressBar(current, total, label) |
| 88 | local width = 28 |
| 89 | local filled = math.floor((current / total) * width) |
| 90 | local bar = string.rep('#', filled) .. string.rep('-', width - filled) |
| 91 | print(string.format('[%s] %d/%d %s', bar, current, total, label or '')) |
| 92 | end |
| 93 | |
| 94 | local function removeStale(localDir, keepSet) |
| 95 | if not fs.exists(localDir) then return end |
| 96 | local function walk(dir, relPrefix) |
| 97 | local items = fs.list(dir) |
| 98 | for i = 1, #items, 1 do |
| 99 | local name = items[i] |
| 100 | local full = dir .. '/' .. name |
| 101 | local relPath = (relPrefix ~= '' and (relPrefix .. '/' .. name)) or name |
| 102 | if fs.isDir(full) then |
| 103 | walk(full, relPath) |
| 104 | if #fs.list(full) == 0 then fs.delete(full) end |
| 105 | else |
| 106 | if relPath ~= 'load.lua' and relPath ~= 'update.lua' and not keepSet[relPath] then |
| 107 | fs.delete(full) |
| 108 | print('Removed stale: ' .. full) |
| 109 | end |
| 110 | end |
| 111 | end |
| 112 | end |
| 113 | walk(localDir, '') |
| 114 | end |
| 115 | |
| 116 | local function updateDir(dir, files) |
| 117 | local updated, same = 0, 0 |
| 118 | local localDir = '/' .. dir |
| 119 | ensureDir(localDir) |
| 120 | |
| 121 | local keep = {} |
| 122 | for i = 1, #files, 1 do keep[files[i]] = true end |
| 123 | |
| 124 | local total = #files |
| 125 | for i = 1, #files, 1 do |
| 126 | local fname = files[i] |
| 127 | local remotePath = dir .. '/' .. fname |
| 128 | local localPath = localDir .. '/' .. fname |
| 129 | local ok, code = fetch(remotePath) |
| 130 | if ok and code then |
| 131 | local old = readFile(localPath) |
| 132 | if old == code then |
| 133 | same = same + 1 |
| 134 | else |
| 135 | ensureParentDir(localPath) |
| 136 | writeFile(localPath, code) |
| 137 | local diff = bytesDiff(old, code) |
| 138 | local change = (diff >= 0 and (tostring(math.abs(diff)) .. ' bytes added')) or (tostring(math.abs(diff)) .. ' bytes removed') |
| 139 | print('Updated: ' .. localPath .. ' (' .. change .. ')') |
| 140 | updated = updated + 1 |
| 141 | end |
| 142 | end |
| 143 | progressBar(i, total, fname) |
| 144 | if sleep then sleep(0) end |
| 145 | end |
| 146 | |
| 147 | removeStale(localDir, keep) |
| 148 | print(string.format('Done. Updated %d, unchanged %d.', updated, same)) |
| 149 | end |
| 150 | |
| 151 | local function promptSelect(options) |
| 152 | for i = 1, #options, 1 do |
| 153 | print(string.format('%d) %s', i, options[i])) |
| 154 | end |
| 155 | io.write('Select number: ') |
| 156 | local choice = read() |
| 157 | local idx = tonumber(choice) |
| 158 | if idx and idx >= 1 and idx <= #options then |
| 159 | return idx |
| 160 | end |
| 161 | return nil |
| 162 | end |
| 163 | |
| 164 | local function showMenu() |
| 165 | print('== Update Menu ==') |
| 166 | print('1) Auto update all (clean stale)') |
| 167 | print('2) Update a single file') |
| 168 | print('3) Remove a local file') |
| 169 | print('4) Exit') |
| 170 | io.write('Choose: ') |
| 171 | local ch = read() |
| 172 | return tonumber(ch) |
| 173 | end |
| 174 | |
| 175 | local function updateAll() |
| 176 | for dir, files in pairs(manifest) do |
| 177 | updateDir(dir, files) |
| 178 | end |
| 179 | end |
| 180 | |
| 181 | local function updateSingle() |
| 182 | local dir, files |
| 183 | for d, f in pairs(manifest) do dir, files = d, f break end |
| 184 | print('Select file to update:') |
| 185 | local idx = promptSelect(files) |
| 186 | if not idx then print('Invalid selection') return end |
| 187 | updateDir(dir, { files[idx] }) |
| 188 | end |
| 189 | |
| 190 | local function removeOne() |
| 191 | local dir, files |
| 192 | for d, f in pairs(manifest) do dir, files = d, f break end |
| 193 | print('Select file to remove locally:') |
| 194 | local idx = promptSelect(files) |
| 195 | if not idx then print('Invalid selection') return end |
| 196 | local localDir = '/' .. dir |
| 197 | local localPath = localDir .. '/' .. files[idx] |
| 198 | if fs.exists(localPath) then |
| 199 | fs.delete(localPath) |
| 200 | print('Removed: ' .. localPath) |
| 201 | else |
| 202 | print('Not found: ' .. localPath) |
| 203 | end |
| 204 | end |
| 205 | |
| 206 | if not http then |
| 207 | print('ERROR: http API not available. Enable in mod config.') |
| 208 | else |
| 209 | if #arg >= 1 then |
| 210 | local cmd = tostring(arg[1]) |
| 211 | if cmd == 'all' then |
| 212 | updateAll() |
| 213 | elseif cmd == 'rm' and arg[2] then |
| 214 | local target = tostring(arg[2]) |
| 215 | local dir, files |
| 216 | for d, f in pairs(manifest) do dir, files = d, f break end |
| 217 | local found = false |
| 218 | for i = 1, #files, 1 do if files[i] == target then found = true break end end |
| 219 | if found then |
| 220 | local localDir = '/' .. dir |
| 221 | local localPath = localDir .. '/' .. target |
| 222 | if fs.exists(localPath) then fs.delete(localPath) print('Removed: ' .. localPath) else print('Not found: ' .. localPath) end |
| 223 | else |
| 224 | print('Unknown file: ' .. target) |
| 225 | end |
| 226 | else |
| 227 | local target = cmd |
| 228 | local dir, files |
| 229 | for d, f in pairs(manifest) do dir, files = d, f break end |
| 230 | local found = false |
| 231 | for i = 1, #files, 1 do if files[i] == target then found = true break end end |
| 232 | if found then updateDir(dir, { target }) else print('Unknown file: ' .. target) end |
| 233 | end |
| 234 | else |
| 235 | while true do |
| 236 | local ch = showMenu() |
| 237 | if ch == 1 then |
| 238 | updateAll() |
| 239 | elseif ch == 2 then |
| 240 | updateSingle() |
| 241 | elseif ch == 3 then |
| 242 | removeOne() |
| 243 | else |
| 244 | break |
| 245 | end |
| 246 | end |
| 247 | end |
| 248 | end |
| 249 | |
| 250 | |
| 251 |