diff --git a/resources/recipes/chr_mon.recipe b/resources/recipes/chr_mon.recipe index f2fec1c24d..79c991efa8 100644 --- a/resources/recipes/chr_mon.recipe +++ b/resources/recipes/chr_mon.recipe @@ -1,19 +1,38 @@ +#!/usr/bin/env python +__license__ = 'GPL v3' +__author__ = 'Kovid Goyal and Sujata Raman, Lorenzo Vigentini' +__copyright__ = '2009, Kovid Goyal and Sujata Raman' +__version__ = 'v1.02' +__date__ = '10, January 2010' +__description__ = 'Providing context and clarity on national and international news, peoples and cultures' + +'''csmonitor.com''' + import re -from calibre import strftime from calibre.web.feeds.news import BasicNewsRecipe class ChristianScienceMonitor(BasicNewsRecipe): - title = 'Christian Science Monitor' - description = 'Providing context and clarity on national and international news, peoples and cultures' - max_articles_per_feed = 20 - __author__ = 'Kovid Goyal and Sujata Raman' + author = 'Kovid Goyal, Sujata Raman and Lorenzo Vigentini' + description = 'Providing context and clarity on national and international news, peoples and cultures' + + cover_url = 'http://www.csmonitor.com/extension/csm_base/design/csm_design/images/csmlogo_179x46.gif' + title = 'Christian Science Monitor' + publisher = 'The Christian Science Monitor' + category = 'News, politics, culture, economy, general interest' + language = 'en' encoding = 'utf-8' - no_stylesheets = True - use_embedded_content = False + timefmt = '[%a, %d %b, %Y]' + oldest_article = 16 + max_articles_per_feed = 20 + use_embedded_content = False + recursion = 10 + + remove_javascript = True + no_stylesheets = True preprocess_regexps = [ (re.compile(i[0], re.IGNORECASE | re.DOTALL), i[1]) for i in @@ -55,33 +74,15 @@ class ChristianScienceMonitor(BasicNewsRecipe): ] keep_only_tags = [ - dict(name='div', attrs={'id':['story','main']}), + dict(name='div', attrs={'id':'mainColumn'}), ] remove_tags = [ dict(name='div', attrs={'id':['story-tools','videoPlayer','storyRelatedBottom','enlarge-photo','photo-paginate']}), - dict(name='div', attrs={'class':[ 'spacer3','divvy spacer7','comment','storyIncludeBottom']}), + dict(name='div', attrs={'class':['storyToolbar cfx','podStoryRel','spacer3','divvy spacer7','comment','storyIncludeBottom']}), dict(name='ul', attrs={'class':[ 'centerliststories']}) , dict(name='form', attrs={'id':[ 'commentform']}) , ] + remove_tags_after = [ dict(name='div', attrs={'class':[ 'ad csmAd']})] - def find_articles(self, section): - ans = [] - for x in section.findAll('head4'): - title = ' '.join(x.findAll(text=True)).strip() - a = x.find('a') - if not a: continue - href = a['href'] - ans.append({'title':title, 'url':href, 'description':'', 'date': strftime('%a, %d %b')}) - - #for x in ans: - # x['url'] += '/output/print' - return ans - - def postprocess_html(self, soup, first_fetch): - html = soup.find('html') - if html is None: - return soup - html.extract() - return html diff --git a/resources/recipes/nytimes_sub.recipe b/resources/recipes/nytimes_sub.recipe index 2dc15d8f0d..9944f919be 100644 --- a/resources/recipes/nytimes_sub.recipe +++ b/resources/recipes/nytimes_sub.recipe @@ -48,7 +48,9 @@ class NYTimes(BasicNewsRecipe): return 'NY Times' def parse_index(self): + self.encoding = 'cp1252' soup = self.index_to_soup('http://www.nytimes.com/pages/todayspaper/index.html') + self.encoding = None def feed_title(div): return ''.join(div.findAll(text=True, recursive=False)).strip() diff --git a/resources/recipes/slashdot.recipe b/resources/recipes/slashdot.recipe index b210079093..dc0067f3ed 100644 --- a/resources/recipes/slashdot.recipe +++ b/resources/recipes/slashdot.recipe @@ -9,6 +9,8 @@ from calibre.web.feeds.news import BasicNewsRecipe class Slashdot(BasicNewsRecipe): title = u'Slashdot.org' + description = '''Tech news. WARNING: This recipe downloads a lot + of content and can result in your IP being banned from slashdot.org''' oldest_article = 7 max_articles_per_feed = 100 language = 'en' diff --git a/src/calibre/devices/__init__.py b/src/calibre/devices/__init__.py index 466a9ec20a..feaeffdcd9 100644 --- a/src/calibre/devices/__init__.py +++ b/src/calibre/devices/__init__.py @@ -5,7 +5,7 @@ __copyright__ = '2008, Kovid Goyal ' Device drivers. ''' -import sys, os, time, pprint +import sys, time, pprint from functools import partial from StringIO import StringIO @@ -29,7 +29,7 @@ def strftime(epoch, zone=time.gmtime): def debug(ioreg_to_tmp=False, buf=None): from calibre.customize.ui import device_plugins - from calibre.devices.scanner import DeviceScanner + from calibre.devices.scanner import DeviceScanner, win_pnp_drives from calibre.constants import iswindows, isosx, __version__ from calibre import prints oldo, olde = sys.stdout, sys.stderr @@ -37,19 +37,11 @@ def debug(ioreg_to_tmp=False, buf=None): if buf is None: buf = StringIO() sys.stdout = sys.stderr = buf - if iswindows: - import pythoncom - pythoncom.CoInitialize() try: out = partial(prints, file=buf) out('Version:', __version__) - wmi = Wmi =None - if iswindows: - wmi = __import__('wmi', globals(), locals(), [], -1) - Wmi = wmi.WMI(find_classes=False) s = DeviceScanner() - s.wmi = Wmi s.scan() devices = (s.devices) if not iswindows: @@ -60,21 +52,9 @@ def debug(ioreg_to_tmp=False, buf=None): out('USB devices on system:') out(pprint.pformat(devices)) if iswindows: - drives = [] + drives = win_pnp_drives(debug=True) out('Drives detected:') - out('\t', '(ID, Partitions, Drive letter)') - for drive in Wmi.Win32_DiskDrive(): - if drive.Partitions == 0: - continue - try: - partition = drive.associators("Win32_DiskDriveToDiskPartition")[0] - logical_disk = partition.associators('Win32_LogicalDiskToPartition')[0] - prefix = logical_disk.DeviceID+os.sep - drives.append((str(drive.PNPDeviceID), drive.Index, prefix)) - except IndexError: - drives.append((str(drive.PNPDeviceID), 'No mount points found')) - for drive in drives: - out('\t', drive) + out(pprint.pformat(drives)) ioreg = None if isosx: @@ -84,13 +64,10 @@ def debug(ioreg_to_tmp=False, buf=None): ioreg = 'Output from mount:\n\n'+mount+'\n\n'+ioreg connected_devices = [] for dev in device_plugins(): - owmi = getattr(dev, 'wmi', None) - dev.wmi = Wmi out('Looking for', dev.__class__.__name__) connected, det = s.is_device_connected(dev, debug=True) if connected: connected_devices.append((dev, det)) - dev.wmi = owmi errors = {} success = False @@ -102,8 +79,6 @@ def debug(ioreg_to_tmp=False, buf=None): out(' ') for dev, det in connected_devices: out('Trying to open', dev.name, '...', end=' ') - owmi = getattr(dev, 'wmi', None) - dev.wmi = Wmi try: dev.reset(detected_device=det) dev.open() @@ -113,8 +88,6 @@ def debug(ioreg_to_tmp=False, buf=None): errors[dev] = traceback.format_exc() out('failed') continue - finally: - dev.wmi = owmi success = True if hasattr(dev, '_main_prefix'): out('Main memory:', repr(dev._main_prefix)) @@ -142,7 +115,4 @@ def debug(ioreg_to_tmp=False, buf=None): finally: sys.stdout = oldo sys.stderr = olde - if iswindows: - import pythoncom - pythoncom.CoUninitialize() diff --git a/src/calibre/devices/interface.py b/src/calibre/devices/interface.py index 128a0bf45e..3d75f0ae22 100644 --- a/src/calibre/devices/interface.py +++ b/src/calibre/devices/interface.py @@ -60,7 +60,7 @@ class DevicePlugin(Plugin): import traceback traceback.print_exc() - def is_usb_connected_windows(self, devices_on_system, pnp_id_iterator, debug=False): + def is_usb_connected_windows(self, devices_on_system, debug=False): def id_iterator(): if hasattr(self.VENDOR_ID, 'keys'): @@ -85,7 +85,7 @@ class DevicePlugin(Plugin): self.test_bcd_windows(device_id, bcd): if debug: self.print_usb_device_info(device_id) - if self.can_handle_windows(device_id, pnp_id_iterator, debug=debug): + if self.can_handle_windows(device_id, debug=debug): return True return False @@ -97,7 +97,7 @@ class DevicePlugin(Plugin): return True return False - def is_usb_connected(self, devices_on_system, pnp_id_iterator, debug=False): + def is_usb_connected(self, devices_on_system, debug=False): ''' Return True, device_info if a device handled by this plugin is currently connected. @@ -105,7 +105,7 @@ class DevicePlugin(Plugin): ''' if iswindows: return self.is_usb_connected_windows(devices_on_system, - pnp_id_iterator, debug=debug), None + debug=debug), None vendors_on_system = set([x[0] for x in devices_on_system]) vendors = self.VENDOR_ID if hasattr(self.VENDOR_ID, '__len__') else [self.VENDOR_ID] @@ -147,7 +147,7 @@ class DevicePlugin(Plugin): """ raise NotImplementedError() - def can_handle_windows(self, device_id, pnp_id_iterator, debug=False): + def can_handle_windows(self, device_id, debug=False): ''' Optional method to perform further checks on a device to see if this driver is capable of handling it. If it is not it should return False. This method diff --git a/src/calibre/devices/prs500/cli/main.py b/src/calibre/devices/prs500/cli/main.py index 2b942d4cfc..814c1f06c2 100755 --- a/src/calibre/devices/prs500/cli/main.py +++ b/src/calibre/devices/prs500/cli/main.py @@ -9,7 +9,7 @@ For usage information run the script. import StringIO, sys, time, os from optparse import OptionParser -from calibre import __version__, iswindows, __appname__ +from calibre import __version__, __appname__ from calibre.devices.errors import PathError from calibre.utils.terminfo import TerminalController from calibre.devices.errors import ArgumentError, DeviceError, DeviceLocked @@ -198,14 +198,9 @@ def main(): args = args[1:] dev = None scanner = DeviceScanner() - if iswindows: - import wmi, pythoncom - pythoncom.CoInitialize() - scanner.wmi = wmi.WMI(find_classes=False) scanner.scan() connected_devices = [] for d in device_plugins(): - d.wmi = scanner.wmi ok, det = scanner.is_device_connected(d) if ok: dev = d diff --git a/src/calibre/devices/scanner.py b/src/calibre/devices/scanner.py index 839552e9a7..70d44dc767 100644 --- a/src/calibre/devices/scanner.py +++ b/src/calibre/devices/scanner.py @@ -6,6 +6,7 @@ manner. ''' import sys, os +from threading import RLock from calibre import iswindows, isosx, plugins, islinux @@ -22,6 +23,54 @@ elif isosx: except: raise RuntimeError('Failed to load the usbobserver plugin: %s'%plugins['usbobserver'][1]) +class WinPNPScanner(object): + + def __init__(self): + self.scanner = None + if iswindows: + self.scanner = plugins['winutil'][0].get_removable_drives + self.lock = RLock() + + def drive_is_ok(self, letter, debug=False): + import win32api, win32file + with self.lock: + oldError = win32api.SetErrorMode(1) #SEM_FAILCRITICALERRORS = 1 + try: + ans = True + try: + win32file.GetDiskFreeSpaceEx(letter+':\\') + except: + ans = False + return ans + finally: + win32api.SetErrorMode(oldError) + + def __call__(self, debug=False): + if self.scanner is None: + return {} + try: + drives = self.scanner(debug) + except: + drives = {} + if debug: + import traceback + traceback.print_exc() + remove = set([]) + for letter in drives: + if not self.drive_is_ok(letter, debug=debug): + remove.add(letter) + for letter in remove: + drives.pop(letter) + ans = {} + for key, val in drives.items(): + val = [x.upper() for x in val] + val = [x for x in val if 'USBSTOR' in x] + if val: + ans[key+':\\'] = val[-1] + return ans + +win_pnp_drives = WinPNPScanner() + class LinuxScanner(object): SYSFS_PATH = os.environ.get('SYSFS_PATH', '/sys') @@ -85,26 +134,13 @@ class DeviceScanner(object): raise RuntimeError('DeviceScanner requires the /sys filesystem to work.') self.scanner = win_scanner if iswindows else osx_scanner if isosx else linux_scanner self.devices = [] - self.wmi = None - self.pnp_ids = set([]) - self.rescan_pnp_ids = True def scan(self): '''Fetch list of connected USB devices from operating system''' self.devices = self.scanner() - if self.rescan_pnp_ids: - self.pnp_ids = set([]) - - def pnp_id_iterator(self): - if self.wmi is not None and not self.pnp_ids: - for drive in self.wmi.Win32_DiskDrive(): - if drive.Partitions > 0: - self.pnp_ids.add(str(drive.PNPDeviceID)) - for x in self.pnp_ids: - yield x def is_device_connected(self, device, debug=False): - return device.is_usb_connected(self.devices, self.pnp_id_iterator, debug=debug) + return device.is_usb_connected(self.devices, debug=debug) def main(args=sys.argv): diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index fd7cf262dc..ccbce861ef 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -203,18 +203,6 @@ class Device(DeviceConfig, DevicePlugin): return False - def windows_get_drive_prefix(self, drive): - prefix = None - - try: - partition = drive.associators("Win32_DiskDriveToDiskPartition")[0] - logical_disk = partition.associators('Win32_LogicalDiskToPartition')[0] - prefix = logical_disk.DeviceID + os.sep - except IndexError: - pass - - return prefix - def windows_sort_drives(self, drives): ''' Called to disambiguate main memory and storage card for devices that @@ -223,8 +211,10 @@ class Device(DeviceConfig, DevicePlugin): ''' return drives - def can_handle_windows(self, device_id, pnp_id_iterator, debug=False): - for pnp_id in pnp_id_iterator(): + def can_handle_windows(self, device_id, debug=False): + from calibre.devices.scanner import win_pnp_drives + drives = win_pnp_drives() + for pnp_id in drives.values(): if self.windows_match_device(pnp_id, 'WINDOWS_MAIN_MEM'): return True if debug: @@ -232,29 +222,20 @@ class Device(DeviceConfig, DevicePlugin): return False def open_windows(self): + from calibre.devices.scanner import win_pnp_drives - def matches_q(drive, attr): - q = getattr(self, attr) - if q is None: return False - if isinstance(q, basestring): - q = [q] - pnp = str(drive.PNPDeviceID) - for x in q: - if x in pnp: - return True - return False - - time.sleep(8) + time.sleep(5) drives = {} - c = self.wmi - for drive in c.Win32_DiskDrive(): - pnp_id = str(drive.PNPDeviceID) - if self.windows_match_device(pnp_id, 'WINDOWS_CARD_A_MEM') and not drives.get('carda', None): - drives['carda'] = self.windows_get_drive_prefix(drive) - elif self.windows_match_device(pnp_id, 'WINDOWS_CARD_B_MEM') and not drives.get('cardb', None): - drives['cardb'] = self.windows_get_drive_prefix(drive) - elif self.windows_match_device(pnp_id, 'WINDOWS_MAIN_MEM') and not drives.get('main', None): - drives['main'] = self.windows_get_drive_prefix(drive) + for drive, pnp_id in win_pnp_drives().items(): + if self.windows_match_device(pnp_id, 'WINDOWS_CARD_A_MEM') and \ + not drives.get('carda', False): + drives['carda'] = drive + elif self.windows_match_device(pnp_id, 'WINDOWS_CARD_B_MEM') and \ + not drives.get('cardb', False): + drives['cardb'] = drive + elif self.windows_match_device(pnp_id, 'WINDOWS_MAIN_MEM') and \ + not drives.get('main', False): + drives['main'] = drive if 'main' in drives.keys() and 'carda' in drives.keys() and \ 'cardb' in drives.keys(): diff --git a/src/calibre/ebooks/oeb/stylizer.py b/src/calibre/ebooks/oeb/stylizer.py index d986033418..9f50796615 100644 --- a/src/calibre/ebooks/oeb/stylizer.py +++ b/src/calibre/ebooks/oeb/stylizer.py @@ -168,6 +168,7 @@ class Stylizer(object): self.rules = rules self._styles = {} class_sel_pat = re.compile(r'\.[a-z]+', re.IGNORECASE) + capital_sel_pat = re.compile(r'h|[A-Z]+') for _, _, cssdict, text, _ in rules: try: selector = CSSSelector(text) @@ -176,6 +177,15 @@ class Stylizer(object): SelectorSyntaxError): continue matches = selector(tree) + + if not matches: + ntext = capital_sel_pat.sub(lambda m: m.group().lower(), text) + if ntext != text: + self.logger.warn('Transformed CSS selector', text, 'to', + ntext) + selector = CSSSelector(ntext) + matches = selector(tree) + if not matches and class_sel_pat.match(text): found = False for x in tree.xpath('//*[@class]'): diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index 23eb33003c..49a341ae14 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -13,7 +13,6 @@ from PyQt4.Qt import QMenu, QAction, QActionGroup, QIcon, SIGNAL, QPixmap, \ from calibre.customize.ui import available_input_formats, available_output_formats, \ device_plugins from calibre.devices.interface import DevicePlugin -from calibre.constants import iswindows from calibre.gui2.dialogs.choose_format import ChooseFormatDialog from calibre.utils.ipc.job import BaseJob from calibre.devices.scanner import DeviceScanner @@ -85,7 +84,6 @@ class DeviceManager(Thread): self.job_manager = job_manager self.current_job = None self.scanner = DeviceScanner() - self.wmi = None self.connected_device = None self.ejected_devices = set([]) @@ -133,7 +131,6 @@ class DeviceManager(Thread): self.connected_device = None def detect_device(self): - self.scanner.rescan_pnp_ids = not self.is_device_connected self.scanner.scan() if self.is_device_connected: connected, detected_device = \ @@ -170,30 +167,18 @@ class DeviceManager(Thread): pass def run(self): - if iswindows: - import pythoncom - pythoncom.CoInitialize() - wmi = __import__('wmi', globals(), locals(), [], -1) - self.wmi = wmi.WMI(find_classes=False) - self.scanner.wmi = self.wmi - for x in self.devices: - x.wmi = self.wmi - try: - while self.keep_going: - self.detect_device() - while True: - job = self.next() - if job is not None: - self.current_job = job - self.device.set_progress_reporter(job.report_progress) - self.current_job.run() - self.current_job = None - else: - break - time.sleep(self.sleep_time) - finally: - if iswindows: - pythoncom.CoUninitialize() + while self.keep_going: + self.detect_device() + while True: + job = self.next() + if job is not None: + self.current_job = job + self.device.set_progress_reporter(job.report_progress) + self.current_job.run() + self.current_job = None + else: + break + time.sleep(self.sleep_time) def create_job(self, func, done, description, args=[], kwargs={}): diff --git a/src/calibre/gui2/dialogs/config/config.ui b/src/calibre/gui2/dialogs/config/config.ui index e638bab1c9..b9306b0f10 100644 --- a/src/calibre/gui2/dialogs/config/config.ui +++ b/src/calibre/gui2/dialogs/config/config.ui @@ -7,7 +7,7 @@ 0 0 - 838 + 884 730 @@ -89,7 +89,7 @@ 0 0 - 562 + 608 683 diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py index cef56e361d..4198fc5684 100644 --- a/src/calibre/gui2/dialogs/metadata_single.py +++ b/src/calibre/gui2/dialogs/metadata_single.py @@ -171,7 +171,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): if self._add_formats(paths): event.accept() - def remove_format(self, x=None): + def remove_format(self, *args): rows = self.formats.selectionModel().selectedRows(0) for row in rows: self.formats.takeItem(row.row()) diff --git a/src/calibre/gui2/shortcuts.py b/src/calibre/gui2/shortcuts.py index 5c4ebcc2fe..1281518889 100644 --- a/src/calibre/gui2/shortcuts.py +++ b/src/calibre/gui2/shortcuts.py @@ -23,9 +23,10 @@ KEY = Qt.UserRole + 3 class Customize(QFrame, Ui_Frame): - def __init__(self, dup_check, parent=None): + def __init__(self, index, dup_check, parent=None): QFrame.__init__(self, parent) self.setupUi(self) + self.data_model = index.model() self.setFocusPolicy(Qt.StrongFocus) self.setAutoFillBackground(True) self.custom.toggled.connect(self.custom_toggled) @@ -86,12 +87,21 @@ class Delegate(QStyledItemDelegate): def __init__(self, parent=None): QStyledItemDelegate.__init__(self, parent) self.editing_indices = {} + self.closeEditor.connect(self.editing_done) def to_doc(self, index): doc = QTextDocument() doc.setHtml(index.data().toString()) return doc + def editing_done(self, editor, hint): + remove = None + for row, w in self.editing_indices.items(): + remove = (row, w.data_model.index(row)) + if remove is not None: + self.editing_indices.pop(remove[0]) + self.sizeHintChanged.emit(remove[1]) + def sizeHint(self, option, index): if index.row() in self.editing_indices: return QSize(200, 200) @@ -111,7 +121,7 @@ class Delegate(QStyledItemDelegate): painter.restore() def createEditor(self, parent, option, index): - w = Customize(index.model().duplicate_check, parent=parent) + w = Customize(index, index.model().duplicate_check, parent=parent) self.editing_indices[index.row()] = w self.sizeHintChanged.emit(index) return w @@ -135,8 +145,6 @@ class Delegate(QStyledItemDelegate): setattr(editor, 'shortcut%d'%(x+1), seq) def setModelData(self, editor, model, index): - self.editing_indices.pop(index.row()) - self.sizeHintChanged.emit(index) self.closeEditor.emit(editor, self.NoHint) custom = [] if editor.custom.isChecked(): diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py index b1267bd772..474345d442 100644 --- a/src/calibre/gui2/widgets.py +++ b/src/calibre/gui2/widgets.py @@ -141,8 +141,8 @@ class FormatList(QListWidget): if event.key() == Qt.Key_Delete: self.emit(SIGNAL('delete_format()')) else: - QListWidget.keyPressEvent(self, event) - + return QListWidget.keyPressEvent(self, event) + class ImageView(QLabel): diff --git a/src/calibre/utils/windows/winutil.c b/src/calibre/utils/windows/winutil.c index be504b7fcf..efd8f1400d 100644 --- a/src/calibre/utils/windows/winutil.c +++ b/src/calibre/utils/windows/winutil.c @@ -204,23 +204,22 @@ get_registry_property(HDEVINFO hDevInfo, DWORD index, DWORD property, BOOL *iter return buffer; } -static BOOL -check_device_id(LPTSTR buffer, unsigned int vid, unsigned int pid) { - WCHAR xVid[9], dVid[9], xPid[9], dPid[9]; - unsigned int j; - swprintf(xVid, L"vid_%4.4x", vid); - swprintf(dVid, L"vid_%4.4d", vid); - swprintf(xPid, L"pid_%4.4x", pid); - swprintf(dPid, L"pid_%4.4d", pid); - - for (j = 0; j < wcslen(buffer); j++) buffer[j] = tolower(buffer[j]); - - return ( (wcsstr(buffer, xVid) != NULL || wcsstr(buffer, dVid) != NULL ) && - (wcsstr(buffer, xPid) != NULL || wcsstr(buffer, dPid) != NULL ) - ); -} - - +static BOOL +check_device_id(LPTSTR buffer, unsigned int vid, unsigned int pid) { + WCHAR xVid[9], dVid[9], xPid[9], dPid[9]; + unsigned int j; + _snwprintf_s(xVid, 9, _TRUNCATE, L"vid_%4.4x", vid); + _snwprintf_s(dVid, 9, _TRUNCATE, L"vid_%4.4d", vid); + _snwprintf_s(xPid, 9, _TRUNCATE, L"pid_%4.4x", pid); + _snwprintf_s(dPid, 9, _TRUNCATE, L"pid_%4.4d", pid); + + for (j = 0; j < wcslen(buffer); j++) buffer[j] = tolower(buffer[j]); + + return ( (wcsstr(buffer, xVid) != NULL || wcsstr(buffer, dVid) != NULL ) && + (wcsstr(buffer, xPid) != NULL || wcsstr(buffer, dPid) != NULL ) + ); +} + static HDEVINFO create_device_info_set(LPGUID guid, PCTSTR enumerator, HWND parent, DWORD flags) { HDEVINFO hDevInfo; @@ -286,7 +285,7 @@ get_all_removable_disks(struct tagDrives *g_drives) if(GetVolumeNameForVolumeMountPoint(caDrive, volume, BUFSIZE)) { g_drives[g_count].letter = caDrive[0]; - wcscpy(g_drives[g_count].volume, volume); + wcscpy_s(g_drives[g_count].volume, BUFSIZE, volume); g_count ++; } @@ -515,15 +514,17 @@ winutil_eject_drive(PyObject *self, PyObject *args) { PSP_DEVICE_INTERFACE_DETAIL_DATA -get_device_grandparent(HDEVINFO hDevInfo, DWORD index, PWSTR buf, PWSTR volume_id, - BOOL *iterate) { +get_device_ancestors(HDEVINFO hDevInfo, DWORD index, PyObject *candidates, BOOL *iterate, BOOL ddebug) { SP_DEVICE_INTERFACE_DATA interfaceData; SP_DEVINFO_DATA devInfoData; BOOL status; PSP_DEVICE_INTERFACE_DETAIL_DATA interfaceDetailData; DWORD interfaceDetailDataSize, reqSize; - DEVINST parent; + DEVINST parent, pos; + wchar_t temp[BUFSIZE]; + int i; + PyObject *devid; interfaceData.cbSize = sizeof (SP_INTERFACE_DEVICE_DATA); devInfoData.cbSize = sizeof (SP_DEVINFO_DATA); @@ -549,7 +550,7 @@ get_device_grandparent(HDEVINFO hDevInfo, DWORD index, PWSTR buf, PWSTR volume_i ); interfaceDetailDataSize = reqSize; - interfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)PyMem_Malloc(interfaceDetailDataSize+10); + interfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)PyMem_Malloc(interfaceDetailDataSize+50); if ( interfaceDetailData == NULL ) { PyErr_NoMemory(); return NULL; @@ -563,38 +564,49 @@ get_device_grandparent(HDEVINFO hDevInfo, DWORD index, PWSTR buf, PWSTR volume_i interfaceDetailDataSize, // Interface detail data size &reqSize, // Buffer size required to get the detail data &devInfoData); // Interface device info + if (ddebug) printf("Getting ancestors\n"); fflush(stdout); if ( status == FALSE ) {PyErr_SetFromWindowsErr(0); PyMem_Free(interfaceDetailData); return NULL;} - // Get the device instance of parent. This points to USBSTOR. - CM_Get_Parent(&parent, devInfoData.DevInst, 0); - // Get the device ID of the USBSTORAGE volume - CM_Get_Device_ID(parent, volume_id, BUFSIZE, 0); - // Get the device instance of grand parent. This points to USB root. - CM_Get_Parent(&parent, parent, 0); - // Get the device ID of the USB root. - CM_Get_Device_ID(parent, buf, BUFSIZE, 0); + pos = devInfoData.DevInst; + + for(i = 0; i < 10; i++) { + // Get the device instance of parent. + if (CM_Get_Parent(&parent, pos, 0) != CR_SUCCESS) break; + if (CM_Get_Device_ID(parent, temp, BUFSIZE, 0) == CR_SUCCESS) { + if (ddebug) wprintf(L"device id: %s\n", temp); fflush(stdout); + devid = PyUnicode_FromWideChar(temp, wcslen(temp)); + if (devid) { + PyList_Append(candidates, devid); + Py_DECREF(devid); + } + } + pos = parent; + } return interfaceDetailData; } static PyObject * -winutil_get_mounted_volumes_for_usb_device(PyObject *self, PyObject *args) { - unsigned int vid, pid, length, j; - HDEVINFO hDevInfo; - BOOL iterate = TRUE; +winutil_get_removable_drives(PyObject *self, PyObject *args) { + HDEVINFO hDevInfo; + BOOL iterate = TRUE, ddebug = FALSE; PSP_DEVICE_INTERFACE_DETAIL_DATA interfaceDetailData; DWORD i; - WCHAR buf[BUFSIZE], volume[BUFSIZE], volume_id[BUFSIZE]; + unsigned int j, length; + WCHAR volume[BUFSIZE]; struct tagDrives g_drives[MAX_DRIVES]; - PyObject *volumes, *key, *val; + PyObject *volumes, *key, *candidates, *pdebug = Py_False, *temp; - if (!PyArg_ParseTuple(args, "ii", &vid, &pid)) { + if (!PyArg_ParseTuple(args, "|O", &pdebug)) { return NULL; } + ddebug = PyObject_IsTrue(pdebug); + volumes = PyDict_New(); if (volumes == NULL) return NULL; + for (j = 0; j < MAX_DRIVES; j++) g_drives[j].letter = 0; @@ -609,47 +621,44 @@ winutil_get_mounted_volumes_for_usb_device(PyObject *self, PyObject *args) { // Enumerate through the set for (i=0; iterate; i++) { - interfaceDetailData = get_device_grandparent(hDevInfo, i, buf, volume_id, &iterate); + candidates = PyList_New(0); + if (candidates == NULL) return PyErr_NoMemory(); + + interfaceDetailData = get_device_ancestors(hDevInfo, i, candidates, &iterate, ddebug); if (interfaceDetailData == NULL) { PyErr_Print(); continue; } - debug("Device num: %d Device Id: %ws\n\n", i, buf); - if (check_device_id(buf, vid, pid)) { - debug("Device matches\n\n"); - length = wcslen(interfaceDetailData->DevicePath); - interfaceDetailData->DevicePath[length] = '\\'; - interfaceDetailData->DevicePath[length+1] = 0; - if(GetVolumeNameForVolumeMountPoint(interfaceDetailData->DevicePath, volume, BUFSIZE)) { - for(j = 0; j < MAX_DRIVES; j++) { - // Compare volume mount point with the one stored earlier. - // If both match, return the corresponding drive letter. - if(g_drives[j].letter != 0 && wcscmp(g_drives[j].volume, volume)==0) - { - key = PyUnicode_FromWideChar(volume_id, wcslen(volume_id)); - val = PyString_FromFormat("%c", (char)g_drives[j].letter); - if (key == NULL || val == NULL) { - PyErr_NoMemory(); - PyMem_Free(interfaceDetailData); - return NULL; - } - PyDict_SetItem(volumes, key, val); - } + length = wcslen(interfaceDetailData->DevicePath); + interfaceDetailData->DevicePath[length] = L'\\'; + interfaceDetailData->DevicePath[length+1] = 0; + + if (ddebug) wprintf(L"Device path: %s\n", interfaceDetailData->DevicePath); fflush(stdout); + // On Vista+ DevicePath contains the information we need. + temp = PyUnicode_FromWideChar(interfaceDetailData->DevicePath, length); + if (temp == NULL) return PyErr_NoMemory(); + PyList_Append(candidates, temp); + Py_DECREF(temp); + if(GetVolumeNameForVolumeMountPointW(interfaceDetailData->DevicePath, volume, BUFSIZE)) { + if (ddebug) wprintf(L"Volume: %s\n", volume); fflush(stdout); + + for(j = 0; j < MAX_DRIVES; j++) { + if(g_drives[j].letter != 0 && wcscmp(g_drives[j].volume, volume)==0) { + if (ddebug) printf("Found drive: %c\n", (char)g_drives[j].letter); fflush(stdout); + key = PyBytes_FromFormat("%c", (char)g_drives[j].letter); + if (key == NULL) return PyErr_NoMemory(); + PyDict_SetItem(volumes, key, candidates); + Py_DECREF(candidates); + break; } - - } else { - debug("Failed to get volume name for volume mount point:\n"); - if (DEBUG) debug("%ws\n\n", format_last_error()); } - PyMem_Free(interfaceDetailData); } - + PyMem_Free(interfaceDetailData); } //for SetupDiDestroyDeviceInfoList(hDevInfo); return volumes; - } static PyObject * @@ -876,21 +885,22 @@ static PyMethodDef WinutilMethods[] = { "script being run. So to replace sys.argv, you should use " "sys.argv[1:] = argv()[1:]."}, - {"is_usb_device_connected", winutil_is_usb_device_connected, METH_VARARGS, - "is_usb_device_connected(vid, pid) -> bool\n\n" - "Check if the USB device identified by VendorID: vid (integer) and" - " ProductID: pid (integer) is currently connected."}, + {"is_usb_device_connected", winutil_is_usb_device_connected, METH_VARARGS, + "is_usb_device_connected(vid, pid) -> bool\n\n" + "Check if the USB device identified by VendorID: vid (integer) and" + " ProductID: pid (integer) is currently connected."}, {"get_usb_devices", winutil_get_usb_devices, METH_VARARGS, "get_usb_devices() -> list of strings\n\n" "Return a list of the hardware IDs of all USB devices " "connected to the system."}, - {"get_mounted_volumes_for_usb_device", winutil_get_mounted_volumes_for_usb_device, METH_VARARGS, - "get_mounted_volumes_for_usb_device(vid, pid) -> dict\n\n" - "Return a dictionary of volume_id:drive_letter for all" - "volumes mounted on the system that belong to the" - "usb device specified by vid (integer) and pid (integer)."}, + {"get_removable_drives", winutil_get_removable_drives, METH_VARARGS, + "get_removable_drives(debug=False) -> dict\n\n" + "Return mapping of all removable drives in the system. Maps drive letters " + "to a list of device id strings, atleast one of which will carry the information " + "needed for device matching. On Vista+ it is always the last string in the list. " + "Note that you should upper case all strings."}, {"set_debug", winutil_set_debug, METH_VARARGS, "set_debug(bool)\n\nSet debugging mode."