1) Version 1.9

2) Added new "dinv debug" interface for testing new features
3) Changed default # of automatic backups from 4 down to 3 and updated the verbage
   in "dinv help backup" to make things more clear
4) Added dbot.shell() framework to execute shell commands in the background without
   pulling up a command prompt window.  The previous method of using os.execute()
   was functionally correct, but on Windows (but not wine?!?) using os.execute()
   could briefly display a command prompt window before automatically closing it.
5) Added routines to detect if files/directories exist and to spin until a specified
   file or directory is created or deleted.  You can spin in either a sleep loop
   (more efficient but requires a co-routine) or in a busy loop (less efficient but
   can be used anywhere.)
6) Replaced all os.execute() calls with equivalent dbot.shell() calls.  The os.execute
   code runs everything in the foreground and stalls until the command completes.  In
   contrast, dbot.shell() runs in the background and does not block the user from
   doing other things.  In places where we need to block (e.g., you don't want the
   user changing saved state in the middle of a backup) the plugin will explicitly
   block now.
7) Fixed an assert that could potentially trigger while opening a state file
master
Durel 7 years ago
parent 1f0aad210f
commit 6273a35a26

@ -16,9 +16,10 @@ Durel's Bag-of-Tricks (dbot)
Author: Durel Author: Durel
Version history: Version history:
v0.0.1 - 2017-07-01 - Initial code v0.1 - 2017-07-01 - Initial code
v0.0.2 - 2017-08-12 - Converted scripts into a plugin v0.2 - 2017-08-12 - Converted scripts into a plugin
v0.0.3 - 2017-09-26 - Fully functional plugin published to github v0.3 - 2017-09-26 - Functional plugin published to github
v1.0 - 2017-10-01 - It's alive! Most pieces are verified by alpha testers.
Description Description
=========== ===========
@ -83,7 +84,7 @@ dbot.callback : Module to help manage callback functions and parameters
save_state="y" save_state="y"
date_written="2017-08-12 08:45:15" date_written="2017-08-12 08:45:15"
requires="4.98" requires="4.98"
version="1.8" version="1.9"
> >
<description trim="y"> <description trim="y">
<![CDATA[ <![CDATA[
@ -576,6 +577,16 @@ Feature Wishlist
> >
</alias> </alias>
<alias
script="inv.cli.debug.fn"
match="^[ ]*dinv[ ]+debug(.*)$"
enabled="y"
regexp="y"
send_to="12"
sequence="100"
>
</alias>
</aliases> </aliases>
<!-- Script --> <!-- Script -->
@ -3637,11 +3648,13 @@ function inv.cli.backup.examples()
dbot.print( dbot.print(
[[@W [[@W
The plugin creates automatic backups for all of your plugin data. It also gives The plugin creates automatic backups for all of your plugin data. It also gives
you the ability to create manual backups at any time. By default, the plugin you the ability to create manual backups at any time.
keeps 4 automatic backups.
By default, automatic backups are enabled. You can enable or disable the By default, the plugin enables automatic backups and maintains backups for the
automatic backups with "@Gdinv backup on@W" or "@Gdinv backup off@W". three most recent days you used the plugin. Automatic backups are taken when you
log out, once every 4 hours you are logged in, and every time you go AFK for at
least 5 seconds. You can enable or disable automatic backups by running
"@Gdinv backup on@W" or "@Gdinv backup off@W".
Most automatic backup systems rotate all previous automatic backups when a new Most automatic backup systems rotate all previous automatic backups when a new
backup is created. In other words, if you have automatic backups 1, 2, 3, and 4 backup is created. In other words, if you have automatic backups 1, 2, 3, and 4
@ -3655,11 +3668,8 @@ time.
The compromise solution implemented in this plugin is for automatic backups to only The compromise solution implemented in this plugin is for automatic backups to only
overwrite backup #1 and to rotate the backups at most once per day. This gives you overwrite backup #1 and to rotate the backups at most once per day. This gives you
frequent backups from "today" (backup #1) and backups from three previous days frequent backups from "today" (backup #1) and backups from two previous days
(#2 - #4). (backups #2 and #3).
Automatic backups are taken when you log out, once every 4 hours you are logged in,
and every time you go AFK for at least 5 seconds.
You have the option to list existing backups (sorted by creation date), create You have the option to list existing backups (sorted by creation date), create
new manual backups, delete a backup (automatic or manual), or restore from an existing new manual backups, delete a backup (automatic or manual), or restore from an existing
@ -3673,12 +3683,11 @@ Examples:
all other backups were created manually. all other backups were created manually.
"@Gdinv backup list@W" "@Gdinv backup list@W"
@WDINV@W Detected 6 backups @WDINV@W Detected 5 backups
@w @W(@c09/18/17 18:30:57@W) @GdummyBackupToShowItWorksForTheHelpfile @w @W(@c09/18/17 18:30:57@W) @GdummyBackupToShowItWorksForTheHelpfile
@w @W(@c09/18/17 18:06:16@W) @Gauto @w @W(@c09/18/17 18:06:16@W) @Gauto
@w @W(@c09/17/17 22:51:49@W) @Gauto2 @w @W(@c09/17/17 22:51:49@W) @Gauto2
@w @W(@c09/16/17 23:12:53@W) @Gauto3 @w @W(@c09/16/17 23:12:53@W) @Gauto3
@w @W(@c09/15/17 23:48:40@W) @Gauto4
@w @W(@c09/15/17 23:42:49@W) @Gbaseline@W @w @W(@c09/15/17 23:42:49@W) @Gbaseline@W
3) Delete the silly manual backup "dummyBackupToShowItWorksForTheHelpfile". 3) Delete the silly manual backup "dummyBackupToShowItWorksForTheHelpfile".
@ -4479,6 +4488,17 @@ Examples:
end -- inv.cli.help.examples end -- inv.cli.help.examples
inv.cli.debug = {}
function inv.cli.debug.fn(name, line, wildcards)
local command = wildcards[1] or ""
command = Trim(command)
dbot.note("Debug params = \"" .. command .. "\"")
dbot.shell(command)
end -- inv.cli.debug.fn
---------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------
-- Item management module: create an inventory table and provide access to it -- Item management module: create an inventory table and provide access to it
-- --
@ -16095,6 +16115,155 @@ function dbot.updateRaw(retval, page, status, headers, fullStatus, requestUrl)
end -- dbot.updateRaw end -- dbot.updateRaw
----------------------------------------------------------------------------------------------------
-- dbot.shell: Run a shell command in the background without pulling up a command prompt window
----------------------------------------------------------------------------------------------------
function dbot.shell(shellCommand)
local retval = DRL_RET_SUCCESS
local mushRetval
if (shellCommand == nil) or (shellCommand == "") then
dbot.warn("dbot.shell: Missing shell command")
return DRL_RET_INVALID_PARAMETER
end -- if
dbot.debug("dbot.shell: Executing \"@G" .. "/C " .. shellCommand .. "@W\"")
local ok, error = utils.shellexecute("cmd", "/C " .. shellCommand, GetInfo(64), "open", 0)
if (not ok) then
dbot.warn("dbot.shell: Command \"@G" .. shellCommand .. "@W\" failed")
retval = DRL_INTERNAL_ERROR
end -- if
return retval
end -- dbot.shell
----------------------------------------------------------------------------------------------------
-- dbot.fileExists: Returns true if the specified file (or directory) exists and false otherwise
----------------------------------------------------------------------------------------------------
function dbot.fileExists(fileName)
if (fileName == nil) or (fileName == "") then
return false
end -- if
local dirQuery = string.gsub(string.gsub(fileName, "\\", "/"), "/$", "")
local dirTable, error = utils.readdir(dirQuery)
if (dirTable == nil) then
return false
else
--tprint(dirTable)
return true
end -- if
end -- dbot.fileExists
----------------------------------------------------------------------------------------------------
-- dbot.spinUntilExists: Spin in a sleep-loop waiting for the specified file to be created
----------------------------------------------------------------------------------------------------
function dbot.spinUntilExists(fileName, timeoutSec)
local totTime = 0
-- Wait until either we detect that the file exists or until we time out
while (not dbot.fileExists(fileName)) do
if (totTime > timeoutSec) then
dbot.warn("dbot.spinUntilExists: Timed out waiting for creation of \"@G" .. fileName .. "@W\"")
return DRL_RET_TIMEOUT
end -- if
wait.time(drlSpinnerPeriodDefault)
totTime = totTime + drlSpinnerPeriodDefault
end -- while
return DRL_RET_SUCCESS
end -- dbot.spinUntilExists
----------------------------------------------------------------------------------------------------
-- dbot.spinWhileExists: Spin in a sleep-loop waiting for the specified file to be deleted
----------------------------------------------------------------------------------------------------
function dbot.spinWhileExists(fileName, timeoutSec)
local totTime = 0
-- Wait until either we detect that the file does not exist or until we time out
while (dbot.fileExists(fileName)) do
if (totTime > timeoutSec) then
dbot.warn("dbot.spinWhileExists: Timed out waiting for deletion of \"@G" .. fileName .. "@W\"")
return DRL_RET_TIMEOUT
end -- if
wait.time(drlSpinnerPeriodDefault)
totTime = totTime + drlSpinnerPeriodDefault
end -- while
return DRL_RET_SUCCESS
end -- dbot.spinWhileExists
----------------------------------------------------------------------------------------------------
-- dbot.spinUntilExistsBusy: Spin in a busy-loop waiting for the specified file to be created
--
-- This is identical to dbot.spinUntilExists() but it uses a busy loop instead of
-- scheduling a wait. A busy loop is less efficient, but you have the option of
-- using it outside of a co-routine and that comes in handy in certain circumstances.
----------------------------------------------------------------------------------------------------
function dbot.spinUntilExistsBusy(fileName, timeoutSec)
local startTime = dbot.getTime()
-- Wait until either we detect that the file exists or until we time out
while (not dbot.fileExists(fileName)) do
-- We time out if we have been in a busy loop for over timeoutSec seconds. This
-- only has a resolution of 1 second so it's possible that we may timeout up to
-- 1 second later than the user requested. I'd rather take a chance of timing
-- out a little late than timing out a little early.
if (dbot.getTime() - startTime > timeoutSec) then
dbot.warn("dbot.spinUntilExists: Timed out waiting for creation of \"@G" .. fileName .. "@W\"")
return DRL_RET_TIMEOUT
end -- if
end -- while
return DRL_RET_SUCCESS
end -- dbot.spinUntilExistsBusy
----------------------------------------------------------------------------------------------------
-- dbot.spinWhileExistsBusy: Spin in a busy-loop waiting for the specified file to be deleted
--
-- This is identical to dbot.spinWhileExists() but it uses a busy loop instead of
-- scheduling a wait. A busy loop is less efficient, but you have the option of
-- using it outside of a co-routine and that comes in handy in certain circumstances.
----------------------------------------------------------------------------------------------------
function dbot.spinWhileExistsBusy(fileName, timeoutSec)
local startTime = dbot.getTime()
-- Wait until either we detect that the file is removed or until we time out
while (dbot.fileExists(fileName)) do
-- We time out if we have been in a busy loop for over timeoutSec seconds. This
-- only has a resolution of 1 second so it's possible that we may timeout up to
-- 1 second later than the user requested. I'd rather take a chance of timing
-- out a little late than timing out a little early.
if (dbot.getTime() - startTime > timeoutSec) then
dbot.warn("dbot.spinWhileExists: Timed out waiting for deletion of \"@G" .. fileName .. "@W\"")
return DRL_RET_TIMEOUT
end -- if
end -- while
return DRL_RET_SUCCESS
end -- dbot.spinWhileExistsBusy
---------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------
-- dbot.tonumber: version of tonumber that strips out commas from a number -- dbot.tonumber: version of tonumber that strips out commas from a number
---------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------
@ -16848,22 +17017,34 @@ dbot.storage.init = {}
dbot.storage.fileVersion = 1 dbot.storage.fileVersion = 1
dbot.storage.hashChars = (2 * 20) -- utils.hash uses a 160-bit (20 byte) hash w/ 2 hex chars per byte dbot.storage.hashChars = (2 * 20) -- utils.hash uses a 160-bit (20 byte) hash w/ 2 hex chars per byte
function dbot.storage.init.atActive() function dbot.storage.init.atActive()
local retval
-- Create directories for our state if they do not yet exist -- Create directories for our state if they do not yet exist
assert(os.execute("if not exist \"" .. pluginStatePath .. "\" mkdir \"" .. pluginStatePath .. "\" > nul"), retval = dbot.shell("if not exist \"" .. pluginStatePath .. "\" mkdir \"" .. pluginStatePath .. "\" > nul")
"dbot.storage.init.atActive: Failed to create plugin state directory \"" .. pluginStatePath .. "\"") if (retval ~= DRL_RET_SUCCESS) then
dbot.warn("dbot.storage.init.atActive: Failed to create plugin state directory \"" ..
pluginStatePath .. "\"")
end -- if
dbot.spinUntilExists(pluginStatePath, 1)
local baseDir = dbot.backup.getBaseDir() local baseDir = dbot.backup.getBaseDir()
dbot.debug("dbot.storage.init.atActive: baseDir=\"" .. baseDir .. "\"") dbot.debug("dbot.storage.init.atActive: baseDir=\"" .. baseDir .. "\"")
assert(os.execute("if not exist \"" .. baseDir .. "\" mkdir \"" .. baseDir .. "\" > nul"), retval = dbot.shell("if not exist \"" .. baseDir .. "\" mkdir \"" .. baseDir .. "\" > nul")
"dbot.storage.init.atActive: Failed to create character-specific state directory \"" .. if (retval ~= DRL_RET_SUCCESS) then
baseDir .. "\"") dbot.warn("dbot.storage.init.atActive: Failed to create character-specific state directory \"" ..
baseDir .. "\"")
end -- if
dbot.spinUntilExists(baseDir, 1)
local currentDir = dbot.backup.getCurrentDir() local currentDir = dbot.backup.getCurrentDir()
dbot.debug("dbot.storage.init.atActive: currentDir=\"" .. currentDir .. "\"") dbot.debug("dbot.storage.init.atActive: currentDir=\"" .. currentDir .. "\"")
assert(os.execute("if not exist \"" .. currentDir .. "\" mkdir \"" .. currentDir .. "\" > nul"), retval = dbot.shell("if not exist \"" .. currentDir .. "\" mkdir \"" .. currentDir .. "\" > nul")
"dbot.storage.init.atActive: Failed to create current state directory \"" .. currentDir .. "\"") if (retval ~= DRL_RET_SUCCESS) then
dbot.warn("dbot.storage.init.atActive: Failed to create current state directory \"" .. currentDir .. "\"")
end -- if
dbot.spinUntilExists(currentDir, 1)
return DRL_RET_SUCCESS return DRL_RET_SUCCESS
@ -16923,16 +17104,19 @@ function dbot.storage.saveTable(fileName, tableName, theTable, doForceSave)
end -- if end -- if
local shortName = string.gsub(fileName, ".*\\", "") local shortName = string.gsub(fileName, ".*\\", "")
--dbot.debug("dbot.storage.saveTable: Saving \"@G" .. shortName .. "@W\"") dbot.debug("dbot.storage.saveTable: Saving \"@G" .. shortName .. "@W\"")
local fileData = "\n" .. serialize.save(tableName, theTable) local fileData = "\n" .. serialize.save(tableName, theTable)
local fileHash = utils.hash((fileData or "") .. dbot.storage.fileVersion) local fileHash = utils.hash((fileData or "") .. dbot.storage.fileVersion)
local f = assert(io.open(fileName, "w+")) local f, errString, errNum = io.open(fileName, "w+")
if (f == nil) then
assert(f:write(dbot.storage.fileVersion .. "\n", fileHash, fileData)) dbot.warn("dbot.storage.saveTable: Failed to save file: @R" .. (errString or "unknown error") .. "@W")
assert(f:flush()) else
assert(f:close()) assert(f:write(dbot.storage.fileVersion .. "\n", fileHash, fileData))
assert(f:flush())
assert(f:close())
end -- if
return retval return retval
end -- dbot.storage.saveTable end -- dbot.storage.saveTable
@ -16988,7 +17172,6 @@ end -- dbot.storage.loadTable
-- auto1-[date] -- auto1-[date]
-- auto2-[date] -- auto2-[date]
-- auto3-[date] -- auto3-[date]
-- auto4-[date]
-- [name]-[date] -- [name]-[date]
-- --
-- Functions: -- Functions:
@ -17029,8 +17212,13 @@ function dbot.backup.init.atActive()
local backupDir = dbot.backup.getBackupDir() local backupDir = dbot.backup.getBackupDir()
dbot.debug("dbot.backup.init.atActive: backupDir=\"" .. backupDir .. "\"") dbot.debug("dbot.backup.init.atActive: backupDir=\"" .. backupDir .. "\"")
assert(os.execute("if not exist \"" .. backupDir .. "\" mkdir \"" .. backupDir .. "\" > nul"),
"dbot.backup.init.atActive: Failed to create backup directory \"" .. backupDir .. "\"") retval = dbot.shell("if not exist \"" .. backupDir .. "\" mkdir \"" .. backupDir .. "\" > nul")
if (retval ~= DRL_RET_SUCCESS) then
dbot.warn("dbot.backup.init.atActive: Failed to create backup directory \"" .. backupDir .. "\"")
return retval
end -- if
dbot.spinUntilExists(backupDir, 1)
-- Add a backup timer to periodically back up the plugin state. We keep the timer running -- Add a backup timer to periodically back up the plugin state. We keep the timer running
-- even if automatic backups are currently disabled. The dbot.backup.current() function -- even if automatic backups are currently disabled. The dbot.backup.current() function
@ -17074,25 +17262,35 @@ end -- dbot.backup.getBackupDir
-- Returns an array of backup directory names -- Returns an array of backup directory names
function dbot.backup.getBackups() function dbot.backup.getBackups()
local backupNames = {} local backupNames = {}
local backupDir, retval = dbot.backup.getBackupDir() local backupDir, retval = dbot.backup.getBackupDir()
if (retval ~= DRL_RET_SUCCESS) then if (retval ~= DRL_RET_SUCCESS) then
dbot.warn("dbot.backup.getBackups: Failed to get backup directory: " .. dbot.retval.getString(retval)) dbot.warn("dbot.backup.getBackups: Failed to get backup directory: " .. dbot.retval.getString(retval))
return backupNames, retval return backupNames, retval
end -- if end -- if
local tmpFile = backupDir .. "temp.txt" -- Read the backup directory. We use the unix-style pathname because the utils.readdir()
dbot.debug("CLI: " .. "dir /s /b /o:n /a:d \"" .. backupDir .. "\" > \"" .. tmpFile .. "\"") -- function won't take the windows-style path. Yeah, I know that seems crazy. I'm probably
assert(os.execute("dir /s /b /o:n /a:d \"" .. backupDir .. "\" > \"" .. tmpFile .. "\"")) -- doing something silly that prevents it from working. The unix-style paths aren't too evil
-- as a work-around though.
local dirQuery = string.gsub(backupDir, "\\", "/") .. "*"
local backDirTable, error = utils.readdir(dirQuery)
if (backDirTable == nil) then
return backupNames, DRL_RET_MISSING_ENTRY
end -- if
for dirName in io.lines(tmpFile) do -- Loop through every directory entry and pull out all of the directories that have the
local fullName = string.gsub(dirName, "^.*backup.*\\", "") or "" -- [name]-[timestamp] format. Those are our backup candidates.
local baseName, baseTime for backName, backEntry in pairs(backDirTable) do
_, _, baseName, baseTime = string.find(fullName, "(.*)-(%d+)$") local _, _, baseName, baseTime = string.find(backName, "(.*)-(%d+)$")
baseName = baseName or "No name available"
baseTime = tonumber(baseTime or 0) baseTime = tonumber(baseTime or 0)
table.insert(backupNames, if (baseName ~= nil) and (backEntry.directory ~= nil) and (backEntry.directory) then
{ dirName = dirName, fullName = fullName, baseName = baseName, baseTime = baseTime }) table.insert(backupNames, { dirName = backupDir .. backName,
fullName = backName,
baseName = baseName,
baseTime = baseTime })
end -- if
end -- for end -- for
-- Sort the backups by date from most recent to oldest -- Sort the backups by date from most recent to oldest
@ -17100,8 +17298,6 @@ function dbot.backup.getBackups()
table.sort(backupNames, function (back1, back2) return back1.baseTime > back2.baseTime end) table.sort(backupNames, function (back1, back2) return back1.baseTime > back2.baseTime end)
end -- if end -- if
assert(os.remove(tmpFile))
return backupNames, retval return backupNames, retval
end -- dbot.backup.getBackups end -- dbot.backup.getBackups
@ -17125,12 +17321,13 @@ function dbot.backup.getFile(name)
end -- dbot.backup.getFile(name) end -- dbot.backup.getFile(name)
-- The automatic backup scheme: auto --> auto2 --> auto3 --> auto4 -- The automatic backup scheme: auto --> auto2 --> auto3
function dbot.backup.current() function dbot.backup.current()
local retval local retval
local backupFile local backupFile
local backupDir
local autoPrefix = "auto" local autoPrefix = "auto"
local maxNumAutoBackups = 4 local maxNumAutoBackups = 3
local newestBackupName = autoPrefix local newestBackupName = autoPrefix
local oldestBackupName = autoPrefix .. maxNumAutoBackups local oldestBackupName = autoPrefix .. maxNumAutoBackups
@ -17149,11 +17346,17 @@ function dbot.backup.current()
return DRL_RET_IN_COMBAT return DRL_RET_IN_COMBAT
end -- if end -- if
backupDir, retval = dbot.backup.getBackupDir()
if (retval ~= DRL_RET_SUCCESS) then
dbot.warn("dbot.backup.current: Failed to get backup directory: " .. dbot.retval.getString(retval))
return retval
end -- if
-- Check if the newest backup was made today. If it was, update it with the current data, leave -- Check if the newest backup was made today. If it was, update it with the current data, leave
-- the other backups alone, and return. Otherwise, rotate the backups down one slot chronologically. -- the other backups alone, and return. Otherwise, rotate the backups down one slot chronologically.
backupFile, retval = dbot.backup.getFile(newestBackupName) backupFile, retval = dbot.backup.getFile(newestBackupName)
if (backupFile ~= nil) then if (backupFile ~= nil) then
if (os.date("%x", os.time()) == os.date("%x", backupFile.baseTime)) then if (os.date("%x", dbot.getTime()) == os.date("%x", backupFile.baseTime)) then
retval = dbot.backup.create(newestBackupName, nil) retval = dbot.backup.create(newestBackupName, nil)
if (retval ~= DRL_RET_SUCCESS) then if (retval ~= DRL_RET_SUCCESS) then
dbot.warn("dbot.backup.current: Failed to create newest automatic backup \"@G" .. newestBackupName .. dbot.warn("dbot.backup.current: Failed to create newest automatic backup \"@G" .. newestBackupName ..
@ -17193,7 +17396,13 @@ function dbot.backup.current()
fullOlderBackup = string.gsub(fullOlderBackup, ".*\\", "") fullOlderBackup = string.gsub(fullOlderBackup, ".*\\", "")
dbot.debug("CLI: " .. "rename \"" .. backupFile.dirName .. "\" \"" .. fullOlderBackup .. "\" > nul") dbot.debug("CLI: " .. "rename \"" .. backupFile.dirName .. "\" \"" .. fullOlderBackup .. "\" > nul")
assert(os.execute("rename \"" .. backupFile.dirName .. "\" \"" .. fullOlderBackup .. "\" > nul")) dbot.shell("rename \"" .. backupFile.dirName .. "\" \"" .. fullOlderBackup .. "\" > nul")
-- Shell commands running in the background aren't guaranteed to complete in the order
-- they were made. As a result, we spin here until we know that the backup was actually
-- renamed before we move on to the next backup.
dbot.spinUntilExistsBusy(backupDir .. fullOlderBackup, 2)
end -- if end -- if
end -- for end -- for
@ -17280,13 +17489,17 @@ function dbot.backup.create(name, endTag)
end -- if end -- if
-- We append the time to the end of the backup name to help track it -- We append the time to the end of the backup name to help track it
local backupTime = os.time() local backupTime = dbot.getTime()
local newBackupDir = backupDir .. name .. "-" .. backupTime local newBackupDir = backupDir .. name .. "-" .. backupTime
dbot.debug("dbot.backup.create: CLI = \"@y" .. "xcopy /E /I \"" .. currentDir .. "\" \"" .. newBackupDir .. dbot.debug("dbot.backup.create: CLI = \"@y" .. "xcopy /E /I \"" .. currentDir .. "\" \"" .. newBackupDir ..
"\" > nul@W\"") "\" > nul@W\"")
assert(os.execute("xcopy /E /I \"" .. currentDir .. "\" \"" .. newBackupDir .. "\" > nul")) retval = dbot.shell("xcopy /E /I \"" .. currentDir .. "\" \"" .. newBackupDir .. "\" > nul")
if (retval ~= DRL_RET_SUCCESS) then
dbot.info("Created backup @W(@c" .. os.date("%c", backupTime) .. "@W) @G" .. name) dbot.warn("dbot.backup.create: Failed to create backup, xcopy shell failed: " ..
dbot.retval.getString(retval))
else
dbot.info("Created backup @W(@c" .. os.date("%c", backupTime) .. "@W) @G" .. name)
end -- if
return inv.tags.stop(invTagsBackup, endTag, retval) return inv.tags.stop(invTagsBackup, endTag, retval)
end -- dbot.backup.create end -- dbot.backup.create
@ -17311,10 +17524,17 @@ function dbot.backup.delete(name, endTag, isQuiet)
for _, backupName in ipairs(backupNames) do for _, backupName in ipairs(backupNames) do
if (backupName.baseName == name) then if (backupName.baseName == name) then
dbot.debug("dbot.backup.delete: Executing \"rmdir /s /q \"" .. backupName.dirName .. "\"\"") dbot.debug("dbot.backup.delete: Executing \"rmdir /s /q \"" .. backupName.dirName .. "\"\"")
assert(os.execute("rmdir /s /q \"" .. backupName.dirName .. "\" > nul")) dbot.shell("rmdir /s /q \"" .. backupName.dirName .. "\" > nul")
if (isQuiet == false) then retval = dbot.spinWhileExistsBusy(backupName.dirName, 2)
dbot.info("Deleted backup @W(@c" .. os.date("%c", backupName.baseTime) .. if (retval ~= DRL_RET_SUCCESS) then
"@W) @G" .. backupName.baseName) dbot.warn("dbot.backup.delete: Failed to delete backup \"@G" .. name .. "@W\": " ..
dbot.retval.getString(retval))
break
else
if (isQuiet == false) then
dbot.info("Deleted backup @W(@c" .. os.date("%c", backupName.baseTime) ..
"@W) @G" .. backupName.baseName)
end -- if
end -- if end -- if
numBackupsDeleted = numBackupsDeleted + 1 numBackupsDeleted = numBackupsDeleted + 1
end -- if end -- if
@ -17328,6 +17548,7 @@ function dbot.backup.delete(name, endTag, isQuiet)
end -- dbot.backup.delete end -- dbot.backup.delete
dbot.backup.restorePkg = nil
function dbot.backup.restore(name, endTag) function dbot.backup.restore(name, endTag)
local retval = DRL_RET_SUCCESS local retval = DRL_RET_SUCCESS
@ -17336,15 +17557,41 @@ function dbot.backup.restore(name, endTag)
return inv.tags.stop(invTagsBackup, endTag, DRL_RET_INVALID_PARAM) return inv.tags.stop(invTagsBackup, endTag, DRL_RET_INVALID_PARAM)
end -- if end -- if
if (dbot.backup.restorePkg ~= nil) then
dbot.info("Skipping backup restore request: another restore is in progress")
return inv.tags.stop(invTagsBackup, endTag, DRL_RET_BUSY)
end -- if
dbot.backup.restorePkg = {}
dbot.backup.restorePkg.name = name
dbot.backup.restorePkg.endTag = endTag
wait.make(dbot.backup.restoreCR)
return retval
end -- dbot.backup.restore
function dbot.backup.restoreCR()
if (dbot.backup.restorePkg == nil) then
dbot.warn("dbot.backup.restoreCR: restore package is nil!?!?")
return inv.tags.stop(invTagsBackup, endTag, DRL_RET_INTERNAL_ERROR)
end -- if
local name = dbot.backup.restorePkg.name
local endTag = dbot.backup.restorePkg.endTag
local backupNames, retval = dbot.backup.getBackups() local backupNames, retval = dbot.backup.getBackups()
if (retval ~= DRL_RET_SUCCESS) then if (retval ~= DRL_RET_SUCCESS) then
dbot.warn("dbot.backup.restore: Failed to get backup list: " .. dbot.retval.getString(retval)) dbot.warn("dbot.backup.restore: Failed to get backup list: " .. dbot.retval.getString(retval))
dbot.backup.restorePkg = nil
return inv.tags.stop(invTagsBackup, endTag, retval) return inv.tags.stop(invTagsBackup, endTag, retval)
end -- if end -- if
local currentDir, retval = dbot.backup.getCurrentDir() local currentDir, retval = dbot.backup.getCurrentDir()
if (retval ~= DRL_RET_SUCCESS) then if (retval ~= DRL_RET_SUCCESS) then
dbot.warn("dbot.backup.restore: Failed to get current directory: " .. dbot.retval.getString(retval)) dbot.warn("dbot.backup.restore: Failed to get current directory: " .. dbot.retval.getString(retval))
dbot.backup.restorePkg = nil
return inv.tags.stop(invTagsBackup, endTag, retval) return inv.tags.stop(invTagsBackup, endTag, retval)
end -- if end -- if
currentDir = string.gsub(currentDir, "\\$", "") -- Some versions of xcopy hate if there is a trailing slash currentDir = string.gsub(currentDir, "\\$", "") -- Some versions of xcopy hate if there is a trailing slash
@ -17355,10 +17602,13 @@ function dbot.backup.restore(name, endTag)
if (backupName.baseName == name) then if (backupName.baseName == name) then
dbot.info("Restoring backup @W(@c" .. os.date("%c", backupName.baseTime) .. dbot.info("Restoring backup @W(@c" .. os.date("%c", backupName.baseTime) ..
"@W) @G" .. backupName.baseName) "@W) @G" .. backupName.baseName)
assert(os.execute("rmdir /s /q \"" .. currentDir .. "\" > nul")) dbot.shell("rmdir /s /q \"" .. currentDir .. "\" > nul")
assert(os.execute("xcopy /E /I \"" .. backupName.dirName .. "\" \"" .. currentDir .. "\" > nul")) dbot.spinWhileExists(currentDir, 5) -- Spin for up to 5 seconds waiting for confirmation it is gone
dbot.debug("dbot.backup.restore: \"@y" .. "xcopy /E /I \"" .. backupName.dirName .. "\" \"" .. dbot.debug("dbot.backup.restore: \"@y" .. "xcopy /E /I \"" .. backupName.dirName .. "\" \"" ..
currentDir .. "\"@W\"") currentDir .. "\"@W\"")
dbot.shell("xcopy /E /I \"" .. backupName.dirName .. "\" \"" .. currentDir .. "\" > nul")
dbot.spinUntilExists(currentDir, 5) -- Spin for up to 5 seconds waiting for confirmation it is there
-- We want to re-init everything to pick up the restored state. We don't want to save the -- We want to re-init everything to pick up the restored state. We don't want to save the
-- current state which will be overwritten. -- current state which will be overwritten.
@ -17377,8 +17627,10 @@ function dbot.backup.restore(name, endTag)
retval = DRL_RET_MISSING_ENTRY retval = DRL_RET_MISSING_ENTRY
end -- if end -- if
dbot.backup.restorePkg = nil
return inv.tags.stop(invTagsBackup, endTag, retval) return inv.tags.stop(invTagsBackup, endTag, retval)
end -- dbot.backup.restore end -- dbot.backup.restoreCR
---------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------

Loading…
Cancel
Save