CedarBackup3.config

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

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