Page 1 of 1

git Updater

Posted: Sun Jan 12, 2020 10:41 pm
by davidwiththenicehat
Intended for clients that do not have download support over telnet.
Allows support for uninstalling than installing mudlet xml packages and .mpackage data packages.
This does not require that you compress your project into one mpackage. It supports downloading multiple xml files through github. Including images and other resources in .mpackages.
However it does not support an .mpackage with multiple packages within it. .mpackages can only contain data, like images.

Usage:
There are two attachments intended as examples.
in function `GitUpdateconfig()` there is a section labeled `--edit below this line-` and `--edit above this line` Configurations there need to be filled out.
Running the update:
If you do not provide need to provide an option for player to update.
set `GitUpdate.repairInstall` to `true`. Call `GitUpdateconfig()` from what method you prefer, alias, event or any other method.
Providing an option for player to update.
call `DownloadGitReleaseJSON()` from an alias, cecholink, repeating temp timer or any other method. The git updater will check if the string in GitUpdate.localVersion matches the most recent tag in your github release. If it does not it will prompt the player to run an update via an alias. You can set the alias name in `GitUpdateconfig()` though it does require you make an alias. If you want to provide an option for the player to update via command. If not you could set `GitUpdate.aliasName = "click here"` As it will provide a cecholink for the player to click. If you do make an aliase all it needs to do is call `DownloadGitScripts()`. After that the update will process.

It requires a github site. A working example of a project that uses this can be found at: https://github.com/TheEternalCitizens/m ... ntegration
With each new release the player will receive an message to update, or could automatically update. If the player has the `GitUpdate.betaTester` set to true, they will receive an update notice for pre releases also. Currently this only allows for running the latest release or prerelease of a project.

Example of a working configuration. Also may be a decent reference for a github page.

Code: Select all

  --API URL for git hub for project
  GitUpdate.gitReleaseURL = [[https://api.github.com/repos/TheEternalCitizens/mudlet-integration/releases]]
  GitUpdate.localRespitory = getMudletHomeDir().."/settings/" --local directory for source files
  if not io.exists(GitUpdate.localRespitory) then --if folder does not exists
    lfs.mkdir(GitUpdate.localRespitory) --create the folder
    debugToDisplay("Update: ERROR localRespitory was not present. Has been created.")
  end --if GitUpdate.localRespitory not exist
  GitUpdate.gitReleaseURLJSONFile = "ParthiaLatest.json" --local file name REST API latest JSON
  GitUpdate.localVersion = "0.1.21" --Must match the tag in your github release
  GitUpdate.aliasName = "tecclient update" --Name of alias user runs to process update
  --Name of the alias to check if an update is available.
  GitUpdate.updateCheckAlias = "tecclient update check" 
  --The full name or a word in the file that this update script is in.
  GitUpdate.updateFileNameReference = "Scripts"
  --notify update engine if player is a beta tester. If the player is a beta
  --tester they will receive update notices for pre-releases for your project
  GitUpdate.betaTester = tecSettings.betaTester 
  --packages that are no longer used in the project and should be removed.
  --They can not share a name with a package that you need. Otherwise this will uninstall it.
  GitUpdate.removePackages = {"TECClientScripts", "TECClientTriggers",
    "TECClientAliases", "TECClientKeyBindings"}
  GitUpdate.removeModules = {} --modules that are no longer used in the project and should be removed.
  --Used to tell player correct installation method if they installed scripts as modules
  GitUpdate.installationURL = "https://github.com/TheEternalCitizens/mudlet-integration/wiki/Installation"
  --use in a repair aliase, set this variable to true if a repair install 
  --is needed.
  GitUpdate.repairInstall = fuzzyBoolean(GitUpdate.repairInstall)
  --cecho compatible color, it will hightlight cecholinks in the update process
  GitUpdate.cmdHighlight = "blue"
Copy code here and paste it into the scripts section of your project.
You could also download and install the attached `Git Update Alias.xml` and import it. That does have preconfigured settings in it. The code below does not.

Code: Select all

--Installs updates from github.
--Intended for projects that do not support downloading over telnet
--To allow error checking package file names must contain package type starting with a capitol
--IE: "My Project Name Scripts.xml" or "MyPackageNameTriggers.xml".

--start of section dedicated for functions used by update process.
--To understand the flow of updates you will need to scroll past these.
--There will be a comment stating when update flow starts.

-- fuzzyBoolean Provided by demonnic
-- Expands boolean definitions to be more flexible.
-- True values are "true", "yes", "0", 0, and true
-- False values are "false", "no", "1", 1, false, and nil
-- @param bool item to test for truthiness
if not fuzzyBoolean then
  function fuzzyBoolean(bool)
    if type(bool) == "boolean" or bool == nil then
      return bool
    elseif tostring(bool) then
      local truth = {
        "yes",
        "true",
        "0"
      }
      local untruth = {
        "no",
        "false",
        "1"
      }
      local boolstr = tostring(bool)
      if table.contains(truth, boolstr) then
        return true
      elseif table.contains(untruth, boolstr) then
        return false
      else
        return nil
      end
    else
      return nil
    end
  end
end --if not fuzzyBoolean

function GitUpdateconfig() --Create variables used for updates

  if GitUpdate then 
    --if events were previously created, kill them.
    if GitUpdate.downloadDoneID then
      killAnonymousEventHandler(GitUpdate.downloadDoneID)
    end --if GitUpdate.downloadDoneID
    if GitUpdate.downloadErrorID then
      killAnonymousEventHandler(GitUpdate.downloadErrorID)
    end --if GitUpdate.downloadErrorID
    if GitUpdate.installPackageID then
      killAnonymousEventHandler(GitUpdate.installPackageID)
    end --if GitUpdate.installPackageID
    if GitUpdate.uninstallPackageID then
      killAnonymousEventHandler(GitUpdate.uninstallPackageID) 
    end --if GitUpdate.downloadDoneID
  end --if GitUpdate

  GitUpdate = GitUpdate or {} --table to hold project variables

--Edit below this line---------------------
  --API URL for git hub for project
  GitUpdate.gitReleaseURL = [[https://api.github.com/repos/yourproject/releases]]
  GitUpdate.localRespitory = getMudletHomeDir().."/settings/" --local directory for source files
  if not io.exists(GitUpdate.localRespitory) then --if folder does not exists
    lfs.mkdir(GitUpdate.localRespitory) --create the folder
    debugToDisplay("Update: ERROR localRespitory was not present. Has been created.")
  end --if GitUpdate.localRespitory not exist
  GitUpdate.gitReleaseURLJSONFile = "Project.json" --local file name REST API latest JSON
  GitUpdate.localVersion = "github tag here" --Must match the tag in your github release
  GitUpdate.aliasName = "update command here" --Name of alias user runs to process update
  --Name of the alias to check if an update is available.
  GitUpdate.updateCheckAlias = "check for updates command here" 
  --The full name or a word in the file that this update script is in.
  --Decides what package is installed last
  GitUpdate.updateFileNameReference = "Name of file containing this script"
  --notify update engine if player is a beta tester. If the player is a beta
  --tester they will receive update notices for pre-releases for your project
  GitUpdate.betaTester = false
  --packages that are no longer used in the project and should be removed.
  --They can not share a name with a package that you need. Otherwise this will uninstall it.
  GitUpdate.removePackages = {} --can be empty
  --modules that are no longer used in the project and should be removed.
  GitUpdate.removeModules = {}  --can be empty
  --Used to tell player correct installation method if they installed scripts as modules
  GitUpdate.installationURL = "https://github.com/yourproject/wiki/Installation"
  --cecho compatible color, it will hightlight cecholinks in the update process
  GitUpdate.cmdHighlight = "blue"
  --use in a repair aliase, set this variable to true if a repair install is needed.
  --no need to change this, here to show it exists.
  GitUpdate.repairInstall = fuzzyBoolean(GitUpdate.repairInstall)
--Edit above this line---------------------

  --No need to change variables below
  GitUpdate.filePattern = ".*%/(.*%..*)$" --string pattern to detect file name at end of url or file path string
  GitUpdate.downloadError = false --Sets to true if there is a download error.
  GitUpdate.installationError = false --sets to true if there is a package installation error.
  GitUpdate.installationInProgress = false --notifies update engine if update is running.
  GitUpdate.releaseIndex = 0 --Position on proper update in gitHub JSON table
  GitUpdate.trackedURLs = {} --will be URLs that need to have downloads tracked
  --create event handlers.
  GitUpdate.downloadDoneID = registerAnonymousEventHandler("sysDownloadDone", "GitUpdateEventHandler")
  GitUpdate.downloadErrorID = registerAnonymousEventHandler("sysDownloadError", "GitUpdateEventHandler")
  GitUpdate.installPackageID = registerAnonymousEventHandler("sysInstallPackage", "GitUpdateEventHandler")
  GitUpdate.uninstallPackageID = registerAnonymousEventHandler("sysUninstallPackage", "GitUpdateEventHandler")
  trackedDownloads = {} --leave blank, needed for events. Is initialized in function trackDownloads
  trackedDownloads.fileList = "" --leave blank, needed for events. Initialized in function trackDownloads
  trackedDownloads.urlList = "" --leave blank, used to check for download errors.
  
  debugToDisplay("function GitUpdate.config: Initialized update engine.\n\tgitReleaseURL: "..GitUpdate.gitReleaseURL
    .."\n\tScriptFileNameReference: "..GitUpdate.updateFileNameReference
    .."\n\tlocalRespitory: "..GitUpdate.localRespitory
    .."\n\tgitReleaseURLJSONFile: "..GitUpdate.gitReleaseURLJSONFile
    .."\n\tLocal Parthia version: "..GitUpdate.localVersion)
end --function GitUpdateconfig()

--displays messages to debugc.
--can be changed to send to what ever console you would like with cecho
--if not debugToDisplay then 
  function debugToDisplay(textMessage)
  		debugc(textMessage) -- print message to screen
  end --end displayToDebug function
--end --if not debugToDisplay

--Displays a yellow notice to debug console
if not tecWarningNotification then
  --fully notifies user when an error is found.
  function tecWarningNotification(errorFound)
    debugc("Warning: "..errorFound)
  end --function tecErrorNotification
end --if not tecWarningNotification then

if not ReplaceStringMagicChar then
  --used to replace magic characters in a string with the proper escape character 
  --sequence. Useful for replacing file names on sysDownload events.
  function ReplaceStringMagicChar(tmpString)
    tmpString = tmpString:gsub("%%", "%%%")
    tmpString = tmpString:gsub("%(", "%%(")
    tmpString = tmpString:gsub("%)", "%%)")
    tmpString = tmpString:gsub("%.", "%%.")
    tmpString = tmpString:gsub("%+", "%%+")
    tmpString = tmpString:gsub("%[", "%%[")
    tmpString = tmpString:gsub("%^", "%%^")
    tmpString = tmpString:gsub("%$", "%%$")
    tmpString = tmpString:gsub("%-", "%%-")
    tmpString = tmpString:gsub("%*", "%%*")
    tmpString = tmpString:gsub("%?", "%%?")
  return tmpString
  end --function ReplaceStringMagicChar(tmpString)
end --if not ReplaceStringMagicChar

--displays a message to player that an error was found, and to contact a developer
if not tecErrorNotification then 
  function tecErrorNotification(errorFound)
    debugc("Error: "..errorFound)
    cecho("<red:>Error: "..errorFound.."\n\tPlease contact a developer.")
  end --function tecErrorNotification
end --if not tecErrorNotification

--Used to easily change update notification. There are multiple instances where
--Parthia notifies player an update is available.
local function updateAvailable(updateBody)
  --if this is a repair install. Download and install without prompting player.
  if GitUpdate.repairInstall then 
    --Start downloading scripts as a temptimer so the function that called
    --updateAvailable can complete.
    tempTimer(1, "DownloadGitScripts()")
    GitUpdate.repairInstall = false --reset so it will not run if player checks for updates
  else --this is not a repair install prompt the user if they would like to update.
    enableAlias(GitUpdate.aliasName) --enable alias so user can run update with command
    cecho("An update is available. The new features it offers are: \n\t")
    cecho(updateBody)
    cecho("\n<:maroon>To update now, please run: ")
    cechoLink("<:blue>"..GitUpdate.aliasName, --link text to display
      [[expandAlias(GitUpdate.aliasName)]], --link command to run
      "Update game client", true) --link tool tip for mouse hover
    echo("\n") --cechoLinks don't cause the window to autoscroll.
  end --if GitUpdate.repairInstall
end --function updateAvailable

--gather urls from github release that player needs to update to
local function collectURLs()
  if GitUpdate.releaseIndex == 0 then --make certain releaseIndex has been initialized
    debugc("function: collectURL, url collection releaseIndex was not initialized.")
    return "function: compareVersions, url collection releaseIndex was not initialized."
  elseif GitUpdate.gitJSONTable[GitUpdate.releaseIndex].assets.browser_download_url then
    debugc("function: collectURL, url collection JSONTable lacks assets.")
    return "function: compareVersions, url collection JSONTable lacks assets."
  else
    for index, gitAsset in --loop through git release assets.
      ipairs(GitUpdate.gitJSONTable[GitUpdate.releaseIndex].assets) do
        --save URL needing download to a new table.
        --debugc("function: compareVersions tracking URL: "..gitAsset.browser_download_url)
        GitUpdate.trackedURLs[index] = gitAsset.browser_download_url
    end --for GitUpdate.gitJSONTable[GitUpdate.releaseIndex].assets
    debugToDisplay("function collectURLs(): GitUpdate.trackedURLs initialized.") 
  end --if releaseIndex == 0
end --collectURLS

--Search for latest nonbeta release or release that is NOT a prerelease
--function is here to reduce complexity of function that calls it.
local function releaseSearch()
  if GitUpdate.gitJSONTable[1].name then --if the table exists
    --debugc("gitUpdate release search, JSON table exists.")
    for index, releaseData in ipairs(GitUpdate.gitJSONTable) do --loop through the JSON table
      --debugc("gitUpdate release search, searching index: "..tostring(index))
      debugToDisplay("function releaseSearch, searching index: "..tostring(index))
      --Find the latest release that is NOT a prerelease.
      if not releaseData.prerelease then --if the release is not a prerelease
        --debugc("gitUpdate release search, found latest release at: "..tostring(index))
        debugToDisplay("function releaseSearch, found latest release at: "..tostring(index)
          .."\n\tRelease tab_name: "..releaseData.tag_name
          .." Local release: "..GitUpdate.localVersion)
        --If the installed Parthia is different that latest release
        if releaseData.tag_name ~= GitUpdate.localVersion then
          GitUpdate.releaseIndex = index --collect the release index
          local errorFound collectURLs() --gather URLs into trackedURLs table
          if errorFound then --collectURLs returns a string if error occured.
            return errorFound
          else
            updateAvailable(releaseData.body) --notify player an update is available
            return
          end --if errorFound else
        else --if releaseData.tag_name ~= GitUpdate.localVersion, Parthia up to date.
          cecho("Parthia is up to date.\n")
          return --end releaseSearch()
        end --if releaseData.prerelease.tag_name ~= GitUpdate.localVersion
      end --if releaseData.prerelease == "false"
    end --for
  else --if the JSON table does NOT exist.
    errorFound = "function releaseSearch, Error, JSON table not found during release search."
    return errorFound--error found stop update.
  end --if GitUpdate.gitJSONTable[1].name exists
end --function releaseSearch

--Tracks downloads so updates do not start until all downloads have completed.
--This has been created to allow it to be module.
--downloadURL is a table containing a urls that need to be downloaded.
--!!!trackDownloads(downloadURLs) HAS to be ran BEFORE the downloads are initiated!!!
function trackDownloads(downloadURLs)  
  if downloadURLs[1] then --if downloadURLs is a table
    debugToDisplay("function trackDownloads, table passed.")
    --move the URL containing the script name to the end of the table.
    for index, urlToTrack in ipairs(downloadURLs) do
      if urlToTrack:match(GitUpdate.updateFileNameReference) then
        debugToDisplay("function trackDownloads, URL containing update script was found at\n\t"
          ..urlToTrack.." moving it to end of table.")
        table.remove(downloadURLs, index) --remove the URL from the table
        table.insert(downloadURLs, urlToTrack) --insert it on the end of the table
        break --Update script can only be in one file.
      end --urlToTrack:match(GitUpdate.updateFileNameReference)
    end --for downloadURLs
    --Make a global table to store URLs that require tracking.
    trackedDownloads = {files = {}, URLs = {}, downloaded = {}, fileList = "", tmpFiles = {}
                        , urlList = ""}
    for index, urlToTrack in ipairs(downloadURLs) do --initialize trackedDownloads table
      --Get the name of the file you need to download.
      local TmpPackageFileName = urlToTrack:match(GitUpdate.filePattern)
      --debugc("TmpPackageFileName "..TmpPackageFileName)
      --Collect package name removing any .s periods that are in it.
      --in git releases spaces in filenames are replaced with .s mudlets install package process
      --is sometimes not compatible with this.
      local PackageNameNoExtension = string.gsub(TmpPackageFileName:match("(.*)%..*$"), "%.", " ") 
      --debugc("PackageNameNoExtension: "..PackageNameNoExtension)
      local PackageNameExtensionOnly = TmpPackageFileName:match(".*(%..*)$")
      --debugc("PackageNameExtensionOnly: "..PackageNameExtensionOnly)
      --Combine the package name with file extension.
      trackedDownloads.files[index] = PackageNameNoExtension..PackageNameExtensionOnly
      debugToDisplay("function: trackDownloads tracking file: "..trackedDownloads.files[index])
      --Boolean to notify client when download has completed.
      trackedDownloads.downloaded[index] = false
      --Get the name of the file you need to download.
      trackedDownloads.URLs[index] = urlToTrack
      --create a string that contains a list of files needing download.
      --This list will be used during sysDownloadDone event to veirfy the correct download
      trackedDownloads.fileList = trackedDownloads.fileList..trackedDownloads.files[index].." "
      --Used to catch download errors.
      trackedDownloads.urlList = trackedDownloads.urlList..urlToTrack.." "
    end --for downloadURLs

  elseif type(downloadURLs) == "string" then --if the variable passed is a string
    debugToDisplay("function: trackDownloads string passed.")
    debugToDisplay("function trackedDownloads: URL passed: "..downloadURLs)  
    if trackedDownloads.files[1] then --make certain the trackedDownloads table exists
      --Look through trackedDownloads table to see if this is one of the tracked URLs
      for index, trackedDownload in ipairs(trackedDownloads.files) do      
        --if the string passed matches a file name in trackedDownloads
        if trackedDownload:match(downloadURLs) then
          debugToDisplay("function trackedDownload: download completed and recorded for: "
            ..trackedDownload)
          trackedDownloads.downloaded[index] = true --record that this URL downloaded successfully.
            --check if all downloads have completed.
            for index, urlDownloaded in ipairs(trackedDownloads.downloaded) do
              if urlDownloaded then --has the current URL affiliaed with the bool been downloaded.
                --debugc("function trackedDownloads: Verifing download completed for index "..index)
                --If all downloaded booleans in table are true.
                if index == #trackedDownloads.downloaded then
                  debugToDisplay("function trackedDownloads: downloads completed.")
                  return true --tells parthia all downloads are complete.
                end --if index == #trackedDownloads.downloaded
              else --Some file in the set has not been downloaded.
                return false --tells parthia all downloads are not complete
              end --if urlDownloaded
            end --for trackedDownloads.downloaded
          break --break the for loop.
        end --if trackedDownload == downloadURLs
      end --for trackedDownloads
    else
      --display error to debug, and return error message to called function
      errorc("function trackDownloads: was not initialized with a table of URLs.")
      return "function trackDownloads: was not initialized with a table of URLs."
    end --if downloadURLs
  else --argument was not a table or string.
    local errorFound = "function trackDownloads: does not accept "..type(downloadURLs)
      .." as an argument.\n\tOnly supports tables for initilization and strings for tracking."
    errorc(errorFound)
    return errorFound
  end --if downloadURLs type check
end --function trackDownloads

--Here moving forward if you would like to understand the flow of updates
--it starts here.

--downloads the github JSON REST API JSON file
function DownloadGitReleaseJSON()
  --check if an installation is in progress.
  if GitUpdate then if GitUpdate.installationInProgress then
    echo("An update is currently processing. Only one update can run at a time.\n")
    return --stop updates one is processing.
  end end
  GitUpdateconfig() --initialize update engine.
  GitUpdate.installationInProgress = true --notify Parthia an update is in progress
  debugToDisplay("Update: Downloading "..GitUpdate.gitReleaseURL
    .."\n\tTo: "..GitUpdate.localRespitory..GitUpdate.gitReleaseURLJSONFile)
  downloadFile(GitUpdate.localRespitory..GitUpdate.gitReleaseURLJSONFile,
    GitUpdate.gitReleaseURL) --download the projects latest release JSON info file.
  GitUpdate.installationInProgress = false --notify Parthia installation not running.
end --function DownloadGitReleaseJSON

--after the JSON files is downloaded GitUpdateEventHandler will run compareVersions

--check if version in use is the latest released
--If player is beta test we compare the latest release both prerelease and release 
--If standard player we look for latest release not prerelease
local function compareVersions()
  debugToDisplay("function compareVersions, opening & reading file:\n\t"
    ..GitUpdate.localRespitory..GitUpdate.gitReleaseURLJSONFile)
  --open the local github latest release JSON file in read only mode
  local gitJSONFile, errorFound = 
    io.open(GitUpdate.localRespitory..GitUpdate.gitReleaseURLJSONFile, "r")
  if not gitJSONFile then --file was not created error occured.
    tecErrorNotification("function compareVersions: "..errorFound)
    gitJSONFile:close() --incase of partial failure, close file.
    gitJSONFile = nil
    GitUpdate.installationInProgress = false --notify Parthia installation not running.
    return  --error found stop update.
  end --if not gitJSONFile
  local gitJSONString = gitJSONFile:read("*a") --read the entire file to a string
  gitJSONFile:close() --close the file
  gitJSONFile = nil
  if not gitJSONString then --the file did not read correctly.
    tecErrorNotification("function compareVersions, file read failure.\n\t"
      ..GitUpdate.localRespitory..GitUpdate.gitReleaseURLJSONFile)
    GitUpdate.installationInProgress = false --notify Parthia installation not running.
    return --error found stop update.
  end --if not gitJSONString
  --Convert github release JSON into a table
  GitUpdate.gitJSONTable = yajl.to_value(gitJSONString)
  if GitUpdate.gitJSONTable[1].name then --if the table was made.
    debugToDisplay("function compareVersions, file read successful.")
  else --if the table was not successfully made we can not continue. Notify player.
    errorFound = "Update: Error, JSON to table conversion unsuccessfull.\n\tOpened file: "
      ..GitUpdate.localRespitory..GitUpdate.gitReleaseURLJSONFile
      .."\n\tIt contained: "..gitJSONString
    tecErrorNotification(errorFound) --Notify player provide method to report error
    GitUpdate.installationInProgress = false --notify Parthia installation not running.
    return --error found stop update.
  end --GitUpdate.gitJSONTable exists
  
  --Look the table for the latest non beta release.
  if GitUpdate.betaTester then --if player is a beta tester install latest release   
    --if latest release tag_name does not match local version number.
    debugToDisplay("Update: Release installed: "..GitUpdate.localVersion
      .."\n\tLatest release: "..GitUpdate.gitJSONTable[1].tag_name)
    if GitUpdate.gitJSONTable[1].tag_name ~= GitUpdate.localVersion then
      GitUpdate.releaseIndex = 1 --notify update engine which version to use
      updateAvailable(GitUpdate.gitJSONTable[1].body) --notify player update is available
      local errorFound collectURLs() --gather URLs into trackedURLs table
      if errorFound then
        tecErrorNotification(errorFound)
        GitUpdate.installationInProgress = false --notify Parthia installation not running.
        return --error found stop update.
      end --if error found
    else
      cecho("Thank you for beta testing!\n\tNo new releases at this time.")
    end --if beta tester is not using latest release
  else --The user is not a beta tester.
    local errorFound = releaseSearch() --search for latest NONbeta release
    if errorFound then
      tecErrorNotification(errorFound)
      GitUpdate.installationInProgress = false --notify Parthia installation not running.
      return --error found stop update.
    end --if error found
  end --if betaTester

  --We do not know how large the git release JSON is we will trash it just incase
  --doing this after error catch, if error is found this table will still exist
  GitUpdate.gitJSONTable = nil
  
  GitUpdate.installationInProgress = false --notify Parthia installation not running.
  
end --function compareVersions

--The user has now run the aliase GitUpdate.aliasName to continue forward.

--Download the scripts in the latest release 
function DownloadGitScripts()
  --turn off the standard update command. So user will not attempt to run it while
  --it is running. Also leave it off so user can not process an update when one 
  --is not required.
  disableAlias(GitUpdate.aliasName)
  GitUpdate.downloadError = false --in case of previous download error.

  --check if an installation is in progress.
  if GitUpdate.installationInProgress then
    echo("An update is currently processing. Please wait for it to complete.\n"
      .."Once the update is done you will need to check if an update is still "
      .."required by running: ")
    cechoLink("<:"..GitUpdate.cmdHighlight..">"..GitUpdate.updateCheckAlias, 
      [[DownloadGitReleaseJSON()]],
      "Check for updates", true)
    return
  end
  GitUpdate.installationInProgress = true --notify Parthia an update is in progress

  --if GitUpdate.trackedURLs has been initialized.
  if type(GitUpdate.trackedURLs[1]) == "string" then 
    trackDownloads(GitUpdate.trackedURLs) --initialize trackDownloads
  else --GitUpdate.trackedURLs was not initialized throw error.
    errorFound = "function DownloadGitScripts, trackedURLs was not initialized."
    tecErrorNotification(errorFound)
    GitUpdate.installationInProgress = false --notify Parthia installation not running.
    return --stop because we can not proceed.
  end --if GitUpdate.trackedURLs is not nil.

  --download the files required.
  for index, trackedDownload in ipairs(GitUpdate.trackedURLs) do
    debugToDisplay("function DownloadGitScripts, downloading "
      ..trackedDownload.."\n\tto "
      ..GitUpdate.localRespitory..trackedDownloads.files[index])
    --download file
    downloadFile(GitUpdate.localRespitory..trackedDownloads.files[index], trackedDownload)
  end --for GitUpdate.trackedURLs

--[[
  --this code can be used to test trackedDownloads without using sysDownloadDone function
  for index, trackedDownload in ipairs(GitUpdate.trackedURLs) do
    debugToDisplay("function GitUpdate.downloadScripts downloading "
      ..trackedDownload.."\n\tto "
      ..GitUpdate.localFileStore..trackedDownloads.files[index])
    boolOrErrorMessage = trackDownloads(trackedDownloads.files[index])
    local tmpString = GitUpdate.localFileStore..trackedDownloads.files[index]
    cecho("File name: "..tmpString:match(GitUpdate.filePattern).."\n")
    cecho("fileList is: "..trackedDownloads.fileList.."\n")
    cecho("Found match in fileList for file downloaded: "..
      trackedDownloads.fileList:match(tmpString:match(GitUpdate.filePattern)).."\n")
    if boolOrErrorMessage then
      debugToDisplay("Downloads completed!!!")
    end
  end --for GitUpdate.trackedURLs
]]--

end --function GitUpdate.downloadScripts()

--After all files have been downloaded GitUpdateEventHandler will call InstallGitUpdates

local function InstallGitUpdates()
  if GitUpdate.downloadError then --if a download error occured.
    cecho("Download error occured. Update can not be processed.\n")
    GitUpdate.downloadError = false --Reset now that we have caught the error.
  end --if GitUpdate.downloadError
  local localSourceDirectory = GitUpdate.localRespitory --Keep required data outside of GitUpdate
  for index, fileName in ipairs(trackedDownloads.files) do --loop through scripts, and install them
    local packageName = fileName:match("(.*)%.") --remove extension from filename
    local packageTypeList = {"alias", "trigger", "timer", "keybind", "script", 
                             "Alias", "Trigger", "Timer", "Keybind", "Script",
                             "KeyBind"}                               
    for index, packageTypeName in ipairs(packageTypeList) do
      packageType = packageName:match(packageTypeName) --find packageType in the package name
      if packageType then --package name contains a package type. Use it to error check.
        debugToDisplay("function InstallGitUpdates: Package type found, "
          ..packageType..". Error checking will be processed for" ..packageName)
        break --packageType found. Contining this would would nil packageType
      end --if packageType
    end --for ipairs(packageTypeList)

    --if this package is installed as a module. Do not install it.
    if getModulePath(packageName) then
      local errorFound = packageName..", is installed as a module. Having one script installed as "
        .."both a module and a package is not supported."
      tecWarningNotification("function InstallGitUpdates, "..errorFound)
      cecho(errorFound.." If you are not a developer player installation instructions "
        .."can be found ")
      cechoLink("<:"..GitUpdate.cmdHighlight..">here", 
        [[openUrl("]]..GitUpdate.installationURL..[[")]],
        "Player installation site", true)
      cecho("\n")
      
    else --script is not installed as a module already proceed with instalation as package
      uninstallPackage(packageName) --remove old package
      --comment out uninstallPackage and uncomment raiseEvent to test without uninstalling package
      --raiseEvent("sysUninstallPackage", packageName)
      
      if packageType then --package name contains a package type. Use it to error check.
        if 0 ~= exists(packageName, packageType) then --if packagename appears in mudlet
          debugToDisplay("<yellow:>Warning function InstallGitUpdates: appearance of the package "
            ..packageName.." in the "..packageType.." section unexpected.")
        end --if 0 ~= exists(packageName, packageType)
      end --if packageType
      
      installPackage(localSourceDirectory..fileName) --install new package
      --comment out installPackage and uncomment raiseEvent to test without installing package
      --raiseEvent("sysInstallPackage", packageName, localSourceDirectory..fileName)
      
      if packageType then --package name contains a package type. Use it to error check.
        if 0 == exists(packageName, packageType) then --if packageName does not exist in mudlet
          local errorFound = "function InstallGitUpdates: "..packageName.." was not found in "
            .." the "..packageType.." section. After package installation."
          tecErrorNotification(errorFound) --display error.
          GitUpdate.installationError = true --Notify InstallGitUpdates there is an error
        end --if 0 ~= exists(packageName, packageType)
      end --if packageType
    end --if getModulePath(packageName)

  end --for InstallGitUpdates
  
  if GitUpdate.installationError then --there were errors during installation.
    cecho("Errors during installation. Please report issues using the report issues button "
      .."in the settings window.\n")
    GitUpdate.installationInProgress = false --notify Parthia installation not running.
      --if events were previously created, kill them.
    if GitUpdate.downloadDoneID then
      killAnonymousEventHandler(GitUpdate.downloadDoneID)
    end --if GitUpdate.downloadDoneID
    if GitUpdate.downloadErrorID then
      killAnonymousEventHandler(GitUpdate.downloadErrorID)
    end --if GitUpdate.downloadErrorID
    if GitUpdate.installPackageID then
      killAnonymousEventHandler(GitUpdate.installPackageID)
    end --if GitUpdate.installPackageID
    if GitUpdate.uninstallPackageID then
      killAnonymousEventHandler(GitUpdate.uninstallPackageID) 
    end --if GitUpdate.downloadDoneID
    return --stop update process
  end --if GitUpdate.installationError
  
  --go through list of unwanted packages and modules remove them. GitUpdate.removeModules
  for _, removeScriptsTable in pairs({GitUpdate.removePackages, GitUpdate.removeModules}) do
    for _, packageToRemove in ipairs(removeScriptsTable) do
      uninstallPackage(packageToRemove) --uninstall unwanted packages.
      --check for a folder that shares a name with an unwanted package in each section type
      for _, packageType in ipairs( {"alias", "trigger", "timer", "keybind", "script"}) do
        --if the a folder exists with the name of an unwanted package
        if (exists(packageToRemove, packageType) > 0) then 
          tecWarningNotification("function InstallGitUpdates, package "
            ..packageToRemove.." was removed but a folder for it still exits "
            .."in the "..packageType.." section.")
        end --if exists(packageToRemove)
      end --for ipairs( "alias", "trigger", "timer", "keybind", "script")
    end --for ipairs(GitUpdate.removePackages) 
  end --for pairs(GitUpdate.removePackages, GitUpdate.removeModules)
  
  --resetProfile() --reset to profile so UI will properly reset.
  --cecho("Loading display please wait.\n")
  --UIManagerSetTheme("PlayersTheme", false) --reset the display
  debugToDisplay("Updates completed.\n")
  cecho("Updates completed. You may need to restart.\n")
  GitUpdate.installationInProgress = false --notify Parthia installation not running.
    --if events were previously created, kill them.
  if GitUpdate.downloadDoneID then
    killAnonymousEventHandler(GitUpdate.downloadDoneID)
  end --if GitUpdate.downloadDoneID
  if GitUpdate.downloadErrorID then
    killAnonymousEventHandler(GitUpdate.downloadErrorID)
  end --if GitUpdate.downloadErrorID
  if GitUpdate.installPackageID then
    killAnonymousEventHandler(GitUpdate.installPackageID)
  end --if GitUpdate.installPackageID
  if GitUpdate.uninstallPackageID then
    killAnonymousEventHandler(GitUpdate.uninstallPackageID) 
  end --if GitUpdate.downloadDoneID
end --function InstallGitUpdates

function GitUpdateEventHandler(event, ...)
  if event == "sysDownloadDone" then --if it is a downloadDone event.
    --arg[1] is full file including directory
    --ReplaceStringMagicChar puts ecscape % before lua magic characters
    local file = ReplaceStringMagicChar(arg[1]) 
    debugToDisplay("function GitUpdateEventHandler, file downloaded post magic "
      .."character replacement it is:\n\t"..file)
    
    if GitUpdate.gitReleaseURLJSONFile:match(file:match(GitUpdate.filePattern)) then --download is version check file
      debugToDisplay("function GitUpdateEventHandler, Download: "..file
        .."\n\tcompareVersions() called.")
      compareVersions() --Use downloaded JSON file to check if latest version is installed
    --if the name of the file being downloaded is in trackedDownloads.fileList
    elseif trackedDownloads.fileList:match(file:match(GitUpdate.filePattern)) then
      local boolOrErrorMessage = trackDownloads(file:match(GitUpdate.filePattern))
          if boolOrErrorMessage then --if true, all files have downloaded.
            debugToDisplay("function GitUpdateEventHandler, all downloads completed. "
              .."Running function InstallGitUpdates()")
            InstallGitUpdates() --install the downloaded scripts
          elseif type(boolOrErrorMessage) == "string" then --if error found
            tecErrorNotification(boolOrErrorMessage) --notify player of error
          else --all downloads are not completed.
            debugToDisplay("function GitUpdateEventHandler, "..file.." completed downloading. Not all tracked files downloaded yet.")
          end --if boolOrErrorMessage
    else
      debugToDisplay("function GitUpdateEventHandler, Downloaded file: "..file
        .."\n\tWas NOT caught by event handler. If this SHOULD be a tracked download, system "
        .."attempted to track: "..file:match(GitUpdate.filePattern))
    end --elseif file downloaded is new script
  elseif event == "sysDownloadError" then --if there is a download error.
    local errorFound = arg[1] --sysDownloadError only argument is error message.
    local erroredURL = arg[1]:match(".*(https:%/%/%S+)") --retreive URL in error message
    --debugc("Errored URL: "..erroredURL)
    --if  then error download's URL matches downloads for this update engine
    if trackedDownloads.urlList:match(erroredURL) or GitUpdate.gitReleaseURL == erroredURL then
      tecErrorNotification("fuction GitUpdate.downloadErrorEventHandler, "..errorFound)
      GitUpdate.downloadError = true --notify InstallGitUpdates not to proceed.
    else --for any other download errors.
      debugToDisplay("fuction GitUpdate.downloadErrorEventHandler, "..errorFound)
    end --if trackedDownloads.urlList:match(erroredURL)
  elseif event == "sysInstallPackage" then --after a package has installed.
    local localInstalledPackageName = arg[1] --notify 
    local localInstalledPackageFileName = arg[2]
    debugToDisplay("function GitUpdateEventHandler: "..localInstalledPackageName.." package installed from file: "
      ..localInstalledPackageFileName)
  elseif event == "sysUninstallPackage" then --after a package has installed.
    local tecUninstalledPackage = arg[1]
    debugToDisplay("function GitUpdateEventHandler: "..tecUninstalledPackage.." package uninstalled.")
  end --if event name
end --function tecUpdate.eventHandler()
I would be happy to update code or provide further options if someone uses the script.

Re: git Updater

Posted: Tue Jan 14, 2020 9:08 am
by tarkenton
Hilariously, I was literally -just- going to look into this. I haven't played with this yet but am going to start poking it this week. I'll let you know how it works out. Thanks in advance!

Re: git Updater

Posted: Tue Jan 14, 2020 2:19 pm
by davidwiththenicehat
Actually I had heard you were going to look into it. That is why I posted it. I should have given you a heads up.
I need to still do some updates with this. For example the unwanted module uninstaller that uses variable `GitUpdate.removeModules`, I put it in a loop that uses uninstallPackage().
`if not io.exists(GitUpdate.localRespitory) then` is always true.
Let me know what else you find.
As it turns out creating an update engine requires a lot of effort.

I'd like to add muddler support, but I am getting burnt out on this package.