#pragma TextEncoding="UTF-8" #pragma rtGlobals=3 #pragma IgorVersion=7 // feedback? ideas? tony.withers@uwo.ca // This file should be saved in the Igor Procedures folder (look in the // Help menu for "Show Igor Pro User Files"). // If you want Igor to automatically reload updated files, choose // Misc->Miscellaneous Settings, click Text Editing in the left pane, // click the External Editor tab, and select Reload Automatically, As // Soon as Modification is Detected. // If you're an end-user of packages that can be updated this way, that's // all you need to do. // ------------------------------------------------------------------------ // If you're developing a package and wish to use this updater: // 1. Copy and paste this block of pragma statements and constants to the procedure file you // want to keep updated. Edit values to match your package - the values here are for this file. #pragma version=2.08 // Edit version number - must be same as release version on IgorExchange #pragma moduleName=updater // Edit regular module name static constant kCHECK_FOR_UPDATES=1 // set to 1 to check for updates on init static strconstant ksSHORTNAME="Updater" // Must correspond to shortname of IgorExchange release // (ksSHORTNAME isn't needed if moduleName matches project shortname) // 2. Insert this code in your procedure file (without //): //static function CheckForUpdates() // if(kCHECK_FOR_UPDATES) // #if (exists("Updater#UpdateCheck")==6) // return Updater#UpdateCheck("fileloc:"+FunctionPath("")) // #endif // endif // return 0 //end // 3. Add this to your package initialization function: // if (CheckForUpdates()) // return 0 // this file has been updated; quit to allow user to reload // endif // --------------------------------------------------------------------------- // Details // Updater is close to being able to handle zip archives. If the project // release is a zip archive, Updater downloads the archive to the // desktop, inflates archive to a temporary location (also on the // desktop), and moves all of the contents to the same location as the ipf // file to be updated. For this to work, the zip archive had better have // a straightforward structure. If the master procedure file is buried in // a subfolder, or if the orginal installation spread the package // contents into other unrelated folders, running the update will cause // problems. Caveat emptor! // On Windows, Andrew Nelson's zip XOP is required: // http://www.igorexchange.com/project/ZIP // Wishlist // See install() function in this file for a prototype package installer. // Easy to unzip on a mac. On a PC, need 3rd party utility. Or this XOP: // http://www.igorexchange.com/project/ZIP // Unfortunately it looks like a file handle isn't being closed properly // by zip.xop, preventing Igor from moving the extracted files. For now, // workaround is to move files to install location, leaving the folder // for the user to delete // Would like to know current status of Misc settings for reloading // updated files. // Provide a way to rollback to earlier releases, or give a list of // releases for the user to choose from. // Deal with XOPs // ----------------------------------------------------------------------------- // code for use in independent modules: //static function CheckForUpdates() // // if (kCHECK_FOR_UPDATES==0) // return 0 // endif // // string keyStr="" // keyStr=ReplaceStringByKey("fileloc", keyStr, FunctionPath("")) // //// find out whether Updater is installed // #if (exists("Updater#UpdateCheck")==6) // this will be 0 if running as an independent module // return Updater#UpdateCheck(keyStr) // #endif // // if(stringmatch(GetIndependentModuleName(), "ProcGlobal")) // return 0 // endif // //// Running as independent module, so will have to use execute. //// Find out whether procedures are compiled: // // variable /G V_updated=0 // variable isUpdated=0 // string cmd, infoStr=FunctionInfo("ProcGlobal#updater#UpdateCheck", "Updater.ipf") // // if (stringmatch(StringByKey("MODULE", infoStr), "updater")) // updater is available AND compiled // sprintf cmd, "V_updated=ProcGlobal#Updater#UpdateCheck(\"%s\")", keyStr // execute cmd // isUpdated=V_updated // endif // // killvariables /Z V_updated // // return isUpdated //end // ------------------------------------------------------------------------------------------------------------ static constant kFREQUENCY=604800 // do not check more frequently than this. weekly=604800. // you can override this setting by adding a copy of the above line to your procedure file and editing the value // you can also override the default location for updates by adding something like this to your procedure: // static strconstant ksLOCATION="http://www.igorexchange.com/node/8197/release" // ------------------------------------------------------------------------------------------------------------ menu "Help" "Updater: Check for new releases", /Q, updater#UpdateAll() end // We'll keep a very small binary file in the updater preferences folder to // store the last update for each project that works with updater static structure LastUpdateStructure uint32 version // Preferences structure version number. 100 means 1.00. uint32 lastUpdate // 32 bits should be good for another 20 years or so... uint32 reserved[8] // Reserved for future use endStructure // Wrapper function for update() // This function is called from the file to be checked static function UpdateCheck(keyList) string keyList variable updated=0 NVAR /Z v_updater=v_updater // if this exists we want to check all open procedures for possible updates if (!NVAR_Exists(v_updater)) // avoid repeatedly checking this file // first find out whether this file needs updating string keyListThisFile="" keyListThisFile=ReplaceStringByKey("fileloc", keyListThisFile, FunctionPath("")) updated+=update(keyListThisFile) endif // now check the calling procedure file updated+=update(keyList) return updated end // Looks for procedure windows with defined modules that contain the // static CheckForUpdates function, and executes that function wherever // it is found. Only works for procedure files in which a regular module // name is defined. static function UpdateAll() // Use a global variable to let UpdateCheck() function in this // procedure know that we want to ignore the frequency setting and // check now for updates to all files that use updater and have // kCheckForUpdates set. variable /g v_updater=0 string procList="", IMlist="", moduleList="", moduleStr="", IMstr="", cmd="" variable i,j, imax, jmax execute "SetIgorOption IndependentModuleDev=1" // would be good to record current status before setting this procList=winlist("*",";","WIN:128") execute "SetIgorOption IndependentModuleDev=0" IMlist=IndependentModuleList(";") // loop through procedure windows imax=itemsinlist(procList) for(i=0; iView all releases", 0, 2) selStart=strsearch(webPageText, "href=", selEnd, 3) if (selEnd==-1 || selStart==-1) return 0 endif url="https://www.wavemetrics.com"+webPageText[selStart+6,selEnd-2] endif // version number of local file Grep /Q/E="(?i)^#pragma[\s]*version[\s]*=" /LIST/Z procPathStr s_value=lowerStr(trimString(s_value, 1)) sscanf s_value, "#pragma version = %f", localVersion if (V_flag!=1 || localVersion<=0) printf "Could not find version pragma in %s\r", parseFilePath(0, procPathStr, ":", 1, 0) return 0 endif if(chatty) sprintf cmd, "Check for updates to %s?", shortName doalert 1, cmd if (v_flag==2) return 0 endif endif printf "%s: Checking for updates... ", shortName s.lastUpdate=datetime // will be rounded to 32 bit integer SavePackagePreferences "updater", prefsFile, 1, s webPageText = FetchURL(url); err = GetRTError(1) if (err != 0) sprintf cmd, "Could not load %s", url print cmd if(chatty) doalert 0, cmd endif return 0 endif // Look for new releases. If the web page location is not IgorExchange, // ParseReleases will have to be changed to take into account the format // of the web page. make /free/n=(0,4)/T w_releases if(ParseReleases(webPageText, w_releases)==0) sprintf cmd, "Could not find packages at %s.\r", url cmd+="\tPlease check for new versions at https://www.wavemetrics.com/projects" if (chatty) doalert 0, cmd endif print cmd return 0 endif variable i url="" for(i=0;i=releaseVersion) sprintf cmd, "Current version (%g) is the most recent release\r", localVersion if (chatty) doalert 0, cmd endif print cmd return 0 endif if(releaseVersion>localVersion) if(currentIgorVersion= %g\r", projectName, releaseVersion, releaseIgorVersion elseif(FindListItem(system, w_releases[i][3])==-1) printf "%s %g not available for %s\r", projectName, releaseVersion, system else url=w_releases[i][2] break endif endif endfor if (strlen(url)==0) return 0 endif fileType=parseFilePath(4, url,":", 0, 0) isZip=stringMatch(fileType, "zip") if(cmpstr(fileType, "ipf") && cmpstr(fileType, "zip")) printf "New version of %s released\r", projectName printf "Updater couldn't find a useable file at %s\r", url return 0 endif if (chatty) sprintf cmd, "%s: Found version %g. Download now?", projectName, releaseVersion doalert 1, cmd if (v_flag==2) // print file location for manual download printf "New version of %s available at %s\r", projectName, url return 0 endif endif if(testing) // for testing, don't hit server pointlessly printf "testing: substituting local file for %s\r", url url="file://C:/Users/tony/Desktop/archive.zip" url="file:///Users/tony/Desktop/archive.zip" isZip=1 endif // download the package file webPageText=FetchURL(url); err = GetRTError(1) if (err != 0) sprintf cmd, "Updater couldn't load %s", url print cmd if(chatty) doalert 0, cmd endif return 0 endif printf "Downloaded %s version %g from %s\r", projectName, releaseVersion, url if(archiveOldFile) // this will archive just the ipf file string oldFileprocPathStr sprintf oldFileprocPathStr, "%s%s%g.%s", SpecialDirPath("Desktop",0,0,0), parseFilePath(3, procPathStr, ":", 0, 0), localVersion, parseFilePath(4, procPathStr, ":", 0, 0) variable flagI=0 getFileFolderInfo /Q/Z oldFileprocPathStr if(v_flag==0) // file already exists in archive location flagI=2 // check before overwriting endif movefile /O/S="Archive current file"/I=(flagI)/Z procPathStr as oldFileprocPathStr if(v_flag==0) printf "Saved copy of %s version %g to %s\r", projectName, localVersion, s_path endif endif // make sure we have somewhere to write our download if(isZip) downloadPathStr=SpecialDirPath("Desktop",0,0,0)+parseFilePath(0, url,"/", 1, 0) else downloadPathStr=procPathStr endif getFileFolderInfo /Q/Z downloadPathStr if (v_flag==0) cmd="" if (!chatty) sprintf cmd, "New update found for %s\r", projectName endif // Aways ask before overwriting file if (isZip) sprintf cmd, "%sOverwrite %s?", cmd, parseFilePath(0, downloadPathStr, ":", 1, 0) else sprintf cmd, "%sOverwrite %s with %s version %g?", cmd, parseFilePath(0, downloadPathStr, ":", 1, 0), projectName, releaseVersion endif doalert 1, cmd if(v_flag==2) printf "%s: update cancelled\r", projectName return 0 endif endif // write download to target ipf file or to zip file on desktop variable refNum=0 open /Z refNum as downloadPathStr if(refNum==0) return 0 endif FBinWrite refNum, webPageText close refNum if(isZip) // assume that contents of zip file go into same folder as current ipf file downloadPathStr=unzipPackage(shortName, downloadPathStr, packagePathStr=ParseFilePath(1, procPathStr, ":", 1, 0)) if (strlen(downloadPathStr)==0) return 0 endif endif printf "Updated %s to version %g\r", projectName, releaseVersion string IgorProcPathStr=SpecialDirPath("Igor Pro User Files",0,0,0)+"Igor Procedures:" if(autoloadOption==1 && strlen(FindAlias(IgorProcPathStr, procPathStr))==0) sprintf cmd "Create %s in Igor Procedure to autoload %s?", selectstring(isWin, "alias", "shortcut"), projectName doAlert 1, cmd if (v_flag==1) createAliasShortcut procPathStr as IgorProcPathStr+ParseFilePath(3, procPathStr, ":", 0, 0)+selectstring(isWin, "Alias", "Shortcut") endif endif return 1 end // Parse html in s_web to find releases. static function ParseReleases(WebPageText, w_releases) string WebPageText wave /T w_releases variable selStart=-1, selEnd=-1, blockStart, blockEnd, selFound string url="", projectName="", releaseText="", platform="" variable i=0 do // find start and end of project release fields selStart= strsearch(WebPageText, "
", selStart, 2) if(selStart==-1) break endif blockStart=selStart selEnd=strsearch(WebPageText, "
", selStart, 2) if(selEnd==-1) break endif blockEnd=selEnd selStart=strsearch(WebPageText, "

", blockStart, 2) selEnd=strsearch(WebPageText, "IGOR.", selStart, 0) projectName=trimstring(WebPageText[selStart+4, selEnd-1]) selStart=selEnd+5 selEnd=strsearch(WebPageText, "

", selStart, 2) releaseText=WebPageText[selStart, selEnd-1] selStart=strsearch(WebPageText, "", selEnd, 2) selStart=strsearch(WebPageText, "blockEnd) break endif url=WebPageText[selStart, selEnd-1] platform="" selFound=strsearch(WebPageText, "Windows", blockStart, 2) if(selFound>blockStart && selFoundMac-", blockStart, 2) if(selFound>blockStart && selFound", selStart, 2) if(selStart==-1) break endif blockStart=selStart selStart=strsearch(WebPageText, "", selStart, 2) selEnd=strsearch(WebPageText, "IGOR.", selStart, 0) projectName=trimstring(WebPageText[selStart+1, selEnd-1]) selStart=selEnd+5 selEnd=strsearch(WebPageText, "", selStart, 2) releaseText=WebPageText[selStart, selEnd-1] selStart=strsearch(WebPageText, "
", selEnd, 2) selStart=strsearch(WebPageText, "Download: ", selStart, 2) selStart=strsearch(WebPageText, "\"", selStart, 2) selEnd=strsearch(WebPageText, "\"", selStart+1, 2) url=WebPageText[selStart+1, selEnd-1] platform="" selFound=strsearch(WebPageText, "Supported on Windows", blockStart, 2) if(selFound>blockStart && selFoundblockStart && selFoundView all releases", 0, 2) selStart=strsearch(webPageText, "href=", selEnd, 3) if (selEnd==-1 || selStart==-1) printf "Installer could not find releases at %s\r", url continue endif url="https://www.wavemetrics.com"+webPageText[selStart+6,selEnd-2] // try to download 'all releases' page webPageText = FetchURL(url); err = GetRTError(1) if (err != 0) printf "Could not load %s", url continue endif printf "Looking for releases at %s\r", url // parse page to populate w_releases make /free/n=(0,4)/T w_releases if(ParseReleases(webPageText, w_releases)==0) printf "Could not find packages at %s.\r", url continue endif // find most recent OS- and version-compatible release url="" for(i=0;i= %g\r", projectName, releaseVersion, releaseIgorVersion elseif(FindListItem(system, w_releases[i][3])==-1) printf "%s %g not available for %s\r", projectName, releaseVersion, system else url=w_releases[i][2] break endif endfor if (strlen(url)==0) continue endif if(testing) // for testing, don't hit server pointlessly printf "testing: substituting local file for %s\r", url url="file://C:/Users/tony/Desktop/archive.zip" url="file:///Users/tony/Desktop/archive.zip" endif // download package webPageText=FetchURL(url); err = GetRTError(1) if (err != 0) printf "Could not load %s", url continue endif printf "Downloaded %s\r", url // where to save the downloaded file - a temporary location // safe to edit this, but will overwrite! string downloadPathStr downloadPathStr=SpecialDirPath("Desktop",0,0,0)+"IgorPackageInstallerTemp:" newpath /C/O/Q downloadPathTemp, downloadPathStr killpath /z downloadPathTemp // save file to disk open /Z refNum as downloadPathStr+parseFilePath(0, url,"/", 1, 0) if(refNum==0) continue endif FBinWrite refNum, webPageText close refNum // S_fileName created by open printf "Saved temporary file %s\r", S_fileName // consider only two possibilities: an uncompressed ipf file or a zip archive if (stringmatch(parseFilePath(4, S_fileName,":", 0, 0), "zip")) if(paramisdefault(location)) // unzipPackage will prompt user for install path // package folder will be created if necessary packagePathStr=unzipPackage(packageName, S_fileName) else getfileFolderInfo /Q/Z location if(v_flag!=0 || V_isFolder==0) printf "Install path not found: %s\r", location continue endif packagePathStr=unzipPackage(packageName, S_fileName, packagePathStr=location) endif if (strlen(packagePathStr)==0) continue endif string ipfList="", ipfStr="" newpath /C/O/Q packagePathTemp, packagePathStr i=0 do ipfStr=IndexedFile(packagePathTemp, i, ".ipf") if(!strlen(ipfStr)) break endif ipfStr=removeending(ipfStr, ".ipf") ipfList=AddListItem(ipfStr, ipfList) i+=1 while(1) killpath /z packagePathTemp if(i==1) // only one procedure file at top level of package folder IncludeProcStr=StringFromList(0, ipfList) elseif(i>1 && (WhichListItem(packageName, ipfList)!=-1) ) // package folder includes a procedure with project name IncludeProcStr=packageName endif // if there's an XOP in the package, don't try to open any procedure file if(strlen(FindFile(packagePathStr,"*.xop"))) IncludeProcStr="" endif // Look for and run itx-format installation script. Script will run // from within the package folder saved in location of user's // choice within Igor Pro User Files folder. string msg string pathToScriptStr="" getFileFolderInfo /Q/Z packagePathStr+"install.itx" if (V_Flag==0) // s_path has been reset by getFileFolderInfo pathToScriptStr=s_path else getFileFolderInfo /Q/Z packagePathStr+"install"+system+".itx" if (V_Flag==0) // s_path has been reset by getFileFolderInfo pathToScriptStr=s_path endif endif if(strLen(pathToScriptStr)) // script could do naughty things, so ask before allowing it to run sprintf msg, "Run installation script?\r%s was downloaded from %s", ParseFilePath(0, pathToScriptStr, ":", 1, 0), url doAlert 1, msg if (v_flag==1) sprintf cmd, "Loadwave /T \"%s\"", s_path execute cmd // will be printed to history endif endif elseif(stringmatch(parseFilePath(4, S_fileName,":", 0, 0), "ipf")) if(paramisdefault(location)) packagePathStr=ChooseInstallLocation(packageName) else getfileFolderInfo /Q/Z location if(v_flag==0) printf "Install path not found: %s\r", location continue endif packagePathStr=location endif getFileFolderInfo /Q/Z packagePathStr+packageName+".ipf" if (v_flag==0) // already exists doalert 1, "overwrite "+packageName+".ipf?" if (v_flag==2) continue endif endif // S_filename still set by open movefile /O/Z S_fileName as packagePathStr+packageName+".ipf" if(V_flag!=0) continue endif IncludeProcStr=packageName printf "Saved %s in %s\r", packageName+".ipf", packagePathStr endif if (strlen(IncludeProcStr)) // found a 'master' procedure file if(autoloadOption==1) // find out whether an alias already exists string IgorProcPathStr=SpecialDirPath("Igor Pro User Files",0,0,0)+"Igor Procedures:" string targetPathStr=packagePathStr+IncludeProcStr+".ipf" autoloadOption=(strlen(FindAlias(IgorProcPathStr, targetPathStr))==0) // make sure target isn't in Igor Procedures folder if(autoloadOption && stringmatch(targetPathStr, IgorProcPathStr+"*")==0) string aliasName=ParseFilePath(3, targetPathStr, ":", 0, 0)+selectstring(isWin, " Alias", " Shortcut") sprintf cmd "Create %s alias in Igor Procedures to autoload %s?", ParseFilePath(0, targetPathStr, ":", 1, 0), packageName doAlert 1, cmd if (v_flag==1) createAliasShortcut targetPathStr as IgorProcPathStr+aliasName printf "Created %s in %s\r", aliasName, IgorProcPathStr endif endif endif // find out if procedure is already loaded wave /T w_loadedProcs=ListToTextWave(winlist("*",";","WIN:128,INDEPENDENTMODULE:1"),";") w_loadedProcs=extractFilename(w_loadedProcs) FindValue /TEXT=IncludeProcStr/TXOP=4 /Z w_loadedProcs if(V_Value==-1) // load the procedure sprintf cmd, "INSERTINCLUDE \"%s\"", IncludeProcStr Execute/P/Q/Z cmd Execute/P/Q/Z "COMPILEPROCEDURES " printf "Included %s in current experiment\r", IncludeProcStr endif endif ListIndex+=1 while(1) return 1 end // strip trailing stuff in the output from winlist static function /S extractFilename(s) string s SplitString/E=".*\.ipf" s // remove module names return removeending(s_value, ".ipf") end // Inflates zip archive to a temporary folder, moves temporary folder to // destination in Igor Pro User Files. static function /S unzipPackage(shortName, archivePathStr, [packagePathStr]) string shortName, archivePathStr, packagePathStr GetFileFolderInfo /Q/Z archivePathStr if(V_Flag || V_isFile==0) return "" endif variable CreatePackageFolder=0 if(paramIsDefault(packagePathStr)) CreatePackageFolder=1 packagePathStr=ChooseInstallLocation(shortName) // find out whether user has chosen an existing subfolder with the name of the project if(stringmatch(ParseFilePath(0, packagePathStr, ":", 1, 0), shortName)==1) CreatePackageFolder=0 endif endif if (strlen(packagePathStr)==0) return "" endif string system=selectString(stringmatch(StringByKey("OS", igorinfo(3))[0,2],"Mac"), "Windows", "Macintosh") variable isWin=stringmatch(system[0,2],"Win") //string tmpPathStr=SpecialDirPath("Temporary",0,0,0), tmpDirStr="" string tmpPathStr=SpecialDirPath("Desktop",0,0,0)+"IgorPackageInstallerTemp:", tmpDirStr="" newpath /C/O/Q unzipPath, tmpPathStr // make sure folder exists on disk variable i=0 do sprintf tmpDirStr,"InstallerTemp%d", i GetFileFolderInfo /Q/Z tmpPathStr+tmpDirStr i+=1 while(V_Flag==0) string unzipPathStr=tmpPathStr+tmpDirStr+":" // create a temporary directory for the uncompressed files newpath /C/O/Q unzipPath, unzipPathStr // inflate archive if(isWin) #if(exists("ZIPfile")==4) ZIPfile /O/X unzipPathStr, archivePathStr // this creates a folder that cannot be moved! // maybe a problem with the ZIP xop (I tested with Windows 10, Igor64). #else print "please install ZIP64 XOP from http://www.igorexchange.com/project/ZIP" return "" #endif else unzipArchive(archivePathStr, unzipPathStr) endif printf "Inflated %s to %s\r", ParseFilePath(0, archivePathStr, ":", 1, 0), unzipPathStr string fileList=IndexedFile(unzipPath, -1, "????") string folderList=IndexedDir(unzipPath, -1, 0) killpath /Z unzipPath // ignore pesky __MACOSX folder i=0 do if(stringmatch(StringFromList(i, folderList), "__MACOSX")) folderList=removeListItem(i, folderList) else i+=1 endif while (imaxLevels || (folderCount-folderIndex)>folderLimit) break endif while(1) killpath /Z tempPath return "" end // Search recursively for s_file within folder described by folderPathStr. // Don't expect this to be quick. // Returns path to file. Wildcards okay. static function /T FindFile(folderPathStr, fileName) String folderPathStr, fileName variable maxLevels=10 // quit after recursion through this many sublevels variable folderLimit=5000 // quit after looking in this many folders. Variable folderIndex, fileIndex, subfolderIndex, folderCount=1, sublevels=0 string folderList, indexFileName if (strlen(folderPathStr)==0) // it would be nice to default to the current save location, but I couldn't find a way to do that folderPathStr=SpecialDirPath("Documents", 0, 0, 0) endif make /free/T/n=0 w_folders, w_subfolders w_folders={folderPathStr} do for(folderIndex=0;folderIndexmaxLevels || (folderCount-folderIndex)>folderLimit) break endif while(1) killpath /Z tempPathFF return "" end // search recursively in folderPathStr for files matching fileName. // returns list of complete paths to files static function /T RecursiveFileList(folderPathStr, fileName) String folderPathStr, fileName variable maxLevels=5 // quit after recursion through this many sublevels variable folderLimit=500 // quit after looking in this many folders. Variable folderIndex, fileIndex, subfolderIndex, folderCount=1, sublevels=0 string folderList, indexFileName string fileList="" if (strlen(folderPathStr)==0) return "" endif make /free/T/n=0 w_folders, w_subfolders w_folders={folderPathStr} do for(folderIndex=0;folderIndexmaxLevels || (folderCount-folderIndex)>folderLimit) break endif while(1) killpath /Z tempPathFF return fileList end