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-ag…

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

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 9

Learn More

Igor XOP Toolkit

Learn More

Igor NIDAQ Tools MX

Learn More