81
53
return stat.f_bsize * stat.f_bavail
56
l = os.environ.get('USBCREATOR_LOG_LEVEL', None)
62
level = logging.CRITICAL
66
logging.basicConfig(level=level,
67
format='usb-creator %(asctime)s (%(levelname)s) %(filename)s:%(lineno)d: %(message)s',
69
handler = logging.handlers.SysLogHandler('/dev/log')
70
logging.getLogger().addHandler(handler)
73
IMG, ISO, CD = range(3)
74
CAN_USE, CANNOT_USE, NEED_SPACE = range(3)
84
75
def __init__(self, frontend):
89
self.original_size = 0
80
#self.copy_timeout = 0
81
#self.original_size = 0
90
83
self.progress_description = ''
91
84
self.frontend = frontend
92
self.logger = Logger()
93
86
DBusGMainLoop(set_as_default=True)
94
self.bus = dbus.Bus(dbus.Bus.TYPE_SYSTEM)
87
self.bus = dbus.SystemBus()
95
88
hal_obj = self.bus.get_object('org.freedesktop.Hal',
96
89
'/org/freedesktop/Hal/Manager')
97
90
self.hal = dbus.Interface(hal_obj, 'org.freedesktop.Hal.Manager')
99
92
self.estimator = RemainingTimeEstimator()
102
# It might make more sense to just reassign stdout since we're logging
104
line = str(line) + '\n'
105
self.logger.write(line)
107
def set_install_source(self, source):
108
# TODO: Make more consistent with install_target
109
self.install_source = source
111
def set_install_target(self, target):
112
self.install_target = self.devices[target]
114
def set_persistence_size(self, persist):
115
self.persistence_size = persist
117
def format_device(self, device):
118
udi = self.hal.FindDeviceStringMatch('block.device', device)
120
children = self.hal.FindDeviceStringMatch('info.parent', udi)
121
for child in children:
122
dev_obj = self.bus.get_object('org.freedesktop.Hal', child)
123
child = dbus.Interface(dev_obj, 'org.freedesktop.Hal.Device')
124
dev = str(child.GetProperty('block.device'))
125
popen(['umount', dev])
126
popen(['umount', device])
128
# TODO: This could really use a progress dialog.
129
res = popen(['parted', '-s', device, 'mklabel', 'msdos'])
130
if res[0].returncode:
131
message = _('Unable to create a partition table:') + '\n' + str(res[1][0])
133
self.frontend.notify(message)
135
res = popen(['parted', '-s', device, 'mkpartfs', 'primary', 'fat32', '0', '--', '-0'])
136
if res[0].returncode:
137
message = _('Unable to format device:') + '\n' + str(res[1][0])
139
self.frontend.notify(message)
141
self.devices.pop(device)
142
self.frontend.device_removed(device, source=False)
94
self.bus.add_signal_receiver(self.device_added,
96
'org.freedesktop.Hal.Manager',
97
'org.freedesktop.Hal',
98
'/org/freedesktop/Hal/Manager')
99
self.bus.add_signal_receiver(self.device_removed,
101
'org.freedesktop.Hal.Manager',
102
'org.freedesktop.Hal',
103
'/org/freedesktop/Hal/Manager')
104
self.detect_devices()
106
# Device detection functions
144
108
def device_added(self, udi):
145
self.log('possibly adding: ' + str(udi))
146
dev_obj = self.bus.get_object("org.freedesktop.Hal", udi)
147
dev = dbus.Interface(dev_obj, "org.freedesktop.Hal.Device")
148
if dev.PropertyExists('volume.is_disc') and dev.GetProperty('volume.is_disc'):
149
if not dev.GetProperty('volume.disc.is_blank'):
150
self.log('got a disc: %s' % dev.GetProperty('volume.label'))
151
self.bus.add_signal_receiver(self.property_modified,
152
'PropertyModified', 'org.freedesktop.Hal.Device',
153
'org.freedesktop.Hal', udi, path_keyword='udi')
154
# Look for the volume first as it may not have appeared yet when you
157
if dev.PropertyExists('block.is_volume') and dev.GetProperty('block.is_volume'):
158
if (dev.PropertyExists('storage.bus') and
159
dev.GetProperty('storage.bus') == 'usb') and \
160
dev.GetProperty('storage.removable'):
161
if dev.GetProperty('volume.fstype') == 'vfat':
164
self.log('didnt add because not vfat')
166
p = dev.GetProperty('info.parent')
167
dev_obj = self.bus.get_object('org.freedesktop.Hal', p)
168
d = dbus.Interface(dev_obj, 'org.freedesktop.Hal.Device')
169
if (d.PropertyExists('storage.bus') and
170
d.GetProperty('storage.bus') == 'usb') and \
171
d.GetProperty('storage.removable'):
172
if dev.GetProperty('volume.fstype') == 'vfat':
177
# Look for empty devices.
178
if self.IsStorageDevice(dev):
179
children = self.hal.FindDeviceStringMatch('info.parent', udi)
181
for child in children:
182
dev_obj = self.bus.get_object('org.freedesktop.Hal', child)
183
child = dbus.Interface(dev_obj, 'org.freedesktop.Hal.Device')
184
if child.GetProperty('block.is_volume') and child.GetProperty('volume.fstype') == 'vfat':
186
self.log('children: ' + str(children))
187
if not c and dev.PropertyExists('storage.removable.media_size'):
188
self.log('no children or children not vfat')
189
device = str(dev.GetProperty('block.device'))
190
self.devices[device] = {
196
'free' : dev.GetProperty('storage.removable.media_size'),
197
'capacity' : dev.GetProperty('storage.removable.media_size'),
200
self.frontend.add_dest(device)
202
def property_modified(self, num_changes, change_list, udi):
203
dev_obj = self.bus.get_object("org.freedesktop.Hal", udi)
204
dev = dbus.Interface(dev_obj, "org.freedesktop.Hal.Device")
205
if dev.PropertyExists('volume.is_disc') and dev.GetProperty('volume.is_disc'):
206
if not dev.GetProperty('volume.is_mounted'):
208
mountpoint = dev.GetProperty('volume.mount_point')
209
# TODO: Is the most appropriate check?
210
if mountpoint and os.path.exists('%s/.disk/info' % mountpoint):
212
if not self.cds.has_key(udi):
109
logging.debug('Possibly adding: %s' % str(udi))
110
dev_obj = self.bus.get_object("org.freedesktop.Hal", udi)
111
dev = dbus.Interface(dev_obj, "org.freedesktop.Hal.Device")
113
# TODO evand 2009-04-21: Confirm these are the only, and correct,
115
bus_types = ('usb', 'mmc')
116
drive_types = ('compact_flash', 'memory_stick', 'smart_media', 'sd_mmc')
126
# TODO evand 2009-04-22: Use GetAllProperties() instead? See
128
udi = dev.GetProperty('info.udi')
129
# I hate HAL. info.subsystem would make the most sense here, but
130
# despite the specification saying that it is a mandatory property,
131
# it is not always present.
132
if dev.PropertyExists('info.category'):
133
# FIXME evand 2009-04-26: QueryCapability to be consistent with
135
storage = dev.GetProperty('info.category') == 'storage'
136
volume = dev.GetProperty('info.category') == 'volume'
138
fs = dev.GetProperty('volume.fstype') == 'vfat'
139
# FIXME evand 2009-04-26: Need to check for .disk/info.
140
disc = dev.GetProperty('volume.is_disc') and not \
141
dev.GetProperty('volume.disc.is_blank')
142
label = dev.GetProperty('volume.label')
143
capacity = dev.GetProperty('volume.size')
144
parent = dev.GetProperty('info.parent')
146
bus = dev.GetProperty('storage.bus') in bus_types
147
type = dev.GetProperty('storage.drive_type') in drive_types
149
label = dev.GetProperty('storage.model')
150
if dev.PropertyExists('storage.removable'):
151
capacity = dev.GetProperty('storage.removable.media_size')
153
capacity = dev.GetProperty('storage.size')
154
if storage or volume:
155
# Keybuk can't tell the difference between the gvfs volume
157
device = dev.GetProperty('block.device')
159
except dbus.DBusException, e:
160
if e.get_dbus_name() == 'org.freedesktop.Hal.NoSuchProperty':
161
# FIXME evand 2009-04-21: Add warning support to the frontend.
162
# Warnings are like errors, only the program can continue after
164
# self.frontend.warning(str(e))
165
logging.critical('No such property: %s' % str(e))
168
logging.debug(' volume: %s' % volume)
169
logging.debug(' disc: %s' % disc)
170
logging.debug(' fs: %s' % fs)
171
logging.debug(' storage: %s' % storage)
172
logging.debug(' bus: %s' % bus)
173
logging.debug(' type: %s' % type)
175
# Only add properties that are needed by the frontend. The rest can be
176
# queried using the UDI as a key.
180
# TODO evand 2009-04-21: Rename to self.sources.
214
181
self.cds[udi] = {
215
'label' : str(dev.GetProperty('volume.label')),
216
'uuid' : str(dev.GetProperty('volume.uuid')),
184
'capacity' : capacity,
217
185
'mountpoint' : mountpoint,
219
'size' : dev.GetProperty('volume.size'),
223
self.frontend.add_source(udi)
224
self.frontend.update_all_rows(None)
226
mountpoint = str(dev.GetProperty('volume.mount_point'))
227
device = str(dev.GetProperty('block.device'))
228
self.devices[device] = {
229
'label' : str(dev.GetProperty('volume.label')).replace(' ', '_'),
230
'fstype' : str(dev.GetProperty('volume.fstype')),
231
'uuid' : str(dev.GetProperty('volume.uuid')),
232
'mountpoint' : mountpoint,
233
'udi' : str(dev.GetProperty('info.udi')),
234
'free' : mountpoint and free_space(mountpoint) or 0,
235
'capacity' : dev.GetProperty('volume.size'),
238
self.frontend.update_dest_row(device)
239
self.log('prop modified')
240
self.log('device_udi: %s' % udi)
241
self.log('num_changes: %d' % num_changes)
186
# TODO evand 2009-04-21: Needed?
189
self.frontend.add_source(udi)
190
self.bus.add_signal_receiver(self.property_modified,
192
'org.freedesktop.Hal.Device',
193
'org.freedesktop.Hal', udi,
197
self.partitions[udi] = {
199
'capacity' : capacity,
202
'status' : Backend.CAN_USE,
204
logging.debug(' %s' % str(self.partitions[udi]))
205
if parent in self.devices:
206
self.devices[parent]['partitions'].append(udi)
207
self.refresh_targets()
209
self.devices[parent] = {
210
'partitions' : [udi],
212
# Don't refresh the frontend, as we know we're going to be
213
# adding a device entry soon.
214
self.bus.add_signal_receiver(self.property_modified,
216
'org.freedesktop.Hal.Device',
217
'org.freedesktop.Hal', udi,
219
# TODO evand 2009-05-05: Move to refresh_targets()? We don't
220
# want to mount partitions if we never care about their free
222
self.timeouts[udi] = gobject.timeout_add(UPDATE_FREE_INTERVAL,
223
self.update_free, udi)
227
if udi in self.devices:
228
partitions = self.devices[udi]['partitions']
231
self.devices[udi] = {
233
'capacity' : capacity,
235
'partitions' : partitions,
237
'status' : Backend.CAN_USE,
239
self.bus.add_signal_receiver(self.property_modified,
241
'org.freedesktop.Hal.Device',
242
'org.freedesktop.Hal', udi,
244
self.refresh_targets()
246
logging.debug('devices: %s' % str(self.devices))
248
def refresh_targets(self):
249
source = self.frontend.get_source()
252
type = self.cds[source]['type']
253
if type == Backend.CD or type == Backend.ISO:
254
# We want to show partitions and devices with no usable
256
for device in self.devices:
257
if self.devices[device]['partitions']:
258
for partition in self.devices[device]['partitions']:
259
self.update_free(partition)
260
ret.append(partition)
263
elif type == Backend.IMG:
264
# We only want to show the devices.
265
ret = self.devices.keys()
267
# FIXME evand 2009-04-25: I'm not entirely confident this is the
268
# right approach, but we should give the user some indication that
269
# the USB disk they've plugged in has been recognized.
270
ret = self.partitions.keys()
276
self.frontend.set_targets(ret)
278
def device_removed(self, udi):
279
logging.debug('Removing %s' % str(udi))
280
self.bus.remove_signal_receiver(self.property_modified, path=udi)
283
self.frontend.remove_source(udi)
284
# FIXME evand 2009-04-26: This is getting really ugly really quickly.
285
# Fold partitions back into devices?
286
elif udi in self.devices:
287
self.devices.pop(udi)
288
self.refresh_targets()
289
elif udi in self.partitions:
290
self.partitions.pop(udi)
291
for u in self.devices:
292
if udi in self.devices[u]['partitions']:
293
self.devices[u]['partitions'].remove(udi)
295
self.refresh_targets()
297
logging.critical('Was notified of changes to a device we do not' \
298
' care about: %s' % str(udi))
300
def property_modified(self, num_changes, change_list, udi):
301
dev_obj = self.bus.get_object('org.freedesktop.Hal', udi)
302
dev = dbus.Interface(dev_obj, 'org.freedesktop.Hal.Device')
303
# FIXME evand 2009-04-29:
304
logging.debug('device_udi: %s' % udi)
305
logging.debug('num_changes: %d' % num_changes)
242
307
for c in change_list:
243
self.log('change: %s' % str(c[0]))
245
def device_removed(self, udi):
247
for device in self.devices.itervalues():
248
if device['udi'] == udi:
249
self.log('removing %s' % udi)
254
self.frontend.device_removed(d, source=False)
255
gobject.source_remove(self.timeouts[d])
260
for device in self.cds.itervalues():
261
if device['udi'] == udi:
262
self.log('removing %s' % udi)
267
self.frontend.device_removed(d, source=True)
269
def add_device(self, dev):
270
# Remove parent device if present as this means there is not an empty
272
dev_obj = self.bus.get_object("org.freedesktop.Hal",
273
dev.GetProperty('info.parent'))
274
device = dbus.Interface(dev_obj, "org.freedesktop.Hal.Device")
275
d = device.GetProperty('block.device')
276
if self.devices.has_key(d):
278
self.frontend.device_removed(d, source=False)
280
udi = dev.GetProperty('info.udi')
281
mountpoint = str(dev.GetProperty('volume.mount_point'))
282
device = str(dev.GetProperty('block.device'))
283
self.devices[device] = {
284
'label' : str(dev.GetProperty('volume.label')).replace(' ', '_'),
285
'fstype' : str(dev.GetProperty('volume.fstype')),
286
'uuid' : str(dev.GetProperty('volume.uuid')),
287
'mountpoint' : mountpoint,
288
'udi' : str(dev.GetProperty('info.udi')),
289
'free' : mountpoint and free_space(mountpoint) or 0,
290
'capacity' : dev.GetProperty('volume.size'),
293
self.bus.add_signal_receiver(self.property_modified,
294
'PropertyModified', 'org.freedesktop.Hal.Device',
295
'org.freedesktop.Hal', udi, path_keyword='udi')
296
self.log('new device:\n%s' % self.devices[device])
297
self.frontend.add_dest(device)
298
def update_free(device):
299
dev = self.devices[device]
300
mp = dev['mountpoint']
303
dev['free'] = free_space(mp)
304
if free != dev['free']:
305
self.frontend.update_dest_row(device)
307
# TODO: Is here really the best place for this?
308
fstype = dev['fstype']
311
dev_obj = self.bus.get_object("org.freedesktop.Hal", str(dev['udi']))
312
d = dbus.Interface(dev_obj, "org.freedesktop.Hal.Device")
313
if d.GetProperty('volume.mount_point'):
315
tmpdir = tempfile.mkdtemp()
316
res = popen(['mount', '-t', fstype, device, tmpdir])
317
if res[0].returncode:
318
self.log('Error mounting %s: %s' % (device, res[1]))
319
popen(['rmdir', tmpdir])
321
dev['mountpoint'] = tmpdir
323
self.timeouts[device] = gobject.timeout_add(2000, update_free, device)
308
logging.debug('change: %s %s' % (str(c[0]), str(c[1])))
309
# TODO evand 2009-04-26: What happens if a device doesn't have a
310
# property value we want now, but does so later? Will that ever
313
dev_obj = self.bus.get_object("org.freedesktop.Hal", udi)
314
dev = dbus.Interface(dev_obj, "org.freedesktop.Hal.Device")
316
self.cds[udi]['mountpoint'] = dev.GetProperty('volume.mount_point')
317
logging.debug('changed: %s' % str(self.cds[udi]))
318
except dbus.DBusException, e:
319
logging.debug('Unable to read the new mount point from %s' % udi)
325
321
def detect_devices(self):
326
# TODO: Handle devices with empty partition tables.
328
devices = self.hal.FindDeviceByCapability('volume')
329
for device in devices:
330
dev_obj = self.bus.get_object('org.freedesktop.Hal', device)
331
dev = dbus.Interface(dev_obj, 'org.freedesktop.Hal.Device')
332
if dev.PropertyExists('volume.is_disc') and dev.GetProperty('volume.is_disc'):
333
if not dev.GetProperty('volume.disc.is_blank'):
334
self.log('got a disc: %s' % dev.GetProperty('volume.label'))
335
udi = dev.GetProperty('info.udi')
336
if not dev.GetProperty('volume.is_mounted'):
337
self.bus.add_signal_receiver(self.property_modified,
338
'PropertyModified', 'org.freedesktop.Hal.Device',
339
'org.freedesktop.Hal', udi, path_keyword='udi')
341
mountpoint = dev.GetProperty('volume.mount_point')
342
# TODO: Is the most appropriate check?
343
if mountpoint and os.path.exists('%s/.disk/info' % mountpoint):
345
'label' : str(dev.GetProperty('volume.label')),
346
'uuid' : str(dev.GetProperty('volume.uuid')),
347
'mountpoint' : mountpoint,
349
'size' : dev.GetProperty('volume.size'),
352
self.frontend.add_source(udi)
354
devices = self.hal.FindDeviceByCapability('storage')
355
for device in devices:
356
dev_obj = self.bus.get_object('org.freedesktop.Hal', device)
357
d = dbus.Interface(dev_obj, 'org.freedesktop.Hal.Device')
358
# Only check physical storage devices (not partitions)
359
if not self.IsStorageDevice(d):
361
# Check if this device has a filesystem but no partitions.
362
if d.GetProperty('block.is_volume'):
363
if d.GetProperty('volume.fstype') == 'vfat':
366
# If not, find all of this device's child partitions
367
children = self.hal.FindDeviceStringMatch('info.parent', device)
369
for child in children:
370
dev_obj = self.bus.get_object('org.freedesktop.Hal', child)
371
child = dbus.Interface(dev_obj, 'org.freedesktop.Hal.Device')
372
if child.GetProperty('block.is_volume'):
373
if child.GetProperty('volume.fstype') == 'vfat':
375
self.add_device(child)
376
# If no partitions were found, check for an empty partition table.
377
if not c and d.PropertyExists('storage.removable.media_size'):
378
udi = d.GetProperty('info.udi')
379
device = str(d.GetProperty('block.device'))
380
self.devices[device] = {
386
'free' : d.GetProperty('storage.removable.media_size'),
387
'capacity' : d.GetProperty('storage.removable.media_size'),
390
self.frontend.add_dest(device)
392
self.bus.add_signal_receiver(self.device_added,
394
"org.freedesktop.Hal.Manager",
395
"org.freedesktop.Hal",
396
"/org/freedesktop/Hal/Manager")
397
self.bus.add_signal_receiver(self.device_removed,
399
"org.freedesktop.Hal.Manager",
400
"org.freedesktop.Hal",
401
"/org/freedesktop/Hal/Manager")
403
def mount_iso(self, filename):
404
# HAL doesn't support loop mounted filesystems and indeed when manually
405
# mounted, the device does not appear in d-feet (searching the
406
# mountpoint). So we have to just manually construct the addition to
408
self.log('mounting %s' % filename)
409
tmpdir = tempfile.mkdtemp()
410
res = popen(['mount', '-t', 'iso9660', '-o', 'loop,ro', filename, tmpdir])
411
if res[0].returncode:
412
self.log('unable to mount %s to %s' % (filename, tmpdir))
322
volumes = self.hal.FindDeviceByCapability('volume')
323
for volume in volumes:
324
self.device_added(volume)
325
disks = self.hal.FindDeviceByCapability('storage')
327
self.device_added(disk)
329
# Manual device addition.
331
# HAL doesn't support loop mounted filesystems, despite udev apparently
332
# supporting them (according to Keybuk), and indeed when manually mounted,
333
# the device does not appear in hal-device. So we have to just manually
336
def add_file_source(self, filename):
338
res = popen(['file', '-b', filename])
339
except USBCreatorProcessException, e:
342
if res.startswith('x86 boot sector'):
343
self.add_image_source(filename)
344
elif res.startswith('ISO 9660'):
345
self.add_iso_source(filename)
347
logging.error('Unknown file type: %s' % str(res))
349
def add_image_source(self, filename):
350
size = os.stat(filename).st_size
351
# TODO: Rename to self.imgs
352
self.cds[filename] = { 'label' : '',
353
'type' : Backend.IMG,
355
'device' : os.path.basename(filename),
357
self.frontend.add_source(filename)
359
def add_iso_source(self, filename):
360
# TODO evand 2009-04-28: Replace with generic mount function that
361
# watches all mountpoints and unmounts them in the end.
362
mount_dir = tempfile.mkdtemp()
364
# Really need to use HAL for all mounting and unmounting. Be sure
365
# to handle org.freedesktop.Hal.Device.Volume.NotMountedByHal .
366
# Oh, not possible is it, given ISO? At least do it for all
367
# partitions? It might not be us mounting it elsewhere. Mounted by
368
# hal property. Also catch Volume.NotMounted and Volume.Busy?
369
res = popen(['mount', '-t', 'iso9660', '-o', 'loop,ro', filename, mount_dir])
370
except USBCreatorProcessException, e:
371
logging.debug('unable to mount %s to %s:\n%s' % (filename, mount_dir, str(e)))
414
372
self.frontend.notify(_('Unable to mount the image %s.\n\n'
415
373
'Please see ~/.usb-creator.log for more details') % filename)
419
fp = open('%s/.disk/info' % tmpdir)
420
line = fp.readline().strip('\0')
421
line = ' '.join(line.split(' ')[:2])
379
fp = open('%s/.disk/info' % mount_dir)
380
label = fp.readline().strip('\0')
381
label = ' '.join(label.split(' ')[:2])
383
logging.debug('Not an Ubuntu CD: %s' % str(e))
387
popen(['umount', mount_dir])
388
popen(['rmdir', mount_dir])
389
except USBCreatorProcessException, e:
390
logging.error('Unable to unmount %s: %s' % (mount_dir, str(e)))
422
393
size = os.stat(filename).st_size
423
self.cds[filename] = { 'label' : line,
394
self.cds[filename] = { 'label' : label,
395
'type' : Backend.ISO,
426
396
'mountpoint' : '',
427
'filename' : filename,
397
'device' : os.path.basename(filename),
429
399
self.frontend.add_source(filename)
431
self.log('Unable to find %s/.disk/info, not using %s' %
434
self.frontend.notify(_('This is not a desktop install CD and '
435
'thus cannot be used by this application.'))
403
def update_state(self, udi):
404
'''Determines whether a disk/partition has sufficient free space for
405
the selected source, or if the source is larger than the disk/partition
406
itself and therefore the disk is unusable with that image.'''
407
source = self.frontend.get_source()
411
source = self.cds[source]
412
source_capacity = source['capacity']
414
# XXX evand 2009-05-05: We assume that a formatted disk will have a
415
# partition that has the same size as the disk's capacity, which is
416
# never the case, but we have no way of accurately guessing the size of
417
# the resulting filesystem.
419
# TODO evand 2009-05-06: Need to factor in the removal of Ubuntu CD
420
# directories (casper, pool, etc).
421
if udi in self.devices:
422
device = self.devices[udi]
423
if source_capacity > device['capacity']:
424
# The image is larger than this disk, we cannot use it.
425
device['status'] = Backend.CANNOT_USE
427
# We can use the disk.
428
device['status'] = Backend.CAN_USE
430
if udi in self.partitions:
431
partition = self.partitions[udi]
432
if source_capacity > partition['capacity']:
433
partition['status'] = Backend.CANNOT_USE
434
elif source_capacity > partition['free']:
435
partition['status'] = Backend.NEED_SPACE
437
partition['status'] = Backend.CAN_USE
439
def update_free(self, udi):
440
'''Peroidically called via a gobject.timeout to update the amount of
441
free space on a partition target. If the amount of space has changed,
442
it calls update_state, which will determine if there is sufficient free
443
space to hold the selected source image, and then notifies the frontend
444
that the partition has changed and it needs to update its UI to reflect
446
# FIXME evand 2009-05-05: Don't poll, use an inotify watch instead.
447
dev_obj = self.bus.get_object('org.freedesktop.Hal', udi)
448
dev = dbus.Interface(dev_obj, 'org.freedesktop.Hal.Device')
449
if not dev.GetProperty('volume.is_mounted'):
450
d = dbus.Interface(dev_obj, 'org.freedesktop.Hal.Device.Volume')
452
d.Mount(self.partitions[udi]['label'], 'vfat', [])
453
self.mountpoints.append(udi)
454
except dbus.DBusException, e:
455
logging.error(str(e))
456
logging.error('Cannot mount %s, not updating free space.' % udi)
458
mp = dev.GetProperty('volume.mount_point')
461
free = free_space(mp)
462
if self.partitions[udi]['free'] != free:
463
logging.debug('%s now has %d B free.' % (udi, free))
464
self.partitions[udi]['free'] = free
465
self.update_state(udi)
466
self.frontend.update_target(udi)
469
def unmount(self, partitions_list):
470
'''Unmount a list of Device.Volumes, specified by UDI.'''
471
for partition in partitions_list:
472
if partition in self.cds:
473
mp = self.cds[partition]['mountpoint']
474
mp and popen(['umount', mp])
475
dev_obj = self.bus.get_object('org.freedesktop.Hal', partition)
476
dev = dbus.Interface(dev_obj, 'org.freedesktop.Hal.Device')
477
if dev.GetProperty('volume.is_mounted'):
478
d = dbus.Interface(dev_obj, 'org.freedesktop.Hal.Device.Volume')
479
# TODO evand 2009-04-28: Catch unmount exceptions.
482
except dbus.DBusException, e:
483
n = e.get_dbus_name()
484
if n == 'org.freedesktop.Hal.Device.Volume.NotMountedByHal':
485
mountpoint = dev.GetProperty('volume.mount_point')
486
popen(['umount', mountpoint])
487
elif n == 'org.freedesktop.Hal.Device.Volume.Busy':
488
# TODO evand 2009-05-01: Need to call into the frontend
489
# for an error dialog here.
492
def format_device(self, udi):
493
'''Format the disk device. If the UDI specified is a Device.Volume,
494
find its parent and format it instead.'''
497
if udi in self.devices:
500
for dev in self.devices:
501
if udi in self.devices[dev]['partitions']:
505
logging.critical('Could not find device to format: %s' % udi)
507
dev_obj = self.bus.get_object('org.freedesktop.Hal', udi)
508
dev = dbus.Interface(dev_obj, 'org.freedesktop.Hal.Device')
509
# TODO evand 2009-04-28: Catch lock exceptions.
510
dev.Lock('Formatting device')
511
partitions = self.devices[device]['partitions']
513
self.unmount(partitions)
514
except dbus.DBusException, e:
515
logging.error('Unable to unmount partitions before formatting.')
518
device = self.devices[device]['device']
520
# TODO: This could really use a progress dialog.
521
popen(['parted', '-s', device, 'mklabel', 'msdos'])
522
popen(['parted', '-s', device, 'mkpartfs', 'primary',
523
'fat32', '0', '--', '-0'])
524
except USBCreatorProcessException, e:
525
message = _('Unable to format the device')
526
logging.debug('%s %s: %s' % (message, device, str(e)))
527
self.frontend.notify(message)
529
# Probably unnecessary.
532
except dbus.DBusException:
538
# TODO evand 2009-05-03: Perhaps raise USBCreatorInstallException on
539
# recoverable errors? Or FatalInstallException and InstallException?
540
source = self.frontend.get_source()
541
target = self.frontend.get_target()
542
persist = self.frontend.get_persistence() * 1024 * 1024
543
if not (source and target):
544
# We do not fail here because the user can go back and select a
545
# proper source and target, should they somehow get here.
546
logging.error('Pressed install without a source or target.')
550
dev_obj = self.bus.get_object('org.freedesktop.Hal', target)
551
dev = dbus.Interface(dev_obj, 'org.freedesktop.Hal.Device')
552
dev.Lock('Writing %s to the disk' % source)
553
if self.cds[source]['type'] == Backend.IMG:
554
self.unmount(self.devices[target]['partitions'])
555
self.write_image(source, target)
557
# TODO evand 2009-05-06: Call self.unmount()
558
self.install_bootloader(target)
559
self.copy_files(source, target, persist)
560
except (dbus.DBusException, Exception), e:
562
print traceback.print_exc()
563
message = _('An error occured during the installation, and it ' \
564
'was unable to complete.')
565
# FIXME evand 2009-05-03: Unmount both source and target here.
567
self.frontend.failed(message)
440
popen(['umount', tmpdir])
441
popen(['rmdir', tmpdir])
443
def install_bootloader(self):
444
# TODO: Needs to be moved into the generic install routine.
571
except dbus.DBusException:
576
os.kill(self.pipe.pid, signal.SIGUSR1)
581
def dd_progress(self, source, condition, source_size):
582
char = source.readline()
583
splitted = char.split()
584
# XXX evand 2009-05-05: Ick.
585
if len(splitted) > 1 and splitted[1] == 'bytes':
587
now = float(splitted[0])
588
per = (now / source_size) * 100
589
remaining, speed = self.estimator.estimate(now, source_size)
590
self.frontend.progress(per, remaining, speed, _('Copying files'))
592
self.frontend.progress(0, 0, 0, _('Copying files'))
595
def write_image(self, source, target):
596
print 'Writing %s to %s' % (source, target)
597
target = self.devices[target]
598
cmd = ['dd', 'bs=1M', 'if=%s' % source,
599
'of=%s' % target['device'], 'conv=fsync']
601
# TODO evand 2009-05-06: Either use popen() or write another function
602
# that wraps errors in exceptions.
603
self.pipe = subprocess.Popen(cmd, stdout=sys.stdout,
604
stderr=subprocess.PIPE, universal_newlines=True,
606
source_size = float(self.cds[source]['capacity'])
607
self.watch = gobject.io_add_watch(self.pipe.stderr,
608
gobject.IO_IN | gobject.IO_HUP,
609
self.dd_progress, source_size)
610
self.copy_timeout = gobject.timeout_add(5000, self.dd_status)
611
# Wait for the process to complete
612
gobject.child_watch_add(self.pipe.pid, self.on_end)
614
def install_bootloader(self, target):
615
# TODO evand 2009-04-29: Perhaps detect bootloader code instead and
616
# offer to overwrite.
618
dev_obj = self.bus.get_object('org.freedesktop.Hal', target)
619
dev = dbus.Interface(dev_obj, 'org.freedesktop.Hal.Device')
620
partition_num = str(dev.GetProperty('volume.partition.number'))
623
assert target in self.partitions
624
device = self.partitions[target]['device']
625
for dev in self.devices:
626
if target in self.devices[dev]['partitions']:
627
rootdevice = self.devices[dev]['device']
629
assert device and rootdevice
630
except (AssertionError, dbus.DBusException), e:
631
logging.error('Could not determine the device: %s' % str(e))
632
# We don't exit because we haven't actually done anything to the device.
447
635
self.frontend.progress(0, None, None, _('Installing the bootloader'))
449
device = self.install_target['device']
450
udi = self.install_target['udi']
451
dev_obj = self.bus.get_object("org.freedesktop.Hal", udi)
452
dev = dbus.Interface(dev_obj, "org.freedesktop.Hal.Device")
453
dev_obj = self.bus.get_object("org.freedesktop.Hal", dev.GetProperty('info.parent'))
454
dev = dbus.Interface(dev_obj, "org.freedesktop.Hal.Device")
455
rootdev = dev.GetProperty('block.device')
456
tmp = device.lstrip(rootdev)
457
match = re.match('.*([0-9]+)$', tmp)
460
num = match.groups()[0]
461
self.log('Marking partition %s as active.' % num)
463
self.frontend.failed(_('Unable to determine the partition number.'))
465
# Install the bootloader to the MBR.
466
self.log('installing the bootloader to %s.' % rootdev)
467
process = popen(['dd', 'if=/usr/lib/syslinux/mbr.bin',
468
'of=%s' % rootdev, 'bs=446', 'count=1', 'conv=sync'])
469
bootloader_failed = _('Error installing the bootloader.')
470
if process[0].returncode:
471
self.frontend.failed(bootloader_failed)
472
self.log('installing the bootloader to %s.' % device)
474
if 'USBCREATOR_SAFE' in os.environ:
477
process = popen(args)[0]
478
if process.returncode:
479
self.frontend.failed(bootloader_failed)
480
popen(['parted', '-s', rootdev, 'set', num, 'boot', 'on'])
482
def copy_files(self):
484
def _copy_files(source, target, persist):
485
cmd = ['/usr/share/usb-creator/install.py', '-s', '%s/.' % source,
486
'-t', '%s' % target, '-p', '%d' % persist]
488
self.pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
489
stderr=sys.stderr, universal_newlines=True)
490
self.watch = gobject.io_add_watch(self.pipe.stdout,
638
# Install the bootloader to the MBR.
639
popen(['dd', 'if=/usr/lib/syslinux/mbr.bin',
640
'of=%s' % rootdevice, 'bs=446', 'count=1', 'conv=sync'])
642
if 'USBCREATOR_SAFE' in os.environ:
646
logging.debug('Marking partition %s as active.' % partition_num)
647
popen(['parted', '-s', rootdevice, 'set', partition_num, 'boot', 'on'])
648
except USBCreatorProcessException, e:
649
logging.error('Failed to install the bootloader: %s' % str(e))
650
# FIXME evand 2009-04-29: exit properly.
653
def copy_files(self, source, target, persist):
654
source_obj = self.cds[source]
655
target_obj = self.partitions[target]
657
# TODO evand 2009-05-05: Need to push this into its own function.
658
if not source_obj['mountpoint'] and source_obj['type'] == Backend.ISO:
659
mount_point = tempfile.mkdtemp()
660
# mount(options [] = None, source, mount_point)?
661
# mounts, adds to mountpoint list
662
# what happens if it fails? Let the exception bubble up to install()
663
popen(['mount', '-o', 'loop,ro', source, mount_point])
664
source_obj['mountpoint'] = mount_point
665
self.mountpoints.append(source)
666
elif not source_obj['mountpoint'] and source_obj['type'] == Backend.CD:
667
# This should use HAL to mount.
668
mount_point = tempfile.mkdtemp()
669
popen(['mount', '-o', 'ro', source_obj['device'], mount_point])
670
self.mountpoints.append(source)
671
source_obj['mountpoint'] = mount_point
673
dev_obj = self.bus.get_object('org.freedesktop.Hal', target)
674
dev = dbus.Interface(dev_obj, 'org.freedesktop.Hal.Device')
675
if not dev.GetProperty('volume.is_mounted'):
676
dev_obj = self.bus.get_object('org.freedesktop.Hal', target)
677
d = dbus.Interface(dev_obj, 'org.freedesktop.Hal.Device.Volume')
678
# TODO evand 2009-05-01: Should we use sync as a mount option here?
680
d.Mount(target_obj['label'], 'vfat', [])
681
self.mountpoints.append(target)
682
mp = dev.GetProperty('volume.mount_point')
684
logging.critical('Unable to find mount point for target media.')
685
# TODO evand 2009-05-05: FAIL
686
target_mp = dev.GetProperty('volume.mount_point')
687
# FIXME 2009-05-1: What if a CD is not mounted?
688
source_mp = source_obj['mountpoint']
690
# We don't care about updating the main window anymore.
691
#for timeout in self.timeouts.itervalues():
692
# gobject.source_remove(timeout)
694
#self.bus.remove_signal_receiver(self.property_modified)
696
# FIXME evand 2009-04-29: Move into scripts/install.py? Can't, need to
697
# do it before calculating the original size.
698
# Remove files we're going to copy.
700
for obj in os.listdir(source_mp):
701
obj = os.path.join(target_mp, obj)
702
if os.path.exists(obj):
703
popen(['rm', '-rf', obj])
704
casper_rw = os.path.join(target_mp, 'casper-rw')
705
popen(['rm', casper_rw])
706
except USBCreatorProcessException, e:
707
# Chances are these files will not exist and rm will return
711
# Update the progress by calculating the free space on the partition.
712
original_size = free_space(target_mp)
713
print 'persist', persist
714
print 'capacity', source_obj['capacity']
715
source_size = float(persist) + float(source_obj['capacity'])
716
self.copy_timeout = gobject.timeout_add(2000, self.copy_progress,
721
cmd = ['/usr/share/usb-creator/install.py', '-s', '%s/.' % source_mp,
722
'-t', '%s' % target_mp, '-p', '%d' % persist]
723
self.pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
725
universal_newlines=True)
726
self.watch = gobject.io_add_watch(self.pipe.stdout,
491
727
gobject.IO_IN | gobject.IO_HUP,
492
728
self.data_available)
493
# Wait for the process to complete
494
gobject.child_watch_add(self.pipe.pid, self.on_end)
496
source = self.cds[self.install_source]
497
if not source['udi']:
498
tmpdir = tempfile.mkdtemp()
499
popen(['mount', '-o', 'loop,ro', self.install_source, tmpdir])
500
source['mountpoint'] = tmpdir
501
elif not source['mountpoint']:
502
tmpdir = tempfile.mkdtemp()
503
popen(['mount', '-o', 'ro', self.install_source, tmpdir])
504
source['mountpoint'] = tmpdir
505
if not self.install_target['mountpoint']:
506
tmpdir = tempfile.mkdtemp()
507
popen(['mount', self.install_target['device'], tmpdir])
508
self.install_target['mountpoint'] = tmpdir
510
# We don't care about updating the main window anymore.
511
for timeout in self.timeouts.itervalues():
512
gobject.source_remove(timeout)
514
self.bus.remove_signal_receiver(self.property_modified)
515
# Remove files we're going to copy.
516
t = self.install_target['mountpoint']
517
for obj in os.listdir(self.cds[self.install_source]['mountpoint']):
518
obj = os.path.join(t, obj)
519
popen(['rm', '-rf', obj])
520
popen(['rm', os.path.join(t, 'casper-rw')])
521
self.original_size = free_space(self.install_target['mountpoint'])
522
_copy_files(source['mountpoint'], self.install_target['mountpoint'],
523
self.persistence_size)
526
self.log('Forcing shutdown of the install process.')
729
# Wait for the process to complete
730
gobject.child_watch_add(self.pipe.pid, self.on_end)
734
'''Called when the user has canceled the installation.'''
735
logging.debug('Forcing shutdown of the install process.')
530
739
os.kill(self.pipe.pid, signal.SIGTERM)
533
source = self.cds[self.install_source]
534
if source['mountpoint'].startswith('/tmp'):
535
self.log('Unmounting source volume.')
536
popen(['umount', source['mountpoint']])
537
popen(['rmdir', source['mountpoint']])
538
if self.install_target['mountpoint'].startswith('/tmp'):
539
self.log('Unmounting target volume.')
540
popen(['umount', self.install_target['mountpoint']])
541
popen(['rmdir', self.install_target['mountpoint']])
741
if e.errno == errno.ESRCH:
745
logging.error('Unable to kill %d: %s' % self.pipe.pid, str(e))
748
def copy_progress(self, original_size, target_mountpoint, source_size):
749
now = free_space(target_mountpoint)
750
done = original_size - now
751
per = (done / float(source_size)) * 100
752
remaining, speed = self.estimator.estimate(done, source_size)
753
self.frontend.progress(per, remaining, speed, self.progress_description)
759
def data_available(self, source, condition):
760
text = source.readline()
762
self.progress_description = text.strip('\n')
771
for udi, timeout in self.timeouts.iteritems():
772
if not gobject.source_remove(timeout):
775
print 'unmounting', self.mountpoints
776
self.unmount(self.mountpoints)
544
for dev in self.devices:
545
mp = self.devices[dev]['mountpoint']
546
if mp and mp.startswith('/tmp'):
547
popen(['umount', mp])
779
'''Called when the user has quit the application.'''
550
782
def on_end(self, pid, error_code):
551
source = self.cds[self.install_source]
552
if source['mountpoint'].startswith('/tmp'):
553
self.log('Unmounting source volume.')
554
popen(['umount', source['mountpoint']])
555
popen(['rmdir', source['mountpoint']])
556
if self.install_target['mountpoint'].startswith('/tmp'):
557
self.log('Unmounting target volume.')
558
popen(['umount', self.install_target['mountpoint']])
559
popen(['rmdir', self.install_target['mountpoint']])
560
self.log('Install command exited with code: %d' % error_code)
783
# FIXME evand 2009-04-29:
784
# unmount source and dest
785
gobject.source_remove(self.watch)
786
if self.copy_timeout:
787
gobject.source_remove(self.copy_timeout)
788
logging.debug('Install command exited with code: %d' % error_code)
561
789
if error_code != 0:
562
790
self.frontend.failed()
564
#gobject.source_remove(self.watch)
565
791
self.frontend.finished()
567
def copy_progress(self):
568
now = free_space(self.install_target['mountpoint'])
569
source = float(self.cds[self.install_source]['size'])
570
source = source + (self.persistence_size * 1024 * 1024)
571
done = (self.original_size - now)
572
per = (done / source) * 100
573
remaining, speed = self.estimator.estimate(done, source)
574
self.frontend.progress(per, remaining, speed, self.progress_description)
577
def data_available(self, source, condition):
578
text = source.readline()
580
self.progress_description = text.strip('\n')
581
if not self.copy_timeout:
582
self.copy_timeout = gobject.timeout_add(2000, self.copy_progress)
585
if self.copy_timeout:
586
gobject.source_remove(self.copy_timeout)
589
def IsStorageDevice(self, d):
590
# We only care about usb devices currently
591
if d.GetProperty('storage.bus') not in ['usb', 'mmc']:
594
# Card readers are not removable, so nix this test.
595
#if d.GetProperty('storage.removable') == False:
598
# Known good drive types
599
drive_types = ['disk',
604
if d.GetProperty('storage.drive_type') in drive_types: