mirror of
https://github.com/robmcmullen/atrcopy.git
synced 2025-02-20 01:28:57 +00:00
Added file deletion for Atari DOS
This commit is contained in:
parent
7f2b07b221
commit
ea92e91865
@ -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
|
||||||
|
@ -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 dirent
|
||||||
|
raise FileNotFound("%s not found on disk" % filename)
|
||||||
|
|
||||||
|
def find_file(self, filename):
|
||||||
|
dirent = self.find_dirent(filename)
|
||||||
return self.get_file(dirent)
|
return self.get_file(dirent)
|
||||||
return ""
|
|
||||||
|
|
||||||
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.
|
||||||
|
@ -36,3 +36,6 @@ class NoSpaceInDirectory(AtrError):
|
|||||||
|
|
||||||
class NotEnoughSpaceOnDisk(AtrError):
|
class NotEnoughSpaceOnDisk(AtrError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class FileNotFound(AtrError):
|
||||||
|
pass
|
||||||
|
@ -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()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user