And the answer, as with so many things, turns out to be in an article about C++/Win32 programming from a decade ago.
The issue, in a nutshell, is that Windows handles floppy disk errors slightly differently than other kinds of drive errors. By default, no matter what you program does, or thinks it's doing, Windows will intercept any errors thrown by the device and present a dialog box to the user rather than letting the program handle it - the exact issue I was having.
But, as it turns out, there's a Win32 API call to solve this issue, primarily SetErrorMode()
In a nutshell (and I'm handwaving a way a lot of the details here), we can use SetErrorMode()
to get Windows to stop being quite so paranoid, do our thing and let the program handle the situation, and then reset the Windows error mode back to what it was before as if we had never been there. (There's probably a Keyser Soze joke here, but I've had the wrong amount of caffeine today to be able to find it.)
Adapting the C++ sample code from the linked article, that looks about like this:
int OldMode; //a place to store the old error mode
//save the old error mode and set the new mode to let us do the work:
OldMode = SetErrorMode(SEM_FAILCRITICALERRORS);
// Do whatever we need to do that might cause an error
SetErrorMode(OldMode); //put things back the way they were
Under C++, detecting errors the right way needs the `GetLastError()' function, which we fortunately don't need to worry about here, since this is a Python question. In our case, Python's exception handling works fine. This, then, is the function I knocked together to check a drive letter for "readiness", all ready for copy-pasting if anyone else needs it:
import win32api
def testDrive( currentLetter ):
"""
Tests a given drive letter to see if the drive is question is ready for
access. This is to handle things like floppy drives and USB card readers
which have to have physical media inserted in order to be accessed.
Returns true if the drive is ready, false if not.
"""
returnValue = False
#This prevents Windows from showing an error to the user, and allows python
#to handle the exception on its own.
oldError = win32api.SetErrorMode( 1 ) #note that SEM_FAILCRITICALERRORS = 1
try:
freeSpace = win32file.GetDiskFreeSpaceEx( letter )
except:
returnValue = False
else:
returnValue = True
#restore the Windows error handling state to whatever it was before we
#started messing with it:
win32api.SetErrorMode( oldError )
return returnValue
I've been using this quite a bit the last few days, and it's been working beautifully for both floppies and USB card readers.
A few notes: pretty much any function needing disk access will work in the try block - all we're looking for in an exception due to the media not being present.
Also, while the python win32api
package exposes all the functions we need, it dones't seem to have any of the flag constants. After a trip to the ancient bowels of MSDN, it turns out that SEM_FAILCRITICALERRORS is equal to 1, which makes our life awfully easy.
I hope this helps someone else with a similar problem!