From ea8c65a17e972af1ac0c6a1334c7d391a39f9065 Mon Sep 17 00:00:00 2001 From: Czar Echavez Date: Tue, 1 Aug 2023 16:18:49 +0100 Subject: [PATCH 01/14] #1714: set up files --- .azure/azure-ci-build-pipeline.yaml | 2 +- src/primaite/simulator/core.py | 2 +- .../simulator/file_system}/__init__.py | 0 .../simulator/file_system/file_system.py | 77 +++++++++++++++++++ .../simulator/file_system/file_system_file.py | 34 ++++++++ .../file_system/file_system_file_type.py | 7 ++ .../file_system/file_system_folder.py | 41 ++++++++++ .../file_system/file_system_item_abc.py | 60 +++++++++++++++ tests/conftest.py | 4 +- .../simulator => _primaite}/__init__.py | 0 .../_primaite/_simulator/__init__.py | 0 .../_simulator/_file_system/__init__.py | 0 .../_file_system/test_file_system.py | 0 .../_file_system/test_file_system_file.py | 0 .../_file_system/test_file_system_folder.py | 0 .../_simulator}/test_core.py | 0 16 files changed, 223 insertions(+), 4 deletions(-) rename {tests/unit_tests/primaite => src/primaite/simulator/file_system}/__init__.py (100%) create mode 100644 src/primaite/simulator/file_system/file_system.py create mode 100644 src/primaite/simulator/file_system/file_system_file.py create mode 100644 src/primaite/simulator/file_system/file_system_file_type.py create mode 100644 src/primaite/simulator/file_system/file_system_folder.py create mode 100644 src/primaite/simulator/file_system/file_system_item_abc.py rename tests/unit_tests/{primaite/simulator => _primaite}/__init__.py (100%) create mode 100644 tests/unit_tests/_primaite/_simulator/__init__.py create mode 100644 tests/unit_tests/_primaite/_simulator/_file_system/__init__.py create mode 100644 tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py create mode 100644 tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_file.py create mode 100644 tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_folder.py rename tests/unit_tests/{primaite/simulator => _primaite/_simulator}/test_core.py (100%) diff --git a/.azure/azure-ci-build-pipeline.yaml b/.azure/azure-ci-build-pipeline.yaml index 0bb03594..9070270a 100644 --- a/.azure/azure-ci-build-pipeline.yaml +++ b/.azure/azure-ci-build-pipeline.yaml @@ -86,5 +86,5 @@ stages: displayName: 'Perform PrimAITE Setup' - script: | - pytest -n 4 + pytest -n auto displayName: 'Run tests' diff --git a/src/primaite/simulator/core.py b/src/primaite/simulator/core.py index 5b9bea1f..fce192c7 100644 --- a/src/primaite/simulator/core.py +++ b/src/primaite/simulator/core.py @@ -6,7 +6,7 @@ from pydantic import BaseModel class SimComponent(BaseModel): - """Extension of pydantic BaseModel with additional methods that must be defined by all classes in the simulator.""" + """Extension of pydantic BaseModel with additional methods that must be defined by all classes in the simulator.""" @abstractmethod def describe_state(self) -> Dict: diff --git a/tests/unit_tests/primaite/__init__.py b/src/primaite/simulator/file_system/__init__.py similarity index 100% rename from tests/unit_tests/primaite/__init__.py rename to src/primaite/simulator/file_system/__init__.py diff --git a/src/primaite/simulator/file_system/file_system.py b/src/primaite/simulator/file_system/file_system.py new file mode 100644 index 00000000..f467595d --- /dev/null +++ b/src/primaite/simulator/file_system/file_system.py @@ -0,0 +1,77 @@ +from typing import Dict, List, Union + +from primaite.simulator.core import SimComponent +from primaite.simulator.file_system.file_system_file import FileSystemFile +from primaite.simulator.file_system.file_system_folder import FileSystemFolder + + +class FileSystem(SimComponent): + """Class that contains all the simulation File System.""" + + files: List[FileSystemFile] + """List containing all the files in the file system.""" + + folders: List[FileSystemFolder] + """List containing all the folders in the file system.""" + + def describe_state(self) -> Dict: + """ + Get the current state of the FileSystem as a dict. + + :return: A dict containing the current state of the FileSystemFile. + """ + pass + + def create_file(self): + """Creates a FileSystemFile and adds it to the list of files.""" + pass + + def create_folder(self): + """Creates a FileSystemFolder and adds it to the list of folders.""" + pass + + def delete_file(self, file_item: str): + """ + Deletes a file and removes it from the files list. + + :param file_item: The UUID of the file item to delete + :type file_item: str + """ + self.files = list(filter(lambda x: (x.get_item_uuid() != file_item), self.files)) + + def delete_folder(self, file_item: str): + """ + Deletes a folder, removes it frdom the folders list and removes any child folders and files. + + :param file_item: The UUID of the file item to delete + :type file_item: str + """ + self.files = list(filter(lambda x: (x.get_item_parent() != file_item), self.files)) + self.folders = list(filter(lambda x: (x.get_item_uuid() != file_item), self.folders)) + + def move_file_item(self, file_item: str, target_directory: str): + """ + Check to see if the file_item and target_directory exists then moves the item by changing its parent item uuid. + + :param file_item: The UUID of the file item to move + :type file_item: str + + :param target_directory: The UUID of the directory the item should be moved into + :type target_directory: str + """ + item = self._file_item_exists(file_item) + if item and any(f for f in self.folders if f.get_item_uuid() == target_directory): + item.move(target_directory) + + def _file_item_exists(self, file_item_uuid: str) -> Union[FileSystemFile, FileSystemFolder, None]: + """Returns true if the file or folder UUID exists.""" + item = next((x for x in self.files if x.get_item_uuid() == file_item_uuid), None) + if item: + return item + + next((x for x in self.folders if x.get_item_uuid() == file_item_uuid), None) + + if item: + return item + + raise Exception(f"No file or folder found with id: {file_item_uuid}") diff --git a/src/primaite/simulator/file_system/file_system_file.py b/src/primaite/simulator/file_system/file_system_file.py new file mode 100644 index 00000000..ee4fe1e5 --- /dev/null +++ b/src/primaite/simulator/file_system/file_system_file.py @@ -0,0 +1,34 @@ +from typing import Dict + +from primaite.simulator.file_system.file_system_file_type import FileSystemFileType +from primaite.simulator.file_system.file_system_item_abc import FileSystemItemABC + + +class FileSystemFile(FileSystemItemABC): + """Class that represents a file in the simulation.""" + + _file_type: FileSystemFileType + """The type of the FileSystemFile""" + + def get_file_type(self) -> FileSystemFileType: + """Returns the FileSystemFileType of the file.""" + return self._file_type + + def move(self, target_directory: str): + """ + Changes the parent_item of the FileSystemFile. + + Essentially simulates the file system item being moved from folder to folder + + :param target_directory: The UUID of the directory the file system item should be moved to + :type target_directory: str + """ + super().move(target_directory) + + def describe_state(self) -> Dict: + """ + Get the current state of the FileSystemFile as a dict. + + :return: A dict containing the current state of the FileSystemFile. + """ + pass diff --git a/src/primaite/simulator/file_system/file_system_file_type.py b/src/primaite/simulator/file_system/file_system_file_type.py new file mode 100644 index 00000000..134b38f4 --- /dev/null +++ b/src/primaite/simulator/file_system/file_system_file_type.py @@ -0,0 +1,7 @@ +from enum import Enum + + +class FileSystemFileType(str, Enum): + """Enum used to determine the FileSystemFile type.""" + + TBD = "TBD" diff --git a/src/primaite/simulator/file_system/file_system_folder.py b/src/primaite/simulator/file_system/file_system_folder.py new file mode 100644 index 00000000..41b9e1dd --- /dev/null +++ b/src/primaite/simulator/file_system/file_system_folder.py @@ -0,0 +1,41 @@ +from typing import Dict + +from primaite.simulator.file_system.file_system_item_abc import FileSystemItemABC + + +class FileSystemFolder(FileSystemItemABC): + """Simulation FileSystemFolder.""" + + _is_quarantined: bool + """Flag that marks the folder as quarantined if true.""" + + def quarantine(self): + """Quarantines the File System Folder.""" + self._is_quarantined = True + + def end_quarantine(self): + """Ends the quarantine of the File System Folder.""" + self._is_quarantined = False + + def quarantine_status(self) -> bool: + """Returns true if the folder is being quarantined.""" + return self._is_quarantined + + def move(self, target_directory: str): + """ + Changes the parent_item of the file system item. + + Essentially simulates the file system item being moved from folder to folder + + :param target_directory: The UUID of the directory the file system item should be moved to + :type target_directory: str + """ + super().move(target_directory) + + def describe_state(self) -> Dict: + """ + Get the current state of the FileSystemFolder as a dict. + + :return: A dict containing the current state of the FileSystemFile. + """ + pass diff --git a/src/primaite/simulator/file_system/file_system_item_abc.py b/src/primaite/simulator/file_system/file_system_item_abc.py new file mode 100644 index 00000000..11a3f858 --- /dev/null +++ b/src/primaite/simulator/file_system/file_system_item_abc.py @@ -0,0 +1,60 @@ +from abc import ABC, abstractmethod +from uuid import uuid4 + +from primaite.simulator.core import SimComponent + + +class FileSystemItemABC(SimComponent, ABC): + """Abstract Base class for any file system items e.g. files and folders.""" + + _uuid: str + """Unique identifier for the FileSystemItem""" + + _parent_item: str + """UUID of the parent FileSystemItem""" + + _item_size: float + """Disk size of the FileSystemItem""" + + def __init__(self, parent_item: str, item_size: float): + """ + Abstract base class used by FileSystem items. + + :param parent_item: The UUID of the FileSystemItem parent + :type parent_item: str + + :param item_size: The size of the FileSystemItem + :type item_size: float + """ + super().__init__() + + # generate random uuid for file system item + self._uuid = str(uuid4()) + + self._parent_item = parent_item + + self._item_size = item_size + + def get_item_uuid(self) -> str: + """Returns the file system item UUID.""" + return self._uuid + + def get_item_parent(self) -> str: + """Returns the UUID of the item's parent.""" + return self._parent_item + + def get_item_size(self) -> float: + """Returns the item size.""" + return self._item_size + + @abstractmethod + def move(self, target_directory: str): + """ + Changes the parent_item of the file system item. + + Essentially simulates the file system item being moved from folder to folder + + :param target_directory: The UUID of the directory the file system item should be moved to + :type target_directory: str + """ + self._parent_item = target_directory diff --git a/tests/conftest.py b/tests/conftest.py index f40b0b94..8102050e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -96,7 +96,7 @@ def temp_primaite_session(request): """ training_config_path = request.param[0] lay_down_config_path = request.param[1] - with patch("primaite.agents.agent_abc.get_session_path", get_temp_session_path) as mck: + with patch("_primaite.agents.agent_abc.get_session_path", get_temp_session_path) as mck: mck.session_timestamp = datetime.now() return TempPrimaiteSession(training_config_path, lay_down_config_path) @@ -112,7 +112,7 @@ def temp_session_path() -> Path: session_timestamp = datetime.now() date_dir = session_timestamp.strftime("%Y-%m-%d") session_path = session_timestamp.strftime("%Y-%m-%d_%H-%M-%S") - session_path = Path(tempfile.gettempdir()) / "primaite" / date_dir / session_path + session_path = Path(tempfile.gettempdir()) / "_primaite" / date_dir / session_path session_path.mkdir(exist_ok=True, parents=True) return session_path diff --git a/tests/unit_tests/primaite/simulator/__init__.py b/tests/unit_tests/_primaite/__init__.py similarity index 100% rename from tests/unit_tests/primaite/simulator/__init__.py rename to tests/unit_tests/_primaite/__init__.py diff --git a/tests/unit_tests/_primaite/_simulator/__init__.py b/tests/unit_tests/_primaite/_simulator/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/__init__.py b/tests/unit_tests/_primaite/_simulator/_file_system/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_file.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_file.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_folder.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_folder.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit_tests/primaite/simulator/test_core.py b/tests/unit_tests/_primaite/_simulator/test_core.py similarity index 100% rename from tests/unit_tests/primaite/simulator/test_core.py rename to tests/unit_tests/_primaite/_simulator/test_core.py From a0356a7fbc3e71046dd5094ecaf7200c88adfcab Mon Sep 17 00:00:00 2001 From: Czar Echavez Date: Thu, 3 Aug 2023 12:14:11 +0100 Subject: [PATCH 02/14] #1714: updated file system classes --- src/primaite/simulator/core.py | 9 ++ .../simulator/file_system/file_system.py | 131 ++++++++++++------ .../simulator/file_system/file_system_file.py | 41 ++++-- .../file_system/file_system_folder.py | 55 ++++++-- .../file_system/file_system_item_abc.py | 60 -------- .../_file_system/test_file_system.py | 80 +++++++++++ .../_file_system/test_file_system_file.py | 14 ++ .../_file_system/test_file_system_folder.py | 41 ++++++ 8 files changed, 302 insertions(+), 129 deletions(-) delete mode 100644 src/primaite/simulator/file_system/file_system_item_abc.py diff --git a/src/primaite/simulator/core.py b/src/primaite/simulator/core.py index a58e0c11..84b03498 100644 --- a/src/primaite/simulator/core.py +++ b/src/primaite/simulator/core.py @@ -1,6 +1,7 @@ """Core of the PrimAITE Simulator.""" from abc import abstractmethod from typing import Callable, Dict, List +from uuid import uuid4 from pydantic import BaseModel @@ -8,6 +9,14 @@ from pydantic import BaseModel class SimComponent(BaseModel): """Extension of pydantic BaseModel with additional methods that must be defined by all classes in the simulator.""" + uuid: str + "The component UUID." + + def __init__(self, **kwargs): + if not kwargs.get("uuid"): + kwargs["uuid"] = str(uuid4()) + super().__init__(**kwargs) + @abstractmethod def describe_state(self) -> Dict: """ diff --git a/src/primaite/simulator/file_system/file_system.py b/src/primaite/simulator/file_system/file_system.py index f467595d..6af6db3e 100644 --- a/src/primaite/simulator/file_system/file_system.py +++ b/src/primaite/simulator/file_system/file_system.py @@ -1,17 +1,17 @@ -from typing import Dict, List, Union +from typing import Dict, List, Optional + +from pydantic import PrivateAttr from primaite.simulator.core import SimComponent from primaite.simulator.file_system.file_system_file import FileSystemFile +from primaite.simulator.file_system.file_system_file_type import FileSystemFileType from primaite.simulator.file_system.file_system_folder import FileSystemFolder class FileSystem(SimComponent): """Class that contains all the simulation File System.""" - files: List[FileSystemFile] - """List containing all the files in the file system.""" - - folders: List[FileSystemFolder] + _folders: List[FileSystemFolder] = PrivateAttr([]) """List containing all the folders in the file system.""" def describe_state(self) -> Dict: @@ -22,56 +22,107 @@ class FileSystem(SimComponent): """ pass - def create_file(self): - """Creates a FileSystemFile and adds it to the list of files.""" - pass + def get_folders(self) -> List[FileSystemFolder]: + """Returns the list of folders.""" + return self._folders - def create_folder(self): + def create_file(self, file_size: float, folder_uuid: Optional[str] = None) -> FileSystemFile: + """ + Creates a FileSystemFile and adds it to the list of files. + + :param: folder_uuid: The uuid of the folder to add the file to + :type: folder_uuid: str + """ + file = None + # if no folder uuid provided, create a folder and add file to it + if folder_uuid is None: + folder = FileSystemFolder() + + file = FileSystemFile(item_parent=folder.uuid, file_size=file_size, file_type=FileSystemFileType.TBD) + folder.add_file(file) + self._folders.append(folder) + else: + # otherwise check for existence and add file + folder = self.get_folder_by_id(folder_uuid) + if folder is not None: + file = FileSystemFile(file_size=file_size, file_type=FileSystemFileType.TBD) + folder.add_file(file=file) + return file + + def create_folder(self) -> FileSystemFolder: """Creates a FileSystemFolder and adds it to the list of folders.""" - pass + folder = FileSystemFolder(item_parent=None) + self._folders.append(folder) + return folder - def delete_file(self, file_item: str): + def delete_file(self, file_id: str): """ Deletes a file and removes it from the files list. - :param file_item: The UUID of the file item to delete - :type file_item: str + :param file_id: The UUID of the file item to delete + :type file_id: str """ - self.files = list(filter(lambda x: (x.get_item_uuid() != file_item), self.files)) + # iterate through folders to delete the item with the matching uuid + for folder in self._folders: + folder.remove_file(file_id) - def delete_folder(self, file_item: str): + def delete_folder(self, folder_id: str): """ - Deletes a folder, removes it frdom the folders list and removes any child folders and files. + Deletes a folder, removes it from the folders list and removes any child folders and files. - :param file_item: The UUID of the file item to delete - :type file_item: str + :param folder_id: The UUID of the file item to delete + :type folder_id: str """ - self.files = list(filter(lambda x: (x.get_item_parent() != file_item), self.files)) - self.folders = list(filter(lambda x: (x.get_item_uuid() != file_item), self.folders)) + self._folders = list(filter(lambda f: (f.uuid != folder_id), self._folders)) - def move_file_item(self, file_item: str, target_directory: str): - """ - Check to see if the file_item and target_directory exists then moves the item by changing its parent item uuid. + def move_file(self, src_folder_id: str, target_folder_id: str, file_id: str): + """Moves a file from one folder to another.""" + # check that both folders and the file exists + src = self.get_folder_by_id(src_folder_id) + target = self.get_folder_by_id(target_folder_id) - :param file_item: The UUID of the file item to move - :type file_item: str + if src is None: + raise Exception(f"src folder with UUID {src_folder_id} could not be found") - :param target_directory: The UUID of the directory the item should be moved into - :type target_directory: str - """ - item = self._file_item_exists(file_item) - if item and any(f for f in self.folders if f.get_item_uuid() == target_directory): - item.move(target_directory) + if target is None: + raise Exception(f"src folder with UUID {target_folder_id} could not be found") - def _file_item_exists(self, file_item_uuid: str) -> Union[FileSystemFile, FileSystemFolder, None]: - """Returns true if the file or folder UUID exists.""" - item = next((x for x in self.files if x.get_item_uuid() == file_item_uuid), None) - if item: - return item + file = src.get_file(file_id=file_id) + if file is None: + raise Exception(f"file with UUID {file_id} could not be found") - next((x for x in self.folders if x.get_item_uuid() == file_item_uuid), None) + # remove file from src + src.remove_file(file_id) - if item: - return item + # add file to target + target.add_file(file) - raise Exception(f"No file or folder found with id: {file_item_uuid}") + def copy_file(self, src_folder_id: str, target_folder_id: str, file_id: str): + """Copies a file from one folder to another.""" + # check that both folders and the file exists + src = self.get_folder_by_id(src_folder_id) + target = self.get_folder_by_id(target_folder_id) + + if src is None: + raise Exception(f"src folder with UUID {src_folder_id} could not be found") + + if target is None: + raise Exception(f"src folder with UUID {target_folder_id} could not be found") + + file = src.get_file(file_id=file_id) + if file is None: + raise Exception(f"file with UUID {file_id} could not be found") + + # add file to target + target.add_file(file) + + def get_file_by_id(self, file_id: str) -> FileSystemFile: + """Checks if the file exists in any file system folders.""" + for folder in self._folders: + file = folder.get_file(file_id=file_id) + if file is not None: + return file + + def get_folder_by_id(self, folder_id: str) -> FileSystemFolder: + """Checks if the folder exists.""" + return next((f for f in self._folders if f.uuid == folder_id), None) diff --git a/src/primaite/simulator/file_system/file_system_file.py b/src/primaite/simulator/file_system/file_system_file.py index ee4fe1e5..bebaa223 100644 --- a/src/primaite/simulator/file_system/file_system_file.py +++ b/src/primaite/simulator/file_system/file_system_file.py @@ -1,30 +1,43 @@ from typing import Dict +from pydantic import PrivateAttr + +from primaite.simulator.core import SimComponent from primaite.simulator.file_system.file_system_file_type import FileSystemFileType -from primaite.simulator.file_system.file_system_item_abc import FileSystemItemABC -class FileSystemFile(FileSystemItemABC): +class FileSystemFile(SimComponent): """Class that represents a file in the simulation.""" - _file_type: FileSystemFileType + _file_type: FileSystemFileType = PrivateAttr() """The type of the FileSystemFile""" + _file_size: float = PrivateAttr() + """Disk size of the FileSystemItem""" + + def __init__(self, file_type: FileSystemFileType, file_size: float, **kwargs): + """ + Initialise FileSystemFile class. + + :param item_parent: The UUID of the FileSystemItem parent + :type item_parent: str + + :param file_size: The size of the FileSystemItem + :type file_size: float + """ + super().__init__(**kwargs) + + self._file_type = file_type + self._file_size = file_size + + def get_file_size(self) -> float: + """Returns the size of the file system item.""" + return self._file_size + def get_file_type(self) -> FileSystemFileType: """Returns the FileSystemFileType of the file.""" return self._file_type - def move(self, target_directory: str): - """ - Changes the parent_item of the FileSystemFile. - - Essentially simulates the file system item being moved from folder to folder - - :param target_directory: The UUID of the directory the file system item should be moved to - :type target_directory: str - """ - super().move(target_directory) - def describe_state(self) -> Dict: """ Get the current state of the FileSystemFile as a dict. diff --git a/src/primaite/simulator/file_system/file_system_folder.py b/src/primaite/simulator/file_system/file_system_folder.py index 41b9e1dd..248a4f98 100644 --- a/src/primaite/simulator/file_system/file_system_folder.py +++ b/src/primaite/simulator/file_system/file_system_folder.py @@ -1,14 +1,50 @@ -from typing import Dict +from typing import Dict, List -from primaite.simulator.file_system.file_system_item_abc import FileSystemItemABC +from pydantic import PrivateAttr + +from primaite.simulator.core import SimComponent +from primaite.simulator.file_system.file_system_file import FileSystemFile -class FileSystemFolder(FileSystemItemABC): +class FileSystemFolder(SimComponent): """Simulation FileSystemFolder.""" - _is_quarantined: bool + _files: List[FileSystemFile] = PrivateAttr([]) + """List of files stored in the folder.""" + + _folder_size: float = PrivateAttr(0) + """The current size of the folder""" + + _is_quarantined: bool = PrivateAttr(False) """Flag that marks the folder as quarantined if true.""" + def get_files(self) -> List[FileSystemFile]: + """Returns the list of files the folder contains.""" + return self._files + + def get_file(self, file_id: str) -> FileSystemFile: + """Return a FileSystemFile with the matching id.""" + return next((f for f in self._files if f.uuid == file_id), None) + + def add_file(self, file: FileSystemFile): + """Adds a file to the folder list.""" + self._folder_size += file.get_file_size() + + # add to list + self._files.append(file) + + def remove_file(self, file_id: str): + """Removes a file from the folder list.""" + file = next((f for f in self._files if f.uuid == file_id), None) + self._files.remove(file) + + # remove folder size from folder + self._folder_size -= file.get_file_size() + + def get_folder_size(self) -> float: + """Returns a sum of all file sizes in the files list.""" + return sum([file.get_file_size() for file in self._files]) + def quarantine(self): """Quarantines the File System Folder.""" self._is_quarantined = True @@ -21,17 +57,6 @@ class FileSystemFolder(FileSystemItemABC): """Returns true if the folder is being quarantined.""" return self._is_quarantined - def move(self, target_directory: str): - """ - Changes the parent_item of the file system item. - - Essentially simulates the file system item being moved from folder to folder - - :param target_directory: The UUID of the directory the file system item should be moved to - :type target_directory: str - """ - super().move(target_directory) - def describe_state(self) -> Dict: """ Get the current state of the FileSystemFolder as a dict. diff --git a/src/primaite/simulator/file_system/file_system_item_abc.py b/src/primaite/simulator/file_system/file_system_item_abc.py deleted file mode 100644 index 11a3f858..00000000 --- a/src/primaite/simulator/file_system/file_system_item_abc.py +++ /dev/null @@ -1,60 +0,0 @@ -from abc import ABC, abstractmethod -from uuid import uuid4 - -from primaite.simulator.core import SimComponent - - -class FileSystemItemABC(SimComponent, ABC): - """Abstract Base class for any file system items e.g. files and folders.""" - - _uuid: str - """Unique identifier for the FileSystemItem""" - - _parent_item: str - """UUID of the parent FileSystemItem""" - - _item_size: float - """Disk size of the FileSystemItem""" - - def __init__(self, parent_item: str, item_size: float): - """ - Abstract base class used by FileSystem items. - - :param parent_item: The UUID of the FileSystemItem parent - :type parent_item: str - - :param item_size: The size of the FileSystemItem - :type item_size: float - """ - super().__init__() - - # generate random uuid for file system item - self._uuid = str(uuid4()) - - self._parent_item = parent_item - - self._item_size = item_size - - def get_item_uuid(self) -> str: - """Returns the file system item UUID.""" - return self._uuid - - def get_item_parent(self) -> str: - """Returns the UUID of the item's parent.""" - return self._parent_item - - def get_item_size(self) -> float: - """Returns the item size.""" - return self._item_size - - @abstractmethod - def move(self, target_directory: str): - """ - Changes the parent_item of the file system item. - - Essentially simulates the file system item being moved from folder to folder - - :param target_directory: The UUID of the directory the file system item should be moved to - :type target_directory: str - """ - self._parent_item = target_directory diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py index e69de29b..7b26f707 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py @@ -0,0 +1,80 @@ +from primaite.simulator.file_system.file_system import FileSystem + + +def test_create_folder_and_file(): + """Test creating a folder and a file.""" + file_system = FileSystem() + folder = file_system.create_folder() + assert len(file_system.get_folders()) is 1 + + file_system.create_file(file_size=10, folder_uuid=folder.uuid) + assert len(file_system.get_folders()[0].get_files()) is 1 + + +def test_create_file(): + """Tests that creating a file without a folder creates a folder and sets that as the file's parent.""" + file_system = FileSystem() + + file = file_system.create_file(file_size=10) + assert len(file_system.get_folders()) is 1 + assert file_system.get_folders()[0].get_file(file.uuid) is file + + +def test_delete_file(): + """Tests that a file can be deleted.""" + file_system = FileSystem() + + file = file_system.create_file(file_size=10) + assert len(file_system.get_folders()) is 1 + assert file_system.get_folders()[0].get_file(file.uuid) is file + + file_system.delete_file(file.uuid) + assert len(file_system.get_folders()) is 1 + assert len(file_system.get_folders()[0].get_files()) is 0 + + +def test_delete_folder(): + file_system = FileSystem() + folder = file_system.create_folder() + assert len(file_system.get_folders()) is 1 + + file_system.delete_folder(folder.uuid) + assert len(file_system.get_folders()) is 0 + + +def test_move_file(): + """Tests the file move function.""" + file_system = FileSystem() + src_folder = file_system.create_folder() + assert len(file_system.get_folders()) is 1 + + target_folder = file_system.create_folder() + assert len(file_system.get_folders()) is 2 + + file = file_system.create_file(file_size=10, folder_uuid=src_folder.uuid) + assert len(file_system.get_folder_by_id(src_folder.uuid).get_files()) is 1 + assert len(file_system.get_folder_by_id(target_folder.uuid).get_files()) is 0 + + file_system.move_file(src_folder.uuid, target_folder.uuid, file.uuid) + + assert len(file_system.get_folder_by_id(src_folder.uuid).get_files()) is 0 + assert len(file_system.get_folder_by_id(target_folder.uuid).get_files()) is 1 + + +def test_copy_file(): + """Tests the file copy function.""" + file_system = FileSystem() + src_folder = file_system.create_folder() + assert len(file_system.get_folders()) is 1 + + target_folder = file_system.create_folder() + assert len(file_system.get_folders()) is 2 + + file = file_system.create_file(file_size=10, folder_uuid=src_folder.uuid) + assert len(file_system.get_folder_by_id(src_folder.uuid).get_files()) is 1 + assert len(file_system.get_folder_by_id(target_folder.uuid).get_files()) is 0 + + file_system.copy_file(src_folder.uuid, target_folder.uuid, file.uuid) + + assert len(file_system.get_folder_by_id(src_folder.uuid).get_files()) is 1 + assert len(file_system.get_folder_by_id(target_folder.uuid).get_files()) is 1 diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_file.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_file.py index e69de29b..34c8dd94 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_file.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_file.py @@ -0,0 +1,14 @@ +from primaite.simulator.file_system.file_system_file import FileSystemFile +from primaite.simulator.file_system.file_system_file_type import FileSystemFileType + + +def test_file_type(): + """Tests tha the FileSystemFile type is set correctly.""" + file = FileSystemFile(file_size=1.5, file_type=FileSystemFileType.TBD) + assert file.get_file_type() is FileSystemFileType.TBD + + +def test_get_file_size(): + """Tests that the file size is being returned properly.""" + file = FileSystemFile(file_size=1.5, file_type=FileSystemFileType.TBD) + assert file.get_file_size() is 1.5 diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_folder.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_folder.py index e69de29b..b67ea385 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_folder.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_folder.py @@ -0,0 +1,41 @@ +from primaite.simulator.file_system.file_system_file import FileSystemFile +from primaite.simulator.file_system.file_system_file_type import FileSystemFileType +from primaite.simulator.file_system.file_system_folder import FileSystemFolder + + +def test_adding_removing_file(): + folder = FileSystemFolder() + + file = FileSystemFile(file_size=10, file_type=FileSystemFileType.TBD) + + folder.add_file(file) + assert folder.get_folder_size() is 10 + assert len(folder.get_files()) is 1 + + folder.remove_file(file_id=file.uuid) + assert folder.get_folder_size() is 0 + assert len(folder.get_files()) is 0 + + +def test_get_file_by_id(): + folder = FileSystemFolder() + + file = FileSystemFile(file_size=10, file_type=FileSystemFileType.TBD) + + folder.add_file(file) + assert folder.get_folder_size() is 10 + assert len(folder.get_files()) is 1 + + assert folder.get_file(file_id=file.uuid) is file + + +def test_folder_quarantine_state(): + folder = FileSystemFolder() + + assert folder.quarantine_status() is False + + folder.quarantine() + assert folder.quarantine_status() is True + + folder.end_quarantine() + assert folder.quarantine_status() is False From b08683fcd322919ce59c9c646d98121265caaa88 Mon Sep 17 00:00:00 2001 From: Czar Echavez Date: Thu, 3 Aug 2023 12:42:16 +0100 Subject: [PATCH 03/14] #1714: fix tests --- pyproject.toml | 2 +- tests/conftest.py | 2 +- tests/unit_tests/_primaite/_simulator/test_core.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4982dfd1..74de37df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ dependencies = [ "stable-baselines3==1.6.2", "tensorflow==2.12.0", "typer[all]==0.9.0", - "pydantic" + "pydantic==2.1.1" ] [tool.setuptools.dynamic] diff --git a/tests/conftest.py b/tests/conftest.py index 8102050e..f1c05187 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -96,7 +96,7 @@ def temp_primaite_session(request): """ training_config_path = request.param[0] lay_down_config_path = request.param[1] - with patch("_primaite.agents.agent_abc.get_session_path", get_temp_session_path) as mck: + with patch("primaite.agents.agent_abc.get_session_path", get_temp_session_path) as mck: mck.session_timestamp = datetime.now() return TempPrimaiteSession(training_config_path, lay_down_config_path) diff --git a/tests/unit_tests/_primaite/_simulator/test_core.py b/tests/unit_tests/_primaite/_simulator/test_core.py index de0732f9..00f29791 100644 --- a/tests/unit_tests/_primaite/_simulator/test_core.py +++ b/tests/unit_tests/_primaite/_simulator/test_core.py @@ -43,7 +43,7 @@ class TestIsolatedSimComponent: comp = TestComponent(name="computer", size=(5, 10)) dump = comp.model_dump() - assert dump == {"name": "computer", "size": (5, 10)} + assert dump["name"] is "computer" def test_apply_action(self): """Validate that we can override apply_action behaviour and it updates the state of the component.""" From 46c70ac084631f1321fd8b9b4b5478da0ef83448 Mon Sep 17 00:00:00 2001 From: Czar Echavez Date: Thu, 3 Aug 2023 22:20:14 +0100 Subject: [PATCH 04/14] #1714: refactor private attributes and made them public + serialisation tests --- .../simulator/file_system/file_system.py | 18 +++++------ .../simulator/file_system/file_system_file.py | 31 ++++++++++++------- .../file_system/file_system_folder.py | 30 +++++++++--------- .../_file_system/test_file_system.py | 15 +++++++++ .../_file_system/test_file_system_file.py | 9 ++++++ .../_file_system/test_file_system_folder.py | 22 +++++++++++-- 6 files changed, 85 insertions(+), 40 deletions(-) diff --git a/src/primaite/simulator/file_system/file_system.py b/src/primaite/simulator/file_system/file_system.py index 6af6db3e..e2f89809 100644 --- a/src/primaite/simulator/file_system/file_system.py +++ b/src/primaite/simulator/file_system/file_system.py @@ -1,7 +1,5 @@ from typing import Dict, List, Optional -from pydantic import PrivateAttr - from primaite.simulator.core import SimComponent from primaite.simulator.file_system.file_system_file import FileSystemFile from primaite.simulator.file_system.file_system_file_type import FileSystemFileType @@ -11,7 +9,7 @@ from primaite.simulator.file_system.file_system_folder import FileSystemFolder class FileSystem(SimComponent): """Class that contains all the simulation File System.""" - _folders: List[FileSystemFolder] = PrivateAttr([]) + folders: List[FileSystemFolder] = [] """List containing all the folders in the file system.""" def describe_state(self) -> Dict: @@ -24,7 +22,7 @@ class FileSystem(SimComponent): def get_folders(self) -> List[FileSystemFolder]: """Returns the list of folders.""" - return self._folders + return self.folders def create_file(self, file_size: float, folder_uuid: Optional[str] = None) -> FileSystemFile: """ @@ -40,7 +38,7 @@ class FileSystem(SimComponent): file = FileSystemFile(item_parent=folder.uuid, file_size=file_size, file_type=FileSystemFileType.TBD) folder.add_file(file) - self._folders.append(folder) + self.folders.append(folder) else: # otherwise check for existence and add file folder = self.get_folder_by_id(folder_uuid) @@ -52,7 +50,7 @@ class FileSystem(SimComponent): def create_folder(self) -> FileSystemFolder: """Creates a FileSystemFolder and adds it to the list of folders.""" folder = FileSystemFolder(item_parent=None) - self._folders.append(folder) + self.folders.append(folder) return folder def delete_file(self, file_id: str): @@ -63,7 +61,7 @@ class FileSystem(SimComponent): :type file_id: str """ # iterate through folders to delete the item with the matching uuid - for folder in self._folders: + for folder in self.folders: folder.remove_file(file_id) def delete_folder(self, folder_id: str): @@ -73,7 +71,7 @@ class FileSystem(SimComponent): :param folder_id: The UUID of the file item to delete :type folder_id: str """ - self._folders = list(filter(lambda f: (f.uuid != folder_id), self._folders)) + self.folders = list(filter(lambda f: (f.uuid != folder_id), self.folders)) def move_file(self, src_folder_id: str, target_folder_id: str, file_id: str): """Moves a file from one folder to another.""" @@ -118,11 +116,11 @@ class FileSystem(SimComponent): def get_file_by_id(self, file_id: str) -> FileSystemFile: """Checks if the file exists in any file system folders.""" - for folder in self._folders: + for folder in self.folders: file = folder.get_file(file_id=file_id) if file is not None: return file def get_folder_by_id(self, folder_id: str) -> FileSystemFolder: """Checks if the folder exists.""" - return next((f for f in self._folders if f.uuid == folder_id), None) + return next((f for f in self.folders if f.uuid == folder_id), None) diff --git a/src/primaite/simulator/file_system/file_system_file.py b/src/primaite/simulator/file_system/file_system_file.py index bebaa223..f10ae0ad 100644 --- a/src/primaite/simulator/file_system/file_system_file.py +++ b/src/primaite/simulator/file_system/file_system_file.py @@ -1,7 +1,6 @@ +from random import choice, random from typing import Dict -from pydantic import PrivateAttr - from primaite.simulator.core import SimComponent from primaite.simulator.file_system.file_system_file_type import FileSystemFileType @@ -9,34 +8,42 @@ from primaite.simulator.file_system.file_system_file_type import FileSystemFileT class FileSystemFile(SimComponent): """Class that represents a file in the simulation.""" - _file_type: FileSystemFileType = PrivateAttr() + file_type: FileSystemFileType = None """The type of the FileSystemFile""" - _file_size: float = PrivateAttr() + file_size: float = 0 """Disk size of the FileSystemItem""" - def __init__(self, file_type: FileSystemFileType, file_size: float, **kwargs): + def __init__(self, **kwargs): """ Initialise FileSystemFile class. - :param item_parent: The UUID of the FileSystemItem parent - :type item_parent: str + :param file_type: The FileSystemFileType of the file + :type file_type: Optional[FileSystemFileType] :param file_size: The size of the FileSystemItem - :type file_size: float + :type file_size: Optional[float] """ super().__init__(**kwargs) - self._file_type = file_type - self._file_size = file_size + self.file_type = choice(list(FileSystemFileType)) + self.file_size = random() + + # set random file size if non provided + if kwargs.get("file_size") is not None: + self.file_size = kwargs.get("file_size") + + # set random file type if none provided + if kwargs.get("file_type") is None: + self.file_type = kwargs.get("file_type") def get_file_size(self) -> float: """Returns the size of the file system item.""" - return self._file_size + return self.file_size def get_file_type(self) -> FileSystemFileType: """Returns the FileSystemFileType of the file.""" - return self._file_type + return self.file_type def describe_state(self) -> Dict: """ diff --git a/src/primaite/simulator/file_system/file_system_folder.py b/src/primaite/simulator/file_system/file_system_folder.py index 248a4f98..a2bcd226 100644 --- a/src/primaite/simulator/file_system/file_system_folder.py +++ b/src/primaite/simulator/file_system/file_system_folder.py @@ -1,7 +1,5 @@ from typing import Dict, List -from pydantic import PrivateAttr - from primaite.simulator.core import SimComponent from primaite.simulator.file_system.file_system_file import FileSystemFile @@ -9,53 +7,53 @@ from primaite.simulator.file_system.file_system_file import FileSystemFile class FileSystemFolder(SimComponent): """Simulation FileSystemFolder.""" - _files: List[FileSystemFile] = PrivateAttr([]) + files: List[FileSystemFile] = [] """List of files stored in the folder.""" - _folder_size: float = PrivateAttr(0) + folder_size: float = 0 """The current size of the folder""" - _is_quarantined: bool = PrivateAttr(False) + is_quarantined: bool = False """Flag that marks the folder as quarantined if true.""" def get_files(self) -> List[FileSystemFile]: """Returns the list of files the folder contains.""" - return self._files + return self.files def get_file(self, file_id: str) -> FileSystemFile: """Return a FileSystemFile with the matching id.""" - return next((f for f in self._files if f.uuid == file_id), None) + return next((f for f in self.files if f.uuid == file_id), None) def add_file(self, file: FileSystemFile): """Adds a file to the folder list.""" - self._folder_size += file.get_file_size() + self.folder_size += file.get_file_size() # add to list - self._files.append(file) + self.files.append(file) def remove_file(self, file_id: str): """Removes a file from the folder list.""" - file = next((f for f in self._files if f.uuid == file_id), None) - self._files.remove(file) + file = next((f for f in self.files if f.uuid == file_id), None) + self.files.remove(file) # remove folder size from folder - self._folder_size -= file.get_file_size() + self.folder_size -= file.get_file_size() def get_folder_size(self) -> float: """Returns a sum of all file sizes in the files list.""" - return sum([file.get_file_size() for file in self._files]) + return sum([file.get_file_size() for file in self.files]) def quarantine(self): """Quarantines the File System Folder.""" - self._is_quarantined = True + self.is_quarantined = True def end_quarantine(self): """Ends the quarantine of the File System Folder.""" - self._is_quarantined = False + self.is_quarantined = False def quarantine_status(self) -> bool: """Returns true if the folder is being quarantined.""" - return self._is_quarantined + return self.is_quarantined def describe_state(self) -> Dict: """ diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py index 7b26f707..e19b2bf5 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py @@ -78,3 +78,18 @@ def test_copy_file(): assert len(file_system.get_folder_by_id(src_folder.uuid).get_files()) is 1 assert len(file_system.get_folder_by_id(target_folder.uuid).get_files()) is 1 + + +def test_serialisation(): + """Test to check that the object serialisation works correctly.""" + file_system = FileSystem() + folder = file_system.create_folder() + assert len(file_system.get_folders()) is 1 + + file_system.create_file(file_size=10, folder_uuid=folder.uuid) + assert len(file_system.get_folders()[0].get_files()) is 1 + + serialised_file_sys = file_system.model_dump_json() + deserialised_file_sys = FileSystem().model_validate_json(serialised_file_sys) + + assert file_system == deserialised_file_sys diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_file.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_file.py index 34c8dd94..31967f31 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_file.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_file.py @@ -12,3 +12,12 @@ def test_get_file_size(): """Tests that the file size is being returned properly.""" file = FileSystemFile(file_size=1.5, file_type=FileSystemFileType.TBD) assert file.get_file_size() is 1.5 + + +def test_serialisation(): + """Test to check that the object serialisation works correctly.""" + file = FileSystemFile(file_size=1.5, file_type=FileSystemFileType.TBD) + serialised_file = file.model_dump_json() + deserialised_file = FileSystemFile().model_validate_json(serialised_file) + + assert file == deserialised_file diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_folder.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_folder.py index b67ea385..c04465c3 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_folder.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_folder.py @@ -4,6 +4,7 @@ from primaite.simulator.file_system.file_system_folder import FileSystemFolder def test_adding_removing_file(): + """Test the adding and removing of a file from a folder.""" folder = FileSystemFolder() file = FileSystemFile(file_size=10, file_type=FileSystemFileType.TBD) @@ -18,18 +19,22 @@ def test_adding_removing_file(): def test_get_file_by_id(): + """Test to make sure that the correct file is returned.""" folder = FileSystemFolder() file = FileSystemFile(file_size=10, file_type=FileSystemFileType.TBD) + file2 = FileSystemFile(file_size=10, file_type=FileSystemFileType.TBD) folder.add_file(file) - assert folder.get_folder_size() is 10 - assert len(folder.get_files()) is 1 + folder.add_file(file2) + assert folder.get_folder_size() is 20 + assert len(folder.get_files()) is 2 assert folder.get_file(file_id=file.uuid) is file def test_folder_quarantine_state(): + """Tests the changing of folder quarantine status.""" folder = FileSystemFolder() assert folder.quarantine_status() is False @@ -39,3 +44,16 @@ def test_folder_quarantine_state(): folder.end_quarantine() assert folder.quarantine_status() is False + + +def test_serialisation(): + """Test to check that the object serialisation works correctly.""" + folder = FileSystemFolder() + file = FileSystemFile(file_size=10, file_type=FileSystemFileType.TBD) + folder.add_file(file) + + serialised_folder = folder.model_dump_json() + + deserialised_folder = FileSystemFolder().model_validate_json(serialised_folder) + + assert folder == deserialised_folder From 028211d2881290ca160a663f109f0a2b8d403534 Mon Sep 17 00:00:00 2001 From: Czar Echavez Date: Mon, 7 Aug 2023 09:34:59 +0100 Subject: [PATCH 05/14] #1714: update to use objects instead of uuids + tests --- .../simulator/file_system/file_system.py | 158 +++++++++++++----- .../simulator/file_system/file_system_file.py | 37 ++-- .../file_system/file_system_file_type.py | 8 +- .../file_system/file_system_folder.py | 33 ++-- .../file_system/file_system_item_abc.py | 13 ++ .../_file_system/test_file_system.py | 36 ++-- .../_file_system/test_file_system_file.py | 10 +- .../_file_system/test_file_system_folder.py | 20 +-- 8 files changed, 211 insertions(+), 104 deletions(-) create mode 100644 src/primaite/simulator/file_system/file_system_item_abc.py diff --git a/src/primaite/simulator/file_system/file_system.py b/src/primaite/simulator/file_system/file_system.py index e2f89809..f8ae9d67 100644 --- a/src/primaite/simulator/file_system/file_system.py +++ b/src/primaite/simulator/file_system/file_system.py @@ -1,3 +1,4 @@ +from random import choice from typing import Dict, List, Optional from primaite.simulator.core import SimComponent @@ -24,95 +25,148 @@ class FileSystem(SimComponent): """Returns the list of folders.""" return self.folders - def create_file(self, file_size: float, folder_uuid: Optional[str] = None) -> FileSystemFile: + def create_file( + self, + file_name: str, + file_size: Optional[float] = None, + file_type: Optional[FileSystemFileType] = None, + folder: Optional[FileSystemFolder] = None, + folder_uuid: Optional[str] = None, + ) -> FileSystemFile: """ Creates a FileSystemFile and adds it to the list of files. + If no file_size or file_type are provided, one will be chosen randomly. + If no folder_uuid or folder is provided, a new folder will be created. + + :param: file_name: The file name + :type: file_name: str + + :param: file_size: The size the file takes on disk. + :type: file_size: Optional[float] + + :param: file_type: The type of the file + :type: Optional[FileSystemFileType] + :param: folder_uuid: The uuid of the folder to add the file to - :type: folder_uuid: str + :type: folder_uuid: Optional[str] """ file = None - # if no folder uuid provided, create a folder and add file to it - if folder_uuid is None: - folder = FileSystemFolder() + folder = None - file = FileSystemFile(item_parent=folder.uuid, file_size=file_size, file_type=FileSystemFileType.TBD) - folder.add_file(file) - self.folders.append(folder) - else: + if file_type is None: + file_type = self.get_random_file_type() + + # if no folder uuid provided, create a folder and add file to it + if folder_uuid is not None: # otherwise check for existence and add file folder = self.get_folder_by_id(folder_uuid) - if folder is not None: - file = FileSystemFile(file_size=file_size, file_type=FileSystemFileType.TBD) - folder.add_file(file=file) + + if folder is not None: + file = FileSystemFile(item_name=file_name, item_size=file_size, file_type=file_type) + folder.add_file(file=file) + else: + # check if a "root" folder exists + folder = self.get_folder_by_name("root") + if folder is None: + # create a root folder + folder = FileSystemFolder(item_name="root") + + # add file to root folder + file = FileSystemFile(item_name=file_name, item_size=file_size, file_type=file_type) + folder.add_file(file) + self.folders.append(folder) return file - def create_folder(self) -> FileSystemFolder: + def create_folder( + self, + folder_name: str, + ) -> FileSystemFolder: """Creates a FileSystemFolder and adds it to the list of folders.""" - folder = FileSystemFolder(item_parent=None) + folder = FileSystemFolder(item_name=folder_name) self.folders.append(folder) return folder - def delete_file(self, file_id: str): + def delete_file(self, file: Optional[FileSystemFile] = None): """ Deletes a file and removes it from the files list. + :param file: The file to delete + :type file: Optional[FileSystemFile] + :param file_id: The UUID of the file item to delete - :type file_id: str + :type file_id: Optional[str] """ # iterate through folders to delete the item with the matching uuid for folder in self.folders: - folder.remove_file(file_id) + folder.remove_file(file=file) - def delete_folder(self, folder_id: str): + def delete_folder(self, folder: FileSystemFolder): """ Deletes a folder, removes it from the folders list and removes any child folders and files. - :param folder_id: The UUID of the file item to delete - :type folder_id: str + :param folder: The folder to remove + :type folder: FileSystemFolder """ - self.folders = list(filter(lambda f: (f.uuid != folder_id), self.folders)) + self.folders.remove(folder) - def move_file(self, src_folder_id: str, target_folder_id: str, file_id: str): - """Moves a file from one folder to another.""" - # check that both folders and the file exists - src = self.get_folder_by_id(src_folder_id) - target = self.get_folder_by_id(target_folder_id) + def move_file(self, file: FileSystemFile, src_folder: FileSystemFolder, target_folder: FileSystemFolder): + """ + Moves a file from one folder to another. - if src is None: - raise Exception(f"src folder with UUID {src_folder_id} could not be found") + can provide - if target is None: - raise Exception(f"src folder with UUID {target_folder_id} could not be found") + :param: file: The file to move + :type: file: FileSystemFile + + :param: src_folder: The folder where the file is located + :type: FileSystemFolder + + :param: target_folder: The folder where the file should be moved to + :type: FileSystemFolder + """ + # check that the folders exist + if src_folder is None: + raise Exception("Source folder not provided") + + if target_folder is None: + raise Exception("Target folder not provided") - file = src.get_file(file_id=file_id) if file is None: - raise Exception(f"file with UUID {file_id} could not be found") + raise Exception("File to be moved is None") # remove file from src - src.remove_file(file_id) + src_folder.remove_file(file) # add file to target - target.add_file(file) + target_folder.add_file(file) - def copy_file(self, src_folder_id: str, target_folder_id: str, file_id: str): - """Copies a file from one folder to another.""" - # check that both folders and the file exists - src = self.get_folder_by_id(src_folder_id) - target = self.get_folder_by_id(target_folder_id) + def copy_file(self, file: FileSystemFile, src_folder: FileSystemFolder, target_folder: FileSystemFolder): + """ + Copies a file from one folder to another. - if src is None: - raise Exception(f"src folder with UUID {src_folder_id} could not be found") + can provide - if target is None: - raise Exception(f"src folder with UUID {target_folder_id} could not be found") + :param: file: The file to move + :type: file: FileSystemFile + + :param: src_folder: The folder where the file is located + :type: FileSystemFolder + + :param: target_folder: The folder where the file should be moved to + :type: FileSystemFolder + """ + if src_folder is None: + raise Exception("Source folder not provided") + + if target_folder is None: + raise Exception("Target folder not provided") - file = src.get_file(file_id=file_id) if file is None: - raise Exception(f"file with UUID {file_id} could not be found") + raise Exception("File to be moved is None") # add file to target - target.add_file(file) + target_folder.add_file(file) def get_file_by_id(self, file_id: str) -> FileSystemFile: """Checks if the file exists in any file system folders.""" @@ -121,6 +175,18 @@ class FileSystem(SimComponent): if file is not None: return file + def get_folder_by_name(self, folder_name: str) -> FileSystemFolder: + """ + Returns a the first folder with a matching name. + + :return: Returns the first FileSydtemFolder with a matching name + """ + return next((f for f in self.folders if f.get_folder_name() == folder_name), None) + def get_folder_by_id(self, folder_id: str) -> FileSystemFolder: """Checks if the folder exists.""" return next((f for f in self.folders if f.uuid == folder_id), None) + + def get_random_file_type(self) -> FileSystemFileType: + """Returns a random FileSystemFileTypeEnum.""" + return choice(list(FileSystemFileType)) diff --git a/src/primaite/simulator/file_system/file_system_file.py b/src/primaite/simulator/file_system/file_system_file.py index f10ae0ad..441a27b0 100644 --- a/src/primaite/simulator/file_system/file_system_file.py +++ b/src/primaite/simulator/file_system/file_system_file.py @@ -1,45 +1,54 @@ -from random import choice, random +from random import choice, randint from typing import Dict -from primaite.simulator.core import SimComponent from primaite.simulator.file_system.file_system_file_type import FileSystemFileType +from primaite.simulator.file_system.file_system_item_abc import FileSystemItemABC -class FileSystemFile(SimComponent): +class FileSystemFile(FileSystemItemABC): """Class that represents a file in the simulation.""" file_type: FileSystemFileType = None """The type of the FileSystemFile""" - file_size: float = 0 - """Disk size of the FileSystemItem""" - def __init__(self, **kwargs): """ Initialise FileSystemFile class. + :param item_name: The name of the file. + :type item_name: str + :param file_type: The FileSystemFileType of the file :type file_type: Optional[FileSystemFileType] - :param file_size: The size of the FileSystemItem - :type file_size: Optional[float] + :param item_size: The size of the FileSystemItem + :type item_size: Optional[float] """ super().__init__(**kwargs) - self.file_type = choice(list(FileSystemFileType)) - self.file_size = random() + # set random file type if none provided + if kwargs.get("item_name") is None: + raise Exception("File name not provided.") # set random file size if non provided - if kwargs.get("file_size") is not None: - self.file_size = kwargs.get("file_size") + if kwargs.get("item_size") is None: + kwargs["item_size"] = float(randint(1, 1000)) # set random file type if none provided if kwargs.get("file_type") is None: - self.file_type = kwargs.get("file_type") + kwargs["file_type"] = choice(list(FileSystemFileType)) + + self.item_name = kwargs.get("item_name") + self.item_size = kwargs.get("item_size") + self.file_type = kwargs.get("file_type") + + def get_file_name(self) -> str: + """Returns the name of the file.""" + return self.item_name def get_file_size(self) -> float: """Returns the size of the file system item.""" - return self.file_size + return self.item_size def get_file_type(self) -> FileSystemFileType: """Returns the FileSystemFileType of the file.""" diff --git a/src/primaite/simulator/file_system/file_system_file_type.py b/src/primaite/simulator/file_system/file_system_file_type.py index 134b38f4..fd11f30f 100644 --- a/src/primaite/simulator/file_system/file_system_file_type.py +++ b/src/primaite/simulator/file_system/file_system_file_type.py @@ -4,4 +4,10 @@ from enum import Enum class FileSystemFileType(str, Enum): """Enum used to determine the FileSystemFile type.""" - TBD = "TBD" + CSV = ("CSV",) + DOC = ("DOC",) + EXE = ("EXE",) + PDF = ("PDF",) + TXT = ("TXT",) + XML = ("XML",) + ZIP = "ZIP" diff --git a/src/primaite/simulator/file_system/file_system_folder.py b/src/primaite/simulator/file_system/file_system_folder.py index a2bcd226..2d0b0eb0 100644 --- a/src/primaite/simulator/file_system/file_system_folder.py +++ b/src/primaite/simulator/file_system/file_system_folder.py @@ -1,21 +1,26 @@ -from typing import Dict, List +from typing import Dict, List, Optional -from primaite.simulator.core import SimComponent from primaite.simulator.file_system.file_system_file import FileSystemFile +from primaite.simulator.file_system.file_system_item_abc import FileSystemItemABC -class FileSystemFolder(SimComponent): +class FileSystemFolder(FileSystemItemABC): """Simulation FileSystemFolder.""" files: List[FileSystemFile] = [] """List of files stored in the folder.""" - folder_size: float = 0 - """The current size of the folder""" - is_quarantined: bool = False """Flag that marks the folder as quarantined if true.""" + def get_folder_name(self) -> str: + """Returns the item_name of the folder.""" + return self.item_name + + def get_folder_size(self) -> float: + """Returns the item_size of the folder.""" + return self.item_size + def get_files(self) -> List[FileSystemFile]: """Returns the list of files the folder contains.""" return self.files @@ -26,18 +31,24 @@ class FileSystemFolder(SimComponent): def add_file(self, file: FileSystemFile): """Adds a file to the folder list.""" - self.folder_size += file.get_file_size() + self.item_size += file.get_file_size() # add to list self.files.append(file) - def remove_file(self, file_id: str): - """Removes a file from the folder list.""" - file = next((f for f in self.files if f.uuid == file_id), None) + def remove_file(self, file: Optional[FileSystemFile]): + """ + Removes a file from the folder list. + + The method can take a FileSystemFile object or a file id. + + :param: file: The file to remove + :type: Optional[FileSystemFile] + """ self.files.remove(file) # remove folder size from folder - self.folder_size -= file.get_file_size() + self.item_size -= file.get_file_size() def get_folder_size(self) -> float: """Returns a sum of all file sizes in the files list.""" diff --git a/src/primaite/simulator/file_system/file_system_item_abc.py b/src/primaite/simulator/file_system/file_system_item_abc.py new file mode 100644 index 00000000..4dca0f4e --- /dev/null +++ b/src/primaite/simulator/file_system/file_system_item_abc.py @@ -0,0 +1,13 @@ +from abc import ABC + +from primaite.simulator.core import SimComponent + + +class FileSystemItemABC(SimComponent, ABC): + """Abstract base class for FileSystemItems used in the file system simulation.""" + + item_size: float = 0 + """The size the item takes up on disk.""" + + item_name: str + """The name of the file system item.""" diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py index e19b2bf5..dae8b34e 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py @@ -4,18 +4,20 @@ from primaite.simulator.file_system.file_system import FileSystem def test_create_folder_and_file(): """Test creating a folder and a file.""" file_system = FileSystem() - folder = file_system.create_folder() + folder = file_system.create_folder(folder_name="test_folder") assert len(file_system.get_folders()) is 1 - file_system.create_file(file_size=10, folder_uuid=folder.uuid) + file_system.create_file(file_name="test_file", file_size=10, folder_uuid=folder.uuid) assert len(file_system.get_folders()[0].get_files()) is 1 + assert file_system.get_folders()[0].get_files()[0].get_file_name() is "test_file" + assert file_system.get_folders()[0].get_files()[0].get_file_size() is 10 def test_create_file(): """Tests that creating a file without a folder creates a folder and sets that as the file's parent.""" file_system = FileSystem() - file = file_system.create_file(file_size=10) + file = file_system.create_file(file_name="test_file", file_size=10) assert len(file_system.get_folders()) is 1 assert file_system.get_folders()[0].get_file(file.uuid) is file @@ -24,38 +26,38 @@ def test_delete_file(): """Tests that a file can be deleted.""" file_system = FileSystem() - file = file_system.create_file(file_size=10) + file = file_system.create_file(file_name="test_file", file_size=10) assert len(file_system.get_folders()) is 1 assert file_system.get_folders()[0].get_file(file.uuid) is file - file_system.delete_file(file.uuid) + file_system.delete_file(file=file) assert len(file_system.get_folders()) is 1 assert len(file_system.get_folders()[0].get_files()) is 0 def test_delete_folder(): file_system = FileSystem() - folder = file_system.create_folder() + folder = file_system.create_folder(folder_name="test_folder") assert len(file_system.get_folders()) is 1 - file_system.delete_folder(folder.uuid) + file_system.delete_folder(folder) assert len(file_system.get_folders()) is 0 def test_move_file(): """Tests the file move function.""" file_system = FileSystem() - src_folder = file_system.create_folder() + src_folder = file_system.create_folder(folder_name="test_folder_1") assert len(file_system.get_folders()) is 1 - target_folder = file_system.create_folder() + target_folder = file_system.create_folder(folder_name="test_folder_2") assert len(file_system.get_folders()) is 2 - file = file_system.create_file(file_size=10, folder_uuid=src_folder.uuid) + file = file_system.create_file(file_name="test_file", file_size=10, folder_uuid=src_folder.uuid) assert len(file_system.get_folder_by_id(src_folder.uuid).get_files()) is 1 assert len(file_system.get_folder_by_id(target_folder.uuid).get_files()) is 0 - file_system.move_file(src_folder.uuid, target_folder.uuid, file.uuid) + file_system.move_file(file=file, src_folder=src_folder, target_folder=target_folder) assert len(file_system.get_folder_by_id(src_folder.uuid).get_files()) is 0 assert len(file_system.get_folder_by_id(target_folder.uuid).get_files()) is 1 @@ -64,17 +66,17 @@ def test_move_file(): def test_copy_file(): """Tests the file copy function.""" file_system = FileSystem() - src_folder = file_system.create_folder() + src_folder = file_system.create_folder(folder_name="test_folder_1") assert len(file_system.get_folders()) is 1 - target_folder = file_system.create_folder() + target_folder = file_system.create_folder(folder_name="test_folder_2") assert len(file_system.get_folders()) is 2 - file = file_system.create_file(file_size=10, folder_uuid=src_folder.uuid) + file = file_system.create_file(file_name="test_file", file_size=10, folder_uuid=src_folder.uuid) assert len(file_system.get_folder_by_id(src_folder.uuid).get_files()) is 1 assert len(file_system.get_folder_by_id(target_folder.uuid).get_files()) is 0 - file_system.copy_file(src_folder.uuid, target_folder.uuid, file.uuid) + file_system.copy_file(file=file, src_folder=src_folder, target_folder=target_folder) assert len(file_system.get_folder_by_id(src_folder.uuid).get_files()) is 1 assert len(file_system.get_folder_by_id(target_folder.uuid).get_files()) is 1 @@ -83,10 +85,10 @@ def test_copy_file(): def test_serialisation(): """Test to check that the object serialisation works correctly.""" file_system = FileSystem() - folder = file_system.create_folder() + folder = file_system.create_folder(folder_name="test_folder") assert len(file_system.get_folders()) is 1 - file_system.create_file(file_size=10, folder_uuid=folder.uuid) + file_system.create_file(file_name="test_file", file_size=10, folder_uuid=folder.uuid) assert len(file_system.get_folders()[0].get_files()) is 1 serialised_file_sys = file_system.model_dump_json() diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_file.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_file.py index 31967f31..669be62d 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_file.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_file.py @@ -4,20 +4,20 @@ from primaite.simulator.file_system.file_system_file_type import FileSystemFileT def test_file_type(): """Tests tha the FileSystemFile type is set correctly.""" - file = FileSystemFile(file_size=1.5, file_type=FileSystemFileType.TBD) - assert file.get_file_type() is FileSystemFileType.TBD + file = FileSystemFile(item_name="test", file_type=FileSystemFileType.DOC) + assert file.get_file_type() is FileSystemFileType.DOC def test_get_file_size(): """Tests that the file size is being returned properly.""" - file = FileSystemFile(file_size=1.5, file_type=FileSystemFileType.TBD) + file = FileSystemFile(item_name="test", item_size=1.5) assert file.get_file_size() is 1.5 def test_serialisation(): """Test to check that the object serialisation works correctly.""" - file = FileSystemFile(file_size=1.5, file_type=FileSystemFileType.TBD) + file = FileSystemFile(item_name="test", item_size=1.5, file_type=FileSystemFileType.DOC) serialised_file = file.model_dump_json() - deserialised_file = FileSystemFile().model_validate_json(serialised_file) + deserialised_file = FileSystemFile(item_name="test").model_validate_json(serialised_file) assert file == deserialised_file diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_folder.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_folder.py index c04465c3..f9ba80f0 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_folder.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_folder.py @@ -5,25 +5,25 @@ from primaite.simulator.file_system.file_system_folder import FileSystemFolder def test_adding_removing_file(): """Test the adding and removing of a file from a folder.""" - folder = FileSystemFolder() + folder = FileSystemFolder(item_name="test") - file = FileSystemFile(file_size=10, file_type=FileSystemFileType.TBD) + file = FileSystemFile(item_name="test_file", item_size=10, file_type=FileSystemFileType.DOC) folder.add_file(file) assert folder.get_folder_size() is 10 assert len(folder.get_files()) is 1 - folder.remove_file(file_id=file.uuid) + folder.remove_file(file) assert folder.get_folder_size() is 0 assert len(folder.get_files()) is 0 def test_get_file_by_id(): """Test to make sure that the correct file is returned.""" - folder = FileSystemFolder() + folder = FileSystemFolder(item_name="test") - file = FileSystemFile(file_size=10, file_type=FileSystemFileType.TBD) - file2 = FileSystemFile(file_size=10, file_type=FileSystemFileType.TBD) + file = FileSystemFile(item_name="test_file", item_size=10, file_type=FileSystemFileType.DOC) + file2 = FileSystemFile(item_name="test_file_2", item_size=10, file_type=FileSystemFileType.DOC) folder.add_file(file) folder.add_file(file2) @@ -35,7 +35,7 @@ def test_get_file_by_id(): def test_folder_quarantine_state(): """Tests the changing of folder quarantine status.""" - folder = FileSystemFolder() + folder = FileSystemFolder(item_name="test") assert folder.quarantine_status() is False @@ -48,12 +48,12 @@ def test_folder_quarantine_state(): def test_serialisation(): """Test to check that the object serialisation works correctly.""" - folder = FileSystemFolder() - file = FileSystemFile(file_size=10, file_type=FileSystemFileType.TBD) + folder = FileSystemFolder(item_name="test") + file = FileSystemFile(item_name="test_file", item_size=10, file_type=FileSystemFileType.DOC) folder.add_file(file) serialised_folder = folder.model_dump_json() - deserialised_folder = FileSystemFolder().model_validate_json(serialised_folder) + deserialised_folder = FileSystemFolder(item_name="test").model_validate_json(serialised_folder) assert folder == deserialised_folder From d57c2a936efd047a75cdae739894b8e4683eafb9 Mon Sep 17 00:00:00 2001 From: Czar Echavez Date: Mon, 7 Aug 2023 10:10:05 +0100 Subject: [PATCH 06/14] #1714: remove duplicate method --- src/primaite/simulator/file_system/file_system_folder.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/primaite/simulator/file_system/file_system_folder.py b/src/primaite/simulator/file_system/file_system_folder.py index 2d0b0eb0..f5024966 100644 --- a/src/primaite/simulator/file_system/file_system_folder.py +++ b/src/primaite/simulator/file_system/file_system_folder.py @@ -50,10 +50,6 @@ class FileSystemFolder(FileSystemItemABC): # remove folder size from folder self.item_size -= file.get_file_size() - def get_folder_size(self) -> float: - """Returns a sum of all file sizes in the files list.""" - return sum([file.get_file_size() for file in self.files]) - def quarantine(self): """Quarantines the File System Folder.""" self.is_quarantined = True From b58a3a3e24455707fbf5ae13d637565c7f30b9c6 Mon Sep 17 00:00:00 2001 From: Czar Echavez Date: Mon, 7 Aug 2023 11:52:54 +0100 Subject: [PATCH 07/14] #1714: FileSystemItem is no longer an abstract base class + Added enums and enum sizes + stream lined FileSystemFile init --- .../simulator/file_system/file_system_file.py | 20 ++- .../file_system/file_system_file_type.py | 127 ++++++++++++++++-- .../file_system/file_system_folder.py | 4 +- .../file_system/file_system_item_abc.py | 8 +- .../_file_system/test_file_system.py | 4 +- .../_file_system/test_file_system_file.py | 4 +- .../_file_system/test_file_system_folder.py | 8 +- 7 files changed, 143 insertions(+), 32 deletions(-) diff --git a/src/primaite/simulator/file_system/file_system_file.py b/src/primaite/simulator/file_system/file_system_file.py index 441a27b0..efb1ae93 100644 --- a/src/primaite/simulator/file_system/file_system_file.py +++ b/src/primaite/simulator/file_system/file_system_file.py @@ -1,11 +1,11 @@ from random import choice, randint from typing import Dict -from primaite.simulator.file_system.file_system_file_type import FileSystemFileType -from primaite.simulator.file_system.file_system_item_abc import FileSystemItemABC +from primaite.simulator.file_system.file_system_file_type import file_type_sizes_KB, FileSystemFileType +from primaite.simulator.file_system.file_system_item_abc import FileSystemItem -class FileSystemFile(FileSystemItemABC): +class FileSystemFile(FileSystemItem): """Class that represents a file in the simulation.""" file_type: FileSystemFileType = None @@ -24,23 +24,19 @@ class FileSystemFile(FileSystemItemABC): :param item_size: The size of the FileSystemItem :type item_size: Optional[float] """ - super().__init__(**kwargs) - # set random file type if none provided if kwargs.get("item_name") is None: raise Exception("File name not provided.") - # set random file size if non provided - if kwargs.get("item_size") is None: - kwargs["item_size"] = float(randint(1, 1000)) - # set random file type if none provided if kwargs.get("file_type") is None: kwargs["file_type"] = choice(list(FileSystemFileType)) - self.item_name = kwargs.get("item_name") - self.item_size = kwargs.get("item_size") - self.file_type = kwargs.get("file_type") + # set random file size if none provided + if kwargs.get("item_size") is None: + kwargs["item_size"] = float(randint(1, file_type_sizes_KB[kwargs["file_type"]])) + + super().__init__(**kwargs) def get_file_name(self) -> str: """Returns the name of the file.""" diff --git a/src/primaite/simulator/file_system/file_system_file_type.py b/src/primaite/simulator/file_system/file_system_file_type.py index fd11f30f..7e2d8706 100644 --- a/src/primaite/simulator/file_system/file_system_file_type.py +++ b/src/primaite/simulator/file_system/file_system_file_type.py @@ -2,12 +2,123 @@ from enum import Enum class FileSystemFileType(str, Enum): - """Enum used to determine the FileSystemFile type.""" + """An enumeration of common file types.""" - CSV = ("CSV",) - DOC = ("DOC",) - EXE = ("EXE",) - PDF = ("PDF",) - TXT = ("TXT",) - XML = ("XML",) - ZIP = "ZIP" + UNKNOWN = 0 + "Unknown file type." + + # Text formats + TXT = 1 + "Plain text file." + DOC = 2 + "Microsoft Word document (.doc)" + DOCX = 3 + "Microsoft Word document (.docx)" + PDF = 4 + "Portable Document Format." + HTML = 5 + "HyperText Markup Language file." + XML = 6 + "Extensible Markup Language file." + CSV = 7 + "Comma-Separated Values file." + + # Spreadsheet formats + XLS = 8 + "Microsoft Excel file (.xls)" + XLSX = 9 + "Microsoft Excel file (.xlsx)" + + # Image formats + JPEG = 10 + "JPEG image file." + PNG = 11 + "PNG image file." + GIF = 12 + "GIF image file." + BMP = 13 + "Bitmap image file." + + # Audio formats + MP3 = 14 + "MP3 audio file." + WAV = 15 + "WAV audio file." + + # Video formats + MP4 = 16 + "MP4 video file." + AVI = 17 + "AVI video file." + MKV = 18 + "MKV video file." + FLV = 19 + "FLV video file." + + # Presentation formats + PPT = 20 + "Microsoft PowerPoint file (.ppt)" + PPTX = 21 + "Microsoft PowerPoint file (.pptx)" + + # Web formats + JS = 22 + "JavaScript file." + CSS = 23 + "Cascading Style Sheets file." + + # Programming languages + PY = 24 + "Python script file." + C = 25 + "C source code file." + CPP = 26 + "C++ source code file." + JAVA = 27 + "Java source code file." + + # Compressed file types + RAR = 28 + "RAR archive file." + ZIP = 29 + "ZIP archive file." + TAR = 30 + "TAR archive file." + GZ = 31 + "Gzip compressed file." + + +file_type_sizes_KB = { + FileSystemFileType.UNKNOWN: 0, + FileSystemFileType.TXT: 4, + FileSystemFileType.DOC: 50, + FileSystemFileType.DOCX: 30, + FileSystemFileType.PDF: 100, + FileSystemFileType.HTML: 15, + FileSystemFileType.XML: 10, + FileSystemFileType.CSV: 15, + FileSystemFileType.XLS: 100, + FileSystemFileType.XLSX: 25, + FileSystemFileType.JPEG: 100, + FileSystemFileType.PNG: 40, + FileSystemFileType.GIF: 30, + FileSystemFileType.BMP: 300, + FileSystemFileType.MP3: 5000, + FileSystemFileType.WAV: 25000, + FileSystemFileType.MP4: 25000, + FileSystemFileType.AVI: 50000, + FileSystemFileType.MKV: 50000, + FileSystemFileType.FLV: 15000, + FileSystemFileType.PPT: 200, + FileSystemFileType.PPTX: 100, + FileSystemFileType.JS: 10, + FileSystemFileType.CSS: 5, + FileSystemFileType.PY: 5, + FileSystemFileType.C: 5, + FileSystemFileType.CPP: 10, + FileSystemFileType.JAVA: 10, + FileSystemFileType.RAR: 1000, + FileSystemFileType.ZIP: 1000, + FileSystemFileType.TAR: 1000, + FileSystemFileType.GZ: 800, +} diff --git a/src/primaite/simulator/file_system/file_system_folder.py b/src/primaite/simulator/file_system/file_system_folder.py index f5024966..a381e57d 100644 --- a/src/primaite/simulator/file_system/file_system_folder.py +++ b/src/primaite/simulator/file_system/file_system_folder.py @@ -1,10 +1,10 @@ from typing import Dict, List, Optional from primaite.simulator.file_system.file_system_file import FileSystemFile -from primaite.simulator.file_system.file_system_item_abc import FileSystemItemABC +from primaite.simulator.file_system.file_system_item_abc import FileSystemItem -class FileSystemFolder(FileSystemItemABC): +class FileSystemFolder(FileSystemItem): """Simulation FileSystemFolder.""" files: List[FileSystemFile] = [] diff --git a/src/primaite/simulator/file_system/file_system_item_abc.py b/src/primaite/simulator/file_system/file_system_item_abc.py index 4dca0f4e..a1258665 100644 --- a/src/primaite/simulator/file_system/file_system_item_abc.py +++ b/src/primaite/simulator/file_system/file_system_item_abc.py @@ -1,9 +1,9 @@ -from abc import ABC +from typing import Dict from primaite.simulator.core import SimComponent -class FileSystemItemABC(SimComponent, ABC): +class FileSystemItem(SimComponent): """Abstract base class for FileSystemItems used in the file system simulation.""" item_size: float = 0 @@ -11,3 +11,7 @@ class FileSystemItemABC(SimComponent, ABC): item_name: str """The name of the file system item.""" + + def describe_state(self) -> Dict: + """Returns the state of the FileSystemItem.""" + pass diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py index dae8b34e..f4c1ccda 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py @@ -10,7 +10,7 @@ def test_create_folder_and_file(): file_system.create_file(file_name="test_file", file_size=10, folder_uuid=folder.uuid) assert len(file_system.get_folders()[0].get_files()) is 1 assert file_system.get_folders()[0].get_files()[0].get_file_name() is "test_file" - assert file_system.get_folders()[0].get_files()[0].get_file_size() is 10 + assert file_system.get_folders()[0].get_files()[0].get_file_size() == 10 def test_create_file(): @@ -92,6 +92,6 @@ def test_serialisation(): assert len(file_system.get_folders()[0].get_files()) is 1 serialised_file_sys = file_system.model_dump_json() - deserialised_file_sys = FileSystem().model_validate_json(serialised_file_sys) + deserialised_file_sys = FileSystem.model_validate_json(serialised_file_sys) assert file_system == deserialised_file_sys diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_file.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_file.py index 669be62d..ed4a4ad5 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_file.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_file.py @@ -11,13 +11,13 @@ def test_file_type(): def test_get_file_size(): """Tests that the file size is being returned properly.""" file = FileSystemFile(item_name="test", item_size=1.5) - assert file.get_file_size() is 1.5 + assert file.get_file_size() == 1.5 def test_serialisation(): """Test to check that the object serialisation works correctly.""" file = FileSystemFile(item_name="test", item_size=1.5, file_type=FileSystemFileType.DOC) serialised_file = file.model_dump_json() - deserialised_file = FileSystemFile(item_name="test").model_validate_json(serialised_file) + deserialised_file = FileSystemFile.model_validate_json(serialised_file) assert file == deserialised_file diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_folder.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_folder.py index f9ba80f0..871b4e94 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_folder.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_folder.py @@ -10,11 +10,11 @@ def test_adding_removing_file(): file = FileSystemFile(item_name="test_file", item_size=10, file_type=FileSystemFileType.DOC) folder.add_file(file) - assert folder.get_folder_size() is 10 + assert folder.get_folder_size() == 10 assert len(folder.get_files()) is 1 folder.remove_file(file) - assert folder.get_folder_size() is 0 + assert folder.get_folder_size() == 0 assert len(folder.get_files()) is 0 @@ -27,7 +27,7 @@ def test_get_file_by_id(): folder.add_file(file) folder.add_file(file2) - assert folder.get_folder_size() is 20 + assert folder.get_folder_size() == 20 assert len(folder.get_files()) is 2 assert folder.get_file(file_id=file.uuid) is file @@ -54,6 +54,6 @@ def test_serialisation(): serialised_folder = folder.model_dump_json() - deserialised_folder = FileSystemFolder(item_name="test").model_validate_json(serialised_folder) + deserialised_folder = FileSystemFolder.model_validate_json(serialised_folder) assert folder == deserialised_folder From 554619e4b40a6789b3446ceda9fb94192f260941 Mon Sep 17 00:00:00 2001 From: Czar Echavez Date: Mon, 7 Aug 2023 14:49:59 +0100 Subject: [PATCH 08/14] #1714: conver file and folder lists to dicts + fixing and adding a few more tests --- .../simulator/file_system/file_system.py | 64 +++++++++++++------ .../file_system/file_system_folder.py | 30 ++++++--- .../_file_system/test_file_system.py | 50 ++++++++++++--- .../_file_system/test_file_system_file.py | 2 +- .../_file_system/test_file_system_folder.py | 18 +++++- .../_primaite/_simulator/test_core.py | 4 +- 6 files changed, 128 insertions(+), 40 deletions(-) diff --git a/src/primaite/simulator/file_system/file_system.py b/src/primaite/simulator/file_system/file_system.py index f8ae9d67..ce6eefb2 100644 --- a/src/primaite/simulator/file_system/file_system.py +++ b/src/primaite/simulator/file_system/file_system.py @@ -1,16 +1,19 @@ from random import choice -from typing import Dict, List, Optional +from typing import Dict, Optional +from primaite import getLogger from primaite.simulator.core import SimComponent from primaite.simulator.file_system.file_system_file import FileSystemFile from primaite.simulator.file_system.file_system_file_type import FileSystemFileType from primaite.simulator.file_system.file_system_folder import FileSystemFolder +_LOGGER = getLogger(__name__) + class FileSystem(SimComponent): """Class that contains all the simulation File System.""" - folders: List[FileSystemFolder] = [] + folders: Dict = {} """List containing all the folders in the file system.""" def describe_state(self) -> Dict: @@ -21,7 +24,7 @@ class FileSystem(SimComponent): """ pass - def get_folders(self) -> List[FileSystemFolder]: + def get_folders(self) -> Dict: """Returns the list of folders.""" return self.folders @@ -48,6 +51,9 @@ class FileSystem(SimComponent): :param: file_type: The type of the file :type: Optional[FileSystemFileType] + :param: folder: The folder to add the file to + :type: folder: Optional[FileSystemFolder] + :param: folder_uuid: The uuid of the folder to add the file to :type: folder_uuid: Optional[str] """ @@ -75,16 +81,21 @@ class FileSystem(SimComponent): # add file to root folder file = FileSystemFile(item_name=file_name, item_size=file_size, file_type=file_type) folder.add_file(file) - self.folders.append(folder) + self.folders[folder.uuid] = folder return file def create_folder( self, folder_name: str, ) -> FileSystemFolder: - """Creates a FileSystemFolder and adds it to the list of folders.""" + """ + Creates a FileSystemFolder and adds it to the list of folders. + + :param: folder_name: The name of the folder + :type: folder_name: str + """ folder = FileSystemFolder(item_name=folder_name) - self.folders.append(folder) + self.folders[folder.uuid] = folder return folder def delete_file(self, file: Optional[FileSystemFile] = None): @@ -93,13 +104,10 @@ class FileSystem(SimComponent): :param file: The file to delete :type file: Optional[FileSystemFile] - - :param file_id: The UUID of the file item to delete - :type file_id: Optional[str] """ # iterate through folders to delete the item with the matching uuid - for folder in self.folders: - folder.remove_file(file=file) + for key in self.folders: + self.get_folder_by_id(key).remove_file(file) def delete_folder(self, folder: FileSystemFolder): """ @@ -108,7 +116,13 @@ class FileSystem(SimComponent): :param folder: The folder to remove :type folder: FileSystemFolder """ - self.folders.remove(folder) + if folder is None or not isinstance(folder, FileSystemFolder): + raise Exception(f"Invalid folder: {folder}") + + if self.folders.get(folder.uuid): + del self.folders[folder.uuid] + else: + _LOGGER.debug(f"File with UUID {folder.uuid} was not found.") def move_file(self, file: FileSystemFile, src_folder: FileSystemFolder, target_folder: FileSystemFolder): """ @@ -170,8 +184,8 @@ class FileSystem(SimComponent): def get_file_by_id(self, file_id: str) -> FileSystemFile: """Checks if the file exists in any file system folders.""" - for folder in self.folders: - file = folder.get_file(file_id=file_id) + for key in self.folders: + file = self.folders[key].get_file(file_id=file_id) if file is not None: return file @@ -181,12 +195,26 @@ class FileSystem(SimComponent): :return: Returns the first FileSydtemFolder with a matching name """ - return next((f for f in self.folders if f.get_folder_name() == folder_name), None) + matching_folder = None + for key in self.folders: + if self.folders[key].get_folder_name() == folder_name: + matching_folder = self.folders[key] + break + return matching_folder def get_folder_by_id(self, folder_id: str) -> FileSystemFolder: - """Checks if the folder exists.""" - return next((f for f in self.folders if f.uuid == folder_id), None) + """ + Checks if the folder exists. + + :param: folder_id: The id of the folder to find + :type: folder_id: str + """ + return self.folders[folder_id] def get_random_file_type(self) -> FileSystemFileType: - """Returns a random FileSystemFileTypeEnum.""" + """ + Returns a random FileSystemFileTypeEnum. + + :return: A random file type Enum + """ return choice(list(FileSystemFileType)) diff --git a/src/primaite/simulator/file_system/file_system_folder.py b/src/primaite/simulator/file_system/file_system_folder.py index a381e57d..d6ac3ef1 100644 --- a/src/primaite/simulator/file_system/file_system_folder.py +++ b/src/primaite/simulator/file_system/file_system_folder.py @@ -1,13 +1,16 @@ -from typing import Dict, List, Optional +from typing import Dict, Optional +from primaite import getLogger from primaite.simulator.file_system.file_system_file import FileSystemFile from primaite.simulator.file_system.file_system_item_abc import FileSystemItem +_LOGGER = getLogger(__name__) + class FileSystemFolder(FileSystemItem): """Simulation FileSystemFolder.""" - files: List[FileSystemFile] = [] + files: Dict = {} """List of files stored in the folder.""" is_quarantined: bool = False @@ -21,20 +24,23 @@ class FileSystemFolder(FileSystemItem): """Returns the item_size of the folder.""" return self.item_size - def get_files(self) -> List[FileSystemFile]: - """Returns the list of files the folder contains.""" + def get_files(self) -> Dict: + """Returns the files dictionary.""" return self.files def get_file(self, file_id: str) -> FileSystemFile: """Return a FileSystemFile with the matching id.""" - return next((f for f in self.files if f.uuid == file_id), None) + return self.files[file_id] def add_file(self, file: FileSystemFile): """Adds a file to the folder list.""" + if file is None or not isinstance(file, FileSystemFile): + raise Exception(f"Invalid file: {file}") + self.item_size += file.get_file_size() # add to list - self.files.append(file) + self.files[file.uuid] = file def remove_file(self, file: Optional[FileSystemFile]): """ @@ -45,10 +51,16 @@ class FileSystemFolder(FileSystemItem): :param: file: The file to remove :type: Optional[FileSystemFile] """ - self.files.remove(file) + if file is None or not isinstance(file, FileSystemFile): + raise Exception(f"Invalid file: {file}") - # remove folder size from folder - self.item_size -= file.get_file_size() + if self.files.get(file.uuid): + del self.files[file.uuid] + + # remove folder size from folder + self.item_size -= file.get_file_size() + else: + _LOGGER.debug(f"File with UUID {file.uuid} was not found.") def quarantine(self): """Quarantines the File System Folder.""" diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py index f4c1ccda..e0a6a2d9 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py @@ -1,4 +1,5 @@ from primaite.simulator.file_system.file_system import FileSystem +from primaite.simulator.file_system.file_system_folder import FileSystemFolder def test_create_folder_and_file(): @@ -7,10 +8,11 @@ def test_create_folder_and_file(): folder = file_system.create_folder(folder_name="test_folder") assert len(file_system.get_folders()) is 1 - file_system.create_file(file_name="test_file", file_size=10, folder_uuid=folder.uuid) - assert len(file_system.get_folders()[0].get_files()) is 1 - assert file_system.get_folders()[0].get_files()[0].get_file_name() is "test_file" - assert file_system.get_folders()[0].get_files()[0].get_file_size() == 10 + file = file_system.create_file(file_name="test_file", file_size=10, folder_uuid=folder.uuid) + assert len(file_system.get_folder_by_id(folder.uuid).get_files()) is 1 + + assert file_system.get_file_by_id(file.uuid).get_file_name() is "test_file" + assert file_system.get_file_by_id(file.uuid).get_file_size() == 10 def test_create_file(): @@ -19,7 +21,7 @@ def test_create_file(): file = file_system.create_file(file_name="test_file", file_size=10) assert len(file_system.get_folders()) is 1 - assert file_system.get_folders()[0].get_file(file.uuid) is file + assert file_system.get_folder_by_name("root").get_file(file.uuid) is file def test_delete_file(): @@ -28,11 +30,31 @@ def test_delete_file(): file = file_system.create_file(file_name="test_file", file_size=10) assert len(file_system.get_folders()) is 1 - assert file_system.get_folders()[0].get_file(file.uuid) is file + + folder_id = list(file_system.get_folders().keys())[0] + folder = file_system.get_folder_by_id(folder_id) + assert folder.get_file(file.uuid) is file file_system.delete_file(file=file) assert len(file_system.get_folders()) is 1 - assert len(file_system.get_folders()[0].get_files()) is 0 + assert len(file_system.get_folder_by_id(folder.uuid).get_files()) is 0 + + +def test_delete_non_existent_file(): + """Tests deleting a non existent file.""" + file_system = FileSystem() + + file = file_system.create_file(file_name="test_file", file_size=10) + not_added_file = file_system.create_file(file_name="test_file", file_size=10) + assert len(file_system.get_folders()) is 1 + + folder_id = list(file_system.get_folders().keys())[0] + folder = file_system.get_folder_by_id(folder_id) + assert folder.get_file(file.uuid) is file + + file_system.delete_file(file=not_added_file) + assert len(file_system.get_folders()) is 1 + assert len(file_system.get_folder_by_id(folder.uuid).get_files()) is 1 def test_delete_folder(): @@ -44,6 +66,16 @@ def test_delete_folder(): assert len(file_system.get_folders()) is 0 +def test_deleting_a_non_existent_folder(): + file_system = FileSystem() + folder = file_system.create_folder(folder_name="test_folder") + not_added_folder = FileSystemFolder(item_name="fake_folder") + assert len(file_system.get_folders()) is 1 + + file_system.delete_folder(not_added_folder) + assert len(file_system.get_folders()) is 1 + + def test_move_file(): """Tests the file move function.""" file_system = FileSystem() @@ -89,9 +121,9 @@ def test_serialisation(): assert len(file_system.get_folders()) is 1 file_system.create_file(file_name="test_file", file_size=10, folder_uuid=folder.uuid) - assert len(file_system.get_folders()[0].get_files()) is 1 + assert file_system.get_folder_by_id(folder.uuid) is folder serialised_file_sys = file_system.model_dump_json() deserialised_file_sys = FileSystem.model_validate_json(serialised_file_sys) - assert file_system == deserialised_file_sys + assert file_system.model_dump_json() == deserialised_file_sys.model_dump_json() diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_file.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_file.py index ed4a4ad5..51f4ce1b 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_file.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_file.py @@ -20,4 +20,4 @@ def test_serialisation(): serialised_file = file.model_dump_json() deserialised_file = FileSystemFile.model_validate_json(serialised_file) - assert file == deserialised_file + assert file.model_dump_json() == deserialised_file.model_dump_json() diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_folder.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_folder.py index 871b4e94..c56d2917 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_folder.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_folder.py @@ -18,6 +18,22 @@ def test_adding_removing_file(): assert len(folder.get_files()) is 0 +def test_remove_non_existent_file(): + """Test the removing of a file that does not exist.""" + folder = FileSystemFolder(item_name="test") + + file = FileSystemFile(item_name="test_file", item_size=10, file_type=FileSystemFileType.DOC) + not_added_file = FileSystemFile(item_name="fake_file", item_size=10, file_type=FileSystemFileType.DOC) + + folder.add_file(file) + assert folder.get_folder_size() == 10 + assert len(folder.get_files()) is 1 + + folder.remove_file(not_added_file) + assert folder.get_folder_size() == 10 + assert len(folder.get_files()) is 1 + + def test_get_file_by_id(): """Test to make sure that the correct file is returned.""" folder = FileSystemFolder(item_name="test") @@ -56,4 +72,4 @@ def test_serialisation(): deserialised_folder = FileSystemFolder.model_validate_json(serialised_folder) - assert folder == deserialised_folder + assert folder.model_dump_json() == deserialised_folder.model_dump_json() diff --git a/tests/unit_tests/_primaite/_simulator/test_core.py b/tests/unit_tests/_primaite/_simulator/test_core.py index 00f29791..4e2df757 100644 --- a/tests/unit_tests/_primaite/_simulator/test_core.py +++ b/tests/unit_tests/_primaite/_simulator/test_core.py @@ -42,8 +42,8 @@ class TestIsolatedSimComponent: return {} comp = TestComponent(name="computer", size=(5, 10)) - dump = comp.model_dump() - assert dump["name"] is "computer" + dump = comp.model_dump_json() + assert comp == TestComponent.model_validate_json(dump) def test_apply_action(self): """Validate that we can override apply_action behaviour and it updates the state of the component.""" From a4c193cd34f8593e52e36d1a6a7c7f0ec1738092 Mon Sep 17 00:00:00 2001 From: Czar Echavez Date: Mon, 7 Aug 2023 16:20:55 +0100 Subject: [PATCH 09/14] #1714: apply recommended changes with removing get methods and using the properties directly --- src/primaite/simulator/core.py | 2 +- .../simulator/file_system/file_system.py | 20 ++--- .../simulator/file_system/file_system_file.py | 26 ++---- .../file_system/file_system_folder.py | 22 +---- .../file_system/file_system_item_abc.py | 8 +- .../_file_system/test_file_system.py | 84 +++++++++---------- .../_file_system/test_file_system_file.py | 12 +-- .../_file_system/test_file_system_folder.py | 44 +++++----- 8 files changed, 96 insertions(+), 122 deletions(-) diff --git a/src/primaite/simulator/core.py b/src/primaite/simulator/core.py index 84b03498..5f8ad57c 100644 --- a/src/primaite/simulator/core.py +++ b/src/primaite/simulator/core.py @@ -10,7 +10,7 @@ class SimComponent(BaseModel): """Extension of pydantic BaseModel with additional methods that must be defined by all classes in the simulator.""" uuid: str - "The component UUID." + """The component UUID.""" def __init__(self, **kwargs): if not kwargs.get("uuid"): diff --git a/src/primaite/simulator/file_system/file_system.py b/src/primaite/simulator/file_system/file_system.py index ce6eefb2..3290570e 100644 --- a/src/primaite/simulator/file_system/file_system.py +++ b/src/primaite/simulator/file_system/file_system.py @@ -31,7 +31,7 @@ class FileSystem(SimComponent): def create_file( self, file_name: str, - file_size: Optional[float] = None, + size: Optional[float] = None, file_type: Optional[FileSystemFileType] = None, folder: Optional[FileSystemFolder] = None, folder_uuid: Optional[str] = None, @@ -39,14 +39,14 @@ class FileSystem(SimComponent): """ Creates a FileSystemFile and adds it to the list of files. - If no file_size or file_type are provided, one will be chosen randomly. + If no size or file_type are provided, one will be chosen randomly. If no folder_uuid or folder is provided, a new folder will be created. :param: file_name: The file name :type: file_name: str - :param: file_size: The size the file takes on disk. - :type: file_size: Optional[float] + :param: size: The size the file takes on disk. + :type: size: Optional[float] :param: file_type: The type of the file :type: Optional[FileSystemFileType] @@ -69,17 +69,17 @@ class FileSystem(SimComponent): folder = self.get_folder_by_id(folder_uuid) if folder is not None: - file = FileSystemFile(item_name=file_name, item_size=file_size, file_type=file_type) + file = FileSystemFile(name=file_name, size=size, file_type=file_type) folder.add_file(file=file) else: # check if a "root" folder exists folder = self.get_folder_by_name("root") if folder is None: # create a root folder - folder = FileSystemFolder(item_name="root") + folder = FileSystemFolder(name="root") # add file to root folder - file = FileSystemFile(item_name=file_name, item_size=file_size, file_type=file_type) + file = FileSystemFile(name=file_name, size=size, file_type=file_type) folder.add_file(file) self.folders[folder.uuid] = folder return file @@ -94,7 +94,7 @@ class FileSystem(SimComponent): :param: folder_name: The name of the folder :type: folder_name: str """ - folder = FileSystemFolder(item_name=folder_name) + folder = FileSystemFolder(name=folder_name) self.folders[folder.uuid] = folder return folder @@ -185,7 +185,7 @@ class FileSystem(SimComponent): def get_file_by_id(self, file_id: str) -> FileSystemFile: """Checks if the file exists in any file system folders.""" for key in self.folders: - file = self.folders[key].get_file(file_id=file_id) + file = self.folders[key].get_file_by_id(file_id=file_id) if file is not None: return file @@ -197,7 +197,7 @@ class FileSystem(SimComponent): """ matching_folder = None for key in self.folders: - if self.folders[key].get_folder_name() == folder_name: + if self.folders[key].name == folder_name: matching_folder = self.folders[key] break return matching_folder diff --git a/src/primaite/simulator/file_system/file_system_file.py b/src/primaite/simulator/file_system/file_system_file.py index efb1ae93..2de2084b 100644 --- a/src/primaite/simulator/file_system/file_system_file.py +++ b/src/primaite/simulator/file_system/file_system_file.py @@ -15,17 +15,17 @@ class FileSystemFile(FileSystemItem): """ Initialise FileSystemFile class. - :param item_name: The name of the file. - :type item_name: str + :param name: The name of the file. + :type name: str :param file_type: The FileSystemFileType of the file :type file_type: Optional[FileSystemFileType] - :param item_size: The size of the FileSystemItem - :type item_size: Optional[float] + :param size: The size of the FileSystemItem + :type size: Optional[float] """ # set random file type if none provided - if kwargs.get("item_name") is None: + if kwargs.get("name") is None: raise Exception("File name not provided.") # set random file type if none provided @@ -33,23 +33,11 @@ class FileSystemFile(FileSystemItem): kwargs["file_type"] = choice(list(FileSystemFileType)) # set random file size if none provided - if kwargs.get("item_size") is None: - kwargs["item_size"] = float(randint(1, file_type_sizes_KB[kwargs["file_type"]])) + if kwargs.get("size") is None: + kwargs["size"] = float(randint(1, file_type_sizes_KB[kwargs["file_type"]])) super().__init__(**kwargs) - def get_file_name(self) -> str: - """Returns the name of the file.""" - return self.item_name - - def get_file_size(self) -> float: - """Returns the size of the file system item.""" - return self.item_size - - def get_file_type(self) -> FileSystemFileType: - """Returns the FileSystemFileType of the file.""" - return self.file_type - def describe_state(self) -> Dict: """ Get the current state of the FileSystemFile as a dict. diff --git a/src/primaite/simulator/file_system/file_system_folder.py b/src/primaite/simulator/file_system/file_system_folder.py index d6ac3ef1..79e19189 100644 --- a/src/primaite/simulator/file_system/file_system_folder.py +++ b/src/primaite/simulator/file_system/file_system_folder.py @@ -16,31 +16,18 @@ class FileSystemFolder(FileSystemItem): is_quarantined: bool = False """Flag that marks the folder as quarantined if true.""" - def get_folder_name(self) -> str: - """Returns the item_name of the folder.""" - return self.item_name - - def get_folder_size(self) -> float: - """Returns the item_size of the folder.""" - return self.item_size - - def get_files(self) -> Dict: - """Returns the files dictionary.""" - return self.files - - def get_file(self, file_id: str) -> FileSystemFile: + def get_file_by_id(self, file_id: str) -> FileSystemFile: """Return a FileSystemFile with the matching id.""" - return self.files[file_id] + return self.files.get(file_id) def add_file(self, file: FileSystemFile): """Adds a file to the folder list.""" if file is None or not isinstance(file, FileSystemFile): raise Exception(f"Invalid file: {file}") - self.item_size += file.get_file_size() - # add to list self.files[file.uuid] = file + self.size += file.size def remove_file(self, file: Optional[FileSystemFile]): """ @@ -57,8 +44,7 @@ class FileSystemFolder(FileSystemItem): if self.files.get(file.uuid): del self.files[file.uuid] - # remove folder size from folder - self.item_size -= file.get_file_size() + self.size -= file.size else: _LOGGER.debug(f"File with UUID {file.uuid} was not found.") diff --git a/src/primaite/simulator/file_system/file_system_item_abc.py b/src/primaite/simulator/file_system/file_system_item_abc.py index a1258665..0594cc35 100644 --- a/src/primaite/simulator/file_system/file_system_item_abc.py +++ b/src/primaite/simulator/file_system/file_system_item_abc.py @@ -6,11 +6,11 @@ from primaite.simulator.core import SimComponent class FileSystemItem(SimComponent): """Abstract base class for FileSystemItems used in the file system simulation.""" - item_size: float = 0 - """The size the item takes up on disk.""" + name: str + """The name of the FileSystemItem.""" - item_name: str - """The name of the file system item.""" + size: float = 0 + """The size the item takes up on disk.""" def describe_state(self) -> Dict: """Returns the state of the FileSystemItem.""" diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py index e0a6a2d9..5bebf487 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py @@ -6,121 +6,121 @@ def test_create_folder_and_file(): """Test creating a folder and a file.""" file_system = FileSystem() folder = file_system.create_folder(folder_name="test_folder") - assert len(file_system.get_folders()) is 1 + assert len(file_system.folders) is 1 - file = file_system.create_file(file_name="test_file", file_size=10, folder_uuid=folder.uuid) - assert len(file_system.get_folder_by_id(folder.uuid).get_files()) is 1 + file = file_system.create_file(file_name="test_file", size=10, folder_uuid=folder.uuid) + assert len(file_system.get_folder_by_id(folder.uuid).files) is 1 - assert file_system.get_file_by_id(file.uuid).get_file_name() is "test_file" - assert file_system.get_file_by_id(file.uuid).get_file_size() == 10 + assert file_system.get_file_by_id(file.uuid).name is "test_file" + assert file_system.get_file_by_id(file.uuid).size == 10 def test_create_file(): """Tests that creating a file without a folder creates a folder and sets that as the file's parent.""" file_system = FileSystem() - file = file_system.create_file(file_name="test_file", file_size=10) - assert len(file_system.get_folders()) is 1 - assert file_system.get_folder_by_name("root").get_file(file.uuid) is file + file = file_system.create_file(file_name="test_file", size=10) + assert len(file_system.folders) is 1 + assert file_system.get_folder_by_name("root").get_file_by_id(file.uuid) is file def test_delete_file(): """Tests that a file can be deleted.""" file_system = FileSystem() - file = file_system.create_file(file_name="test_file", file_size=10) - assert len(file_system.get_folders()) is 1 + file = file_system.create_file(file_name="test_file", size=10) + assert len(file_system.folders) is 1 - folder_id = list(file_system.get_folders().keys())[0] + folder_id = list(file_system.folders.keys())[0] folder = file_system.get_folder_by_id(folder_id) - assert folder.get_file(file.uuid) is file + assert folder.get_file_by_id(file.uuid) is file file_system.delete_file(file=file) - assert len(file_system.get_folders()) is 1 - assert len(file_system.get_folder_by_id(folder.uuid).get_files()) is 0 + assert len(file_system.folders) is 1 + assert len(file_system.get_folder_by_id(folder.uuid).files) is 0 def test_delete_non_existent_file(): """Tests deleting a non existent file.""" file_system = FileSystem() - file = file_system.create_file(file_name="test_file", file_size=10) - not_added_file = file_system.create_file(file_name="test_file", file_size=10) - assert len(file_system.get_folders()) is 1 + file = file_system.create_file(file_name="test_file", size=10) + not_added_file = file_system.create_file(file_name="test_file", size=10) + assert len(file_system.folders) is 1 - folder_id = list(file_system.get_folders().keys())[0] + folder_id = list(file_system.folders.keys())[0] folder = file_system.get_folder_by_id(folder_id) - assert folder.get_file(file.uuid) is file + assert folder.get_file_by_id(file.uuid) is file file_system.delete_file(file=not_added_file) - assert len(file_system.get_folders()) is 1 - assert len(file_system.get_folder_by_id(folder.uuid).get_files()) is 1 + assert len(file_system.folders) is 1 + assert len(file_system.get_folder_by_id(folder.uuid).files) is 1 def test_delete_folder(): file_system = FileSystem() folder = file_system.create_folder(folder_name="test_folder") - assert len(file_system.get_folders()) is 1 + assert len(file_system.folders) is 1 file_system.delete_folder(folder) - assert len(file_system.get_folders()) is 0 + assert len(file_system.folders) is 0 def test_deleting_a_non_existent_folder(): file_system = FileSystem() folder = file_system.create_folder(folder_name="test_folder") - not_added_folder = FileSystemFolder(item_name="fake_folder") - assert len(file_system.get_folders()) is 1 + not_added_folder = FileSystemFolder(name="fake_folder") + assert len(file_system.folders) is 1 file_system.delete_folder(not_added_folder) - assert len(file_system.get_folders()) is 1 + assert len(file_system.folders) is 1 def test_move_file(): """Tests the file move function.""" file_system = FileSystem() src_folder = file_system.create_folder(folder_name="test_folder_1") - assert len(file_system.get_folders()) is 1 + assert len(file_system.folders) is 1 target_folder = file_system.create_folder(folder_name="test_folder_2") - assert len(file_system.get_folders()) is 2 + assert len(file_system.folders) is 2 - file = file_system.create_file(file_name="test_file", file_size=10, folder_uuid=src_folder.uuid) - assert len(file_system.get_folder_by_id(src_folder.uuid).get_files()) is 1 - assert len(file_system.get_folder_by_id(target_folder.uuid).get_files()) is 0 + file = file_system.create_file(file_name="test_file", size=10, folder_uuid=src_folder.uuid) + assert len(file_system.get_folder_by_id(src_folder.uuid).files) is 1 + assert len(file_system.get_folder_by_id(target_folder.uuid).files) is 0 file_system.move_file(file=file, src_folder=src_folder, target_folder=target_folder) - assert len(file_system.get_folder_by_id(src_folder.uuid).get_files()) is 0 - assert len(file_system.get_folder_by_id(target_folder.uuid).get_files()) is 1 + assert len(file_system.get_folder_by_id(src_folder.uuid).files) is 0 + assert len(file_system.get_folder_by_id(target_folder.uuid).files) is 1 def test_copy_file(): """Tests the file copy function.""" file_system = FileSystem() src_folder = file_system.create_folder(folder_name="test_folder_1") - assert len(file_system.get_folders()) is 1 + assert len(file_system.folders) is 1 target_folder = file_system.create_folder(folder_name="test_folder_2") - assert len(file_system.get_folders()) is 2 + assert len(file_system.folders) is 2 - file = file_system.create_file(file_name="test_file", file_size=10, folder_uuid=src_folder.uuid) - assert len(file_system.get_folder_by_id(src_folder.uuid).get_files()) is 1 - assert len(file_system.get_folder_by_id(target_folder.uuid).get_files()) is 0 + file = file_system.create_file(file_name="test_file", size=10, folder_uuid=src_folder.uuid) + assert len(file_system.get_folder_by_id(src_folder.uuid).files) is 1 + assert len(file_system.get_folder_by_id(target_folder.uuid).files) is 0 file_system.copy_file(file=file, src_folder=src_folder, target_folder=target_folder) - assert len(file_system.get_folder_by_id(src_folder.uuid).get_files()) is 1 - assert len(file_system.get_folder_by_id(target_folder.uuid).get_files()) is 1 + assert len(file_system.get_folder_by_id(src_folder.uuid).files) is 1 + assert len(file_system.get_folder_by_id(target_folder.uuid).files) is 1 def test_serialisation(): """Test to check that the object serialisation works correctly.""" file_system = FileSystem() folder = file_system.create_folder(folder_name="test_folder") - assert len(file_system.get_folders()) is 1 + assert len(file_system.folders) is 1 - file_system.create_file(file_name="test_file", file_size=10, folder_uuid=folder.uuid) + file_system.create_file(file_name="test_file", size=10, folder_uuid=folder.uuid) assert file_system.get_folder_by_id(folder.uuid) is folder serialised_file_sys = file_system.model_dump_json() diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_file.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_file.py index 51f4ce1b..629b9bb9 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_file.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_file.py @@ -4,19 +4,19 @@ from primaite.simulator.file_system.file_system_file_type import FileSystemFileT def test_file_type(): """Tests tha the FileSystemFile type is set correctly.""" - file = FileSystemFile(item_name="test", file_type=FileSystemFileType.DOC) - assert file.get_file_type() is FileSystemFileType.DOC + file = FileSystemFile(name="test", file_type=FileSystemFileType.DOC) + assert file.file_type is FileSystemFileType.DOC -def test_get_file_size(): +def test_get_size(): """Tests that the file size is being returned properly.""" - file = FileSystemFile(item_name="test", item_size=1.5) - assert file.get_file_size() == 1.5 + file = FileSystemFile(name="test", size=1.5) + assert file.size == 1.5 def test_serialisation(): """Test to check that the object serialisation works correctly.""" - file = FileSystemFile(item_name="test", item_size=1.5, file_type=FileSystemFileType.DOC) + file = FileSystemFile(name="test", size=1.5, file_type=FileSystemFileType.DOC) serialised_file = file.model_dump_json() deserialised_file = FileSystemFile.model_validate_json(serialised_file) diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_folder.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_folder.py index c56d2917..1940e886 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_folder.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_folder.py @@ -5,53 +5,53 @@ from primaite.simulator.file_system.file_system_folder import FileSystemFolder def test_adding_removing_file(): """Test the adding and removing of a file from a folder.""" - folder = FileSystemFolder(item_name="test") + folder = FileSystemFolder(name="test") - file = FileSystemFile(item_name="test_file", item_size=10, file_type=FileSystemFileType.DOC) + file = FileSystemFile(name="test_file", size=10, file_type=FileSystemFileType.DOC) folder.add_file(file) - assert folder.get_folder_size() == 10 - assert len(folder.get_files()) is 1 + assert folder.size == 10 + assert len(folder.files) is 1 folder.remove_file(file) - assert folder.get_folder_size() == 0 - assert len(folder.get_files()) is 0 + assert folder.size == 0 + assert len(folder.files) is 0 def test_remove_non_existent_file(): """Test the removing of a file that does not exist.""" - folder = FileSystemFolder(item_name="test") + folder = FileSystemFolder(name="test") - file = FileSystemFile(item_name="test_file", item_size=10, file_type=FileSystemFileType.DOC) - not_added_file = FileSystemFile(item_name="fake_file", item_size=10, file_type=FileSystemFileType.DOC) + file = FileSystemFile(name="test_file", size=10, file_type=FileSystemFileType.DOC) + not_added_file = FileSystemFile(name="fake_file", size=10, file_type=FileSystemFileType.DOC) folder.add_file(file) - assert folder.get_folder_size() == 10 - assert len(folder.get_files()) is 1 + assert folder.size == 10 + assert len(folder.files) is 1 folder.remove_file(not_added_file) - assert folder.get_folder_size() == 10 - assert len(folder.get_files()) is 1 + assert folder.size == 10 + assert len(folder.files) is 1 def test_get_file_by_id(): """Test to make sure that the correct file is returned.""" - folder = FileSystemFolder(item_name="test") + folder = FileSystemFolder(name="test") - file = FileSystemFile(item_name="test_file", item_size=10, file_type=FileSystemFileType.DOC) - file2 = FileSystemFile(item_name="test_file_2", item_size=10, file_type=FileSystemFileType.DOC) + file = FileSystemFile(name="test_file", size=10, file_type=FileSystemFileType.DOC) + file2 = FileSystemFile(name="test_file_2", size=10, file_type=FileSystemFileType.DOC) folder.add_file(file) folder.add_file(file2) - assert folder.get_folder_size() == 20 - assert len(folder.get_files()) is 2 + assert folder.size == 20 + assert len(folder.files) is 2 - assert folder.get_file(file_id=file.uuid) is file + assert folder.get_file_by_id(file_id=file.uuid) is file def test_folder_quarantine_state(): """Tests the changing of folder quarantine status.""" - folder = FileSystemFolder(item_name="test") + folder = FileSystemFolder(name="test") assert folder.quarantine_status() is False @@ -64,8 +64,8 @@ def test_folder_quarantine_state(): def test_serialisation(): """Test to check that the object serialisation works correctly.""" - folder = FileSystemFolder(item_name="test") - file = FileSystemFile(item_name="test_file", item_size=10, file_type=FileSystemFileType.DOC) + folder = FileSystemFolder(name="test") + file = FileSystemFile(name="test_file", size=10, file_type=FileSystemFileType.DOC) folder.add_file(file) serialised_folder = folder.model_dump_json() From 700950b85627506728ebbdb83c5ed7e2bdc85255 Mon Sep 17 00:00:00 2001 From: Czar Echavez Date: Mon, 7 Aug 2023 15:38:15 +0000 Subject: [PATCH 10/14] Apply suggestions from code review --- src/primaite/simulator/file_system/file_system_file.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/primaite/simulator/file_system/file_system_file.py b/src/primaite/simulator/file_system/file_system_file.py index 2de2084b..b3358372 100644 --- a/src/primaite/simulator/file_system/file_system_file.py +++ b/src/primaite/simulator/file_system/file_system_file.py @@ -25,8 +25,7 @@ class FileSystemFile(FileSystemItem): :type size: Optional[float] """ # set random file type if none provided - if kwargs.get("name") is None: - raise Exception("File name not provided.") + # set random file type if none provided if kwargs.get("file_type") is None: From c8ee409b3b26ad8351fa21f802523baf9755ee12 Mon Sep 17 00:00:00 2001 From: Czar Echavez Date: Tue, 8 Aug 2023 08:29:51 +0100 Subject: [PATCH 11/14] #1714: run precommit --- src/primaite/simulator/file_system/file_system_file.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/primaite/simulator/file_system/file_system_file.py b/src/primaite/simulator/file_system/file_system_file.py index b3358372..5f784072 100644 --- a/src/primaite/simulator/file_system/file_system_file.py +++ b/src/primaite/simulator/file_system/file_system_file.py @@ -26,7 +26,6 @@ class FileSystemFile(FileSystemItem): """ # set random file type if none provided - # set random file type if none provided if kwargs.get("file_type") is None: kwargs["file_type"] = choice(list(FileSystemFileType)) From f854404ba0d3588443d6bfba328bb8ecfb82add1 Mon Sep 17 00:00:00 2001 From: Czar Echavez Date: Tue, 8 Aug 2023 08:41:50 +0100 Subject: [PATCH 12/14] #1714: added file system to changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d66257b5..dd7e3466 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- File System - ability to emulate a node's file system during a simulation + ## [2.0.0] - 2023-07-26 ### Added From c2b783c858e159ceef0596d33d77becfb86c4ce4 Mon Sep 17 00:00:00 2001 From: Czar Echavez Date: Tue, 8 Aug 2023 08:17:40 +0000 Subject: [PATCH 13/14] Apply suggestions from code review --- src/primaite/simulator/file_system/file_system_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/primaite/simulator/file_system/file_system_file.py b/src/primaite/simulator/file_system/file_system_file.py index 5f784072..95c824d6 100644 --- a/src/primaite/simulator/file_system/file_system_file.py +++ b/src/primaite/simulator/file_system/file_system_file.py @@ -32,7 +32,7 @@ class FileSystemFile(FileSystemItem): # set random file size if none provided if kwargs.get("size") is None: - kwargs["size"] = float(randint(1, file_type_sizes_KB[kwargs["file_type"]])) + kwargs["size"] = file_type_sizes_KB[kwargs["file_type"]] super().__init__(**kwargs) From 2f27e02877f8fdd921c8fd510b99fc60951896bb Mon Sep 17 00:00:00 2001 From: Czar Echavez Date: Tue, 8 Aug 2023 09:53:32 +0100 Subject: [PATCH 14/14] #1714: fix precommit --- src/primaite/simulator/file_system/file_system_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/primaite/simulator/file_system/file_system_file.py b/src/primaite/simulator/file_system/file_system_file.py index 95c824d6..f9fc2e1f 100644 --- a/src/primaite/simulator/file_system/file_system_file.py +++ b/src/primaite/simulator/file_system/file_system_file.py @@ -1,4 +1,4 @@ -from random import choice, randint +from random import choice from typing import Dict from primaite.simulator.file_system.file_system_file_type import file_type_sizes_KB, FileSystemFileType