CedarBackup2.config

Package CedarBackup2 :: Module config |
[hide private]
[frames] | no frames]

# Source Code for Module CedarBackup2.config

       1  # -*- coding: iso-8859-1 -*-
       2  # vim: set ft=python ts=3 sw=3 expandtab:
       3  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
       4  #
       5  #              C E D A R
       6  #          S O L U T I O N S       "Software done right."
       7  #           S O F T W A R E
       8  #
       9  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
      10  #
      11  # Copyright (c) 2004-2008,2010 Kenneth J. Pronovici.
      12  # All rights reserved.
      13  #
      14  # This program is free software; you can redistribute it and/or
      15  # modify it under the terms of the GNU General Public License,
      16  # Version 2, as published by the Free Software Foundation.
      17  #
      18  # This program is distributed in the hope that it will be useful,
      19  # but WITHOUT ANY WARRANTY; without even the implied warranty of
      20  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
      21  #
      22  # Copies of the GNU General Public License are available from
      23  # the Free Software Foundation website, http://www.gnu.org/.
      24  #
      25  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
      26  #
      27  # Author   : Kenneth J. Pronovici <pronovic@ieee.org>
      28  # Language : Python 2 (>= 2.7)
      29  # Project  : Cedar Backup, release 2
      30  # Purpose  : Provides configuration-related objects.
      31  #
      32  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
      33
      34  ########################################################################
      35  # Module documentation
      36  ########################################################################
      37
      38  """
      39  Provides configuration-related objects.
      40
      41  Summary
      42  =======
      43
      44     Cedar Backup stores all of its configuration in an XML document typically
      45     called C{cback.conf}.  The standard location for this document is in
      46     C{/etc}, but users can specify a different location if they want to.
      47
      48     The C{Config} class is a Python object representation of a Cedar Backup XML
      49     configuration file.  The representation is two-way: XML data can be used to
      50     create a C{Config} object, and then changes to the object can be propogated
      51     back to disk.  A C{Config} object can even be used to create a configuration
      52     file from scratch programmatically.
      53
      54     The C{Config} class is intended to be the only Python-language interface to
      55     Cedar Backup configuration on disk.  Cedar Backup will use the class as its
      56     internal representation of configuration, and applications external to Cedar
      57     Backup itself (such as a hypothetical third-party configuration tool written
      58     in Python or a third party extension module) should also use the class when
      59     they need to read and write configuration files.
      60
      61  Backwards Compatibility
      62  =======================
      63
      64     The configuration file format has changed between Cedar Backup 1.x and Cedar
      65     Backup 2.x.  Any Cedar Backup 1.x configuration file is also a valid Cedar
      66     Backup 2.x configuration file.  However, it doesn't work to go the other
      67     direction, as the 2.x configuration files contains additional configuration
      68     is not accepted by older versions of the software.
      69
      70  XML Configuration Structure
      71  ===========================
      72
      73     A C{Config} object can either be created "empty", or can be created based on
      74     XML input (either in the form of a string or read in from a file on disk).
      75     Generally speaking, the XML input I{must} result in a C{Config} object which
      76     passes the validations laid out below in the I{Validation} section.
      77
      78     An XML configuration file is composed of seven sections:
      79
      80        - I{reference}: specifies reference information about the file (author, revision, etc)
      81        - I{extensions}: specifies mappings to Cedar Backup extensions (external code)
      82        - I{options}: specifies global configuration options
      83        - I{peers}: specifies the set of peers in a master's backup pool
      84        - I{collect}: specifies configuration related to the collect action
      85        - I{stage}: specifies configuration related to the stage action
      86        - I{store}: specifies configuration related to the store action
      87        - I{purge}: specifies configuration related to the purge action
      88
      89     Each section is represented by an class in this module, and then the overall
      90     C{Config} class is a composition of the various other classes.
      91
      92     Any configuration section that is missing in the XML document (or has not
      93     been filled into an "empty" document) will just be set to C{None} in the
      94     object representation.  The same goes for individual fields within each
      95     configuration section.  Keep in mind that the document might not be
      96     completely valid if some sections or fields aren't filled in - but that
      97     won't matter until validation takes place (see the I{Validation} section
      98     below).
      99
     100  Unicode vs. String Data
     101  =======================
     102
     103     By default, all string data that comes out of XML documents in Python is
     104     unicode data (i.e. C{u"whatever"}).  This is fine for many things, but when
     105     it comes to filesystem paths, it can cause us some problems.  We really want
     106     strings to be encoded in the filesystem encoding rather than being unicode.
     107     So, most elements in configuration which represent filesystem paths are
     108     coverted to plain strings using L{util.encodePath}.  The main exception is
     109     the various C{absoluteExcludePath} and C{relativeExcludePath} lists.  These
     110     are I{not} converted, because they are generally only used for filtering,
     111     not for filesystem operations.
     112
     113  Validation
     114  ==========
     115
     116     There are two main levels of validation in the C{Config} class and its
     117     children.  The first is field-level validation.  Field-level validation
     118     comes into play when a given field in an object is assigned to or updated.
     119     We use Python's C{property} functionality to enforce specific validations on
     120     field values, and in some places we even use customized list classes to
     121     enforce validations on list members.  You should expect to catch a
     122     C{ValueError} exception when making assignments to configuration class
     123     fields.
     124
     125     The second level of validation is post-completion validation.  Certain
     126     validations don't make sense until a document is fully "complete".  We don't
     127     want these validations to apply all of the time, because it would make
     128     building up a document from scratch a real pain.  For instance, we might
     129     have to do things in the right order to keep from throwing exceptions, etc.
     130
     131     All of these post-completion validations are encapsulated in the
     132     L{Config.validate} method.  This method can be called at any time by a
     133     client, and will always be called immediately after creating a C{Config}
     134     object from XML data and before exporting a C{Config} object to XML.  This
     135     way, we get decent ease-of-use but we also don't accept or emit invalid
     136     configuration files.
     137
     138     The L{Config.validate} implementation actually takes two passes to
     139     completely validate a configuration document.  The first pass at validation
     140     is to ensure that the proper sections are filled into the document.  There
     141     are default requirements, but the caller has the opportunity to override
     142     these defaults.
     143
     144     The second pass at validation ensures that any filled-in section contains
     145     valid data.  Any section which is not set to C{None} is validated according
     146     to the rules for that section (see below).
     147
     148     I{Reference Validations}
     149
     150     No validations.
     151
     152     I{Extensions Validations}
     153
     154     The list of actions may be either C{None} or an empty list C{[]} if desired.
     155     Each extended action must include a name, a module and a function.  Then, an
     156     extended action must include either an index or dependency information.
     157     Which one is required depends on which order mode is configured.
     158
     159     I{Options Validations}
     160
     161     All fields must be filled in except the rsh command.  The rcp and rsh
     162     commands are used as default values for all remote peers.  Remote peers can
     163     also rely on the backup user as the default remote user name if they choose.
     164
     165     I{Peers Validations}
     166
     167     Local peers must be completely filled in, including both name and collect
     168     directory.  Remote peers must also fill in the name and collect directory,
     169     but can leave the remote user and rcp command unset.  In this case, the
     170     remote user is assumed to match the backup user from the options section and
     171     rcp command is taken directly from the options section.
     172
     173     I{Collect Validations}
     174
     175     The target directory must be filled in.  The collect mode, archive mode and
     176     ignore file are all optional.  The list of absolute paths to exclude and
     177     patterns to exclude may be either C{None} or an empty list C{[]} if desired.
     178
     179     Each collect directory entry must contain an absolute path to collect, and
     180     then must either be able to take collect mode, archive mode and ignore file
     181     configuration from the parent C{CollectConfig} object, or must set each
     182     value on its own.  The list of absolute paths to exclude, relative paths to
     183     exclude and patterns to exclude may be either C{None} or an empty list C{[]}
     184     if desired.  Any list of absolute paths to exclude or patterns to exclude
     185     will be combined with the same list in the C{CollectConfig} object to make
     186     the complete list for a given directory.
     187
     188     I{Stage Validations}
     189
     190     The target directory must be filled in.  There must be at least one peer
     191     (remote or local) between the two lists of peers.  A list with no entries
     192     can be either C{None} or an empty list C{[]} if desired.
     193
     194     If a set of peers is provided, this configuration completely overrides
     195     configuration in the peers configuration section, and the same validations
     196     apply.
     197
     198     I{Store Validations}
     199
     200     The device type and drive speed are optional, and all other values are
     201     required (missing booleans will be set to defaults, which is OK).
     202
     203     The image writer functionality in the C{writer} module is supposed to be
     204     able to handle a device speed of C{None}.  Any caller which needs a "real"
     205     (non-C{None}) value for the device type can use C{DEFAULT_DEVICE_TYPE},
     206     which is guaranteed to be sensible.
     207
     208     I{Purge Validations}
     209
     210     The list of purge directories may be either C{None} or an empty list C{[]}
     211     if desired.  All purge directories must contain a path and a retain days
     212     value.
     213
     214  @sort: ActionDependencies, ActionHook, PreActionHook, PostActionHook,
     215         ExtendedAction, CommandOverride, CollectFile, CollectDir, PurgeDir, LocalPeer,
     216         RemotePeer, ReferenceConfig, ExtensionsConfig, OptionsConfig, PeersConfig,
     217         CollectConfig, StageConfig, StoreConfig, PurgeConfig, Config,
     218         DEFAULT_DEVICE_TYPE, DEFAULT_MEDIA_TYPE,
     219         VALID_DEVICE_TYPES, VALID_MEDIA_TYPES,
     220         VALID_COLLECT_MODES, VALID_ARCHIVE_MODES,
     221         VALID_ORDER_MODES
     222
     223  @var DEFAULT_DEVICE_TYPE: The default device type.
     224  @var DEFAULT_MEDIA_TYPE: The default media type.
     225  @var VALID_DEVICE_TYPES: List of valid device types.
     226  @var VALID_MEDIA_TYPES: List of valid media types.
     227  @var VALID_COLLECT_MODES: List of valid collect modes.
     228  @var VALID_COMPRESS_MODES: List of valid compress modes.
     229  @var VALID_ARCHIVE_MODES: List of valid archive modes.
     230  @var VALID_ORDER_MODES: List of valid extension order modes.
     231
     232  @author: Kenneth J. Pronovici <pronovic@ieee.org>
     233  """
     234
     235  ########################################################################
     236  # Imported modules
     237  ########################################################################
     238
     239  # System modules
     240  import os
     241  import re
     242  import logging
     243
     244  # Cedar Backup modules
     245  from CedarBackup2.writers.util import validateScsiId, validateDriveSpeed
     246  from CedarBackup2.util import UnorderedList, AbsolutePathList, ObjectTypeList, parseCommaSeparatedString
     247  from CedarBackup2.util import RegexMatchList, RegexList, encodePath, checkUnique
     248  from CedarBackup2.util import convertSize, displayBytes, UNIT_BYTES, UNIT_KBYTES, UNIT_MBYTES, UNIT_GBYTES
     249  from CedarBackup2.xmlutil import isElement, readChildren, readFirstChild
     250  from CedarBackup2.xmlutil import readStringList, readString, readInteger, readBoolean
     251  from CedarBackup2.xmlutil import addContainerNode, addStringNode, addIntegerNode, addBooleanNode
     252  from CedarBackup2.xmlutil import createInputDom, createOutputDom, serializeDom
     253
     254
     255  ########################################################################
     256  # Module-wide constants and variables
     257  ########################################################################
     258
     259  logger = logging.getLogger("CedarBackup2.log.config")
     260
     261  DEFAULT_DEVICE_TYPE   = "cdwriter"
     262  DEFAULT_MEDIA_TYPE    = "cdrw-74"
     263
     264  VALID_DEVICE_TYPES    = [ "cdwriter", "dvdwriter", ]
     265  VALID_CD_MEDIA_TYPES  = [ "cdr-74", "cdrw-74", "cdr-80", "cdrw-80", ]
     266  VALID_DVD_MEDIA_TYPES = [ "dvd+r", "dvd+rw", ]
     267  VALID_MEDIA_TYPES     = VALID_CD_MEDIA_TYPES + VALID_DVD_MEDIA_TYPES
     268  VALID_COLLECT_MODES   = [ "daily", "weekly", "incr", ]
     269  VALID_ARCHIVE_MODES   = [ "tar", "targz", "tarbz2", ]
     270  VALID_COMPRESS_MODES  = [ "none", "gzip", "bzip2", ]
     271  VALID_ORDER_MODES     = [ "index", "dependency", ]
     272  VALID_BLANK_MODES     = [ "daily", "weekly", ]
     273  VALID_BYTE_UNITS      = [ UNIT_BYTES, UNIT_KBYTES, UNIT_MBYTES, UNIT_GBYTES, ]
     274  VALID_FAILURE_MODES   = [ "none", "all", "daily", "weekly", ]
     275
     276  REWRITABLE_MEDIA_TYPES = [ "cdrw-74", "cdrw-80", "dvd+rw", ]
     277
     278  ACTION_NAME_REGEX     = r"^[a-z0-9]*$"
     279
     280
     281  ########################################################################
     282  # ByteQuantity class definition
     283  ########################################################################
     284
     285 -class ByteQuantity(object):
     286
     287     """
     288     Class representing a byte quantity.
     289
     290     A byte quantity has both a quantity and a byte-related unit.  Units are
     291     maintained using the constants from util.py.  If no units are provided,
     292     C{UNIT_BYTES} is assumed.
     293
     294     The quantity is maintained internally as a string so that issues of
     295     precision can be avoided.  It really isn't possible to store a floating
     296     point number here while being able to losslessly translate back and forth
     297     between XML and object representations.  (Perhaps the Python 2.4 Decimal
     298     class would have been an option, but I originally wanted to stay compatible
     299     with Python 2.3.)
     300
     301     Even though the quantity is maintained as a string, the string must be in a
     302     valid floating point positive number.  Technically, any floating point
     303     string format supported by Python is allowble.  However, it does not make
     304     sense to have a negative quantity of bytes in this context.
     305
     306     @sort: __init__, __repr__, __str__, __cmp__, quantity, units, bytes
     307     """
     308
     309 -   def __init__(self, quantity=None, units=None):
     310        """
     311        Constructor for the C{ByteQuantity} class.
     312
     313        @param quantity: Quantity of bytes, something interpretable as a float
     314        @param units: Unit of bytes, one of VALID_BYTE_UNITS
     315
     316        @raise ValueError: If one of the values is invalid.
     317        """
     318        self._quantity = None
     319        self._units = None
     320        self.quantity = quantity
     321        self.units = units
     322
     323 -   def __repr__(self):
     324        """
     325        Official string representation for class instance.
     326        """
     327        return "ByteQuantity(%s, %s)" % (self.quantity, self.units)
     328
     329 -   def __str__(self):
     330        """
     331        Informal string representation for class instance.
     332        """
     333        return "%s" % displayBytes(self.bytes)
     334
     335 -   def __cmp__(self, other):
     336        """
     337        Definition of equals operator for this class.
     338        Lists within this class are "unordered" for equality comparisons.
     339        @param other: Other object to compare to.
     340        @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other.
     341        """
     342        if other is None:
     343           return 1
     344        elif isinstance(other, ByteQuantity):
     345           if self.bytes != other.bytes:
     346              if self.bytes < other.bytes:
     347                 return -1
     348              else:
     349                 return 1
     350           return 0
     351        else:
     352           return self.__cmp__(ByteQuantity(other, UNIT_BYTES)) # will fail if other can't be coverted to float
     353
     354 -   def _setQuantity(self, value):
     355        """
     356        Property target used to set the quantity
     357        The value must be interpretable as a float if it is not None
     358        @raise ValueError: If the value is an empty string.
     359        @raise ValueError: If the value is not a valid floating point number
     360        @raise ValueError: If the value is less than zero
     361        """
     362        if value is None:
     363           self._quantity = None
     364        else:
     365           try:
     366              floatValue = float(value)  # allow integer, float, string, etc.
     367           except:
     368              raise ValueError("Quantity must be interpretable as a float")
     369           if floatValue < 0.0:
     370              raise ValueError("Quantity cannot be negative.")
     371           self._quantity = str(value) # keep around string
     372
     373 -   def _getQuantity(self):
     374        """
     375        Property target used to get the quantity.
     376        """
     377        return self._quantity
     378
     379 -   def _setUnits(self, value):
     380        """
     381        Property target used to set the units value.
     382        If not C{None}, the units value must be one of the values in L{VALID_BYTE_UNITS}.
     383        @raise ValueError: If the value is not valid.
     384        """
     385        if value is None:
     386           self._units = UNIT_BYTES
     387        else:
     388           if value not in VALID_BYTE_UNITS:
     389              raise ValueError("Units value must be one of %s." % VALID_BYTE_UNITS)
     390           self._units = value
     391
     392 -   def _getUnits(self):
     393        """
     394        Property target used to get the units value.
     395        """
     396        return self._units
     397
     398 -   def _getBytes(self):
     399        """
     400        Property target used to return the byte quantity as a floating point number.
     401        If there is no quantity set, then a value of 0.0 is returned.
     402        """
     403        if self.quantity is not None and self.units is not None:
     404           return convertSize(self.quantity, self.units, UNIT_BYTES)
     405        return 0.0
     406
     407     quantity = property(_getQuantity, _setQuantity, None, doc="Byte quantity, as a string")
     408     units = property(_getUnits, _setUnits, None, doc="Units for byte quantity, for instance UNIT_BYTES")
     409     bytes = property(_getBytes, None, None, doc="Byte quantity, as a floating point number.")
     410
     411
     412  ########################################################################
     413  # ActionDependencies class definition
     414  ########################################################################
     415
     416 -class ActionDependencies(object):
     417
     418     """
     419     Class representing dependencies associated with an extended action.
     420
     421     Execution ordering for extended actions is done in one of two ways: either by using
     422     index values (lower index gets run first) or by having the extended action specify
     423     dependencies in terms of other named actions.  This class encapsulates the dependency
     424     information for an extended action.
     425
     426     The following restrictions exist on data in this class:
     427
     428        - Any action name must be a non-empty string matching C{ACTION_NAME_REGEX}
     429
     430     @sort: __init__, __repr__, __str__, __cmp__, beforeList, afterList
     431     """
     432
     433 -   def __init__(self, beforeList=None, afterList=None):
     434        """
     435        Constructor for the C{ActionDependencies} class.
     436
     437        @param beforeList: List of named actions that this action must be run before
     438        @param afterList: List of named actions that this action must be run after
     439
     440        @raise ValueError: If one of the values is invalid.
     441        """
     442        self._beforeList = None
     443        self._afterList = None
     444        self.beforeList = beforeList
     445        self.afterList = afterList
     446
     447 -   def __repr__(self):
     448        """
     449        Official string representation for class instance.
     450        """
     451        return "ActionDependencies(%s, %s)" % (self.beforeList, self.afterList)
     452
     453 -   def __str__(self):
     454        """
     455        Informal string representation for class instance.
     456        """
     457        return self.__repr__()
     458
     459 -   def __cmp__(self, other):
     460        """
     461        Definition of equals operator for this class.
     462        @param other: Other object to compare to.
     463        @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other.
     464        """
     465        if other is None:
     466           return 1
     467        if self.beforeList != other.beforeList:
     468           if self.beforeList < other.beforeList:
     469              return -1
     470           else:
     471              return 1
     472        if self.afterList != other.afterList:
     473           if self.afterList < other.afterList:
     474              return -1
     475           else:
     476              return 1
     477        return 0
     478
     479 -   def _setBeforeList(self, value):
     480        """
     481        Property target used to set the "run before" list.
     482        Either the value must be C{None} or each element must be a string matching ACTION_NAME_REGEX.
     483        @raise ValueError: If the value does not match the regular expression.
     484        """
     485        if value is None:
     486           self._beforeList = None
     487        else:
     488           try:
     489              saved = self._beforeList
     490              self._beforeList = RegexMatchList(ACTION_NAME_REGEX, emptyAllowed=False, prefix="Action name")
     491              self._beforeList.extend(value)
     492           except Exception, e:
     493              self._beforeList = saved
     494              raise e
     495
     496 -   def _getBeforeList(self):
     497        """
     498        Property target used to get the "run before" list.
     499        """
     500        return self._beforeList
     501
     502 -   def _setAfterList(self, value):
     503        """
     504        Property target used to set the "run after" list.
     505        Either the value must be C{None} or each element must be a string matching ACTION_NAME_REGEX.
     506        @raise ValueError: If the value does not match the regular expression.
     507        """
     508        if value is None:
     509           self._afterList = None
     510        else:
     511           try:
     512              saved = self._afterList
     513              self._afterList = RegexMatchList(ACTION_NAME_REGEX, emptyAllowed=False, prefix="Action name")
     514              self._afterList.extend(value)
     515           except Exception, e:
     516              self._afterList = saved
     517              raise e
     518
     519 -   def _getAfterList(self):
     520        """
     521        Property target used to get the "run after" list.
     522        """
     523        return self._afterList
     524
     525     beforeList = property(_getBeforeList, _setBeforeList, None, "List of named actions that this action must be run before.")
     526     afterList = property(_getAfterList, _setAfterList, None, "List of named actions that this action must be run after.")
     527
     528
     529  ########################################################################
     530  # ActionHook class definition
     531  ########################################################################
     532
     533 -class ActionHook(object):
     534
     535     """
     536     Class representing a hook associated with an action.
     537
     538     A hook associated with an action is a shell command to be executed either
     539     before or after a named action is executed.
     540
     541     The following restrictions exist on data in this class:
     542
     543        - The action name must be a non-empty string matching C{ACTION_NAME_REGEX}
     544        - The shell command must be a non-empty string.
     545
     546     The internal C{before} and C{after} instance variables are always set to
     547     False in this parent class.
     548
     549     @sort: __init__, __repr__, __str__, __cmp__, action, command, before, after
     550     """
     551
     552 -   def __init__(self, action=None, command=None):
     553        """
     554        Constructor for the C{ActionHook} class.
     555
     556        @param action: Action this hook is associated with
     557        @param command: Shell command to execute
     558
     559        @raise ValueError: If one of the values is invalid.
     560        """
     561        self._action = None
     562        self._command = None
     563        self._before = False
     564        self._after = False
     565        self.action = action
     566        self.command = command
     567
     568 -   def __repr__(self):
     569        """
     570        Official string representation for class instance.
     571        """
     572        return "ActionHook(%s, %s, %s, %s)" % (self.action, self.command, self.before, self.after)
     573
     574 -   def __str__(self):
     575        """
     576        Informal string representation for class instance.
     577        """
     578        return self.__repr__()
     579
     580 -   def __cmp__(self, other):
     581        """
     582        Definition of equals operator for this class.
     583        @param other: Other object to compare to.
     584        @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other.
     585        """
     586        if other is None:
     587           return 1
     588        if self.action != other.action:
     589           if self.action < other.action:
     590              return -1
     591           else:
     592              return 1
     593        if self.command != other.command:
     594           if self.command < other.command:
     595              return -1
     596           else:
     597              return 1
     598        if self.before != other.before:
     599           if self.before < other.before:
     600              return -1
     601           else:
     602              return 1
     603        if self.after != other.after:
     604           if self.after < other.after:
     605              return -1
     606           else:
     607              return 1
     608        return 0
     609
     610 -   def _setAction(self, value):
     611        """
     612        Property target used to set the action name.
     613        The value must be a non-empty string if it is not C{None}.
     614        It must also consist only of lower-case letters and digits.
     615        @raise ValueError: If the value is an empty string.
     616        """
     617        pattern = re.compile(ACTION_NAME_REGEX)
     618        if value is not None:
     619           if len(value) < 1:
     620              raise ValueError("The action name must be a non-empty string.")
     621           if not pattern.search(value):
     622              raise ValueError("The action name must consist of only lower-case letters and digits.")
     623        self._action = value
     624
     625 -   def _getAction(self):
     626        """
     627        Property target used to get the action name.
     628        """
     629        return self._action
     630
     631 -   def _setCommand(self, value):
     632        """
     633        Property target used to set the command.
     634        The value must be a non-empty string if it is not C{None}.
     635        @raise ValueError: If the value is an empty string.
     636        """
     637        if value is not None:
     638           if len(value) < 1:
     639              raise ValueError("The command must be a non-empty string.")
     640        self._command = value
     641
     642 -   def _getCommand(self):
     643        """
     644        Property target used to get the command.
     645        """
     646        return self._command
     647
     648 -   def _getBefore(self):
     649        """
     650        Property target used to get the before flag.
     651        """
     652        return self._before
     653
     654 -   def _getAfter(self):
     655        """
     656        Property target used to get the after flag.
     657        """
     658        return self._after
     659
     660     action = property(_getAction, _setAction, None, "Action this hook is associated with.")
     661     command = property(_getCommand, _setCommand, None, "Shell command to execute.")
     662     before = property(_getBefore, None, None, "Indicates whether command should be executed before action.")
     663     after = property(_getAfter, None, None, "Indicates whether command should be executed after action.")
     664
     665 -class PreActionHook(ActionHook):
     666
     667     """
     668     Class representing a pre-action hook associated with an action.
     669
     670     A hook associated with an action is a shell command to be executed either
     671     before or after a named action is executed.  In this case, a pre-action hook
     672     is executed before the named action.
     673
     674     The following restrictions exist on data in this class:
     675
     676        - The action name must be a non-empty string consisting of lower-case letters and digits.
     677        - The shell command must be a non-empty string.
     678
     679     The internal C{before} instance variable is always set to True in this
     680     class.
     681
     682     @sort: __init__, __repr__, __str__, __cmp__, action, command, before, after
     683     """
     684
     685 -   def __init__(self, action=None, command=None):
     686        """
     687        Constructor for the C{PreActionHook} class.
     688
     689        @param action: Action this hook is associated with
     690        @param command: Shell command to execute
     691
     692        @raise ValueError: If one of the values is invalid.
     693        """
     694        ActionHook.__init__(self, action, command)
     695        self._before = True
     696
     697 -   def __repr__(self):
     698        """
     699        Official string representation for class instance.
     700        """
     701        return "PreActionHook(%s, %s, %s, %s)" % (self.action, self.command, self.before, self.after)
     702
     703 -class PostActionHook(ActionHook):
     704
     705     """
     706     Class representing a pre-action hook associated with an action.
     707
     708     A hook associated with an action is a shell command to be executed either
     709     before or after a named action is executed.  In this case, a post-action hook
     710     is executed after the named action.
     711
     712     The following restrictions exist on data in this class:
     713
     714        - The action name must be a non-empty string consisting of lower-case letters and digits.
     715        - The shell command must be a non-empty string.
     716
     717     The internal C{before} instance variable is always set to True in this
     718     class.
     719
     720     @sort: __init__, __repr__, __str__, __cmp__, action, command, before, after
     721     """
     722
     723 -   def __init__(self, action=None, command=None):
     724        """
     725        Constructor for the C{PostActionHook} class.
     726
     727        @param action: Action this hook is associated with
     728        @param command: Shell command to execute
     729
     730        @raise ValueError: If one of the values is invalid.
     731        """
     732        ActionHook.__init__(self, action, command)
     733        self._after = True
     734
     735 -   def __repr__(self):
     736        """
     737        Official string representation for class instance.
     738        """
     739        return "PostActionHook(%s, %s, %s, %s)" % (self.action, self.command, self.before, self.after)
     740
     741
     742  ########################################################################
     743  # BlankBehavior class definition
     744  ########################################################################
     745
     746 -class BlankBehavior(object):
     747
     748     """
     749     Class representing optimized store-action media blanking behavior.
     750
     751     The following restrictions exist on data in this class:
     752
     753        - The blanking mode must be a one of the values in L{VALID_BLANK_MODES}
     754        - The blanking factor must be a positive floating point number
     755
     756     @sort: __init__, __repr__, __str__, __cmp__, blankMode, blankFactor
     757     """
     758
     759 -   def __init__(self, blankMode=None, blankFactor=None):
     760        """
     761        Constructor for the C{BlankBehavior} class.
     762
     763        @param blankMode: Blanking mode
     764        @param blankFactor: Blanking factor
     765
     766        @raise ValueError: If one of the values is invalid.
     767        """
     768        self._blankMode = None
     769        self._blankFactor = None
     770        self.blankMode = blankMode
     771        self.blankFactor = blankFactor
     772
     773 -   def __repr__(self):
     774        """
     775        Official string representation for class instance.
     776        """
     777        return "BlankBehavior(%s, %s)" % (self.blankMode, self.blankFactor)
     778
     779 -   def __str__(self):
     780        """
     781        Informal string representation for class instance.
     782        """
     783        return self.__repr__()
     784
     785 -   def __cmp__(self, other):
     786        """
     787        Definition of equals operator for this class.
     788        @param other: Other object to compare to.
     789        @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other.
     790        """
     791        if other is None:
     792           return 1
     793        if self.blankMode != other.blankMode:
     794           if self.blankMode < other.blankMode:
     795              return -1
     796           else:
     797              return 1
     798        if self.blankFactor != other.blankFactor:
     799           if self.blankFactor < other.blankFactor:
     800              return -1
     801           else:
     802              return 1
     803        return 0
     804
     805 -   def _setBlankMode(self, value):
     806        """
     807        Property target used to set the blanking mode.
     808        The value must be one of L{VALID_BLANK_MODES}.
     809        @raise ValueError: If the value is not valid.
     810        """
     811        if value is not None:
     812           if value not in VALID_BLANK_MODES:
     813              raise ValueError("Blanking mode must be one of %s." % VALID_BLANK_MODES)
     814        self._blankMode = value
     815
     816 -   def _getBlankMode(self):
     817        """
     818        Property target used to get the blanking mode.
     819        """
     820        return self._blankMode
     821
     822 -   def _setBlankFactor(self, value):
     823        """
     824        Property target used to set the blanking factor.
     825        The value must be a non-empty string if it is not C{None}.
     826        @raise ValueError: If the value is an empty string.
     827        @raise ValueError: If the value is not a valid floating point number
     828        @raise ValueError: If the value is less than zero
     829        """
     830        if value is not None:
     831           if len(value) < 1:
     832              raise ValueError("Blanking factor must be a non-empty string.")
     833           floatValue = float(value)
     834           if floatValue < 0.0:
     835              raise ValueError("Blanking factor cannot be negative.")
     836        self._blankFactor = value # keep around string
     837
     838 -   def _getBlankFactor(self):
     839        """
     840        Property target used to get the blanking factor.
     841        """
     842        return self._blankFactor
     843
     844     blankMode = property(_getBlankMode, _setBlankMode, None, "Blanking mode")
     845     blankFactor = property(_getBlankFactor, _setBlankFactor, None, "Blanking factor")
     846
     847
     848  ########################################################################
     849  # ExtendedAction class definition
     850  ########################################################################
     851
     852 -class ExtendedAction(object):
     853
     854     """
     855     Class representing an extended action.
     856
     857     Essentially, an extended action needs to allow the following to happen::
     858
     859        exec("from %s import %s" % (module, function))
     860        exec("%s(action, configPath")" % function)
     861
     862     The following restrictions exist on data in this class:
     863
     864        - The action name must be a non-empty string consisting of lower-case letters and digits.
     865        - The module must be a non-empty string and a valid Python identifier.
     866        - The function must be an on-empty string and a valid Python identifier.
     867        - If set, the index must be a positive integer.
     868        - If set, the dependencies attribute must be an C{ActionDependencies} object.
     869
     870     @sort: __init__, __repr__, __str__, __cmp__, name, module, function, index, dependencies
     871     """
     872
     873 -   def __init__(self, name=None, module=None, function=None, index=None, dependencies=None):
     874        """
     875        Constructor for the C{ExtendedAction} class.
     876
     877        @param name: Name of the extended action
     878        @param module: Name of the module containing the extended action function
     879        @param function: Name of the extended action function
     880        @param index: Index of action, used for execution ordering
     881        @param dependencies: Dependencies for action, used for execution ordering
     882
     883        @raise ValueError: If one of the values is invalid.
     884        """
     885        self._name = None
     886        self._module = None
     887        self._function = None
     888        self._index = None
     889        self._dependencies = None
     890        self.name = name
     891        self.module = module
     892        self.function = function
     893        self.index = index
     894        self.dependencies = dependencies
     895
     896 -   def __repr__(self):
     897        """
     898        Official string representation for class instance.
     899        """
     900        return "ExtendedAction(%s, %s, %s, %s, %s)" % (self.name, self.module, self.function, self.index, self.dependencies)
     901
     902 -   def __str__(self):
     903        """
     904        Informal string representation for class instance.
     905        """
     906        return self.__repr__()
     907
     908 -   def __cmp__(self, other):
     909        """
     910        Definition of equals operator for this class.
     911        @param other: Other object to compare to.
     912        @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other.
     913        """
     914        if other is None:
     915           return 1
     916        if self.name != other.name:
     917           if self.name < other.name:
     918              return -1
     919           else:
     920              return 1
     921        if self.module != other.module:
     922           if self.module < other.module:
     923              return -1
     924           else:
     925              return 1
     926        if self.function != other.function:
     927           if self.function < other.function:
     928              return -1
     929           else:
     930              return 1
     931        if self.index != other.index:
     932           if self.index < other.index:
     933              return -1
     934           else:
     935              return 1
     936        if self.dependencies != other.dependencies:
     937           if self.dependencies < other.dependencies:
     938              return -1
     939           else:
     940              return 1
     941        return 0
     942
     943 -   def _setName(self, value):
     944        """
     945        Property target used to set the action name.
     946        The value must be a non-empty string if it is not C{None}.
     947        It must also consist only of lower-case letters and digits.
     948        @raise ValueError: If the value is an empty string.
     949        """
     950        pattern = re.compile(ACTION_NAME_REGEX)
     951        if value is not None:
     952           if len(value) < 1:
     953              raise ValueError("The action name must be a non-empty string.")
     954           if not pattern.search(value):
     955              raise ValueError("The action name must consist of only lower-case letters and digits.")
     956        self._name = value
     957
     958 -   def _getName(self):
     959        """
     960        Property target used to get the action name.
     961        """
     962        return self._name
     963
     964 -   def _setModule(self, value):
     965        """
     966        Property target used to set the module name.
     967        The value must be a non-empty string if it is not C{None}.
     968        It must also be a valid Python identifier.
     969        @raise ValueError: If the value is an empty string.
     970        """
     971        pattern = re.compile(r"^([A-Za-z_][A-Za-z0-9_]*)(\.[A-Za-z_][A-Za-z0-9_]*)*$")
     972        if value is not None:
     973           if len(value) < 1:
     974              raise ValueError("The module name must be a non-empty string.")
     975           if not pattern.search(value):
     976              raise ValueError("The module name must be a valid Python identifier.")
     977        self._module = value
     978
     979 -   def _getModule(self):
     980        """
     981        Property target used to get the module name.
     982        """
     983        return self._module
     984
     985 -   def _setFunction(self, value):
     986        """
     987        Property target used to set the function name.
     988        The value must be a non-empty string if it is not C{None}.
     989        It must also be a valid Python identifier.
     990        @raise ValueError: If the value is an empty string.
     991        """
     992        pattern = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
     993        if value is not None:
     994           if len(value) < 1:
     995              raise ValueError("The function name must be a non-empty string.")
     996           if not pattern.search(value):
     997              raise ValueError("The function name must be a valid Python identifier.")
     998        self._function = value
     999
    1000 -   def _getFunction(self):
    1001        """
    1002        Property target used to get the function name.
    1003        """
    1004        return self._function
    1005
    1006 -   def _setIndex(self, value):
    1007        """
    1008        Property target used to set the action index.
    1009        The value must be an integer >= 0.
    1010        @raise ValueError: If the value is not valid.
    1011        """
    1012        if value is None:
    1013           self._index = None
    1014        else:
    1015           try:
    1016              value = int(value)
    1017           except TypeError:
    1018              raise ValueError("Action index value must be an integer >= 0.")
    1019           if value < 0:
    1020              raise ValueError("Action index value must be an integer >= 0.")
    1021           self._index = value
    1022
    1023 -   def _getIndex(self):
    1024        """
    1025        Property target used to get the action index.
    1026        """
    1027        return self._index
    1028
    1029 -   def _setDependencies(self, value):
    1030        """
    1031        Property target used to set the action dependencies information.
    1032        If not C{None}, the value must be a C{ActionDependecies} object.
    1033        @raise ValueError: If the value is not a C{ActionDependencies} object.
    1034        """
    1035        if value is None:
    1036           self._dependencies = None
    1037        else:
    1038           if not isinstance(value, ActionDependencies):
    1039              raise ValueError("Value must be a C{ActionDependencies} object.")
    1040           self._dependencies = value
    1041
    1042 -   def _getDependencies(self):
    1043        """
    1044        Property target used to get action dependencies information.
    1045        """
    1046        return self._dependencies
    1047
    1048     name = property(_getName, _setName, None, "Name of the extended action.")
    1049     module = property(_getModule, _setModule, None, "Name of the module containing the extended action function.")
    1050     function = property(_getFunction, _setFunction, None, "Name of the extended action function.")
    1051     index = property(_getIndex, _setIndex, None, "Index of action, used for execution ordering.")
    1052     dependencies = property(_getDependencies, _setDependencies, None, "Dependencies for action, used for execution ordering.")
    1053
    1054
    1055  ########################################################################
    1056  # CommandOverride class definition
    1057  ########################################################################
    1058
    1059 -class CommandOverride(object):
    1060
    1061     """
    1062     Class representing a piece of Cedar Backup command override configuration.
    1063
    1064     The following restrictions exist on data in this class:
    1065
    1066        - The absolute path must be absolute
    1067
    1068     @note: Lists within this class are "unordered" for equality comparisons.
    1069
    1070     @sort: __init__, __repr__, __str__, __cmp__, command, absolutePath
    1071     """
    1072
    1073 -   def __init__(self, command=None, absolutePath=None):
    1074        """
    1075        Constructor for the C{CommandOverride} class.
    1076
    1077        @param command: Name of command to be overridden.
    1078        @param absolutePath: Absolute path of the overrridden command.
    1079
    1080        @raise ValueError: If one of the values is invalid.
    1081        """
    1082        self._command = None
    1083        self._absolutePath = None
    1084        self.command = command
    1085        self.absolutePath = absolutePath
    1086
    1087 -   def __repr__(self):
    1088        """
    1089        Official string representation for class instance.
    1090        """
    1091        return "CommandOverride(%s, %s)" % (self.command, self.absolutePath)
    1092
    1093 -   def __str__(self):
    1094        """
    1095        Informal string representation for class instance.
    1096        """
    1097        return self.__repr__()
    1098
    1099 -   def __cmp__(self, other):
    1100        """
    1101        Definition of equals operator for this class.
    1102        @param other: Other object to compare to.
    1103        @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other.
    1104        """
    1105        if other is None:
    1106           return 1
    1107        if self.command != other.command:
    1108           if self.command < other.command:
    1109              return -1
    1110           else:
    1111              return 1
    1112        if self.absolutePath != other.absolutePath:
    1113           if self.absolutePath < other.absolutePath:
    1114              return -1
    1115           else:
    1116              return 1
    1117        return 0
    1118
    1119 -   def _setCommand(self, value):
    1120        """
    1121        Property target used to set the command.
    1122        The value must be a non-empty string if it is not C{None}.
    1123        @raise ValueError: If the value is an empty string.
    1124        """
    1125        if value is not None:
    1126           if len(value) < 1:
    1127              raise ValueError("The command must be a non-empty string.")
    1128        self._command = value
    1129
    1130 -   def _getCommand(self):
    1131        """
    1132        Property target used to get the command.
    1133        """
    1134        return self._command
    1135
    1136 -   def _setAbsolutePath(self, value):
    1137        """
    1138        Property target used to set the absolute path.
    1139        The value must be an absolute path if it is not C{None}.
    1140        It does not have to exist on disk at the time of assignment.
    1141        @raise ValueError: If the value is not an absolute path.
    1142        @raise ValueError: If the value cannot be encoded properly.
    1143        """
    1144        if value is not None:
    1145           if not os.path.isabs(value):
    1146              raise ValueError("Not an absolute path: [%s]" % value)
    1147        self._absolutePath = encodePath(value)
    1148
    1149 -   def _getAbsolutePath(self):
    1150        """
    1151        Property target used to get the absolute path.
    1152        """
    1153        return self._absolutePath
    1154
    1155     command = property(_getCommand, _setCommand, None, doc="Name of command to be overridden.")
    1156     absolutePath = property(_getAbsolutePath, _setAbsolutePath, None, doc="Absolute path of the overrridden command.")
    1157
    1158
    1159  ########################################################################
    1160  # CollectFile class definition
    1161  ########################################################################
    1162
    1163 -class CollectFile(object):
    1164
    1165     """
    1166     Class representing a Cedar Backup collect file.
    1167
    1168     The following restrictions exist on data in this class:
    1169
    1170        - Absolute paths must be absolute
    1171        - The collect mode must be one of the values in L{VALID_COLLECT_MODES}.
    1172        - The archive mode must be one of the values in L{VALID_ARCHIVE_MODES}.
    1173
    1174     @sort: __init__, __repr__, __str__, __cmp__, absolutePath, collectMode, archiveMode
    1175     """
    1176
    1177 -   def __init__(self, absolutePath=None, collectMode=None, archiveMode=None):
    1178        """
    1179        Constructor for the C{CollectFile} class.
    1180
    1181        @param absolutePath: Absolute path of the file to collect.
    1182        @param collectMode: Overridden collect mode for this file.
    1183        @param archiveMode: Overridden archive mode for this file.
    1184
    1185        @raise ValueError: If one of the values is invalid.
    1186        """
    1187        self._absolutePath = None
    1188        self._collectMode = None
    1189        self._archiveMode = None
    1190        self.absolutePath = absolutePath
    1191        self.collectMode = collectMode
    1192        self.archiveMode = archiveMode
    1193
    1194 -   def __repr__(self):
    1195        """
    1196        Official string representation for class instance.
    1197        """
    1198        return "CollectFile(%s, %s, %s)" % (self.absolutePath, self.collectMode, self.archiveMode)
    1199
    1200 -   def __str__(self):
    1201        """
    1202        Informal string representation for class instance.
    1203        """
    1204        return self.__repr__()
    1205
    1206 -   def __cmp__(self, other):
    1207        """
    1208        Definition of equals operator for this class.
    1209        @param other: Other object to compare to.
    1210        @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other.
    1211        """
    1212        if other is None:
    1213           return 1
    1214        if self.absolutePath != other.absolutePath:
    1215           if self.absolutePath < other.absolutePath:
    1216              return -1
    1217           else:
    1218              return 1
    1219        if self.collectMode != other.collectMode:
    1220           if self.collectMode < other.collectMode:
    1221              return -1
    1222           else:
    1223              return 1
    1224        if self.archiveMode != other.archiveMode:
    1225           if self.archiveMode < other.archiveMode:
    1226              return -1
    1227           else:
    1228              return 1
    1229        return 0
    1230
    1231 -   def _setAbsolutePath(self, value):
    1232        """
    1233        Property target used to set the absolute path.
    1234        The value must be an absolute path if it is not C{None}.
    1235        It does not have to exist on disk at the time of assignment.
    1236        @raise ValueError: If the value is not an absolute path.
    1237        @raise ValueError: If the value cannot be encoded properly.
    1238        """
    1239        if value is not None:
    1240           if not os.path.isabs(value):
    1241              raise ValueError("Not an absolute path: [%s]" % value)
    1242        self._absolutePath = encodePath(value)
    1243
    1244 -   def _getAbsolutePath(self):
    1245        """
    1246        Property target used to get the absolute path.
    1247        """
    1248        return self._absolutePath
    1249
    1250 -   def _setCollectMode(self, value):
    1251        """
    1252        Property target used to set the collect mode.
    1253        If not C{None}, the mode must be one of the values in L{VALID_COLLECT_MODES}.
    1254        @raise ValueError: If the value is not valid.
    1255        """
    1256        if value is not None:
    1257           if value not in VALID_COLLECT_MODES:
    1258              raise ValueError("Collect mode must be one of %s." % VALID_COLLECT_MODES)
    1259        self._collectMode = value
    1260
    1261 -   def _getCollectMode(self):
    1262        """
    1263        Property target used to get the collect mode.
    1264        """
    1265        return self._collectMode
    1266
    1267 -   def _setArchiveMode(self, value):
    1268        """
    1269        Property target used to set the archive mode.
    1270        If not C{None}, the mode must be one of the values in L{VALID_ARCHIVE_MODES}.
    1271        @raise ValueError: If the value is not valid.
    1272        """
    1273        if value is not None:
    1274           if value not in VALID_ARCHIVE_MODES:
    1275              raise ValueError("Archive mode must be one of %s." % VALID_ARCHIVE_MODES)
    1276        self._archiveMode = value
    1277
    1278 -   def _getArchiveMode(self):
    1279        """
    1280        Property target used to get the archive mode.
    1281        """
    1282        return self._archiveMode
    1283
    1284     absolutePath = property(_getAbsolutePath, _setAbsolutePath, None, doc="Absolute path of the file to collect.")
    1285     collectMode = property(_getCollectMode, _setCollectMode, None, doc="Overridden collect mode for this file.")
    1286     archiveMode = property(_getArchiveMode, _setArchiveMode, None, doc="Overridden archive mode for this file.")
    1287
    1288
    1289  ########################################################################
    1290  # CollectDir class definition
    1291  ########################################################################
    1292
    1293 -class CollectDir(object):
    1294
    1295     """
    1296     Class representing a Cedar Backup collect directory.
    1297
    1298     The following restrictions exist on data in this class:
    1299
    1300        - Absolute paths must be absolute
    1301        - The collect mode must be one of the values in L{VALID_COLLECT_MODES}.
    1302        - The archive mode must be one of the values in L{VALID_ARCHIVE_MODES}.
    1303        - The ignore file must be a non-empty string.
    1304
    1305     For the C{absoluteExcludePaths} list, validation is accomplished through the
    1306     L{util.AbsolutePathList} list implementation that overrides common list
    1307     methods and transparently does the absolute path validation for us.
    1308
    1309     @note: Lists within this class are "unordered" for equality comparisons.
    1310
    1311     @sort: __init__, __repr__, __str__, __cmp__, absolutePath, collectMode,
    1312            archiveMode, ignoreFile, linkDepth, dereference, absoluteExcludePaths,
    1313            relativeExcludePaths, excludePatterns
    1314     """
    1315
    1316 -   def __init__(self, absolutePath=None, collectMode=None, archiveMode=None, ignoreFile=None,
    1317                  absoluteExcludePaths=None, relativeExcludePaths=None, excludePatterns=None,
    1318                  linkDepth=None, dereference=False, recursionLevel=None):
    1319        """
    1320        Constructor for the C{CollectDir} class.
    1321
    1322        @param absolutePath: Absolute path of the directory to collect.
    1323        @param collectMode: Overridden collect mode for this directory.
    1324        @param archiveMode: Overridden archive mode for this directory.
    1325        @param ignoreFile: Overidden ignore file name for this directory.
    1326        @param linkDepth: Maximum at which soft links should be followed.
    1327        @param dereference: Whether to dereference links that are followed.
    1328        @param absoluteExcludePaths: List of absolute paths to exclude.
    1329        @param relativeExcludePaths: List of relative paths to exclude.
    1330        @param excludePatterns: List of regular expression patterns to exclude.
    1331
    1332        @raise ValueError: If one of the values is invalid.
    1333        """
    1334        self._absolutePath = None
    1335        self._collectMode = None
    1336        self._archiveMode = None
    1337        self._ignoreFile = None
    1338        self._linkDepth = None
    1339        self._dereference = None
    1340        self._recursionLevel = None
    1341        self._absoluteExcludePaths = None
    1342        self._relativeExcludePaths = None
    1343        self._excludePatterns = None
    1344        self.absolutePath = absolutePath
    1345        self.collectMode = collectMode
    1346        self.archiveMode = archiveMode
    1347        self.ignoreFile = ignoreFile
    1348        self.linkDepth = linkDepth
    1349        self.dereference = dereference
    1350        self.recursionLevel = recursionLevel
    1351        self.absoluteExcludePaths = absoluteExcludePaths
    1352        self.relativeExcludePaths = relativeExcludePaths
    1353        self.excludePatterns = excludePatterns
    1354
    1355 -   def __repr__(self):
    1356        """
    1357        Official string representation for class instance.
    1358        """
    1359        return "CollectDir(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)" % (self.absolutePath, self.collectMode,
    1360                                                                       self.archiveMode, self.ignoreFile,
    1361                                                                       self.absoluteExcludePaths,
    1362                                                                       self.relativeExcludePaths,
    1363                                                                       self.excludePatterns,
    1364                                                                       self.linkDepth, self.dereference,
    1365                                                                       self.recursionLevel)
    1366
    1367 -   def __str__(self):
    1368        """
    1369        Informal string representation for class instance.
    1370        """
    1371        return self.__repr__()
    1372
    1373 -   def __cmp__(self, other):
    1374        """
    1375        Definition of equals operator for this class.
    1376        Lists within this class are "unordered" for equality comparisons.
    1377        @param other: Other object to compare to.
    1378        @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other.
    1379        """
    1380        if other is None:
    1381           return 1
    1382        if self.absolutePath != other.absolutePath:
    1383           if self.absolutePath < other.absolutePath:
    1384              return -1
    1385           else:
    1386              return 1
    1387        if self.collectMode != other.collectMode:
    1388           if self.collectMode < other.collectMode:
    1389              return -1
    1390           else:
    1391              return 1
    1392        if self.archiveMode != other.archiveMode:
    1393           if self.archiveMode < other.archiveMode:
    1394              return -1
    1395           else:
    1396              return 1
    1397        if self.ignoreFile != other.ignoreFile:
    1398           if self.ignoreFile < other.ignoreFile:
    1399              return -1
    1400           else:
    1401              return 1
    1402        if self.linkDepth != other.linkDepth:
    1403           if self.linkDepth < other.linkDepth:
    1404              return -1
    1405           else:
    1406              return 1
    1407        if self.dereference != other.dereference:
    1408           if self.dereference < other.dereference:
    1409              return -1
    1410           else:
    1411              return 1
    1412        if self.recursionLevel != other.recursionLevel:
    1413           if self.recursionLevel < other.recursionLevel:
    1414              return -1
    1415           else:
    1416              return 1
    1417        if self.absoluteExcludePaths != other.absoluteExcludePaths:
    1418           if self.absoluteExcludePaths < other.absoluteExcludePaths:
    1419              return -1
    1420           else:
    1421              return 1
    1422        if self.relativeExcludePaths != other.relativeExcludePaths:
    1423           if self.relativeExcludePaths < other.relativeExcludePaths:
    1424              return -1
    1425           else:
    1426              return 1
    1427        if self.excludePatterns != other.excludePatterns:
    1428           if self.excludePatterns < other.excludePatterns:
    1429              return -1
    1430           else:
    1431              return 1
    1432        return 0
    1433
    1434 -   def _setAbsolutePath(self, value):
    1435        """
    1436        Property target used to set the absolute path.
    1437        The value must be an absolute path if it is not C{None}.
    1438        It does not have to exist on disk at the time of assignment.
    1439        @raise ValueError: If the value is not an absolute path.
    1440        @raise ValueError: If the value cannot be encoded properly.
    1441        """
    1442        if value is not None:
    1443           if not os.path.isabs(value):
    1444              raise ValueError("Not an absolute path: [%s]" % value)
    1445        self._absolutePath = encodePath(value)
    1446
    1447 -   def _getAbsolutePath(self):
    1448        """
    1449        Property target used to get the absolute path.
    1450        """
    1451        return self._absolutePath
    1452
    1453 -   def _setCollectMode(self, value):
    1454        """
    1455        Property target used to set the collect mode.
    1456        If not C{None}, the mode must be one of the values in L{VALID_COLLECT_MODES}.
    1457        @raise ValueError: If the value is not valid.
    1458        """
    1459        if value is not None:
    1460           if value not in VALID_COLLECT_MODES:
    1461              raise ValueError("Collect mode must be one of %s." % VALID_COLLECT_MODES)
    1462        self._collectMode = value
    1463
    1464 -   def _getCollectMode(self):
    1465        """
    1466        Property target used to get the collect mode.
    1467        """
    1468        return self._collectMode
    1469
    1470 -   def _setArchiveMode(self, value):
    1471        """
    1472        Property target used to set the archive mode.
    1473        If not C{None}, the mode must be one of the values in L{VALID_ARCHIVE_MODES}.
    1474        @raise ValueError: If the value is not valid.
    1475        """
    1476        if value is not None:
    1477           if value not in VALID_ARCHIVE_MODES:
    1478              raise ValueError("Archive mode must be one of %s." % VALID_ARCHIVE_MODES)
    1479        self._archiveMode = value
    1480
    1481 -   def _getArchiveMode(self):
    1482        """
    1483        Property target used to get the archive mode.
    1484        """
    1485        return self._archiveMode
    1486
    1487 -   def _setIgnoreFile(self, value):
    1488        """
    1489        Property target used to set the ignore file.
    1490        The value must be a non-empty string if it is not C{None}.
    1491        @raise ValueError: If the value is an empty string.
    1492        """
    1493        if value is not None:
    1494           if len(value) < 1:
    1495              raise ValueError("The ignore file must be a non-empty string.")
    1496        self._ignoreFile = value
    1497
    1498 -   def _getIgnoreFile(self):
    1499        """
    1500        Property target used to get the ignore file.
    1501        """
    1502        return self._ignoreFile
    1503
    1504 -   def _setLinkDepth(self, value):
    1505        """
    1506        Property target used to set the link depth.
    1507        The value must be an integer >= 0.
    1508        @raise ValueError: If the value is not valid.
    1509        """
    1510        if value is None:
    1511           self._linkDepth = None
    1512        else:
    1513           try:
    1514              value = int(value)
    1515           except TypeError:
    1516              raise ValueError("Link depth value must be an integer >= 0.")
    1517           if value < 0:
    1518              raise ValueError("Link depth value must be an integer >= 0.")
    1519           self._linkDepth = value
    1520
    1521 -   def _getLinkDepth(self):
    1522        """
    1523        Property target used to get the action linkDepth.
    1524        """
    1525        return self._linkDepth
    1526
    1527 -   def _setDereference(self, value):
    1528        """
    1529        Property target used to set the dereference flag.
    1530        No validations, but we normalize the value to C{True} or C{False}.
    1531        """
    1532        if value:
    1533           self._dereference = True
    1534        else:
    1535           self._dereference = False
    1536
    1537 -   def _getDereference(self):
    1538        """
    1539        Property target used to get the dereference flag.
    1540        """
    1541        return self._dereference
    1542
    1543 -   def _setRecursionLevel(self, value):
    1544        """
    1545        Property target used to set the recursionLevel.
    1546        The value must be an integer.
    1547        @raise ValueError: If the value is not valid.
    1548        """
    1549        if value is None:
    1550           self._recursionLevel = None
    1551        else:
    1552           try:
    1553              value = int(value)
    1554           except TypeError:
    1555              raise ValueError("Recusion level value must be an integer.")
    1556           self._recursionLevel = value
    1557
    1558 -   def _getRecursionLevel(self):
    1559        """
    1560        Property target used to get the action recursionLevel.
    1561        """
    1562        return self._recursionLevel
    1563
    1564 -   def _setAbsoluteExcludePaths(self, value):
    1565        """
    1566        Property target used to set the absolute exclude paths list.
    1567        Either the value must be C{None} or each element must be an absolute path.
    1568        Elements do not have to exist on disk at the time of assignment.
    1569        @raise ValueError: If the value is not an absolute path.
    1570        """
    1571        if value is None:
    1572           self._absoluteExcludePaths = None
    1573        else:
    1574           try:
    1575              saved = self._absoluteExcludePaths
    1576              self._absoluteExcludePaths = AbsolutePathList()
    1577              self._absoluteExcludePaths.extend(value)
    1578           except Exception, e:
    1579              self._absoluteExcludePaths = saved
    1580              raise e
    1581
    1582 -   def _getAbsoluteExcludePaths(self):
    1583        """
    1584        Property target used to get the absolute exclude paths list.
    1585        """
    1586        return self._absoluteExcludePaths
    1587
    1588 -   def _setRelativeExcludePaths(self, value):
    1589        """
    1590        Property target used to set the relative exclude paths list.
    1591        Elements do not have to exist on disk at the time of assignment.
    1592        """
    1593        if value is None:
    1594           self._relativeExcludePaths = None
    1595        else:
    1596           try:
    1597              saved = self._relativeExcludePaths
    1598              self._relativeExcludePaths = UnorderedList()
    1599              self._relativeExcludePaths.extend(value)
    1600           except Exception, e:
    1601              self._relativeExcludePaths = saved
    1602              raise e
    1603
    1604 -   def _getRelativeExcludePaths(self):
    1605        """
    1606        Property target used to get the relative exclude paths list.
    1607        """
    1608        return self._relativeExcludePaths
    1609
    1610 -   def _setExcludePatterns(self, value):
    1611        """
    1612        Property target used to set the exclude patterns list.
    1613        """
    1614        if value is None:
    1615           self._excludePatterns = None
    1616        else:
    1617           try:
    1618              saved = self._excludePatterns
    1619              self._excludePatterns = RegexList()
    1620              self._excludePatterns.extend(value)
    1621           except Exception, e:
    1622              self._excludePatterns = saved
    1623              raise e
    1624
    1625 -   def _getExcludePatterns(self):
    1626        """
    1627        Property target used to get the exclude patterns list.
    1628        """
    1629        return self._excludePatterns
    1630
    1631     absolutePath = property(_getAbsolutePath, _setAbsolutePath, None, doc="Absolute path of the directory to collect.")
    1632     collectMode = property(_getCollectMode, _setCollectMode, None, doc="Overridden collect mode for this directory.")
    1633     archiveMode = property(_getArchiveMode, _setArchiveMode, None, doc="Overridden archive mode for this directory.")
    1634     ignoreFile = property(_getIgnoreFile, _setIgnoreFile, None, doc="Overridden ignore file name for this directory.")
    1635     linkDepth = property(_getLinkDepth, _setLinkDepth, None, doc="Maximum at which soft links should be followed.")
    1636     dereference = property(_getDereference, _setDereference, None, doc="Whether to dereference links that are followed.")
    1637     recursionLevel = property(_getRecursionLevel, _setRecursionLevel, None, "Recursion level to use for recursive directory collection")
    1638     absoluteExcludePaths = property(_getAbsoluteExcludePaths, _setAbsoluteExcludePaths, None, "List of absolute paths to exclude.")
    1639     relativeExcludePaths = property(_getRelativeExcludePaths, _setRelativeExcludePaths, None, "List of relative paths to exclude.")
    1640     excludePatterns = property(_getExcludePatterns, _setExcludePatterns, None, "List of regular expression patterns to exclude.")
    1641
    1642
    1643  ########################################################################
    1644  # PurgeDir class definition
    1645  ########################################################################
    1646
    1647 -class PurgeDir(object):
    1648
    1649     """
    1650     Class representing a Cedar Backup purge directory.
    1651
    1652     The following restrictions exist on data in this class:
    1653
    1654        - The absolute path must be an absolute path
    1655        - The retain days value must be an integer >= 0.
    1656
    1657     @sort: __init__, __repr__, __str__, __cmp__, absolutePath, retainDays
    1658     """
    1659
    1660 -   def __init__(self, absolutePath=None, retainDays=None):
    1661        """
    1662        Constructor for the C{PurgeDir} class.
    1663
    1664        @param absolutePath: Absolute path of the directory to be purged.
    1665        @param retainDays: Number of days content within directory should be retained.
    1666
    1667        @raise ValueError: If one of the values is invalid.
    1668        """
    1669        self._absolutePath = None
    1670        self._retainDays = None
    1671        self.absolutePath = absolutePath
    1672        self.retainDays = retainDays
    1673
    1674 -   def __repr__(self):
    1675        """
    1676        Official string representation for class instance.
    1677        """
    1678        return "PurgeDir(%s, %s)" % (self.absolutePath, self.retainDays)
    1679
    1680 -   def __str__(self):
    1681        """
    1682        Informal string representation for class instance.
    1683        """
    1684        return self.__repr__()
    1685
    1686 -   def __cmp__(self, other):
    1687        """
    1688        Definition of equals operator for this class.
    1689        @param other: Other object to compare to.
    1690        @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other.
    1691        """
    1692        if other is None:
    1693           return 1
    1694        if self.absolutePath != other.absolutePath:
    1695           if self.absolutePath < other.absolutePath:
    1696              return -1
    1697           else:
    1698              return 1
    1699        if self.retainDays != other.retainDays:
    1700           if self.retainDays < other.retainDays:
    1701              return -1
    1702           else:
    1703              return 1
    1704        return 0
    1705
    1706 -   def _setAbsolutePath(self, value):
    1707        """
    1708        Property target used to set the absolute path.
    1709        The value must be an absolute path if it is not C{None}.
    1710        It does not have to exist on disk at the time of assignment.
    1711        @raise ValueError: If the value is not an absolute path.
    1712        @raise ValueError: If the value cannot be encoded properly.
    1713        """
    1714        if value is not None:
    1715           if not os.path.isabs(value):
    1716              raise ValueError("Absolute path must, er, be an absolute path.")
    1717        self._absolutePath = encodePath(value)
    1718
    1719 -   def _getAbsolutePath(self):
    1720        """
    1721        Property target used to get the absolute path.
    1722        """
    1723        return self._absolutePath
    1724
    1725 -   def _setRetainDays(self, value):
    1726        """
    1727        Property target used to set the retain days value.
    1728        The value must be an integer >= 0.
    1729        @raise ValueError: If the value is not valid.
    1730        """
    1731        if value is None:
    1732           self._retainDays = None
    1733        else:
    1734           try:
    1735              value = int(value)
    1736           except TypeError:
    1737              raise ValueError("Retain days value must be an integer >= 0.")
    1738           if value < 0:
    1739              raise ValueError("Retain days value must be an integer >= 0.")
    1740           self._retainDays = value
    1741
    1742 -   def _getRetainDays(self):
    1743        """
    1744        Property target used to get the absolute path.
    1745        """
    1746        return self._retainDays
    1747
    1748     absolutePath = property(_getAbsolutePath, _setAbsolutePath, None, "Absolute path of directory to purge.")
    1749     retainDays = property(_getRetainDays, _setRetainDays, None, "Number of days content within directory should be retained.")
    1750
    1751
    1752  ########################################################################
    1753  # LocalPeer class definition
    1754  ########################################################################
    1755
    1756 -class LocalPeer(object):
    1757
    1758     """
    1759     Class representing a Cedar Backup peer.
    1760
    1761     The following restrictions exist on data in this class:
    1762
    1763        - The peer name must be a non-empty string.
    1764        - The collect directory must be an absolute path.
    1765        - The ignore failure mode must be one of the values in L{VALID_FAILURE_MODES}.
    1766
    1767     @sort: __init__, __repr__, __str__, __cmp__, name, collectDir
    1768     """
    1769
    1770 -   def __init__(self, name=None, collectDir=None, ignoreFailureMode=None):
    1771        """
    1772        Constructor for the C{LocalPeer} class.
    1773
    1774        @param name: Name of the peer, typically a valid hostname.
    1775        @param collectDir: Collect directory to stage files from on peer.
    1776        @param ignoreFailureMode: Ignore failure mode for peer.
    1777
    1778        @raise ValueError: If one of the values is invalid.
    1779        """
    1780        self._name = None
    1781        self._collectDir = None
    1782        self._ignoreFailureMode = None
    1783        self.name = name
    1784        self.collectDir = collectDir
    1785        self.ignoreFailureMode = ignoreFailureMode
    1786
    1787 -   def __repr__(self):
    1788        """
    1789        Official string representation for class instance.
    1790        """
    1791        return "LocalPeer(%s, %s, %s)" % (self.name, self.collectDir, self.ignoreFailureMode)
    1792
    1793 -   def __str__(self):
    1794        """
    1795        Informal string representation for class instance.
    1796        """
    1797        return self.__repr__()
    1798
    1799 -   def __cmp__(self, other):
    1800        """
    1801        Definition of equals operator for this class.
    1802        @param other: Other object to compare to.
    1803        @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other.
    1804        """
    1805        if other is None:
    1806           return 1
    1807        if self.name != other.name:
    1808           if self.name < other.name:
    1809              return -1
    1810           else:
    1811              return 1
    1812        if self.collectDir != other.collectDir:
    1813           if self.collectDir < other.collectDir:
    1814              return -1
    1815           else:
    1816              return 1
    1817        if self.ignoreFailureMode != other.ignoreFailureMode:
    1818           if self.ignoreFailureMode < other.ignoreFailureMode:
    1819              return -1
    1820           else:
    1821              return 1
    1822        return 0
    1823
    1824 -   def _setName(self, value):
    1825        """
    1826        Property target used to set the peer name.
    1827        The value must be a non-empty string if it is not C{None}.
    1828        @raise ValueError: If the value is an empty string.
    1829        """
    1830        if value is not None:
    1831           if len(value) < 1:
    1832              raise ValueError("The peer name must be a non-empty string.")
    1833        self._name = value
    1834
    1835 -   def _getName(self):
    1836        """
    1837        Property target used to get the peer name.
    1838        """
    1839        return self._name
    1840
    1841 -   def _setCollectDir(self, value):
    1842        """
    1843        Property target used to set the collect directory.
    1844        The value must be an absolute path if it is not C{None}.
    1845        It does not have to exist on disk at the time of assignment.
    1846        @raise ValueError: If the value is not an absolute path.
    1847        @raise ValueError: If the value cannot be encoded properly.
    1848        """
    1849        if value is not None:
    1850           if not os.path.isabs(value):
    1851              raise ValueError("Collect directory must be an absolute path.")
    1852        self._collectDir = encodePath(value)
    1853
    1854 -   def _getCollectDir(self):
    1855        """
    1856        Property target used to get the collect directory.
    1857        """
    1858        return self._collectDir
    1859
    1860 -   def _setIgnoreFailureMode(self, value):
    1861        """
    1862        Property target used to set the ignoreFailure mode.
    1863        If not C{None}, the mode must be one of the values in L{VALID_FAILURE_MODES}.
    1864        @raise ValueError: If the value is not valid.
    1865        """
    1866        if value is not None:
    1867           if value not in VALID_FAILURE_MODES:
    1868              raise ValueError("Ignore failure mode must be one of %s." % VALID_FAILURE_MODES)
    1869        self._ignoreFailureMode = value
    1870
    1871 -   def _getIgnoreFailureMode(self):
    1872        """
    1873        Property target used to get the ignoreFailure mode.
    1874        """
    1875        return self._ignoreFailureMode
    1876
    1877     name = property(_getName, _setName, None, "Name of the peer, typically a valid hostname.")
    1878     collectDir = property(_getCollectDir, _setCollectDir, None, "Collect directory to stage files from on peer.")
    1879     ignoreFailureMode = property(_getIgnoreFailureMode, _setIgnoreFailureMode, None, "Ignore failure mode for peer.")
    1880
    1881
    1882  ########################################################################
    1883  # RemotePeer class definition
    1884  ########################################################################
    1885
    1886 -class RemotePeer(object):
    1887
    1888     """
    1889     Class representing a Cedar Backup peer.
    1890
    1891     The following restrictions exist on data in this class:
    1892
    1893        - The peer name must be a non-empty string.
    1894        - The collect directory must be an absolute path.
    1895        - The remote user must be a non-empty string.
    1896        - The rcp command must be a non-empty string.
    1897        - The rsh command must be a non-empty string.
    1898        - The cback command must be a non-empty string.
    1899        - Any managed action name must be a non-empty string matching C{ACTION_NAME_REGEX}
    1900        - The ignore failure mode must be one of the values in L{VALID_FAILURE_MODES}.
    1901
    1902     @sort: __init__, __repr__, __str__, __cmp__, name, collectDir, remoteUser, rcpCommand
    1903     """
    1904
    1905 -   def __init__(self, name=None, collectDir=None, remoteUser=None,
    1906                  rcpCommand=None, rshCommand=None, cbackCommand=None,
    1907                  managed=False, managedActions=None, ignoreFailureMode=None):
    1908        """
    1909        Constructor for the C{RemotePeer} class.
    1910
    1911        @param name: Name of the peer, must be a valid hostname.
    1912        @param collectDir: Collect directory to stage files from on peer.
    1913        @param remoteUser: Name of backup user on remote peer.
    1914        @param rcpCommand: Overridden rcp-compatible copy command for peer.
    1915        @param rshCommand: Overridden rsh-compatible remote shell command for peer.
    1916        @param cbackCommand: Overridden cback-compatible command to use on remote peer.
    1917        @param managed: Indicates whether this is a managed peer.
    1918        @param managedActions: Overridden set of actions that are managed on the peer.
    1919        @param ignoreFailureMode: Ignore failure mode for peer.
    1920
    1921        @raise ValueError: If one of the values is invalid.
    1922        """
    1923        self._name = None
    1924        self._collectDir = None
    1925        self._remoteUser = None
    1926        self._rcpCommand = None
    1927        self._rshCommand = None
    1928        self._cbackCommand = None
    1929        self._managed = None
    1930        self._managedActions = None
    1931        self._ignoreFailureMode = None
    1932        self.name = name
    1933        self.collectDir = collectDir
    1934        self.remoteUser = remoteUser
    1935        self.rcpCommand = rcpCommand
    1936        self.rshCommand = rshCommand
    1937        self.cbackCommand = cbackCommand
    1938        self.managed = managed
    1939        self.managedActions = managedActions
    1940        self.ignoreFailureMode = ignoreFailureMode
    1941
    1942 -   def __repr__(self):
    1943        """
    1944        Official string representation for class instance.
    1945        """
    1946        return "RemotePeer(%s, %s, %s, %s, %s, %s, %s, %s, %s)" % (self.name, self.collectDir, self.remoteUser,
    1947                                                                   self.rcpCommand, self.rshCommand, self.cbackCommand,
    1948                                                                   self.managed, self.managedActions, self.ignoreFailureMode)
    1949
    1950 -   def __str__(self):
    1951        """
    1952        Informal string representation for class instance.
    1953        """
    1954        return self.__repr__()
    1955
    1956 -   def __cmp__(self, other):
    1957        """
    1958        Definition of equals operator for this class.
    1959        @param other: Other object to compare to.
    1960        @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other.
    1961        """
    1962        if other is None:
    1963           return 1
    1964        if self.name != other.name:
    1965           if self.name < other.name:
    1966              return -1
    1967           else:
    1968              return 1
    1969        if self.collectDir != other.collectDir:
    1970           if self.collectDir < other.collectDir:
    1971              return -1
    1972           else:
    1973              return 1
    1974        if self.remoteUser != other.remoteUser:
    1975           if self.remoteUser < other.remoteUser:
    1976              return -1
    1977           else:
    1978              return 1
    1979        if self.rcpCommand != other.rcpCommand:
    1980           if self.rcpCommand < other.rcpCommand:
    1981              return -1
    1982           else:
    1983              return 1
    1984        if self.rshCommand != other.rshCommand:
    1985           if self.rshCommand < other.rshCommand:
    1986              return -1
    1987           else:
    1988              return 1
    1989        if self.cbackCommand != other.cbackCommand:
    1990           if self.cbackCommand < other.cbackCommand:
    1991              return -1
    1992           else:
    1993              return 1
    1994        if self.managed != other.managed:
    1995           if self.managed < other.managed:
    1996              return -1
    1997           else:
    1998              return 1
    1999        if self.managedActions != other.managedActions:
    2000           if self.managedActions < other.managedActions:
    2001              return -1
    2002           else:
    2003              return 1
    2004        if self.ignoreFailureMode != other.ignoreFailureMode:
    2005           if self.ignoreFailureMode < other.ignoreFailureMode:
    2006              return -1
    2007           else:
    2008              return 1
    2009        return 0
    2010
    2011 -   def _setName(self, value):
    2012        """
    2013        Property target used to set the peer name.
    2014        The value must be a non-empty string if it is not C{None}.
    2015        @raise ValueError: If the value is an empty string.
    2016        """
    2017        if value is not None:
    2018           if len(value) < 1:
    2019              raise ValueError("The peer name must be a non-empty string.")
    2020        self._name = value
    2021
    2022 -   def _getName(self):
    2023        """
    2024        Property target used to get the peer name.
    2025        """
    2026        return self._name
    2027
    2028 -   def _setCollectDir(self, value):
    2029        """
    2030        Property target used to set the collect directory.
    2031        The value must be an absolute path if it is not C{None}.
    2032        It does not have to exist on disk at the time of assignment.
    2033        @raise ValueError: If the value is not an absolute path.
    2034        @raise ValueError: If the value cannot be encoded properly.
    2035        """
    2036        if value is not None:
    2037           if not os.path.isabs(value):
    2038              raise ValueError("Collect directory must be an absolute path.")
    2039        self._collectDir = encodePath(value)
    2040
    2041 -   def _getCollectDir(self):
    2042        """
    2043        Property target used to get the collect directory.
    2044        """
    2045        return self._collectDir
    2046
    2047 -   def _setRemoteUser(self, value):
    2048        """
    2049        Property target used to set the remote user.
    2050        The value must be a non-empty string if it is not C{None}.
    2051        @raise ValueError: If the value is an empty string.
    2052        """
    2053        if value is not None:
    2054           if len(value) < 1:
    2055              raise ValueError("The remote user must be a non-empty string.")
    2056        self._remoteUser = value
    2057
    2058 -   def _getRemoteUser(self):
    2059        """
    2060        Property target used to get the remote user.
    2061        """
    2062        return self._remoteUser
    2063
    2064 -   def _setRcpCommand(self, value):
    2065        """
    2066        Property target used to set the rcp command.
    2067        The value must be a non-empty string if it is not C{None}.
    2068        @raise ValueError: If the value is an empty string.
    2069        """
    2070        if value is not None:
    2071           if len(value) < 1:
    2072              raise ValueError("The rcp command must be a non-empty string.")
    2073        self._rcpCommand = value
    2074
    2075 -   def _getRcpCommand(self):
    2076        """
    2077        Property target used to get the rcp command.
    2078        """
    2079        return self._rcpCommand
    2080
    2081 -   def _setRshCommand(self, value):
    2082        """
    2083        Property target used to set the rsh command.
    2084        The value must be a non-empty string if it is not C{None}.
    2085        @raise ValueError: If the value is an empty string.
    2086        """
    2087        if value is not None:
    2088           if len(value) < 1:
    2089              raise ValueError("The rsh command must be a non-empty string.")
    2090        self._rshCommand = value
    2091
    2092 -   def _getRshCommand(self):
    2093        """
    2094        Property target used to get the rsh command.
    2095        """
    2096        return self._rshCommand
    2097
    2098 -   def _setCbackCommand(self, value):
    2099        """
    2100        Property target used to set the cback command.
    2101        The value must be a non-empty string if it is not C{None}.
    2102        @raise ValueError: If the value is an empty string.
    2103        """
    2104        if value is not None:
    2105           if len(value) < 1:
    2106              raise ValueError("The cback command must be a non-empty string.")
    2107        self._cbackCommand = value
    2108
    2109 -   def _getCbackCommand(self):
    2110        """
    2111        Property target used to get the cback command.
    2112        """
    2113        return self._cbackCommand
    2114
    2115 -   def _setManaged(self, value):
    2116        """
    2117        Property target used to set the managed flag.
    2118        No validations, but we normalize the value to C{True} or C{False}.
    2119        """
    2120        if value:
    2121           self._managed = True
    2122        else:
    2123           self._managed = False
    2124
    2125 -   def _getManaged(self):
    2126        """
    2127        Property target used to get the managed flag.
    2128        """
    2129        return self._managed
    2130
    2131 -   def _setManagedActions(self, value):
    2132        """
    2133        Property target used to set the managed actions list.
    2134        Elements do not have to exist on disk at the time of assignment.
    2135        """
    2136        if value is None:
    2137           self._managedActions = None
    2138        else:
    2139           try:
    2140              saved = self._managedActions
    2141              self._managedActions = RegexMatchList(ACTION_NAME_REGEX, emptyAllowed=False, prefix="Action name")
    2142              self._managedActions.extend(value)
    2143           except Exception, e:
    2144              self._managedActions = saved
    2145              raise e
    2146
    2147 -   def _getManagedActions(self):
    2148        """
    2149        Property target used to get the managed actions list.
    2150        """
    2151        return self._managedActions
    2152
    2153 -   def _setIgnoreFailureMode(self, value):
    2154        """
    2155        Property target used to set the ignoreFailure mode.
    2156        If not C{None}, the mode must be one of the values in L{VALID_FAILURE_MODES}.
    2157        @raise ValueError: If the value is not valid.
    2158        """
    2159        if value is not None:
    2160           if value not in VALID_FAILURE_MODES:
    2161              raise ValueError("Ignore failure mode must be one of %s." % VALID_FAILURE_MODES)
    2162        self._ignoreFailureMode = value
    2163
    2164 -   def _getIgnoreFailureMode(self):
    2165        """
    2166        Property target used to get the ignoreFailure mode.
    2167        """
    2168        return self._ignoreFailureMode
    2169
    2170     name = property(_getName, _setName, None, "Name of the peer, must be a valid hostname.")
    2171     collectDir = property(_getCollectDir, _setCollectDir, None, "Collect directory to stage files from on peer.")
    2172     remoteUser = property(_getRemoteUser, _setRemoteUser, None, "Name of backup user on remote peer.")
    2173     rcpCommand = property(_getRcpCommand, _setRcpCommand, None, "Overridden rcp-compatible copy command for peer.")
    2174     rshCommand = property(_getRshCommand, _setRshCommand, None, "Overridden rsh-compatible remote shell command for peer.")
    2175     cbackCommand = property(_getCbackCommand, _setCbackCommand, None, "Overridden cback-compatible command to use on remote peer.")
    2176     managed = property(_getManaged, _setManaged, None, "Indicates whether this is a managed peer.")
    2177     managedActions = property(_getManagedActions, _setManagedActions, None, "Overridden set of actions that are managed on the peer.")
    2178     ignoreFailureMode = property(_getIgnoreFailureMode, _setIgnoreFailureMode, None, "Ignore failure mode for peer.")
    2179
    2180
    2181  ########################################################################
    2182  # ReferenceConfig class definition
    2183  ########################################################################
    2184
    2185 -class ReferenceConfig(object):
    2186
    2187     """
    2188     Class representing a Cedar Backup reference configuration.
    2189
    2190     The reference information is just used for saving off metadata about
    2191     configuration and exists mostly for backwards-compatibility with Cedar
    2192     Backup 1.x.
    2193
    2194     @sort: __init__, __repr__, __str__, __cmp__, author, revision, description, generator
    2195     """
    2196
    2197 -   def __init__(self, author=None, revision=None, description=None, generator=None):
    2198        """
    2199        Constructor for the C{ReferenceConfig} class.
    2200
    2201        @param author: Author of the configuration file.
    2202        @param revision: Revision of the configuration file.
    2203        @param description: Description of the configuration file.
    2204        @param generator: Tool that generated the configuration file.
    2205        """
    2206        self._author = None
    2207        self._revision = None
    2208        self._description = None
    2209        self._generator = None
    2210        self.author = author
    2211        self.revision = revision
    2212        self.description = description
    2213        self.generator = generator
    2214
    2215 -   def __repr__(self):
    2216        """
    2217        Official string representation for class instance.
    2218        """
    2219        return "ReferenceConfig(%s, %s, %s, %s)" % (self.author, self.revision, self.description, self.generator)
    2220
    2221 -   def __str__(self):
    2222        """
    2223        Informal string representation for class instance.
    2224        """
    2225        return self.__repr__()
    2226
    2227 -   def __cmp__(self, other):
    2228        """
    2229        Definition of equals operator for this class.
    2230        @param other: Other object to compare to.
    2231        @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other.
    2232        """
    2233        if other is None:
    2234           return 1
    2235        if self.author != other.author:
    2236           if self.author < other.author:
    2237              return -1
    2238           else:
    2239              return 1
    2240        if self.revision != other.revision:
    2241           if self.revision < other.revision:
    2242              return -1
    2243           else:
    2244              return 1
    2245        if self.description != other.description:
    2246           if self.description < other.description:
    2247              return -1
    2248           else:
    2249              return 1
    2250        if self.generator != other.generator:
    2251           if self.generator < other.generator:
    2252              return -1
    2253           else:
    2254              return 1
    2255        return 0
    2256
    2257 -   def _setAuthor(self, value):
    2258        """
    2259        Property target used to set the author value.
    2260        No validations.
    2261        """
    2262        self._author = value
    2263
    2264 -   def _getAuthor(self):
    2265        """
    2266        Property target used to get the author value.
    2267        """
    2268        return self._author
    2269
    2270 -   def _setRevision(self, value):
    2271        """
    2272        Property target used to set the revision value.
    2273        No validations.
    2274        """
    2275        self._revision = value
    2276
    2277 -   def _getRevision(self):
    2278        """
    2279        Property target used to get the revision value.
    2280        """
    2281        return self._revision
    2282
    2283 -   def _setDescription(self, value):
    2284        """
    2285        Property target used to set the description value.
    2286        No validations.
    2287        """
    2288        self._description = value
    2289
    2290 -   def _getDescription(self):
    2291        """
    2292        Property target used to get the description value.
    2293        """
    2294        return self._description
    2295
    2296 -   def _setGenerator(self, value):
    2297        """
    2298        Property target used to set the generator value.
    2299        No validations.
    2300        """
    2301        self._generator = value
    2302
    2303 -   def _getGenerator(self):
    2304        """
    2305        Property target used to get the generator value.
    2306        """
    2307        return self._generator
    2308
    2309     author = property(_getAuthor, _setAuthor, None, "Author of the configuration file.")
    2310     revision = property(_getRevision, _setRevision, None, "Revision of the configuration file.")
    2311     description = property(_getDescription, _setDescription, None, "Description of the configuration file.")
    2312     generator = property(_getGenerator, _setGenerator, None, "Tool that generated the configuration file.")
    2313
    2314
    2315  ########################################################################
    2316  # ExtensionsConfig class definition
    2317  ########################################################################
    2318
    2319 -class ExtensionsConfig(object):
    2320
    2321     """
    2322     Class representing Cedar Backup extensions configuration.
    2323
    2324     Extensions configuration is used to specify "extended actions" implemented
    2325     by code external to Cedar Backup.  For instance, a hypothetical third party
    2326     might write extension code to collect database repository data.  If they
    2327     write a properly-formatted extension function, they can use the extension
    2328     configuration to map a command-line Cedar Backup action (i.e. "database")
    2329     to their function.
    2330
    2331     The following restrictions exist on data in this class:
    2332
    2333        - If set, the order mode must be one of the values in C{VALID_ORDER_MODES}
    2334        - The actions list must be a list of C{ExtendedAction} objects.
    2335
    2336     @sort: __init__, __repr__, __str__, __cmp__, orderMode, actions
    2337     """
    2338
    2339 -   def __init__(self, actions=None, orderMode=None):
    2340        """
    2341        Constructor for the C{ExtensionsConfig} class.
    2342        @param actions: List of extended actions
    2343        """
    2344        self._orderMode = None
    2345        self._actions = None
    2346        self.orderMode = orderMode
    2347        self.actions = actions
    2348
    2349 -   def __repr__(self):
    2350        """
    2351        Official string representation for class instance.
    2352        """
    2353        return "ExtensionsConfig(%s, %s)" % (self.orderMode, self.actions)
    2354
    2355 -   def __str__(self):
    2356        """
    2357        Informal string representation for class instance.
    2358        """
    2359        return self.__repr__()
    2360
    2361 -   def __cmp__(self, other):
    2362        """
    2363        Definition of equals operator for this class.
    2364        @param other: Other object to compare to.
    2365        @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other.
    2366        """
    2367        if other is None:
    2368           return 1
    2369        if self.orderMode != other.orderMode:
    2370           if self.orderMode < other.orderMode:
    2371              return -1
    2372           else:
    2373              return 1
    2374        if self.actions != other.actions:
    2375           if self.actions < other.actions:
    2376              return -1
    2377           else:
    2378              return 1
    2379        return 0
    2380
    2381 -   def _setOrderMode(self, value):
    2382        """
    2383        Property target used to set the order mode.
    2384        The value must be one of L{VALID_ORDER_MODES}.
    2385        @raise ValueError: If the value is not valid.
    2386        """
    2387        if value is not None:
    2388           if value not in VALID_ORDER_MODES:
    2389              raise ValueError("Order mode must be one of %s." % VALID_ORDER_MODES)
    2390        self._orderMode = value
    2391
    2392 -   def _getOrderMode(self):
    2393        """
    2394        Property target used to get the order mode.
    2395        """
    2396        return self._orderMode
    2397
    2398 -   def _setActions(self, value):
    2399        """
    2400        Property target used to set the actions list.
    2401        Either the value must be C{None} or each element must be an C{ExtendedAction}.
    2402        @raise ValueError: If the value is not a C{ExtendedAction}
    2403        """
    2404        if value is None:
    2405           self._actions = None
    2406        else:
    2407           try:
    2408              saved = self._actions
    2409              self._actions = ObjectTypeList(ExtendedAction, "ExtendedAction")
    2410              self._actions.extend(value)
    2411           except Exception, e:
    2412              self._actions = saved
    2413              raise e
    2414
    2415 -   def _getActions(self):
    2416        """
    2417        Property target used to get the actions list.
    2418        """
    2419        return self._actions
    2420
    2421     orderMode = property(_getOrderMode, _setOrderMode, None, "Order mode for extensions, to control execution ordering.")
    2422     actions = property(_getActions, _setActions, None, "List of extended actions.")
    2423
    2424
    2425  ########################################################################
    2426  # OptionsConfig class definition
    2427  ########################################################################
    2428
    2429 -class OptionsConfig(object):
    2430
    2431     """
    2432     Class representing a Cedar Backup global options configuration.
    2433
    2434     The options section is used to store global configuration options and
    2435     defaults that can be applied to other sections.
    2436
    2437     The following restrictions exist on data in this class:
    2438
    2439        - The working directory must be an absolute path.
    2440        - The starting day must be a day of the week in English, i.e. C{"monday"}, C{"tuesday"}, etc.
    2441        - All of the other values must be non-empty strings if they are set to something other than C{None}.
    2442        - The overrides list must be a list of C{CommandOverride} objects.
    2443        - The hooks list must be a list of C{ActionHook} objects.
    2444        - The cback command must be a non-empty string.
    2445        - Any managed action name must be a non-empty string matching C{ACTION_NAME_REGEX}
    2446
    2447     @sort: __init__, __repr__, __str__, __cmp__, startingDay, workingDir,
    2448           backupUser, backupGroup, rcpCommand, rshCommand, overrides
    2449     """
    2450
    2451 -   def __init__(self, startingDay=None, workingDir=None, backupUser=None,
    2452                  backupGroup=None, rcpCommand=None, overrides=None,
    2453                  hooks=None, rshCommand=None, cbackCommand=None,
    2454                  managedActions=None):
    2455        """
    2456        Constructor for the C{OptionsConfig} class.
    2457
    2458        @param startingDay: Day that starts the week.
    2459        @param workingDir: Working (temporary) directory to use for backups.
    2460        @param backupUser: Effective user that backups should run as.
    2461        @param backupGroup: Effective group that backups should run as.
    2462        @param rcpCommand: Default rcp-compatible copy command for staging.
    2463        @param rshCommand: Default rsh-compatible command to use for remote shells.
    2464        @param cbackCommand: Default cback-compatible command to use on managed remote peers.
    2465        @param overrides: List of configured command path overrides, if any.
    2466        @param hooks: List of configured pre- and post-action hooks.
    2467        @param managedActions: Default set of actions that are managed on remote peers.
    2468
    2469        @raise ValueError: If one of the values is invalid.
    2470        """
    2471        self._startingDay = None
    2472        self._workingDir = None
    2473        self._backupUser = None
    2474        self._backupGroup = None
    2475        self._rcpCommand = None
    2476        self._rshCommand = None
    2477        self._cbackCommand = None
    2478        self._overrides = None
    2479        self._hooks = None
    2480        self._managedActions = None
    2481        self.startingDay = startingDay
    2482        self.workingDir = workingDir
    2483        self.backupUser = backupUser
    2484        self.backupGroup = backupGroup
    2485        self.rcpCommand = rcpCommand
    2486        self.rshCommand = rshCommand
    2487        self.cbackCommand = cbackCommand
    2488        self.overrides = overrides
    2489        self.hooks = hooks
    2490        self.managedActions = managedActions
    2491
    2492 -   def __repr__(self):
    2493        """
    2494        Official string representation for class instance.
    2495        """
    2496        return "OptionsConfig(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)" % (self.startingDay, self.workingDir,
    2497                                                                          self.backupUser, self.backupGroup,
    2498                                                                          self.rcpCommand, self.overrides,
    2499                                                                          self.hooks, self.rshCommand,
    2500                                                                          self.cbackCommand, self.managedActions)
    2501
    2502 -   def __str__(self):
    2503        """
    2504        Informal string representation for class instance.
    2505        """
    2506        return self.__repr__()
    2507
    2508 -   def __cmp__(self, other):
    2509        """
    2510        Definition of equals operator for this class.
    2511        @param other: Other object to compare to.
    2512        @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other.
    2513        """
    2514        if other is None:
    2515           return 1
    2516        if self.startingDay != other.startingDay:
    2517           if self.startingDay < other.startingDay:
    2518              return -1
    2519           else:
    2520              return 1
    2521        if self.workingDir != other.workingDir:
    2522           if self.workingDir < other.workingDir:
    2523              return -1
    2524           else:
    2525              return 1
    2526        if self.backupUser != other.backupUser:
    2527           if self.backupUser < other.backupUser:
    2528              return -1
    2529           else:
    2530              return 1
    2531        if self.backupGroup != other.backupGroup:
    2532           if self.backupGroup < other.backupGroup:
    2533              return -1
    2534           else:
    2535              return 1
    2536        if self.rcpCommand != other.rcpCommand:
    2537           if self.rcpCommand < other.rcpCommand:
    2538              return -1
    2539           else:
    2540              return 1
    2541        if self.rshCommand != other.rshCommand:
    2542           if self.rshCommand < other.rshCommand:
    2543              return -1
    2544           else:
    2545              return 1
    2546        if self.cbackCommand != other.cbackCommand:
    2547           if self.cbackCommand < other.cbackCommand:
    2548              return -1
    2549           else:
    2550              return 1
    2551        if self.overrides != other.overrides:
    2552           if self.overrides < other.overrides:
    2553              return -1
    2554           else:
    2555              return 1
    2556        if self.hooks != other.hooks:
    2557           if self.hooks < other.hooks:
    2558              return -1
    2559           else:
    2560              return 1
    2561        if self.managedActions != other.managedActions:
    2562           if self.managedActions < other.managedActions:
    2563              return -1
    2564           else:
    2565              return 1
    2566        return 0
    2567
    2568 -   def addOverride(self, command, absolutePath):
    2569        """
    2570        If no override currently exists for the command, add one.
    2571        @param command: Name of command to be overridden.
    2572        @param absolutePath: Absolute path of the overrridden command.
    2573        """
    2574        override = CommandOverride(command, absolutePath)
    2575        if self.overrides is None:
    2576           self.overrides = [ override, ]
    2577        else:
    2578           exists = False
    2579           for obj in self.overrides:
    2580              if obj.command == override.command:
    2581                 exists = True
    2582                 break
    2583           if not exists:
    2584              self.overrides.append(override)
    2585
    2586 -   def replaceOverride(self, command, absolutePath):
    2587        """
    2588        If override currently exists for the command, replace it; otherwise add it.
    2589        @param command: Name of command to be overridden.
    2590        @param absolutePath: Absolute path of the overrridden command.
    2591        """
    2592        override = CommandOverride(command, absolutePath)
    2593        if self.overrides is None:
    2594           self.overrides = [ override, ]
    2595        else:
    2596           exists = False
    2597           for obj in self.overrides:
    2598              if obj.command == override.command:
    2599                 exists = True
    2600                 obj.absolutePath = override.absolutePath
    2601                 break
    2602           if not exists:
    2603              self.overrides.append(override)
    2604
    2605 -   def _setStartingDay(self, value):
    2606        """
    2607        Property target used to set the starting day.
    2608        If it is not C{None}, the value must be a valid English day of the week,
    2609        one of C{"monday"}, C{"tuesday"}, C{"wednesday"}, etc.
    2610        @raise ValueError: If the value is not a valid day of the week.
    2611        """
    2612        if value is not None:
    2613           if value not in ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday", ]:
    2614              raise ValueError("Starting day must be an English day of the week, i.e. \"monday\".")
    2615        self._startingDay = value
    2616
    2617 -   def _getStartingDay(self):
    2618        """
    2619        Property target used to get the starting day.
    2620        """
    2621        return self._startingDay
    2622
    2623 -   def _setWorkingDir(self, value):
    2624        """
    2625        Property target used to set the working directory.
    2626        The value must be an absolute path if it is not C{None}.
    2627        It does not have to exist on disk at the time of assignment.
    2628        @raise ValueError: If the value is not an absolute path.
    2629        @raise ValueError: If the value cannot be encoded properly.
    2630        """
    2631        if value is not None:
    2632           if not os.path.isabs(value):
    2633              raise ValueError("Working directory must be an absolute path.")
    2634        self._workingDir = encodePath(value)
    2635
    2636 -   def _getWorkingDir(self):
    2637        """
    2638        Property target used to get the working directory.
    2639        """
    2640        return self._workingDir
    2641
    2642 -   def _setBackupUser(self, value):
    2643        """
    2644        Property target used to set the backup user.
    2645        The value must be a non-empty string if it is not C{None}.
    2646        @raise ValueError: If the value is an empty string.
    2647        """
    2648        if value is not None:
    2649           if len(value) < 1:
    2650              raise ValueError("Backup user must be a non-empty string.")
    2651        self._backupUser = value
    2652
    2653 -   def _getBackupUser(self):
    2654        """
    2655        Property target used to get the backup user.
    2656        """
    2657        return self._backupUser
    2658
    2659 -   def _setBackupGroup(self, value):
    2660        """
    2661        Property target used to set the backup group.
    2662        The value must be a non-empty string if it is not C{None}.
    2663        @raise ValueError: If the value is an empty string.
    2664        """
    2665        if value is not None:
    2666           if len(value) < 1:
    2667              raise ValueError("Backup group must be a non-empty string.")
    2668        self._backupGroup = value
    2669
    2670 -   def _getBackupGroup(self):
    2671        """
    2672        Property target used to get the backup group.
    2673        """
    2674        return self._backupGroup
    2675
    2676 -   def _setRcpCommand(self, value):
    2677        """
    2678        Property target used to set the rcp command.
    2679        The value must be a non-empty string if it is not C{None}.
    2680        @raise ValueError: If the value is an empty string.
    2681        """
    2682        if value is not None:
    2683           if len(value) < 1:
    2684              raise ValueError("The rcp command must be a non-empty string.")
    2685        self._rcpCommand = value
    2686
    2687 -   def _getRcpCommand(self):
    2688        """
    2689        Property target used to get the rcp command.
    2690        """
    2691        return self._rcpCommand
    2692
    2693 -   def _setRshCommand(self, value):
    2694        """
    2695        Property target used to set the rsh command.
    2696        The value must be a non-empty string if it is not C{None}.
    2697        @raise ValueError: If the value is an empty string.
    2698        """
    2699        if value is not None:
    2700           if len(value) < 1:
    2701              raise ValueError("The rsh command must be a non-empty string.")
    2702        self._rshCommand = value
    2703
    2704 -   def _getRshCommand(self):
    2705        """
    2706        Property target used to get the rsh command.
    2707        """
    2708        return self._rshCommand
    2709
    2710 -   def _setCbackCommand(self, value):
    2711        """
    2712        Property target used to set the cback command.
    2713        The value must be a non-empty string if it is not C{None}.
    2714        @raise ValueError: If the value is an empty string.
    2715        """
    2716        if value is not None:
    2717           if len(value) < 1:
    2718              raise ValueError("The cback command must be a non-empty string.")
    2719        self._cbackCommand = value
    2720
    2721 -   def _getCbackCommand(self):
    2722        """
    2723        Property target used to get the cback command.
    2724        """
    2725        return self._cbackCommand
    2726
    2727 -   def _setOverrides(self, value):
    2728        """
    2729        Property target used to set the command path overrides list.
    2730        Either the value must be C{None} or each element must be a C{CommandOverride}.
    2731        @raise ValueError: If the value is not a C{CommandOverride}
    2732        """
    2733        if value is None:
    2734           self._overrides = None
    2735        else:
    2736           try:
    2737              saved = self._overrides
    2738              self._overrides = ObjectTypeList(CommandOverride, "CommandOverride")
    2739              self._overrides.extend(value)
    2740           except Exception, e:
    2741              self._overrides = saved
    2742              raise e
    2743
    2744 -   def _getOverrides(self):
    2745        """
    2746        Property target used to get the command path overrides list.
    2747        """
    2748        return self._overrides
    2749
    2750 -   def _setHooks(self, value):
    2751        """
    2752        Property target used to set the pre- and post-action hooks list.
    2753        Either the value must be C{None} or each element must be an C{ActionHook}.
    2754        @raise ValueError: If the value is not a C{CommandOverride}
    2755        """
    2756        if value is None:
    2757           self._hooks = None
    2758        else:
    2759           try:
    2760              saved = self._hooks
    2761              self._hooks = ObjectTypeList(ActionHook, "ActionHook")
    2762              self._hooks.extend(value)
    2763           except Exception, e:
    2764              self._hooks = saved
    2765              raise e
    2766
    2767 -   def _getHooks(self):
    2768        """
    2769        Property target used to get the command path hooks list.
    2770        """
    2771        return self._hooks
    2772
    2773 -   def _setManagedActions(self, value):
    2774        """
    2775        Property target used to set the managed actions list.
    2776        Elements do not have to exist on disk at the time of assignment.
    2777        """
    2778        if value is None:
    2779           self._managedActions = None
    2780        else:
    2781           try:
    2782              saved = self._managedActions
    2783              self._managedActions = RegexMatchList(ACTION_NAME_REGEX, emptyAllowed=False, prefix="Action name")
    2784              self._managedActions.extend(value)
    2785           except Exception, e:
    2786              self._managedActions = saved
    2787              raise e
    2788
    2789 -   def _getManagedActions(self):
    2790        """
    2791        Property target used to get the managed actions list.
    2792        """
    2793        return self._managedActions
    2794
    2795     startingDay = property(_getStartingDay, _setStartingDay, None, "Day that starts the week.")
    2796     workingDir = property(_getWorkingDir, _setWorkingDir, None, "Working (temporary) directory to use for backups.")
    2797     backupUser = property(_getBackupUser, _setBackupUser, None, "Effective user that backups should run as.")
    2798     backupGroup = property(_getBackupGroup, _setBackupGroup, None, "Effective group that backups should run as.")
    2799     rcpCommand = property(_getRcpCommand, _setRcpCommand, None, "Default rcp-compatible copy command for staging.")
    2800     rshCommand = property(_getRshCommand, _setRshCommand, None, "Default rsh-compatible command to use for remote shells.")
    2801     cbackCommand = property(_getCbackCommand, _setCbackCommand, None, "Default cback-compatible command to use on managed remote peers.")
    2802     overrides = property(_getOverrides, _setOverrides, None, "List of configured command path overrides, if any.")
    2803     hooks = property(_getHooks, _setHooks, None, "List of configured pre- and post-action hooks.")
    2804     managedActions = property(_getManagedActions, _setManagedActions, None, "Default set of actions that are managed on remote peers.")
    2805
    2806
    2807  ########################################################################
    2808  # PeersConfig class definition
    2809  ########################################################################
    2810
    2811 -class PeersConfig(object):
    2812
    2813     """
    2814     Class representing Cedar Backup global peer configuration.
    2815
    2816     This section contains a list of local and remote peers in a master's backup
    2817     pool.  The section is optional.  If a master does not define this section,
    2818     then all peers are unmanaged, and the stage configuration section must
    2819     explicitly list any peer that is to be staged.  If this section is
    2820     configured, then peers may be managed or unmanaged, and the stage section
    2821     peer configuration (if any) completely overrides this configuration.
    2822
    2823     The following restrictions exist on data in this class:
    2824
    2825        - The list of local peers must contain only C{LocalPeer} objects
    2826        - The list of remote peers must contain only C{RemotePeer} objects
    2827
    2828     @note: Lists within this class are "unordered" for equality comparisons.
    2829
    2830     @sort: __init__, __repr__, __str__, __cmp__, localPeers, remotePeers
    2831     """
    2832
    2833 -   def __init__(self, localPeers=None, remotePeers=None):
    2834        """
    2835        Constructor for the C{PeersConfig} class.
    2836
    2837        @param localPeers: List of local peers.
    2838        @param remotePeers: List of remote peers.
    2839
    2840        @raise ValueError: If one of the values is invalid.
    2841        """
    2842        self._localPeers = None
    2843        self._remotePeers = None
    2844        self.localPeers = localPeers
    2845        self.remotePeers = remotePeers
    2846
    2847 -   def __repr__(self):
    2848        """
    2849        Official string representation for class instance.
    2850        """
    2851        return "PeersConfig(%s, %s)" % (self.localPeers, self.remotePeers)
    2852
    2853 -   def __str__(self):
    2854        """
    2855        Informal string representation for class instance.
    2856        """
    2857        return self.__repr__()
    2858
    2859 -   def __cmp__(self, other):
    2860        """
    2861        Definition of equals operator for this class.
    2862        Lists within this class are "unordered" for equality comparisons.
    2863        @param other: Other object to compare to.
    2864        @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other.
    2865        """
    2866        if other is None:
    2867           return 1
    2868        if self.localPeers != other.localPeers:
    2869           if self.localPeers < other.localPeers:
    2870              return -1
    2871           else:
    2872              return 1
    2873        if self.remotePeers != other.remotePeers:
    2874           if self.remotePeers < other.remotePeers:
    2875              return -1
    2876           else:
    2877              return 1
    2878        return 0
    2879
    2880 -   def hasPeers(self):
    2881        """
    2882        Indicates whether any peers are filled into this object.
    2883        @return: Boolean true if any local or remote peers are filled in, false otherwise.
    2884        """
    2885        return ((self.localPeers is not None and len(self.localPeers) > 0) or
    2886                (self.remotePeers is not None and len(self.remotePeers) > 0))
    2887
    2888 -   def _setLocalPeers(self, value):
    2889        """
    2890        Property target used to set the local peers list.
    2891        Either the value must be C{None} or each element must be a C{LocalPeer}.
    2892        @raise ValueError: If the value is not an absolute path.
    2893        """
    2894        if value is None:
    2895           self._localPeers = None
    2896        else:
    2897           try:
    2898              saved = self._localPeers
    2899              self._localPeers = ObjectTypeList(LocalPeer, "LocalPeer")
    2900              self._localPeers.extend(value)
    2901           except Exception, e:
    2902              self._localPeers = saved
    2903              raise e
    2904
    2905 -   def _getLocalPeers(self):
    2906        """
    2907        Property target used to get the local peers list.
    2908        """
    2909        return self._localPeers
    2910
    2911 -   def _setRemotePeers(self, value):
    2912        """
    2913        Property target used to set the remote peers list.
    2914        Either the value must be C{None} or each element must be a C{RemotePeer}.
    2915        @raise ValueError: If the value is not a C{RemotePeer}
    2916        """
    2917        if value is None:
    2918           self._remotePeers = None
    2919        else:
    2920           try:
    2921              saved = self._remotePeers
    2922              self._remotePeers = ObjectTypeList(RemotePeer, "RemotePeer")
    2923              self._remotePeers.extend(value)
    2924           except Exception, e:
    2925              self._remotePeers = saved
    2926              raise e
    2927
    2928 -   def _getRemotePeers(self):
    2929        """
    2930        Property target used to get the remote peers list.
    2931        """
    2932        return self._remotePeers
    2933
    2934     localPeers = property(_getLocalPeers, _setLocalPeers, None, "List of local peers.")
    2935     remotePeers = property(_getRemotePeers, _setRemotePeers, None, "List of remote peers.")
    2936
    2937
    2938  ########################################################################
    2939  # CollectConfig class definition
    2940  ########################################################################
    2941
    2942 -class CollectConfig(object):
    2943
    2944     """
    2945     Class representing a Cedar Backup collect configuration.
    2946
    2947     The following restrictions exist on data in this class:
    2948
    2949        - The target directory must be an absolute path.
    2950        - The collect mode must be one of the values in L{VALID_COLLECT_MODES}.
    2951        - The archive mode must be one of the values in L{VALID_ARCHIVE_MODES}.
    2952        - The ignore file must be a non-empty string.
    2953        - Each of the paths in C{absoluteExcludePaths} must be an absolute path
    2954        - The collect file list must be a list of C{CollectFile} objects.
    2955        - The collect directory list must be a list of C{CollectDir} objects.
    2956
    2957     For the C{absoluteExcludePaths} list, validation is accomplished through the
    2958     L{util.AbsolutePathList} list implementation that overrides common list
    2959     methods and transparently does the absolute path validation for us.
    2960
    2961     For the C{collectFiles} and C{collectDirs} list, validation is accomplished
    2962     through the L{util.ObjectTypeList} list implementation that overrides common
    2963     list methods and transparently ensures that each element has an appropriate
    2964     type.
    2965
    2966     @note: Lists within this class are "unordered" for equality comparisons.
    2967
    2968     @sort: __init__, __repr__, __str__, __cmp__, targetDir,
    2969            collectMode, archiveMode, ignoreFile, absoluteExcludePaths,
    2970            excludePatterns, collectFiles, collectDirs
    2971     """
    2972
    2973 -   def __init__(self, targetDir=None, collectMode=None, archiveMode=None, ignoreFile=None,
    2974                  absoluteExcludePaths=None, excludePatterns=None, collectFiles=None,
    2975                  collectDirs=None):
    2976        """
    2977        Constructor for the C{CollectConfig} class.
    2978
    2979        @param targetDir: Directory to collect files into.
    2980        @param collectMode: Default collect mode.
    2981        @param archiveMode: Default archive mode for collect files.
    2982        @param ignoreFile: Default ignore file name.
    2983        @param absoluteExcludePaths: List of absolute paths to exclude.
    2984        @param excludePatterns: List of regular expression patterns to exclude.
    2985        @param collectFiles: List of collect files.
    2986        @param collectDirs: List of collect directories.
    2987
    2988        @raise ValueError: If one of the values is invalid.
    2989        """
    2990        self._targetDir = None
    2991        self._collectMode = None
    2992        self._archiveMode = None
    2993        self._ignoreFile = None
    2994        self._absoluteExcludePaths = None
    2995        self._excludePatterns = None
    2996        self._collectFiles = None
    2997        self._collectDirs = None
    2998        self.targetDir = targetDir
    2999        self.collectMode = collectMode
    3000        self.archiveMode = archiveMode
    3001        self.ignoreFile = ignoreFile
    3002        self.absoluteExcludePaths = absoluteExcludePaths
    3003        self.excludePatterns = excludePatterns
    3004        self.collectFiles = collectFiles
    3005        self.collectDirs = collectDirs
    3006
    3007 -   def __repr__(self):
    3008        """
    3009        Official string representation for class instance.
    3010        """
    3011        return "CollectConfig(%s, %s, %s, %s, %s, %s, %s, %s)" % (self.targetDir, self.collectMode, self.archiveMode,
    3012                                                                  self.ignoreFile, self.absoluteExcludePaths,
    3013                                                                  self.excludePatterns, self.collectFiles, self.collectDirs)
    3014
    3015 -   def __str__(self):
    3016        """
    3017        Informal string representation for class instance.
    3018        """
    3019        return self.__repr__()
    3020
    3021 -   def __cmp__(self, other):
    3022        """
    3023        Definition of equals operator for this class.
    3024        Lists within this class are "unordered" for equality comparisons.
    3025        @param other: Other object to compare to.
    3026        @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other.
    3027        """
    3028        if other is None:
    3029           return 1
    3030        if self.targetDir != other.targetDir:
    3031           if self.targetDir < other.targetDir:
    3032              return -1
    3033           else:
    3034              return 1
    3035        if self.collectMode != other.collectMode:
    3036           if self.collectMode < other.collectMode:
    3037              return -1
    3038           else:
    3039              return 1
    3040        if self.archiveMode != other.archiveMode:
    3041           if self.archiveMode < other.archiveMode:
    3042              return -1
    3043           else:
    3044              return 1
    3045        if self.ignoreFile != other.ignoreFile:
    3046           if self.ignoreFile < other.ignoreFile:
    3047              return -1
    3048           else:
    3049              return 1
    3050        if self.absoluteExcludePaths != other.absoluteExcludePaths:
    3051           if self.absoluteExcludePaths < other.absoluteExcludePaths:
    3052              return -1
    3053           else:
    3054              return 1
    3055        if self.excludePatterns != other.excludePatterns:
    3056           if self.excludePatterns < other.excludePatterns:
    3057              return -1
    3058           else:
    3059              return 1
    3060        if self.collectFiles != other.collectFiles:
    3061           if self.collectFiles < other.collectFiles:
    3062              return -1
    3063           else:
    3064              return 1
    3065        if self.collectDirs != other.collectDirs:
    3066           if self.collectDirs < other.collectDirs:
    3067              return -1
    3068           else:
    3069              return 1
    3070        return 0
    3071
    3072 -   def _setTargetDir(self, value):
    3073        """
    3074        Property target used to set the target directory.
    3075        The value must be an absolute path if it is not C{None}.
    3076        It does not have to exist on disk at the time of assignment.
    3077        @raise ValueError: If the value is not an absolute path.
    3078        @raise ValueError: If the value cannot be encoded properly.
    3079        """
    3080        if value is not None:
    3081           if not os.path.isabs(value):
    3082              raise ValueError("Target directory must be an absolute path.")
    3083        self._targetDir = encodePath(value)
    3084
    3085 -   def _getTargetDir(self):
    3086        """
    3087        Property target used to get the target directory.
    3088        """
    3089        return self._targetDir
    3090
    3091 -   def _setCollectMode(self, value):
    3092        """
    3093        Property target used to set the collect mode.
    3094        If not C{None}, the mode must be one of L{VALID_COLLECT_MODES}.
    3095        @raise ValueError: If the value is not valid.
    3096        """
    3097        if value is not None:
    3098           if value not in VALID_COLLECT_MODES:
    3099              raise ValueError("Collect mode must be one of %s." % VALID_COLLECT_MODES)
    3100        self._collectMode = value
    3101
    3102 -   def _getCollectMode(self):
    3103        """
    3104        Property target used to get the collect mode.
    3105        """
    3106        return self._collectMode
    3107
    3108 -   def _setArchiveMode(self, value):
    3109        """
    3110        Property target used to set the archive mode.
    3111        If not C{None}, the mode must be one of L{VALID_ARCHIVE_MODES}.
    3112        @raise ValueError: If the value is not valid.
    3113        """
    3114        if value is not None:
    3115           if value not in VALID_ARCHIVE_MODES:
    3116              raise ValueError("Archive mode must be one of %s." % VALID_ARCHIVE_MODES)
    3117        self._archiveMode = value
    3118
    3119 -   def _getArchiveMode(self):
    3120        """
    3121        Property target used to get the archive mode.
    3122        """
    3123        return self._archiveMode
    3124
    3125 -   def _setIgnoreFile(self, value):
    3126        """
    3127        Property target used to set the ignore file.
    3128        The value must be a non-empty string if it is not C{None}.
    3129        @raise ValueError: If the value is an empty string.
    3130        @raise ValueError: If the value cannot be encoded properly.
    3131        """
    3132        if value is not None:
    3133           if len(value) < 1:
    3134              raise ValueError("The ignore file must be a non-empty string.")
    3135        self._ignoreFile = encodePath(value)
    3136
    3137 -   def _getIgnoreFile(self):
    3138        """
    3139        Property target used to get the ignore file.
    3140        """
    3141        return self._ignoreFile
    3142
    3143 -   def _setAbsoluteExcludePaths(self, value):
    3144        """
    3145        Property target used to set the absolute exclude paths list.
    3146        Either the value must be C{None} or each element must be an absolute path.
    3147        Elements do not have to exist on disk at the time of assignment.
    3148        @raise ValueError: If the value is not an absolute path.
    3149        """
    3150        if value is None:
    3151           self._absoluteExcludePaths = None
    3152        else:
    3153           try:
    3154              saved = self._absoluteExcludePaths
    3155              self._absoluteExcludePaths = AbsolutePathList()
    3156              self._absoluteExcludePaths.extend(value)
    3157           except Exception, e:
    3158              self._absoluteExcludePaths = saved
    3159              raise e
    3160
    3161 -   def _getAbsoluteExcludePaths(self):
    3162        """
    3163        Property target used to get the absolute exclude paths list.
    3164        """
    3165        return self._absoluteExcludePaths
    3166
    3167 -   def _setExcludePatterns(self, value):
    3168        """
    3169        Property target used to set the exclude patterns list.
    3170        """
    3171        if value is None:
    3172           self._excludePatterns = None
    3173        else:
    3174           try:
    3175              saved = self._excludePatterns
    3176              self._excludePatterns = RegexList()
    3177              self._excludePatterns.extend(value)
    3178           except Exception, e:
    3179              self._excludePatterns = saved
    3180              raise e
    3181
    3182 -   def _getExcludePatterns(self):
    3183        """
    3184        Property target used to get the exclude patterns list.
    3185        """
    3186        return self._excludePatterns
    3187
    3188 -   def _setCollectFiles(self, value):
    3189        """
    3190        Property target used to set the collect files list.
    3191        Either the value must be C{None} or each element must be a C{CollectFile}.
    3192        @raise ValueError: If the value is not a C{CollectFile}
    3193        """
    3194        if value is None:
    3195           self._collectFiles = None
    3196        else:
    3197           try:
    3198              saved = self._collectFiles
    3199              self._collectFiles = ObjectTypeList(CollectFile, "CollectFile")
    3200              self._collectFiles.extend(value)
    3201           except Exception, e:
    3202              self._collectFiles = saved
    3203              raise e
    3204
    3205 -   def _getCollectFiles(self):
    3206        """
    3207        Property target used to get the collect files list.
    3208        """
    3209        return self._collectFiles
    3210
    3211 -   def _setCollectDirs(self, value):
    3212        """
    3213        Property target used to set the collect dirs list.
    3214        Either the value must be C{None} or each element must be a C{CollectDir}.
    3215        @raise ValueError: If the value is not a C{CollectDir}
    3216        """
    3217        if value is None:
    3218           self._collectDirs = None
    3219        else:
    3220           try:
    3221              saved = self._collectDirs
    3222              self._collectDirs = ObjectTypeList(CollectDir, "CollectDir")
    3223              self._collectDirs.extend(value)
    3224           except Exception, e:
    3225              self._collectDirs = saved
    3226              raise e
    3227
    3228 -   def _getCollectDirs(self):
    3229        """
    3230        Property target used to get the collect dirs list.
    3231        """
    3232        return self._collectDirs
    3233
    3234     targetDir = property(_getTargetDir, _setTargetDir, None, "Directory to collect files into.")
    3235     collectMode = property(_getCollectMode, _setCollectMode, None, "Default collect mode.")
    3236     archiveMode = property(_getArchiveMode, _setArchiveMode, None, "Default archive mode for collect files.")
    3237     ignoreFile = property(_getIgnoreFile, _setIgnoreFile, None, "Default ignore file name.")
    3238     absoluteExcludePaths = property(_getAbsoluteExcludePaths, _setAbsoluteExcludePaths, None, "List of absolute paths to exclude.")
    3239     excludePatterns = property(_getExcludePatterns, _setExcludePatterns, None, "List of regular expressions patterns to exclude.")
    3240     collectFiles = property(_getCollectFiles, _setCollectFiles, None, "List of collect files.")
    3241     collectDirs = property(_getCollectDirs, _setCollectDirs, None, "List of collect directories.")
    3242
    3243
    3244  ########################################################################
    3245  # StageConfig class definition
    3246  ########################################################################
    3247
    3248 -class StageConfig(object):
    3249
    3250     """
    3251     Class representing a Cedar Backup stage configuration.
    3252
    3253     The following restrictions exist on data in this class:
    3254
    3255        - The target directory must be an absolute path
    3256        - The list of local peers must contain only C{LocalPeer} objects
    3257        - The list of remote peers must contain only C{RemotePeer} objects
    3258
    3259     @note: Lists within this class are "unordered" for equality comparisons.
    3260
    3261     @sort: __init__, __repr__, __str__, __cmp__, targetDir, localPeers, remotePeers
    3262     """
    3263
    3264 -   def __init__(self, targetDir=None, localPeers=None, remotePeers=None):
    3265        """
    3266        Constructor for the C{StageConfig} class.
    3267
    3268        @param targetDir: Directory to stage files into, by peer name.
    3269        @param localPeers: List of local peers.
    3270        @param remotePeers: List of remote peers.
    3271
    3272        @raise ValueError: If one of the values is invalid.
    3273        """
    3274        self._targetDir = None
    3275        self._localPeers = None
    3276        self._remotePeers = None
    3277        self.targetDir = targetDir
    3278        self.localPeers = localPeers
    3279        self.remotePeers = remotePeers
    3280
    3281 -   def __repr__(self):
    3282        """
    3283        Official string representation for class instance.
    3284        """
    3285        return "StageConfig(%s, %s, %s)" % (self.targetDir, self.localPeers, self.remotePeers)
    3286
    3287 -   def __str__(self):
    3288        """
    3289        Informal string representation for class instance.
    3290        """
    3291        return self.__repr__()
    3292
    3293 -   def __cmp__(self, other):
    3294        """
    3295        Definition of equals operator for this class.
    3296        Lists within this class are "unordered" for equality comparisons.
    3297        @param other: Other object to compare to.
    3298        @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other.
    3299        """
    3300        if other is None:
    3301           return 1
    3302        if self.targetDir != other.targetDir:
    3303           if self.targetDir < other.targetDir:
    3304              return -1
    3305           else:
    3306              return 1
    3307        if self.localPeers != other.localPeers:
    3308           if self.localPeers < other.localPeers:
    3309              return -1
    3310           else:
    3311              return 1
    3312        if self.remotePeers != other.remotePeers:
    3313           if self.remotePeers < other.remotePeers:
    3314              return -1
    3315           else:
    3316              return 1
    3317        return 0
    3318
    3319 -   def hasPeers(self):
    3320        """
    3321        Indicates whether any peers are filled into this object.
    3322        @return: Boolean true if any local or remote peers are filled in, false otherwise.
    3323        """
    3324        return ((self.localPeers is not None and len(self.localPeers) > 0) or
    3325                (self.remotePeers is not None and len(self.remotePeers) > 0))
    3326
    3327 -   def _setTargetDir(self, value):
    3328        """
    3329        Property target used to set the target directory.
    3330        The value must be an absolute path if it is not C{None}.
    3331        It does not have to exist on disk at the time of assignment.
    3332        @raise ValueError: If the value is not an absolute path.
    3333        @raise ValueError: If the value cannot be encoded properly.
    3334        """
    3335        if value is not None:
    3336           if not os.path.isabs(value):
    3337              raise ValueError("Target directory must be an absolute path.")
    3338        self._targetDir = encodePath(value)
    3339
    3340 -   def _getTargetDir(self):
    3341        """
    3342        Property target used to get the target directory.
    3343        """
    3344        return self._targetDir
    3345
    3346 -   def _setLocalPeers(self, value):
    3347        """
    3348        Property target used to set the local peers list.
    3349        Either the value must be C{None} or each element must be a C{LocalPeer}.
    3350        @raise ValueError: If the value is not an absolute path.
    3351        """
    3352        if value is None:
    3353           self._localPeers = None
    3354        else:
    3355           try:
    3356              saved = self._localPeers
    3357              self._localPeers = ObjectTypeList(LocalPeer, "LocalPeer")
    3358              self._localPeers.extend(value)
    3359           except Exception, e:
    3360              self._localPeers = saved
    3361              raise e
    3362
    3363 -   def _getLocalPeers(self):
    3364        """
    3365        Property target used to get the local peers list.
    3366        """
    3367        return self._localPeers
    3368
    3369 -   def _setRemotePeers(self, value):
    3370        """
    3371        Property target used to set the remote peers list.
    3372        Either the value must be C{None} or each element must be a C{RemotePeer}.
    3373        @raise ValueError: If the value is not a C{RemotePeer}
    3374        """
    3375        if value is None:
    3376           self._remotePeers = None
    3377        else:
    3378           try:
    3379              saved = self._remotePeers
    3380              self._remotePeers = ObjectTypeList(RemotePeer, "RemotePeer")
    3381              self._remotePeers.extend(value)
    3382           except Exception, e:
    3383              self._remotePeers = saved
    3384              raise e
    3385
    3386 -   def _getRemotePeers(self):
    3387        """
    3388        Property target used to get the remote peers list.
    3389        """
    3390        return self._remotePeers
    3391
    3392     targetDir = property(_getTargetDir, _setTargetDir, None, "Directory to stage files into, by peer name.")
    3393     localPeers = property(_getLocalPeers, _setLocalPeers, None, "List of local peers.")
    3394     remotePeers = property(_getRemotePeers, _setRemotePeers, None, "List of remote peers.")
    3395
    3396
    3397  ########################################################################
    3398  # StoreConfig class definition
    3399  ########################################################################
    3400
    3401 -class StoreConfig(object):
    3402
    3403     """
    3404     Class representing a Cedar Backup store configuration.
    3405
    3406     The following restrictions exist on data in this class:
    3407
    3408        - The source directory must be an absolute path.
    3409        - The media type must be one of the values in L{VALID_MEDIA_TYPES}.
    3410        - The device type must be one of the values in L{VALID_DEVICE_TYPES}.
    3411        - The device path must be an absolute path.
    3412        - The SCSI id, if provided, must be in the form specified by L{validateScsiId}.
    3413        - The drive speed must be an integer >= 1
    3414        - The blanking behavior must be a C{BlankBehavior} object
    3415        - The refresh media delay must be an integer >= 0
    3416        - The eject delay must be an integer >= 0
    3417
    3418     Note that although the blanking factor must be a positive floating point
    3419     number, it is stored as a string. This is done so that we can losslessly go
    3420     back and forth between XML and object representations of configuration.
    3421
    3422     @sort: __init__, __repr__, __str__, __cmp__, sourceDir,
    3423            mediaType, deviceType, devicePath, deviceScsiId,
    3424            driveSpeed, checkData, checkMedia, warnMidnite, noEject,
    3425            blankBehavior, refreshMediaDelay, ejectDelay
    3426     """
    3427
    3428 -   def __init__(self, sourceDir=None, mediaType=None, deviceType=None,
    3429                  devicePath=None, deviceScsiId=None, driveSpeed=None,
    3430                  checkData=False, warnMidnite=False, noEject=False,
    3431                  checkMedia=False, blankBehavior=None, refreshMediaDelay=None,
    3432                  ejectDelay=None):
    3433        """
    3434        Constructor for the C{StoreConfig} class.
    3435
    3436        @param sourceDir: Directory whose contents should be written to media.
    3437        @param mediaType: Type of the media (see notes above).
    3438        @param deviceType: Type of the device (optional, see notes above).
    3439        @param devicePath: Filesystem device name for writer device, i.e. C{/dev/cdrw}.
    3440        @param deviceScsiId: SCSI id for writer device, i.e. C{[<method>:]scsibus,target,lun}.
    3441        @param driveSpeed: Speed of the drive, i.e. C{2} for 2x drive, etc.
    3442        @param checkData: Whether resulting image should be validated.
    3443        @param checkMedia: Whether media should be checked before being written to.
    3444        @param warnMidnite: Whether to generate warnings for crossing midnite.
    3445        @param noEject: Indicates that the writer device should not be ejected.
    3446        @param blankBehavior: Controls optimized blanking behavior.
    3447        @param refreshMediaDelay: Delay, in seconds, to add after refreshing media
    3448        @param ejectDelay: Delay, in seconds, to add after ejecting media before closing the tray
    3449
    3450        @raise ValueError: If one of the values is invalid.
    3451        """
    3452        self._sourceDir = None
    3453        self._mediaType = None
    3454        self._deviceType = None
    3455        self._devicePath = None
    3456        self._deviceScsiId = None
    3457        self._driveSpeed = None
    3458        self._checkData = None
    3459        self._checkMedia = None
    3460        self._warnMidnite = None
    3461        self._noEject = None
    3462        self._blankBehavior = None
    3463        self._refreshMediaDelay = None
    3464        self._ejectDelay = None
    3465        self.sourceDir = sourceDir
    3466        self.mediaType = mediaType
    3467        self.deviceType = deviceType
    3468        self.devicePath = devicePath
    3469        self.deviceScsiId = deviceScsiId
    3470        self.driveSpeed = driveSpeed
    3471        self.checkData = checkData
    3472        self.checkMedia = checkMedia
    3473        self.warnMidnite = warnMidnite
    3474        self.noEject = noEject
    3475        self.blankBehavior = blankBehavior
    3476        self.refreshMediaDelay = refreshMediaDelay
    3477        self.ejectDelay = ejectDelay
    3478
    3479 -   def __repr__(self):
    3480        """
    3481        Official string representation for class instance.
    3482        """
    3483        return "StoreConfig(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)" % (
    3484            self.sourceDir, self.mediaType, self.deviceType,
    3485            self.devicePath, self.deviceScsiId, self.driveSpeed,
    3486            self.checkData, self.warnMidnite, self.noEject,
    3487            self.checkMedia, self.blankBehavior, self.refreshMediaDelay,
    3488            self.ejectDelay)
    3489
    3490 -   def __str__(self):
    3491        """
    3492        Informal string representation for class instance.
    3493        """
    3494        return self.__repr__()
    3495
    3496 -   def __cmp__(self, other):
    3497        """
    3498        Definition of equals operator for this class.
    3499        @param other: Other object to compare to.
    3500        @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other.
    3501        """
    3502        if other is None:
    3503           return 1
    3504        if self.sourceDir != other.sourceDir:
    3505           if self.sourceDir < other.sourceDir:
    3506              return -1
    3507           else:
    3508              return 1
    3509        if self.mediaType != other.mediaType:
    3510           if self.mediaType < other.mediaType:
    3511              return -1
    3512           else:
    3513              return 1
    3514        if self.deviceType != other.deviceType:
    3515           if self.deviceType < other.deviceType:
    3516              return -1
    3517           else:
    3518              return 1
    3519        if self.devicePath != other.devicePath:
    3520           if self.devicePath < other.devicePath:
    3521              return -1
    3522           else:
    3523              return 1
    3524        if self.deviceScsiId != other.deviceScsiId:
    3525           if self.deviceScsiId < other.deviceScsiId:
    3526              return -1
    3527           else:
    3528              return 1
    3529        if self.driveSpeed != other.driveSpeed:
    3530           if self.driveSpeed < other.driveSpeed:
    3531              return -1
    3532           else:
    3533              return 1
    3534        if self.checkData != other.checkData:
    3535           if self.checkData < other.checkData:
    3536              return -1
    3537           else:
    3538              return 1
    3539        if self.checkMedia != other.checkMedia:
    3540           if self.checkMedia < other.checkMedia:
    3541              return -1
    3542           else:
    3543              return 1
    3544        if self.warnMidnite != other.warnMidnite:
    3545           if self.warnMidnite < other.warnMidnite:
    3546              return -1
    3547           else:
    3548              return 1
    3549        if self.noEject != other.noEject:
    3550           if self.noEject < other.noEject:
    3551              return -1
    3552           else:
    3553              return 1
    3554        if self.blankBehavior != other.blankBehavior:
    3555           if self.blankBehavior < other.blankBehavior:
    3556              return -1
    3557           else:
    3558              return 1
    3559        if self.refreshMediaDelay != other.refreshMediaDelay:
    3560           if self.refreshMediaDelay < other.refreshMediaDelay:
    3561              return -1
    3562           else:
    3563              return 1
    3564        if self.ejectDelay != other.ejectDelay:
    3565           if self.ejectDelay < other.ejectDelay:
    3566              return -1
    3567           else:
    3568              return 1
    3569        return 0
    3570
    3571 -   def _setSourceDir(self, value):
    3572        """
    3573        Property target used to set the source directory.
    3574        The value must be an absolute path if it is not C{None}.
    3575        It does not have to exist on disk at the time of assignment.
    3576        @raise ValueError: If the value is not an absolute path.
    3577        @raise ValueError: If the value cannot be encoded properly.
    3578        """
    3579        if value is not None:
    3580           if not os.path.isabs(value):
    3581              raise ValueError("Source directory must be an absolute path.")
    3582        self._sourceDir = encodePath(value)
    3583
    3584 -   def _getSourceDir(self):
    3585        """
    3586        Property target used to get the source directory.
    3587        """
    3588        return self._sourceDir
    3589
    3590 -   def _setMediaType(self, value):
    3591        """
    3592        Property target used to set the media type.
    3593        The value must be one of L{VALID_MEDIA_TYPES}.
    3594        @raise ValueError: If the value is not valid.
    3595        """
    3596        if value is not None:
    3597           if value not in VALID_MEDIA_TYPES:
    3598              raise ValueError("Media type must be one of %s." % VALID_MEDIA_TYPES)
    3599        self._mediaType = value
    3600
    3601 -   def _getMediaType(self):
    3602        """
    3603        Property target used to get the media type.
    3604        """
    3605        return self._mediaType
    3606
    3607 -   def _setDeviceType(self, value):
    3608        """
    3609        Property target used to set the device type.
    3610        The value must be one of L{VALID_DEVICE_TYPES}.
    3611        @raise ValueError: If the value is not valid.
    3612        """
    3613        if value is not None:
    3614           if value not in VALID_DEVICE_TYPES:
    3615              raise ValueError("Device type must be one of %s." % VALID_DEVICE_TYPES)
    3616        self._deviceType = value
    3617
    3618 -   def _getDeviceType(self):
    3619        """
    3620        Property target used to get the device type.
    3621        """
    3622        return self._deviceType
    3623
    3624 -   def _setDevicePath(self, value):
    3625        """
    3626        Property target used to set the device path.
    3627        The value must be an absolute path if it is not C{None}.
    3628        It does not have to exist on disk at the time of assignment.
    3629        @raise ValueError: If the value is not an absolute path.
    3630        @raise ValueError: If the value cannot be encoded properly.
    3631        """
    3632        if value is not None:
    3633           if not os.path.isabs(value):
    3634              raise ValueError("Device path must be an absolute path.")
    3635        self._devicePath = encodePath(value)
    3636
    3637 -   def _getDevicePath(self):
    3638        """
    3639        Property target used to get the device path.
    3640        """
    3641        return self._devicePath
    3642
    3643 -   def _setDeviceScsiId(self, value):
    3644        """
    3645        Property target used to set the SCSI id
    3646        The SCSI id must be valid per L{validateScsiId}.
    3647        @raise ValueError: If the value is not valid.
    3648        """
    3649        if value is None:
    3650           self._deviceScsiId = None
    3651        else:
    3652           self._deviceScsiId = validateScsiId(value)
    3653
    3654 -   def _getDeviceScsiId(self):
    3655        """
    3656        Property target used to get the SCSI id.
    3657        """
    3658        return self._deviceScsiId
    3659
    3660 -   def _setDriveSpeed(self, value):
    3661        """
    3662        Property target used to set the drive speed.
    3663        The drive speed must be valid per L{validateDriveSpeed}.
    3664        @raise ValueError: If the value is not valid.
    3665        """
    3666        self._driveSpeed = validateDriveSpeed(value)
    3667
    3668 -   def _getDriveSpeed(self):
    3669        """
    3670        Property target used to get the drive speed.
    3671        """
    3672        return self._driveSpeed
    3673
    3674 -   def _setCheckData(self, value):
    3675        """
    3676        Property target used to set the check data flag.
    3677        No validations, but we normalize the value to C{True} or C{False}.
    3678        """
    3679        if value:
    3680           self._checkData = True
    3681        else:
    3682           self._checkData = False
    3683
    3684 -   def _getCheckData(self):
    3685        """
    3686        Property target used to get the check data flag.
    3687        """
    3688        return self._checkData
    3689
    3690 -   def _setCheckMedia(self, value):
    3691        """
    3692        Property target used to set the check media flag.
    3693        No validations, but we normalize the value to C{True} or C{False}.
    3694        """
    3695        if value:
    3696           self._checkMedia = True
    3697        else:
    3698           self._checkMedia = False
    3699
    3700 -   def _getCheckMedia(self):
    3701        """
    3702        Property target used to get the check media flag.
    3703        """
    3704        return self._checkMedia
    3705
    3706 -   def _setWarnMidnite(self, value):
    3707        """
    3708        Property target used to set the midnite warning flag.
    3709        No validations, but we normalize the value to C{True} or C{False}.
    3710        """
    3711        if value:
    3712           self._warnMidnite = True
    3713        else:
    3714           self._warnMidnite = False
    3715
    3716 -   def _getWarnMidnite(self):
    3717        """
    3718        Property target used to get the midnite warning flag.
    3719        """
    3720        return self._warnMidnite
    3721
    3722 -   def _setNoEject(self, value):
    3723        """
    3724        Property target used to set the no-eject flag.
    3725        No validations, but we normalize the value to C{True} or C{False}.
    3726        """
    3727        if value:
    3728           self._noEject = True
    3729        else:
    3730           self._noEject = False
    3731
    3732 -   def _getNoEject(self):
    3733        """
    3734        Property target used to get the no-eject flag.
    3735        """
    3736        return self._noEject
    3737
    3738 -   def _setBlankBehavior(self, value):
    3739        """
    3740        Property target used to set blanking behavior configuration.
    3741        If not C{None}, the value must be a C{BlankBehavior} object.
    3742        @raise ValueError: If the value is not a C{BlankBehavior}
    3743        """
    3744        if value is None:
    3745           self._blankBehavior = None
    3746        else:
    3747           if not isinstance(value, BlankBehavior):
    3748              raise ValueError("Value must be a C{BlankBehavior} object.")
    3749           self._blankBehavior = value
    3750
    3751 -   def _getBlankBehavior(self):
    3752        """
    3753        Property target used to get the blanking behavior configuration.
    3754        """
    3755        return self._blankBehavior
    3756
    3757 -   def _setRefreshMediaDelay(self, value):
    3758        """
    3759        Property target used to set the refreshMediaDelay.
    3760        The value must be an integer >= 0.
    3761        @raise ValueError: If the value is not valid.
    3762        """
    3763        if value is None:
    3764           self._refreshMediaDelay = None
    3765        else:
    3766           try:
    3767              value = int(value)
    3768           except TypeError:
    3769              raise ValueError("Action refreshMediaDelay value must be an integer >= 0.")
    3770           if value < 0:
    3771              raise ValueError("Action refreshMediaDelay value must be an integer >= 0.")
    3772           if value == 0:
    3773              value = None  # normalize this out, since it's the default
    3774           self._refreshMediaDelay = value
    3775
    3776 -   def _getRefreshMediaDelay(self):
    3777        """
    3778        Property target used to get the action refreshMediaDelay.
    3779        """
    3780        return self._refreshMediaDelay
    3781
    3782 -   def _setEjectDelay(self, value):
    3783        """
    3784        Property target used to set the ejectDelay.
    3785        The value must be an integer >= 0.
    3786        @raise ValueError: If the value is not valid.
    3787        """
    3788        if value is None:
    3789           self._ejectDelay = None
    3790        else:
    3791           try:
    3792              value = int(value)
    3793           except TypeError:
    3794              raise ValueError("Action ejectDelay value must be an integer >= 0.")
    3795           if value < 0:
    3796              raise ValueError("Action ejectDelay value must be an integer >= 0.")
    3797           if value == 0:
    3798              value = None  # normalize this out, since it's the default
    3799           self._ejectDelay = value
    3800
    3801 -   def _getEjectDelay(self):
    3802        """
    3803        Property target used to get the action ejectDelay.
    3804        """
    3805        return self._ejectDelay
    3806
    3807     sourceDir = property(_getSourceDir, _setSourceDir, None, "Directory whose contents should be written to media.")
    3808     mediaType = property(_getMediaType, _setMediaType, None, "Type of the media (see notes above).")
    3809     deviceType = property(_getDeviceType, _setDeviceType, None, "Type of the device (optional, see notes above).")
    3810     devicePath = property(_getDevicePath, _setDevicePath, None, "Filesystem device name for writer device.")
    3811     deviceScsiId = property(_getDeviceScsiId, _setDeviceScsiId, None, "SCSI id for writer device (optional, see notes above).")
    3812     driveSpeed = property(_getDriveSpeed, _setDriveSpeed, None, "Speed of the drive.")
    3813     checkData = property(_getCheckData, _setCheckData, None, "Whether resulting image should be validated.")
    3814     checkMedia = property(_getCheckMedia, _setCheckMedia, None, "Whether media should be checked before being written to.")
    3815     warnMidnite = property(_getWarnMidnite, _setWarnMidnite, None, "Whether to generate warnings for crossing midnite.")
    3816     noEject = property(_getNoEject, _setNoEject, None, "Indicates that the writer device should not be ejected.")
    3817     blankBehavior = property(_getBlankBehavior, _setBlankBehavior, None, "Controls optimized blanking behavior.")
    3818     refreshMediaDelay = property(_getRefreshMediaDelay, _setRefreshMediaDelay, None, "Delay, in seconds, to add after refreshing media.")
    3819     ejectDelay = property(_getEjectDelay, _setEjectDelay, None, "Delay, in seconds, to add after ejecting media before closing the tray")
    3820
    3821
    3822  ########################################################################
    3823  # PurgeConfig class definition
    3824  ########################################################################
    3825
    3826 -class PurgeConfig(object):
    3827
    3828     """
    3829     Class representing a Cedar Backup purge configuration.
    3830
    3831     The following restrictions exist on data in this class:
    3832
    3833        - The purge directory list must be a list of C{PurgeDir} objects.
    3834
    3835     For the C{purgeDirs} list, validation is accomplished through the
    3836     L{util.ObjectTypeList} list implementation that overrides common list
    3837     methods and transparently ensures that each element is a C{PurgeDir}.
    3838
    3839     @note: Lists within this class are "unordered" for equality comparisons.
    3840
    3841     @sort: __init__, __repr__, __str__, __cmp__, purgeDirs
    3842     """
    3843
    3844 -   def __init__(self, purgeDirs=None):
    3845        """
    3846        Constructor for the C{Purge} class.
    3847        @param purgeDirs: List of purge directories.
    3848        @raise ValueError: If one of the values is invalid.
    3849        """
    3850        self._purgeDirs = None
    3851        self.purgeDirs = purgeDirs
    3852
    3853 -   def __repr__(self):
    3854        """
    3855        Official string representation for class instance.
    3856        """
    3857        return "PurgeConfig(%s)" % self.purgeDirs
    3858
    3859 -   def __str__(self):
    3860        """
    3861        Informal string representation for class instance.
    3862        """
    3863        return self.__repr__()
    3864
    3865 -   def __cmp__(self, other):
    3866        """
    3867        Definition of equals operator for this class.
    3868        Lists within this class are "unordered" for equality comparisons.
    3869        @param other: Other object to compare to.
    3870        @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other.
    3871        """
    3872        if other is None:
    3873           return 1
    3874        if self.purgeDirs != other.purgeDirs:
    3875           if self.purgeDirs < other.purgeDirs:
    3876              return -1
    3877           else:
    3878              return 1
    3879        return 0
    3880
    3881 -   def _setPurgeDirs(self, value):
    3882        """
    3883        Property target used to set the purge dirs list.
    3884        Either the value must be C{None} or each element must be a C{PurgeDir}.
    3885        @raise ValueError: If the value is not a C{PurgeDir}
    3886        """
    3887        if value is None:
    3888           self._purgeDirs = None
    3889        else:
    3890           try:
    3891              saved = self._purgeDirs
    3892              self._purgeDirs = ObjectTypeList(PurgeDir, "PurgeDir")
    3893              self._purgeDirs.extend(value)
    3894           except Exception, e:
    3895              self._purgeDirs = saved
    3896              raise e
    3897
    3898 -   def _getPurgeDirs(self):
    3899        """
    3900        Property target used to get the purge dirs list.
    3901        """
    3902        return self._purgeDirs
    3903
    3904     purgeDirs = property(_getPurgeDirs, _setPurgeDirs, None, "List of directories to purge.")
    3905
    3906
    3907  ########################################################################
    3908  # Config class definition
    3909  ########################################################################
    3910
    3911 -class Config(object):
    3912
    3913     ######################
    3914     # Class documentation
    3915     ######################
    3916
    3917     """
    3918     Class representing a Cedar Backup XML configuration document.
    3919
    3920     The C{Config} class is a Python object representation of a Cedar Backup XML
    3921     configuration file.  It is intended to be the only Python-language interface
    3922     to Cedar Backup configuration on disk for both Cedar Backup itself and for
    3923     external applications.
    3924
    3925     The object representation is two-way: XML data can be used to create a
    3926     C{Config} object, and then changes to the object can be propogated back to
    3927     disk.  A C{Config} object can even be used to create a configuration file
    3928     from scratch programmatically.
    3929
    3930     This class and the classes it is composed from often use Python's
    3931     C{property} construct to validate input and limit access to values.  Some
    3932     validations can only be done once a document is considered "complete"
    3933     (see module notes for more details).
    3934
    3935     Assignments to the various instance variables must match the expected
    3936     type, i.e. C{reference} must be a C{ReferenceConfig}.  The internal check
    3937     uses the built-in C{isinstance} function, so it should be OK to use
    3938     subclasses if you want to.
    3939
    3940     If an instance variable is not set, its value will be C{None}.  When an
    3941     object is initialized without using an XML document, all of the values
    3942     will be C{None}.  Even when an object is initialized using XML, some of
    3943     the values might be C{None} because not every section is required.
    3944
    3945     @note: Lists within this class are "unordered" for equality comparisons.
    3946
    3947     @sort: __init__, __repr__, __str__, __cmp__, extractXml, validate,
    3948            reference, extensions, options, collect, stage, store, purge,
    3949            _getReference, _setReference, _getExtensions, _setExtensions,
    3950            _getOptions, _setOptions, _getPeers, _setPeers, _getCollect,
    3951            _setCollect, _getStage, _setStage, _getStore, _setStore,
    3952            _getPurge, _setPurge
    3953     """
    3954
    3955     ##############
    3956     # Constructor
    3957     ##############
    3958
    3959 -   def __init__(self, xmlData=None, xmlPath=None, validate=True):
    3960        """
    3961        Initializes a configuration object.
    3962
    3963        If you initialize the object without passing either C{xmlData} or
    3964        C{xmlPath}, then configuration will be empty and will be invalid until it
    3965        is filled in properly.
    3966
    3967        No reference to the original XML data or original path is saved off by
    3968        this class.  Once the data has been parsed (successfully or not) this
    3969        original information is discarded.
    3970
    3971        Unless the C{validate} argument is C{False}, the L{Config.validate}
    3972        method will be called (with its default arguments) against configuration
    3973        after successfully parsing any passed-in XML.  Keep in mind that even if
    3974        C{validate} is C{False}, it might not be possible to parse the passed-in
    3975        XML document if lower-level validations fail.
    3976
    3977        @note: It is strongly suggested that the C{validate} option always be set
    3978        to C{True} (the default) unless there is a specific need to read in
    3979        invalid configuration from disk.
    3980
    3981        @param xmlData: XML data representing configuration.
    3982        @type xmlData: String data.
    3983
    3984        @param xmlPath: Path to an XML file on disk.
    3985        @type xmlPath: Absolute path to a file on disk.
    3986
    3987        @param validate: Validate the document after parsing it.
    3988        @type validate: Boolean true/false.
    3989
    3990        @raise ValueError: If both C{xmlData} and C{xmlPath} are passed-in.
    3991        @raise ValueError: If the XML data in C{xmlData} or C{xmlPath} cannot be parsed.
    3992        @raise ValueError: If the parsed configuration document is not valid.
    3993        """
    3994        self._reference = None
    3995        self._extensions = None
    3996        self._options = None
    3997        self._peers = None
    3998        self._collect = None
    3999        self._stage = None
    4000        self._store = None
    4001        self._purge = None
    4002        self.reference = None
    4003        self.extensions = None
    4004        self.options = None
    4005        self.peers = None
    4006        self.collect = None
    4007        self.stage = None
    4008        self.store = None
    4009        self.purge = None
    4010        if xmlData is not None and xmlPath is not None:
    4011           raise ValueError("Use either xmlData or xmlPath, but not both.")
    4012        if xmlData is not None:
    4013           self._parseXmlData(xmlData)
    4014           if validate:
    4015              self.validate()
    4016        elif xmlPath is not None:
    4017           xmlData = open(xmlPath).read()
    4018           self._parseXmlData(xmlData)
    4019           if validate:
    4020              self.validate()
    4021
    4022
    4023     #########################
    4024     # String representations
    4025     #########################
    4026
    4027 -   def __repr__(self):
    4028        """
    4029        Official string representation for class instance.
    4030        """
    4031        return "Config(%s, %s, %s, %s, %s, %s, %s, %s)" % (self.reference, self.extensions, self.options,
    4032                                                           self.peers, self.collect, self.stage, self.store,
    4033                                                           self.purge)
    4034
    4035 -   def __str__(self):
    4036        """
    4037        Informal string representation for class instance.
    4038        """
    4039        return self.__repr__()
    4040
    4041
    4042     #############################
    4043     # Standard comparison method
    4044     #############################
    4045
    4046 -   def __cmp__(self, other):
    4047        """
    4048        Definition of equals operator for this class.
    4049        Lists within this class are "unordered" for equality comparisons.
    4050        @param other: Other object to compare to.
    4051        @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other.
    4052        """
    4053        if other is None:
    4054           return 1
    4055        if self.reference != other.reference:
    4056           if self.reference < other.reference:
    4057              return -1
    4058           else:
    4059              return 1
    4060        if self.extensions != other.extensions:
    4061           if self.extensions < other.extensions:
    4062              return -1
    4063           else:
    4064              return 1
    4065        if self.options != other.options:
    4066           if self.options < other.options:
    4067              return -1
    4068           else:
    4069              return 1
    4070        if self.peers != other.peers:
    4071           if self.peers < other.peers:
    4072              return -1
    4073           else:
    4074              return 1
    4075        if self.collect != other.collect:
    4076           if self.collect < other.collect:
    4077              return -1
    4078           else:
    4079              return 1
    4080        if self.stage != other.stage:
    4081           if self.stage < other.stage:
    4082              return -1
    4083           else:
    4084              return 1
    4085        if self.store != other.store:
    4086           if self.store < other.store:
    4087              return -1
    4088           else:
    4089              return 1
    4090        if self.purge != other.purge:
    4091           if self.purge < other.purge:
    4092              return -1
    4093           else:
    4094              return 1
    4095        return 0
    4096
    4097
    4098     #############
    4099     # Properties
    4100     #############
    4101
    4102 -   def _setReference(self, value):
    4103        """
    4104        Property target used to set the reference configuration value.
    4105        If not C{None}, the value must be a C{ReferenceConfig} object.
    4106        @raise ValueError: If the value is not a C{ReferenceConfig}
    4107        """
    4108        if value is None:
    4109           self._reference = None
    4110        else:
    4111           if not isinstance(value, ReferenceConfig):
    4112              raise ValueError("Value must be a C{ReferenceConfig} object.")
    4113           self._reference = value
    4114
    4115 -   def _getReference(self):
    4116        """
    4117        Property target used to get the reference configuration value.
    4118        """
    4119        return self._reference
    4120
    4121 -   def _setExtensions(self, value):
    4122        """
    4123        Property target used to set the extensions configuration value.
    4124        If not C{None}, the value must be a C{ExtensionsConfig} object.
    4125        @raise ValueError: If the value is not a C{ExtensionsConfig}
    4126        """
    4127        if value is None:
    4128           self._extensions = None
    4129        else:
    4130           if not isinstance(value, ExtensionsConfig):
    4131              raise ValueError("Value must be a C{ExtensionsConfig} object.")
    4132           self._extensions = value
    4133
    4134 -   def _getExtensions(self):
    4135        """
    4136        Property target used to get the extensions configuration value.
    4137        """
    4138        return self._extensions
    4139
    4140 -   def _setOptions(self, value):
    4141        """
    4142        Property target used to set the options configuration value.
    4143        If not C{None}, the value must be an C{OptionsConfig} object.
    4144        @raise ValueError: If the value is not a C{OptionsConfig}
    4145        """
    4146        if value is None:
    4147           self._options = None
    4148        else:
    4149           if not isinstance(value, OptionsConfig):
    4150              raise ValueError("Value must be a C{OptionsConfig} object.")
    4151           self._options = value
    4152
    4153 -   def _getOptions(self):
    4154        """
    4155        Property target used to get the options configuration value.
    4156        """
    4157        return self._options
    4158
    4159 -   def _setPeers(self, value):
    4160        """
    4161        Property target used to set the peers configuration value.
    4162        If not C{None}, the value must be an C{PeersConfig} object.
    4163        @raise ValueError: If the value is not a C{PeersConfig}
    4164        """
    4165        if value is None:
    4166           self._peers = None
    4167        else:
    4168           if not isinstance(value, PeersConfig):
    4169              raise ValueError("Value must be a C{PeersConfig} object.")
    4170           self._peers = value
    4171
    4172 -   def _getPeers(self):
    4173        """
    4174        Property target used to get the peers configuration value.
    4175        """
    4176        return self._peers
    4177
    4178 -   def _setCollect(self, value):
    4179        """
    4180        Property target used to set the collect configuration value.
    4181        If not C{None}, the value must be a C{CollectConfig} object.
    4182        @raise ValueError: If the value is not a C{CollectConfig}
    4183        """
    4184        if value is None:
    4185           self._collect = None
    4186        else:
    4187           if not isinstance(value, CollectConfig):
    4188              raise ValueError("Value must be a C{CollectConfig} object.")
    4189           self._collect = value
    4190
    4191 -   def _getCollect(self):
    4192        """
    4193        Property target used to get the collect configuration value.
    4194        """
    4195        return self._collect
    4196
    4197 -   def _setStage(self, value):
    4198        """
    4199        Property target used to set the stage configuration value.
    4200        If not C{None}, the value must be a C{StageConfig} object.
    4201        @raise ValueError: If the value is not a C{StageConfig}
    4202        """
    4203        if value is None:
    4204           self._stage = None
    4205        else:
    4206           if not isinstance(value, StageConfig):
    4207              raise ValueError("Value must be a C{StageConfig} object.")
    4208           self._stage = value
    4209
    4210 -   def _getStage(self):
    4211        """
    4212        Property target used to get the stage configuration value.
    4213        """
    4214        return self._stage
    4215
    4216 -   def _setStore(self, value):
    4217        """
    4218        Property target used to set the store configuration value.
    4219        If not C{None}, the value must be a C{StoreConfig} object.
    4220        @raise ValueError: If the value is not a C{StoreConfig}
    4221        """
    4222        if value is None:
    4223           self._store = None
    4224        else:
    4225           if not isinstance(value, StoreConfig):
    4226              raise ValueError("Value must be a C{StoreConfig} object.")
    4227           self._store = value
    4228
    4229 -   def _getStore(self):
    4230        """
    4231        Property target used to get the store configuration value.
    4232        """
    4233        return self._store
    4234
    4235 -   def _setPurge(self, value):
    4236        """
    4237        Property target used to set the purge configuration value.
    4238        If not C{None}, the value must be a C{PurgeConfig} object.
    4239        @raise ValueError: If the value is not a C{PurgeConfig}
    4240        """
    4241        if value is None:
    4242           self._purge = None
    4243        else:
    4244           if not isinstance(value, PurgeConfig):
    4245              raise ValueError("Value must be a C{PurgeConfig} object.")
    4246           self._purge = value
    4247
    4248 -   def _getPurge(self):
    4249        """
    4250        Property target used to get the purge configuration value.
    4251        """
    4252        return self._purge
    4253
    4254     reference = property(_getReference, _setReference, None, "Reference configuration in terms of a C{ReferenceConfig} object.")
    4255     extensions = property(_getExtensions, _setExtensions, None, "Extensions configuration in terms of a C{ExtensionsConfig} object.")
    4256     options = property(_getOptions, _setOptions, None, "Options configuration in terms of a C{OptionsConfig} object.")
    4257     peers = property(_getPeers, _setPeers, None, "Peers configuration in terms of a C{PeersConfig} object.")
    4258     collect = property(_getCollect, _setCollect, None, "Collect configuration in terms of a C{CollectConfig} object.")
    4259     stage = property(_getStage, _setStage, None, "Stage configuration in terms of a C{StageConfig} object.")
    4260     store = property(_getStore, _setStore, None, "Store configuration in terms of a C{StoreConfig} object.")
    4261     purge = property(_getPurge, _setPurge, None, "Purge configuration in terms of a C{PurgeConfig} object.")
    4262
    4263
    4264     #################
    4265     # Public methods
    4266     #################
    4267
    4268 -   def extractXml(self, xmlPath=None, validate=True):
    4269        """
    4270        Extracts configuration into an XML document.
    4271
    4272        If C{xmlPath} is not provided, then the XML document will be returned as
    4273        a string.  If C{xmlPath} is provided, then the XML document will be written
    4274        to the file and C{None} will be returned.
    4275
    4276        Unless the C{validate} parameter is C{False}, the L{Config.validate}
    4277        method will be called (with its default arguments) against the
    4278        configuration before extracting the XML.  If configuration is not valid,
    4279        then an XML document will not be extracted.
    4280
    4281        @note: It is strongly suggested that the C{validate} option always be set
    4282        to C{True} (the default) unless there is a specific need to write an
    4283        invalid configuration file to disk.
    4284
    4285        @param xmlPath: Path to an XML file to create on disk.
    4286        @type xmlPath: Absolute path to a file.
    4287
    4288        @param validate: Validate the document before extracting it.
    4289        @type validate: Boolean true/false.
    4290
    4291        @return: XML string data or C{None} as described above.
    4292
    4293        @raise ValueError: If configuration within the object is not valid.
    4294        @raise IOError: If there is an error writing to the file.
    4295        @raise OSError: If there is an error writing to the file.
    4296        """
    4297        if validate:
    4298           self.validate()
    4299        xmlData = self._extractXml()
    4300        if xmlPath is not None:
    4301           open(xmlPath, "w").write(xmlData)
    4302           return None
    4303        else:
    4304           return xmlData
    4305
    4306 -   def validate(self, requireOneAction=True, requireReference=False, requireExtensions=False, requireOptions=True,
    4307                  requireCollect=False, requireStage=False, requireStore=False, requirePurge=False, requirePeers=False):
    4308        """
    4309        Validates configuration represented by the object.
    4310
    4311        This method encapsulates all of the validations that should apply to a
    4312        fully "complete" document but are not already taken care of by earlier
    4313        validations.  It also provides some extra convenience functionality which
    4314        might be useful to some people.  The process of validation is laid out in
    4315        the I{Validation} section in the class notes (above).
    4316
    4317        @param requireOneAction: Require at least one of the collect, stage, store or purge sections.
    4318        @param requireReference: Require the reference section.
    4319        @param requireExtensions: Require the extensions section.
    4320        @param requireOptions: Require the options section.
    4321        @param requirePeers: Require the peers section.
    4322        @param requireCollect: Require the collect section.
    4323        @param requireStage: Require the stage section.
    4324        @param requireStore: Require the store section.
    4325        @param requirePurge: Require the purge section.
    4326
    4327        @raise ValueError: If one of the validations fails.
    4328        """
    4329        if requireOneAction and (self.collect, self.stage, self.store, self.purge) == (None, None, None, None):
    4330           raise ValueError("At least one of the collect, stage, store and purge sections is required.")
    4331        if requireReference and self.reference is None:
    4332           raise ValueError("The reference is section is required.")
    4333        if requireExtensions and self.extensions is None:
    4334           raise ValueError("The extensions is section is required.")
    4335        if requireOptions and self.options is None:
    4336           raise ValueError("The options is section is required.")
    4337        if requirePeers and self.peers is None:
    4338           raise ValueError("The peers is section is required.")
    4339        if requireCollect and self.collect is None:
    4340           raise ValueError("The collect is section is required.")
    4341        if requireStage and self.stage is None:
    4342           raise ValueError("The stage is section is required.")
    4343        if requireStore and self.store is None:
    4344           raise ValueError("The store is section is required.")
    4345        if requirePurge and self.purge is None:
    4346           raise ValueError("The purge is section is required.")
    4347        self._validateContents()
    4348
    4349
    4350     #####################################
    4351     # High-level methods for parsing XML
    4352     #####################################
    4353
    4354 -   def _parseXmlData(self, xmlData):
    4355        """
    4356        Internal method to parse an XML string into the object.
    4357
    4358        This method parses the XML document into a DOM tree (C{xmlDom}) and then
    4359        calls individual static methods to parse each of the individual
    4360        configuration sections.
    4361
    4362        Most of the validation we do here has to do with whether the document can
    4363        be parsed and whether any values which exist are valid.  We don't do much
    4364        validation as to whether required elements actually exist unless we have
    4365        to to make sense of the document (instead, that's the job of the
    4366        L{validate} method).
    4367
    4368        @param xmlData: XML data to be parsed
    4369        @type xmlData: String data
    4370
    4371        @raise ValueError: If the XML cannot be successfully parsed.
    4372        """
    4373        (xmlDom, parentNode) = createInputDom(xmlData)
    4374        self._reference = Config._parseReference(parentNode)
    4375        self._extensions = Config._parseExtensions(parentNode)
    4376        self._options = Config._parseOptions(parentNode)
    4377        self._peers = Config._parsePeers(parentNode)
    4378        self._collect = Config._parseCollect(parentNode)
    4379        self._stage = Config._parseStage(parentNode)
    4380        self._store = Config._parseStore(parentNode)
    4381        self._purge = Config._parsePurge(parentNode)
    4382
    4383     @staticmethod
    4384 -   def _parseReference(parentNode):
    4385        """
    4386        Parses a reference configuration section.
    4387
    4388        We read the following fields::
    4389
    4390           author         //cb_config/reference/author
    4391           revision       //cb_config/reference/revision
    4392           description    //cb_config/reference/description
    4393           generator      //cb_config/reference/generator
    4394
    4395        @param parentNode: Parent node to search beneath.
    4396
    4397        @return: C{ReferenceConfig} object or C{None} if the section does not exist.
    4398        @raise ValueError: If some filled-in value is invalid.
    4399        """
    4400        reference = None
    4401        sectionNode = readFirstChild(parentNode, "reference")
    4402        if sectionNode is not None:
    4403           reference = ReferenceConfig()
    4404           reference.author = readString(sectionNode, "author")
    4405           reference.revision = readString(sectionNode, "revision")
    4406           reference.description = readString(sectionNode, "description")
    4407           reference.generator = readString(sectionNode, "generator")
    4408        return reference
    4409
    4410     @staticmethod
    4411 -   def _parseExtensions(parentNode):
    4412        """
    4413        Parses an extensions configuration section.
    4414
    4415        We read the following fields::
    4416
    4417           orderMode            //cb_config/extensions/order_mode
    4418
    4419        We also read groups of the following items, one list element per item::
    4420
    4421           name                 //cb_config/extensions/action/name
    4422           module               //cb_config/extensions/action/module
    4423           function             //cb_config/extensions/action/function
    4424           index                //cb_config/extensions/action/index
    4425           dependencies         //cb_config/extensions/action/depends
    4426
    4427        The extended actions are parsed by L{_parseExtendedActions}.
    4428
    4429        @param parentNode: Parent node to search beneath.
    4430
    4431        @return: C{ExtensionsConfig} object or C{None} if the section does not exist.
    4432        @raise ValueError: If some filled-in value is invalid.
    4433        """
    4434        extensions = None
    4435        sectionNode = readFirstChild(parentNode, "extensions")
    4436        if sectionNode is not None:
    4437           extensions = ExtensionsConfig()
    4438           extensions.orderMode = readString(sectionNode, "order_mode")
    4439           extensions.actions = Config._parseExtendedActions(sectionNode)
    4440        return extensions
    4441
    4442     @staticmethod
    4443 -   def _parseOptions(parentNode):
    4444        """
    4445        Parses a options configuration section.
    4446
    4447        We read the following fields::
    4448
    4449           startingDay    //cb_config/options/starting_day
    4450           workingDir     //cb_config/options/working_dir
    4451           backupUser     //cb_config/options/backup_user
    4452           backupGroup    //cb_config/options/backup_group
    4453           rcpCommand     //cb_config/options/rcp_command
    4454           rshCommand     //cb_config/options/rsh_command
    4455           cbackCommand   //cb_config/options/cback_command
    4456           managedActions //cb_config/options/managed_actions
    4457
    4458        The list of managed actions is a comma-separated list of action names.
    4459
    4460        We also read groups of the following items, one list element per
    4461        item::
    4462
    4463           overrides      //cb_config/options/override
    4464           hooks          //cb_config/options/hook
    4465
    4466        The overrides are parsed by L{_parseOverrides} and the hooks are parsed
    4467        by L{_parseHooks}.
    4468
    4469        @param parentNode: Parent node to search beneath.
    4470
    4471        @return: C{OptionsConfig} object or C{None} if the section does not exist.
    4472        @raise ValueError: If some filled-in value is invalid.
    4473        """
    4474        options = None
    4475        sectionNode = readFirstChild(parentNode, "options")
    4476        if sectionNode is not None:
    4477           options = OptionsConfig()
    4478           options.startingDay = readString(sectionNode, "starting_day")
    4479           options.workingDir = readString(sectionNode, "working_dir")
    4480           options.backupUser = readString(sectionNode, "backup_user")
    4481           options.backupGroup = readString(sectionNode, "backup_group")
    4482           options.rcpCommand = readString(sectionNode, "rcp_command")
    4483           options.rshCommand = readString(sectionNode, "rsh_command")
    4484           options.cbackCommand = readString(sectionNode, "cback_command")
    4485           options.overrides = Config._parseOverrides(sectionNode)
    4486           options.hooks = Config._parseHooks(sectionNode)
    4487           managedActions = readString(sectionNode, "managed_actions")
    4488           options.managedActions = parseCommaSeparatedString(managedActions)
    4489        return options
    4490
    4491     @staticmethod
    4492 -   def _parsePeers(parentNode):
    4493        """
    4494        Parses a peers configuration section.
    4495
    4496        We read groups of the following items, one list element per
    4497        item::
    4498
    4499           localPeers     //cb_config/stage/peer
    4500           remotePeers    //cb_config/stage/peer
    4501
    4502        The individual peer entries are parsed by L{_parsePeerList}.
    4503
    4504        @param parentNode: Parent node to search beneath.
    4505
    4506        @return: C{StageConfig} object or C{None} if the section does not exist.
    4507        @raise ValueError: If some filled-in value is invalid.
    4508        """
    4509        peers = None
    4510        sectionNode = readFirstChild(parentNode, "peers")
    4511        if sectionNode is not None:
    4512           peers = PeersConfig()
    4513           (peers.localPeers, peers.remotePeers) = Config._parsePeerList(sectionNode)
    4514        return peers
    4515
    4516     @staticmethod
    4517 -   def _parseCollect(parentNode):
    4518        """
    4519        Parses a collect configuration section.
    4520
    4521        We read the following individual fields::
    4522
    4523           targetDir            //cb_config/collect/collect_dir
    4524           collectMode          //cb_config/collect/collect_mode
    4525           archiveMode          //cb_config/collect/archive_mode
    4526           ignoreFile           //cb_config/collect/ignore_file
    4527
    4528        We also read groups of the following items, one list element per
    4529        item::
    4530
    4531           absoluteExcludePaths //cb_config/collect/exclude/abs_path
    4532           excludePatterns      //cb_config/collect/exclude/pattern
    4533           collectFiles         //cb_config/collect/file
    4534           collectDirs          //cb_config/collect/dir
    4535
    4536        The exclusions are parsed by L{_parseExclusions}, the collect files are
    4537        parsed by L{_parseCollectFiles}, and the directories are parsed by
    4538        L{_parseCollectDirs}.
    4539
    4540        @param parentNode: Parent node to search beneath.
    4541
    4542        @return: C{CollectConfig} object or C{None} if the section does not exist.
    4543        @raise ValueError: If some filled-in value is invalid.
    4544        """
    4545        collect = None
    4546        sectionNode = readFirstChild(parentNode, "collect")
    4547        if sectionNode is not None:
    4548           collect = CollectConfig()
    4549           collect.targetDir = readString(sectionNode, "collect_dir")
    4550           collect.collectMode = readString(sectionNode, "collect_mode")
    4551           collect.archiveMode = readString(sectionNode, "archive_mode")
    4552           collect.ignoreFile = readString(sectionNode, "ignore_file")
    4553           (collect.absoluteExcludePaths, unused, collect.excludePatterns) = Config._parseExclusions(sectionNode)
    4554           collect.collectFiles = Config._parseCollectFiles(sectionNode)
    4555           collect.collectDirs = Config._parseCollectDirs(sectionNode)
    4556        return collect
    4557
    4558     @staticmethod
    4559 -   def _parseStage(parentNode):
    4560        """
    4561        Parses a stage configuration section.
    4562
    4563        We read the following individual fields::
    4564
    4565           targetDir      //cb_config/stage/staging_dir
    4566
    4567        We also read groups of the following items, one list element per
    4568        item::
    4569
    4570           localPeers     //cb_config/stage/peer
    4571           remotePeers    //cb_config/stage/peer
    4572
    4573        The individual peer entries are parsed by L{_parsePeerList}.
    4574
    4575        @param parentNode: Parent node to search beneath.
    4576
    4577        @return: C{StageConfig} object or C{None} if the section does not exist.
    4578        @raise ValueError: If some filled-in value is invalid.
    4579        """
    4580        stage = None
    4581        sectionNode = readFirstChild(parentNode, "stage")
    4582        if sectionNode is not None:
    4583           stage = StageConfig()
    4584           stage.targetDir = readString(sectionNode, "staging_dir")
    4585           (stage.localPeers, stage.remotePeers) = Config._parsePeerList(sectionNode)
    4586        return stage
    4587
    4588     @staticmethod
    4589 -   def _parseStore(parentNode):
    4590        """
    4591        Parses a store configuration section.
    4592
    4593        We read the following fields::
    4594
    4595           sourceDir         //cb_config/store/source_dir
    4596           mediaType         //cb_config/store/media_type
    4597           deviceType        //cb_config/store/device_type
    4598           devicePath        //cb_config/store/target_device
    4599           deviceScsiId      //cb_config/store/target_scsi_id
    4600           driveSpeed        //cb_config/store/drive_speed
    4601           checkData         //cb_config/store/check_data
    4602           checkMedia        //cb_config/store/check_media
    4603           warnMidnite       //cb_config/store/warn_midnite
    4604           noEject           //cb_config/store/no_eject
    4605
    4606        Blanking behavior configuration is parsed by the C{_parseBlankBehavior}
    4607        method.
    4608
    4609        @param parentNode: Parent node to search beneath.
    4610
    4611        @return: C{StoreConfig} object or C{None} if the section does not exist.
    4612        @raise ValueError: If some filled-in value is invalid.
    4613        """
    4614        store = None
    4615        sectionNode = readFirstChild(parentNode, "store")
    4616        if sectionNode is not None:
    4617           store = StoreConfig()
    4618           store.sourceDir = readString(sectionNode,  "source_dir")
    4619           store.mediaType = readString(sectionNode,  "media_type")
    4620           store.deviceType = readString(sectionNode,  "device_type")
    4621           store.devicePath = readString(sectionNode,  "target_device")
    4622           store.deviceScsiId = readString(sectionNode,  "target_scsi_id")
    4623           store.driveSpeed = readInteger(sectionNode, "drive_speed")
    4624           store.checkData = readBoolean(sectionNode, "check_data")
    4625           store.checkMedia = readBoolean(sectionNode, "check_media")
    4626           store.warnMidnite = readBoolean(sectionNode, "warn_midnite")
    4627           store.noEject = readBoolean(sectionNode, "no_eject")
    4628           store.blankBehavior = Config._parseBlankBehavior(sectionNode)
    4629           store.refreshMediaDelay = readInteger(sectionNode, "refresh_media_delay")
    4630           store.ejectDelay = readInteger(sectionNode, "eject_delay")
    4631        return store
    4632
    4633     @staticmethod
    4634 -   def _parsePurge(parentNode):
    4635        """
    4636        Parses a purge configuration section.
    4637
    4638        We read groups of the following items, one list element per
    4639        item::
    4640
    4641           purgeDirs     //cb_config/purge/dir
    4642
    4643        The individual directory entries are parsed by L{_parsePurgeDirs}.
    4644
    4645        @param parentNode: Parent node to search beneath.
    4646
    4647        @return: C{PurgeConfig} object or C{None} if the section does not exist.
    4648        @raise ValueError: If some filled-in value is invalid.
    4649        """
    4650        purge = None
    4651        sectionNode = readFirstChild(parentNode, "purge")
    4652        if sectionNode is not None:
    4653           purge = PurgeConfig()
    4654           purge.purgeDirs = Config._parsePurgeDirs(sectionNode)
    4655        return purge
    4656
    4657     @staticmethod
    4658 -   def _parseExtendedActions(parentNode):
    4659        """
    4660        Reads extended actions data from immediately beneath the parent.
    4661
    4662        We read the following individual fields from each extended action::
    4663
    4664           name           name
    4665           module         module
    4666           function       function
    4667           index          index
    4668           dependencies   depends
    4669
    4670        Dependency information is parsed by the C{_parseDependencies} method.
    4671
    4672        @param parentNode: Parent node to search beneath.
    4673
    4674        @return: List of extended actions.
    4675        @raise ValueError: If the data at the location can't be read
    4676        """
    4677        lst = []
    4678        for entry in readChildren(parentNode, "action"):
    4679           if isElement(entry):
    4680              action = ExtendedAction()
    4681              action.name = readString(entry, "name")
    4682              action.module = readString(entry, "module")
    4683              action.function = readString(entry, "function")
    4684              action.index = readInteger(entry, "index")
    4685              action.dependencies = Config._parseDependencies(entry)
    4686              lst.append(action)
    4687        if lst == []:
    4688           lst = None
    4689        return lst
    4690
    4691     @staticmethod
    4692 -   def _parseExclusions(parentNode):
    4693        """
    4694        Reads exclusions data from immediately beneath the parent.
    4695
    4696        We read groups of the following items, one list element per item::
    4697
    4698           absolute    exclude/abs_path
    4699           relative    exclude/rel_path
    4700           patterns    exclude/pattern
    4701
    4702        If there are none of some pattern (i.e. no relative path items) then
    4703        C{None} will be returned for that item in the tuple.
    4704
    4705        This method can be used to parse exclusions on both the collect
    4706        configuration level and on the collect directory level within collect
    4707        configuration.
    4708
    4709        @param parentNode: Parent node to search beneath.
    4710
    4711        @return: Tuple of (absolute, relative, patterns) exclusions.
    4712        """
    4713        sectionNode = readFirstChild(parentNode, "exclude")
    4714        if sectionNode is None:
    4715           return (None, None, None)
    4716        else:
    4717           absolute = readStringList(sectionNode, "abs_path")
    4718           relative = readStringList(sectionNode, "rel_path")
    4719           patterns = readStringList(sectionNode, "pattern")
    4720           return (absolute, relative, patterns)
    4721
    4722     @staticmethod
    4723 -   def _parseOverrides(parentNode):
    4724        """
    4725        Reads a list of C{CommandOverride} objects from immediately beneath the parent.
    4726
    4727        We read the following individual fields::
    4728
    4729           command                 command
    4730           absolutePath            abs_path
    4731
    4732        @param parentNode: Parent node to search beneath.
    4733
    4734        @return: List of C{CommandOverride} objects or C{None} if none are found.
    4735        @raise ValueError: If some filled-in value is invalid.
    4736        """
    4737        lst = []
    4738        for entry in readChildren(parentNode, "override"):
    4739           if isElement(entry):
    4740              override = CommandOverride()
    4741              override.command = readString(entry, "command")
    4742              override.absolutePath = readString(entry, "abs_path")
    4743              lst.append(override)
    4744        if lst == []:
    4745           lst = None
    4746        return lst
    4747
    4748     @staticmethod
    4749     # pylint: disable=R0204
    4750 -   def _parseHooks(parentNode):
    4751        """
    4752        Reads a list of C{ActionHook} objects from immediately beneath the parent.
    4753
    4754        We read the following individual fields::
    4755
    4756           action                  action
    4757           command                 command
    4758
    4759        @param parentNode: Parent node to search beneath.
    4760
    4761        @return: List of C{ActionHook} objects or C{None} if none are found.
    4762        @raise ValueError: If some filled-in value is invalid.
    4763        """
    4764        lst = []
    4765        for entry in readChildren(parentNode, "pre_action_hook"):
    4766           if isElement(entry):
    4767              hook = PreActionHook()
    4768              hook.action = readString(entry, "action")
    4769              hook.command = readString(entry, "command")
    4770              lst.append(hook)
    4771        for entry in readChildren(parentNode, "post_action_hook"):
    4772           if isElement(entry):
    4773              hook = PostActionHook()
    4774              hook.action = readString(entry, "action")
    4775              hook.command = readString(entry, "command")
    4776              lst.append(hook)
    4777        if lst == []:
    4778           lst = None
    4779        return lst
    4780
    4781     @staticmethod
    4782 -   def _parseCollectFiles(parentNode):
    4783        """
    4784        Reads a list of C{CollectFile} objects from immediately beneath the parent.
    4785
    4786        We read the following individual fields::
    4787
    4788           absolutePath            abs_path
    4789           collectMode             mode I{or} collect_mode
    4790           archiveMode             archive_mode
    4791
    4792        The collect mode is a special case.  Just a C{mode} tag is accepted, but
    4793        we prefer C{collect_mode} for consistency with the rest of the config
    4794        file and to avoid confusion with the archive mode.  If both are provided,
    4795        only C{mode} will be used.
    4796
    4797        @param parentNode: Parent node to search beneath.
    4798
    4799        @return: List of C{CollectFile} objects or C{None} if none are found.
    4800        @raise ValueError: If some filled-in value is invalid.
    4801        """
    4802        lst = []
    4803        for entry in readChildren(parentNode, "file"):
    4804           if isElement(entry):
    4805              cfile = CollectFile()
    4806              cfile.absolutePath = readString(entry, "abs_path")
    4807              cfile.collectMode = readString(entry, "mode")
    4808              if cfile.collectMode is None:
    4809                 cfile.collectMode = readString(entry, "collect_mode")
    4810              cfile.archiveMode = readString(entry, "archive_mode")
    4811              lst.append(cfile)
    4812        if lst == []:
    4813           lst = None
    4814        return lst
    4815
    4816     @staticmethod
    4817 -   def _parseCollectDirs(parentNode):
    4818        """
    4819        Reads a list of C{CollectDir} objects from immediately beneath the parent.
    4820
    4821        We read the following individual fields::
    4822
    4823           absolutePath            abs_path
    4824           collectMode             mode I{or} collect_mode
    4825           archiveMode             archive_mode
    4826           ignoreFile              ignore_file
    4827           linkDepth               link_depth
    4828           dereference             dereference
    4829           recursionLevel          recursion_level
    4830
    4831        The collect mode is a special case.  Just a C{mode} tag is accepted for
    4832        backwards compatibility, but we prefer C{collect_mode} for consistency
    4833        with the rest of the config file and to avoid confusion with the archive
    4834        mode.  If both are provided, only C{mode} will be used.
    4835
    4836        We also read groups of the following items, one list element per
    4837        item::
    4838
    4839           absoluteExcludePaths    exclude/abs_path
    4840           relativeExcludePaths    exclude/rel_path
    4841           excludePatterns         exclude/pattern
    4842
    4843        The exclusions are parsed by L{_parseExclusions}.
    4844
    4845        @param parentNode: Parent node to search beneath.
    4846
    4847        @return: List of C{CollectDir} objects or C{None} if none are found.
    4848        @raise ValueError: If some filled-in value is invalid.
    4849        """
    4850        lst = []
    4851        for entry in readChildren(parentNode, "dir"):
    4852           if isElement(entry):
    4853              cdir = CollectDir()
    4854              cdir.absolutePath = readString(entry, "abs_path")
    4855              cdir.collectMode = readString(entry, "mode")
    4856              if cdir.collectMode is None:
    4857                 cdir.collectMode = readString(entry, "collect_mode")
    4858              cdir.archiveMode = readString(entry, "archive_mode")
    4859              cdir.ignoreFile = readString(entry, "ignore_file")
    4860              cdir.linkDepth = readInteger(entry, "link_depth")
    4861              cdir.dereference = readBoolean(entry, "dereference")
    4862              cdir.recursionLevel = readInteger(entry, "recursion_level")
    4863              (cdir.absoluteExcludePaths, cdir.relativeExcludePaths, cdir.excludePatterns) = Config._parseExclusions(entry)
    4864              lst.append(cdir)
    4865        if lst == []:
    4866           lst = None
    4867        return lst
    4868
    4869     @staticmethod
    4870 -   def _parsePurgeDirs(parentNode):
    4871        """
    4872        Reads a list of C{PurgeDir} objects from immediately beneath the parent.
    4873
    4874        We read the following individual fields::
    4875
    4876           absolutePath            <baseExpr>/abs_path
    4877           retainDays              <baseExpr>/retain_days
    4878
    4879        @param parentNode: Parent node to search beneath.
    4880
    4881        @return: List of C{PurgeDir} objects or C{None} if none are found.
    4882        @raise ValueError: If the data at the location can't be read
    4883        """
    4884        lst = []
    4885        for entry in readChildren(parentNode, "dir"):
    4886           if isElement(entry):
    4887              cdir = PurgeDir()
    4888              cdir.absolutePath = readString(entry, "abs_path")
    4889              cdir.retainDays = readInteger(entry, "retain_days")
    4890              lst.append(cdir)
    4891        if lst == []:
    4892           lst = None
    4893        return lst
    4894
    4895     @staticmethod
    4896 -   def _parsePeerList(parentNode):
    4897        """
    4898        Reads remote and local peer data from immediately beneath the parent.
    4899
    4900        We read the following individual fields for both remote
    4901        and local peers::
    4902
    4903           name        name
    4904           collectDir  collect_dir
    4905
    4906        We also read the following individual fields for remote peers
    4907        only::
    4908
    4909           remoteUser     backup_user
    4910           rcpCommand     rcp_command
    4911           rshCommand     rsh_command
    4912           cbackCommand   cback_command
    4913           managed        managed
    4914           managedActions managed_actions
    4915
    4916        Additionally, the value in the C{type} field is used to determine whether
    4917        this entry is a remote peer.  If the type is C{"remote"}, it's a remote
    4918        peer, and if the type is C{"local"}, it's a remote peer.
    4919
    4920        If there are none of one type of peer (i.e. no local peers) then C{None}
    4921        will be returned for that item in the tuple.
    4922
    4923        @param parentNode: Parent node to search beneath.
    4924
    4925        @return: Tuple of (local, remote) peer lists.
    4926        @raise ValueError: If the data at the location can't be read
    4927        """
    4928        localPeers = []
    4929        remotePeers = []
    4930        for entry in readChildren(parentNode, "peer"):
    4931           if isElement(entry):
    4932              peerType = readString(entry, "type")
    4933              if peerType == "local":
    4934                 localPeer = LocalPeer()
    4935                 localPeer.name = readString(entry, "name")
    4936                 localPeer.collectDir = readString(entry, "collect_dir")
    4937                 localPeer.ignoreFailureMode = readString(entry, "ignore_failures")
    4938                 localPeers.append(localPeer)
    4939              elif peerType == "remote":
    4940                 remotePeer = RemotePeer()
    4941                 remotePeer.name = readString(entry, "name")
    4942                 remotePeer.collectDir = readString(entry, "collect_dir")
    4943                 remotePeer.remoteUser = readString(entry, "backup_user")
    4944                 remotePeer.rcpCommand = readString(entry, "rcp_command")
    4945                 remotePeer.rshCommand = readString(entry, "rsh_command")
    4946                 remotePeer.cbackCommand = readString(entry, "cback_command")
    4947                 remotePeer.ignoreFailureMode = readString(entry, "ignore_failures")
    4948                 remotePeer.managed = readBoolean(entry, "managed")
    4949                 managedActions = readString(entry, "managed_actions")
    4950                 remotePeer.managedActions = parseCommaSeparatedString(managedActions)
    4951                 remotePeers.append(remotePeer)
    4952        if localPeers == []:
    4953           localPeers = None
    4954        if remotePeers == []:
    4955           remotePeers = None
    4956        return (localPeers, remotePeers)
    4957
    4958     @staticmethod
    4959 -   def _parseDependencies(parentNode):
    4960        """
    4961        Reads extended action dependency information from a parent node.
    4962
    4963        We read the following individual fields::
    4964
    4965           runBefore   depends/run_before
    4966           runAfter    depends/run_after
    4967
    4968        Each of these fields is a comma-separated list of action names.
    4969
    4970        The result is placed into an C{ActionDependencies} object.
    4971
    4972        If the dependencies parent node does not exist, C{None} will be returned.
    4973        Otherwise, an C{ActionDependencies} object will always be created, even
    4974        if it does not contain any actual dependencies in it.
    4975
    4976        @param parentNode: Parent node to search beneath.
    4977
    4978        @return: C{ActionDependencies} object or C{None}.
    4979        @raise ValueError: If the data at the location can't be read
    4980        """
    4981        sectionNode = readFirstChild(parentNode, "depends")
    4982        if sectionNode is None:
    4983           return None
    4984        else:
    4985           runBefore = readString(sectionNode, "run_before")
    4986           runAfter = readString(sectionNode, "run_after")
    4987           beforeList = parseCommaSeparatedString(runBefore)
    4988           afterList = parseCommaSeparatedString(runAfter)
    4989           return ActionDependencies(beforeList, afterList)
    4990
    4991     @staticmethod
    4992 -   def _parseBlankBehavior(parentNode):
    4993        """
    4994        Reads a single C{BlankBehavior} object from immediately beneath the parent.
    4995
    4996        We read the following individual fields::
    4997
    4998           blankMode     blank_behavior/mode
    4999           blankFactor   blank_behavior/factor
    5000
    5001        @param parentNode: Parent node to search beneath.
    5002
    5003        @return: C{BlankBehavior} object or C{None} if none if the section is not found
    5004        @raise ValueError: If some filled-in value is invalid.
    5005        """
    5006        blankBehavior = None
    5007        sectionNode = readFirstChild(parentNode, "blank_behavior")
    5008        if sectionNode is not None:
    5009           blankBehavior = BlankBehavior()
    5010           blankBehavior.blankMode = readString(sectionNode, "mode")
    5011           blankBehavior.blankFactor = readString(sectionNode, "factor")
    5012        return blankBehavior
    5013
    5014
    5015     ########################################
    5016     # High-level methods for generating XML
    5017     ########################################
    5018
    5019 -   def _extractXml(self):
    5020        """
    5021        Internal method to extract configuration into an XML string.
    5022
    5023        This method assumes that the internal L{validate} method has been called
    5024        prior to extracting the XML, if the caller cares.  No validation will be
    5025        done internally.
    5026
    5027        As a general rule, fields that are set to C{None} will be extracted into
    5028        the document as empty tags.  The same goes for container tags that are
    5029        filled based on lists - if the list is empty or C{None}, the container
    5030        tag will be empty.
    5031        """
    5032        (xmlDom, parentNode) = createOutputDom()
    5033        Config._addReference(xmlDom, parentNode, self.reference)
    5034        Config._addExtensions(xmlDom, parentNode, self.extensions)
    5035        Config._addOptions(xmlDom, parentNode, self.options)
    5036        Config._addPeers(xmlDom, parentNode, self.peers)
    5037        Config._addCollect(xmlDom, parentNode, self.collect)
    5038        Config._addStage(xmlDom, parentNode, self.stage)
    5039        Config._addStore(xmlDom, parentNode, self.store)
    5040        Config._addPurge(xmlDom, parentNode, self.purge)
    5041        xmlData = serializeDom(xmlDom)
    5042        xmlDom.unlink()
    5043        return xmlData
    5044
    5045     @staticmethod
    5046 -   def _addReference(xmlDom, parentNode, referenceConfig):
    5047        """
    5048        Adds a <reference> configuration section as the next child of a parent.
    5049
    5050        We add the following fields to the document::
    5051
    5052           author         //cb_config/reference/author
    5053           revision       //cb_config/reference/revision
    5054           description    //cb_config/reference/description
    5055           generator      //cb_config/reference/generator
    5056
    5057        If C{referenceConfig} is C{None}, then no container will be added.
    5058
    5059        @param xmlDom: DOM tree as from L{createOutputDom}.
    5060        @param parentNode: Parent that the section should be appended to.
    5061        @param referenceConfig: Reference configuration section to be added to the document.
    5062        """
    5063        if referenceConfig is not None:
    5064           sectionNode = addContainerNode(xmlDom, parentNode, "reference")
    5065           addStringNode(xmlDom, sectionNode, "author", referenceConfig.author)
    5066           addStringNode(xmlDom, sectionNode, "revision", referenceConfig.revision)
    5067           addStringNode(xmlDom, sectionNode, "description", referenceConfig.description)
    5068           addStringNode(xmlDom, sectionNode, "generator", referenceConfig.generator)
    5069
    5070     @staticmethod
    5071 -   def _addExtensions(xmlDom, parentNode, extensionsConfig):
    5072        """
    5073        Adds an <extensions> configuration section as the next child of a parent.
    5074
    5075        We add the following fields to the document::
    5076
    5077           order_mode     //cb_config/extensions/order_mode
    5078
    5079        We also add groups of the following items, one list element per item::
    5080
    5081           actions        //cb_config/extensions/action
    5082
    5083        The extended action entries are added by L{_addExtendedAction}.
    5084
    5085        If C{extensionsConfig} is C{None}, then no container will be added.
    5086
    5087        @param xmlDom: DOM tree as from L{createOutputDom}.
    5088        @param parentNode: Parent that the section should be appended to.
    5089        @param extensionsConfig: Extensions configuration section to be added to the document.
    5090        """
    5091        if extensionsConfig is not None:
    5092           sectionNode = addContainerNode(xmlDom, parentNode, "extensions")
    5093           addStringNode(xmlDom, sectionNode, "order_mode", extensionsConfig.orderMode)
    5094           if extensionsConfig.actions is not None:
    5095              for action in extensionsConfig.actions:
    5096                 Config._addExtendedAction(xmlDom, sectionNode, action)
    5097
    5098     @staticmethod
    5099 -   def _addOptions(xmlDom, parentNode, optionsConfig):
    5100        """
    5101        Adds a <options> configuration section as the next child of a parent.
    5102
    5103        We add the following fields to the document::
    5104
    5105           startingDay    //cb_config/options/starting_day
    5106           workingDir     //cb_config/options/working_dir
    5107           backupUser     //cb_config/options/backup_user
    5108           backupGroup    //cb_config/options/backup_group
    5109           rcpCommand     //cb_config/options/rcp_command
    5110           rshCommand     //cb_config/options/rsh_command
    5111           cbackCommand   //cb_config/options/cback_command
    5112           managedActions //cb_config/options/managed_actions
    5113
    5114        We also add groups of the following items, one list element per
    5115        item::
    5116
    5117           overrides      //cb_config/options/override
    5118           hooks          //cb_config/options/pre_action_hook
    5119           hooks          //cb_config/options/post_action_hook
    5120
    5121        The individual override items are added by L{_addOverride}.  The
    5122        individual hook items are added by L{_addHook}.
    5123
    5124        If C{optionsConfig} is C{None}, then no container will be added.
    5125
    5126        @param xmlDom: DOM tree as from L{createOutputDom}.
    5127        @param parentNode: Parent that the section should be appended to.
    5128        @param optionsConfig: Options configuration section to be added to the document.
    5129        """
    5130        if optionsConfig is not None:
    5131           sectionNode = addContainerNode(xmlDom, parentNode, "options")
    5132           addStringNode(xmlDom, sectionNode, "starting_day", optionsConfig.startingDay)
    5133           addStringNode(xmlDom, sectionNode, "working_dir", optionsConfig.workingDir)
    5134           addStringNode(xmlDom, sectionNode, "backup_user", optionsConfig.backupUser)
    5135           addStringNode(xmlDom, sectionNode, "backup_group", optionsConfig.backupGroup)
    5136           addStringNode(xmlDom, sectionNode, "rcp_command", optionsConfig.rcpCommand)
    5137           addStringNode(xmlDom, sectionNode, "rsh_command", optionsConfig.rshCommand)
    5138           addStringNode(xmlDom, sectionNode, "cback_command", optionsConfig.cbackCommand)
    5139           managedActions = Config._buildCommaSeparatedString(optionsConfig.managedActions)
    5140           addStringNode(xmlDom, sectionNode, "managed_actions", managedActions)
    5141           if optionsConfig.overrides is not None:
    5142              for override in optionsConfig.overrides:
    5143                 Config._addOverride(xmlDom, sectionNode, override)
    5144           if optionsConfig.hooks is not None:
    5145              for hook in optionsConfig.hooks:
    5146                 Config._addHook(xmlDom, sectionNode, hook)
    5147
    5148     @staticmethod
    5149 -   def _addPeers(xmlDom, parentNode, peersConfig):
    5150        """
    5151        Adds a <peers> configuration section as the next child of a parent.
    5152
    5153        We add groups of the following items, one list element per
    5154        item::
    5155
    5156           localPeers     //cb_config/peers/peer
    5157           remotePeers    //cb_config/peers/peer
    5158
    5159        The individual local and remote peer entries are added by
    5160        L{_addLocalPeer} and L{_addRemotePeer}, respectively.
    5161
    5162        If C{peersConfig} is C{None}, then no container will be added.
    5163
    5164        @param xmlDom: DOM tree as from L{createOutputDom}.
    5165        @param parentNode: Parent that the section should be appended to.
    5166        @param peersConfig: Peers configuration section to be added to the document.
    5167        """
    5168        if peersConfig is not None:
    5169           sectionNode = addContainerNode(xmlDom, parentNode, "peers")
    5170           if peersConfig.localPeers is not None:
    5171              for localPeer in peersConfig.localPeers:
    5172                 Config._addLocalPeer(xmlDom, sectionNode, localPeer)
    5173           if peersConfig.remotePeers is not None:
    5174              for remotePeer in peersConfig.remotePeers:
    5175                 Config._addRemotePeer(xmlDom, sectionNode, remotePeer)
    5176
    5177     @staticmethod
    5178 -   def _addCollect(xmlDom, parentNode, collectConfig):
    5179        """
    5180        Adds a <collect> configuration section as the next child of a parent.
    5181
    5182        We add the following fields to the document::
    5183
    5184           targetDir            //cb_config/collect/collect_dir
    5185           collectMode          //cb_config/collect/collect_mode
    5186           archiveMode          //cb_config/collect/archive_mode
    5187           ignoreFile           //cb_config/collect/ignore_file
    5188
    5189        We also add groups of the following items, one list element per
    5190        item::
    5191
    5192           absoluteExcludePaths //cb_config/collect/exclude/abs_path
    5193           excludePatterns      //cb_config/collect/exclude/pattern
    5194           collectFiles         //cb_config/collect/file
    5195           collectDirs          //cb_config/collect/dir
    5196
    5197        The individual collect files are added by L{_addCollectFile} and
    5198        individual collect directories are added by L{_addCollectDir}.
    5199
    5200        If C{collectConfig} is C{None}, then no container will be added.
    5201
    5202        @param xmlDom: DOM tree as from L{createOutputDom}.
    5203        @param parentNode: Parent that the section should be appended to.
    5204        @param collectConfig: Collect configuration section to be added to the document.
    5205        """
    5206        if collectConfig is not None:
    5207           sectionNode = addContainerNode(xmlDom, parentNode, "collect")
    5208           addStringNode(xmlDom, sectionNode, "collect_dir", collectConfig.targetDir)
    5209           addStringNode(xmlDom, sectionNode, "collect_mode", collectConfig.collectMode)
    5210           addStringNode(xmlDom, sectionNode, "archive_mode", collectConfig.archiveMode)
    5211           addStringNode(xmlDom, sectionNode, "ignore_file", collectConfig.ignoreFile)
    5212           if ((collectConfig.absoluteExcludePaths is not None and collectConfig.absoluteExcludePaths != []) or
    5213               (collectConfig.excludePatterns is not None and collectConfig.excludePatterns != [])):
    5214              excludeNode = addContainerNode(xmlDom, sectionNode, "exclude")
    5215              if collectConfig.absoluteExcludePaths is not None:
    5216                 for absolutePath in collectConfig.absoluteExcludePaths:
    5217                    addStringNode(xmlDom, excludeNode, "abs_path", absolutePath)
    5218              if collectConfig.excludePatterns is not None:
    5219                 for pattern in collectConfig.excludePatterns:
    5220                    addStringNode(xmlDom, excludeNode, "pattern", pattern)
    5221           if collectConfig.collectFiles is not None:
    5222              for collectFile in collectConfig.collectFiles:
    5223                 Config._addCollectFile(xmlDom, sectionNode, collectFile)
    5224           if collectConfig.collectDirs is not None:
    5225              for collectDir in collectConfig.collectDirs:
    5226                 Config._addCollectDir(xmlDom, sectionNode, collectDir)
    5227
    5228     @staticmethod
    5229 -   def _addStage(xmlDom, parentNode, stageConfig):
    5230        """
    5231        Adds a <stage> configuration section as the next child of a parent.
    5232
    5233        We add the following fields to the document::
    5234
    5235           targetDir      //cb_config/stage/staging_dir
    5236
    5237        We also add groups of the following items, one list element per
    5238        item::
    5239
    5240           localPeers     //cb_config/stage/peer
    5241           remotePeers    //cb_config/stage/peer
    5242
    5243        The individual local and remote peer entries are added by
    5244        L{_addLocalPeer} and L{_addRemotePeer}, respectively.
    5245
    5246        If C{stageConfig} is C{None}, then no container will be added.
    5247
    5248        @param xmlDom: DOM tree as from L{createOutputDom}.
    5249        @param parentNode: Parent that the section should be appended to.
    5250        @param stageConfig: Stage configuration section to be added to the document.
    5251        """
    5252        if stageConfig is not None:
    5253           sectionNode = addContainerNode(xmlDom, parentNode, "stage")
    5254           addStringNode(xmlDom, sectionNode, "staging_dir", stageConfig.targetDir)
    5255           if stageConfig.localPeers is not None:
    5256              for localPeer in stageConfig.localPeers:
    5257                 Config._addLocalPeer(xmlDom, sectionNode, localPeer)
    5258           if stageConfig.remotePeers is not None:
    5259              for remotePeer in stageConfig.remotePeers:
    5260                 Config._addRemotePeer(xmlDom, sectionNode, remotePeer)
    5261
    5262     @staticmethod
    5263 -   def _addStore(xmlDom, parentNode, storeConfig):
    5264        """
    5265        Adds a <store> configuration section as the next child of a parent.
    5266
    5267        We add the following fields to the document::
    5268
    5269           sourceDir         //cb_config/store/source_dir
    5270           mediaType         //cb_config/store/media_type
    5271           deviceType        //cb_config/store/device_type
    5272           devicePath        //cb_config/store/target_device
    5273           deviceScsiId      //cb_config/store/target_scsi_id
    5274           driveSpeed        //cb_config/store/drive_speed
    5275           checkData         //cb_config/store/check_data
    5276           checkMedia        //cb_config/store/check_media
    5277           warnMidnite       //cb_config/store/warn_midnite
    5278           noEject           //cb_config/store/no_eject
    5279           refreshMediaDelay //cb_config/store/refresh_media_delay
    5280           ejectDelay        //cb_config/store/eject_delay
    5281
    5282        Blanking behavior configuration is added by the L{_addBlankBehavior}
    5283        method.
    5284
    5285        If C{storeConfig} is C{None}, then no container will be added.
    5286
    5287        @param xmlDom: DOM tree as from L{createOutputDom}.
    5288        @param parentNode: Parent that the section should be appended to.
    5289        @param storeConfig: Store configuration section to be added to the document.
    5290        """
    5291        if storeConfig is not None:
    5292           sectionNode = addContainerNode(xmlDom, parentNode, "store")
    5293           addStringNode(xmlDom, sectionNode, "source_dir", storeConfig.sourceDir)
    5294           addStringNode(xmlDom, sectionNode, "media_type", storeConfig.mediaType)
    5295           addStringNode(xmlDom, sectionNode, "device_type", storeConfig.deviceType)
    5296           addStringNode(xmlDom, sectionNode, "target_device", storeConfig.devicePath)
    5297           addStringNode(xmlDom, sectionNode, "target_scsi_id", storeConfig.deviceScsiId)
    5298           addIntegerNode(xmlDom, sectionNode, "drive_speed", storeConfig.driveSpeed)
    5299           addBooleanNode(xmlDom, sectionNode, "check_data", storeConfig.checkData)
    5300           addBooleanNode(xmlDom, sectionNode, "check_media", storeConfig.checkMedia)
    5301           addBooleanNode(xmlDom, sectionNode, "warn_midnite", storeConfig.warnMidnite)
    5302           addBooleanNode(xmlDom, sectionNode, "no_eject", storeConfig.noEject)
    5303           addIntegerNode(xmlDom, sectionNode, "refresh_media_delay", storeConfig.refreshMediaDelay)
    5304           addIntegerNode(xmlDom, sectionNode, "eject_delay", storeConfig.ejectDelay)
    5305           Config._addBlankBehavior(xmlDom, sectionNode, storeConfig.blankBehavior)
    5306
    5307     @staticmethod
    5308 -   def _addPurge(xmlDom, parentNode, purgeConfig):
    5309        """
    5310        Adds a <purge> configuration section as the next child of a parent.
    5311
    5312        We add the following fields to the document::
    5313
    5314           purgeDirs     //cb_config/purge/dir
    5315
    5316        The individual directory entries are added by L{_addPurgeDir}.
    5317
    5318        If C{purgeConfig} is C{None}, then no container will be added.
    5319
    5320        @param xmlDom: DOM tree as from L{createOutputDom}.
    5321        @param parentNode: Parent that the section should be appended to.
    5322        @param purgeConfig: Purge configuration section to be added to the document.
    5323        """
    5324        if purgeConfig is not None:
    5325           sectionNode = addContainerNode(xmlDom, parentNode, "purge")
    5326           if purgeConfig.purgeDirs is not None:
    5327              for purgeDir in purgeConfig.purgeDirs:
    5328                 Config._addPurgeDir(xmlDom, sectionNode, purgeDir)
    5329
    5330     @staticmethod
    5331 -   def _addExtendedAction(xmlDom, parentNode, action):
    5332        """
    5333        Adds an extended action container as the next child of a parent.
    5334
    5335        We add the following fields to the document::
    5336
    5337           name           action/name
    5338           module         action/module
    5339           function       action/function
    5340           index          action/index
    5341           dependencies   action/depends
    5342
    5343        Dependencies are added by the L{_addDependencies} method.
    5344
    5345        The <action> node itself is created as the next child of the parent node.
    5346        This method only adds one action node.  The parent must loop for each action
    5347        in the C{ExtensionsConfig} object.
    5348
    5349        If C{action} is C{None}, this method call will be a no-op.
    5350
    5351        @param xmlDom: DOM tree as from L{createOutputDom}.
    5352        @param parentNode: Parent that the section should be appended to.
    5353        @param action: Purge directory to be added to the document.
    5354        """
    5355        if action is not None:
    5356           sectionNode = addContainerNode(xmlDom, parentNode, "action")
    5357           addStringNode(xmlDom, sectionNode, "name", action.name)
    5358           addStringNode(xmlDom, sectionNode, "module", action.module)
    5359           addStringNode(xmlDom, sectionNode, "function", action.function)
    5360           addIntegerNode(xmlDom, sectionNode, "index", action.index)
    5361           Config._addDependencies(xmlDom, sectionNode, action.dependencies)
    5362
    5363     @staticmethod
    5364 -   def _addOverride(xmlDom, parentNode, override):
    5365        """
    5366        Adds a command override container as the next child of a parent.
    5367
    5368        We add the following fields to the document::
    5369
    5370           command                 override/command
    5371           absolutePath            override/abs_path
    5372
    5373        The <override> node itself is created as the next child of the parent
    5374        node.  This method only adds one override node.  The parent must loop for
    5375        each override in the C{OptionsConfig} object.
    5376
    5377        If C{override} is C{None}, this method call will be a no-op.
    5378
    5379        @param xmlDom: DOM tree as from L{createOutputDom}.
    5380        @param parentNode: Parent that the section should be appended to.
    5381        @param override: Command override to be added to the document.
    5382        """
    5383        if override is not None:
    5384           sectionNode = addContainerNode(xmlDom, parentNode, "override")
    5385           addStringNode(xmlDom, sectionNode, "command", override.command)
    5386           addStringNode(xmlDom, sectionNode, "abs_path", override.absolutePath)
    5387
    5388     @staticmethod
    5389 -   def _addHook(xmlDom, parentNode, hook):
    5390        """
    5391        Adds an action hook container as the next child of a parent.
    5392
    5393        The behavior varies depending on the value of the C{before} and C{after}
    5394        flags on the hook.  If the C{before} flag is set, it's a pre-action hook,
    5395        and we'll add the following fields::
    5396
    5397           action                  pre_action_hook/action
    5398           command                 pre_action_hook/command
    5399
    5400        If the C{after} flag is set, it's a post-action hook, and we'll add the
    5401        following fields::
    5402
    5403           action                  post_action_hook/action
    5404           command                 post_action_hook/command
    5405
    5406        The <pre_action_hook> or <post_action_hook> node itself is created as the
    5407        next child of the parent node.  This method only adds one hook node.  The
    5408        parent must loop for each hook in the C{OptionsConfig} object.
    5409
    5410        If C{hook} is C{None}, this method call will be a no-op.
    5411
    5412        @param xmlDom: DOM tree as from L{createOutputDom}.
    5413        @param parentNode: Parent that the section should be appended to.
    5414        @param hook: Command hook to be added to the document.
    5415        """
    5416        if hook is not None:
    5417           if hook.before:
    5418              sectionNode = addContainerNode(xmlDom, parentNode, "pre_action_hook")
    5419           else:
    5420              sectionNode = addContainerNode(xmlDom, parentNode, "post_action_hook")
    5421           addStringNode(xmlDom, sectionNode, "action", hook.action)
    5422           addStringNode(xmlDom, sectionNode, "command", hook.command)
    5423
    5424     @staticmethod
    5425 -   def _addCollectFile(xmlDom, parentNode, collectFile):
    5426        """
    5427        Adds a collect file container as the next child of a parent.
    5428
    5429        We add the following fields to the document::
    5430
    5431           absolutePath            dir/abs_path
    5432           collectMode             dir/collect_mode
    5433           archiveMode             dir/archive_mode
    5434
    5435        Note that for consistency with collect directory handling we'll only emit
    5436        the preferred C{collect_mode} tag.
    5437
    5438        The <file> node itself is created as the next child of the parent node.
    5439        This method only adds one collect file node.  The parent must loop
    5440        for each collect file in the C{CollectConfig} object.
    5441
    5442        If C{collectFile} is C{None}, this method call will be a no-op.
    5443
    5444        @param xmlDom: DOM tree as from L{createOutputDom}.
    5445        @param parentNode: Parent that the section should be appended to.
    5446        @param collectFile: Collect file to be added to the document.
    5447        """
    5448        if collectFile is not None:
    5449           sectionNode = addContainerNode(xmlDom, parentNode, "file")
    5450           addStringNode(xmlDom, sectionNode, "abs_path", collectFile.absolutePath)
    5451           addStringNode(xmlDom, sectionNode, "collect_mode", collectFile.collectMode)
    5452           addStringNode(xmlDom, sectionNode, "archive_mode", collectFile.archiveMode)
    5453
    5454     @staticmethod
    5455 -   def _addCollectDir(xmlDom, parentNode, collectDir):
    5456        """
    5457        Adds a collect directory container as the next child of a parent.
    5458
    5459        We add the following fields to the document::
    5460
    5461           absolutePath            dir/abs_path
    5462           collectMode             dir/collect_mode
    5463           archiveMode             dir/archive_mode
    5464           ignoreFile              dir/ignore_file
    5465           linkDepth               dir/link_depth
    5466           dereference             dir/dereference
    5467           recursionLevel          dir/recursion_level
    5468
    5469        Note that an original XML document might have listed the collect mode
    5470        using the C{mode} tag, since we accept both C{collect_mode} and C{mode}.
    5471        However, here we'll only emit the preferred C{collect_mode} tag.
    5472
    5473        We also add groups of the following items, one list element per item::
    5474
    5475           absoluteExcludePaths    dir/exclude/abs_path
    5476           relativeExcludePaths    dir/exclude/rel_path
    5477           excludePatterns         dir/exclude/pattern
    5478
    5479        The <dir> node itself is created as the next child of the parent node.
    5480        This method only adds one collect directory node.  The parent must loop
    5481        for each collect directory in the C{CollectConfig} object.
    5482
    5483        If C{collectDir} is C{None}, this method call will be a no-op.
    5484
    5485        @param xmlDom: DOM tree as from L{createOutputDom}.
    5486        @param parentNode: Parent that the section should be appended to.
    5487        @param collectDir: Collect directory to be added to the document.
    5488        """
    5489        if collectDir is not None:
    5490           sectionNode = addContainerNode(xmlDom, parentNode, "dir")
    5491           addStringNode(xmlDom, sectionNode, "abs_path", collectDir.absolutePath)
    5492           addStringNode(xmlDom, sectionNode, "collect_mode", collectDir.collectMode)
    5493           addStringNode(xmlDom, sectionNode, "archive_mode", collectDir.archiveMode)
    5494           addStringNode(xmlDom, sectionNode, "ignore_file", collectDir.ignoreFile)
    5495           addIntegerNode(xmlDom, sectionNode, "link_depth", collectDir.linkDepth)
    5496           addBooleanNode(xmlDom, sectionNode, "dereference", collectDir.dereference)
    5497           addIntegerNode(xmlDom, sectionNode, "recursion_level", collectDir.recursionLevel)
    5498           if ((collectDir.absoluteExcludePaths is not None and collectDir.absoluteExcludePaths != []) or
    5499               (collectDir.relativeExcludePaths is not None and collectDir.relativeExcludePaths != []) or
    5500               (collectDir.excludePatterns is not None and collectDir.excludePatterns != [])):
    5501              excludeNode = addContainerNode(xmlDom, sectionNode, "exclude")
    5502              if collectDir.absoluteExcludePaths is not None:
    5503                 for absolutePath in collectDir.absoluteExcludePaths:
    5504                    addStringNode(xmlDom, excludeNode, "abs_path", absolutePath)
    5505              if collectDir.relativeExcludePaths is not None:
    5506                 for relativePath in collectDir.relativeExcludePaths:
    5507                    addStringNode(xmlDom, excludeNode, "rel_path", relativePath)
    5508              if collectDir.excludePatterns is not None:
    5509                 for pattern in collectDir.excludePatterns:
    5510                    addStringNode(xmlDom, excludeNode, "pattern", pattern)
    5511
    5512     @staticmethod
    5513 -   def _addLocalPeer(xmlDom, parentNode, localPeer):
    5514        """
    5515        Adds a local peer container as the next child of a parent.
    5516
    5517        We add the following fields to the document::
    5518
    5519           name                peer/name
    5520           collectDir          peer/collect_dir
    5521           ignoreFailureMode   peer/ignore_failures
    5522
    5523        Additionally, C{peer/type} is filled in with C{"local"}, since this is a
    5524        local peer.
    5525
    5526        The <peer> node itself is created as the next child of the parent node.
    5527        This method only adds one peer node.  The parent must loop for each peer
    5528        in the C{StageConfig} object.
    5529
    5530        If C{localPeer} is C{None}, this method call will be a no-op.
    5531
    5532        @param xmlDom: DOM tree as from L{createOutputDom}.
    5533        @param parentNode: Parent that the section should be appended to.
    5534        @param localPeer: Purge directory to be added to the document.
    5535        """
    5536        if localPeer is not None:
    5537           sectionNode = addContainerNode(xmlDom, parentNode, "peer")
    5538           addStringNode(xmlDom, sectionNode, "name", localPeer.name)
    5539           addStringNode(xmlDom, sectionNode, "type", "local")
    5540           addStringNode(xmlDom, sectionNode, "collect_dir", localPeer.collectDir)
    5541           addStringNode(xmlDom, sectionNode, "ignore_failures", localPeer.ignoreFailureMode)
    5542
    5543     @staticmethod
    5544 -   def _addRemotePeer(xmlDom, parentNode, remotePeer):
    5545        """
    5546        Adds a remote peer container as the next child of a parent.
    5547
    5548        We add the following fields to the document::
    5549
    5550           name                peer/name
    5551           collectDir          peer/collect_dir
    5552           remoteUser          peer/backup_user
    5553           rcpCommand          peer/rcp_command
    5554           rcpCommand          peer/rcp_command
    5555           rshCommand          peer/rsh_command
    5556           cbackCommand        peer/cback_command
    5557           ignoreFailureMode   peer/ignore_failures
    5558           managed             peer/managed
    5559           managedActions      peer/managed_actions
    5560
    5561        Additionally, C{peer/type} is filled in with C{"remote"}, since this is a
    5562        remote peer.
    5563
    5564        The <peer> node itself is created as the next child of the parent node.
    5565        This method only adds one peer node.  The parent must loop for each peer
    5566        in the C{StageConfig} object.
    5567
    5568        If C{remotePeer} is C{None}, this method call will be a no-op.
    5569
    5570        @param xmlDom: DOM tree as from L{createOutputDom}.
    5571        @param parentNode: Parent that the section should be appended to.
    5572        @param remotePeer: Purge directory to be added to the document.
    5573        """
    5574        if remotePeer is not None:
    5575           sectionNode = addContainerNode(xmlDom, parentNode, "peer")
    5576           addStringNode(xmlDom, sectionNode, "name", remotePeer.name)
    5577           addStringNode(xmlDom, sectionNode, "type", "remote")
    5578           addStringNode(xmlDom, sectionNode, "collect_dir", remotePeer.collectDir)
    5579           addStringNode(xmlDom, sectionNode, "backup_user", remotePeer.remoteUser)
    5580           addStringNode(xmlDom, sectionNode, "rcp_command", remotePeer.rcpCommand)
    5581           addStringNode(xmlDom, sectionNode, "rsh_command", remotePeer.rshCommand)
    5582           addStringNode(xmlDom, sectionNode, "cback_command", remotePeer.cbackCommand)
    5583           addStringNode(xmlDom, sectionNode, "ignore_failures", remotePeer.ignoreFailureMode)
    5584           addBooleanNode(xmlDom, sectionNode, "managed", remotePeer.managed)
    5585           managedActions = Config._buildCommaSeparatedString(remotePeer.managedActions)
    5586           addStringNode(xmlDom, sectionNode, "managed_actions", managedActions)
    5587
    5588     @staticmethod
    5589 -   def _addPurgeDir(xmlDom, parentNode, purgeDir):
    5590        """
    5591        Adds a purge directory container as the next child of a parent.
    5592
    5593        We add the following fields to the document::
    5594
    5595           absolutePath            dir/abs_path
    5596           retainDays              dir/retain_days
    5597
    5598        The <dir> node itself is created as the next child of the parent node.
    5599        This method only adds one purge directory node.  The parent must loop for
    5600        each purge directory in the C{PurgeConfig} object.
    5601
    5602        If C{purgeDir} is C{None}, this method call will be a no-op.
    5603
    5604        @param xmlDom: DOM tree as from L{createOutputDom}.
    5605        @param parentNode: Parent that the section should be appended to.
    5606        @param purgeDir: Purge directory to be added to the document.
    5607        """
    5608        if purgeDir is not None:
    5609           sectionNode = addContainerNode(xmlDom, parentNode, "dir")
    5610           addStringNode(xmlDom, sectionNode, "abs_path", purgeDir.absolutePath)
    5611           addIntegerNode(xmlDom, sectionNode, "retain_days", purgeDir.retainDays)
    5612
    5613     @staticmethod
    5614 -   def _addDependencies(xmlDom, parentNode, dependencies):
    5615        """
    5616        Adds a extended action dependencies to parent node.
    5617
    5618        We add the following fields to the document::
    5619
    5620           runBefore      depends/run_before
    5621           runAfter       depends/run_after
    5622
    5623        If C{dependencies} is C{None}, this method call will be a no-op.
    5624
    5625        @param xmlDom: DOM tree as from L{createOutputDom}.
    5626        @param parentNode: Parent that the section should be appended to.
    5627        @param dependencies: C{ActionDependencies} object to be added to the document
    5628        """
    5629        if dependencies is not None:
    5630           sectionNode = addContainerNode(xmlDom, parentNode, "depends")
    5631           runBefore = Config._buildCommaSeparatedString(dependencies.beforeList)
    5632           runAfter = Config._buildCommaSeparatedString(dependencies.afterList)
    5633           addStringNode(xmlDom, sectionNode, "run_before", runBefore)
    5634           addStringNode(xmlDom, sectionNode, "run_after", runAfter)
    5635
    5636     @staticmethod
    5637 -   def _buildCommaSeparatedString(valueList):
    5638        """
    5639        Creates a comma-separated string from a list of values.
    5640
    5641        As a special case, if C{valueList} is C{None}, then C{None} will be
    5642        returned.
    5643
    5644        @param valueList: List of values to be placed into a string
    5645
    5646        @return: Values from valueList as a comma-separated string.
    5647        """
    5648        if valueList is None:
    5649           return None
    5650        return ",".join(valueList)
    5651
    5652     @staticmethod
    5653 -   def _addBlankBehavior(xmlDom, parentNode, blankBehavior):
    5654        """
    5655        Adds a blanking behavior container as the next child of a parent.
    5656
    5657        We add the following fields to the document::
    5658
    5659           blankMode    blank_behavior/mode
    5660           blankFactor  blank_behavior/factor
    5661
    5662        The <blank_behavior> node itself is created as the next child of the
    5663        parent node.
    5664
    5665        If C{blankBehavior} is C{None}, this method call will be a no-op.
    5666
    5667        @param xmlDom: DOM tree as from L{createOutputDom}.
    5668        @param parentNode: Parent that the section should be appended to.
    5669        @param blankBehavior: Blanking behavior to be added to the document.
    5670        """
    5671        if blankBehavior is not None:
    5672           sectionNode = addContainerNode(xmlDom, parentNode, "blank_behavior")
    5673           addStringNode(xmlDom, sectionNode, "mode", blankBehavior.blankMode)
    5674           addStringNode(xmlDom, sectionNode, "factor", blankBehavior.blankFactor)
    5675
    5676
    5677     #################################################
    5678     # High-level methods used for validating content
    5679     #################################################
    5680
    5681 -   def _validateContents(self):
    5682        """
    5683        Validates configuration contents per rules discussed in module
    5684        documentation.
    5685
    5686        This is the second pass at validation.  It ensures that any filled-in
    5687        section contains valid data.  Any sections which is not set to C{None} is
    5688        validated per the rules for that section, laid out in the module
    5689        documentation (above).
    5690
    5691        @raise ValueError: If configuration is invalid.
    5692        """
    5693        self._validateReference()
    5694        self._validateExtensions()
    5695        self._validateOptions()
    5696        self._validatePeers()
    5697        self._validateCollect()
    5698        self._validateStage()
    5699        self._validateStore()
    5700        self._validatePurge()
    5701
    5702 -   def _validateReference(self):
    5703        """
    5704        Validates reference configuration.
    5705        There are currently no reference-related validations.
    5706        @raise ValueError: If reference configuration is invalid.
    5707        """
    5708        pass
    5709
    5710 -   def _validateExtensions(self):
    5711        """
    5712        Validates extensions configuration.
    5713
    5714        The list of actions may be either C{None} or an empty list C{[]} if
    5715        desired.  Each extended action must include a name, a module, and a
    5716        function.
    5717
    5718        Then, if the order mode is None or "index", an index is required; and if
    5719        the order mode is "dependency", dependency information is required.
    5720
    5721        @raise ValueError: If reference configuration is invalid.
    5722        """
    5723        if self.extensions is not None:
    5724           if self.extensions.actions is not None:
    5725              names = []
    5726              for action in self.extensions.actions:
    5727                 if action.name is None:
    5728                    raise ValueError("Each extended action must set a name.")
    5729                 names.append(action.name)
    5730                 if action.module is None:
    5731                    raise ValueError("Each extended action must set a module.")
    5732                 if action.function is None:
    5733                    raise ValueError("Each extended action must set a function.")
    5734                 if self.extensions.orderMode is None or self.extensions.orderMode == "index":
    5735                    if action.index is None:
    5736                       raise ValueError("Each extended action must set an index, based on order mode.")
    5737                 elif self.extensions.orderMode == "dependency":
    5738                    if action.dependencies is None:
    5739                       raise ValueError("Each extended action must set dependency information, based on order mode.")
    5740              checkUnique("Duplicate extension names exist:", names)
    5741
    5742 -   def _validateOptions(self):
    5743        """
    5744        Validates options configuration.
    5745
    5746        All fields must be filled in except the rsh command.  The rcp and rsh
    5747        commands are used as default values for all remote peers.  Remote peers
    5748        can also rely on the backup user as the default remote user name if they
    5749        choose.
    5750
    5751        @raise ValueError: If reference configuration is invalid.
    5752        """
    5753        if self.options is not None:
    5754           if self.options.startingDay is None:
    5755              raise ValueError("Options section starting day must be filled in.")
    5756           if self.options.workingDir is None:
    5757              raise ValueError("Options section working directory must be filled in.")
    5758           if self.options.backupUser is None:
    5759              raise ValueError("Options section backup user must be filled in.")
    5760           if self.options.backupGroup is None:
    5761              raise ValueError("Options section backup group must be filled in.")
    5762           if self.options.rcpCommand is None:
    5763              raise ValueError("Options section remote copy command must be filled in.")
    5764
    5765 -   def _validatePeers(self):
    5766        """
    5767        Validates peers configuration per rules in L{_validatePeerList}.
    5768        @raise ValueError: If peers configuration is invalid.
    5769        """
    5770        if self.peers is not None:
    5771           self._validatePeerList(self.peers.localPeers, self.peers.remotePeers)
    5772
    5773 -   def _validateCollect(self):
    5774        """
    5775        Validates collect configuration.
    5776
    5777        The target directory must be filled in.  The collect mode, archive mode,
    5778        ignore file, and recursion level are all optional.  The list of absolute
    5779        paths to exclude and patterns to exclude may be either C{None} or an
    5780        empty list C{[]} if desired.
    5781
    5782        Each collect directory entry must contain an absolute path to collect,
    5783        and then must either be able to take collect mode, archive mode and
    5784        ignore file configuration from the parent C{CollectConfig} object, or
    5785        must set each value on its own.  The list of absolute paths to exclude,
    5786        relative paths to exclude and patterns to exclude may be either C{None}
    5787        or an empty list C{[]} if desired.  Any list of absolute paths to exclude
    5788        or patterns to exclude will be combined with the same list in the
    5789        C{CollectConfig} object to make the complete list for a given directory.
    5790
    5791        @raise ValueError: If collect configuration is invalid.
    5792        """
    5793        if self.collect is not None:
    5794           if self.collect.targetDir is None:
    5795              raise ValueError("Collect section target directory must be filled in.")
    5796           if self.collect.collectFiles is not None:
    5797              for collectFile in self.collect.collectFiles:
    5798                 if collectFile.absolutePath is None:
    5799                    raise ValueError("Each collect file must set an absolute path.")
    5800                 if self.collect.collectMode is None and collectFile.collectMode is None:
    5801                    raise ValueError("Collect mode must either be set in parent collect section or individual collect file.")
    5802                 if self.collect.archiveMode is None and collectFile.archiveMode is None:
    5803                    raise ValueError("Archive mode must either be set in parent collect section or individual collect file.")
    5804           if self.collect.collectDirs is not None:
    5805              for collectDir in self.collect.collectDirs:
    5806                 if collectDir.absolutePath is None:
    5807                    raise ValueError("Each collect directory must set an absolute path.")
    5808                 if self.collect.collectMode is None and collectDir.collectMode is None:
    5809                    raise ValueError("Collect mode must either be set in parent collect section or individual collect directory.")
    5810                 if self.collect.archiveMode is None and collectDir.archiveMode is None:
    5811                    raise ValueError("Archive mode must either be set in parent collect section or individual collect directory.")
    5812                 if self.collect.ignoreFile is None and collectDir.ignoreFile is None:
    5813                    raise ValueError("Ignore file must either be set in parent collect section or individual collect directory.")
    5814                 if (collectDir.linkDepth is None or collectDir.linkDepth < 1) and collectDir.dereference:
    5815                    raise ValueError("Dereference flag is only valid when a non-zero link depth is in use.")
    5816
    5817 -   def _validateStage(self):
    5818        """
    5819        Validates stage configuration.
    5820
    5821        The target directory must be filled in, and the peers are
    5822        also validated.
    5823
    5824        Peers are only required in this section if the peers configuration
    5825        section is not filled in.  However, if any peers are filled in
    5826        here, they override the peers configuration and must meet the
    5827        validation criteria in L{_validatePeerList}.
    5828
    5829        @raise ValueError: If stage configuration is invalid.
    5830        """
    5831        if self.stage is not None:
    5832           if self.stage.targetDir is None:
    5833              raise ValueError("Stage section target directory must be filled in.")
    5834           if self.peers is None:
    5835              # In this case, stage configuration is our only configuration and must be valid.
    5836              self._validatePeerList(self.stage.localPeers, self.stage.remotePeers)
    5837           else:
    5838              # In this case, peers configuration is the default and stage configuration overrides.
    5839              # Validation is only needed if it's stage configuration is actually filled in.
    5840              if self.stage.hasPeers():
    5841                 self._validatePeerList(self.stage.localPeers, self.stage.remotePeers)
    5842
    5843 -   def _validateStore(self):
    5844        """
    5845        Validates store configuration.
    5846
    5847        The device type, drive speed, and blanking behavior are optional.  All
    5848        other values are required. Missing booleans will be set to defaults.
    5849
    5850        If blanking behavior is provided, then both a blanking mode and a
    5851        blanking factor are required.
    5852
    5853        The image writer functionality in the C{writer} module is supposed to be
    5854        able to handle a device speed of C{None}.
    5855
    5856        Any caller which needs a "real" (non-C{None}) value for the device type
    5857        can use C{DEFAULT_DEVICE_TYPE}, which is guaranteed to be sensible.
    5858
    5859        This is also where we make sure that the media type -- which is already a
    5860        valid type -- matches up properly with the device type.
    5861
    5862        @raise ValueError: If store configuration is invalid.
    5863        """
    5864        if self.store is not None:
    5865           if self.store.sourceDir is None:
    5866              raise ValueError("Store section source directory must be filled in.")
    5867           if self.store.mediaType is None:
    5868              raise ValueError("Store section media type must be filled in.")
    5869           if self.store.devicePath is None:
    5870              raise ValueError("Store section device path must be filled in.")
    5871           if self.store.deviceType is None or self.store.deviceType == "cdwriter":
    5872              if self.store.mediaType not in VALID_CD_MEDIA_TYPES:
    5873                 raise ValueError("Media type must match device type.")
    5874           elif self.store.deviceType == "dvdwriter":
    5875              if self.store.mediaType not in VALID_DVD_MEDIA_TYPES:
    5876                 raise ValueError("Media type must match device type.")
    5877           if self.store.blankBehavior is not None:
    5878              if self.store.blankBehavior.blankMode is None and self.store.blankBehavior.blankFactor is None:
    5879                 raise ValueError("If blanking behavior is provided, all values must be filled in.")
    5880
    5881 -   def _validatePurge(self):
    5882        """
    5883        Validates purge configuration.
    5884
    5885        The list of purge directories may be either C{None} or an empty list
    5886        C{[]} if desired.  All purge directories must contain a path and a retain
    5887        days value.
    5888
    5889        @raise ValueError: If purge configuration is invalid.
    5890        """
    5891        if self.purge is not None:
    5892           if self.purge.purgeDirs is not None:
    5893              for purgeDir in self.purge.purgeDirs:
    5894                 if purgeDir.absolutePath is None:
    5895                    raise ValueError("Each purge directory must set an absolute path.")
    5896                 if purgeDir.retainDays is None:
    5897                    raise ValueError("Each purge directory must set a retain days value.")
    5898
    5899 -   def _validatePeerList(self, localPeers, remotePeers):
    5900        """
    5901        Validates the set of local and remote peers.
    5902
    5903        Local peers must be completely filled in, including both name and collect
    5904        directory.  Remote peers must also fill in the name and collect
    5905        directory, but can leave the remote user and rcp command unset.  In this
    5906        case, the remote user is assumed to match the backup user from the
    5907        options section and rcp command is taken directly from the options
    5908        section.
    5909
    5910        @param localPeers: List of local peers
    5911        @param remotePeers: List of remote peers
    5912
    5913        @raise ValueError: If stage configuration is invalid.
    5914        """
    5915        if localPeers is None and remotePeers is None:
    5916           raise ValueError("Peer list must contain at least one backup peer.")
    5917        if localPeers is None and remotePeers is not None:
    5918           if len(remotePeers) < 1:
    5919              raise ValueError("Peer list must contain at least one backup peer.")
    5920        elif localPeers is not None and remotePeers is None:
    5921           if len(localPeers) < 1:
    5922              raise ValueError("Peer list must contain at least one backup peer.")
    5923        elif localPeers is not None and remotePeers is not None:
    5924           if len(localPeers) + len(remotePeers) < 1:
    5925              raise ValueError("Peer list must contain at least one backup peer.")
    5926        names = []
    5927        if localPeers is not None:
    5928           for localPeer in localPeers:
    5929              if localPeer.name is None:
    5930                 raise ValueError("Local peers must set a name.")
    5931              names.append(localPeer.name)
    5932              if localPeer.collectDir is None:
    5933                 raise ValueError("Local peers must set a collect directory.")
    5934        if remotePeers is not None:
    5935           for remotePeer in remotePeers:
    5936              if remotePeer.name is None:
    5937                 raise ValueError("Remote peers must set a name.")
    5938              names.append(remotePeer.name)
    5939              if remotePeer.collectDir is None:
    5940                 raise ValueError("Remote peers must set a collect directory.")
    5941              if (self.options is None or self.options.backupUser is None) and remotePeer.remoteUser is None:
    5942                 raise ValueError("Remote user must either be set in options section or individual remote peer.")
    5943              if (self.options is None or self.options.rcpCommand is None) and remotePeer.rcpCommand is None:
    5944                 raise ValueError("Remote copy command must either be set in options section or individual remote peer.")
    5945              if remotePeer.managed:
    5946                 if (self.options is None or self.options.rshCommand is None) and remotePeer.rshCommand is None:
    5947                    raise ValueError("Remote shell command must either be set in options section or individual remote peer.")
    5948                 if (self.options is None or self.options.cbackCommand is None) and remotePeer.cbackCommand is None:
    5949                    raise ValueError("Remote cback command must either be set in options section or individual remote peer.")
    5950                 if ((self.options is None or self.options.managedActions is None or len(self.options.managedActions) < 1)
    5951                     and (remotePeer.managedActions is None or len(remotePeer.managedActions) < 1)):
    5952                    raise ValueError("Managed actions list must be set in options section or individual remote peer.")
    5953        checkUnique("Duplicate peer names exist:", names)
    5954
    5955
    5956  ########################################################################
    5957  # General utility functions
    5958  ########################################################################
    5959
    5960 -def readByteQuantity(parent, name):
    5961     """
    5962     Read a byte size value from an XML document.
    5963
    5964     A byte size value is an interpreted string value.  If the string value
    5965     ends with "MB" or "GB", then the string before that is interpreted as
    5966     megabytes or gigabytes.  Otherwise, it is intepreted as bytes.
    5967
    5968     @param parent: Parent node to search beneath.
    5969     @param name: Name of node to search for.
    5970
    5971     @return: ByteQuantity parsed from XML document
    5972     """
    5973     data = readString(parent, name)
    5974     if data is None:
    5975        return None
    5976     data = data.strip()
    5977     if data.endswith("KB"):
    5978        quantity = data[0:data.rfind("KB")].strip()
    5979        units = UNIT_KBYTES
    5980     elif data.endswith("MB"):
    5981        quantity = data[0:data.rfind("MB")].strip()
    5982        units = UNIT_MBYTES
    5983     elif data.endswith("GB"):
    5984        quantity = data[0:data.rfind("GB")].strip()
    5985        units = UNIT_GBYTES
    5986     else:
    5987        quantity = data.strip()
    5988        units = UNIT_BYTES
    5989     return ByteQuantity(quantity, units)
    5990
    5991 -def addByteQuantityNode(xmlDom, parentNode, nodeName, byteQuantity):
    5992     """
    5993     Adds a text node as the next child of a parent, to contain a byte size.
    5994
    5995     If the C{byteQuantity} is None, then the node will be created, but will
    5996     be empty (i.e. will contain no text node child).
    5997
    5998     The size in bytes will be normalized.  If it is larger than 1.0 GB, it will
    5999     be shown in GB ("1.0 GB").  If it is larger than 1.0 MB ("1.0 MB"), it will
    6000     be shown in MB.  Otherwise, it will be shown in bytes ("423413").
    6001
    6002     @param xmlDom: DOM tree as from C{impl.createDocument()}.
    6003     @param parentNode: Parent node to create child for.
    6004     @param nodeName: Name of the new container node.
    6005     @param byteQuantity: ByteQuantity object to put into the XML document
    6006
    6007     @return: Reference to the newly-created node.
    6008     """
    6009     if byteQuantity is None:
    6010        byteString = None
    6011     elif byteQuantity.units == UNIT_KBYTES:
    6012        byteString = "%s KB" % byteQuantity.quantity
    6013     elif byteQuantity.units == UNIT_MBYTES:
    6014        byteString = "%s MB" % byteQuantity.quantity
    6015     elif byteQuantity.units == UNIT_GBYTES:
    6016        byteString = "%s GB" % byteQuantity.quantity
    6017     else:
    6018        byteString = byteQuantity.quantity
    6019     return addStringNode(xmlDom, parentNode, nodeName, byteString)
    6020