Package CedarBackup2 :: Package writers :: Module cdwriter
[hide private]
[frames] | no frames]

Source Code for Module CedarBackup2.writers.cdwriter

   1  # -*- coding: iso-8859-1 -*- 
   2  # vim: set ft=python ts=3 sw=3 expandtab: 
   3  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 
   4  # 
   5  #              C E D A R 
   6  #          S O L U T I O N S       "Software done right." 
   7  #           S O F T W A R E 
   8  # 
   9  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 
  10  # 
  11  # Copyright (c) 2004-2008,2010 Kenneth J. Pronovici. 
  12  # All rights reserved. 
  13  # 
  14  # This program is free software; you can redistribute it and/or 
  15  # modify it under the terms of the GNU General Public License, 
  16  # Version 2, as published by the Free Software Foundation. 
  17  # 
  18  # This program is distributed in the hope that it will be useful, 
  19  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
  20  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
  21  # 
  22  # Copies of the GNU General Public License are available from 
  23  # the Free Software Foundation website, http://www.gnu.org/. 
  24  # 
  25  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 
  26  # 
  27  # Author   : Kenneth J. Pronovici <pronovic@ieee.org> 
  28  # Language : Python 2 (>= 2.7) 
  29  # Project  : Cedar Backup, release 2 
  30  # Purpose  : Provides functionality related to CD writer devices. 
  31  # 
  32  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 
  33   
  34  ######################################################################## 
  35  # Module documentation 
  36  ######################################################################## 
  37   
  38  """ 
  39  Provides functionality related to CD writer devices. 
  40   
  41  @sort: MediaDefinition, MediaCapacity, CdWriter, 
  42         MEDIA_CDRW_74, MEDIA_CDR_74, MEDIA_CDRW_80, MEDIA_CDR_80 
  43   
  44  @var MEDIA_CDRW_74: Constant representing 74-minute CD-RW media. 
  45  @var MEDIA_CDR_74: Constant representing 74-minute CD-R media. 
  46  @var MEDIA_CDRW_80: Constant representing 80-minute CD-RW media. 
  47  @var MEDIA_CDR_80: Constant representing 80-minute CD-R media. 
  48   
  49  @author: Kenneth J. Pronovici <pronovic@ieee.org> 
  50  """ 
  51   
  52  ######################################################################## 
  53  # Imported modules 
  54  ######################################################################## 
  55   
  56  # System modules 
  57  import os 
  58  import re 
  59  import logging 
  60  import tempfile 
  61  import time 
  62   
  63  # Cedar Backup modules 
  64  from CedarBackup2.util import resolveCommand, executeCommand 
  65  from CedarBackup2.util import convertSize, displayBytes, encodePath 
  66  from CedarBackup2.util import UNIT_SECTORS, UNIT_BYTES, UNIT_KBYTES, UNIT_MBYTES 
  67  from CedarBackup2.writers.util import validateDevice, validateScsiId, validateDriveSpeed 
  68  from CedarBackup2.writers.util import IsoImage 
  69   
  70   
  71  ######################################################################## 
  72  # Module-wide constants and variables 
  73  ######################################################################## 
  74   
  75  logger = logging.getLogger("CedarBackup2.log.writers.cdwriter") 
  76   
  77  MEDIA_CDRW_74  = 1 
  78  MEDIA_CDR_74   = 2 
  79  MEDIA_CDRW_80  = 3 
  80  MEDIA_CDR_80   = 4 
  81   
  82  CDRECORD_COMMAND = [ "cdrecord", ] 
  83  EJECT_COMMAND    = [ "eject", ] 
  84  MKISOFS_COMMAND  = [ "mkisofs", ] 
85 86 87 ######################################################################## 88 # MediaDefinition class definition 89 ######################################################################## 90 91 -class MediaDefinition(object):
92 93 """ 94 Class encapsulating information about CD media definitions. 95 96 The following media types are accepted: 97 98 - C{MEDIA_CDR_74}: 74-minute CD-R media (650 MB capacity) 99 - C{MEDIA_CDRW_74}: 74-minute CD-RW media (650 MB capacity) 100 - C{MEDIA_CDR_80}: 80-minute CD-R media (700 MB capacity) 101 - C{MEDIA_CDRW_80}: 80-minute CD-RW media (700 MB capacity) 102 103 Note that all of the capacities associated with a media definition are in 104 terms of ISO sectors (C{util.ISO_SECTOR_SIZE)}. 105 106 @sort: __init__, mediaType, rewritable, initialLeadIn, leadIn, capacity 107 """ 108
109 - def __init__(self, mediaType):
110 """ 111 Creates a media definition for the indicated media type. 112 @param mediaType: Type of the media, as discussed above. 113 @raise ValueError: If the media type is unknown or unsupported. 114 """ 115 self._mediaType = None 116 self._rewritable = False 117 self._initialLeadIn = 0. 118 self._leadIn = 0.0 119 self._capacity = 0.0 120 self._setValues(mediaType)
121
122 - def _setValues(self, mediaType):
123 """ 124 Sets values based on media type. 125 @param mediaType: Type of the media, as discussed above. 126 @raise ValueError: If the media type is unknown or unsupported. 127 """ 128 if mediaType not in [MEDIA_CDR_74, MEDIA_CDRW_74, MEDIA_CDR_80, MEDIA_CDRW_80]: 129 raise ValueError("Invalid media type %d." % mediaType) 130 self._mediaType = mediaType 131 self._initialLeadIn = 11400.0 # per cdrecord's documentation 132 self._leadIn = 6900.0 # per cdrecord's documentation 133 if self._mediaType == MEDIA_CDR_74: 134 self._rewritable = False 135 self._capacity = convertSize(650.0, UNIT_MBYTES, UNIT_SECTORS) 136 elif self._mediaType == MEDIA_CDRW_74: 137 self._rewritable = True 138 self._capacity = convertSize(650.0, UNIT_MBYTES, UNIT_SECTORS) 139 elif self._mediaType == MEDIA_CDR_80: 140 self._rewritable = False 141 self._capacity = convertSize(700.0, UNIT_MBYTES, UNIT_SECTORS) 142 elif self._mediaType == MEDIA_CDRW_80: 143 self._rewritable = True 144 self._capacity = convertSize(700.0, UNIT_MBYTES, UNIT_SECTORS)
145
146 - def _getMediaType(self):
147 """ 148 Property target used to get the media type value. 149 """ 150 return self._mediaType
151
152 - def _getRewritable(self):
153 """ 154 Property target used to get the rewritable flag value. 155 """ 156 return self._rewritable
157
158 - def _getInitialLeadIn(self):
159 """ 160 Property target used to get the initial lead-in value. 161 """ 162 return self._initialLeadIn
163
164 - def _getLeadIn(self):
165 """ 166 Property target used to get the lead-in value. 167 """ 168 return self._leadIn
169
170 - def _getCapacity(self):
171 """ 172 Property target used to get the capacity value. 173 """ 174 return self._capacity
175 176 mediaType = property(_getMediaType, None, None, doc="Configured media type.") 177 rewritable = property(_getRewritable, None, None, doc="Boolean indicating whether the media is rewritable.") 178 initialLeadIn = property(_getInitialLeadIn, None, None, doc="Initial lead-in required for first image written to media.") 179 leadIn = property(_getLeadIn, None, None, doc="Lead-in required on successive images written to media.") 180 capacity = property(_getCapacity, None, None, doc="Total capacity of the media before any required lead-in.")
181
182 183 ######################################################################## 184 # MediaCapacity class definition 185 ######################################################################## 186 187 -class MediaCapacity(object):
188 189 """ 190 Class encapsulating information about CD media capacity. 191 192 Space used includes the required media lead-in (unless the disk is unused). 193 Space available attempts to provide a picture of how many bytes are 194 available for data storage, including any required lead-in. 195 196 The boundaries value is either C{None} (if multisession discs are not 197 supported or if the disc has no boundaries) or in exactly the form provided 198 by C{cdrecord -msinfo}. It can be passed as-is to the C{IsoImage} class. 199 200 @sort: __init__, bytesUsed, bytesAvailable, boundaries, totalCapacity, utilized 201 """ 202
203 - def __init__(self, bytesUsed, bytesAvailable, boundaries):
204 """ 205 Initializes a capacity object. 206 @raise IndexError: If the boundaries tuple does not have enough elements. 207 @raise ValueError: If the boundaries values are not integers. 208 @raise ValueError: If the bytes used and available values are not floats. 209 """ 210 self._bytesUsed = float(bytesUsed) 211 self._bytesAvailable = float(bytesAvailable) 212 if boundaries is None: 213 self._boundaries = None 214 else: 215 self._boundaries = (int(boundaries[0]), int(boundaries[1]))
216
217 - def __str__(self):
218 """ 219 Informal string representation for class instance. 220 """ 221 return "utilized %s of %s (%.2f%%)" % (displayBytes(self.bytesUsed), displayBytes(self.totalCapacity), self.utilized)
222
223 - def _getBytesUsed(self):
224 """ 225 Property target to get the bytes-used value. 226 """ 227 return self._bytesUsed
228
229 - def _getBytesAvailable(self):
230 """ 231 Property target to get the bytes-available value. 232 """ 233 return self._bytesAvailable
234
235 - def _getBoundaries(self):
236 """ 237 Property target to get the boundaries tuple. 238 """ 239 return self._boundaries
240
241 - def _getTotalCapacity(self):
242 """ 243 Property target to get the total capacity (used + available). 244 """ 245 return self.bytesUsed + self.bytesAvailable
246
247 - def _getUtilized(self):
248 """ 249 Property target to get the percent of capacity which is utilized. 250 """ 251 if self.bytesAvailable <= 0.0: 252 return 100.0 253 elif self.bytesUsed <= 0.0: 254 return 0.0 255 return (self.bytesUsed / self.totalCapacity) * 100.0
256 257 bytesUsed = property(_getBytesUsed, None, None, doc="Space used on disc, in bytes.") 258 bytesAvailable = property(_getBytesAvailable, None, None, doc="Space available on disc, in bytes.") 259 boundaries = property(_getBoundaries, None, None, doc="Session disc boundaries, in terms of ISO sectors.") 260 totalCapacity = property(_getTotalCapacity, None, None, doc="Total capacity of the disc, in bytes.") 261 utilized = property(_getUtilized, None, None, "Percentage of the total capacity which is utilized.")
262
263 264 ######################################################################## 265 # _ImageProperties class definition 266 ######################################################################## 267 268 -class _ImageProperties(object):
269 """ 270 Simple value object to hold image properties for C{DvdWriter}. 271 """
272 - def __init__(self):
273 self.newDisc = False 274 self.tmpdir = None 275 self.mediaLabel = None 276 self.entries = None # dict mapping path to graft point
277
278 279 ######################################################################## 280 # CdWriter class definition 281 ######################################################################## 282 283 -class CdWriter(object):
284 285 ###################### 286 # Class documentation 287 ###################### 288 289 """ 290 Class representing a device that knows how to write CD media. 291 292 Summary 293 ======= 294 295 This is a class representing a device that knows how to write CD media. It 296 provides common operations for the device, such as ejecting the media, 297 writing an ISO image to the media, or checking for the current media 298 capacity. It also provides a place to store device attributes, such as 299 whether the device supports writing multisession discs, etc. 300 301 This class is implemented in terms of the C{eject} and C{cdrecord} 302 programs, both of which should be available on most UN*X platforms. 303 304 Image Writer Interface 305 ====================== 306 307 The following methods make up the "image writer" interface shared 308 with other kinds of writers (such as DVD writers):: 309 310 __init__ 311 initializeImage() 312 addImageEntry() 313 writeImage() 314 setImageNewDisc() 315 retrieveCapacity() 316 getEstimatedImageSize() 317 318 Only these methods will be used by other Cedar Backup functionality 319 that expects a compatible image writer. 320 321 The media attribute is also assumed to be available. 322 323 Media Types 324 =========== 325 326 This class knows how to write to two different kinds of media, represented 327 by the following constants: 328 329 - C{MEDIA_CDR_74}: 74-minute CD-R media (650 MB capacity) 330 - C{MEDIA_CDRW_74}: 74-minute CD-RW media (650 MB capacity) 331 - C{MEDIA_CDR_80}: 80-minute CD-R media (700 MB capacity) 332 - C{MEDIA_CDRW_80}: 80-minute CD-RW media (700 MB capacity) 333 334 Most hardware can read and write both 74-minute and 80-minute CD-R and 335 CD-RW media. Some older drives may only be able to write CD-R media. 336 The difference between the two is that CD-RW media can be rewritten 337 (erased), while CD-R media cannot be. 338 339 I do not support any other configurations for a couple of reasons. The 340 first is that I've never tested any other kind of media. The second is 341 that anything other than 74 or 80 minute is apparently non-standard. 342 343 Device Attributes vs. Media Attributes 344 ====================================== 345 346 A given writer instance has two different kinds of attributes associated 347 with it, which I call device attributes and media attributes. Device 348 attributes are things which can be determined without looking at the 349 media, such as whether the drive supports writing multisession disks or 350 has a tray. Media attributes are attributes which vary depending on the 351 state of the media, such as the remaining capacity on a disc. In 352 general, device attributes are available via instance variables and are 353 constant over the life of an object, while media attributes can be 354 retrieved through method calls. 355 356 Talking to Hardware 357 =================== 358 359 This class needs to talk to CD writer hardware in two different ways: 360 through cdrecord to actually write to the media, and through the 361 filesystem to do things like open and close the tray. 362 363 Historically, CdWriter has interacted with cdrecord using the scsiId 364 attribute, and with most other utilities using the device attribute. 365 This changed somewhat in Cedar Backup 2.9.0. 366 367 When Cedar Backup was first written, the only way to interact with 368 cdrecord was by using a SCSI device id. IDE devices were mapped to 369 pseudo-SCSI devices through the kernel. Later, extended SCSI "methods" 370 arrived, and it became common to see C{ATA:1,0,0} or C{ATAPI:0,0,0} as a 371 way to address IDE hardware. By late 2006, C{ATA} and C{ATAPI} had 372 apparently been deprecated in favor of just addressing the IDE device 373 directly by name, i.e. C{/dev/cdrw}. 374 375 Because of this latest development, it no longer makes sense to require a 376 CdWriter to be created with a SCSI id -- there might not be one. So, the 377 passed-in SCSI id is now optional. Also, there is now a hardwareId 378 attribute. This attribute is filled in with either the SCSI id (if 379 provided) or the device (otherwise). The hardware id is the value that 380 will be passed to cdrecord in the C{dev=} argument. 381 382 Testing 383 ======= 384 385 It's rather difficult to test this code in an automated fashion, even if 386 you have access to a physical CD writer drive. It's even more difficult 387 to test it if you are running on some build daemon (think of a Debian 388 autobuilder) which can't be expected to have any hardware or any media 389 that you could write to. 390 391 Because of this, much of the implementation below is in terms of static 392 methods that are supposed to take defined actions based on their 393 arguments. Public methods are then implemented in terms of a series of 394 calls to simplistic static methods. This way, we can test as much as 395 possible of the functionality via testing the static methods, while 396 hoping that if the static methods are called appropriately, things will 397 work properly. It's not perfect, but it's much better than no testing at 398 all. 399 400 @sort: __init__, isRewritable, _retrieveProperties, retrieveCapacity, _getBoundaries, 401 _calculateCapacity, openTray, closeTray, refreshMedia, writeImage, 402 _blankMedia, _parsePropertiesOutput, _parseBoundariesOutput, 403 _buildOpenTrayArgs, _buildCloseTrayArgs, _buildPropertiesArgs, 404 _buildBoundariesArgs, _buildBlankArgs, _buildWriteArgs, 405 device, scsiId, hardwareId, driveSpeed, media, deviceType, deviceVendor, 406 deviceId, deviceBufferSize, deviceSupportsMulti, deviceHasTray, deviceCanEject, 407 initializeImage, addImageEntry, writeImage, setImageNewDisc, getEstimatedImageSize 408 """ 409 410 ############## 411 # Constructor 412 ############## 413
414 - def __init__(self, device, scsiId=None, driveSpeed=None, 415 mediaType=MEDIA_CDRW_74, noEject=False, 416 refreshMediaDelay=0, ejectDelay=0, unittest=False):
417 """ 418 Initializes a CD writer object. 419 420 The current user must have write access to the device at the time the 421 object is instantiated, or an exception will be thrown. However, no 422 media-related validation is done, and in fact there is no need for any 423 media to be in the drive until one of the other media attribute-related 424 methods is called. 425 426 The various instance variables such as C{deviceType}, C{deviceVendor}, 427 etc. might be C{None}, if we're unable to parse this specific information 428 from the C{cdrecord} output. This information is just for reference. 429 430 The SCSI id is optional, but the device path is required. If the SCSI id 431 is passed in, then the hardware id attribute will be taken from the SCSI 432 id. Otherwise, the hardware id will be taken from the device. 433 434 If cdrecord improperly detects whether your writer device has a tray and 435 can be safely opened and closed, then pass in C{noEject=False}. This 436 will override the properties and the device will never be ejected. 437 438 @note: The C{unittest} parameter should never be set to C{True} 439 outside of Cedar Backup code. It is intended for use in unit testing 440 Cedar Backup internals and has no other sensible purpose. 441 442 @param device: Filesystem device associated with this writer. 443 @type device: Absolute path to a filesystem device, i.e. C{/dev/cdrw} 444 445 @param scsiId: SCSI id for the device (optional). 446 @type scsiId: If provided, SCSI id in the form C{[<method>:]scsibus,target,lun} 447 448 @param driveSpeed: Speed at which the drive writes. 449 @type driveSpeed: Use C{2} for 2x device, etc. or C{None} to use device default. 450 451 @param mediaType: Type of the media that is assumed to be in the drive. 452 @type mediaType: One of the valid media type as discussed above. 453 454 @param noEject: Overrides properties to indicate that the device does not support eject. 455 @type noEject: Boolean true/false 456 457 @param refreshMediaDelay: Refresh media delay to use, if any 458 @type refreshMediaDelay: Number of seconds, an integer >= 0 459 460 @param ejectDelay: Eject delay to use, if any 461 @type ejectDelay: Number of seconds, an integer >= 0 462 463 @param unittest: Turns off certain validations, for use in unit testing. 464 @type unittest: Boolean true/false 465 466 @raise ValueError: If the device is not valid for some reason. 467 @raise ValueError: If the SCSI id is not in a valid form. 468 @raise ValueError: If the drive speed is not an integer >= 1. 469 @raise IOError: If device properties could not be read for some reason. 470 """ 471 self._image = None # optionally filled in by initializeImage() 472 self._device = validateDevice(device, unittest) 473 self._scsiId = validateScsiId(scsiId) 474 self._driveSpeed = validateDriveSpeed(driveSpeed) 475 self._media = MediaDefinition(mediaType) 476 self._noEject = noEject 477 self._refreshMediaDelay = refreshMediaDelay 478 self._ejectDelay = ejectDelay 479 if not unittest: 480 (self._deviceType, 481 self._deviceVendor, 482 self._deviceId, 483 self._deviceBufferSize, 484 self._deviceSupportsMulti, 485 self._deviceHasTray, 486 self._deviceCanEject) = self._retrieveProperties()
487 488 489 ############# 490 # Properties 491 ############# 492
493 - def _getDevice(self):
494 """ 495 Property target used to get the device value. 496 """ 497 return self._device
498
499 - def _getScsiId(self):
500 """ 501 Property target used to get the SCSI id value. 502 """ 503 return self._scsiId
504
505 - def _getHardwareId(self):
506 """ 507 Property target used to get the hardware id value. 508 """ 509 if self._scsiId is None: 510 return self._device 511 return self._scsiId
512
513 - def _getDriveSpeed(self):
514 """ 515 Property target used to get the drive speed. 516 """ 517 return self._driveSpeed
518
519 - def _getMedia(self):
520 """ 521 Property target used to get the media description. 522 """ 523 return self._media
524
525 - def _getDeviceType(self):
526 """ 527 Property target used to get the device type. 528 """ 529 return self._deviceType
530
531 - def _getDeviceVendor(self):
532 """ 533 Property target used to get the device vendor. 534 """ 535 return self._deviceVendor
536
537 - def _getDeviceId(self):
538 """ 539 Property target used to get the device id. 540 """ 541 return self._deviceId
542
543 - def _getDeviceBufferSize(self):
544 """ 545 Property target used to get the device buffer size. 546 """ 547 return self._deviceBufferSize
548
549 - def _getDeviceSupportsMulti(self):
550 """ 551 Property target used to get the device-support-multi flag. 552 """ 553 return self._deviceSupportsMulti
554
555 - def _getDeviceHasTray(self):
556 """ 557 Property target used to get the device-has-tray flag. 558 """ 559 return self._deviceHasTray
560
561 - def _getDeviceCanEject(self):
562 """ 563 Property target used to get the device-can-eject flag. 564 """ 565 return self._deviceCanEject
566
567 - def _getRefreshMediaDelay(self):
568 """ 569 Property target used to get the configured refresh media delay, in seconds. 570 """ 571 return self._refreshMediaDelay
572
573 - def _getEjectDelay(self):
574 """ 575 Property target used to get the configured eject delay, in seconds. 576 """ 577 return self._ejectDelay
578 579 device = property(_getDevice, None, None, doc="Filesystem device name for this writer.") 580 scsiId = property(_getScsiId, None, None, doc="SCSI id for the device, in the form C{[<method>:]scsibus,target,lun}.") 581 hardwareId = property(_getHardwareId, None, None, doc="Hardware id for this writer, either SCSI id or device path.") 582 driveSpeed = property(_getDriveSpeed, None, None, doc="Speed at which the drive writes.") 583 media = property(_getMedia, None, None, doc="Definition of media that is expected to be in the device.") 584 deviceType = property(_getDeviceType, None, None, doc="Type of the device, as returned from C{cdrecord -prcap}.") 585 deviceVendor = property(_getDeviceVendor, None, None, doc="Vendor of the device, as returned from C{cdrecord -prcap}.") 586 deviceId = property(_getDeviceId, None, None, doc="Device identification, as returned from C{cdrecord -prcap}.") 587 deviceBufferSize = property(_getDeviceBufferSize, None, None, doc="Size of the device's write buffer, in bytes.") 588 deviceSupportsMulti = property(_getDeviceSupportsMulti, None, None, doc="Indicates whether device supports multisession discs.") 589 deviceHasTray = property(_getDeviceHasTray, None, None, doc="Indicates whether the device has a media tray.") 590 deviceCanEject = property(_getDeviceCanEject, None, None, doc="Indicates whether the device supports ejecting its media.") 591 refreshMediaDelay = property(_getRefreshMediaDelay, None, None, doc="Refresh media delay, in seconds.") 592 ejectDelay = property(_getEjectDelay, None, None, doc="Eject delay, in seconds.") 593 594 595 ################################################# 596 # Methods related to device and media attributes 597 ################################################# 598
599 - def isRewritable(self):
600 """Indicates whether the media is rewritable per configuration.""" 601 return self._media.rewritable
602
603 - def _retrieveProperties(self):
604 """ 605 Retrieves properties for a device from C{cdrecord}. 606 607 The results are returned as a tuple of the object device attributes as 608 returned from L{_parsePropertiesOutput}: C{(deviceType, deviceVendor, 609 deviceId, deviceBufferSize, deviceSupportsMulti, deviceHasTray, 610 deviceCanEject)}. 611 612 @return: Results tuple as described above. 613 @raise IOError: If there is a problem talking to the device. 614 """ 615 args = CdWriter._buildPropertiesArgs(self.hardwareId) 616 command = resolveCommand(CDRECORD_COMMAND) 617 (result, output) = executeCommand(command, args, returnOutput=True, ignoreStderr=True) 618 if result != 0: 619 raise IOError("Error (%d) executing cdrecord command to get properties." % result) 620 return CdWriter._parsePropertiesOutput(output)
621
622 - def retrieveCapacity(self, entireDisc=False, useMulti=True):
623 """ 624 Retrieves capacity for the current media in terms of a C{MediaCapacity} 625 object. 626 627 If C{entireDisc} is passed in as C{True} the capacity will be for the 628 entire disc, as if it were to be rewritten from scratch. If the drive 629 does not support writing multisession discs or if C{useMulti} is passed 630 in as C{False}, the capacity will also be as if the disc were to be 631 rewritten from scratch, but the indicated boundaries value will be 632 C{None}. The same will happen if the disc cannot be read for some 633 reason. Otherwise, the capacity (including the boundaries) will 634 represent whatever space remains on the disc to be filled by future 635 sessions. 636 637 @param entireDisc: Indicates whether to return capacity for entire disc. 638 @type entireDisc: Boolean true/false 639 640 @param useMulti: Indicates whether a multisession disc should be assumed, if possible. 641 @type useMulti: Boolean true/false 642 643 @return: C{MediaCapacity} object describing the capacity of the media. 644 @raise IOError: If the media could not be read for some reason. 645 """ 646 boundaries = self._getBoundaries(entireDisc, useMulti) 647 return CdWriter._calculateCapacity(self._media, boundaries)
648
649 - def _getBoundaries(self, entireDisc=False, useMulti=True):
650 """ 651 Gets the ISO boundaries for the media. 652 653 If C{entireDisc} is passed in as C{True} the boundaries will be C{None}, 654 as if the disc were to be rewritten from scratch. If the drive does not 655 support writing multisession discs, the returned value will be C{None}. 656 The same will happen if the disc can't be read for some reason. 657 Otherwise, the returned value will be represent the boundaries of the 658 disc's current contents. 659 660 The results are returned as a tuple of (lower, upper) as needed by the 661 C{IsoImage} class. Note that these values are in terms of ISO sectors, 662 not bytes. Clients should generally consider the boundaries value 663 opaque, however. 664 665 @param entireDisc: Indicates whether to return capacity for entire disc. 666 @type entireDisc: Boolean true/false 667 668 @param useMulti: Indicates whether a multisession disc should be assumed, if possible. 669 @type useMulti: Boolean true/false 670 671 @return: Boundaries tuple or C{None}, as described above. 672 @raise IOError: If the media could not be read for some reason. 673 """ 674 if not self._deviceSupportsMulti: 675 logger.debug("Device does not support multisession discs; returning boundaries None.") 676 return None 677 elif not useMulti: 678 logger.debug("Use multisession flag is False; returning boundaries None.") 679 return None 680 elif entireDisc: 681 logger.debug("Entire disc flag is True; returning boundaries None.") 682 return None 683 else: 684 args = CdWriter._buildBoundariesArgs(self.hardwareId) 685 command = resolveCommand(CDRECORD_COMMAND) 686 (result, output) = executeCommand(command, args, returnOutput=True, ignoreStderr=True) 687 if result != 0: 688 logger.debug("Error (%d) executing cdrecord command to get capacity.", result) 689 logger.warn("Unable to read disc (might not be initialized); returning boundaries of None.") 690 return None 691 boundaries = CdWriter._parseBoundariesOutput(output) 692 if boundaries is None: 693 logger.debug("Returning disc boundaries: None") 694 else: 695 logger.debug("Returning disc boundaries: (%d, %d)", boundaries[0], boundaries[1]) 696 return boundaries
697 698 @staticmethod
699 - def _calculateCapacity(media, boundaries):
700 """ 701 Calculates capacity for the media in terms of boundaries. 702 703 If C{boundaries} is C{None} or the lower bound is 0 (zero), then the 704 capacity will be for the entire disc minus the initial lead in. 705 Otherwise, capacity will be as if the caller wanted to add an additional 706 session to the end of the existing data on the disc. 707 708 @param media: MediaDescription object describing the media capacity. 709 @param boundaries: Session boundaries as returned from L{_getBoundaries}. 710 711 @return: C{MediaCapacity} object describing the capacity of the media. 712 """ 713 if boundaries is None or boundaries[1] == 0: 714 logger.debug("Capacity calculations are based on a complete disc rewrite.") 715 sectorsAvailable = media.capacity - media.initialLeadIn 716 if sectorsAvailable < 0: sectorsAvailable = 0.0 717 bytesUsed = 0.0 718 bytesAvailable = convertSize(sectorsAvailable, UNIT_SECTORS, UNIT_BYTES) 719 else: 720 logger.debug("Capacity calculations are based on a new ISO session.") 721 sectorsAvailable = media.capacity - boundaries[1] - media.leadIn 722 if sectorsAvailable < 0: sectorsAvailable = 0.0 723 bytesUsed = convertSize(boundaries[1], UNIT_SECTORS, UNIT_BYTES) 724 bytesAvailable = convertSize(sectorsAvailable, UNIT_SECTORS, UNIT_BYTES) 725 logger.debug("Used [%s], available [%s].", displayBytes(bytesUsed), displayBytes(bytesAvailable)) 726 return MediaCapacity(bytesUsed, bytesAvailable, boundaries)
727 728 729 ####################################################### 730 # Methods used for working with the internal ISO image 731 ####################################################### 732
733 - def initializeImage(self, newDisc, tmpdir, mediaLabel=None):
734 """ 735 Initializes the writer's associated ISO image. 736 737 This method initializes the C{image} instance variable so that the caller 738 can use the C{addImageEntry} method. Once entries have been added, the 739 C{writeImage} method can be called with no arguments. 740 741 @param newDisc: Indicates whether the disc should be re-initialized 742 @type newDisc: Boolean true/false. 743 744 @param tmpdir: Temporary directory to use if needed 745 @type tmpdir: String representing a directory path on disk 746 747 @param mediaLabel: Media label to be applied to the image, if any 748 @type mediaLabel: String, no more than 25 characters long 749 """ 750 self._image = _ImageProperties() 751 self._image.newDisc = newDisc 752 self._image.tmpdir = encodePath(tmpdir) 753 self._image.mediaLabel = mediaLabel 754 self._image.entries = {} # mapping from path to graft point (if any)
755
756 - def addImageEntry(self, path, graftPoint):
757 """ 758 Adds a filepath entry to the writer's associated ISO image. 759 760 The contents of the filepath -- but not the path itself -- will be added 761 to the image at the indicated graft point. If you don't want to use a 762 graft point, just pass C{None}. 763 764 @note: Before calling this method, you must call L{initializeImage}. 765 766 @param path: File or directory to be added to the image 767 @type path: String representing a path on disk 768 769 @param graftPoint: Graft point to be used when adding this entry 770 @type graftPoint: String representing a graft point path, as described above 771 772 @raise ValueError: If initializeImage() was not previously called 773 """ 774 if self._image is None: 775 raise ValueError("Must call initializeImage() before using this method.") 776 if not os.path.exists(path): 777 raise ValueError("Path [%s] does not exist." % path) 778 self._image.entries[path] = graftPoint
779
780 - def setImageNewDisc(self, newDisc):
781 """ 782 Resets (overrides) the newDisc flag on the internal image. 783 @param newDisc: New disc flag to set 784 @raise ValueError: If initializeImage() was not previously called 785 """ 786 if self._image is None: 787 raise ValueError("Must call initializeImage() before using this method.") 788 self._image.newDisc = newDisc
789 790 # pylint: disable=C0201
791 - def getEstimatedImageSize(self):
792 """ 793 Gets the estimated size of the image associated with the writer. 794 @return: Estimated size of the image, in bytes. 795 @raise IOError: If there is a problem calling C{mkisofs}. 796 @raise ValueError: If initializeImage() was not previously called 797 """ 798 if self._image is None: 799 raise ValueError("Must call initializeImage() before using this method.") 800 image = IsoImage() 801 for path in self._image.entries.keys(): 802 image.addEntry(path, self._image.entries[path], override=False, contentsOnly=True) 803 return image.getEstimatedSize()
804 805 806 ###################################### 807 # Methods which expose device actions 808 ###################################### 809
810 - def openTray(self):
811 """ 812 Opens the device's tray and leaves it open. 813 814 This only works if the device has a tray and supports ejecting its media. 815 We have no way to know if the tray is currently open or closed, so we 816 just send the appropriate command and hope for the best. If the device 817 does not have a tray or does not support ejecting its media, then we do 818 nothing. 819 820 If the writer was constructed with C{noEject=True}, then this is a no-op. 821 822 Starting with Debian wheezy on my backup hardware, I started seeing 823 consistent problems with the eject command. I couldn't tell whether 824 these problems were due to the device management system or to the new 825 kernel (3.2.0). Initially, I saw simple eject failures, possibly because 826 I was opening and closing the tray too quickly. I worked around that 827 behavior with the new ejectDelay flag. 828 829 Later, I sometimes ran into issues after writing an image to a disc: 830 eject would give errors like "unable to eject, last error: Inappropriate 831 ioctl for device". Various sources online (like Ubuntu bug #875543) 832 suggested that the drive was being locked somehow, and that the 833 workaround was to run 'eject -i off' to unlock it. Sure enough, that 834 fixed the problem for me, so now it's a normal error-handling strategy. 835 836 @raise IOError: If there is an error talking to the device. 837 """ 838 if not self._noEject: 839 if self._deviceHasTray and self._deviceCanEject: 840 args = CdWriter._buildOpenTrayArgs(self._device) 841 result = executeCommand(EJECT_COMMAND, args)[0] 842 if result != 0: 843 logger.debug("Eject failed; attempting kludge of unlocking the tray before retrying.") 844 self.unlockTray() 845 result = executeCommand(EJECT_COMMAND, args)[0] 846 if result != 0: 847 raise IOError("Error (%d) executing eject command to open tray (failed even after unlocking tray)." % result) 848 logger.debug("Kludge was apparently successful.") 849 if self.ejectDelay is not None: 850 logger.debug("Per configuration, sleeping %d seconds after opening tray.", self.ejectDelay) 851 time.sleep(self.ejectDelay)
852
853 - def unlockTray(self):
854 """ 855 Unlocks the device's tray. 856 @raise IOError: If there is an error talking to the device. 857 """ 858 args = CdWriter._buildUnlockTrayArgs(self._device) 859 command = resolveCommand(EJECT_COMMAND) 860 result = executeCommand(command, args)[0] 861 if result != 0: 862 raise IOError("Error (%d) executing eject command to unlock tray." % result)
863
864 - def closeTray(self):
865 """ 866 Closes the device's tray. 867 868 This only works if the device has a tray and supports ejecting its media. 869 We have no way to know if the tray is currently open or closed, so we 870 just send the appropriate command and hope for the best. If the device 871 does not have a tray or does not support ejecting its media, then we do 872 nothing. 873 874 If the writer was constructed with C{noEject=True}, then this is a no-op. 875 876 @raise IOError: If there is an error talking to the device. 877 """ 878 if not self._noEject: 879 if self._deviceHasTray and self._deviceCanEject: 880 args = CdWriter._buildCloseTrayArgs(self._device) 881 command = resolveCommand(EJECT_COMMAND) 882 result = executeCommand(command, args)[0] 883 if result != 0: 884 raise IOError("Error (%d) executing eject command to close tray." % result)
885
886 - def refreshMedia(self):
887 """ 888 Opens and then immediately closes the device's tray, to refresh the 889 device's idea of the media. 890 891 Sometimes, a device gets confused about the state of its media. Often, 892 all it takes to solve the problem is to eject the media and then 893 immediately reload it. (There are also configurable eject and refresh 894 media delays which can be applied, for situations where this makes a 895 difference.) 896 897 This only works if the device has a tray and supports ejecting its media. 898 We have no way to know if the tray is currently open or closed, so we 899 just send the appropriate command and hope for the best. If the device 900 does not have a tray or does not support ejecting its media, then we do 901 nothing. The configured delays still apply, though. 902 903 @raise IOError: If there is an error talking to the device. 904 """ 905 self.openTray() 906 self.closeTray() 907 self.unlockTray() # on some systems, writing a disc leaves the tray locked, yikes! 908 if self.refreshMediaDelay is not None: 909 logger.debug("Per configuration, sleeping %d seconds to stabilize media state.", self.refreshMediaDelay) 910 time.sleep(self.refreshMediaDelay) 911 logger.debug("Media refresh complete; hopefully media state is stable now.")
912
913 - def writeImage(self, imagePath=None, newDisc=False, writeMulti=True):
914 """ 915 Writes an ISO image to the media in the device. 916 917 If C{newDisc} is passed in as C{True}, we assume that the entire disc 918 will be overwritten, and the media will be blanked before writing it if 919 possible (i.e. if the media is rewritable). 920 921 If C{writeMulti} is passed in as C{True}, then a multisession disc will 922 be written if possible (i.e. if the drive supports writing multisession 923 discs). 924 925 if C{imagePath} is passed in as C{None}, then the existing image 926 configured with C{initializeImage} will be used. Under these 927 circumstances, the passed-in C{newDisc} flag will be ignored. 928 929 By default, we assume that the disc can be written multisession and that 930 we should append to the current contents of the disc. In any case, the 931 ISO image must be generated appropriately (i.e. must take into account 932 any existing session boundaries, etc.) 933 934 @param imagePath: Path to an ISO image on disk, or C{None} to use writer's image 935 @type imagePath: String representing a path on disk 936 937 @param newDisc: Indicates whether the entire disc will overwritten. 938 @type newDisc: Boolean true/false. 939 940 @param writeMulti: Indicates whether a multisession disc should be written, if possible. 941 @type writeMulti: Boolean true/false 942 943 @raise ValueError: If the image path is not absolute. 944 @raise ValueError: If some path cannot be encoded properly. 945 @raise IOError: If the media could not be written to for some reason. 946 @raise ValueError: If no image is passed in and initializeImage() was not previously called 947 """ 948 if imagePath is None: 949 if self._image is None: 950 raise ValueError("Must call initializeImage() before using this method with no image path.") 951 try: 952 imagePath = self._createImage() 953 self._writeImage(imagePath, writeMulti, self._image.newDisc) 954 finally: 955 if imagePath is not None and os.path.exists(imagePath): 956 try: os.unlink(imagePath) 957 except: pass 958 else: 959 imagePath = encodePath(imagePath) 960 if not os.path.isabs(imagePath): 961 raise ValueError("Image path must be absolute.") 962 self._writeImage(imagePath, writeMulti, newDisc)
963 964 # pylint: disable=C0201
965 - def _createImage(self):
966 """ 967 Creates an ISO image based on configuration in self._image. 968 @return: Path to the newly-created ISO image on disk. 969 @raise IOError: If there is an error writing the image to disk. 970 @raise ValueError: If there are no filesystem entries in the image 971 @raise ValueError: If a path cannot be encoded properly. 972 """ 973 path = None 974 capacity = self.retrieveCapacity(entireDisc=self._image.newDisc) 975 image = IsoImage(self.device, capacity.boundaries) 976 image.volumeId = self._image.mediaLabel # may be None, which is also valid 977 for key in self._image.entries.keys(): 978 image.addEntry(key, self._image.entries[key], override=False, contentsOnly=True) 979 size = image.getEstimatedSize() 980 logger.info("Image size will be %s.", displayBytes(size)) 981 available = capacity.bytesAvailable 982 logger.debug("Media capacity: %s", displayBytes(available)) 983 if size > available: 984 logger.error("Image [%s] does not fit in available capacity [%s].", displayBytes(size), displayBytes(available)) 985 raise IOError("Media does not contain enough capacity to store image.") 986 try: 987 (handle, path) = tempfile.mkstemp(dir=self._image.tmpdir) 988 try: os.close(handle) 989 except: pass 990 image.writeImage(path) 991 logger.debug("Completed creating image [%s].", path) 992 return path 993 except Exception, e: 994 if path is not None and os.path.exists(path): 995 try: os.unlink(path) 996 except: pass 997 raise e
998
999 - def _writeImage(self, imagePath, writeMulti, newDisc):
1000 """ 1001 Write an ISO image to disc using cdrecord. 1002 The disc is blanked first if C{newDisc} is C{True}. 1003 @param imagePath: Path to an ISO image on disk 1004 @param writeMulti: Indicates whether a multisession disc should be written, if possible. 1005 @param newDisc: Indicates whether the entire disc will overwritten. 1006 """ 1007 if newDisc: 1008 self._blankMedia() 1009 args = CdWriter._buildWriteArgs(self.hardwareId, imagePath, self._driveSpeed, writeMulti and self._deviceSupportsMulti) 1010 command = resolveCommand(CDRECORD_COMMAND) 1011 result = executeCommand(command, args)[0] 1012 if result != 0: 1013 raise IOError("Error (%d) executing command to write disc." % result) 1014 self.refreshMedia()
1015
1016 - def _blankMedia(self):
1017 """ 1018 Blanks the media in the device, if the media is rewritable. 1019 @raise IOError: If the media could not be written to for some reason. 1020 """ 1021 if self.isRewritable(): 1022 args = CdWriter._buildBlankArgs(self.hardwareId) 1023 command = resolveCommand(CDRECORD_COMMAND) 1024 result = executeCommand(command, args)[0] 1025 if result != 0: 1026 raise IOError("Error (%d) executing command to blank disc." % result) 1027 self.refreshMedia()
1028 1029 1030 ####################################### 1031 # Methods used to parse command output 1032 ####################################### 1033 1034 @staticmethod
1035 - def _parsePropertiesOutput(output):
1036 """ 1037 Parses the output from a C{cdrecord} properties command. 1038 1039 The C{output} parameter should be a list of strings as returned from 1040 C{executeCommand} for a C{cdrecord} command with arguments as from 1041 C{_buildPropertiesArgs}. The list of strings will be parsed to yield 1042 information about the properties of the device. 1043 1044 The output is expected to be a huge long list of strings. Unfortunately, 1045 the strings aren't in a completely regular format. However, the format 1046 of individual lines seems to be regular enough that we can look for 1047 specific values. Two kinds of parsing take place: one kind of parsing 1048 picks out out specific values like the device id, device vendor, etc. 1049 The other kind of parsing just sets a boolean flag C{True} if a matching 1050 line is found. All of the parsing is done with regular expressions. 1051 1052 Right now, pretty much nothing in the output is required and we should 1053 parse an empty document successfully (albeit resulting in a device that 1054 can't eject, doesn't have a tray and doesnt't support multisession 1055 discs). I had briefly considered erroring out if certain lines weren't 1056 found or couldn't be parsed, but that seems like a bad idea given that 1057 most of the information is just for reference. 1058 1059 The results are returned as a tuple of the object device attributes: 1060 C{(deviceType, deviceVendor, deviceId, deviceBufferSize, 1061 deviceSupportsMulti, deviceHasTray, deviceCanEject)}. 1062 1063 @param output: Output from a C{cdrecord -prcap} command. 1064 1065 @return: Results tuple as described above. 1066 @raise IOError: If there is problem parsing the output. 1067 """ 1068 deviceType = None 1069 deviceVendor = None 1070 deviceId = None 1071 deviceBufferSize = None 1072 deviceSupportsMulti = False 1073 deviceHasTray = False 1074 deviceCanEject = False 1075 typePattern = re.compile(r"(^Device type\s*:\s*)(.*)(\s*)(.*$)") 1076 vendorPattern = re.compile(r"(^Vendor_info\s*:\s*'\s*)(.*?)(\s*')(.*$)") 1077 idPattern = re.compile(r"(^Identifikation\s*:\s*'\s*)(.*?)(\s*')(.*$)") 1078 bufferPattern = re.compile(r"(^\s*Buffer size in KB:\s*)(.*?)(\s*$)") 1079 multiPattern = re.compile(r"^\s*Does read multi-session.*$") 1080 trayPattern = re.compile(r"^\s*Loading mechanism type: tray.*$") 1081 ejectPattern = re.compile(r"^\s*Does support ejection.*$") 1082 for line in output: 1083 if typePattern.search(line): 1084 deviceType = typePattern.search(line).group(2) 1085 logger.info("Device type is [%s].", deviceType) 1086 elif vendorPattern.search(line): 1087 deviceVendor = vendorPattern.search(line).group(2) 1088 logger.info("Device vendor is [%s].", deviceVendor) 1089 elif idPattern.search(line): 1090 deviceId = idPattern.search(line).group(2) 1091 logger.info("Device id is [%s].", deviceId) 1092 elif bufferPattern.search(line): 1093 try: 1094 sectors = int(bufferPattern.search(line).group(2)) 1095 deviceBufferSize = convertSize(sectors, UNIT_KBYTES, UNIT_BYTES) 1096 logger.info("Device buffer size is [%d] bytes.", deviceBufferSize) 1097 except TypeError: pass 1098 elif multiPattern.search(line): 1099 deviceSupportsMulti = True 1100 logger.info("Device does support multisession discs.") 1101 elif trayPattern.search(line): 1102 deviceHasTray = True 1103 logger.info("Device has a tray.") 1104 elif ejectPattern.search(line): 1105 deviceCanEject = True 1106 logger.info("Device can eject its media.") 1107 return (deviceType, deviceVendor, deviceId, deviceBufferSize, deviceSupportsMulti, deviceHasTray, deviceCanEject)
1108 1109 @staticmethod
1110 - def _parseBoundariesOutput(output):
1111 """ 1112 Parses the output from a C{cdrecord} capacity command. 1113 1114 The C{output} parameter should be a list of strings as returned from 1115 C{executeCommand} for a C{cdrecord} command with arguments as from 1116 C{_buildBoundaryArgs}. The list of strings will be parsed to yield 1117 information about the capacity of the media in the device. 1118 1119 Basically, we expect the list of strings to include just one line, a pair 1120 of values. There isn't supposed to be whitespace, but we allow it anyway 1121 in the regular expression. Any lines below the one line we parse are 1122 completely ignored. It would be a good idea to ignore C{stderr} when 1123 executing the C{cdrecord} command that generates output for this method, 1124 because sometimes C{cdrecord} spits out kernel warnings about the actual 1125 output. 1126 1127 The results are returned as a tuple of (lower, upper) as needed by the 1128 C{IsoImage} class. Note that these values are in terms of ISO sectors, 1129 not bytes. Clients should generally consider the boundaries value 1130 opaque, however. 1131 1132 @note: If the boundaries output can't be parsed, we return C{None}. 1133 1134 @param output: Output from a C{cdrecord -msinfo} command. 1135 1136 @return: Boundaries tuple as described above. 1137 @raise IOError: If there is problem parsing the output. 1138 """ 1139 if len(output) < 1: 1140 logger.warn("Unable to read disc (might not be initialized); returning full capacity.") 1141 return None 1142 boundaryPattern = re.compile(r"(^\s*)([0-9]*)(\s*,\s*)([0-9]*)(\s*$)") 1143 parsed = boundaryPattern.search(output[0]) 1144 if not parsed: 1145 raise IOError("Unable to parse output of boundaries command.") 1146 try: 1147 boundaries = ( int(parsed.group(2)), int(parsed.group(4)) ) 1148 except TypeError: 1149 raise IOError("Unable to parse output of boundaries command.") 1150 return boundaries
1151 1152 1153 ################################# 1154 # Methods used to build commands 1155 ################################# 1156 1157 @staticmethod
1158 - def _buildOpenTrayArgs(device):
1159 """ 1160 Builds a list of arguments to be passed to a C{eject} command. 1161 1162 The arguments will cause the C{eject} command to open the tray and 1163 eject the media. No validation is done by this method as to whether 1164 this action actually makes sense. 1165 1166 @param device: Filesystem device name for this writer, i.e. C{/dev/cdrw}. 1167 1168 @return: List suitable for passing to L{util.executeCommand} as C{args}. 1169 """ 1170 args = [] 1171 args.append(device) 1172 return args
1173 1174 @staticmethod
1175 - def _buildUnlockTrayArgs(device):
1176 """ 1177 Builds a list of arguments to be passed to a C{eject} command. 1178 1179 The arguments will cause the C{eject} command to unlock the tray. 1180 1181 @param device: Filesystem device name for this writer, i.e. C{/dev/cdrw}. 1182 1183 @return: List suitable for passing to L{util.executeCommand} as C{args}. 1184 """ 1185 args = [] 1186 args.append("-i") 1187 args.append("off") 1188 args.append(device) 1189 return args
1190 1191 @staticmethod
1192 - def _buildCloseTrayArgs(device):
1193 """ 1194 Builds a list of arguments to be passed to a C{eject} command. 1195 1196 The arguments will cause the C{eject} command to close the tray and reload 1197 the media. No validation is done by this method as to whether this 1198 action actually makes sense. 1199 1200 @param device: Filesystem device name for this writer, i.e. C{/dev/cdrw}. 1201 1202 @return: List suitable for passing to L{util.executeCommand} as C{args}. 1203 """ 1204 args = [] 1205 args.append("-t") 1206 args.append(device) 1207 return args
1208 1209 @staticmethod
1210 - def _buildPropertiesArgs(hardwareId):
1211 """ 1212 Builds a list of arguments to be passed to a C{cdrecord} command. 1213 1214 The arguments will cause the C{cdrecord} command to ask the device 1215 for a list of its capacities via the C{-prcap} switch. 1216 1217 @param hardwareId: Hardware id for the device (either SCSI id or device path) 1218 1219 @return: List suitable for passing to L{util.executeCommand} as C{args}. 1220 """ 1221 args = [] 1222 args.append("-prcap") 1223 args.append("dev=%s" % hardwareId) 1224 return args
1225 1226 @staticmethod
1227 - def _buildBoundariesArgs(hardwareId):
1228 """ 1229 Builds a list of arguments to be passed to a C{cdrecord} command. 1230 1231 The arguments will cause the C{cdrecord} command to ask the device for 1232 the current multisession boundaries of the media using the C{-msinfo} 1233 switch. 1234 1235 @param hardwareId: Hardware id for the device (either SCSI id or device path) 1236 1237 @return: List suitable for passing to L{util.executeCommand} as C{args}. 1238 """ 1239 args = [] 1240 args.append("-msinfo") 1241 args.append("dev=%s" % hardwareId) 1242 return args
1243 1244 @staticmethod
1245 - def _buildBlankArgs(hardwareId, driveSpeed=None):
1246 """ 1247 Builds a list of arguments to be passed to a C{cdrecord} command. 1248 1249 The arguments will cause the C{cdrecord} command to blank the media in 1250 the device identified by C{hardwareId}. No validation is done by this method 1251 as to whether the action makes sense (i.e. to whether the media even can 1252 be blanked). 1253 1254 @param hardwareId: Hardware id for the device (either SCSI id or device path) 1255 @param driveSpeed: Speed at which the drive writes. 1256 1257 @return: List suitable for passing to L{util.executeCommand} as C{args}. 1258 """ 1259 args = [] 1260 args.append("-v") 1261 args.append("blank=fast") 1262 if driveSpeed is not None: 1263 args.append("speed=%d" % driveSpeed) 1264 args.append("dev=%s" % hardwareId) 1265 return args
1266 1267 @staticmethod
1268 - def _buildWriteArgs(hardwareId, imagePath, driveSpeed=None, writeMulti=True):
1269 """ 1270 Builds a list of arguments to be passed to a C{cdrecord} command. 1271 1272 The arguments will cause the C{cdrecord} command to write the indicated 1273 ISO image (C{imagePath}) to the media in the device identified by 1274 C{hardwareId}. The C{writeMulti} argument controls whether to write a 1275 multisession disc. No validation is done by this method as to whether 1276 the action makes sense (i.e. to whether the device even can write 1277 multisession discs, for instance). 1278 1279 @param hardwareId: Hardware id for the device (either SCSI id or device path) 1280 @param imagePath: Path to an ISO image on disk. 1281 @param driveSpeed: Speed at which the drive writes. 1282 @param writeMulti: Indicates whether to write a multisession disc. 1283 1284 @return: List suitable for passing to L{util.executeCommand} as C{args}. 1285 """ 1286 args = [] 1287 args.append("-v") 1288 if driveSpeed is not None: 1289 args.append("speed=%d" % driveSpeed) 1290 args.append("dev=%s" % hardwareId) 1291 if writeMulti: 1292 args.append("-multi") 1293 args.append("-data") 1294 args.append(imagePath) 1295 return args
1296