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>'''
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.
9
import os, subprocess, time, re
10
import os, subprocess, time, re, sys, glob, shutil
11
from itertools import repeat
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
15
class Device(_Device):
20
class Device(DeviceConfig, DevicePlugin):
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
125
149
return total_clusters * mult, free_clusters * mult
127
151
def total_space(self, end_session=True):
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)
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]
168
return (msz, casz, cbsz)
142
170
def free_space(self, end_session=True):
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
152
183
msz = self._windows_space(self._main_prefix)[1]
153
csz = self._windows_space(self._card_prefix)[1]
157
def windows_match_device(self, pnp_id, device_id):
158
pnp_id = pnp_id.upper()
160
if device_id and pnp_id is not None:
161
device_id = device_id.upper()
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]
187
return (msz, casz, cbsz)
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:
196
if hasattr(device_id, 'search'):
197
return device_id.search(pnp_id) is not None
199
if isinstance(device_id, basestring):
200
device_id = [device_id]
205
if 'PROD_' + x in pnp_id:
188
230
def open_windows(self):
232
def matches_q(drive, attr):
233
q = getattr(self, attr)
234
if q is None: return False
235
if isinstance(q, basestring):
237
pnp = str(drive.PNPDeviceID)
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)
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():
202
260
if 'main' not in drives:
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
335
def get_card_prefix(pat):
338
return re.search(pat, mount).group(2) + os.sep
342
self._card_a_prefix = get_card_prefix(card_a_pat)
343
self._card_b_prefix = get_card_prefix(card_b_pat)
345
def find_device_nodes(self):
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):
353
isfile = os.path.isfile(p)
361
if not raw.startswith('0x'):
365
# Find device node based on vendor, product and bcd
366
d, j = os.path.dirname, os.path.join
370
q = getattr(self, attr)
371
if q is None: return True
372
return q == val or val in q
374
for x, isfile in walk('/sys/devices'):
375
if isfile and x.endswith('idVendor'):
377
for y in ('idProduct',):
378
if not os.access(j(usb_dir, y), os.R_OK):
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')):
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'):
397
raise DeviceError(_('Unable to detect the %s disk drive.')
398
%self.__class__.__name__)
400
devnodes, ok = [], {}
401
for x, isfile in walk(usb_dir):
402
if not isfile and '/block/' in x:
404
idx = parts.index('block')
405
if idx == len(parts)-2:
409
exists = int(open(sz).read()) > 0
411
node = self.find_largest_partition(x)
417
devnodes.append(node)
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)
423
def linux_swap_drives(self, drives):
426
def node_mountpoint(self, node):
429
return raw.replace('\\040', ' ').replace('\\011', '\t').replace('\\012',
430
'\n').replace('\\0134', '\\')
432
for line in open('/proc/mounts').readlines():
435
return de_mangle(line[1])
438
def find_largest_partition(self, path):
439
node = path.split('/')[-1]
441
for x in glob.glob(path+'/'+node+'*'):
444
if not os.access(sz, os.R_OK):
447
sz = int(open(sz).read())
451
nodes.append((x.split('/')[-1], sz))
453
nodes.sort(cmp=lambda x, y: cmp(x[1], y[1]))
259
459
def open_linux(self):
261
bus = dbus.SystemBus()
262
hm = dbus.Interface(bus.get_object("org.freedesktop.Hal", "/org/freedesktop/Hal/Manager"), "org.freedesktop.Hal.Manager")
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')
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)+'/'
276
mm = hm.FindDeviceStringMatch(__appname__+'.mainvolume', self.__class__.__name__)
278
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%(self.__class__.__name__,))
279
self._main_prefix = None
282
self._main_prefix = conditional_mount(dev)+os.sep
284
except dbus.exceptions.DBusException:
287
if not self._main_prefix:
288
raise DeviceError('Could not open device for reading. Try a reboot.')
290
self._card_prefix = None
291
cards = hm.FindDeviceStringMatch(__appname__+'.cardvolume', self.__class__.__name__)
295
self._card_prefix = conditional_mount(dev)+os.sep
461
def mount(node, type):
462
mp = self.node_mountpoint(node)
466
label = self.MAIN_MEMORY_VOLUME_LABEL
468
label = self.STORAGE_CARD_VOLUME_LABEL
470
label = self.STORAGE_CARD2_VOLUME_LABEL
472
label = self.STORAGE_CARD_VOLUME_LABEL + ' 2'
475
q = ' (%d)'%extra if extra else ''
476
if not os.path.exists('/media/'+label+q):
480
label += ' (%d)'%extra
482
def do_mount(node, label):
483
cmd = ['pmount', '-w', '-s']
485
p = subprocess.Popen(cmd + [node, label])
487
raise DeviceError(_('You must install the pmount package.'))
488
while p.poll() is None:
492
ret = do_mount(node, label)
495
return self.node_mountpoint(node)+'/', 0
498
main, carda, cardb = self.find_device_nodes()
500
raise DeviceError(_('Unable to detect the %s disk drive.')
501
%self.__class__.__name__)
503
mp, ret = mount(main, 'main')
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)
515
print >>sys.stderr, 'Unable to mount card (Error code: %d)'%ret
517
if not mp.endswith('/'): mp += '/'
518
setattr(self, prefix, mp)
304
self._main_prefix = self._card_prefix = None
522
self._main_prefix = self._card_a_prefix = self._card_b_prefix = None
307
525
self.open_linux()
542
def eject_windows(self):
543
from calibre.constants import plugins
544
from threading import Thread
545
winutil, winutil_err = plugins['winutil']
547
for x in ('_main_prefix', '_card_a_prefix', '_card_b_prefix'):
548
x = getattr(self, x, None)
550
drives.append(x[0].upper())
555
winutil.eject_drive(bytes(d)[0])
559
t = Thread(target=do_it, args=[drives])
562
self.__save_win_eject_thread = t
565
for x in ('_main_prefix', '_card_a_prefix', '_card_b_prefix'):
566
x = getattr(self, x, None)
569
subprocess.Popen(['diskutil', 'eject', x])
573
def eject_linux(self):
574
drives = self.find_device_nodes()
578
cmd = ['pumount', '-l']
580
p = subprocess.Popen(cmd + [drive])
583
while p.poll() is None:
585
success = success or p.returncode == 0
587
subprocess.Popen(['sudo', 'eject', drive])
590
for x in ('_main_prefix', '_card_a_prefix', '_card_b_prefix'):
591
x = getattr(self, x, None)
593
if x.startswith('/media/') and os.path.exists(x) \
594
and not os.listdir(x):
617
self._main_prefix = self._card_a_prefix = self._card_b_prefix = None
619
def create_upload_path(self, path, mdata, fname):
622
if self.SUPPORTS_SUB_DIRS and self.settings().use_subdirs:
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', ''))
630
newpath = os.path.join(newpath, c)
632
c = sanitize(mdata.get('timestamp', ''))
634
newpath = os.path.join(newpath, c)
637
elif tag.startswith('/'):
638
for c in tag.split('/'):
641
newpath = os.path.join(newpath, c)
646
c = sanitize(mdata.get('authors', _('Unknown')))
648
newpath = os.path.join(newpath, c)
650
c = sanitize(mdata.get('title', _('Unknown')))
652
newpath = os.path.join(newpath, c)
655
newpath = os.path.abspath(newpath)
656
fname = sanitize(fname)
657
resizable.append(fname)
659
filepath = os.path.join(newpath, fname)
661
if len(filepath) > 245:
662
extra = len(filepath) - 245
663
delta = int(ceil(extra/float(len(resizable))))
666
r = x[0] if x is resizable[-1] else ''
668
if x is resizable[-1]:
669
b, e = os.path.splitext(x)
671
if r.startswith('.'): r = x[0]+r
674
if x is resizable[-1]:
675
filepath = filepath.replace(os.sep+x, os.sep+r)
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)
682
if not os.path.exists(newpath):