CedarBackup3.writers.cdwriter

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

# Source Code for Module CedarBackup3.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,2015 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 3 (>= 3.4)
      29  # Project  : Cedar Backup, release 3
      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 CedarBackup3.util import resolveCommand, executeCommand
      65  from CedarBackup3.util import convertSize, displayBytes, encodePath
      66  from CedarBackup3.util import UNIT_SECTORS, UNIT_BYTES, UNIT_KBYTES, UNIT_MBYTES
      67  from CedarBackup3.writers.util import validateDevice, validateScsiId, validateDriveSpeed
      68  from CedarBackup3.writers.util import IsoImage
      69
      70
      71  ########################################################################
      72  # Module-wide constants and variables
      73  ########################################################################
      74
      75  logger = logging.getLogger("CedarBackup3.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.warning("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 -   def getEstimatedImageSize(self):
     791        """
     792        Gets the estimated size of the image associated with the writer.
     793        @return: Estimated size of the image, in bytes.
     794        @raise IOError: If there is a problem calling C{mkisofs}.
     795        @raise ValueError: If initializeImage() was not previously called
     796        """
     797        if self._image is None:
     798           raise ValueError("Must call initializeImage() before using this method.")
     799        image = IsoImage()
     800        for path in list(self._image.entries.keys()):
     801           image.addEntry(path, self._image.entries[path], override=False, contentsOnly=True)
     802        return image.getEstimatedSize()
     803
     804
     805     ######################################
     806     # Methods which expose device actions
     807     ######################################
     808
     809 -   def openTray(self):
     810        """
     811        Opens the device's tray and leaves it open.
     812
     813        This only works if the device has a tray and supports ejecting its media.
     814        We have no way to know if the tray is currently open or closed, so we
     815        just send the appropriate command and hope for the best.  If the device
     816        does not have a tray or does not support ejecting its media, then we do
     817        nothing.
     818
     819        If the writer was constructed with C{noEject=True}, then this is a no-op.
     820
     821        Starting with Debian wheezy on my backup hardware, I started seeing
     822        consistent problems with the eject command.  I couldn't tell whether
     823        these problems were due to the device management system or to the new
     824        kernel (3.2.0).  Initially, I saw simple eject failures, possibly because
     825        I was opening and closing the tray too quickly.  I worked around that
     826        behavior with the new ejectDelay flag.
     827
     828        Later, I sometimes ran into issues after writing an image to a disc:
     829        eject would give errors like "unable to eject, last error: Inappropriate
     830        ioctl for device".  Various sources online (like Ubuntu bug #875543)
     831        suggested that the drive was being locked somehow, and that the
     832        workaround was to run 'eject -i off' to unlock it.  Sure enough, that
     833        fixed the problem for me, so now it's a normal error-handling strategy.
     834
     835        @raise IOError: If there is an error talking to the device.
     836        """
     837        if not self._noEject:
     838           if self._deviceHasTray and self._deviceCanEject:
     839              args = CdWriter._buildOpenTrayArgs(self._device)
     840              result = executeCommand(EJECT_COMMAND, args)[0]
     841              if result != 0:
     842                 logger.debug("Eject failed; attempting kludge of unlocking the tray before retrying.")
     843                 self.unlockTray()
     844                 result = executeCommand(EJECT_COMMAND, args)[0]
     845                 if result != 0:
     846                    raise IOError("Error (%d) executing eject command to open tray (failed even after unlocking tray)." % result)
     847                 logger.debug("Kludge was apparently successful.")
     848              if self.ejectDelay is not None:
     849                 logger.debug("Per configuration, sleeping %d seconds after opening tray.", self.ejectDelay)
     850                 time.sleep(self.ejectDelay)
     851
     852 -   def unlockTray(self):
     853        """
     854        Unlocks the device's tray.
     855        @raise IOError: If there is an error talking to the device.
     856        """
     857        args = CdWriter._buildUnlockTrayArgs(self._device)
     858        command = resolveCommand(EJECT_COMMAND)
     859        result = executeCommand(command, args)[0]
     860        if result != 0:
     861           raise IOError("Error (%d) executing eject command to unlock tray." % result)
     862
     863 -   def closeTray(self):
     864        """
     865        Closes the device's tray.
     866
     867        This only works if the device has a tray and supports ejecting its media.
     868        We have no way to know if the tray is currently open or closed, so we
     869        just send the appropriate command and hope for the best.  If the device
     870        does not have a tray or does not support ejecting its media, then we do
     871        nothing.
     872
     873        If the writer was constructed with C{noEject=True}, then this is a no-op.
     874
     875        @raise IOError: If there is an error talking to the device.
     876        """
     877        if not self._noEject:
     878           if self._deviceHasTray and self._deviceCanEject:
     879              args = CdWriter._buildCloseTrayArgs(self._device)
     880              command = resolveCommand(EJECT_COMMAND)
     881              result = executeCommand(command, args)[0]
     882              if result != 0:
     883                 raise IOError("Error (%d) executing eject command to close tray." % result)
     884
     885 -   def refreshMedia(self):
     886        """
     887        Opens and then immediately closes the device's tray, to refresh the
     888        device's idea of the media.
     889
     890        Sometimes, a device gets confused about the state of its media.  Often,
     891        all it takes to solve the problem is to eject the media and then
     892        immediately reload it.  (There are also configurable eject and refresh
     893        media delays which can be applied, for situations where this makes a
     894        difference.)
     895
     896        This only works if the device has a tray and supports ejecting its media.
     897        We have no way to know if the tray is currently open or closed, so we
     898        just send the appropriate command and hope for the best.  If the device
     899        does not have a tray or does not support ejecting its media, then we do
     900        nothing.  The configured delays still apply, though.
     901
     902        @raise IOError: If there is an error talking to the device.
     903        """
     904        self.openTray()
     905        self.closeTray()
     906        self.unlockTray()  # on some systems, writing a disc leaves the tray locked, yikes!
     907        if self.refreshMediaDelay is not None:
     908           logger.debug("Per configuration, sleeping %d seconds to stabilize media state.", self.refreshMediaDelay)
     909           time.sleep(self.refreshMediaDelay)
     910        logger.debug("Media refresh complete; hopefully media state is stable now.")
     911
     912 -   def writeImage(self, imagePath=None, newDisc=False, writeMulti=True):
     913        """
     914        Writes an ISO image to the media in the device.
     915
     916        If C{newDisc} is passed in as C{True}, we assume that the entire disc
     917        will be overwritten, and the media will be blanked before writing it if
     918        possible (i.e. if the media is rewritable).
     919
     920        If C{writeMulti} is passed in as C{True}, then a multisession disc will
     921        be written if possible (i.e. if the drive supports writing multisession
     922        discs).
     923
     924        if C{imagePath} is passed in as C{None}, then the existing image
     925        configured with C{initializeImage} will be used.  Under these
     926        circumstances, the passed-in C{newDisc} flag will be ignored.
     927
     928        By default, we assume that the disc can be written multisession and that
     929        we should append to the current contents of the disc.  In any case, the
     930        ISO image must be generated appropriately (i.e. must take into account
     931        any existing session boundaries, etc.)
     932
     933        @param imagePath: Path to an ISO image on disk, or C{None} to use writer's image
     934        @type imagePath: String representing a path on disk
     935
     936        @param newDisc: Indicates whether the entire disc will overwritten.
     937        @type newDisc: Boolean true/false.
     938
     939        @param writeMulti: Indicates whether a multisession disc should be written, if possible.
     940        @type writeMulti: Boolean true/false
     941
     942        @raise ValueError: If the image path is not absolute.
     943        @raise ValueError: If some path cannot be encoded properly.
     944        @raise IOError: If the media could not be written to for some reason.
     945        @raise ValueError: If no image is passed in and initializeImage() was not previously called
     946        """
     947        if imagePath is None:
     948           if self._image is None:
     949              raise ValueError("Must call initializeImage() before using this method with no image path.")
     950           try:
     951              imagePath = self._createImage()
     952              self._writeImage(imagePath, writeMulti, self._image.newDisc)
     953           finally:
     954              if imagePath is not None and os.path.exists(imagePath):
     955                 try: os.unlink(imagePath)
     956                 except: pass
     957        else:
     958           imagePath = encodePath(imagePath)
     959           if not os.path.isabs(imagePath):
     960              raise ValueError("Image path must be absolute.")
     961           self._writeImage(imagePath, writeMulti, newDisc)
     962
     963 -   def _createImage(self):
     964        """
     965        Creates an ISO image based on configuration in self._image.
     966        @return: Path to the newly-created ISO image on disk.
     967        @raise IOError: If there is an error writing the image to disk.
     968        @raise ValueError: If there are no filesystem entries in the image
     969        @raise ValueError: If a path cannot be encoded properly.
     970        """
     971        path = None
     972        capacity = self.retrieveCapacity(entireDisc=self._image.newDisc)
     973        image = IsoImage(self.device, capacity.boundaries)
     974        image.volumeId = self._image.mediaLabel  # may be None, which is also valid
     975        for key in list(self._image.entries.keys()):
     976           image.addEntry(key, self._image.entries[key], override=False, contentsOnly=True)
     977        size = image.getEstimatedSize()
     978        logger.info("Image size will be %s.", displayBytes(size))
     979        available = capacity.bytesAvailable
     980        logger.debug("Media capacity: %s", displayBytes(available))
     981        if size > available:
     982           logger.error("Image [%s] does not fit in available capacity [%s].", displayBytes(size), displayBytes(available))
     983           raise IOError("Media does not contain enough capacity to store image.")
     984        try:
     985           (handle, path) = tempfile.mkstemp(dir=self._image.tmpdir)
     986           try: os.close(handle)
     987           except: pass
     988           image.writeImage(path)
     989           logger.debug("Completed creating image [%s].", path)
     990           return path
     991        except Exception as e:
     992           if path is not None and os.path.exists(path):
     993              try: os.unlink(path)
     994              except: pass
     995           raise e
     996
     997 -   def _writeImage(self, imagePath, writeMulti, newDisc):
     998        """
     999        Write an ISO image to disc using cdrecord.
    1000        The disc is blanked first if C{newDisc} is C{True}.
    1001        @param imagePath: Path to an ISO image on disk
    1002        @param writeMulti: Indicates whether a multisession disc should be written, if possible.
    1003        @param newDisc: Indicates whether the entire disc will overwritten.
    1004        """
    1005        if newDisc:
    1006           self._blankMedia()
    1007        args = CdWriter._buildWriteArgs(self.hardwareId, imagePath, self._driveSpeed, writeMulti and self._deviceSupportsMulti)
    1008        command = resolveCommand(CDRECORD_COMMAND)
    1009        result = executeCommand(command, args)[0]
    1010        if result != 0:
    1011           raise IOError("Error (%d) executing command to write disc." % result)
    1012        self.refreshMedia()
    1013
    1014 -   def _blankMedia(self):
    1015        """
    1016        Blanks the media in the device, if the media is rewritable.
    1017        @raise IOError: If the media could not be written to for some reason.
    1018        """
    1019        if self.isRewritable():
    1020           args = CdWriter._buildBlankArgs(self.hardwareId)
    1021           command = resolveCommand(CDRECORD_COMMAND)
    1022           result = executeCommand(command, args)[0]
    1023           if result != 0:
    1024              raise IOError("Error (%d) executing command to blank disc." % result)
    1025           self.refreshMedia()
    1026
    1027
    1028     #######################################
    1029     # Methods used to parse command output
    1030     #######################################
    1031
    1032     @staticmethod
    1033 -   def _parsePropertiesOutput(output):
    1034        """
    1035        Parses the output from a C{cdrecord} properties command.
    1036
    1037        The C{output} parameter should be a list of strings as returned from
    1038        C{executeCommand} for a C{cdrecord} command with arguments as from
    1039        C{_buildPropertiesArgs}.  The list of strings will be parsed to yield
    1040        information about the properties of the device.
    1041
    1042        The output is expected to be a huge long list of strings.  Unfortunately,
    1043        the strings aren't in a completely regular format.  However, the format
    1044        of individual lines seems to be regular enough that we can look for
    1045        specific values.  Two kinds of parsing take place: one kind of parsing
    1046        picks out out specific values like the device id, device vendor, etc.
    1047        The other kind of parsing just sets a boolean flag C{True} if a matching
    1048        line is found.  All of the parsing is done with regular expressions.
    1049
    1050        Right now, pretty much nothing in the output is required and we should
    1051        parse an empty document successfully (albeit resulting in a device that
    1052        can't eject, doesn't have a tray and doesnt't support multisession
    1053        discs).   I had briefly considered erroring out if certain lines weren't
    1054        found or couldn't be parsed, but that seems like a bad idea given that
    1055        most of the information is just for reference.
    1056
    1057        The results are returned as a tuple of the object device attributes:
    1058        C{(deviceType, deviceVendor, deviceId, deviceBufferSize,
    1059        deviceSupportsMulti, deviceHasTray, deviceCanEject)}.
    1060
    1061        @param output: Output from a C{cdrecord -prcap} command.
    1062
    1063        @return: Results tuple as described above.
    1064        @raise IOError: If there is problem parsing the output.
    1065        """
    1066        deviceType = None
    1067        deviceVendor = None
    1068        deviceId = None
    1069        deviceBufferSize = None
    1070        deviceSupportsMulti = False
    1071        deviceHasTray = False
    1072        deviceCanEject = False
    1073        typePattern   = re.compile(r"(^Device type\s*:\s*)(.*)(\s*)(.*$)")
    1074        vendorPattern = re.compile(r"(^Vendor_info\s*:\s*'\s*)(.*?)(\s*')(.*$)")
    1075        idPattern     = re.compile(r"(^Identifikation\s*:\s*'\s*)(.*?)(\s*')(.*$)")
    1076        bufferPattern = re.compile(r"(^\s*Buffer size in KB:\s*)(.*?)(\s*$)")
    1077        multiPattern  = re.compile(r"^\s*Does read multi-session.*$")
    1078        trayPattern   = re.compile(r"^\s*Loading mechanism type: tray.*$")
    1079        ejectPattern  = re.compile(r"^\s*Does support ejection.*$")
    1080        for line in output:
    1081           if typePattern.search(line):
    1082              deviceType =  typePattern.search(line).group(2)
    1083              logger.info("Device type is [%s].", deviceType)
    1084           elif vendorPattern.search(line):
    1085              deviceVendor = vendorPattern.search(line).group(2)
    1086              logger.info("Device vendor is [%s].", deviceVendor)
    1087           elif idPattern.search(line):
    1088              deviceId = idPattern.search(line).group(2)
    1089              logger.info("Device id is [%s].", deviceId)
    1090           elif bufferPattern.search(line):
    1091              try:
    1092                 sectors = int(bufferPattern.search(line).group(2))
    1093                 deviceBufferSize = convertSize(sectors, UNIT_KBYTES, UNIT_BYTES)
    1094                 logger.info("Device buffer size is [%d] bytes.", deviceBufferSize)
    1095              except TypeError: pass
    1096           elif multiPattern.search(line):
    1097              deviceSupportsMulti = True
    1098              logger.info("Device does support multisession discs.")
    1099           elif trayPattern.search(line):
    1100              deviceHasTray = True
    1101              logger.info("Device has a tray.")
    1102           elif ejectPattern.search(line):
    1103              deviceCanEject = True
    1104              logger.info("Device can eject its media.")
    1105        return (deviceType, deviceVendor, deviceId, deviceBufferSize, deviceSupportsMulti, deviceHasTray, deviceCanEject)
    1106
    1107     @staticmethod
    1108 -   def _parseBoundariesOutput(output):
    1109        """
    1110        Parses the output from a C{cdrecord} capacity command.
    1111
    1112        The C{output} parameter should be a list of strings as returned from
    1113        C{executeCommand} for a C{cdrecord} command with arguments as from
    1114        C{_buildBoundaryArgs}.  The list of strings will be parsed to yield
    1115        information about the capacity of the media in the device.
    1116
    1117        Basically, we expect the list of strings to include just one line, a pair
    1118        of values.  There isn't supposed to be whitespace, but we allow it anyway
    1119        in the regular expression.  Any lines below the one line we parse are
    1120        completely ignored.  It would be a good idea to ignore C{stderr} when
    1121        executing the C{cdrecord} command that generates output for this method,
    1122        because sometimes C{cdrecord} spits out kernel warnings about the actual
    1123        output.
    1124
    1125        The results are returned as a tuple of (lower, upper) as needed by the
    1126        C{IsoImage} class.  Note that these values are in terms of ISO sectors,
    1127        not bytes.  Clients should generally consider the boundaries value
    1128        opaque, however.
    1129
    1130        @note: If the boundaries output can't be parsed, we return C{None}.
    1131
    1132        @param output: Output from a C{cdrecord -msinfo} command.
    1133
    1134        @return: Boundaries tuple as described above.
    1135        @raise IOError: If there is problem parsing the output.
    1136        """
    1137        if len(output) < 1:
    1138           logger.warning("Unable to read disc (might not be initialized); returning full capacity.")
    1139           return None
    1140        boundaryPattern = re.compile(r"(^\s*)([0-9]*)(\s*,\s*)([0-9]*)(\s*$)")
    1141        parsed = boundaryPattern.search(output[0])
    1142        if not parsed:
    1143           raise IOError("Unable to parse output of boundaries command.")
    1144        try:
    1145           boundaries = ( int(parsed.group(2)), int(parsed.group(4)) )
    1146        except TypeError:
    1147           raise IOError("Unable to parse output of boundaries command.")
    1148        return boundaries
    1149
    1150
    1151     #################################
    1152     # Methods used to build commands
    1153     #################################
    1154
    1155     @staticmethod
    1156 -   def _buildOpenTrayArgs(device):
    1157        """
    1158        Builds a list of arguments to be passed to a C{eject} command.
    1159
    1160        The arguments will cause the C{eject} command to open the tray and
    1161        eject the media.  No validation is done by this method as to whether
    1162        this action actually makes sense.
    1163
    1164        @param device: Filesystem device name for this writer, i.e. C{/dev/cdrw}.
    1165
    1166        @return: List suitable for passing to L{util.executeCommand} as C{args}.
    1167        """
    1168        args = []
    1169        args.append(device)
    1170        return args
    1171
    1172     @staticmethod
    1173 -   def _buildUnlockTrayArgs(device):
    1174        """
    1175        Builds a list of arguments to be passed to a C{eject} command.
    1176
    1177        The arguments will cause the C{eject} command to unlock the tray.
    1178
    1179        @param device: Filesystem device name for this writer, i.e. C{/dev/cdrw}.
    1180
    1181        @return: List suitable for passing to L{util.executeCommand} as C{args}.
    1182        """
    1183        args = []
    1184        args.append("-i")
    1185        args.append("off")
    1186        args.append(device)
    1187        return args
    1188
    1189     @staticmethod
    1190 -   def _buildCloseTrayArgs(device):
    1191        """
    1192        Builds a list of arguments to be passed to a C{eject} command.
    1193
    1194        The arguments will cause the C{eject} command to close the tray and reload
    1195        the media.  No validation is done by this method as to whether this
    1196        action actually makes sense.
    1197
    1198        @param device: Filesystem device name for this writer, i.e. C{/dev/cdrw}.
    1199
    1200        @return: List suitable for passing to L{util.executeCommand} as C{args}.
    1201        """
    1202        args = []
    1203        args.append("-t")
    1204        args.append(device)
    1205        return args
    1206
    1207     @staticmethod
    1208 -   def _buildPropertiesArgs(hardwareId):
    1209        """
    1210        Builds a list of arguments to be passed to a C{cdrecord} command.
    1211
    1212        The arguments will cause the C{cdrecord} command to ask the device
    1213        for a list of its capacities via the C{-prcap} switch.
    1214
    1215        @param hardwareId: Hardware id for the device (either SCSI id or device path)
    1216
    1217        @return: List suitable for passing to L{util.executeCommand} as C{args}.
    1218        """
    1219        args = []
    1220        args.append("-prcap")
    1221        args.append("dev=%s" % hardwareId)
    1222        return args
    1223
    1224     @staticmethod
    1225 -   def _buildBoundariesArgs(hardwareId):
    1226        """
    1227        Builds a list of arguments to be passed to a C{cdrecord} command.
    1228
    1229        The arguments will cause the C{cdrecord} command to ask the device for
    1230        the current multisession boundaries of the media using the C{-msinfo}
    1231        switch.
    1232
    1233        @param hardwareId: Hardware id for the device (either SCSI id or device path)
    1234
    1235        @return: List suitable for passing to L{util.executeCommand} as C{args}.
    1236        """
    1237        args = []
    1238        args.append("-msinfo")
    1239        args.append("dev=%s" % hardwareId)
    1240        return args
    1241
    1242     @staticmethod
    1243 -   def _buildBlankArgs(hardwareId, driveSpeed=None):
    1244        """
    1245        Builds a list of arguments to be passed to a C{cdrecord} command.
    1246
    1247        The arguments will cause the C{cdrecord} command to blank the media in
    1248        the device identified by C{hardwareId}.  No validation is done by this method
    1249        as to whether the action makes sense (i.e. to whether the media even can
    1250        be blanked).
    1251
    1252        @param hardwareId: Hardware id for the device (either SCSI id or device path)
    1253        @param driveSpeed: Speed at which the drive writes.
    1254
    1255        @return: List suitable for passing to L{util.executeCommand} as C{args}.
    1256        """
    1257        args = []
    1258        args.append("-v")
    1259        args.append("blank=fast")
    1260        if driveSpeed is not None:
    1261           args.append("speed=%d" % driveSpeed)
    1262        args.append("dev=%s" % hardwareId)
    1263        return args
    1264
    1265     @staticmethod
    1266 -   def _buildWriteArgs(hardwareId, imagePath, driveSpeed=None, writeMulti=True):
    1267        """
    1268        Builds a list of arguments to be passed to a C{cdrecord} command.
    1269
    1270        The arguments will cause the C{cdrecord} command to write the indicated
    1271        ISO image (C{imagePath}) to the media in the device identified by
    1272        C{hardwareId}.  The C{writeMulti} argument controls whether to write a
    1273        multisession disc.  No validation is done by this method as to whether
    1274        the action makes sense (i.e. to whether the device even can write
    1275        multisession discs, for instance).
    1276
    1277        @param hardwareId: Hardware id for the device (either SCSI id or device path)
    1278        @param imagePath: Path to an ISO image on disk.
    1279        @param driveSpeed: Speed at which the drive writes.
    1280        @param writeMulti: Indicates whether to write a multisession disc.
    1281
    1282        @return: List suitable for passing to L{util.executeCommand} as C{args}.
    1283        """
    1284        args = []
    1285        args.append("-v")
    1286        if driveSpeed is not None:
    1287           args.append("speed=%d" % driveSpeed)
    1288        args.append("dev=%s" % hardwareId)
    1289        if writeMulti:
    1290           args.append("-multi")
    1291        args.append("-data")
    1292        args.append(imagePath)
    1293        return args
    1294