def create_file(self, path: str, is_dir=False) -> int: """Create a new file or directory """ dirname, fullname = os.path.split(path) file_dir = self.find_dir(dirname) if file_dir is None: raise FATException(f'{path} directory does not exist') filename, extension = os.path.splitext(fullname) extension = extension.lstrip('.') # does the file already exist? if file_dir.find_entry(filename, extension): raise FATException(f'{path} file already exists') # find a free cluster for the new file file_cluster = self.find_free_cluster() if file_cluster == const.EOF: raise FATException('There is no free space') self.entries[file_cluster] = const.EOC # create file entry additional_entry_options = {} if is_dir: additional_entry_options['attributes'] = const.FileAttributes.DIRECTORY file_dir.add_entry(filename, extension, file_cluster, **additional_entry_options) self.write_file(file_dir.cluster_number, file_dir.serialize()) return file_cluster
def _validation(self): if self.filename and len(self.filename) > 8: raise FATException( f'Validation error: filename is too long: {len(self.filename)}' ) if self.extension and len(self.extension) > 3: raise FATException( f'Validation error: extension is too long: {len(self.extension)}' ) if self.is_dir() and self.extension: raise FATException( 'Validation error: directory has to have not specified extension' )
def find_file(self, path: str) -> int: dirname, fullname = os.path.split(path) file_dir = self.find_dir(dirname) if file_dir is None: raise FATException(f'{path} directory does not exist') filename, extension = os.path.splitext(fullname) extension = extension.lstrip('.') file_dir_entry = file_dir.find_entry(filename, extension) if not file_dir_entry: raise FATException(f'{path} file does not exist') if file_dir_entry.is_dir(): raise FATException(f'{path} is a directory. Use `find_dir` instead.') return file_dir_entry.first_file_cluster
def find_free_cluster(self, start_index: int=const.FAT_CLUSTER_TO_USE_FROM) -> int or const.EOF: """Try to find a free cluster, returns EOF if no any """ if not self.is_cluster_number(start_index): raise FATException('Incorrect cluster position') for index, entry in enumerate(self.entries[start_index:], start=start_index): if entry == const.FAT_ENTRY_EMPTY: return index return const.EOF
def parse(self, data: bytes): if len(data) != self.SIZE: raise FATException(f'data has to have length={self.SIZE}') (self.filename, self.extension, self.attributes, high_first_cluster, low_first_cluster) = struct.unpack(self.FORMAT, data) self.filename = self.filename.rstrip(b'\x00').decode() self.extension = self.extension.rstrip(b'\x00').decode() self.first_file_cluster = ( high_first_cluster << 16) | low_first_cluster
def read_file(self, file_number: int) -> bytearray: if not self.is_cluster_number(file_number): raise FATException(f'Incorrect file_number: {file_number}') file_content = bytearray() fat_number = file_number with open(self.volume_path, 'rb') as volume: while self.is_cluster_number(fat_number): fat_entry = self.entries[fat_number] if fat_entry == const.FAT_ENTRY_EMPTY: raise FATException(f'Cluster is empty for the fat_number={fat_number}, file_number={file_number}') # read cluster data cluster_position = self.get_cluster_position(fat_number) volume.seek(cluster_position) cluster_value = volume.read(self.cluster_size) file_content.extend(cluster_value) # go to the next cluster in the chain fat_number = fat_entry return file_content
def write_file(self, file_number: int, data: bytes): if not self.is_cluster_number(file_number): raise FATException(f'Incorrect file_number: {file_number}') # TODO: oprimize it for contiguous clusters (don't need to split by chunks) # rewrire all clusters fat_number = file_number prev_number = None with open(self.volume_path, 'r+b') as volume: for data_chunk in split_by_chunks(data, self.cluster_size): # there's no enough size in the file? -> add a new cluster to the file if fat_number == const.EOC: fat_number = self.find_free_cluster(prev_number) if fat_number == const.EOF: fat_number = self.find_free_cluster() if fat_number == const.EOF: raise FATException('There is no free space') self.entries[prev_number] = fat_number fat_entry = self.entries[fat_number] if fat_entry == const.FAT_ENTRY_EMPTY: raise FATException(f'Cluster is empty for the fat_number={fat_number}, file_number={file_number}') # write data cluster_position = self.get_cluster_position(fat_number) volume.seek(cluster_position) volume.write(data_chunk) # go to the next cluster in the chain prev_number = fat_number fat_number = fat_entry # free not used clusters # TODO pass
def find_dir(self, path: str) -> DirectoryTable or None: if not path.startswith('/'): raise FATException('path has to be absolute.') if len(path.strip()) == 1: return self.root _, *dirs = path.split('/') current_dir = self.root for directory in dirs: for dir_entry in current_dir.entries: if dir_entry.is_dir() and dir_entry.filename == directory: current_dir = self.read_dir(dir_entry.first_file_cluster) break # directory doesn't have the next subdirectory in the path else: return None return current_dir
def get_cluster_position(self, number: int): """Get offset of a cluster in the volume by its entry number """ if not self.is_cluster_number(number): raise FATException('Incorrect cluster position') return self.cluster_size * number