From 03fd7f164e391693838f7d53f802b935542a10c4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 5 Jun 2009 16:30:59 -0700 Subject: [PATCH] Support for ejecting devices on windows --- installer/osx/freeze.py | 2 +- src/calibre/devices/usbms/device.py | 21 ++- src/calibre/utils/windows/winutil.c | 213 +++++++++++++++++++++++++++- 3 files changed, 233 insertions(+), 3 deletions(-) diff --git a/installer/osx/freeze.py b/installer/osx/freeze.py index 28d2bd9aa2..b380d69d92 100644 --- a/installer/osx/freeze.py +++ b/installer/osx/freeze.py @@ -230,7 +230,7 @@ _check_symlinks_prescript() all_functions = main_functions['console'] + main_functions['gui'] print print 'Adding PoDoFo' - pdf = glob.glob(os.path.expanduser('/Volumes/sw/podofo/*.dylib'))[0] + pdf = glob.glob(os.path.expanduser('/Volumes/sw/podofo/libpodofo.0.7.0.dylib'))[0] shutil.copyfile(pdf, os.path.join(frameworks_dir, os.path.basename(pdf))) diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index f0c54d7051..d5dcf3e440 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -492,7 +492,26 @@ class Device(DeviceConfig, DevicePlugin): self.open_osx() def eject_windows(self): - pass + from calibre.constants import plugins + from threading import Thread + winutil, winutil_err = plugins['winutil'] + drives = [] + for x in ('_main_prefix', '_card_a_prefix', '_card_b_prefix'): + x = getattr(self, x, None) + if x is not None: + drives.append(x[0].upper()) + + def do_it(drives): + for d in drives: + try: + winutil.eject_drive(d) + except: + pass + + t = Thread(target=do_it, args=[drives]) + t.daemon = True + t.start() + self.__save_win_eject_thread = t def eject_osx(self): for x in ('_main_prefix', '_card_a_prefix', '_card_b_prefix'): diff --git a/src/calibre/utils/windows/winutil.c b/src/calibre/utils/windows/winutil.c index 3388ba557b..5666c5bc0f 100644 --- a/src/calibre/utils/windows/winutil.c +++ b/src/calibre/utils/windows/winutil.c @@ -271,7 +271,7 @@ get_all_removable_disks(struct tagDrives *g_drives) // Loop for all drives (MAX_DRIVES = 26) - for(nLoopIndex = 0; nLoopIndex< MAX_DRIVES; nLoopIndex++) + for(nLoopIndex = 0; nLoopIndex < MAX_DRIVES; nLoopIndex++) { // if a drive is present, if(dwDriveMask & 1) @@ -306,6 +306,213 @@ get_all_removable_disks(struct tagDrives *g_drives) } +static DEVINST +GetDrivesDevInstByDeviceNumber(long DeviceNumber, + UINT DriveType, char* szDosDeviceName) +{ + GUID *guid; + HDEVINFO hDevInfo; + DWORD dwIndex, dwBytesReturned; + BOOL bRet, IsFloppy; + BYTE Buf[1024]; + PSP_DEVICE_INTERFACE_DETAIL_DATA pspdidd; + long res; + HANDLE hDrive; + STORAGE_DEVICE_NUMBER sdn; + + IsFloppy = (strstr(szDosDeviceName, "\\Floppy") != NULL); // is there a better way? + + switch (DriveType) { + case DRIVE_REMOVABLE: + if ( IsFloppy ) { + guid = (GUID*)&GUID_DEVINTERFACE_FLOPPY; + } else { + guid = (GUID*)&GUID_DEVINTERFACE_DISK; + } + break; + case DRIVE_FIXED: + guid = (GUID*)&GUID_DEVINTERFACE_DISK; + break; + case DRIVE_CDROM: + guid = (GUID*)&GUID_DEVINTERFACE_CDROM; + break; + default: + PyErr_SetString(PyExc_ValueError, "Invalid drive type"); + return 0; + } + + // Get device interface info set handle + // for all devices attached to system + hDevInfo = SetupDiGetClassDevs(guid, NULL, NULL, + DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + + if (hDevInfo == INVALID_HANDLE_VALUE) { + PyErr_SetString(PyExc_ValueError, "Invalid handle value"); + return 0; + } + + // Retrieve a context structure for a device interface + // of a device information set. + dwIndex = 0; + bRet = FALSE; + + + pspdidd = + (PSP_DEVICE_INTERFACE_DETAIL_DATA)Buf; + SP_DEVICE_INTERFACE_DATA spdid; + SP_DEVINFO_DATA spdd; + DWORD dwSize; + + spdid.cbSize = sizeof(spdid); + + while ( TRUE ) { + bRet = SetupDiEnumDeviceInterfaces(hDevInfo, NULL, + guid, dwIndex, &spdid); + if ( !bRet ) { + break; + } + + dwSize = 0; + SetupDiGetDeviceInterfaceDetail(hDevInfo, + &spdid, NULL, 0, &dwSize, NULL); + + if ( dwSize!=0 && dwSize<=sizeof(Buf) ) { + pspdidd->cbSize = sizeof(*pspdidd); // 5 Bytes! + + ZeroMemory((PVOID)&spdd, sizeof(spdd)); + spdd.cbSize = sizeof(spdd); + + res = + SetupDiGetDeviceInterfaceDetail(hDevInfo, & + spdid, pspdidd, + dwSize, &dwSize, + &spdd); + if ( res ) { + hDrive = CreateFile(pspdidd->DevicePath,0, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, NULL); + if ( hDrive != INVALID_HANDLE_VALUE ) { + dwBytesReturned = 0; + res = DeviceIoControl(hDrive, + IOCTL_STORAGE_GET_DEVICE_NUMBER, + NULL, 0, &sdn, sizeof(sdn), + &dwBytesReturned, NULL); + if ( res ) { + if ( DeviceNumber == (long)sdn.DeviceNumber ) { + CloseHandle(hDrive); + SetupDiDestroyDeviceInfoList(hDevInfo); + return spdd.DevInst; + } + } + CloseHandle(hDrive); + } + } + } + dwIndex++; + } + + SetupDiDestroyDeviceInfoList(hDevInfo); + PyErr_SetString(PyExc_ValueError, "Invalid device number"); + + return 0; +} + + + +static BOOL +eject_drive_letter(char DriveLetter) { + char szRootPath[4], szDevicePath[3], szVolumeAccessPath[7], szDosDeviceName[MAX_PATH]; + long DeviceNumber, res, tries; + HANDLE hVolume; + STORAGE_DEVICE_NUMBER sdn; + DWORD dwBytesReturned; + DEVINST DevInst; + ULONG Status; + ULONG ProblemNumber; + PNP_VETO_TYPE VetoType; + WCHAR VetoNameW[MAX_PATH]; + BOOL bSuccess; + DEVINST DevInstParent; + + szRootPath[0] = DriveLetter; szRootPath[1] = ':'; szRootPath[2] = '\\'; szRootPath[3] = (char)0; + szDevicePath[0] = DriveLetter; szDevicePath[1] = ':'; szDevicePath[2] = (char)0; + szVolumeAccessPath[0] = '\\'; szVolumeAccessPath[1] = '\\'; szVolumeAccessPath[2] = '.'; + szVolumeAccessPath[3] = '\\'; szVolumeAccessPath[4] = DriveLetter; szVolumeAccessPath[5] = ':'; + szVolumeAccessPath[6] = (char)0; + + + DeviceNumber = -1; + + hVolume = CreateFile(szVolumeAccessPath, 0, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, NULL); + if (hVolume == INVALID_HANDLE_VALUE) { + PyErr_SetString(PyExc_ValueError, "Invalid handle value for drive letter"); + return FALSE; + } + + dwBytesReturned = 0; + res = DeviceIoControl(hVolume, + IOCTL_STORAGE_GET_DEVICE_NUMBER, + NULL, 0, &sdn, sizeof(sdn), + &dwBytesReturned, NULL); + if ( res ) { + DeviceNumber = sdn.DeviceNumber; + } + CloseHandle(hVolume); + + if ( DeviceNumber == -1 ) { + PyErr_SetString(PyExc_ValueError, "Can't find drive number"); + return FALSE; + } + + res = QueryDosDevice(szDevicePath, szDosDeviceName, MAX_PATH); + if ( !res ) { + PyErr_SetString(PyExc_ValueError, "Can't find dos device"); + return FALSE; + } + + DevInst = GetDrivesDevInstByDeviceNumber(DeviceNumber, + DriveType, szDosDeviceName); + if (DevInst == 0) return FALSE; + + DevInstParent = 0; + Status = 0; + ProblemNumber = 0; + PNP_VETO_TYPE VetoType; + bSuccess = FALSE; + + res = CM_Get_Parent(&DevInstParent, DevInst, 0); + + for ( tries = 0; tries < 3; tries++ ) { + VetoNameW[0] = 0; + + res = CM_Request_Device_EjectW(DevInstParent, + &VetoType, VetoNameW, MAX_PATH, 0); + + bSuccess = (res==CR_SUCCESS && + VetoType==PNP_VetoTypeUnknown); + if ( bSuccess ) { + break; + } + + Sleep(500); // required to give the next tries a chance! + } + if (!bSuccess) PyErr_SetString(PyExc_ValueError, "Failed to eject drive after three tries"); + return bSuccess; +} + +static PyObject * +winutil_eject_drive(PyObject *self, PyObject *args) { + char DriveLetter; + + if (!PyArg_ParseTuple(args, "c", &DriveLetter)) return NULL; + + if (!eject_drive_letter(DriveLetter)) return NULL; + Py_RETURN_NONE; +} + + PSP_DEVICE_INTERFACE_DETAIL_DATA get_device_grandparent(HDEVINFO hDevInfo, DWORD index, PWSTR buf, PWSTR volume_id, BOOL *iterate) { @@ -697,6 +904,10 @@ is not present, current time as returned by localtime() is used. format must\n\ be a unicode string. Returns unicode strings." }, + {"eject_drive", winutil_eject_drive, METH_VARARGS, + "eject_drive(drive_letter)\n\nEject a drive. Raises an exception on failure." + }, + {NULL, NULL, 0, NULL} };