Added file deletion for Atari DOS

This commit is contained in:
Rob McMullen 2017-02-21 23:07:24 -08:00
parent 7f2b07b221
commit ea92e91865
4 changed files with 134 additions and 49 deletions

View File

@ -1,7 +1,7 @@
import numpy as np import numpy as np
from errors import * from errors import *
from diskimages import DiskImageBase, Directory, VTOC, WriteableSector from diskimages import DiskImageBase, Directory, VTOC, WriteableSector, BaseSectorList
from segments import EmptySegment, ObjSegment, RawSectorsSegment, DefaultSegment, SegmentSaver from segments import EmptySegment, ObjSegment, RawSectorsSegment, DefaultSegment, SegmentSaver
from utils import to_numpy from utils import to_numpy
@ -139,8 +139,6 @@ class AtariDosDirent(object):
def encode_dirent(self): def encode_dirent(self):
data = np.zeros([16], dtype=np.uint8) data = np.zeros([16], dtype=np.uint8)
values = data.view(dtype=self.format)[0] values = data.view(dtype=self.format)[0]
self.dos_2 = True
self.in_use = True
flag = (1 * int(self.opened_output)) | (2 * int(self.dos_2)) | (4 * int(self.mydos)) | (0x10 * int(self.is_dir)) | (0x20 * int(self.locked)) | (0x40 * int(self.in_use)) | (0x80 * int(self.deleted)) flag = (1 * int(self.opened_output)) | (2 * int(self.dos_2)) | (4 * int(self.mydos)) | (0x10 * int(self.is_dir)) | (0x20 * int(self.locked)) | (0x40 * int(self.in_use)) | (0x80 * int(self.deleted))
values[0] = flag values[0] = flag
values[1] = self.num_sectors values[1] = self.num_sectors
@ -149,6 +147,10 @@ class AtariDosDirent(object):
values[4] = self.ext values[4] = self.ext
return data return data
def mark_deleted(self):
self.deleted = True
self.in_use = False
def update_sector_info(self, sector_list): def update_sector_info(self, sector_list):
self.num_sectors = sector_list.num_sectors self.num_sectors = sector_list.num_sectors
self.starting_sector = sector_list.first_sector self.starting_sector = sector_list.first_sector
@ -162,6 +164,17 @@ class AtariDosDirent(object):
return False return False
return True return True
def get_sector_list(self, image):
sector_list = BaseSectorList(image.bytes_per_sector)
self.start_read(image)
while True:
sector = WriteableSector(image.bytes_per_sector, None, self.current_sector)
sector_list.append(sector)
_, last, _, _ = self.read_sector(image)
if last:
break
return sector_list
def start_read(self, image): def start_read(self, image):
if not self.is_sane: if not self.is_sane:
raise InvalidDirent("Invalid directory entry '%s'" % str(self)) raise InvalidDirent("Invalid directory entry '%s'" % str(self))
@ -198,6 +211,8 @@ class AtariDosDirent(object):
self.filename = "%-8s" % filename[0:8] self.filename = "%-8s" % filename[0:8]
self.ext = ext self.ext = ext
self.file_num = index self.file_num = index
self.dos_2 = True
self.in_use = True
class MydosDirent(AtariDosDirent): class MydosDirent(AtariDosDirent):
@ -362,14 +377,15 @@ class AtariDosDiskImage(DiskImageBase):
if dirent.mydos: if dirent.mydos:
dirent = MydosDirent(self, num, dir_bytes[i:i+16]) dirent = MydosDirent(self, num, dir_bytes[i:i+16])
if directory is not None:
directory.set(num, dirent)
if dirent.in_use: if dirent.in_use:
files.append(dirent) files.append(dirent)
if not dirent.is_sane: if not dirent.is_sane:
self.all_sane = False self.all_sane = False
log.debug("dirent %d not sane: %s" % (num, dirent))
elif dirent.flag == 0: elif dirent.flag == 0:
break break
if directory is not None:
directory.set(num, dirent)
i += 16 i += 16
num += 1 num += 1
self.files = files self.files = files

View File

@ -348,11 +348,15 @@ class DiskImageBase(object):
def get_directory_segments(self): def get_directory_segments(self):
return [] return []
def find_file(self, filename): def find_dirent(self, filename):
for dirent in self.files: for dirent in self.files:
if filename == dirent.get_filename(): if filename == dirent.get_filename():
return self.get_file(dirent) return dirent
return "" raise FileNotFound("%s not found on disk" % filename)
def find_file(self, filename):
dirent = self.find_dirent(filename)
return self.get_file(dirent)
def get_file(self, dirent): def get_file(self, dirent):
segment = self.get_file_segment(dirent) segment = self.get_file_segment(dirent)
@ -387,8 +391,7 @@ class DiskImageBase(object):
sector_list = self.sector_list_class(self.bytes_per_sector, self.payload_bytes_per_sector, data, self.writeable_sector_class) sector_list = self.sector_list_class(self.bytes_per_sector, self.payload_bytes_per_sector, data, self.writeable_sector_class)
vtoc_segments = self.get_vtoc_segments() vtoc_segments = self.get_vtoc_segments()
vtoc = self.vtoc_class(self.bytes_per_sector, vtoc_segments) vtoc = self.vtoc_class(self.bytes_per_sector, vtoc_segments)
sector_list.calc_sector_map(dirent, vtoc) directory.save_dirent(dirent, vtoc, sector_list)
directory.save_dirent(dirent, sector_list)
self.write_sector_list(sector_list) self.write_sector_list(sector_list)
self.write_sector_list(vtoc) self.write_sector_list(vtoc)
self.write_sector_list(directory) self.write_sector_list(directory)
@ -400,10 +403,23 @@ class DiskImageBase(object):
log.debug("writing: %s" % sector) log.debug("writing: %s" % sector)
self.bytes[pos:pos + size] = sector.data self.bytes[pos:pos + size] = sector.data
def delete_file(self, filename):
directory = self.directory_class(self.bytes_per_sector)
self.get_directory(directory)
dirent = directory.find_dirent(filename)
sector_list = dirent.get_sector_list(self)
vtoc_segments = self.get_vtoc_segments()
vtoc = self.vtoc_class(self.bytes_per_sector, vtoc_segments)
directory.remove_dirent(dirent, vtoc, sector_list)
self.write_sector_list(sector_list)
self.write_sector_list(vtoc)
self.write_sector_list(directory)
self.get_metadata()
class WriteableSector(object): class WriteableSector(object):
def __init__(self, sector_size, data=None): def __init__(self, sector_size, data=None, num=-1):
self._sector_num = -1 self._sector_num = num
self._next_sector = 0 self._next_sector = 0
self.sector_size = sector_size self.sector_size = sector_size
self.file_num = 0 self.file_num = 0
@ -491,12 +507,17 @@ class Directory(BaseSectorList):
def get_free_dirent(self): def get_free_dirent(self):
used = set() used = set()
for i, d in self.dirents.iteritems(): d = self.dirents.items()
if not d.in_use: d.sort()
for i, dirent in d:
if not dirent.in_use:
return i return i
used.add(i) used.add(i)
if len(used) >= self.num_dirents: if self.num_dirents > 0 and (len(used) >= self.num_dirents):
raise NoSpaceInDirectory() raise NoSpaceInDirectory()
i += 1
used.add(i)
return i
def add_dirent(self, filename, filetype): def add_dirent(self, filename, filetype):
index = self.get_free_dirent() index = self.get_free_dirent()
@ -505,15 +526,45 @@ class Directory(BaseSectorList):
self.set(index, dirent) self.set(index, dirent)
return dirent return dirent
def save_dirent(self, dirent, sector_list): def find_dirent(self, filename):
for dirent in self.dirents.values():
if filename == dirent.get_filename():
return dirent
raise FileNotFound("%s not found on disk" % filename)
def save_dirent(self, dirent, vtoc, sector_list):
self.place_sector_list(dirent, vtoc, sector_list)
dirent.update_sector_info(sector_list) dirent.update_sector_info(sector_list)
self.calc_sectors() self.calc_sectors()
def set_location(self, sector): def place_sector_list(self, dirent, vtoc, sector_list):
raise NotImplementedError """ Map out the sectors and link the sectors together
def set_size(self, size): raises NotEnoughSpaceOnDisk if the whole file won't fit. It will not
raise NotImplementedError allow partial writes.
"""
sector_list.calc_extra_sectors()
num = len(sector_list)
order = vtoc.reserve_space(num)
if len(order) != num:
raise InvalidFile("VTOC reserved space for %d sectors. Sectors needed: %d" % (len(order), num))
file_length = 0
last_sector = None
for sector, sector_num in zip(sector_list.sectors, order):
sector.sector_num = sector_num
sector.file_num = dirent.file_num
file_length += sector.used
if last_sector is not None:
last_sector.next_sector_num = sector_num
last_sector = sector
if last_sector is not None:
last_sector.next_sector_num = 0
sector_list.file_length = file_length
def remove_dirent(self, dirent, vtoc, sector_list):
vtoc.free_sector_list(sector_list)
dirent.mark_deleted()
self.calc_sectors()
@property @property
def dirent_class(self): def dirent_class(self):
@ -596,6 +647,10 @@ class VTOC(BaseSectorList):
def calc_bitmap(self): def calc_bitmap(self):
raise NotImplementedError raise NotImplementedError
def free_sector_list(self, sector_list):
for sector in sector_list:
self.sector_map[sector.sector_num] = 1
class SectorList(BaseSectorList): class SectorList(BaseSectorList):
def __init__(self, bytes_per_sector, usable, data, sector_class): def __init__(self, bytes_per_sector, usable, data, sector_class):
@ -613,29 +668,6 @@ class SectorList(BaseSectorList):
self.sectors.append(sector) self.sectors.append(sector)
index += count index += count
def calc_sector_map(self, dirent, vtoc):
""" Map out the sectors and link the sectors together
raises NotEnoughSpaceOnDisk if the whole file won't fit. It will not
allow partial writes.
"""
self.calc_extra_sectors()
num = len(self.sectors)
order = vtoc.reserve_space(num)
if len(order) != len(self.sectors):
raise InvalidFile("VTOC reserved space for %d sectors. Sectors needed: %d" % (len(order), len(self.sectors)))
self.file_length = 0
last_sector = None
for sector, sector_num in zip(self.sectors, order):
sector.sector_num = sector_num
sector.file_num = dirent.file_num
self.file_length += sector.used
if last_sector is not None:
last_sector.next_sector_num = sector_num
last_sector = sector
if last_sector is not None:
last_sector.next_sector_num = 0
def calc_extra_sectors(self): def calc_extra_sectors(self):
""" Add extra sectors to the list. """ Add extra sectors to the list.

View File

@ -36,3 +36,6 @@ class NoSpaceInDirectory(AtrError):
class NotEnoughSpaceOnDisk(AtrError): class NotEnoughSpaceOnDisk(AtrError):
pass pass
class FileNotFound(AtrError):
pass

View File

@ -12,11 +12,12 @@ class TestAtariDosSDImage(object):
rawdata = SegmentData(data) rawdata = SegmentData(data)
self.image = AtariDosDiskImage(rawdata) self.image = AtariDosDiskImage(rawdata)
def check_entries(self, entries, save_image_name=None): def check_entries(self, entries, prefix="TEST", save=None):
orig_num_files = len(self.image.files) orig_num_files = len(self.image.files)
filenames = []
count = 1 count = 1
for data in entries: for data in entries:
filename = "TEST%d.BIN" % count filename = "%s%d.BIN" % (prefix, count)
self.image.write_file(filename, None, data) self.image.write_file(filename, None, data)
assert len(self.image.files) == orig_num_files + count assert len(self.image.files) == orig_num_files + count
data2 = self.image.find_file(filename) data2 = self.image.find_file(filename)
@ -26,13 +27,16 @@ class TestAtariDosSDImage(object):
# loop over them again to make sure data wasn't overwritten # loop over them again to make sure data wasn't overwritten
count = 1 count = 1
for data in entries: for data in entries:
filename = "TEST%d.BIN" % count filename = "%s%d.BIN" % (prefix, count)
data2 = self.image.find_file(filename) data2 = self.image.find_file(filename)
assert data.tostring() == data2 assert data.tostring() == data2
count += 1 count += 1
filenames.append(filename)
if save_image_name is not None: if save is not None:
self.image.save(save_image_name) self.image.save(save)
return filenames
def test_small(self): def test_small(self):
assert len(self.image.files) == 5 assert len(self.image.files) == 5
@ -68,7 +72,7 @@ class TestAtariDosSDImage(object):
np.arange(9*1024, dtype=np.uint8), np.arange(9*1024, dtype=np.uint8),
np.arange(10*1024, dtype=np.uint8), np.arange(10*1024, dtype=np.uint8),
] ]
self.check_entries(entries, "many_small.atr") self.check_entries(entries, save="many_small.atr")
def test_big_failure(self): def test_big_failure(self):
assert len(self.image.files) == 5 assert len(self.image.files) == 5
@ -80,6 +84,36 @@ class TestAtariDosSDImage(object):
self.image.write_file("RAMP50K2.BIN", None, data) self.image.write_file("RAMP50K2.BIN", None, data)
assert len(self.image.files) == 6 assert len(self.image.files) == 6
def test_delete(self):
entries1 = [
np.arange(3*1024, dtype=np.uint8),
np.arange(3*1024, dtype=np.uint8),
np.arange(3*1024, dtype=np.uint8),
np.arange(3*1024, dtype=np.uint8),
np.arange(3*1024, dtype=np.uint8),
np.arange(3*1024, dtype=np.uint8),
np.arange(3*1024, dtype=np.uint8),
np.arange(3*1024, dtype=np.uint8),
np.arange(3*1024, dtype=np.uint8),
np.arange(10*1024, dtype=np.uint8),
np.arange(10*1024, dtype=np.uint8),
]
entries2 = [
np.arange(10*1024, dtype=np.uint8),
np.arange(11*1024, dtype=np.uint8),
]
filenames = self.check_entries(entries1, "FIRST")
assert len(self.image.files) == 16
self.image.delete_file(filenames[2])
self.image.delete_file(filenames[5])
self.image.delete_file(filenames[0])
self.image.delete_file(filenames[8])
assert len(self.image.files) == 12
filename = self.check_entries(entries2, "SECOND", save="test_delete.atr")
assert len(self.image.files) == 14
if __name__ == "__main__": if __name__ == "__main__":
t = TestAtariDosFile() t = TestAtariDosFile()