예제 #1
0
파일: device.py 프로젝트: siebert/calibre
                self._card_a_prefix = mp
                self._card_a_vol = vol['vol']
                if verbose:
                    print "FBSD:	card a = ", self._card_a_prefix
            if mtd == 2:
                self._card_b_prefix = mp
                self._card_b_vol = vol['vol']
                if verbose:
                    print "FBSD:	card b = ", self._card_b_prefix
                # Note that mtd is used as a bool... not incrementing is fine.
                break
            mtd += 1

        if mtd > 0:
            return True
        raise DeviceError(_('Unable to mount the device'))

#
# ------------------------------------------------------
#
#    this one is pretty simple:
#        just umount each of the previously
#        mounted filesystems, using the stored volume object
#

    def eject_freebsd(self):
        import dbus
        # There should be some way to access the -v arg...
        verbose = False

        if self._main_prefix:
예제 #2
0
    def read_device_collections(self, connection, source_id, dbpath):
        sequence_min = self.get_database_min_id(source_id)
        sequence_max = sequence_min
        sequence_dirty = 0

        debug_print("Collection Sequence Min: %d, Source Id: %d" %
                    (sequence_min, source_id))

        try:
            cursor = connection.cursor()

            # Get existing collections
            query = 'SELECT _id, tagname FROM tags'
            cursor.execute(query)
        except Exception:
            import traceback
            tb = traceback.format_exc()
            raise DeviceError((
                ('The Paladin database is corrupted. '
                 ' Delete the file %s on your reader and then disconnect '
                 ' reconnect it. If you are using an SD card, you '
                 ' should delete the file on the card as well. Note that '
                 ' deleting this file will cause your reader to forget '
                 ' any notes/highlights, etc.') % dbpath) +
                              ' Underlying error:'
                              '\n' + tb)

        db_collections = {}
        for i, row in enumerate(cursor):
            db_collections[row[1]] = row[0]
            if row[0] < sequence_min:
                sequence_dirty = 1
            else:
                sequence_max = max(sequence_max, row[0])

        # If the database is 'dirty', then we should fix up the Ids and the sequence number
        if sequence_dirty == 1:
            debug_print("Collection Sequence Dirty for Source Id: %d" %
                        source_id)
            sequence_max = sequence_max + 1
            for collection, collectionId in db_collections.items():
                if collectionId < sequence_min:
                    # Record the new Id and write it to the DB
                    db_collections[collection] = sequence_max
                    sequence_max = sequence_max + 1

                    # Fix the collection DB
                    query = 'UPDATE tags SET _id = ? WHERE tagname = ?'
                    t = (
                        db_collections[collection],
                        collection,
                    )
                    cursor.execute(query, t)

                    # Fix any references in existing collections
                    query = 'UPDATE booktags SET tag_id = ? WHERE tag_id = ?'
                    t = (
                        db_collections[collection],
                        collectionId,
                    )
                    cursor.execute(query, t)

            self.set_database_sequence_id(connection, 'tags', sequence_max)
            debug_print("Collection Sequence Max: %d, Source Id: %d" %
                        (sequence_max, source_id))

        # Fix up the collections table now...
        sequence_dirty = 0
        sequence_max = sequence_min

        debug_print("Collections Sequence Min: %d, Source Id: %d" %
                    (sequence_min, source_id))

        query = 'SELECT _id FROM booktags'
        cursor.execute(query)

        db_collection_pairs = []
        for i, row in enumerate(cursor):
            db_collection_pairs.append(row[0])
            if row[0] < sequence_min:
                sequence_dirty = 1
            else:
                sequence_max = max(sequence_max, row[0])

        if sequence_dirty == 1:
            debug_print("Collections Sequence Dirty for Source Id: %d" %
                        source_id)
            sequence_max = sequence_max + 1
            for pairId in db_collection_pairs:
                if pairId < sequence_min:
                    # Record the new Id and write it to the DB
                    query = 'UPDATE booktags SET _id = ? WHERE _id = ?'
                    t = (
                        sequence_max,
                        pairId,
                    )
                    cursor.execute(query, t)
                    sequence_max = sequence_max + 1

            self.set_database_sequence_id(connection, 'booktags', sequence_max)
            debug_print("Collections Sequence Max: %d, Source Id: %d" %
                        (sequence_max, source_id))

        cursor.close()
        return db_collections
예제 #3
0
    def __init__(self, paths, ext_paths, prefixes, use_author_sort):
        from lxml import etree

        if DEBUG:
            debug_print('Building XMLCache...', paths)
        self.paths = paths
        self.prefixes = prefixes
        self.use_author_sort = use_author_sort

        # Parse XML files {{{
        parser = etree.XMLParser(recover=True)
        self.roots = {}
        for source_id, path in paths.items():
            if source_id == 0:
                if not os.path.exists(path):
                    raise DeviceError(
                        ('The SONY XML cache %r does not exist. Try'
                         ' disconnecting and reconnecting your reader.') %
                        repr(path))
                with lopen(path, 'rb') as f:
                    raw = f.read()
            else:
                raw = EMPTY_CARD_CACHE
                if os.access(path, os.R_OK):
                    with lopen(path, 'rb') as f:
                        raw = f.read()

            self.roots[source_id] = etree.fromstring(
                xml_to_unicode(raw,
                               strip_encoding_pats=True,
                               assume_utf8=True,
                               verbose=DEBUG)[0],
                parser=parser)
            if self.roots[source_id] is None:
                raise Exception(
                    ('The SONY database at %r is corrupted. Try '
                     ' disconnecting and reconnecting your reader.') % path)

        self.ext_paths, self.ext_roots = {}, {}
        for source_id, path in ext_paths.items():
            if not os.path.exists(path):
                try:
                    with lopen(path, 'wb') as f:
                        f.write(EMPTY_EXT_CACHE)
                        fsync(f)
                except:
                    pass
            if os.access(path, os.W_OK):
                try:
                    with lopen(path, 'rb') as f:
                        self.ext_roots[source_id] = etree.fromstring(
                            xml_to_unicode(f.read(),
                                           strip_encoding_pats=True,
                                           assume_utf8=True,
                                           verbose=DEBUG)[0],
                            parser=parser)
                        self.ext_paths[source_id] = path
                except:
                    pass

        # }}}

        recs = self.roots[0].xpath('//*[local-name()="records"]')
        if not recs:
            raise DeviceError('The SONY XML database is corrupted (no'
                              ' <records>). Try disconnecting an reconnecting'
                              ' your reader.')
        self.record_roots = {}
        self.record_roots.update(self.roots)
        self.record_roots[0] = recs[0]

        self.detect_namespaces()
        debug_print('Done building XMLCache...')
예제 #4
0
    def open_freebsd(self):
        import dbus
        # There should be some way to access the -v arg...
        verbose = False

        # this gives us access to the S/N, etc. of the reader that the scanner has found
        # and the match routines for some of that data, like s/n, vendor ID, etc.
        d = self.detected_device

        if not d.serial:
            raise DeviceError("Device has no S/N.  Can't continue")
            return False

        vols = []

        bus = dbus.SystemBus()
        manager = dbus.Interface(
            bus.get_object('org.freedesktop.Hal',
                           '/org/freedesktop/Hal/Manager'),
            'org.freedesktop.Hal.Manager')
        paths = manager.FindDeviceStringMatch('usb.serial', d.serial)
        for path in paths:
            objif = dbus.Interface(bus.get_object('org.freedesktop.Hal', path),
                                   'org.freedesktop.Hal.Device')
            # Extra paranoia...
            try:
                if d.idVendor == objif.GetProperty('usb.vendor_id') and \
                        d.idProduct == objif.GetProperty('usb.product_id') and \
                        d.manufacturer == objif.GetProperty('usb.vendor') and \
                        d.product == objif.GetProperty('usb.product') and \
                        d.serial == objif.GetProperty('usb.serial'):
                    midpath = manager.FindDeviceStringMatch(
                        'info.parent', path)
                    dpaths = manager.FindDeviceStringMatch(
                        'storage.originating_device',
                        path) + manager.FindDeviceStringMatch(
                            'storage.originating_device', midpath[0])
                    for dpath in dpaths:
                        # devif = dbus.Interface(bus.get_object('org.freedesktop.Hal', dpath), 'org.freedesktop.Hal.Device')
                        try:
                            vpaths = manager.FindDeviceStringMatch(
                                'block.storage_device', dpath)
                            for vpath in vpaths:
                                try:
                                    vdevif = dbus.Interface(
                                        bus.get_object('org.freedesktop.Hal',
                                                       vpath),
                                        'org.freedesktop.Hal.Device')
                                    if not vdevif.GetProperty(
                                            'block.is_volume'):
                                        continue
                                    if vdevif.GetProperty(
                                            'volume.fsusage') != 'filesystem':
                                        continue
                                    volif = dbus.Interface(
                                        bus.get_object('org.freedesktop.Hal',
                                                       vpath),
                                        'org.freedesktop.Hal.Device.Volume')
                                    pdevif = dbus.Interface(
                                        bus.get_object(
                                            'org.freedesktop.Hal',
                                            vdevif.GetProperty('info.parent')),
                                        'org.freedesktop.Hal.Device')
                                    vol = {
                                        'node':
                                        pdevif.GetProperty('block.device'),
                                        'dev': vdevif,
                                        'vol': volif,
                                        'label':
                                        vdevif.GetProperty('volume.label')
                                    }
                                    vols.append(vol)
                                except dbus.exceptions.DBusException as e:
                                    print(e)
                                    continue
                        except dbus.exceptions.DBusException as e:
                            print(e)
                            continue
            except dbus.exceptions.DBusException as e:
                continue

        def ocmp(x, y):
            if x['node'] < y['node']:
                return -1
            if x['node'] > y['node']:
                return 1
            return 0

        vols.sort(cmp=ocmp)

        if verbose:
            print("FBSD:	", vols)

        mtd = 0

        for vol in vols:
            mp = ''
            if vol['dev'].GetProperty('volume.is_mounted'):
                mp = vol['dev'].GetProperty('volume.mount_point')
            else:
                try:
                    vol['vol'].Mount('Calibre-' + vol['label'],
                                     vol['dev'].GetProperty('volume.fstype'),
                                     [])
                    loops = 0
                    while not vol['dev'].GetProperty('volume.is_mounted'):
                        time.sleep(1)
                        loops += 1
                        if loops > 100:
                            print(
                                "ERROR: Timeout waiting for mount to complete")
                            continue
                    mp = vol['dev'].GetProperty('volume.mount_point')
                except dbus.exceptions.DBusException as e:
                    print("Failed to mount ", e)
                    continue

            # Mount Point becomes Mount Path
            mp += '/'

            if verbose:
                print("FBSD:	  mounted", vol['label'], "on", mp)
            if mtd == 0:
                self._main_prefix = mp
                self._main_vol = vol['vol']
                if verbose:
                    print("FBSD:	main = ", self._main_prefix)
            if mtd == 1:
                self._card_a_prefix = mp
                self._card_a_vol = vol['vol']
                if verbose:
                    print("FBSD:	card a = ", self._card_a_prefix)
            if mtd == 2:
                self._card_b_prefix = mp
                self._card_b_vol = vol['vol']
                if verbose:
                    print("FBSD:	card b = ", self._card_b_prefix)
                # Note that mtd is used as a bool... not incrementing is fine.
                break
            mtd += 1

        if mtd > 0:
            return True
        raise DeviceError(_('Unable to mount the device'))
예제 #5
0
    def read_device_books(self, connection, source_id, dbpath):
        sequence_min = self.get_database_min_id(source_id)
        sequence_max = sequence_min
        sequence_dirty = 0

        debug_print("Book Sequence Min: %d, Source Id: %d" %
                    (sequence_min, source_id))

        try:
            cursor = connection.cursor()

            # Get existing books
            query = 'SELECT filename, _id FROM books'
            cursor.execute(query)
        except Exception:
            import traceback
            tb = traceback.format_exc()
            raise DeviceError((
                ('The Paladin database is corrupted. '
                 ' Delete the file %s on your reader and then disconnect '
                 ' reconnect it. If you are using an SD card, you '
                 ' should delete the file on the card as well. Note that '
                 ' deleting this file will cause your reader to forget '
                 ' any notes/highlights, etc.') % dbpath) +
                              ' Underlying error:'
                              '\n' + tb)

        # Get the books themselves, but keep track of any that are less than the minimum.
        # Record what the max id being used is as well.
        db_books = {}
        for i, row in enumerate(cursor):
            if not hasattr(row[0], 'replace'):
                continue
            lpath = row[0].replace('\\', '/')
            db_books[lpath] = row[1]
            if row[1] < sequence_min:
                sequence_dirty = 1
            else:
                sequence_max = max(sequence_max, row[1])

        # If the database is 'dirty', then we should fix up the Ids and the sequence number
        if sequence_dirty == 1:
            debug_print("Book Sequence Dirty for Source Id: %d" % source_id)
            sequence_max = sequence_max + 1
            for book, bookId in db_books.items():
                if bookId < sequence_min:
                    # Record the new Id and write it to the DB
                    db_books[book] = sequence_max
                    sequence_max = sequence_max + 1

                    # Fix the Books DB
                    query = 'UPDATE books SET _id = ? WHERE filename = ?'
                    t = (
                        db_books[book],
                        book,
                    )
                    cursor.execute(query, t)

                    # Fix any references so that they point back to the right book
                    t = (
                        db_books[book],
                        bookId,
                    )
                    query = 'UPDATE booktags SET tag_id = ? WHERE tag_id = ?'
                    cursor.execute(query, t)

            self.set_database_sequence_id(connection, 'books', sequence_max)
            debug_print("Book Sequence Max: %d, Source Id: %d" %
                        (sequence_max, source_id))

        cursor.close()
        return db_books
예제 #6
0
    def find_device_nodes(self, detected_device=None):
        def walk(base):
            base = os.path.abspath(os.path.realpath(base))
            for x in os.listdir(base):
                p = os.path.join(base, x)
                if os.path.islink(p) or not os.access(p, os.R_OK):
                    continue
                isfile = os.path.isfile(p)
                yield p, isfile
                if not isfile:
                    for y, q in walk(p):
                        yield y, q

        def raw2num(raw):
            raw = raw.lower()
            if not raw.startswith('0x'):
                raw = '0x' + raw
            return int(raw, 16)

        # Find device node based on vendor, product and bcd
        d, j = os.path.dirname, os.path.join
        usb_dir = None

        if detected_device is None:
            detected_device = self.detected_device

        def test(val, attr):
            q = getattr(detected_device, attr)
            return q == val

        for x, isfile in walk('/sys/devices'):
            if isfile and x.endswith('idVendor'):
                usb_dir = d(x)
                for y in ('idProduct', 'idVendor', 'bcdDevice'):
                    if not os.access(j(usb_dir, y), os.R_OK):
                        usb_dir = None
                        continue
                e = lambda q: raw2num(open(j(usb_dir, q)).read())
                ven, prod, bcd = map(e, ('idVendor', 'idProduct', 'bcdDevice'))
                if not (test(ven, 'idVendor') and test(prod, 'idProduct')
                        and test(bcd, 'bcdDevice')):
                    usb_dir = None
                    continue
                else:
                    break

        if usb_dir is None:
            raise DeviceError(
                _('Unable to detect the %s disk drive.') %
                self.__class__.__name__)

        devnodes, ok = [], {}
        for x, isfile in walk(usb_dir):
            if not isfile and '/block/' in x:
                parts = x.split('/')
                idx = parts.index('block')
                if idx == len(parts) - 2:
                    sz = j(x, 'size')
                    node = parts[idx + 1]
                    try:
                        exists = int(open(sz).read()) > 0
                        if exists:
                            node = self.find_largest_partition(x)
                            ok[node] = True
                        else:
                            ok[node] = False
                    except:
                        ok[node] = False
                    if DEBUG and not ok[node]:
                        print(
                            '\nIgnoring the node: %s as could not read size from: %s'
                            % (node, sz))

                    devnodes.append(node)

        devnodes += list(repeat(None, 3))
        ans = ['/dev/' + x if ok.get(x, False) else None for x in devnodes]
        ans.sort(key=lambda x: x[5:] if x else 'zzzzz')
        return self.linux_swap_drives(ans[:3])
예제 #7
0
    def _osx_bsd_names(self):
        drives = self.osx_get_usb_drives()
        matches = []
        d = self.detected_device
        if d.serial:
            for path, vid, pid, bcd, ven, prod, serial in drives:
                if d.match_serial(serial):
                    matches.append(path)
        if not matches and d.manufacturer and d.product:
            for path, vid, pid, bcd, man, prod, serial in drives:
                if d.match_strings(vid, pid, bcd, man, prod):
                    matches.append(path)
        if not matches:
            # Since Apple started mangling the names stored in the IOKit
            # registry, we cannot trust match_strings() so fallback to matching
            # on just numbers. See http://www.mobileread.com/forums/showthread.php?t=273213
            for path, vid, pid, bcd, man, prod, serial in drives:
                if d.match_numbers(vid, pid, bcd):
                    matches.append(path)
        if not matches:
            from pprint import pformat
            raise DeviceError(
                'Could not detect BSD names for %s. Try rebooting.\nOutput from osx_get_usb_drives():\n%s'
                % (self.name, pformat(drives)))

        pat = re.compile(r'(?P<m>\d+)([a-z]+(?P<p>\d+)){0,1}')

        def nums(x):
            'Return (disk num, partition number)'
            m = pat.search(x)
            if m is None:
                return (10000, -1)
            g = m.groupdict()
            if g['p'] is None:
                g['p'] = 0
            return map(int, (g.get('m'), g.get('p')))

        def dcmp(x, y):
            '''
            Sorting based on the following scheme:
                - disks without partitions are first
                  - sub sorted based on disk number
                - disks with partitions are sorted first on
                  disk number, then on partition number
            '''
            x = x.rpartition('/')[-1]
            y = y.rpartition('/')[-1]
            x, y = nums(x), nums(y)
            if x[1] == 0 and y[1] > 0:
                return cmp(1, 2)
            if x[1] > 0 and y[1] == 0:
                return cmp(2, 1)
            ans = cmp(x[0], y[0])
            if ans == 0:
                ans = cmp(x[1], y[1])
            return ans

        matches.sort(cmp=dcmp)
        drives = {'main': matches[0]}
        if len(matches) > 1:
            drives['carda'] = matches[1]
        if len(matches) > 2:
            drives['cardb'] = matches[2]

        return drives
예제 #8
0
    def open_freebsd(self):
        import dbus
        # There should be some way to access the -v arg...
        verbose = False

        # this gives us access to the S/N, etc. of the reader that the scanner has found
        # and the match routines for some of that data, like s/n, vendor ID, etc.
        d = self.detected_device

        if not d.serial:
            raise DeviceError("Device has no S/N.  Can't continue")
            return False

        vols = []

        bus = dbus.SystemBus()
        manager = dbus.Interface(
            bus.get_object('org.freedesktop.Hal',
                           '/org/freedesktop/Hal/Manager'),
            'org.freedesktop.Hal.Manager')
        paths = manager.FindDeviceStringMatch('usb.serial', d.serial)
        for path in paths:
            objif = dbus.Interface(bus.get_object('org.freedesktop.Hal', path),
                                   'org.freedesktop.Hal.Device')
            # Extra paranoia...
            try:
                if d.idVendor == objif.GetProperty('usb.vendor_id') and \
                        d.idProduct == objif.GetProperty('usb.product_id') and \
                        d.manufacturer == objif.GetProperty('usb.vendor') and \
                        d.product == objif.GetProperty('usb.product') and \
                        d.serial == objif.GetProperty('usb.serial'):
                    dpaths = manager.FindDeviceStringMatch(
                        'storage.originating_device', path)
                    for dpath in dpaths:
                        #devif = dbus.Interface(bus.get_object('org.freedesktop.Hal', dpath), 'org.freedesktop.Hal.Device')
                        try:
                            vpaths = manager.FindDeviceStringMatch(
                                'block.storage_device', dpath)
                            for vpath in vpaths:
                                try:
                                    vdevif = dbus.Interface(
                                        bus.get_object('org.freedesktop.Hal',
                                                       vpath),
                                        'org.freedesktop.Hal.Device')
                                    if not vdevif.GetProperty(
                                            'block.is_volume'):
                                        continue
                                    if vdevif.GetProperty(
                                            'volume.fsusage') != 'filesystem':
                                        continue
                                    volif = dbus.Interface(
                                        bus.get_object('org.freedesktop.Hal',
                                                       vpath),
                                        'org.freedesktop.Hal.Device.Volume')
                                    pdevif = dbus.Interface(
                                        bus.get_object(
                                            'org.freedesktop.Hal',
                                            vdevif.GetProperty('info.parent')),
                                        'org.freedesktop.Hal.Device')
                                    vol = {
                                        'node':
                                        pdevif.GetProperty('block.device'),
                                        'dev': vdevif,
                                        'vol': volif,
                                        'label':
                                        vdevif.GetProperty('volume.label')
                                    }
                                    vols.append(vol)
                                except dbus.exceptions.DBusException, e:
                                    print e
                                    continue
                        except dbus.exceptions.DBusException, e:
                            print e
                            continue
            except dbus.exceptions.DBusException, e:
                continue
예제 #9
0
    def _osx_bsd_names(self):
        drives = self.osx_get_usb_drives()
        matches = []
        d = self.detected_device
        if d.serial:
            for path, vid, pid, bcd, ven, prod, serial in drives:
                if d.match_serial(serial):
                    matches.append(path)
        if not matches:
            if d.manufacturer and d.product:
                for path, vid, pid, bcd, man, prod, serial in drives:
                    if d.match_strings(vid, pid, bcd, man, prod):
                        matches.append(path)
            else:
                for path, vid, pid, bcd, man, prod, serial in drives:
                    if d.match_numbers(vid, pid, bcd):
                        matches.append(path)
        if not matches:
            raise DeviceError(
                'Could not detect BSD names for %s. Try rebooting.' %
                self.name)

        pat = re.compile(r'(?P<m>\d+)([a-z]+(?P<p>\d+)){0,1}')

        def nums(x):
            'Return (disk num, partition number)'
            m = pat.search(x)
            if m is None:
                return (10000, -1)
            g = m.groupdict()
            if g['p'] is None:
                g['p'] = 0
            return map(int, (g.get('m'), g.get('p')))

        def dcmp(x, y):
            '''
            Sorting based on the following scheme:
                - disks without partitions are first
                  - sub sorted based on disk number
                - disks with partitions are sorted first on
                  disk number, then on partition number
            '''
            x = x.rpartition('/')[-1]
            y = y.rpartition('/')[-1]
            x, y = nums(x), nums(y)
            if x[1] == 0 and y[1] > 0:
                return cmp(1, 2)
            if x[1] > 0 and y[1] == 0:
                return cmp(2, 1)
            ans = cmp(x[0], y[0])
            if ans == 0:
                ans = cmp(x[1], y[1])
            return ans

        matches.sort(cmp=dcmp)
        drives = {'main': matches[0]}
        if len(matches) > 1:
            drives['carda'] = matches[1]
        if len(matches) > 2:
            drives['cardb'] = matches[2]

        return drives
예제 #10
0
    def open_windows(self):
        from calibre.devices.scanner import win_pnp_drives, drivecmp

        time.sleep(5)
        drives = {}
        seen = set()
        prod_pat = re.compile(r'PROD_(.+?)&')
        dup_prod_id = False

        def check_for_dups(pnp_id):
            try:
                match = prod_pat.search(pnp_id)
                if match is not None:
                    prodid = match.group(1)
                    if prodid in seen:
                        return True
                    else:
                        seen.add(prodid)
            except:
                pass
            return False

        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
                dup_prod_id |= check_for_dups(pnp_id)
            elif self.windows_match_device(pnp_id, 'WINDOWS_CARD_B_MEM') and \
                    not drives.get('cardb', False):
                drives['cardb'] = drive
                dup_prod_id |= check_for_dups(pnp_id)
            elif self.windows_match_device(pnp_id, 'WINDOWS_MAIN_MEM') and \
                    not drives.get('main', False):
                drives['main'] = drive
                dup_prod_id |= check_for_dups(pnp_id)

            if 'main' in drives.keys() and 'carda' in drives.keys() and \
                    'cardb' in drives.keys():
                break

        # This is typically needed when the device has the same
        # WINDOWS_MAIN_MEM and WINDOWS_CARD_A_MEM in which case
        # if the device is connected without a card, the above
        # will incorrectly identify the main mem as carda
        # See for example the driver for the Nook
        if drives.get('carda', None) is not None and \
                drives.get('main', None) is None:
            drives['main'] = drives.pop('carda')

        if drives.get('main', None) is None:
            raise DeviceError(
                _('Unable to detect the %s disk drive. Try rebooting.') %
                self.__class__.__name__)

        # Sort drives by their PNP drive numbers if the CARD and MAIN
        # MEM strings are identical
        if dup_prod_id or \
                self.WINDOWS_MAIN_MEM in (self.WINDOWS_CARD_A_MEM,
                self.WINDOWS_CARD_B_MEM) or \
                self.WINDOWS_CARD_A_MEM == self.WINDOWS_CARD_B_MEM:
            letters = sorted(drives.values(), cmp=drivecmp)
            drives = {}
            for which, letter in zip(['main', 'carda', 'cardb'], letters):
                drives[which] = letter

        drives = self.windows_sort_drives(drives)
        self._main_prefix = drives.get('main')
        self._card_a_prefix = drives.get('carda', None)
        self._card_b_prefix = drives.get('cardb', None)