CedarBackup2.writers.cdwriter

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