HISE Logo Forum
    • Categories
    • Register
    • Login

    Simple Implementation Example of Auto-Notifying Users of App/Plugin Updates (and Maybe Downloading Them)

    Scheduled Pinned Locked Moved Scripting
    3 Posts 2 Posters 196 Views
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • clevername27C
      clevername27
      last edited by clevername27

      If you're clever, you can use nifty stuff with GIT to fully automate this process.

      I am not. So I offer a less-ambitious implementation.

      This code will (should) run as-is, and I've uploaded the data and payload file to my server, so you can simply cut and paste.

      But it doesn't run correctly - there's something I couldn't quite work out. A fallback mechanism still delivers the download, but it's not optimal. If you can see where I've gone wrong, (or have improvements), please post, and I'll revise.

      Further Reading:

      • HISE's Server class documentation.
      • HISE's Download class documentation.
      • d.sealy's YouTube tutorial on file downloads.
      • Immanuel Kant's Critique of Pure Reason.
      //////////////////////////////////////////////////////
      //                                                   / 
      //              Version Check & Download             /
      //                                                   / 
      //////////////////////////////////////////////////////
      //
      // This function checks to see if there's a newer 
      // version of your app/plugin than the version the 
      // functin is called from. 
      //
      // There are two files. The first is an online data file
      // containing the information about the current
      // version of your app/plugin; the other is the
      // current version of your app/plugin.
      //
      // The data file is of the (non-nested) JSON variety. 
      // It can be generated dynamically. But for ultimate 
      // simplicity, it can simply be a static text file 
      // that you manually edit (whenever you release a new 
      // version of your exciting app/plugin).
      //
      // The data in this example has key/value pairs (both are 
      // strings): the current version ID, and the filename of the
      // zipped app/plugin. You can create as many of these
      // pairs as you like in your data file, for whatever
      // nefarious purpose(s) you intend.
      //
      // We'll use GET instead of POST. With GET, you are
      // exposing your data, publicly. GET is simpler to 
      // implement, though, and there shouldn't be anything in 
      // your data file that you don't want people to see,
      // regardless. Do ensure you drop an index.html file 
      // into any exposed directory.
      //
      // Much of this implementation example is architected 
      // on error-handling, as it's non-Turing-decidable to
      // ensure that any network operation works as it
      // should (or at least as we cab reasonably expect). The 
      // internet is a lawless place, and even when all the fates 
      // align to grant us a functional download, you can reliably 
      // count on the user's operating system to randomly throw up 
      // roadblocks. All we can control is how we respond to these 
      // problems, and aim to adequately inform users so they can
      // solve the problems themselves (instead of emailing us
      // about a problem we probably can't).
      //
      // It's helpful when tutorial code simply works, so
      // we're not making an actual version comparison,
      // and the referenced files are actually online (though not
      // the droids you're looking for).
      //
      // Speaking of which, I wasn't able to get the native download
      // code I wrote here to work on my machine/network/timeline. If
      // you spot the error (or know which Gods to appease), please
      // post a note here on the forum.
      
      // Here's a bunch of global variables. A better implementation 
      // would employ more local scoping. But I'm a lazy, lazy man.
      const var baseUpdateURL = "https://bemhost.net";
      const var dataFileName =  "update_data.json";
      const var downloadDirectory = "/hise_forum/";
      const downloadsFolder = FileSystem.getFolder(FileSystem.Downloads);
      var downloadURL = null;
      var downloadObject = null;
      
      // Base-Level Function.
      inline function checkForAppUpdate() {
      	
      	// Let's first check if we can find an internet connection. Because if we can't...
      	if (Server.isOnline()) {
      		
      		// Clear the Server Controller tile.
      		Server.cleanFinishedDownloads();
      	
      		// Specify the base URL where we're doing business.
      		Server.setBaseURL(baseUpdateURL);
      		
      		// Clear the HTTP header, in case angry ferrets left data there.
      		Server.setHttpHeader("");
      		
      		local updateDataFile = downloadDirectory + dataFileName;
      
      		// This function does two things: One is to access (and parse) your online 
      		// JSON data file; and second, to define the callback that's executed 
      		// when the server responds. The key phrase here is "when", because if 
      		// the server doesn't respond, the callback never rings. But if it does,
      		// there's two variables returned that we'll use: "status" and "response".
      		Server.callWithGET(updateDataFile, {}, function(status, response) {
      
      			// Ask the server if it's ready to mingle.
      			if (status == Server.StatusOK) {
      
      				// Check if both our keys can be found in the online data file before proceeding.
      				if (isDefined(response[0].version) && isDefined(response[0].filename)) { 
      					
      					// Get the current version string.
      					var versionString = response[0].version;
      					
      					// Compare the currently running app/plugin version with the one from the online data file.
      					// The actual comparison here would be: (Engine.getVersion() == versionString), but in the interest 
      					// of making this example more useful, we'll just fire the "update available" condition.
      					if (Engine.getVersion() == versionString) {
      
      						// Get the file name to download from the data file. 
      						reg fileName = response[0].filename;
      						
      						// Here, we'll create the full download URL for our payload file.
      						downloadURL = baseUpdateURL + downloadDirectory + fileName;
      						
      						// Ask the user about downloading the current version with a showYesNoWindow. I'm a 
      						// little disappointed that Christoph didn't also include the quantum-mechanics 
      						// version, showYesNoMaybeWindow. Anyway, a "yes" response executes the download callback.
      						// We want to do something a little tricky here - pass a variable to a callback function, so
      						// that's what the brackets are for around "filename".
      						Engine.showYesNoWindow (	"Update Available", 
      													"An updated version of this tantalising product is available. Do you want to download it?", 
      													function[fileName] () {
      														
      														// Create a file stub. We'll use the handy FileSystem class here to provide a reference
      														// to the user's download folder, and create the stub (as being inside out that folder).													
      														var fileStub = FileSystem.getFolder(FileSystem.Documents).getChildFile(fileName);
      														
      														// Next, we'll create a Download object called...something. The Download class
      														// provides lots of fu house of exciting things to learn and do about the download, itself.
      														var downloadObject = Server.downloadFile (downloadURL, {}, fileStub, DownloadCallback);
      													}
      												);
      												
      					} else {} // The user is running the current version. Nothing to do.	
      				
      				// Couldn't find the key/value pairs in the online data file.					
      				} else Engine.showMessageBox("Auto-update Error", "There was an error while checking for an updated version: Remote version data could not be accessed.", 0);			
      			
      			// Server didn't respond with 200 code.
      			} else Engine.showMessageBox("Auto-update Error", "There was an error while checking for an updated version: The remote server is offline.", 0);
      		});
      		
      	// Internet connection not found.
      	} else Engine.showMessageBox( "Interwebs Not Found","As far as we can tell, you're not connected to the internet. So we can't automatically check if there's a new version of our award-awaiting product.", 0);
      } 
      
      // One of the nice things about HISE's API for handling downloads is that there's many ways you
      // can architect your implementation. I chose to have a giant function to deal with many of the
      // possible problems, and a separate one to deal with the download, itself. Note that this is
      // not an inline function. And also, that there are still things that can go wrong here, even if
      // your code is perfect (unlike mine) - if that's the case, then the problem is likely the
      // OS or the user's doing. Probably. Maybe?
      function DownloadCallback () {
      
      		if (this.data.finished) {
      			
      				// Our download was a success.
      				if (this.data.success) Engine.showMessageBox("Download Complete", "The file has finished downloading.", 0);
      				
      				// This file WILL BE DOWNLOADED!
      				else Engine.openWebsite(downloadURL);
      		}
      }
      
      
      A 1 Reply Last reply Reply Quote 4
      • A
        ally @clevername27
        last edited by

        @clevername27 I think

        downloadURL = baseUpdateURL + downloadDirectory + fileName;
        

        is causing your problem. Server.downloadFile() is looking for a subURL, so baseUpdateURL is redundant. Weirdly, using my own website worked regardless, but yours did kick off the fallback.

        Either way, thanks for sharing this! Super helpful and posted right when I started trying to implement this exact feature :)

        clevername27C 1 Reply Last reply Reply Quote 1
        • clevername27C
          clevername27 @ally
          last edited by

          @ally Cheers, mate.

          1 Reply Last reply Reply Quote 0
          • First post
            Last post

          46

          Online

          1.7k

          Users

          11.7k

          Topics

          101.8k

          Posts