Drag-and-drop loading of zipped ibw files

This procedure loads ibw files from a zipped file by dropping the zip onto the Igor window. The function LoadZippedIBW(filePath, fileName) can also be called from the command line or other functions. The zip is expanded into the current OS temp folder and the ibw files are deleted after loading. The code is 'dumb' and only works correctly for zip files with nothing else than ibw files in them, i.e., no support for other file types or a folder-structure. In pre-Igor 9 environments the zip is expanded via Tony's unzip script. Any suggestions for improvements are welcome. Supply your own PostProcessData() function if the waves need to be processed in some way (I use this for renaming and normalization).

Static Constant kVerbose = 0

Static Function BeforeFileOpenHook(refNum, fileNameStr, pathNameStr, fileTypeStr, fileCreatorStr, fileKind)
	Variable refNum, fileKind
	String fileNameStr, pathNameStr, fileTypeStr, fileCreatorStr
	
	If (StringMatch(fileNameStr,"*.zip"))
		PathInfo $pathNameStr
		LoadZippedIBW(S_path, fileNameStr)
		return 1
	endif
	return 0
End

//#################################

Function LoadZippedIBW(filePath, fileName)
	String filePath, fileName
	
	Variable unzipError, i
	String tempFolder = SpecialDirPath("Temporary", 0, 0, 0)
	
	NewPath/O/Q tmpPath, tempFolder
	
	unzipError = unzipArchive(filePath+fileName, tempFolder)
	if (unzipError)
		Print "Could not unzip file " + fileName
		return -1
	endif
	
	String tempFileList = IndexedFile(tmpPath, -1, "IGBW")
	if (strlen(tempFileList) > 0)
		for (i = 0; i < ItemsInList(tempFileList); i += 1)
			String currFile = StringFromList(i,tempFileList)
			LoadWave/O/Q/P=tmpPath currFile
			DeleteFile/Z/P=tmpPath currFile
			
			if (strlen(S_waveNames) > 0)
				Wave work = $StringFromList(0, S_waveNames)
				//PostProcessData(work, currFile)		// post-process loaded wave
			endif
		endfor
	endif
	
	KillPath/Z tmpPath
	return 0
End

//#################################

// stripped down unzip procedure written by Tony Withers + Igor 9 support
// https://www.wavemetrics.com/code-snippet/expand-zip-archive-platform-agnostic

Static Function unzipArchive(archivePathStr, unzippedPathStr)
	String archivePathStr, unzippedPathStr

	GetFileFolderInfo /Q/Z archivePathStr
	if(V_Flag || V_isFile==0)
		printf "Could not find file %s\r", archivePathStr
		return -1
	endif
	if(findlistItem(ParseFilePath(4, archivePathStr, ":", 0, 0), "zip;", ";", 0, 0) == -1)
		printf "%s doesn't appear to be a zip archive\r", ParseFilePath(0, archivePathStr, ":", 1, 0)
		return -1
	endif

#if(IgorVersion() >= 9)		// in Igor 9 just use unZip and quit
	unzippedPathStr = RemoveEnding(unzippedPathStr,":")
	unzipFile/O/Z archivePathStr, unzippedPathStr
	return V_flag
#endif
	
	String cmd
	if(stringmatch(StringByKey("OS", igorinfo(3))[0,2],"Win"))	// Windows
		Variable WinVersion = str2num(StringByKey("OSVERSION", igorinfo(3)))
		if (WinVersion<6.2)										// 6.2 = windows 8.0
			Print "unzipArchive requires Windows 8 or later"
			return 0
		endif
		archivePathStr	= ParseFilePath(5, archivePathStr, "\\", 0, 0)
		unzippedPathStr	= ParseFilePath(5, unzippedPathStr, "\\", 0, 0)
		cmd = "powershell.exe -nologo -noprofile -command \"& { Add-Type -A 'System.IO.Compression.FileSystem';"
		sprintf cmd "%s [IO.Compression.ZipFile]::ExtractToDirectory('%s', '%s'); }\"", cmd, archivePathStr, unzippedPathStr
	else														// Mac
		archivePathStr	= ParseFilePath(5, archivePathStr, "/", 0, 0)
		unzippedPathStr	= ParseFilePath(5, unzippedPathStr, "/", 0, 0)
		sprintf cmd, "unzip %s -d %s", archivePathStr, unzippedPathStr
		sprintf cmd, "do shell script \"%s\"", cmd
	endif
	
	ExecuteScriptText /B/UNQ/Z cmd
	if (kVerbose)
		Print S_value	// output from ExecuteScriptText
	endif
	return V_flag
End

 

LoadZippedIBW_v1.ipf (3.4 KB)

I recommend that you create a new directory within the temp directory and unzip into that directory. Otherwise, if there happen to be any .ibw files in tempFolder those will be loaded as well.

If you made this change, you could then use LoadData instead of LoadWave to load the entire contents of that temporary directory at once. Though for your purposes, that might complicate things because you need to post-process the data one wave at a time.

Thank you for the suggestion. Yes, I thought about that, but the reason I decided against working in a separate folder was the following: I was under the impression that deleting a folder is kind of a big deal (judging from the warning message in the help), and I can't imagine that there are ibw files in the system's temporary folder. I went for the (seemingly) safer and slightly less robust method here. If you say that I should not worry too much then I might give it a go.

Edit: I tried to get the unzip to work inside a folder, and was thinking about supporting a folder structure inside zip files, but there were quite a few roadblocks. First, deleting a folder really is a big deal with forced user interaction. To keep this process smooth one would need to leave the folder in place, but then what to do with the contents? Is there some way to delete disk folders silently?

Also, while LoadData would make recursive loading easy, I really need to post-process each wave. The main reason is that the data from the measurement software contains the exact same internal wave name in every file, so I cannot load multiple files without renaming each wave before the next file comes in. If LoadData had some way to rename each wave according to the file name, it would work for my case. But I understand the general purpose of LoadData, and the problematic implications of such a behavior (what to do with packet experiments etc.).

So in conclusion, I don't see a way to improve the functionality here without lots of compromises. If there are some neat tricks I would be happy to learn about them.

> Is there some way to delete disk folders silently?

If you only need for yourself you should be able to tweak that in the settings.

The documentation has

> The default behavior is to display a dialog asking for permission. The user can alter this behavior via the Miscellaneous Settings > dialog's Misc category.

You can use this function to delete a folder and its contents without prompting the user or enabling that setting in the Miscellaneous Settings category:

 

//**
// Forcefully delete a folder.  DeleteFolder also
// works but if the user hasn't told Igor not to prompt
// them before doing so then the tests won't finish running.
//
// BE CAREFUL USING THIS.
//*
static Function ForceDeleteFolder(fullPathToFolder)
	String fullPathToFolder
	Variable error
	String platform = IgorInfo(2)
	String errorMessage
	String envInfoString
	StrSwitch (platform)
		Case "Windows":
			try
				// Do some setup.
				String tmpDir = SpecialDirPath("Temporary", 0, 0, 0)
				AbortOnValue (numtype(strlen(tmpDir)) != 0 || !(strlen(tmpDir) > 0)), 1
  
				// Make sure that the directory we just got is, in fact, a directory.
				GetFileFolderInfo/Q tmpDir
				AbortOnValue (V_Flag >= 0 && !V_isFolder), 3
 
				// Set an Igor symbolic path to the temporary directory.
				String tmpDirectoryPath = UniqueName("tmpPath", 12, 0)
				NewPath/Q $(tmpDirectoryPath), tmpDir
				AbortOnValue (V_flag), 5		// Setting of the new path failed.
 
				// Write a temporary batch file to the temporary directory that will
				// call the "rmdir" command from the command shell and save the
				// output of the command to a temporary file.
				String tempBatFileName, tempResultsFileName
				String tempBatFileFullPath, tempResultsFileFullPath
				Variable tempBatFileRefNum, tempResultsFileRefNum
 
				sprintf tempBatFileName, "IgorCommandScript_%.4u.bat", abs(trunc(StopMsTimer(-2)))
				Open/P=$(tmpDirectoryPath)/T=".bat"/Z=1 tempBatFileRefNum as tempBatFileName
				AbortOnValue (V_flag != 0), 7
 
				// Add a path separator character to the end of the path, if necessary, and add on the file name.
				tempBatFileFullPath = ParseFilePath(2, tmpDir, ":", 0, 0) + tempBatFileName
 
				// Convert the path into a windows path that uses "\" as the path separator.
				tempBatFileFullPath = ParseFilePath(5, tempBatFileFullPath, "\\", 0, 0)
 
 				// /s deletes all contents of directory and then the directory
 				// /q prevents DOS from asking us if we're sure.
 				String windowsFilePath = ParseFilePath(5, fullPathToFolder, "\\", 0, 0)
 				if (strlen(windowsFilePath) <= 0)
 					AbortOnValue 1, 8
 				endif
				fprintf tempBatFileRefNum, "rmdir \"%s\"/s /q \r\n", windowsFilePath
				Close tempBatFileRefNum
 
				// Call the batch file we just created.  Timeout after 2 seconds if this doesn't succeed.
				String scriptText
				sprintf scriptText, "cmd.exe /C \"%s\"", tempBatFileFullPath
				ExecuteScriptText/W=2/Z scriptText
 
				// Check for an error.
				AbortOnValue (V_flag != 0), 9
 
				// Delete the temporary batch file and temporary results file.
				DeleteFile/P=$(tmpDirectoryPath)/Z=1 tempBatFileName
				// If we get an error here we don't really care.  We've already got
				// the goods, so just run.
				break
			catch
				error = -1
			endtry
 
		Case "Macintosh":
			// This is a lot easier on the Macintosh because we can just use AppleScript.
			try
				// Figure out the POSIX path of the folder to delete.
				String posixPath = ParseFilePath(5, fullPathToFolder, "/", 0, 0)
				if (strlen(posixPath) > 0)
					String appleScriptCommand
					sprintf appleScriptCommand, "tell application \"Finder\"  to delete ((POSIX file \"%s\") as alias)", posixPath
					ExecuteScriptText/Z appleScriptCommand
					AbortOnValue V_flag, 1				
				endif
				envInfoString = S_Value
			catch
				errorMessage = "Could not execute AppleScript command."
				print errorMessage
				error = -1				
			endtry
			break
	EndSwitch
	if (numtype(strlen(tmpDirectoryPath)) == 0)		// Check for a null string
		KillPath/Z $(tmpDirectoryPath)	
	endif
	return error
End

Note: On macOS (10.14 and later at least), the user will be prompted to allow Igor to use AppleScript the first time this is run.

Thank you very much for the script. Very interesting, also as an example how to run things on a system level. A bit overkill for just extracting a few zip files, though. So for my purposes I will stay with my 'dumb' approach.

Forum

Support

Gallery

Igor Pro 10

Learn More

Igor XOP Toolkit

Learn More

Igor NIDAQ Tools MX

Learn More