~ubuntu-branches/ubuntu/karmic/calibre/karmic

« back to all changes in this revision

Viewing changes to src/calibre/devices/usbms/device.py

  • Committer: Bazaar Package Importer
  • Author(s): Martin Pitt
  • Date: 2009-07-30 12:49:41 UTC
  • mfrom: (1.3.2 upstream)
  • Revision ID: james.westby@ubuntu.com-20090730124941-qjdsmri25zt8zocn
Tags: 0.6.3+dfsg-0ubuntu1
* New upstream release. Please see http://calibre.kovidgoyal.net/new_in_6/
  for the list of new features and changes.
* remove_postinstall.patch: Update for new version.
* build_debug.patch: Does not apply any more, disable for now. Might not be
  necessary any more.
* debian/copyright: Fix reference to versionless GPL.
* debian/rules: Drop obsolete dh_desktop call.
* debian/rules: Add workaround for weird Python 2.6 setuptools behaviour of
  putting compiled .so files into src/calibre/plugins/calibre/plugins
  instead of src/calibre/plugins.
* debian/rules: Drop hal fdi moving, new upstream version does not use hal
  any more. Drop hal dependency, too.
* debian/rules: Install udev rules into /lib/udev/rules.d.
* Add debian/calibre.preinst: Remove unmodified
  /etc/udev/rules.d/95-calibre.rules on upgrade.
* debian/control: Bump Python dependencies to 2.6, since upstream needs
  it now.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
__license__   = 'GPL v3'
2
 
__copyright__ = '2009, John Schember <john at nachtimwald.com>'
 
2
__copyright__ = '''2009, John Schember <john at nachtimwald.com>
 
3
                    and Kovid Goyal <kovid@kovidgoyal.net>'''
3
4
'''
4
5
Generic device driver. This is not a complete stand alone driver. It is
5
6
intended to be subclassed with the relevant parts implemented for a particular
6
7
device. This class handles device detection.
7
8
'''
8
9
 
9
 
import os, subprocess, time, re
 
10
import os, subprocess, time, re, sys, glob, shutil
 
11
from itertools import repeat
 
12
from math import ceil
10
13
 
11
 
from calibre.devices.interface import Device as _Device
 
14
from calibre.devices.interface import DevicePlugin
12
15
from calibre.devices.errors import DeviceError
 
16
from calibre.devices.usbms.deviceconfig import DeviceConfig
13
17
from calibre import iswindows, islinux, isosx, __appname__
 
18
from calibre.utils.filenames import ascii_filename as sanitize
14
19
 
15
 
class Device(_Device):
 
20
class Device(DeviceConfig, DevicePlugin):
16
21
    '''
17
22
    This class provides logic common to all drivers for devices that export themselves
18
23
    as USB Mass Storage devices. If you are writing such a driver, inherit from this
25
30
 
26
31
    VENDOR_NAME = None
27
32
    WINDOWS_MAIN_MEM = None
28
 
    WINDOWS_CARD_MEM = None
 
33
    WINDOWS_CARD_A_MEM = None
 
34
    WINDOWS_CARD_B_MEM = None
29
35
 
 
36
    # The following are used by the check_ioreg_line method and can be either:
 
37
    # None, a string, a list of strings or a compiled regular expression
30
38
    OSX_MAIN_MEM = None
31
 
    OSX_CARD_MEM = None
 
39
    OSX_CARD_A_MEM = None
 
40
    OSX_CARD_B_MEM = None
32
41
 
33
42
    MAIN_MEMORY_VOLUME_LABEL  = ''
34
43
    STORAGE_CARD_VOLUME_LABEL = ''
 
44
    STORAGE_CARD2_VOLUME_LABEL = None
 
45
 
 
46
    SUPPORTS_SUB_DIRS = False
 
47
    MUST_READ_METADATA = False
35
48
 
36
49
    FDI_TEMPLATE = \
37
50
'''
63
76
          </match>
64
77
      </match>
65
78
  </device>
 
79
  <device>
 
80
      <match key="info.category" string="volume">
 
81
          <match key="@info.parent:@info.parent:@info.parent:@info.parent:usb.vendor_id" int="%(vendor_id)s">
 
82
              <match key="@info.parent:@info.parent:@info.parent:@info.parent:usb.product_id" int="%(product_id)s">
 
83
                %(BCD_start)s
 
84
                  <match key="@info.parent:storage.lun" int="%(lun2)d">
 
85
                          <merge key="volume.label" type="string">%(storage_card)s</merge>
 
86
                          <merge key="%(app)s.cardvolume" type="string">%(deviceclass)s</merge>
 
87
                  </match>
 
88
                %(BCD_end)s
 
89
              </match>
 
90
          </match>
 
91
      </match>
 
92
  </device>
66
93
'''
 
94
    FDI_LUNS = {'lun0':0, 'lun1':1, 'lun2':2}
67
95
    FDI_BCD_TEMPLATE = '<match key="@info.parent:@info.parent:@info.parent:@info.parent:usb.device_revision_bcd" int="%(bcd)s">'
68
 
    FDI_LUNS = {'lun0':0, 'lun1':1, 'lun2':2}
69
 
 
70
 
 
71
 
    def __init__(self, key='-1', log_packets=False, report_progress=None) :
72
 
        self._main_prefix = self._card_prefix = None
 
96
 
 
97
    def reset(self, key='-1', log_packets=False, report_progress=None) :
 
98
        self._main_prefix = self._card_a_prefix = self._card_b_prefix = None
73
99
 
74
100
    @classmethod
75
101
    def get_fdi(cls):
76
102
        fdi = ''
77
 
 
78
103
        for vid in cls.VENDOR_ID:
79
104
            for pid in cls.PRODUCT_ID:
80
105
                fdi_base_values = dict(
85
110
                                       main_memory=cls.MAIN_MEMORY_VOLUME_LABEL,
86
111
                                       storage_card=cls.STORAGE_CARD_VOLUME_LABEL,
87
112
                                  )
88
 
 
89
113
                fdi_base_values.update(cls.FDI_LUNS)
90
114
 
91
115
                if cls.BCD is None:
105
129
        self.report_progress = report_progress
106
130
 
107
131
    def card_prefix(self, end_session=True):
108
 
        return self._card_prefix
 
132
        return (self._card_a_prefix, self._card_b_prefix)
109
133
 
110
134
    @classmethod
111
135
    def _windows_space(cls, prefix):
125
149
        return total_clusters * mult, free_clusters * mult
126
150
 
127
151
    def total_space(self, end_session=True):
128
 
        msz = csz = 0
 
152
        msz = casz = cbsz = 0
129
153
        if not iswindows:
130
154
            if self._main_prefix is not None:
131
155
                stats = os.statvfs(self._main_prefix)
132
156
                msz = stats.f_frsize * (stats.f_blocks + stats.f_bavail - stats.f_bfree)
133
 
            if self._card_prefix is not None:
134
 
                stats = os.statvfs(self._card_prefix)
135
 
                csz = stats.f_frsize * (stats.f_blocks + stats.f_bavail - stats.f_bfree)
 
157
            if self._card_a_prefix is not None:
 
158
                stats = os.statvfs(self._card_a_prefix)
 
159
                casz = stats.f_frsize * (stats.f_blocks + stats.f_bavail - stats.f_bfree)
 
160
            if self._card_b_prefix is not None:
 
161
                stats = os.statvfs(self._card_b_prefix)
 
162
                cbsz = stats.f_frsize * (stats.f_blocks + stats.f_bavail - stats.f_bfree)
136
163
        else:
137
164
            msz = self._windows_space(self._main_prefix)[0]
138
 
            csz = self._windows_space(self._card_prefix)[0]
 
165
            casz = self._windows_space(self._card_a_prefix)[0]
 
166
            cbsz = self._windows_space(self._card_b_prefix)[0]
139
167
 
140
 
        return (msz, 0, csz)
 
168
        return (msz, casz, cbsz)
141
169
 
142
170
    def free_space(self, end_session=True):
143
 
        msz = csz = 0
 
171
        msz = casz = cbsz = 0
144
172
        if not iswindows:
145
173
            if self._main_prefix is not None:
146
174
                stats = os.statvfs(self._main_prefix)
147
175
                msz = stats.f_frsize * stats.f_bavail
148
 
            if self._card_prefix is not None:
149
 
                stats = os.statvfs(self._card_prefix)
150
 
                csz = stats.f_frsize * stats.f_bavail
 
176
            if self._card_a_prefix is not None:
 
177
                stats = os.statvfs(self._card_a_prefix)
 
178
                casz = stats.f_frsize * stats.f_bavail
 
179
            if self._card_b_prefix is not None:
 
180
                stats = os.statvfs(self._card_b_prefix)
 
181
                cbsz = stats.f_frsize * stats.f_bavail
151
182
        else:
152
183
            msz = self._windows_space(self._main_prefix)[1]
153
 
            csz = self._windows_space(self._card_prefix)[1]
154
 
 
155
 
        return (msz, 0, csz)
156
 
 
157
 
    def windows_match_device(self, pnp_id, device_id):
158
 
        pnp_id = pnp_id.upper()
159
 
 
160
 
        if device_id and pnp_id is not None:
161
 
            device_id = device_id.upper()
162
 
 
163
 
            if 'VEN_' + self.VENDOR_NAME in pnp_id and 'PROD_' + device_id in pnp_id:
 
184
            casz = self._windows_space(self._card_a_prefix)[1]
 
185
            cbsz = self._windows_space(self._card_b_prefix)[1]
 
186
 
 
187
        return (msz, casz, cbsz)
 
188
 
 
189
    def windows_match_device(self, drive, attr):
 
190
        pnp_id = str(drive.PNPDeviceID).upper()
 
191
        device_id = getattr(self, attr)
 
192
        if device_id is None or \
 
193
                'VEN_' + str(self.VENDOR_NAME).upper() not in pnp_id:
 
194
            return False
 
195
 
 
196
        if hasattr(device_id, 'search'):
 
197
            return device_id.search(pnp_id) is not None
 
198
 
 
199
        if isinstance(device_id, basestring):
 
200
            device_id = [device_id]
 
201
 
 
202
        for x in device_id:
 
203
            x = x.upper()
 
204
 
 
205
            if 'PROD_' + x in pnp_id:
164
206
                return True
165
207
 
166
208
        return False
186
228
        return drives
187
229
 
188
230
    def open_windows(self):
 
231
 
 
232
        def matches_q(drive, attr):
 
233
            q = getattr(self, attr)
 
234
            if q is None: return False
 
235
            if isinstance(q, basestring):
 
236
                q = [q]
 
237
            pnp = str(drive.PNPDeviceID)
 
238
            for x in q:
 
239
                if x in pnp:
 
240
                    return True
 
241
            return False
 
242
 
 
243
 
189
244
        time.sleep(6)
190
245
        drives = {}
191
246
        wmi = __import__('wmi', globals(), locals(), [], -1)
192
247
        c = wmi.WMI(find_classes=False)
193
248
        for drive in c.Win32_DiskDrive():
194
 
            if self.windows_match_device(str(drive.PNPDeviceID), self.WINDOWS_MAIN_MEM):
 
249
            if self.windows_match_device(drive, 'WINDOWS_CARD_A_MEM') and not drives.get('carda', None):
 
250
                drives['carda'] = self.windows_get_drive_prefix(drive)
 
251
            elif self.windows_match_device(drive, 'WINDOWS_CARD_B_MEM') and not drives.get('cardb', None):
 
252
                drives['cardb'] = self.windows_get_drive_prefix(drive)
 
253
            elif self.windows_match_device(drive, 'WINDOWS_MAIN_MEM') and not drives.get('main', None):
195
254
                drives['main'] = self.windows_get_drive_prefix(drive)
196
 
            elif self.windows_match_device(str(drive.PNPDeviceID), self.WINDOWS_CARD_MEM):
197
 
                drives['card'] = self.windows_get_drive_prefix(drive)
198
255
 
199
 
            if 'main' in drives.keys() and 'card' in drives.keys():
 
256
            if 'main' in drives.keys() and 'carda' in drives.keys() and \
 
257
                    'cardb' in drives.keys():
200
258
                break
201
259
 
202
260
        if 'main' not in drives:
206
264
 
207
265
        drives = self.windows_sort_drives(drives)
208
266
        self._main_prefix = drives.get('main')
209
 
        self._card_prefix = drives.get('card', None)
 
267
        self._card_a_prefix = drives.get('carda', None)
 
268
        self._card_b_prefix = drives.get('cardb', None)
210
269
 
211
270
    @classmethod
212
271
    def run_ioreg(cls, raw=None):
218
277
        return subprocess.Popen((ioreg+' -w 0 -S -c IOMedia').split(),
219
278
                                stdout=subprocess.PIPE).communicate()[0]
220
279
 
 
280
    def osx_sort_names(self, names):
 
281
        return names
 
282
 
 
283
    def check_ioreg_line(self, line, pat):
 
284
        if pat is None:
 
285
            return False
 
286
        if not line.strip().endswith('<class IOMedia>'):
 
287
            return False
 
288
        if hasattr(pat, 'search'):
 
289
            return pat.search(line) is not None
 
290
        if isinstance(pat, basestring):
 
291
            pat = [pat]
 
292
        for x in pat:
 
293
            if x in line:
 
294
                return True
 
295
        return False
 
296
 
221
297
 
222
298
    def get_osx_mountpoints(self, raw=None):
223
299
        raw = self.run_ioreg(raw)
235
311
                    break
236
312
 
237
313
        for i, line in enumerate(lines):
238
 
            if self.OSX_MAIN_MEM is not None and line.strip().endswith('<class IOMedia>') and self.OSX_MAIN_MEM in line:
 
314
            if 'main' not in names and self.check_ioreg_line(line, self.OSX_MAIN_MEM):
239
315
                get_dev_node(lines[i+1:], 'main')
240
 
            if self.OSX_CARD_MEM is not None and line.strip().endswith('<class IOMedia>') and self.OSX_CARD_MEM in line:
241
 
                get_dev_node(lines[i+1:], 'card')
242
 
            if len(names.keys()) == 2:
 
316
            if 'carda' not in names and self.check_ioreg_line(line, self.OSX_CARD_A_MEM):
 
317
                get_dev_node(lines[i+1:], 'carda')
 
318
            if 'cardb' not in names and self.check_ioreg_line(line, self.OSX_CARD_B_MEM):
 
319
                get_dev_node(lines[i+1:], 'cardb')
 
320
            if len(names.keys()) == 3:
243
321
                break
244
 
        return names
 
322
        return self.osx_sort_names(names)
245
323
 
246
324
    def open_osx(self):
247
325
        mount = subprocess.Popen('mount', shell=True,  stdout=subprocess.PIPE).stdout.read()
251
329
            raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%self.__class__.__name__)
252
330
        main_pat = dev_pat % names['main']
253
331
        self._main_prefix = re.search(main_pat, mount).group(2) + os.sep
254
 
        card_pat = names['card'] if 'card' in names.keys() else None
255
 
        if card_pat is not None:
256
 
            card_pat = dev_pat % card_pat
257
 
            self._card_prefix = re.search(card_pat, mount).group(2) + os.sep
 
332
        card_a_pat = names['carda'] if 'carda' in names.keys() else None
 
333
        card_b_pat = names['cardb'] if 'cardb' in names.keys() else None
 
334
 
 
335
        def get_card_prefix(pat):
 
336
            if pat is not None:
 
337
                pat = dev_pat % pat
 
338
                return re.search(pat, mount).group(2) + os.sep
 
339
            else:
 
340
                return None
 
341
 
 
342
        self._card_a_prefix = get_card_prefix(card_a_pat)
 
343
        self._card_b_prefix = get_card_prefix(card_b_pat)
 
344
 
 
345
    def find_device_nodes(self):
 
346
 
 
347
        def walk(base):
 
348
            base = os.path.abspath(os.path.realpath(base))
 
349
            for x in os.listdir(base):
 
350
                p = os.path.join(base, x)
 
351
                if os.path.islink(p) or not os.access(p, os.R_OK):
 
352
                    continue
 
353
                isfile = os.path.isfile(p)
 
354
                yield p, isfile
 
355
                if not isfile:
 
356
                    for y, q in walk(p):
 
357
                        yield y, q
 
358
 
 
359
        def raw2num(raw):
 
360
            raw = raw.lower()
 
361
            if not raw.startswith('0x'):
 
362
                raw = '0x' + raw
 
363
            return int(raw, 16)
 
364
 
 
365
        # Find device node based on vendor, product and bcd
 
366
        d, j = os.path.dirname, os.path.join
 
367
        usb_dir = None
 
368
 
 
369
        def test(val, attr):
 
370
            q = getattr(self, attr)
 
371
            if q is None: return True
 
372
            return q == val or val in q
 
373
 
 
374
        for x, isfile in walk('/sys/devices'):
 
375
            if isfile and x.endswith('idVendor'):
 
376
                usb_dir = d(x)
 
377
                for y in ('idProduct',):
 
378
                    if not os.access(j(usb_dir, y), os.R_OK):
 
379
                        usb_dir = None
 
380
                        continue
 
381
                e = lambda q : raw2num(open(j(usb_dir, q)).read())
 
382
                ven, prod = map(e, ('idVendor', 'idProduct'))
 
383
                if not (test(ven, 'VENDOR_ID') and test(prod, 'PRODUCT_ID')):
 
384
                    usb_dir = None
 
385
                    continue
 
386
                if self.BCD is not None:
 
387
                    if not os.access(j(usb_dir, 'bcdDevice'), os.R_OK) or \
 
388
                            not test(e('bcdDevice'), 'BCD'):
 
389
                        usb_dir = None
 
390
                        continue
 
391
                    else:
 
392
                        break
 
393
                else:
 
394
                    break
 
395
 
 
396
        if usb_dir is None:
 
397
            raise DeviceError(_('Unable to detect the %s disk drive.')
 
398
                    %self.__class__.__name__)
 
399
 
 
400
        devnodes, ok = [], {}
 
401
        for x, isfile in walk(usb_dir):
 
402
            if not isfile and '/block/' in x:
 
403
                parts = x.split('/')
 
404
                idx = parts.index('block')
 
405
                if idx == len(parts)-2:
 
406
                    sz = j(x, 'size')
 
407
                    node = parts[idx+1]
 
408
                    try:
 
409
                        exists = int(open(sz).read()) > 0
 
410
                        if exists:
 
411
                            node = self.find_largest_partition(x)
 
412
                            ok[node] = True
 
413
                        else:
 
414
                            ok[node] = False
 
415
                    except:
 
416
                        ok[node] = False
 
417
                    devnodes.append(node)
 
418
 
 
419
        devnodes += list(repeat(None, 3))
 
420
        ans = tuple(['/dev/'+x if ok.get(x, False) else None for x in devnodes[:3]])
 
421
        return self.linux_swap_drives(ans)
 
422
 
 
423
    def linux_swap_drives(self, drives):
 
424
        return drives
 
425
 
 
426
    def node_mountpoint(self, node):
 
427
 
 
428
        def de_mangle(raw):
 
429
            return raw.replace('\\040', ' ').replace('\\011', '\t').replace('\\012',
 
430
                    '\n').replace('\\0134', '\\')
 
431
 
 
432
        for line in open('/proc/mounts').readlines():
 
433
            line = line.split()
 
434
            if line[0] == node:
 
435
                return de_mangle(line[1])
 
436
        return None
 
437
 
 
438
    def find_largest_partition(self, path):
 
439
        node = path.split('/')[-1]
 
440
        nodes = []
 
441
        for x in glob.glob(path+'/'+node+'*'):
 
442
            sz = x + '/size'
 
443
 
 
444
            if not os.access(sz, os.R_OK):
 
445
                continue
 
446
            try:
 
447
                sz = int(open(sz).read())
 
448
            except:
 
449
                continue
 
450
            if sz > 0:
 
451
                nodes.append((x.split('/')[-1], sz))
 
452
 
 
453
        nodes.sort(cmp=lambda x, y: cmp(x[1], y[1]))
 
454
        if not nodes:
 
455
            return node
 
456
        return nodes[-1][0]
 
457
 
258
458
 
259
459
    def open_linux(self):
260
 
        import dbus
261
 
        bus = dbus.SystemBus()
262
 
        hm  = dbus.Interface(bus.get_object("org.freedesktop.Hal", "/org/freedesktop/Hal/Manager"), "org.freedesktop.Hal.Manager")
263
 
 
264
 
        def conditional_mount(dev):
265
 
            mmo = bus.get_object("org.freedesktop.Hal", dev)
266
 
            label = mmo.GetPropertyString('volume.label', dbus_interface='org.freedesktop.Hal.Device')
267
 
            is_mounted = mmo.GetPropertyString('volume.is_mounted', dbus_interface='org.freedesktop.Hal.Device')
268
 
            mount_point = mmo.GetPropertyString('volume.mount_point', dbus_interface='org.freedesktop.Hal.Device')
269
 
            fstype = mmo.GetPropertyString('volume.fstype', dbus_interface='org.freedesktop.Hal.Device')
270
 
            if is_mounted:
271
 
                return str(mount_point)
272
 
            mmo.Mount(label, fstype, ['umask=077', 'uid='+str(os.getuid()), 'sync'],
273
 
                          dbus_interface='org.freedesktop.Hal.Device.Volume')
274
 
            return os.path.normpath('/media/'+label)+'/'
275
 
 
276
 
        mm = hm.FindDeviceStringMatch(__appname__+'.mainvolume', self.__class__.__name__)
277
 
        if not mm:
278
 
            raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%(self.__class__.__name__,))
279
 
        self._main_prefix = None
280
 
        for dev in mm:
281
 
            try:
282
 
                self._main_prefix = conditional_mount(dev)+os.sep
283
 
                break
284
 
            except dbus.exceptions.DBusException:
285
 
                continue
286
 
 
287
 
        if not self._main_prefix:
288
 
            raise DeviceError('Could not open device for reading. Try a reboot.')
289
 
 
290
 
        self._card_prefix = None
291
 
        cards = hm.FindDeviceStringMatch(__appname__+'.cardvolume', self.__class__.__name__)
292
 
 
293
 
        for dev in cards:
294
 
            try:
295
 
                self._card_prefix = conditional_mount(dev)+os.sep
296
 
                break
297
 
            except:
298
 
                import traceback
299
 
                print traceback
300
 
                continue
 
460
 
 
461
        def mount(node, type):
 
462
            mp = self.node_mountpoint(node)
 
463
            if mp is not None:
 
464
                return mp, 0
 
465
            if type == 'main':
 
466
                label = self.MAIN_MEMORY_VOLUME_LABEL
 
467
            if type == 'carda':
 
468
                label = self.STORAGE_CARD_VOLUME_LABEL
 
469
            if type == 'cardb':
 
470
                label = self.STORAGE_CARD2_VOLUME_LABEL
 
471
                if label is None:
 
472
                    label = self.STORAGE_CARD_VOLUME_LABEL + ' 2'
 
473
            extra = 0
 
474
            while True:
 
475
                q = ' (%d)'%extra if extra else ''
 
476
                if not os.path.exists('/media/'+label+q):
 
477
                    break
 
478
                extra += 1
 
479
            if extra:
 
480
                label += ' (%d)'%extra
 
481
 
 
482
            def do_mount(node, label):
 
483
                cmd = ['pmount', '-w', '-s']
 
484
                try:
 
485
                    p = subprocess.Popen(cmd + [node, label])
 
486
                except OSError:
 
487
                    raise DeviceError(_('You must install the pmount package.'))
 
488
                while p.poll() is None:
 
489
                    time.sleep(0.1)
 
490
                return p.returncode
 
491
 
 
492
            ret = do_mount(node, label)
 
493
            if ret != 0:
 
494
                return None, ret
 
495
            return self.node_mountpoint(node)+'/', 0
 
496
 
 
497
 
 
498
        main, carda, cardb = self.find_device_nodes()
 
499
        if main is None:
 
500
            raise DeviceError(_('Unable to detect the %s disk drive.')
 
501
                    %self.__class__.__name__)
 
502
 
 
503
        mp, ret = mount(main, 'main')
 
504
        if mp is None:
 
505
            raise DeviceError(
 
506
            _('Unable to mount main memory (Error code: %d)')%ret)
 
507
        if not mp.endswith('/'): mp += '/'
 
508
        self._main_prefix = mp
 
509
        cards = [(carda, '_card_a_prefix', 'carda'),
 
510
                 (cardb, '_card_b_prefix', 'cardb')]
 
511
        for card, prefix, typ in cards:
 
512
            if card is None: continue
 
513
            mp, ret = mount(card, typ)
 
514
            if mp is None:
 
515
                print >>sys.stderr, 'Unable to mount card (Error code: %d)'%ret
 
516
            else:
 
517
                if not mp.endswith('/'): mp += '/'
 
518
                setattr(self, prefix, mp)
301
519
 
302
520
    def open(self):
303
521
        time.sleep(5)
304
 
        self._main_prefix = self._card_prefix = None
 
522
        self._main_prefix = self._card_a_prefix = self._card_b_prefix = None
305
523
        if islinux:
306
524
            try:
307
525
                self.open_linux()
321
539
                time.sleep(3)
322
540
                self.open_osx()
323
541
 
 
542
    def eject_windows(self):
 
543
        from calibre.constants import plugins
 
544
        from threading import Thread
 
545
        winutil, winutil_err = plugins['winutil']
 
546
        drives = []
 
547
        for x in ('_main_prefix', '_card_a_prefix', '_card_b_prefix'):
 
548
            x = getattr(self, x, None)
 
549
            if x is not None:
 
550
                drives.append(x[0].upper())
 
551
 
 
552
        def do_it(drives):
 
553
            for d in drives:
 
554
                try:
 
555
                    winutil.eject_drive(bytes(d)[0])
 
556
                except:
 
557
                    pass
 
558
 
 
559
        t = Thread(target=do_it, args=[drives])
 
560
        t.daemon = True
 
561
        t.start()
 
562
        self.__save_win_eject_thread = t
 
563
 
 
564
    def eject_osx(self):
 
565
        for x in ('_main_prefix', '_card_a_prefix', '_card_b_prefix'):
 
566
            x = getattr(self, x, None)
 
567
            if x is not None:
 
568
                try:
 
569
                    subprocess.Popen(['diskutil', 'eject', x])
 
570
                except:
 
571
                    pass
 
572
 
 
573
    def eject_linux(self):
 
574
        drives = self.find_device_nodes()
 
575
        success = False
 
576
        for drive in drives:
 
577
            if drive:
 
578
                cmd = ['pumount', '-l']
 
579
                try:
 
580
                    p = subprocess.Popen(cmd + [drive])
 
581
                except:
 
582
                    pass
 
583
                while p.poll() is None:
 
584
                    time.sleep(0.1)
 
585
                success = success or p.returncode == 0
 
586
                try:
 
587
                    subprocess.Popen(['sudo', 'eject', drive])
 
588
                except:
 
589
                    pass
 
590
        for x in ('_main_prefix', '_card_a_prefix', '_card_b_prefix'):
 
591
            x = getattr(self, x, None)
 
592
            if x is not None:
 
593
                if x.startswith('/media/') and os.path.exists(x) \
 
594
                        and not os.listdir(x):
 
595
                    try:
 
596
                        shutil.rmtree(x)
 
597
                    except:
 
598
                        pass
 
599
 
 
600
 
 
601
    def eject(self):
 
602
        if islinux:
 
603
            try:
 
604
                self.eject_linux()
 
605
            except:
 
606
                pass
 
607
        if iswindows:
 
608
            try:
 
609
                self.eject_windows()
 
610
            except:
 
611
                pass
 
612
        if isosx:
 
613
            try:
 
614
                self.eject_osx()
 
615
            except:
 
616
                pass
 
617
        self._main_prefix = self._card_a_prefix = self._card_b_prefix = None
 
618
 
 
619
    def create_upload_path(self, path, mdata, fname):
 
620
        resizable = []
 
621
        newpath = path
 
622
        if self.SUPPORTS_SUB_DIRS and self.settings().use_subdirs:
 
623
 
 
624
            if 'tags' in mdata.keys():
 
625
                for tag in mdata['tags']:
 
626
                    if tag.startswith(_('News')):
 
627
                        newpath = os.path.join(newpath, 'news')
 
628
                        c = sanitize(mdata.get('title', ''))
 
629
                        if c:
 
630
                            newpath = os.path.join(newpath, c)
 
631
                            resizable.append(c)
 
632
                        c = sanitize(mdata.get('timestamp', ''))
 
633
                        if c:
 
634
                            newpath = os.path.join(newpath, c)
 
635
                            resizable.append(c)
 
636
                        break
 
637
                    elif tag.startswith('/'):
 
638
                        for c in tag.split('/'):
 
639
                            c = sanitize(c)
 
640
                            if not c: continue
 
641
                            newpath = os.path.join(newpath, c)
 
642
                            resizable.append(c)
 
643
                        break
 
644
 
 
645
            if newpath == path:
 
646
                c = sanitize(mdata.get('authors', _('Unknown')))
 
647
                if c:
 
648
                    newpath = os.path.join(newpath, c)
 
649
                    resizable.append(c)
 
650
                c = sanitize(mdata.get('title', _('Unknown')))
 
651
                if c:
 
652
                    newpath = os.path.join(newpath, c)
 
653
                    resizable.append(c)
 
654
 
 
655
        newpath = os.path.abspath(newpath)
 
656
        fname = sanitize(fname)
 
657
        resizable.append(fname)
 
658
 
 
659
        filepath = os.path.join(newpath, fname)
 
660
 
 
661
        if len(filepath) > 245:
 
662
            extra = len(filepath) - 245
 
663
            delta = int(ceil(extra/float(len(resizable))))
 
664
            for x in resizable:
 
665
                if delta > len(x):
 
666
                    r = x[0] if x is resizable[-1] else ''
 
667
                else:
 
668
                    if x is resizable[-1]:
 
669
                        b, e = os.path.splitext(x)
 
670
                        r = b[:-delta]+e
 
671
                        if r.startswith('.'): r = x[0]+r
 
672
                    else:
 
673
                        r = x[:-delta]
 
674
                if x is resizable[-1]:
 
675
                    filepath = filepath.replace(os.sep+x, os.sep+r)
 
676
                else:
 
677
                    filepath = filepath.replace(os.sep+x+os.sep, os.sep+r+os.sep)
 
678
            filepath = filepath.replace(os.sep+os.sep, os.sep)
 
679
            newpath = os.path.dirname(filepath)
 
680
 
 
681
 
 
682
        if not os.path.exists(newpath):
 
683
            os.makedirs(newpath)
 
684
 
 
685
        return filepath
 
686
 
 
687