python-adfs

changeset 64:2e2bd0a863db

Tidied up the API and added some classes for files and directories.
author David Boddie <david@boddie.org.uk>
date Sun Jun 08 20:17:05 2008 +0200
parents 1a92043cd7bb
children 828d28f47e66
files ADF2INF.py ADFSlib.py
diffstat 2 files changed, 264 insertions(+), 173 deletions(-) [+]
line diff
     1.1 --- a/ADF2INF.py	Sun Jun 08 15:22:16 2008 +0200
     1.2 +++ b/ADF2INF.py	Sun Jun 08 20:17:05 2008 +0200
     1.3 @@ -3,13 +3,13 @@
     1.4  Name        : ADF2INF.py
     1.5  Author      : David Boddie
     1.6  Created     : Wed 18th October 2000
     1.7 -Updated     : Mon 21st July 2003
     1.8 +Updated     : Sun 8th June 2008
     1.9  Purpose     : Convert ADFS disc images (ADF) to INF files
    1.10  WWW         : http://david.boddie.org.uk/Projects/Python/ADFSlib
    1.11  
    1.12  License:
    1.13  
    1.14 -Copyright (c) 2000-2003, David Boddie
    1.15 +Copyright (c) 2000-2008, David Boddie
    1.16  
    1.17  This software is free software; you can redistribute it and/or
    1.18  modify it under the terms of the GNU General Public License as
    1.19 @@ -172,9 +172,13 @@
    1.20          print 'name into which the contents of the disc will be written.'
    1.21          print
    1.22          print "The -t flag causes the load and execution addresses of files to be"
    1.23 -        print "interpreted as filetype information for files created on RISC OS."
    1.24 +        print "interpreted as file type information for files created on RISC OS."
    1.25          print "A separator used to append a suffix onto the file is optional and"
    1.26 -        print "defaults to the standard period character. e.g. myfile.fff"
    1.27 +        print "defaults to the standard period character; e.g. myfile.fff"
    1.28 +        print
    1.29 +        print "The -s flag is used to specify the character which joins the file"
    1.30 +        print "name to the file type. This can only be specified when extracting"
    1.31 +        print "files from a disc image."
    1.32          print
    1.33          print "The -v flag causes the disc image to be checked for simple defects and"
    1.34          print "determines whether there are files and directories which cannot be"
    1.35 @@ -285,9 +289,7 @@
    1.36          print 'Contents of', adfsdisc.disc_name,':'
    1.37          print
    1.38          
    1.39 -        adfsdisc.print_catalogue(
    1.40 -            adfsdisc.files, adfsdisc.root_name, filetypes, separator
    1.41 -            )
    1.42 +        adfsdisc.print_catalogue(adfsdisc.files, adfsdisc.root_name, filetypes)
    1.43          
    1.44          print
    1.45          
     2.1 --- a/ADFSlib.py	Sun Jun 08 15:22:16 2008 +0200
     2.2 +++ b/ADFSlib.py	Sun Jun 08 20:17:05 2008 +0200
     2.3 @@ -4,7 +4,7 @@
     2.4  
     2.5  A library for reading ADFS disc images.
     2.6  
     2.7 -Copyright (c) 2003-2005, David Boddie
     2.8 +Copyright (c) 2003-2008, David Boddie
     2.9  
    2.10  This software is free software; you can redistribute it and/or
    2.11  modify it under the terms of the GNU General Public License as
    2.12 @@ -24,8 +24,8 @@
    2.13  """
    2.14  
    2.15  __author__ = "David Boddie <david@boddie.org.uk>"
    2.16 -__date__ = "Sun 10th April 2005"
    2.17 -__version__ = "0.22"
    2.18 +__date__ = "Sun 8th June 2008"
    2.19 +__version__ = "0.30"
    2.20  
    2.21  
    2.22  import os, string, struct
    2.23 @@ -41,8 +41,64 @@
    2.24      pass
    2.25  
    2.26  
    2.27 +class ADFSdirectory:
    2.28 +
    2.29 +    """ADFSdirectory
    2.30 +    
    2.31 +    directory = ADFSdirectory(name, files)
    2.32 +    """
    2.33 +    
    2.34 +    def __init__(self, name, files):
    2.35 +    
    2.36 +        self.name = name
    2.37 +        self.files = files
    2.38 +    
    2.39 +    def __repr__(self):
    2.40 +    
    2.41 +        return '<%s instance, "%s", at %x>' % (self.__class__, self.name, id(self))
    2.42 +
    2.43 +
    2.44 +class ADFSfile:
    2.45 +
    2.46 +    """ADFSfile
    2.47 +    
    2.48 +    file = ADFSfile(name, data, load_address, execution_address, length)
    2.49 +    """
    2.50 +    
    2.51 +    def __init__(self, name, data, load_address, execution_address, length):
    2.52 +    
    2.53 +        self.name = name
    2.54 +        self.data = data
    2.55 +        self.load_address = load_address
    2.56 +        self.execution_address = execution_address
    2.57 +        self.length = length
    2.58 +    
    2.59 +    def __repr__(self):
    2.60 +    
    2.61 +        return '<%s instance, "%s", at %x>' % (self.__class__, self.name, id(self))
    2.62 +    
    2.63 +    def filetype(self):
    2.64 +    
    2.65 +        return "%x" % ((self.load_address >> 8) & 0xfff)
    2.66 +
    2.67 +
    2.68  class ADFSdisc:
    2.69  
    2.70 +    """ADFSdisc
    2.71 +    
    2.72 +    disc = ADFSdisc(file_handle, verify = 0)
    2.73 +    
    2.74 +    Represents an ADFS disc image stored in the file with the specified file
    2.75 +    handle. The image is not verified by default; pass True or another
    2.76 +    non-False value to request automatic verification of the disc format.
    2.77 +    
    2.78 +    If the disc image specified cannot be read successfully, an ADFS_exception
    2.79 +    is raised.
    2.80 +    
    2.81 +    Once an ADFSdisc instance has been created, it can be used to access the
    2.82 +    contents of the disc image. The files attribute contains
    2.83 +    """
    2.84 +    
    2.85      def __init__(self, adf, verify = 0):
    2.86      
    2.87          # Log problems if the verify flag is set.
    2.88 @@ -86,7 +142,7 @@
    2.89              self.sector_size = 1024
    2.90              interleave = 0
    2.91              
    2.92 -            format = self.identify_format(adf)
    2.93 +            format = self._identify_format(adf)
    2.94              
    2.95              if format == 'D':
    2.96              
    2.97 @@ -112,7 +168,7 @@
    2.98              raise ADFS_exception, 'Please supply a .adf, .adl or .adD file.'
    2.99          
   2.100          # Read tracks
   2.101 -        self.sectors = self.read_tracks(adf, interleave)
   2.102 +        self.sectors = self._read_tracks(adf, interleave)
   2.103          
   2.104          # Close the ADF file
   2.105          adf.close()
   2.106 @@ -129,60 +185,60 @@
   2.107          
   2.108              # Find the root directory name and all the files and directories
   2.109              # contained within it
   2.110 -            self.root_name, self.files = self.read_old_catalogue(0x400)
   2.111 +            self.root_name, self.files = self._read_old_catalogue(0x400)
   2.112          
   2.113          elif self.disc_type == 'adE':
   2.114          
   2.115              # Read the disc name and map
   2.116 -            self.disc_name = self.safe(self.read_disc_info(), with_space = 1)
   2.117 +            self.disc_name = self._safe(self._read_disc_info(), with_space = 1)
   2.118              
   2.119              # Find the root directory name and all the files and directories
   2.120              # contained within it
   2.121 -            self.root_name, self.files = self.read_new_catalogue(0x800)
   2.122 +            self.root_name, self.files = self._read_new_catalogue(0x800)
   2.123          
   2.124          elif self.disc_type == 'adEbig':
   2.125          
   2.126              # Read the disc name and map
   2.127 -            self.disc_name = self.safe(self.read_disc_info(), with_space = 1)
   2.128 +            self.disc_name = self._safe(self._read_disc_info(), with_space = 1)
   2.129              
   2.130              # Find the root directory name and all the files and directories
   2.131              # contained within it
   2.132 -            self.root_name, self.files = self.read_new_catalogue(0xc8800)
   2.133 +            self.root_name, self.files = self._read_new_catalogue(0xc8800)
   2.134          
   2.135          else:
   2.136          
   2.137              # Find the root directory name and all the files and directories
   2.138              # contained within it
   2.139 -            self.root_name, self.files = self.read_old_catalogue(2*self.sector_size)
   2.140 +            self.root_name, self.files = self._read_old_catalogue(2*self.sector_size)
   2.141      
   2.142      
   2.143      # Little endian reading
   2.144      
   2.145 -    def read_signed_word(self, s):
   2.146 +    def _read_signed_word(self, s):
   2.147      
   2.148          return struct.unpack("<i", s)[0]
   2.149      
   2.150 -    def read_unsigned_word(self, s):
   2.151 +    def _read_unsigned_word(self, s):
   2.152      
   2.153          return struct.unpack("<I", s)[0]
   2.154      
   2.155 -    def read_signed_byte(self, s):
   2.156 +    def _read_signed_byte(self, s):
   2.157      
   2.158          return struct.unpack("<b", s)[0]
   2.159      
   2.160 -    def read_unsigned_byte(self, s):
   2.161 +    def _read_unsigned_byte(self, s):
   2.162      
   2.163          return struct.unpack("<B", s)[0]
   2.164      
   2.165 -    def read_unsigned_half_word(self, s):
   2.166 +    def _read_unsigned_half_word(self, s):
   2.167      
   2.168          return struct.unpack("<H", s)[0]
   2.169      
   2.170 -    def read_signed_half_word(self, s):
   2.171 +    def _read_signed_half_word(self, s):
   2.172      
   2.173          return struct.unpack("<h", s)[0]
   2.174      
   2.175 -    def str2num(self, size, s):
   2.176 +    def _str2num(self, size, s):
   2.177      
   2.178          i = 0
   2.179          n = 0
   2.180 @@ -194,7 +250,7 @@
   2.181          return n
   2.182      
   2.183      
   2.184 -    def binary(self, size, n):
   2.185 +    def _binary(self, size, n):
   2.186      
   2.187          new = ""
   2.188          while (n != 0) & (size > 0):
   2.189 @@ -213,7 +269,7 @@
   2.190          return new
   2.191      
   2.192      
   2.193 -    def identify_format(self, adf):
   2.194 +    def _identify_format(self, adf):
   2.195      
   2.196          # Look for a valid disc record when determining whether the disc
   2.197          # image represents an 800K D or E format floppy disc. First, the
   2.198 @@ -225,7 +281,7 @@
   2.199          
   2.200          # This will be done again for E format and later discs.
   2.201          
   2.202 -        record = self.read_disc_record(4)
   2.203 +        record = self._read_disc_record(4)
   2.204          
   2.205          # Define a checklist of criteria to satisfy.
   2.206          checklist = \
   2.207 @@ -351,7 +407,7 @@
   2.208              return '?'
   2.209      
   2.210      
   2.211 -    def read_disc_record(self, offset):
   2.212 +    def _read_disc_record(self, offset):
   2.213      
   2.214          # Total sectors per track (sectors * heads)
   2.215          log2_sector_size = ord(self.sectors[offset])
   2.216 @@ -382,14 +438,14 @@
   2.217              density = 'unknown'
   2.218          
   2.219          # Length of ID fields in the disc map
   2.220 -        idlen = self.read_unsigned_byte(self.sectors[offset + 4])
   2.221 +        idlen = self._read_unsigned_byte(self.sectors[offset + 4])
   2.222          # Number of bytes per map bit.
   2.223 -        bytes_per_bit = 2 ** self.read_unsigned_byte(self.sectors[offset + 5])
   2.224 +        bytes_per_bit = 2 ** self._read_unsigned_byte(self.sectors[offset + 5])
   2.225          # LowSector
   2.226          # StartUp
   2.227          # LinkBits
   2.228          # BitSize (size of ID field?)
   2.229 -        bit_size = self.read_unsigned_byte(self.sectors[offset + 6 : offset + 7])
   2.230 +        bit_size = self._read_unsigned_byte(self.sectors[offset + 6 : offset + 7])
   2.231          #print "Bit size: %s" % hex(bit_size)
   2.232          # RASkew
   2.233          # BootOpt
   2.234 @@ -397,14 +453,14 @@
   2.235          zones = ord(self.sectors[offset + 9])
   2.236          # ZoneSpare
   2.237          # RootDir
   2.238 -        root = self.str2num(3, self.sectors[offset + 13 : offset + 16]) # was 15
   2.239 +        root = self._str2num(3, self.sectors[offset + 13 : offset + 16]) # was 15
   2.240          # Identify
   2.241          # SequenceSides
   2.242          # DoubleStep
   2.243          # DiscSize
   2.244 -        disc_size = self.read_unsigned_word(self.sectors[offset + 16 : offset + 20])
   2.245 +        disc_size = self._read_unsigned_word(self.sectors[offset + 16 : offset + 20])
   2.246          # DiscId
   2.247 -        disc_id   = self.read_unsigned_half_word(self.sectors[offset + 20 : offset + 22])
   2.248 +        disc_id   = self._read_unsigned_half_word(self.sectors[offset + 20 : offset + 22])
   2.249          # DiscName
   2.250          disc_name = string.strip(self.sectors[offset + 22 : offset + 32])
   2.251          
   2.252 @@ -415,23 +471,23 @@
   2.253              'disc name': disc_name, 'zones': zones, 'root dir': root }
   2.254      
   2.255      
   2.256 -    def read_disc_info(self):
   2.257 +    def _read_disc_info(self):
   2.258      
   2.259          checksum = ord(self.sectors[0])
   2.260 -        first_free = self.read_unsigned_half_word(self.sectors[1:3])
   2.261 +        first_free = self._read_unsigned_half_word(self.sectors[1:3])
   2.262          
   2.263          if self.disc_type == 'adE':
   2.264          
   2.265 -            self.record = self.read_disc_record(4)
   2.266 +            self.record = self._read_disc_record(4)
   2.267              
   2.268              self.sector_size = self.record["sector size"]
   2.269              
   2.270              self.map_header = 0
   2.271              self.map_start, self.map_end = 0x40, 0x400
   2.272 -            self.free_space = self.read_free_space(
   2.273 +            self.free_space = self._read_free_space(
   2.274                  self.map_header, self.map_start, self.map_end
   2.275                  )
   2.276 -            self.disc_map = self.read_new_map(
   2.277 +            self.disc_map = self._read_new_map(
   2.278                  self.map_header, self.map_start, self.map_end
   2.279                  )
   2.280              
   2.281 @@ -439,16 +495,16 @@
   2.282          
   2.283          if self.disc_type == 'adEbig':
   2.284          
   2.285 -            self.record = self.read_disc_record(0xc6804)
   2.286 +            self.record = self._read_disc_record(0xc6804)
   2.287              
   2.288              self.sector_size = self.record["sector size"]
   2.289              
   2.290              self.map_header = 0xc6800
   2.291              self.map_start, self.map_end = 0xc6840, 0xc7800
   2.292 -            self.free_space = self.read_free_space(
   2.293 +            self.free_space = self._read_free_space(
   2.294                  self.map_header, self.map_start, self.map_end
   2.295                  )
   2.296 -            self.disc_map = self.read_new_map(
   2.297 +            self.disc_map = self._read_new_map(
   2.298                  self.map_header, self.map_start, self.map_end
   2.299                  )
   2.300              
   2.301 @@ -460,7 +516,7 @@
   2.302      #    zone_size = 819200 / record['zones']
   2.303      #    ids_per_zone = zone_size /
   2.304      
   2.305 -    def read_new_map(self, header, begin, end):
   2.306 +    def _read_new_map(self, header, begin, end):
   2.307      
   2.308          disc_map = {}
   2.309          
   2.310 @@ -509,7 +565,7 @@
   2.311              
   2.312                  # If there is enough space left in this zone to allow
   2.313                  # further fragments then read the next two bytes.
   2.314 -                value = self.read_unsigned_half_word(self.sectors[a:a+2])
   2.315 +                value = self._read_unsigned_half_word(self.sectors[a:a+2])
   2.316                  
   2.317                  entry = value & 0x7fff
   2.318                  
   2.319 @@ -548,10 +604,10 @@
   2.320                          # and implicitly finish reading this fragment
   2.321                          # (current_piece remains None).
   2.322                          
   2.323 -                        start_addr = self.find_address_from_map(
   2.324 +                        start_addr = self._find_address_from_map(
   2.325                              a, begin, entry
   2.326                              )
   2.327 -                        end_addr = self.find_address_from_map(
   2.328 +                        end_addr = self._find_address_from_map(
   2.329                              next, begin, entry
   2.330                              )
   2.331                          
   2.332 @@ -583,10 +639,10 @@
   2.333                      
   2.334                      # For relevant entries add the block to the list of
   2.335                      # pieces found.
   2.336 -                    start_addr = self.find_address_from_map(
   2.337 +                    start_addr = self._find_address_from_map(
   2.338                          current_start, begin, current_piece
   2.339                          )
   2.340 -                    end_addr = self.find_address_from_map(
   2.341 +                    end_addr = self._find_address_from_map(
   2.342                          next, begin, current_piece
   2.343                          )
   2.344                      
   2.345 @@ -617,7 +673,7 @@
   2.346          
   2.347          return disc_map
   2.348      
   2.349 -    def read_free_space(self, header, begin, end):
   2.350 +    def _read_free_space(self, header, begin, end):
   2.351      
   2.352          free_space = []
   2.353          
   2.354 @@ -632,7 +688,7 @@
   2.355              
   2.356              # Start by reading the offset in bits from the start of the header
   2.357              # of the first item of free space in the map.
   2.358 -            offset = self.read_unsigned_half_word(self.sectors[a:a+2])
   2.359 +            offset = self._read_unsigned_half_word(self.sectors[a:a+2])
   2.360              
   2.361              # The top bit is apparently always set, so mask it off and convert
   2.362              # the result into bytes. * Not sure if this is the case for
   2.363 @@ -652,7 +708,7 @@
   2.364              while a < next_zone:
   2.365              
   2.366                  # Read the offset to the next free fragment in this zone.
   2.367 -                offset = self.read_unsigned_half_word(self.sectors[a:a+2])
   2.368 +                offset = self._read_unsigned_half_word(self.sectors[a:a+2])
   2.369                  
   2.370                  # Convert this to a byte offset.
   2.371                  next = ((offset & 0x7fff) >> 3)
   2.372 @@ -664,7 +720,7 @@
   2.373                  
   2.374                      c = b + 1
   2.375                      
   2.376 -                    value = self.read_unsigned_byte(self.sectors[b])
   2.377 +                    value = self._read_unsigned_byte(self.sectors[b])
   2.378                      
   2.379                      if (value & 0x80) != 0:
   2.380                      
   2.381 @@ -692,7 +748,7 @@
   2.382          # Return the free space list.
   2.383          return free_space
   2.384      
   2.385 -    def find_address_from_map(self, addr, begin, entry):
   2.386 +    def _find_address_from_map(self, addr, begin, entry):
   2.387      
   2.388          if self.disc_type == 'adE':
   2.389          
   2.390 @@ -718,7 +774,7 @@
   2.391          
   2.392          return address
   2.393      
   2.394 -    def find_in_new_map(self, file_no):
   2.395 +    def _find_in_new_map(self, file_no):
   2.396      
   2.397          try:
   2.398          
   2.399 @@ -728,7 +784,7 @@
   2.400          
   2.401              return []
   2.402      
   2.403 -    def read_tracks(self, f, inter):
   2.404 +    def _read_tracks(self, f, inter):
   2.405      
   2.406          t = ""
   2.407          
   2.408 @@ -773,7 +829,7 @@
   2.409          return t
   2.410      
   2.411      
   2.412 -    def read_sectors(self, adf):
   2.413 +    def _read_sectors(self, adf):
   2.414      
   2.415          s = []
   2.416          try:
   2.417 @@ -796,7 +852,7 @@
   2.418          return s
   2.419      
   2.420      
   2.421 -    def safe(self, s, with_space = 0):
   2.422 +    def _safe(self, s, with_space = 0):
   2.423      
   2.424          new = ""
   2.425          if with_space == 1:
   2.426 @@ -819,7 +875,7 @@
   2.427          return new
   2.428      
   2.429      
   2.430 -    def read_freespace(self):
   2.431 +    def _read_freespace(self):
   2.432      
   2.433          # Currently unused
   2.434              
   2.435 @@ -829,36 +885,36 @@
   2.436          p = 0
   2.437          while self.sectors[base+p] != 0:
   2.438      
   2.439 -            free.append(self.str2num(3, self.sectors[base+p:base_p+3]))
   2.440 +            free.append(self._str2num(3, self.sectors[base+p:base+p+3]))
   2.441      
   2.442          name = self.sectors[self.sector_size-9:self.sector_size-4]
   2.443      
   2.444 -        disc_size = self.str2num(
   2.445 +        disc_size = self._str2num(
   2.446              3, self.sectors[self.sector_size-4:self.sector_size-1]
   2.447              )
   2.448      
   2.449 -        checksum0 = self.read_unsigned_byte(self.sectors[self.sector_size-1])
   2.450 +        checksum0 = self._read_unsigned_byte(self.sectors[self.sector_size-1])
   2.451      
   2.452          base = self.sector_size
   2.453      
   2.454          p = 0
   2.455          while self.sectors[base+p] != 0:
   2.456      
   2.457 -            free.append(self.str2num(3, self.sectors[base+p:base_p+3]))
   2.458 +            free.append(self._str2num(3, self.sectors[base+p:base+p+3]))
   2.459      
   2.460          name = name + \
   2.461              self.sectors[base+self.sector_size-10:base+self.sector_size-5]
   2.462      
   2.463 -        disc_id = self.str2num(
   2.464 +        disc_id = self._str2num(
   2.465              2, self.sectors[base+self.sector_size-5:base+self.sector_size-3]
   2.466              )
   2.467      
   2.468 -        boot = self.read_unsigned_byte(self.sectors[base+self.sector_size-3])
   2.469 +        boot = self._read_unsigned_byte(self.sectors[base+self.sector_size-3])
   2.470      
   2.471 -        checksum1 = self.read_unsigned_byte(self.sectors[base+self.sector_size-1])
   2.472 +        checksum1 = self._read_unsigned_byte(self.sectors[base+self.sector_size-1])
   2.473      
   2.474      
   2.475 -    def read_old_catalogue(self, base):
   2.476 +    def _read_old_catalogue(self, base):
   2.477      
   2.478          head = base
   2.479      #    base = sector_size*2
   2.480 @@ -890,28 +946,28 @@
   2.481                      top_set = counter
   2.482                  counter = counter + 1
   2.483              
   2.484 -            name = self.safe(self.sectors[head+p:head+p+10])
   2.485 +            name = self._safe(self.sectors[head+p:head+p+10])
   2.486              
   2.487 -            load = self.read_unsigned_word(self.sectors[head+p+10:head+p+14])
   2.488 -            exe = self.read_unsigned_word(self.sectors[head+p+14:head+p+18])
   2.489 -            length = self.read_unsigned_word(self.sectors[head+p+18:head+p+22])
   2.490 +            load = self._read_unsigned_word(self.sectors[head+p+10:head+p+14])
   2.491 +            exe = self._read_unsigned_word(self.sectors[head+p+14:head+p+18])
   2.492 +            length = self._read_unsigned_word(self.sectors[head+p+18:head+p+22])
   2.493              
   2.494              if self.disc_type == 'adD':
   2.495 -                inddiscadd = 256 * self.str2num(
   2.496 +                inddiscadd = 256 * self._str2num(
   2.497                      3, self.sectors[head+p+22:head+p+25]
   2.498                      )
   2.499              else:
   2.500 -                inddiscadd = self.sector_size * self.str2num(
   2.501 +                inddiscadd = self.sector_size * self._str2num(
   2.502                      3, self.sectors[head+p+22:head+p+25]
   2.503                      )
   2.504              
   2.505 -            olddirobseq = self.read_unsigned_byte(self.sectors[head+p+25])
   2.506 +            olddirobseq = self._read_unsigned_byte(self.sectors[head+p+25])
   2.507              
   2.508              #print string.expandtabs(
   2.509              #   "%s\t%s\t%s\t%s" % (
   2.510 -            #       name, "("+self.binary(8, olddirobseq)+")",
   2.511 -            #       "("+self.binary(8, load)+")",
   2.512 -            #       "("+self.binary(8, exe)+")"
   2.513 +            #       name, "("+self._binary(8, olddirobseq)+")",
   2.514 +            #       "("+self._binary(8, load)+")",
   2.515 +            #       "("+self._binary(8, exe)+")"
   2.516              #   ) )
   2.517              #print string.expandtabs(
   2.518              #   "%s\t%02x\t%08x\t%08x" % (
   2.519 @@ -926,17 +982,15 @@
   2.520                  
   2.521                      # A directory has been found.
   2.522                      lower_dir_name, lower_files = \
   2.523 -                        self.read_old_catalogue(inddiscadd)
   2.524 +                        self._read_old_catalogue(inddiscadd)
   2.525                          
   2.526 -                    files.append([name, lower_files])
   2.527 +                    files.append(ADFSdirectory(name, lower_files))
   2.528                  
   2.529                  else:
   2.530                  
   2.531                      # A file has been found.
   2.532 -                    files.append(
   2.533 -                        [ name, self.sectors[inddiscadd:inddiscadd+length],
   2.534 -                          load, exe, length ]
   2.535 -                        )
   2.536 +                    data = self.sectors[inddiscadd:inddiscadd+length]
   2.537 +                    files.append(ADFSfile(name, data, load, exe, length))
   2.538              
   2.539              else:
   2.540              
   2.541 @@ -947,17 +1001,15 @@
   2.542                  
   2.543                      # A directory has been found.
   2.544                      lower_dir_name, lower_files = \
   2.545 -                        self.read_old_catalogue(inddiscadd)
   2.546 +                        self._read_old_catalogue(inddiscadd)
   2.547                      
   2.548 -                    files.append([name, lower_files])
   2.549 +                    files.append(ADFSdirectory(name, lower_files))
   2.550                  
   2.551                  else:
   2.552                  
   2.553                      # A file has been found.
   2.554 -                    files.append(
   2.555 -                        [ name, self.sectors[inddiscadd:inddiscadd+length],
   2.556 -                          load, exe, length ]
   2.557 -                        )
   2.558 +                    data = self.sectors[inddiscadd:inddiscadd+length]
   2.559 +                    files.append(ADFSfile(name, data, load, exe, length))
   2.560              
   2.561              p = p + 26
   2.562          
   2.563 @@ -985,11 +1037,11 @@
   2.564          # Read the directory name, its parent and any title given.
   2.565          if self.disc_type == 'adD':
   2.566          
   2.567 -            dir_name = self.safe(
   2.568 +            dir_name = self._safe(
   2.569                  self.sectors[tail+self.sector_size-16:tail+self.sector_size-6]
   2.570                  )
   2.571              
   2.572 -            parent = 256*self.str2num(
   2.573 +            parent = 256*self._str2num(
   2.574                  3,
   2.575                  self.sectors[tail+self.sector_size-38:tail+self.sector_size-35]
   2.576                  )
   2.577 @@ -998,16 +1050,16 @@
   2.578                  self.sectors[tail+self.sector_size-35:tail+self.sector_size-16]
   2.579          else:
   2.580          
   2.581 -            dir_name = self.safe(
   2.582 +            dir_name = self._safe(
   2.583                  self.sectors[tail+self.sector_size-52:tail+self.sector_size-42]
   2.584                  )
   2.585              
   2.586 -            parent = self.sector_size*self.str2num(
   2.587 +            parent = self.sector_size*self._str2num(
   2.588                  3,
   2.589                  self.sectors[tail+self.sector_size-42:tail+self.sector_size-39]
   2.590                  )
   2.591              
   2.592 -            dir_title = self.safe(
   2.593 +            dir_title = self._safe(
   2.594                  self.sectors[tail+self.sector_size-39:tail+self.sector_size-20]
   2.595                  )
   2.596          
   2.597 @@ -1016,7 +1068,7 @@
   2.598              # Use the directory title as the disc name.
   2.599              
   2.600              # Note that the title may contain spaces.
   2.601 -            self.disc_name = self.safe(dir_title, with_space = 1)
   2.602 +            self.disc_name = self._safe(dir_title, with_space = 1)
   2.603          
   2.604          #print "Directory title", dir_title
   2.605          #print "Directory name ", dir_name
   2.606 @@ -1037,11 +1089,11 @@
   2.607          return dir_name, files
   2.608      
   2.609      
   2.610 -    def read_new_address(self, s):
   2.611 +    def _read_new_address(self, s):
   2.612      
   2.613          # From the three character string passed, determine the address on the
   2.614          # disc.
   2.615 -        value = self.str2num(3, s)
   2.616 +        value = self._str2num(3, s)
   2.617          
   2.618          # This is a SIN (System Internal Number)
   2.619          # The bottom 8 bits are the sector offset + 1
   2.620 @@ -1059,8 +1111,8 @@
   2.621          
   2.622          # The pieces of the object are returned as a list of pairs of
   2.623          # addresses.
   2.624 -        #pieces = self.find_in_new_map(self.map_start, self.map_end, file_no)
   2.625 -        pieces = self.find_in_new_map(file_no)
   2.626 +        #pieces = self._find_in_new_map(self.map_start, self.map_end, file_no)
   2.627 +        pieces = self._find_in_new_map(file_no)
   2.628          
   2.629          #print map(lambda x: map(hex, x), pieces)
   2.630          
   2.631 @@ -1076,7 +1128,7 @@
   2.632          return pieces
   2.633      
   2.634      
   2.635 -    def read_new_catalogue(self, base):
   2.636 +    def _read_new_catalogue(self, base):
   2.637      
   2.638          head = base
   2.639          p = 0
   2.640 @@ -1110,22 +1162,22 @@
   2.641                      top_set = counter
   2.642                  counter = counter + 1
   2.643              
   2.644 -            name = self.safe(self.sectors[head+p:head+p+10])
   2.645 +            name = self._safe(self.sectors[head+p:head+p+10])
   2.646              
   2.647              #print hex(head+p), name
   2.648              
   2.649 -            load = self.read_unsigned_word(self.sectors[head+p+10:head+p+14])
   2.650 -            exe = self.read_unsigned_word(self.sectors[head+p+14:head+p+18])
   2.651 -            length = self.read_unsigned_word(self.sectors[head+p+18:head+p+22])
   2.652 +            load = self._read_unsigned_word(self.sectors[head+p+10:head+p+14])
   2.653 +            exe = self._read_unsigned_word(self.sectors[head+p+14:head+p+18])
   2.654 +            length = self._read_unsigned_word(self.sectors[head+p+18:head+p+22])
   2.655              
   2.656              #print hex(ord(self.sectors[head+p+22])), \
   2.657              #        hex(ord(self.sectors[head+p+23])), \
   2.658              #        hex(ord(self.sectors[head+p+24]))
   2.659              
   2.660 -            inddiscadd = self.read_new_address(
   2.661 +            inddiscadd = self._read_new_address(
   2.662                  self.sectors[head+p+22:head+p+25]
   2.663                  )
   2.664 -            newdiratts = self.read_unsigned_byte(self.sectors[head+p+25])
   2.665 +            newdiratts = self._read_unsigned_byte(self.sectors[head+p+25])
   2.666              
   2.667              if inddiscadd == -1:
   2.668              
   2.669 @@ -1141,7 +1193,7 @@
   2.670                              )
   2.671                          self.verify_log.append( (
   2.672                              WARNING, "    file details: %x" % \
   2.673 -                            self.str2num(3, self.sectors[head+p+22:head+p+25])
   2.674 +                            self._str2num(3, self.sectors[head+p+22:head+p+25])
   2.675                              ) )
   2.676                          self.verify_log.append(
   2.677                              (WARNING, "    atts: %x" % newdiratts)
   2.678 @@ -1160,7 +1212,7 @@
   2.679                          self.verify_log.append( (
   2.680                              WARNING,
   2.681                              "    file details: %x" % \
   2.682 -                            self.str2num(3, self.sectors[head+p+22:head+p+25])
   2.683 +                            self._str2num(3, self.sectors[head+p+22:head+p+25])
   2.684                              ) )
   2.685                          self.verify_log.append(
   2.686                              (WARNING, "    atts: %x" % newdiratts)
   2.687 @@ -1170,7 +1222,7 @@
   2.688                  
   2.689                      # Store a zero length file. This appears to be the
   2.690                      # standard behaviour for storing empty files.
   2.691 -                    files.append([name, "", load, exe, length])
   2.692 +                    files.append(ADFSfile(name, "", load, exe, length))
   2.693                      
   2.694                      #print hex(head+p), hex(head+p+22)
   2.695              
   2.696 @@ -1200,10 +1252,10 @@
   2.697                          # as a directory.
   2.698                          
   2.699                          lower_dir_name, lower_files = \
   2.700 -                            self.read_new_catalogue(start)
   2.701 +                            self._read_new_catalogue(start)
   2.702                          
   2.703                          # Store the directory name and file found therein.
   2.704 -                        files.append([name, lower_files])
   2.705 +                        files.append(ADFSdirectory(name, lower_files))
   2.706                  
   2.707                  else:
   2.708                  
   2.709 @@ -1223,7 +1275,7 @@
   2.710                          file = file + self.sectors[start : (start + amount)]
   2.711                          remaining = remaining - amount
   2.712                      
   2.713 -                    files.append([name, file, load, exe, length])
   2.714 +                    files.append(ADFSfile(name, file, load, exe, length))
   2.715              
   2.716              p = p + 26
   2.717          
   2.718 @@ -1246,11 +1298,11 @@
   2.719              
   2.720              return '', files
   2.721          
   2.722 -        dir_name = self.safe(
   2.723 +        dir_name = self._safe(
   2.724              self.sectors[tail+self.sector_size-16:tail+self.sector_size-6]
   2.725              )
   2.726          
   2.727 -        #parent = self.read_new_address(
   2.728 +        #parent = self._read_new_address(
   2.729          #    self.sectors[tail+self.sector_size-38:tail+self.sector_size-35], dir = 1
   2.730          #    )
   2.731          #print "This directory:", hex(head), "Parent:", hex(parent)
   2.732 @@ -1258,7 +1310,7 @@
   2.733          parent = \
   2.734              self.sectors[tail+self.sector_size-38:tail+self.sector_size-35]
   2.735          
   2.736 -        #256*self.str2num(
   2.737 +        #256*self._str2num(
   2.738          #   3, self.sectors[tail+self.sector_size-38:tail+self.sector_size-35]
   2.739          #)
   2.740          
   2.741 @@ -1289,8 +1341,10 @@
   2.742          return dir_name, files
   2.743      
   2.744      
   2.745 -    def read_leafname(self, path):
   2.746 +    def _read_leafname(self, path):
   2.747      
   2.748 +        # Unused
   2.749 +        
   2.750          pos = string.rfind(path, os.sep)
   2.751          if pos != -1:
   2.752              return path[pos+1:]
   2.753 @@ -1298,9 +1352,28 @@
   2.754              return path
   2.755      
   2.756      
   2.757 -    def print_catalogue(self, files = None, path = "$", filetypes = 0,
   2.758 -                        separator = ","):
   2.759 +    def print_catalogue(self, files = None, path = "$", filetypes = 0):
   2.760      
   2.761 +        """print_catalogue(self, files = None, path = "$", filetypes = 0)
   2.762 +        
   2.763 +        Prints the contents of the disc catalogue to standard output.
   2.764 +        Usually, this method is called without specifying any of the keyword
   2.765 +        arguments, but these can be used to customise the output.
   2.766 +        
   2.767 +        If files is None, the contents of the entire disc will be shown.
   2.768 +        A subset of the list of files obtained from the instance's files
   2.769 +        attribute can be passed if only a subset of the catalogue needs to
   2.770 +        be displayed.
   2.771 +        
   2.772 +        The path parameter specifies the representation of the root directory
   2.773 +        in the output. By default, root directories are represented by the
   2.774 +        familiar "$" symbol.
   2.775 +        
   2.776 +        If filetypes is set to True or a non-False value, the file types of
   2.777 +        each file will be displayed; otherwise, load and execution addresses
   2.778 +        will be displayed instead.
   2.779 +        """
   2.780 +        
   2.781          if files is None:
   2.782          
   2.783              files = self.files
   2.784 @@ -1309,19 +1382,18 @@
   2.785          
   2.786              print path, "(empty)"
   2.787          
   2.788 -        for i in files:
   2.789 +        for obj in files:
   2.790      
   2.791 -            name = i[0]
   2.792 -            if type(i[1]) != type([]):
   2.793 +            name = obj.name
   2.794 +            if isinstance(obj, ADFSfile):
   2.795              
   2.796 -                load, exec_addr, length = i[2], i[3], i[4]
   2.797 -                
   2.798 -                if filetypes == 0:
   2.799 +                if not filetypes:
   2.800                  
   2.801                      # Load and execution addresses treated as valid.
   2.802                      print string.expandtabs(
   2.803                          "%s.%s\t%X\t%X\t%X" % (
   2.804 -                            path, name, load, exec_addr, length
   2.805 +                            path, name, obj.load_address,
   2.806 +                            obj.execution_address, obj.length
   2.807                              ), 16
   2.808                          )
   2.809                  
   2.810 @@ -1329,17 +1401,17 @@
   2.811                  
   2.812                      # Load address treated as a filetype.
   2.813                      print string.expandtabs(
   2.814 -                        "%s.%s\t%X\t%X" % (
   2.815 -                            path, name, ((load >> 8) & 0xfff), length
   2.816 +                        "%s.%s\t%s\t%X" % (
   2.817 +                            path, name, obj.filetype().upper(), obj.length
   2.818                              ), 16
   2.819                          )
   2.820              
   2.821              else:
   2.822              
   2.823 -                self.print_catalogue(i[1], path + "." + name, filetypes)
   2.824 +                self.print_catalogue(obj.files, path + "." + name, filetypes)
   2.825      
   2.826      
   2.827 -    def convert_name(self, old_name, convert_dict):
   2.828 +    def _convert_name(self, old_name, convert_dict):
   2.829      
   2.830          # Use the conversion dictionary to convert any forbidden
   2.831          # characters to accepted local substitutes.
   2.832 @@ -1364,10 +1436,10 @@
   2.833          
   2.834          return name
   2.835      
   2.836 -    def extract_old_files(self, l, path, filetypes = 0, separator = ",",
   2.837 -                          convert_dict = {}):
   2.838 +    def _extract_old_files(self, objects, path, filetypes = 0, separator = ",",
   2.839 +                           convert_dict = {}):
   2.840      
   2.841 -        new_path = self.create_directory(path)
   2.842 +        new_path = self._create_directory(path)
   2.843          
   2.844          if new_path != "":
   2.845          
   2.846 @@ -1377,18 +1449,17 @@
   2.847          
   2.848              return
   2.849          
   2.850 -        for i in l:
   2.851 +        for obj in objects:
   2.852          
   2.853 -            old_name = i[0]
   2.854 +            old_name = obj.name
   2.855              
   2.856 -            name = self.convert_name(old_name, convert_dict)
   2.857 +            name = self._convert_name(old_name, convert_dict)
   2.858              
   2.859 -            if type(i[1]) != type([]):
   2.860 +            if isinstance(obj, ADFSfile):
   2.861              
   2.862                  # A file.
   2.863 -                load, exec_addr, length = i[2], i[3], i[4]
   2.864                  
   2.865 -                if filetypes == 0:
   2.866 +                if not filetypes:
   2.867                  
   2.868                      # Load and execution addresses assumed to be valid.
   2.869                      
   2.870 @@ -1398,16 +1469,17 @@
   2.871                      
   2.872                      try:
   2.873                          out = open(out_file, "wb")
   2.874 -                        out.write(i[1])
   2.875 +                        out.write(obj.data)
   2.876                          out.close()
   2.877                      except IOError:
   2.878                          print "Couldn't open the file: %s" % out_file
   2.879                      
   2.880                      try:
   2.881                          inf = open(inf_file, "w")
   2.882 -                        load, exec_addr, length = i[2], i[3], i[4]
   2.883 -                        inf.write( "$.%s\t%X\t%X\t%X" % \
   2.884 -                                   ( name, load, exec_addr, length ) )
   2.885 +                        inf.write("$.%s\t%X\t%X\t%X" % (
   2.886 +                            name, obj.load_address, obj.execution_address,
   2.887 +                            obj.length
   2.888 +                            ))
   2.889                          inf.close()
   2.890                      except IOError:
   2.891                          print "Couldn't open the file: %s" % inf_file
   2.892 @@ -1415,12 +1487,11 @@
   2.893                  else:
   2.894                  
   2.895                      # Interpret the load address as a filetype.
   2.896 -                    out_file = os.path.join(path, name) + separator + "%x" % \
   2.897 -                               ((load >> 8) & 0xfff)
   2.898 +                    out_file = os.path.join(path, name) + separator + obj.filetype()
   2.899                      
   2.900                      try:
   2.901                          out = open(out_file, "wb")
   2.902 -                        out.write(i[1])
   2.903 +                        out.write(obj.data)
   2.904                          out.close()
   2.905                      except IOError:
   2.906                          print "Couldn't open the file: %s" % out_file
   2.907 @@ -1428,15 +1499,15 @@
   2.908              
   2.909                  new_path = os.path.join(path, name)
   2.910                  
   2.911 -                self.extract_old_files(
   2.912 -                    i[1], new_path, filetypes, separator, convert_dict
   2.913 +                self._extract_old_files(
   2.914 +                    obj.files, new_path, filetypes, separator, convert_dict
   2.915                      )
   2.916      
   2.917      
   2.918 -    def extract_new_files(self, l, path, filetypes = 0, separator = ",",
   2.919 -                          convert_dict = {}):
   2.920 +    def _extract_new_files(self, objects, path, filetypes = 0, separator = ",",
   2.921 +                           convert_dict = {}):
   2.922      
   2.923 -        new_path = self.create_directory(path)
   2.924 +        new_path = self._create_directory(path)
   2.925          
   2.926          if new_path != "":
   2.927          
   2.928 @@ -1446,20 +1517,19 @@
   2.929          
   2.930              return
   2.931          
   2.932 -        for i in l:
   2.933 +        for obj in objects:
   2.934          
   2.935 -            old_name = i[0]
   2.936 +            old_name = obj.name
   2.937              
   2.938              # Use the conversion dictionary to convert any forbidden
   2.939              # characters to accepted local substitutes.
   2.940 -            name = self.convert_name(old_name, convert_dict)
   2.941 +            name = self._convert_name(old_name, convert_dict)
   2.942              
   2.943 -            if type(i[1]) != type([]):
   2.944 +            if isinstance(obj, ADFSfile):
   2.945              
   2.946                  # A file.
   2.947 -                load, exec_addr, length = i[2], i[3], i[4]
   2.948                  
   2.949 -                if filetypes == 0:
   2.950 +                if not filetypes:
   2.951                  
   2.952                      # Load and execution addresses assumed to be valid.
   2.953                      
   2.954 @@ -1469,28 +1539,28 @@
   2.955                      
   2.956                      try:
   2.957                          out = open(out_file, "wb")
   2.958 -                        out.write(i[1])
   2.959 +                        out.write(obj.data)
   2.960                          out.close()
   2.961                      except IOError:
   2.962                          print "Couldn't open the file: %s" % out_file
   2.963                      
   2.964                      try:
   2.965                          inf = open(inf_file, "w")
   2.966 -                        load, exec_addr, length = i[2], i[3], i[4]
   2.967 -                        inf.write( "$.%s\t%X\t%X\t%X" % \
   2.968 -                                   ( name, load, exec_addr, length ) )
   2.969 +                        inf.write("$.%s\t%X\t%X\t%X" % (
   2.970 +                            name, obj.load_address, obj.execution_address,
   2.971 +                            obj.length
   2.972 +                            ))
   2.973                          inf.close()
   2.974                      except IOError:
   2.975                          print "Couldn't open the file: %s" % inf_file
   2.976                  else:
   2.977                  
   2.978                      # Interpret the load address as a filetype.
   2.979 -                    out_file = path + os.sep + name + separator + "%x" % \
   2.980 -                               ((load >> 8) & 0xfff)
   2.981 +                    out_file = path + os.sep + name + separator + obj.filetype()
   2.982                      
   2.983                      try:
   2.984                          out = open(out_file, "wb")
   2.985 -                        out.write(i[1])
   2.986 +                        out.write(obj.data)
   2.987                          out.close()
   2.988                      except IOError:
   2.989                          print "Couldn't open the file: %s" % out_file
   2.990 @@ -1498,43 +1568,62 @@
   2.991              
   2.992                  new_path = os.path.join(path, name)
   2.993                  
   2.994 -                self.extract_new_files(
   2.995 -                    i[1], new_path, filetypes, separator, convert_dict
   2.996 +                self._extract_new_files(
   2.997 +                    obj.files, new_path, filetypes, separator, convert_dict
   2.998                      )
   2.999      
  2.1000      
  2.1001      def extract_files(self, out_path, files = None, filetypes = 0,
  2.1002                        separator = ",", convert_dict = {}):
  2.1003      
  2.1004 +        """extract_files(self, out_path, files = None, filetypes = 0,
  2.1005 +                         separator = ",", convert_dict = {})
  2.1006 +        
  2.1007 +        Extracts the files stored in the disc image into a directory structure
  2.1008 +        stored on the path specified by out_path.
  2.1009 +        
  2.1010 +        The files parameter specified a list of ADFSfile or ADFSdirectory
  2.1011 +        instances to extract to the target file system. This keyword argument
  2.1012 +        can be omitted if all files and directories in the disc image are to
  2.1013 +        be extracted.
  2.1014 +        
  2.1015 +        If the filetypes keyword argument is set to True, or another non-False
  2.1016 +        value, file type suffixes are appended to each file created using the
  2.1017 +        separator string supplied to join the file name to the file type.
  2.1018 +        
  2.1019 +        The convert_dict parameter can be used to specify a mapping between
  2.1020 +        characters used in ADFS file names and those on the target file system.
  2.1021 +        """
  2.1022 +        
  2.1023          if files is None:
  2.1024          
  2.1025              files = self.files
  2.1026          
  2.1027          if self.disc_type == 'adD':
  2.1028          
  2.1029 -            self.extract_old_files(
  2.1030 +            self._extract_old_files(
  2.1031                  files, out_path, filetypes, separator, convert_dict
  2.1032                  )
  2.1033          
  2.1034          elif self.disc_type == 'adE':
  2.1035          
  2.1036 -            self.extract_new_files(
  2.1037 +            self._extract_new_files(
  2.1038                  files, out_path, filetypes, separator, convert_dict
  2.1039                  )
  2.1040          
  2.1041          elif self.disc_type == 'adEbig':
  2.1042          
  2.1043 -            self.extract_new_files(
  2.1044 +            self._extract_new_files(
  2.1045                  files, out_path, filetypes, separator, convert_dict
  2.1046                  )
  2.1047          
  2.1048          else:
  2.1049          
  2.1050 -            self.extract_old_files(
  2.1051 +            self._extract_old_files(
  2.1052                  files, out_path, filetypes, separator, convert_dict
  2.1053                  )
  2.1054      
  2.1055 -    def create_directory(self, path, name = None):
  2.1056 +    def _create_directory(self, path, name = None):
  2.1057      
  2.1058          elements = []
  2.1059          
  2.1060 @@ -1589,9 +1678,9 @@
  2.1061          # Success
  2.1062          return built
  2.1063      
  2.1064 -    def plural(self, msg, values, words):
  2.1065 +    def _plural(self, msg, values, words):
  2.1066      
  2.1067 -        """message = plural(self, msg, values, words)
  2.1068 +        """message = _plural(self, msg, values, words)
  2.1069          
  2.1070          Return a message which takes into account the plural form of
  2.1071          words in the original message, assuming that the appropriate
  2.1072 @@ -1634,7 +1723,7 @@
  2.1073          
  2.1074          if hasattr(self, "disc_map") and self.disc_map.has_key(1):
  2.1075          
  2.1076 -            print self.plural(
  2.1077 +            print self._plural(
  2.1078                  "%i mapped %s found.", [len(self.disc_map[1])],
  2.1079                  [("defects", "defect", "defects")]
  2.1080                  )