python-adfs
changeset 52:a9449aa046e1
Added support for character translations in filenames. Defects are now
treated like normal files and directories; the number of mapped defects is
displayed before the log of verification faults.
| author | David Boddie <david@boddie.org.uk> |
|---|---|
| date | Sun Jul 20 00:24:28 2003 +0200 |
| parents | a8eead849e21 |
| children | f8ff54851fbc |
| files | ADFSlib.py |
| diffstat | 1 files changed, 170 insertions(+), 72 deletions(-) [+] |
line diff
1.1 --- a/ADFSlib.py Sat Jul 19 21:42:57 2003 +0200 1.2 +++ b/ADFSlib.py Sun Jul 20 00:24:28 2003 +0200 1.3 @@ -26,8 +26,8 @@ 1.4 """ 1.5 1.6 __author__ = "David Boddie <david@boddie.org.uk>" 1.7 -__date__ = "Thu 3rd July 2003" 1.8 -__version__ = "0.14" 1.9 +__date__ = "Sat 19th July 2003" 1.10 +__version__ = "0.20" 1.11 1.12 1.13 import os, string 1.14 @@ -127,7 +127,7 @@ 1.15 1.16 # Read the disc name and map 1.17 self.disc_name = self.read_disc_info() 1.18 - 1.19 + 1.20 # Find the root directory name and all the files and directories 1.21 # contained within it 1.22 self.root_name, self.files = self.read_new_catalogue(0x800) 1.23 @@ -136,12 +136,13 @@ 1.24 1.25 # Read the disc name and map 1.26 self.disc_name = self.read_disc_info() 1.27 - 1.28 + 1.29 # Find the root directory name and all the files and directories 1.30 # contained within it 1.31 self.root_name, self.files = self.read_new_catalogue(0xc8800) 1.32 1.33 else: 1.34 + 1.35 # Find the root directory name and all the files and directories 1.36 # contained within it 1.37 self.root_name, self.files = self.read_old_catalogue(2*self.sector_size) 1.38 @@ -152,10 +153,10 @@ 1.39 i = 0 1.40 n = 0 1.41 while i < size: 1.42 - 1.43 + 1.44 n = n | (ord(s[i]) << (i*8)) 1.45 i = i + 1 1.46 - 1.47 + 1.48 return n 1.49 1.50 1.51 @@ -168,13 +169,13 @@ 1.52 new = "1" + new 1.53 else: 1.54 new = "0" + new 1.55 - 1.56 + 1.57 n = n >> 1 1.58 size = size - 1 1.59 - 1.60 + 1.61 if size > 0: 1.62 new = ("0"*size) + new 1.63 - 1.64 + 1.65 return new 1.66 1.67 1.68 @@ -187,7 +188,7 @@ 1.69 adf.seek(0x801) 1.70 word2 = adf.read(4) 1.71 adf.seek(0) 1.72 - 1.73 + 1.74 if word1 == 'Hugo': 1.75 return 'D' 1.76 elif word2 == 'Nick': 1.77 @@ -214,7 +215,7 @@ 1.78 density = 'quad' # Quad density disc 1.79 else: 1.80 density = 'unknown' 1.81 - 1.82 + 1.83 # LowSector 1.84 # StartUp 1.85 # LinkBits 1.86 @@ -237,7 +238,7 @@ 1.87 disc_id = self.str2num(2, self.sectors[offset + 20 : offset + 22]) 1.88 # DiscName 1.89 disc_name = string.strip(self.sectors[offset + 22 : offset + 32]) 1.90 - 1.91 + 1.92 return {'sectors': nsectors, 'heads': heads, 'density': density, 1.93 'disc size': disc_size, 'disc ID': disc_id, 1.94 'disc name': disc_name, 'zones': zones, 'root': root} 1.95 @@ -263,7 +264,7 @@ 1.96 return self.record['disc name'] #, map 1.97 1.98 if self.disc_type == 'adEbig': 1.99 - 1.100 + 1.101 self.record = self.read_disc_record(0xc6804) 1.102 self.map_header = 0xc6800 1.103 self.map_start, self.map_end = 0xc6840, 0xc7800 1.104 @@ -285,7 +286,7 @@ 1.105 def read_new_map(self, header, begin, end): 1.106 1.107 disc_map = {} 1.108 - 1.109 + 1.110 a = begin 1.111 1.112 current_piece = None 1.113 @@ -304,7 +305,8 @@ 1.114 1.115 if (a % self.sector_size) < 4: 1.116 1.117 - # In a zone header. 1.118 + # In a zone header. Not the first zone header as this 1.119 + # was already skipped when we started reading. 1.120 next = a + 4 - (a % self.sector_size) 1.121 1.122 # Set the next zone offset. 1.123 @@ -328,6 +330,8 @@ 1.124 1.125 elif current_piece is None and (next_zone - a) >= 2: 1.126 1.127 + # If there is enough space left in this zone to allow 1.128 + # further fragments then read the next two bytes. 1.129 value = self.str2num(2, self.sectors[a:a+2]) 1.130 1.131 entry = value & 0x7fff 1.132 @@ -336,14 +340,14 @@ 1.133 # the disc address and hence the file number. 1.134 # i.e.the top bit of the file number cannot be set. 1.135 1.136 - if entry == 1: 1.137 + #if entry == 1: 1.138 + # 1.139 + # # File number 1 (defect) 1.140 + # next = a + 2 1.141 + # 1.142 + if entry >= 1: 1.143 1.144 - # File number 1 (defect) 1.145 - next = a + 2 1.146 - 1.147 - elif entry > 1: 1.148 - 1.149 - # Files or directories (greater than 1) 1.150 + # Defects (1), files or directories (greater than 1) 1.151 next = a + 2 1.152 1.153 # Define a new entry. 1.154 @@ -351,6 +355,7 @@ 1.155 1.156 if not disc_map.has_key(entry): 1.157 1.158 + # Create a new map entry if none exists. 1.159 disc_map[entry] = [] 1.160 1.161 if (value & 0x8000) == 0: 1.162 @@ -363,7 +368,8 @@ 1.163 1.164 # For an immediately terminated fragment, add the 1.165 # extents of the block to the list of pieces found 1.166 - # and implicitly finish reading this fragment. 1.167 + # and implicitly finish reading this fragment 1.168 + # (current_piece remains None). 1.169 1.170 start_addr = self.find_address_from_map( 1.171 a, begin, entry 1.172 @@ -413,6 +419,7 @@ 1.173 [ start_addr, end_addr] 1.174 ) 1.175 1.176 + # Look for a new fragment. 1.177 current_piece = None 1.178 1.179 else: 1.180 @@ -540,27 +547,28 @@ 1.181 def read_tracks(self, f, inter): 1.182 1.183 t = "" 1.184 - 1.185 + 1.186 if inter==0: 1.187 try: 1.188 for i in range(0, self.ntracks): 1.189 - 1.190 + 1.191 t = t + f.read(self.nsectors * self.sector_size) 1.192 - 1.193 + 1.194 except IOError: 1.195 print 'Less than %i tracks found.' % self.ntracks 1.196 f.close() 1.197 raise ADFS_exception, \ 1.198 'Less than %i tracks found.' % self.ntracks 1.199 - 1.200 + 1.201 else: 1.202 - 1.203 + 1.204 # Tracks are interleaved (0 80 1 81 2 82 ... 79 159) so rearrange 1.205 # them into the form (0 1 2 3 ... 159) 1.206 - 1.207 + 1.208 try: 1.209 + 1.210 for i in range(0, self.ntracks): 1.211 - 1.212 + 1.213 if i < (self.ntracks >> 1): 1.214 f.seek(i*2*self.nsectors*self.sector_size, 0) 1.215 t = t + f.read(self.nsectors*self.sector_size) 1.216 @@ -568,12 +576,14 @@ 1.217 j = i - (self.ntracks >> 1) 1.218 f.seek(((j*2)+1)*self.nsectors*self.sector_size, 0) 1.219 t = t + f.read(self.nsectors*self.sector_size) 1.220 + 1.221 except IOError: 1.222 + 1.223 print 'Less than %i tracks found.' % self.ntracks 1.224 f.close() 1.225 raise ADFS_exception, \ 1.226 'Less than %i tracks found.' % self.ntracks 1.227 - 1.228 + 1.229 return t 1.230 1.231 1.232 @@ -581,22 +591,22 @@ 1.233 1.234 s = [] 1.235 try: 1.236 - 1.237 + 1.238 for i in range(0, self.ntracks): 1.239 - 1.240 + 1.241 for j in range(0, self.nsectors): 1.242 - 1.243 + 1.244 s.append(adf.read(self.sector_size)) 1.245 - 1.246 + 1.247 except IOError: 1.248 - 1.249 + 1.250 print 'Less than %i tracks x %i sectors found.' % \ 1.251 (self.ntracks, self.nsectors) 1.252 adf.close() 1.253 raise ADFS_exception, \ 1.254 'Less than %i tracks x %i sectors found.' % \ 1.255 (self.ntracks, self.nsectors) 1.256 - 1.257 + 1.258 return s 1.259 1.260 1.261 @@ -609,9 +619,10 @@ 1.262 lower = 32 1.263 1.264 for i in s: 1.265 + 1.266 if ord(i) <= lower: 1.267 break 1.268 - 1.269 + 1.270 if ord(i) >= 128: 1.271 c = ord(i)^128 1.272 if c > 32: 1.273 @@ -623,7 +634,7 @@ 1.274 1.275 1.276 def read_freespace(self): 1.277 - 1.278 + 1.279 # Currently unused 1.280 1.281 base = 0 1.282 @@ -676,13 +687,13 @@ 1.283 self.verify_log.append('Not a directory: %x' % head) 1.284 1.285 return "", [] 1.286 - 1.287 + 1.288 p = p + 5 1.289 - 1.290 + 1.291 files = [] 1.292 - 1.293 + 1.294 while ord(self.sectors[head+p]) != 0: 1.295 - 1.296 + 1.297 old_name = self.sectors[head+p:head+p+10] 1.298 top_set = 0 1.299 counter = 1 1.300 @@ -690,13 +701,13 @@ 1.301 if (ord(i) & 128) != 0: 1.302 top_set = counter 1.303 counter = counter + 1 1.304 - 1.305 + 1.306 name = self.safe(self.sectors[head+p:head+p+10]) 1.307 - 1.308 + 1.309 load = self.str2num(4, self.sectors[head+p+10:head+p+14]) 1.310 exe = self.str2num(4, self.sectors[head+p+14:head+p+18]) 1.311 length = self.str2num(4, self.sectors[head+p+18:head+p+22]) 1.312 - 1.313 + 1.314 if self.disc_type == 'adD': 1.315 inddiscadd = 256 * self.str2num( 1.316 3, self.sectors[head+p+22:head+p+25] 1.317 @@ -705,9 +716,9 @@ 1.318 inddiscadd = self.sector_size * self.str2num( 1.319 3, self.sectors[head+p+22:head+p+25] 1.320 ) 1.321 - 1.322 + 1.323 olddirobseq = self.str2num(1, self.sectors[head+p+25]) 1.324 - 1.325 + 1.326 #print string.expandtabs( 1.327 # "%s\t%s\t%s\t%s" % ( 1.328 # name, "("+self.binary(8, olddirobseq)+")", 1.329 @@ -718,7 +729,7 @@ 1.330 # "%s\t%02x\t%08x\t%08x" % ( 1.331 # name, olddirobseq, load, exe 1.332 # ) ) 1.333 - 1.334 + 1.335 if self.disc_type == 'adD': 1.336 1.337 # Old format 800K discs. 1.338 @@ -738,6 +749,7 @@ 1.339 [ name, self.sectors[inddiscadd:inddiscadd+length], 1.340 load, exe, length ] 1.341 ) 1.342 + 1.343 else: 1.344 1.345 # Old format < 800K discs. 1.346 @@ -763,12 +775,12 @@ 1.347 1.348 1.349 # Go to tail of directory structure (0x200 -- 0x700) 1.350 - 1.351 + 1.352 if self.disc_type == 'adD': 1.353 tail = head + self.sector_size # 1024 bytes 1.354 else: 1.355 tail = head + (self.sector_size*4) # 1024 bytes 1.356 - 1.357 + 1.358 dir_end = self.sectors[tail+self.sector_size-5:tail+self.sector_size-1] 1.359 if dir_end != 'Hugo': 1.360 1.361 @@ -780,7 +792,7 @@ 1.362 ) 1.363 1.364 return '', files 1.365 - 1.366 + 1.367 # Read the directory name, its parent and any title given. 1.368 if self.disc_type == 'adD': 1.369 1.370 @@ -816,10 +828,10 @@ 1.371 1.372 # Note that the title may contain spaces. 1.373 self.disc_name = self.safe(dir_title, with_space = 1) 1.374 - 1.375 - # print "Directory title", dir_title 1.376 - # print "Directory name ", dir_name 1.377 - 1.378 + 1.379 + #print "Directory title", dir_title 1.380 + #print "Directory name ", dir_name 1.381 + 1.382 endseq = self.sectors[tail+self.sector_size-6] 1.383 if endseq != dir_seq: 1.384 1.385 @@ -831,7 +843,7 @@ 1.386 ) 1.387 1.388 return dir_name, files 1.389 - 1.390 + 1.391 return dir_name, files 1.392 1.393 1.394 @@ -966,7 +978,7 @@ 1.395 #print hex(head+p), hex(head+p+22) 1.396 1.397 else: 1.398 - 1.399 + 1.400 if (newdiratts & 0x8) != 0: 1.401 1.402 #print '%s -> %s' % (name, hex(inddiscadd)) 1.403 @@ -1015,7 +1027,7 @@ 1.404 remaining = remaining - amount 1.405 1.406 files.append([name, file, load, exe, length]) 1.407 - 1.408 + 1.409 p = p + 26 1.410 1.411 1.412 @@ -1123,7 +1135,32 @@ 1.413 self.print_catalogue(i[1], path + "." + name, filetypes) 1.414 1.415 1.416 - def extract_old_files(self, l, path, filetypes = 0, separator = ","): 1.417 + def convert_name(self, old_name, convert_dict): 1.418 + 1.419 + # Use the conversion dictionary to convert any forbidden 1.420 + # characters to accepted local substitutes. 1.421 + name = "" 1.422 + 1.423 + for c in old_name: 1.424 + 1.425 + if c in convert_dict.keys(): 1.426 + 1.427 + name = name + convert_dict[c] 1.428 + 1.429 + else: 1.430 + 1.431 + name = name + c 1.432 + 1.433 + if self.verify and old_name != name: 1.434 + 1.435 + self.verify_log.append( 1.436 + "Changed %s to %s" % (old_name, name) 1.437 + ) 1.438 + 1.439 + return name 1.440 + 1.441 + def extract_old_files(self, l, path, filetypes = 0, separator = ",", 1.442 + convert_dict = {}): 1.443 1.444 new_path = self.create_directory(path) 1.445 1.446 @@ -1137,7 +1174,9 @@ 1.447 1.448 for i in l: 1.449 1.450 - name = i[0] 1.451 + old_name = i[0] 1.452 + 1.453 + name = self.convert_name(old_name, convert_dict) 1.454 1.455 if type(i[1]) != type([]): 1.456 1.457 @@ -1184,10 +1223,13 @@ 1.458 1.459 new_path = os.path.join(path, name) 1.460 1.461 - self.extract_old_files(i[1], new_path, filetypes) 1.462 + self.extract_old_files( 1.463 + i[1], new_path, filetypes, separator, convert_dict 1.464 + ) 1.465 1.466 1.467 - def extract_new_files(self, l, path, filetypes = 0, separator = ","): 1.468 + def extract_new_files(self, l, path, filetypes = 0, separator = ",", 1.469 + convert_dict = {}): 1.470 1.471 new_path = self.create_directory(path) 1.472 1.473 @@ -1201,13 +1243,17 @@ 1.474 1.475 for i in l: 1.476 1.477 - name = i[0] 1.478 + old_name = i[0] 1.479 + 1.480 + # Use the conversion dictionary to convert any forbidden 1.481 + # characters to accepted local substitutes. 1.482 + name = self.convert_name(old_name, convert_dict) 1.483 1.484 if type(i[1]) != type([]): 1.485 1.486 # A file. 1.487 load, exec_addr, length = i[2], i[3], i[4] 1.488 - 1.489 + 1.490 if filetypes == 0: 1.491 1.492 # Load and execution addresses assumed to be valid. 1.493 @@ -1247,11 +1293,13 @@ 1.494 1.495 new_path = os.path.join(path, name) 1.496 1.497 - self.extract_new_files(i[1], new_path, filetypes) 1.498 + self.extract_new_files( 1.499 + i[1], new_path, filetypes, separator, convert_dict 1.500 + ) 1.501 1.502 1.503 def extract_files(self, out_path, files = None, filetypes = 0, 1.504 - separator = ","): 1.505 + separator = ",", convert_dict = {}): 1.506 1.507 if files is None: 1.508 1.509 @@ -1259,19 +1307,27 @@ 1.510 1.511 if self.disc_type == 'adD': 1.512 1.513 - self.extract_old_files(files, out_path, filetypes) 1.514 + self.extract_old_files( 1.515 + files, out_path, filetypes, separator, convert_dict 1.516 + ) 1.517 1.518 elif self.disc_type == 'adE': 1.519 1.520 - self.extract_new_files(files, out_path, filetypes) 1.521 + self.extract_new_files( 1.522 + files, out_path, filetypes, separator, convert_dict 1.523 + ) 1.524 1.525 elif self.disc_type == 'adEbig': 1.526 1.527 - self.extract_new_files(files, out_path, filetypes) 1.528 + self.extract_new_files( 1.529 + files, out_path, filetypes, separator, convert_dict 1.530 + ) 1.531 1.532 else: 1.533 1.534 - self.extract_old_files(files, out_path, filetypes) 1.535 + self.extract_old_files( 1.536 + files, out_path, filetypes, separator, convert_dict 1.537 + ) 1.538 1.539 def create_directory(self, path, name = None): 1.540 1.541 @@ -1328,6 +1384,41 @@ 1.542 # Success 1.543 return built 1.544 1.545 + def plural(self, msg, values, words): 1.546 + 1.547 + """message = plural(self, msg, values, words) 1.548 + 1.549 + Return a message which takes into account the plural form of 1.550 + words in the original message, assuming that the appropriate 1.551 + form for negative numbers of items is the same as that for 1.552 + more than one item. 1.553 + 1.554 + values is a list of numeric values referenced in the message. 1.555 + words is a list of sequences of words to substitute into the 1.556 + message. This takes the form 1.557 + 1.558 + [ word_to_use_for_zero_items, 1.559 + word_to_use_for_one_item, 1.560 + word_to_use_for_two_or_more_items ] 1.561 + 1.562 + The length of the values and words lists must be equal. 1.563 + """ 1.564 + 1.565 + substitutions = [] 1.566 + 1.567 + for i in range(0, len(values)): 1.568 + 1.569 + n = values[i] 1.570 + 1.571 + # Each number must be mapped to a value in the range [0, 2]. 1.572 + if n > 1: n = 2 1.573 + elif n < 0: n = 2 1.574 + 1.575 + substitutions.append(values[i]) 1.576 + substitutions.append(words[i][n]) 1.577 + 1.578 + return msg % tuple(substitutions) 1.579 + 1.580 def print_log(self): 1.581 1.582 """print_log() 1.583 @@ -1335,9 +1426,16 @@ 1.584 \rPrint the disc verification log. 1.585 """ 1.586 1.587 + if hasattr(self, "disc_map") and self.disc_map.has_key(1): 1.588 + 1.589 + print self.plural( 1.590 + "%i mapped %s found.", [len(self.disc_map[1])], 1.591 + [("defects", "defect", "defects")] 1.592 + ) 1.593 + 1.594 if self.verify_log == []: 1.595 1.596 - print "No problems found." 1.597 + print "All objects located." 1.598 return 1.599 1.600 for line in self.verify_log:
