write.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. # greaseweazle/tools/write.py
  2. #
  3. # Greaseweazle control script: Write Image to Disk.
  4. #
  5. # Written & released by Keir Fraser <keir.xen@gmail.com>
  6. #
  7. # This is free and unencumbered software released into the public domain.
  8. # See the file COPYING for more details, or visit <http://unlicense.org>.
  9. description = "Write a disk from the specified image file."
  10. import sys
  11. from greaseweazle.tools import util
  12. from greaseweazle import error, track
  13. from greaseweazle import usb as USB
  14. from greaseweazle.codec import formats
  15. # Read and parse the image file.
  16. def open_image(args, image_class):
  17. try:
  18. image = image_class.from_file(args.file)
  19. args.raw_image_class = True
  20. except TypeError:
  21. image = image_class.from_file(args.file, args.fmt_cls)
  22. args.raw_image_class = False
  23. return image
  24. # write_from_image:
  25. # Writes the specified image file to floppy disk.
  26. def write_from_image(usb, args, image):
  27. # Measure drive RPM.
  28. # We will adjust the flux intervals per track to allow for this.
  29. drive = usb.read_track(2)
  30. del drive.list
  31. verified_count, not_verified_count = 0, 0
  32. for t in args.tracks:
  33. cyl, head = t.cyl, t.head
  34. track = image.get_track(cyl, head)
  35. if track is None and not args.erase_empty:
  36. continue
  37. print("\r%sing Track %u.%u..." %
  38. ("Writ" if track is not None else "Eras", cyl, head),
  39. end="", flush=True)
  40. usb.seek(t.physical_cyl, head)
  41. if track is None:
  42. usb.erase_track(drive.ticks_per_rev * 1.1)
  43. continue
  44. if args.raw_image_class and args.fmt_cls is not None:
  45. track = args.fmt_cls.decode_track(cyl, head, track).raw_track()
  46. if args.precomp is not None:
  47. track.precomp = args.precomp.track_precomp(cyl)
  48. flux = track.flux_for_writeout()
  49. # @factor adjusts flux times for speed variations between the
  50. # read-in and write-out drives.
  51. factor = drive.ticks_per_rev / flux.index_list[0]
  52. # Convert the flux samples to Greaseweazle sample frequency.
  53. rem = 0.0
  54. flux_list = []
  55. for x in flux.list:
  56. y = x * factor + rem
  57. val = round(y)
  58. rem = y - val
  59. flux_list.append(val)
  60. # Encode the flux times for Greaseweazle, and write them out.
  61. verified = False
  62. for retry in range(args.retries+1):
  63. if retry != 0:
  64. print("T%u.%u: Verify Failure - Retry (%d)"
  65. % (cyl, head, retry))
  66. usb.write_track(flux_list = flux_list,
  67. cue_at_index = flux.index_cued,
  68. terminate_at_index = flux.terminate_at_index)
  69. try:
  70. no_verify = args.no_verify or track.verify is None
  71. except AttributeError: # track.verify undefined
  72. no_verify = True
  73. if no_verify:
  74. not_verified_count += 1
  75. verified = True
  76. break
  77. v_revs, v_ticks = track.verify_revs, 0
  78. if isinstance(v_revs, float):
  79. v_ticks = int(drive.ticks_per_rev * v_revs)
  80. v_revs = 2
  81. v_flux = usb.read_track(revs = v_revs, ticks = v_ticks)
  82. v_flux.scale(flux.time_per_rev / drive.time_per_rev)
  83. verified = track.verify.verify_track(v_flux)
  84. if verified:
  85. verified_count += 1
  86. break
  87. if retry == 0:
  88. print()
  89. error.check(verified, "Failed to verify Track %u.%u" % (cyl, head))
  90. print()
  91. if not_verified_count == 0:
  92. print("All tracks verified")
  93. else:
  94. if verified_count == 0:
  95. s = "No tracks verified "
  96. else:
  97. s = ("%d tracks verified; %d tracks *not* verified "
  98. % (verified_count, not_verified_count))
  99. s += ("(Reason: Verify %s)"
  100. % ("unavailable", "disabled")[args.no_verify])
  101. print(s)
  102. class PrecompSpec:
  103. def __str__(self):
  104. s = "Precomp %s" % track.Precomp.TYPESTRING[self.type]
  105. for e in self.list:
  106. s += ", %d-:%dns" % e
  107. return s
  108. def track_precomp(self, cyl):
  109. for c,s in reversed(self.list):
  110. if cyl >= c:
  111. return track.Precomp(self.type, s)
  112. return None
  113. def importspec(self, spec):
  114. self.list = []
  115. self.type = track.Precomp.MFM
  116. for x in spec.split(':'):
  117. k,v = x.split('=')
  118. if k == 'type':
  119. self.type = track.Precomp.TYPESTRING.index(v.upper())
  120. else:
  121. self.list.append((int(k), int(v)))
  122. self.list.sort()
  123. def __init__(self, spec):
  124. try:
  125. self.importspec(spec)
  126. except:
  127. raise ValueError
  128. def main(argv):
  129. epilog = "FORMAT options:\n" + formats.print_formats()
  130. parser = util.ArgumentParser(usage='%(prog)s [options] file',
  131. epilog=epilog)
  132. parser.add_argument("--device", help="greaseweazle device name")
  133. parser.add_argument("--drive", type=util.drive_letter, default='A',
  134. help="drive to write (A,B,0,1,2)")
  135. parser.add_argument("--format", help="disk format")
  136. parser.add_argument("--tracks", type=util.TrackSet,
  137. help="which tracks to write")
  138. parser.add_argument("--erase-empty", action="store_true",
  139. help="erase empty tracks (default: skip)")
  140. parser.add_argument("--no-verify", action="store_true",
  141. help="disable verify")
  142. parser.add_argument("--retries", type=int, default=3,
  143. help="number of retries on verify failure")
  144. parser.add_argument("--precomp", type=PrecompSpec,
  145. help="write precompensation")
  146. parser.add_argument("file", help="input filename")
  147. parser.description = description
  148. parser.prog += ' ' + argv[1]
  149. args = parser.parse_args(argv[2:])
  150. try:
  151. image_class = util.get_image_class(args.file)
  152. if not args.format and hasattr(image_class, 'default_format'):
  153. args.format = image_class.default_format
  154. def_tracks, args.fmt_cls = None, None
  155. if args.format:
  156. try:
  157. args.fmt_cls = formats.formats[args.format]()
  158. except KeyError as ex:
  159. raise error.Fatal("""\
  160. Unknown format '%s'
  161. Known formats:\n%s"""
  162. % (args.format, formats.print_formats()))
  163. def_tracks = args.fmt_cls.default_tracks
  164. if def_tracks is None:
  165. def_tracks = util.TrackSet('c=0-81:h=0-1')
  166. if args.tracks is not None:
  167. def_tracks.update_from_trackspec(args.tracks.trackspec)
  168. args.tracks = def_tracks
  169. usb = util.usb_open(args.device)
  170. image = open_image(args, image_class)
  171. s = str(args.tracks)
  172. if args.precomp is not None:
  173. s += "; %s" % args.precomp
  174. print("Writing %s" % s)
  175. util.with_drive_selected(write_from_image, usb, args, image)
  176. except USB.CmdError as err:
  177. print("Command Failed: %s" % err)
  178. if __name__ == "__main__":
  179. main(sys.argv)
  180. # Local variables:
  181. # python-indent: 4
  182. # End: