From 62ff30aa64c297d736ce6eece9f578b40d412299 Mon Sep 17 00:00:00 2001 From: gesslar <1266935+gesslar@users.noreply.github.com> Date: Tue, 1 Sep 2026 02:01:08 -0400 Subject: [PATCH] some fixes and things --- BUILD.md | 2 +- LICENSE.txt | 2 - mfile | 2 +- package-lock.json | 53 +++- package.json | 17 +- src/resources/test/dependency_queue_spec.lua | 243 +++++++++++++++++-- src/scripts/conditions.lua | 2 +- src/scripts/dependency_queue.lua | 94 ++++++- 8 files changed, 363 insertions(+), 52 deletions(-) diff --git a/BUILD.md b/BUILD.md index 9a465fd..efa6ff2 100644 --- a/BUILD.md +++ b/BUILD.md @@ -66,7 +66,7 @@ python mini.py build/tmp/Glu-unified.lua build/Glu-min.lua ### `npm run build` -Runs the full build process (muddle + single + min) +Runs the full build process (muddy + single + min) ## Distribution Usage diff --git a/LICENSE.txt b/LICENSE.txt index 77bdec6..23c49c1 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,5 +1,3 @@ -Copyright (C) 2026 by Brian M. Workman bmw@gesslar.dev - Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. diff --git a/mfile b/mfile index fc872f0..ffd16f1 100644 --- a/mfile +++ b/mfile @@ -1,7 +1,7 @@ { "package": "Glu", "title": "Lua class library for Mudlet", - "version": "2.1.0", + "version": "2.2.0", "author": "gesslar", "icon": "liquid-glue.png", "dependencies": "", diff --git a/package-lock.json b/package-lock.json index 78ccdfc..46acb45 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,17 @@ { "name": "glu", - "version": "2.1.0", + "version": "2.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "glu", - "version": "2.1.0", + "version": "2.2.0", "license": "0BSD", "devDependencies": { "@gesslar/bedoc": "^2.1.3", - "@gesslar/muddy": "^2.2.3", - "@gesslar/toolkit": "^5.6.0" + "@gesslar/muddy": "^3.3.0", + "@gesslar/toolkit": "^5.9.1" } }, "node_modules/@gesslar/actioneer": { @@ -62,16 +62,17 @@ } }, "node_modules/@gesslar/muddy": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@gesslar/muddy/-/muddy-2.2.3.tgz", - "integrity": "sha512-n3zZromLSJZeGxGF1MZygep10h0Qr5nz+8OIUeKjzLOTs484UAgPf7RNvPt9JBvTcbBswYfYnFTApbTBKzOcDA==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@gesslar/muddy/-/muddy-3.3.0.tgz", + "integrity": "sha512-BRW0bvpf3z5u6vczJhmQ+CjIn4FTWk+wZF+NEpGStd+ibhCEc9hIOql/h16Yvix6aoawHYXA9t7skE8/XmqIEg==", "dev": true, "license": "0BSD", "dependencies": { "@gesslar/actioneer": "^3.1.1", "@gesslar/colours": "^1.0.0", - "@gesslar/toolkit": "^5.6.0", + "@gesslar/toolkit": "^5.8.0", "adm-zip": "^0.5.17", + "chokidar": "^5.0.0", "commander": "^15.0.0", "xmlbuilder2": "^4.0.3" }, @@ -99,9 +100,9 @@ } }, "node_modules/@gesslar/toolkit": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/@gesslar/toolkit/-/toolkit-5.6.0.tgz", - "integrity": "sha512-GPcA7qRBNp+6yPmn1jP2y2o0jrVFU3qn6U5+sdejcQgLi3rv7qDTrUosGIKTV0YhYJsiV5zoAxB4G6GHt7H0tw==", + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/@gesslar/toolkit/-/toolkit-5.9.1.tgz", + "integrity": "sha512-SjfcMGZxu1IDVJQznO87p8whs3T3O9/b1DxEwz1uq+HHZB+JNsKAoeNzW/TkJ7GWn9ioM0HUEya3ISB+FcB8Nw==", "dev": true, "hasInstallScript": true, "license": "0BSD", @@ -201,6 +202,22 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/commander": { "version": "15.0.0", "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", @@ -288,6 +305,20 @@ "node": ">=6" } }, + "node_modules/readdirp": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", diff --git a/package.json b/package.json index 585cbe5..2811829 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "name": "glu", - "version": "2.1.0", + "version": "2.2.0", "description": "A modular utility library for Mudlet that just works. No fuss, no muss.", "scripts": { - "build": "(\nset -e pipefail\nnpx @gesslar/muddy\nnpm run build:single\nnpm run build:min\nnpm run build:docs\nnpm run build:lib\n)", + "build": "npx -y @gesslar/muddy", "build:single": "python unify.py src/scripts build/Glu-single.lua", "build:min": "python unify.py src/scripts build/tmp/Glu-unified.lua && python mini.py build/tmp/Glu-unified.lua build/Glu-min.lua", "build:docs": "npx bedoc --config bedoc/bedoc.yaml --sub docs", @@ -12,10 +12,11 @@ "test": "docker run --rm -v \"$PWD\":/workspace gesslardev/mudlet-busted", "test:tree": "docker run --rm -e BUSTED_OUTPUT=treeOutput -v \"$PWD\":/workspace gesslardev/mudlet-busted", "docker:update": "docker pull gesslardev/mudlet-busted", - "major": "npx @gesslar/muddy version major", - "minor": "npx @gesslar/muddy version minor", - "patch": "npx @gesslar/muddy version patch", - "update": "npx npm-check-updates -u && npm install" + "watch": "npx -y @gesslar/muddy -w", + "major": "npx -y @gesslar/muddy version major --package && npm i", + "minor": "npx -y @gesslar/muddy version minor --package && npm i", + "patch": "npx -y @gesslar/muddy version patch --package && npm i", + "update": "npx -y npm-check-updates -u && npm install" }, "keywords": [ "mudlet", @@ -28,8 +29,8 @@ "license": "0BSD", "devDependencies": { "@gesslar/bedoc": "^2.1.3", - "@gesslar/muddy": "^2.2.3", - "@gesslar/toolkit": "^5.6.0" + "@gesslar/muddy": "^3.3.0", + "@gesslar/toolkit": "^5.9.1" }, "files": [ "README.md", diff --git a/src/resources/test/dependency_queue_spec.lua b/src/resources/test/dependency_queue_spec.lua index 85847cd..99aca35 100644 --- a/src/resources/test/dependency_queue_spec.lua +++ b/src/resources/test/dependency_queue_spec.lua @@ -169,10 +169,12 @@ describe("dependency_queue module", function() describe("install flow", function() local real_installPackage local real_tempTimer + local real_downloadFile before_each(function() real_installPackage = _G.installPackage real_tempTimer = _G.tempTimer + real_downloadFile = _G.downloadFile -- Mock tempTimer to execute callback immediately (same pattern as Mudlet's own tests) _G.tempTimer = function(time, code) @@ -185,11 +187,15 @@ describe("dependency_queue module", function() -- Mock installPackage to do nothing — we fire sysInstall manually _G.installPackage = function() end + + -- Mock downloadFile to do nothing — we fire sysDownloadDone/Error manually + _G.downloadFile = function() end end) after_each(function() _G.installPackage = real_installPackage _G.tempTimer = real_tempTimer + _G.downloadFile = real_downloadFile -- Clean up the test package if it somehow got installed if table.index_of(getPackages(), "ThreshCopy") then @@ -211,8 +217,9 @@ describe("dependency_queue module", function() dq.start() - -- Simulate Mudlet firing sysInstall after the package installs - raiseEvent("sysInstall", "ThreshCopy") + -- The download lands, then Mudlet fires sysInstall once installed + raiseEvent("sysDownloadDone", dq.current.target) + raiseEvent("sysInstallPackage", "ThreshCopy", dq.current.target) assert.is_true(cb_success) assert.is_nil(cb_message) @@ -222,21 +229,70 @@ describe("dependency_queue module", function() local cb_success = nil local cb_message = nil - g.dependency_queue.new_dependency_queue( - {{name = "ThreshCopy", url = "https://example.com/fake"}}, + local dq = g.dependency_queue.new_dependency_queue( + {{name = "ThreshCopy", url = "https://example.com/fake.mpackage"}}, function(success, message) cb_success = success cb_message = message end ) - -- Simulate Mudlet firing sysDownloadError - raiseEvent("sysDownloadError", "ThreshCopy") + dq.start() + + -- Mudlet fires sysDownloadError(error, local_file, url) for the file we chose + raiseEvent("sysDownloadError", "404 not found", dq.current.target, + "https://example.com/fake.mpackage") assert.is_false(cb_success) assert.is_truthy(cb_message) end) + it("should install from the downloaded file when sysDownloadDone fires", function() + local installed_from = nil + _G.installPackage = function(path) installed_from = path end + + local dq = g.dependency_queue.new_dependency_queue( + {{name = "FakePkgDone", url = "https://example.com/FakePkgDone.mpackage"}}, + function() end + ) + + dq.start() + + local target = dq.current.target + raiseEvent("sysDownloadDone", target) + + assert.are.equal(target, installed_from) + dq.clean_up() + end) + + it("should download to a uuid file name keeping the url extension", function() + local dq = g.dependency_queue.new_dependency_queue( + {{name = "FakePkgPath", url = "https://example.com/pkg/FakePkgPath.mpackage"}}, + function() end + ) + + dq.start() + + -- The name is a uuid; only the extension is carried over from the url + assert.is_truthy(string.find(dq.current.target, "%.mpackage$")) + assert.is_falsy(string.find(dq.current.target, "FakePkgPath")) + assert.is_truthy(string.find(dq.current.target, + "/%x+%-%x+%-%x+%-%x+%-%x+%.mpackage$")) + dq.clean_up() + end) + + it("should keep a non-mpackage extension from the url", function() + local dq = g.dependency_queue.new_dependency_queue( + {{name = "FakePkgZip", url = "https://example.com/pkg/FakePkgZip.zip"}}, + function() end + ) + + dq.start() + + assert.is_truthy(string.find(dq.current.target, "%.zip$")) + dq.clean_up() + end) + it("should install multiple packages in sequence", function() local cb_success = nil @@ -252,10 +308,12 @@ describe("dependency_queue module", function() dq.start() - -- First package installs - raiseEvent("sysInstall", "FakePkgA") - -- Second package installs - raiseEvent("sysInstall", "FakePkgB") + -- First package downloads, then installs + raiseEvent("sysDownloadDone", dq.current.target) + raiseEvent("sysInstallPackage", "FakePkgA", dq.current.target) + -- Second package downloads, then installs + raiseEvent("sysDownloadDone", dq.current.target) + raiseEvent("sysInstallPackage", "FakePkgB", dq.current.target) assert.is_true(cb_success) end) @@ -291,10 +349,12 @@ describe("dependency_queue module", function() describe("install flow edge cases", function() local real_installPackage local real_tempTimer + local real_downloadFile before_each(function() real_installPackage = _G.installPackage real_tempTimer = _G.tempTimer + real_downloadFile = _G.downloadFile _G.tempTimer = function(time, code) if type(code) == "function" then @@ -305,11 +365,13 @@ describe("dependency_queue module", function() end _G.installPackage = function() end + _G.downloadFile = function() end end) after_each(function() _G.installPackage = real_installPackage _G.tempTimer = real_tempTimer + _G.downloadFile = real_downloadFile end) it("should ignore sysInstall for non-matching package names", function() @@ -323,9 +385,10 @@ describe("dependency_queue module", function() ) dq.start() + raiseEvent("sysDownloadDone", dq.current.target) - -- Fire sysInstall for a different package — should be ignored - raiseEvent("sysInstall", "SomeOtherPackage") + -- Fire an install for a different package's file — should be ignored + raiseEvent("sysInstallPackage", "SomeOtherPackage", "/tmp/elsewhere/SomeOtherPackage.mpackage") assert.is_nil(cb_success) dq.clean_up() @@ -349,7 +412,8 @@ describe("dependency_queue module", function() dq.start() -- First package fails to download - raiseEvent("sysDownloadError", "FakePkgFirst") + raiseEvent("sysDownloadError", "404 not found", dq.current.target, + "https://example.com/first") assert.is_false(cb_success) assert.is_truthy(cb_message) @@ -373,14 +437,16 @@ describe("dependency_queue module", function() dq.start() -- First package succeeds - raiseEvent("sysInstall", "FakePkgAlpha") + raiseEvent("sysDownloadDone", dq.current.target) + raiseEvent("sysInstallPackage", "FakePkgAlpha", dq.current.target) -- Reset to check second callback cb_success = nil cb_message = nil - -- Second package fails - raiseEvent("sysDownloadError", "FakePkgBeta") + -- Second package fails; current now points at the second download + raiseEvent("sysDownloadError", "404 not found", dq.current.target, + "https://example.com/beta") assert.is_false(cb_success) assert.is_truthy(cb_message) @@ -394,13 +460,153 @@ describe("dependency_queue module", function() local handler_name = dq.handler_name dq.start() - raiseEvent("sysInstall", "FakePkgCleanup") + raiseEvent("sysDownloadDone", dq.current.target) + raiseEvent("sysInstallPackage", "FakePkgCleanup", dq.current.target) -- After completion, handler_name should be nil (cleaned up) assert.is_nil(dq.handler_name) assert.is_nil(dq.queue) end) + it("should ignore a download error for an unrelated download", function() + local cb_success = nil + + local dq = g.dependency_queue.new_dependency_queue( + {{name = "FakePkgMine", url = "https://example.com/mine.mpackage"}}, + function(success, message) + cb_success = success + end + ) + + dq.start() + + -- Something else in the profile fails to download — a path we did not choose + raiseEvent("sysDownloadError", "404 not found", "/tmp/theirs.mpackage", + "https://elsewhere.example.com/theirs.mpackage") + + assert.is_nil(cb_success) + assert.is_not_nil(dq.handler_name) + assert.is_not_nil(dq.queue) + + -- Our own download and install still complete normally afterwards + raiseEvent("sysDownloadDone", dq.current.target) + raiseEvent("sysInstallPackage", "FakePkgMine", dq.current.target) + + assert.is_true(cb_success) + end) + + it("should ignore an unrelated download sharing our file name", function() + local cb_success = nil + + local dq = g.dependency_queue.new_dependency_queue( + {{name = "FakePkgSame", url = "https://example.com/same.mpackage"}}, + function(success, message) + cb_success = success + end + ) + + dq.start() + + -- Same final file name, different directory: the uuid keeps it distinct + local collision = string.gsub(dq.current.target, "same%.mpackage$", "") .. + "../other/same.mpackage" + + raiseEvent("sysDownloadError", "404 not found", collision, + "https://elsewhere.example.com/same.mpackage") + + assert.is_nil(cb_success) + assert.is_not_nil(dq.queue) + dq.clean_up() + end) + + it("should match a download error however the url was redirected", function() + local cb_success = nil + + local dq = g.dependency_queue.new_dependency_queue( + {{name = "FakePkgRedir", url = "https://example.com/pkg/redir.mpackage"}}, + function(success, message) + cb_success = success + end + ) + + dq.start() + + -- Redirection rewrote the url to a signed path with no recognisable file + -- name; identity comes from the local path, so this is still ours + raiseEvent("sysDownloadError", "connection reset", dq.current.target, + "https://cdn.example.net/asset/5dd0c819-e4ee-447e-b18f-24a7b5ed17fe?sig=abc") + + assert.is_false(cb_success) + end) + + it("should ignore an install of the same name from elsewhere", function() + local cb_success = nil + + local dq = g.dependency_queue.new_dependency_queue( + { + {name = "FakePkgRace", url = "https://example.com/race.mpackage"}, + {name = "FakePkgAfter", url = "https://example.com/after.mpackage"}, + }, + function(success, message) + cb_success = success + end + ) + + dq.start() + + local target = dq.current.target + + -- Another component installs a package of the same name from its own + -- file — same name, different path, so it is not ours + raiseEvent("sysInstallPackage", "FakePkgRace", "/tmp/elsewhere/FakePkgRace.mpackage") + + assert.is_nil(cb_success) + assert.are.equal(2, #dq.packages) + assert.is_not_nil(dq.current) + assert.are.equal(target, dq.current.target) + + -- Our own download and install still proceed normally + raiseEvent("sysDownloadDone", target) + raiseEvent("sysInstallPackage", "FakePkgRace", target) + + assert.are.equal(1, #dq.packages) + dq.clean_up() + end) + + it("should not discard the active download on an install from elsewhere", function() + local dq = g.dependency_queue.new_dependency_queue( + {{name = "FakePkgKeep", url = "https://example.com/keep.mpackage"}}, + function() end + ) + + dq.start() + + local target = dq.current.target + raiseEvent("sysInstallPackage", "FakePkgKeep", "/tmp/elsewhere/FakePkgKeep.mpackage") + + -- Our in-flight download must not be discarded + assert.is_not_nil(dq.current) + assert.are.equal(target, dq.current.target) + dq.clean_up() + end) + + it("should ignore sysDownloadDone for an unrelated download", function() + local installed = false + _G.installPackage = function() installed = true end + + local dq = g.dependency_queue.new_dependency_queue( + {{name = "FakePkgOther", url = "https://example.com/other.mpackage"}}, + function() end + ) + + dq.start() + + raiseEvent("sysDownloadDone", "/tmp/not-ours.mpackage") + + assert.is_false(installed) + dq.clean_up() + end) + it("should clean up event handlers after download error", function() local dq = g.dependency_queue.new_dependency_queue( {{name = "FakePkgErrClean", url = "https://example.com/errclean"}}, @@ -408,7 +614,8 @@ describe("dependency_queue module", function() ) dq.start() - raiseEvent("sysDownloadError", "FakePkgErrClean") + raiseEvent("sysDownloadError", "404 not found", dq.current.target, + "https://example.com/errclean") assert.is_nil(dq.handler_name) assert.is_nil(dq.queue) diff --git a/src/scripts/conditions.lua b/src/scripts/conditions.lua index 12ec8cb..5231e26 100644 --- a/src/scripts/conditions.lua +++ b/src/scripts/conditions.lua @@ -155,7 +155,7 @@ local ConditionsClass = Glu.glass.register({ --- Checks if a value is of a specific type. ---@param value any The value to check - ---@param type string The type to check against + ---@param expected_type string The type to check against ---@param message string|nil The message to return if the values are not of the specified type ---@return boolean ok The condition ---@return string|nil message The message diff --git a/src/scripts/dependency_queue.lua b/src/scripts/dependency_queue.lua index ac82bc5..423eb36 100644 --- a/src/scripts/dependency_queue.lua +++ b/src/scripts/dependency_queue.lua @@ -3,11 +3,12 @@ local DependencyQueueClass = Glu.glass.register({ name = "dependency_queue", extends = "queue", call = "new_dependency_queue", - dependencies = { "queue", "table", }, + dependencies = { "queue", "table", "fd", "url", }, setup = function(___, self) --- Builds a queue that installs any of the given Mudlet packages that are not already installed. - --- Filters out already-installed packages, wires up sysInstall and sysDownloadError event - --- handlers, and invokes the callback with success and a message when finished. + --- Filters out already-installed packages, downloads each remaining package into its own + --- temporary directory, installs it from there, and invokes the callback with success and a + --- message when finished. --- ---@param packages table List of package descriptors, each a {name, url} table. ---@param cb function Callback called with a success boolean and a message string when finished. @@ -30,6 +31,7 @@ local DependencyQueueClass = Glu.glass.register({ cb = cb, queue = self.new_queue(), packages = not_installed, + current = nil, handler_name = f "dependency_{id}_installed", } ___.table.add(self, this) @@ -37,16 +39,46 @@ local DependencyQueueClass = Glu.glass.register({ for _, package in ipairs(not_installed) do local func = function() cecho("Installing dependency `" .. package.name .. "`...\n") - installPackage(package.url) + + -- Mudlet's download and install events are global, and the URL they + -- carry may have been rewritten by redirection, so the URL cannot + -- identify an event as ours. The local path can: we name the file + -- for a fresh UUID that nothing else in the profile can collide + -- with. The name is ours to choose because Mudlet takes the package + -- name from the archive's config.lua manifest, not from the file -- + -- only the extension has to survive, since that selects the format. + local dir = ___.fd.fix_path(getMudletHomeDir() .. "/tmp") + local ok, err = ___.fd.assure_dir(dir) + if not ok then + local reason = err or "unknown error" + self.cb(false, f "Could not create a temporary directory for `{package.name}`: {reason}\n") + self.clean_up() + return + end + + local parsed = ___.url.parse(package.url) + local ext = parsed and parsed.file and string.match(parsed.file, "%.(%w+)$") or "mpackage" + + self.current = { + package = package, + target = ___.fd.fix_path(dir .. "/" .. ___.id() .. "." .. ext), + } + + downloadFile(self.current.target, package.url) end self.queue.push(func) end - registerNamedEventHandler("glu", self.handler_name, "sysInstall", - function(event, package) - if package ~= self.packages[1].name then return end + registerNamedEventHandler("glu", self.handler_name, "sysInstallPackage", + function(event, package, file) + -- sysInstallPackage reports the file Mudlet was asked to install, so + -- the same UUID path that identifies our downloads identifies our + -- installs. sysInstall carries only a package name, which any other + -- component installing something of that name would also produce. + if not self.is_ours(file) then return end + self.discard_temp() ___.table.shift(self.packages) tempTimer(1, function() local q, count = self.queue.execute() @@ -58,17 +90,59 @@ local DependencyQueueClass = Glu.glass.register({ end ) + registerNamedEventHandler("glu", self.handler_name .. "_download_done", "sysDownloadDone", + function(event, local_file) + if not self.is_ours(local_file) then return end + + installPackage(self.current.target) + end + ) + registerNamedEventHandler("glu", self.handler_name .. "_download_error", "sysDownloadError", - function(event, package) - self.cb(false, f "Failed to download dependency `{package}`.\nCleaning up.\n") + function(event, err, local_file, url) + if not self.is_ours(local_file) then return end + + local name = self.current.package.name + local reason = err or "unknown error" + + self.discard_temp() + self.cb(false, f "Failed to download dependency `{name}`: {reason}\nCleaning up.\n") self.clean_up() end ) + --- Determines whether a download or install event refers to the file this + --- queue is currently working on. The path was chosen by us and contains a UUID, + --- so an exact match is unambiguous and no guessing is required. + --- + ---@param local_file string? The local path reported by the download event. + ---@return boolean result Whether the event refers to our download. + function self.is_ours(local_file) + if not self.current or type(local_file) ~= "string" then return false end + + return ___.fd.fix_path(local_file) == self.current.target + end + + --- Removes the temporary file holding the current download, if any. + --- Safe to call more than once. + function self.discard_temp() + local current = self.current + if not current then return end + + if ___.fd.file_exists(current.target) then ___.fd.rmfile(current.target) end + + self.current = nil + end + --- Removes the registered event handlers and tears down the queue. - --- Deletes the named sysInstall and sysDownloadError handlers and clears the handler name and queue. + --- Deletes the named sysInstallPackage, sysDownloadDone and sysDownloadError + --- handlers, discards any temporary download, and clears the handler name + --- and queue. function self.clean_up() + self.discard_temp() + deleteNamedEventHandler("glu", self.handler_name) + deleteNamedEventHandler("glu", self.handler_name .. "_download_done") deleteNamedEventHandler("glu", self.handler_name .. "_download_error") self.handler_name = nil self.queue = nil