#1800 - Simplified a bunch of stuff in the file system in prep for services and applications. Started adding the database logic. Waiting for the software manager/session manager work from another ticket.
This commit is contained in:
@@ -1,5 +1,14 @@
|
||||
from datetime import datetime
|
||||
|
||||
from primaite import _PRIMAITE_ROOT
|
||||
|
||||
TEMP_SIM_OUTPUT = _PRIMAITE_ROOT.parent.parent / "simulation_output"
|
||||
SIM_OUTPUT = None
|
||||
"A path at the repo root dir to use temporarily for sim output testing while in dev."
|
||||
# TODO: Remove once we integrate the simulation into PrimAITE and it uses the primaite session path
|
||||
|
||||
if not SIM_OUTPUT:
|
||||
session_timestamp = datetime.now()
|
||||
date_dir = session_timestamp.strftime("%Y-%m-%d")
|
||||
sim_path = session_timestamp.strftime("%Y-%m-%d_%H-%M-%S")
|
||||
SIM_OUTPUT = _PRIMAITE_ROOT.parent.parent / "simulation_output" / date_dir / sim_path
|
||||
SIM_OUTPUT.mkdir(exist_ok=True, parents=True)
|
||||
|
||||
@@ -149,8 +149,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from primaite.simulator.file_system.file_system_file_type import FileSystemFileType\n",
|
||||
"from primaite.simulator.file_system.file_system_file import FileSystemFile"
|
||||
"from primaite.simulator.file_system.file_type import FileType\n",
|
||||
"from primaite.simulator.file_system.file_system import File"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -160,7 +160,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"my_pc_downloads_folder = my_pc.file_system.create_folder(\"downloads\")\n",
|
||||
"my_pc_downloads_folder.add_file(FileSystemFile(name=\"firefox_installer.zip\",file_type=FileSystemFileType.ZIP))"
|
||||
"my_pc_downloads_folder.add_file(File(name=\"firefox_installer.zip\",file_type=FileType.ZIP))"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -171,7 +171,7 @@
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"FileSystemFile(uuid='7d56a563-ecc0-4011-8c97-240dd6c885c0', name='favicon.ico', size=40.0, file_type=<FileSystemFileType.PNG: '11'>, action_manager=None)"
|
||||
"File(uuid='7d56a563-ecc0-4011-8c97-240dd6c885c0', name='favicon.ico', size=40.0, file_type=<FileType.PNG: '11'>, action_manager=None)"
|
||||
]
|
||||
},
|
||||
"execution_count": 9,
|
||||
@@ -181,7 +181,7 @@
|
||||
],
|
||||
"source": [
|
||||
"my_server_folder = my_server.file_system.create_folder(\"static\")\n",
|
||||
"my_server.file_system.create_file(\"favicon.ico\", file_type=FileSystemFileType.PNG)"
|
||||
"my_server.file_system.create_file(\"favicon.ico\", file_type=FileType.PNG)"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,242 +1,441 @@
|
||||
from random import choice
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
import os.path
|
||||
from abc import abstractmethod
|
||||
from pathlib import Path
|
||||
from typing import Dict, Optional
|
||||
|
||||
from prettytable import MARKDOWN, PrettyTable
|
||||
|
||||
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
|
||||
from primaite.simulator.file_system.file_type import FileType, get_file_type_from_extension
|
||||
from primaite.simulator.system.core.sys_log import SysLog
|
||||
|
||||
_LOGGER = getLogger(__name__)
|
||||
|
||||
|
||||
class FileSystem(SimComponent):
|
||||
"""Class that contains all the simulation File System."""
|
||||
def convert_size(size_bytes):
|
||||
"""
|
||||
Convert a file size from bytes to a string with a more human-readable format.
|
||||
|
||||
folders: Dict[str, FileSystemFolder] = {}
|
||||
"""List containing all the folders in the file system."""
|
||||
This function takes the size of a file in bytes and converts it to a string representation with appropriate size
|
||||
units (B, KB, MB, GB, etc.).
|
||||
|
||||
:param size_bytes: The size of the file in bytes.
|
||||
:return: The human-readable string representation of the file size.
|
||||
"""
|
||||
if size_bytes == 0:
|
||||
return "0 B"
|
||||
|
||||
# Tuple of size units starting from Bytes up to Yottabytes
|
||||
size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
|
||||
|
||||
# Calculate the index (i) that will be used to select the appropriate size unit from size_name
|
||||
i = int(math.floor(math.log(size_bytes, 1024)))
|
||||
|
||||
# Calculate the adjusted size value (s) in terms of the new size unit
|
||||
p = math.pow(1024, i)
|
||||
s = round(size_bytes / p, 2)
|
||||
|
||||
return f"{s} {size_name[i]}"
|
||||
|
||||
|
||||
class FileSystemItemABC(SimComponent):
|
||||
"""Abstract base class for file system items used in the file system simulation."""
|
||||
|
||||
name: str
|
||||
"The name of the FileSystemItemABC."
|
||||
|
||||
def describe_state(self) -> Dict:
|
||||
"""
|
||||
Produce a dictionary describing the current state of this object.
|
||||
|
||||
Please see :py:meth:`primaite.simulator.core.SimComponent.describe_state` for a more detailed explanation.
|
||||
|
||||
:return: Current state of this object and child objects.
|
||||
:rtype: Dict
|
||||
"""
|
||||
state = super().describe_state()
|
||||
state.update({"folders": {uuid: folder.describe_state() for uuid, folder in self.folders.items()}})
|
||||
state.update(
|
||||
{
|
||||
"name": self.name,
|
||||
}
|
||||
)
|
||||
return state
|
||||
|
||||
def get_folders(self) -> Dict:
|
||||
"""Returns the list of folders."""
|
||||
return self.folders
|
||||
@property
|
||||
def size_str(self):
|
||||
return convert_size(self.size)
|
||||
|
||||
|
||||
class FileSystem(SimComponent):
|
||||
"""Class that contains all the simulation File System."""
|
||||
|
||||
folders: Dict[str, Folder] = {}
|
||||
"List containing all the folders in the file system."
|
||||
_folders_by_name: Dict[str, Folder] = {}
|
||||
sys_log: SysLog
|
||||
sim_root: Path
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
# Ensure a default root folder
|
||||
if not self.folders:
|
||||
self.create_folder("root")
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
return sum(folder.size for folder in self.folders.values())
|
||||
|
||||
def show(self, markdown: bool = False, full: bool = False):
|
||||
"""Prints a of the FileSystem"""
|
||||
headers = ["Folder", "Size"]
|
||||
if full:
|
||||
headers[0] = "File Path"
|
||||
table = PrettyTable(headers)
|
||||
if markdown:
|
||||
table.set_style(MARKDOWN)
|
||||
table.align = "l"
|
||||
table.title = f"{self.sys_log.hostname} File System"
|
||||
for folder in self.folders.values():
|
||||
if not full:
|
||||
table.add_row([folder.name, folder.size_str])
|
||||
else:
|
||||
for file in folder.files.values():
|
||||
table.add_row([file.path, file.size_str])
|
||||
if full:
|
||||
print(table.get_string(sortby="File Path"))
|
||||
else:
|
||||
print(table.get_string(sortby="Folder"))
|
||||
|
||||
def describe_state(self) -> Dict:
|
||||
"""
|
||||
Produce a dictionary describing the current state of this object.
|
||||
|
||||
:return: Current state of this object and child objects.
|
||||
"""
|
||||
state = super().describe_state()
|
||||
state["folders"] = {folder.name: folder.describe_state() for folder in self.folders.values()}
|
||||
return state
|
||||
|
||||
def create_folder(self, folder_name: str) -> Folder:
|
||||
"""
|
||||
Creates a Folder and adds it to the list of folders.
|
||||
|
||||
:param folder_name: The name of the folder.
|
||||
"""
|
||||
# check if folder with name already exists
|
||||
if self.get_folder(folder_name):
|
||||
raise Exception(f"Cannot create folder as it already exists: {folder_name}")
|
||||
|
||||
folder = Folder(name=folder_name, fs=self)
|
||||
|
||||
self.folders[folder.uuid] = folder
|
||||
self._folders_by_name[folder.name] = folder
|
||||
self.sys_log.info(f"Created folder /{folder.name}")
|
||||
return folder
|
||||
|
||||
def delete_folder(self, folder_name: str):
|
||||
"""
|
||||
Deletes a folder, removes it from the folders list and removes any child folders and files.
|
||||
|
||||
:param folder_name: The name of the folder.
|
||||
"""
|
||||
if folder_name == "root":
|
||||
self.sys_log.warning("Cannot delete the root folder.")
|
||||
return
|
||||
folder = self._folders_by_name.get(folder_name)
|
||||
if folder:
|
||||
for file in folder.files.values():
|
||||
self.delete_file(file)
|
||||
self.folders.pop(folder.uuid)
|
||||
self._folders_by_name.pop(folder.name)
|
||||
self.sys_log.info(f"Deleted folder /{folder.name} and its contents")
|
||||
else:
|
||||
_LOGGER.debug(f"Cannot delete folder as it does not exist: {folder_name}")
|
||||
|
||||
def create_file(
|
||||
self,
|
||||
file_name: str,
|
||||
size: Optional[float] = None,
|
||||
file_type: Optional[FileSystemFileType] = None,
|
||||
folder: Optional[FileSystemFolder] = None,
|
||||
folder_uuid: Optional[str] = None,
|
||||
) -> FileSystemFile:
|
||||
size: Optional[int] = None,
|
||||
file_type: Optional[FileType] = None,
|
||||
folder_name: Optional[str] = None,
|
||||
real: bool = False,
|
||||
) -> File:
|
||||
"""
|
||||
Creates a FileSystemFile and adds it to the list of files.
|
||||
Creates a File and adds it to the list of files.
|
||||
|
||||
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: size: The size the file takes on disk.
|
||||
:type: size: Optional[float]
|
||||
|
||||
: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]
|
||||
:param file_name: The file name.
|
||||
:param size: The size the file takes on disk in bytes.
|
||||
:param file_type: The type of the file.
|
||||
:param folder_name: The folder to add the file to.
|
||||
:param real: "Indicates whether the File is actually a real file in the Node sim fs output."
|
||||
"""
|
||||
file = None
|
||||
folder = None
|
||||
|
||||
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:
|
||||
if folder_name:
|
||||
# check if file with name already exists
|
||||
if folder.get_file_by_name(file_name):
|
||||
raise Exception(f'File with name "{file_name}" already exists.')
|
||||
|
||||
file = FileSystemFile(name=file_name, size=size, file_type=file_type)
|
||||
folder.add_file(file=file)
|
||||
folder = self._folders_by_name.get(folder_name)
|
||||
# If not then create it
|
||||
if not folder:
|
||||
folder = self.create_folder(folder_name)
|
||||
else:
|
||||
# check if a "root" folder exists
|
||||
folder = self.get_folder_by_name("root")
|
||||
if folder is None:
|
||||
# create a root folder
|
||||
folder = FileSystemFolder(name="root")
|
||||
# Use root folder if folder_name not supplied
|
||||
folder = self._folders_by_name["root"]
|
||||
|
||||
# add file to root folder
|
||||
file = FileSystemFile(name=file_name, size=size, file_type=file_type)
|
||||
folder.add_file(file)
|
||||
self.folders[folder.uuid] = folder
|
||||
# Create the file and add it to the folder
|
||||
file = File(
|
||||
name=file_name,
|
||||
sim_size=size,
|
||||
file_type=file_type,
|
||||
folder=folder,
|
||||
real=real,
|
||||
sim_path=self.sim_root if real else None,
|
||||
)
|
||||
folder.add_file(file)
|
||||
self.sys_log.info(f"Created file /{file.path}")
|
||||
return file
|
||||
|
||||
def create_folder(
|
||||
self,
|
||||
folder_name: str,
|
||||
) -> FileSystemFolder:
|
||||
"""
|
||||
Creates a FileSystemFolder and adds it to the list of folders.
|
||||
def get_file(self, folder_name: str, file_name: str) -> Optional[File]:
|
||||
folder = self.get_folder(folder_name)
|
||||
if folder:
|
||||
return folder.get_file(file_name)
|
||||
self.fs.sys_log.info(f"file not found /{folder_name}/{file_name}")
|
||||
|
||||
:param: folder_name: The name of the folder
|
||||
:type: folder_name: str
|
||||
"""
|
||||
# check if folder with name already exists
|
||||
if self.get_folder_by_name(folder_name):
|
||||
raise Exception(f'Folder with name "{folder_name}" already exists.')
|
||||
def delete_file(self, folder_name: str, file_name: str):
|
||||
folder = self.get_folder(folder_name)
|
||||
if folder:
|
||||
file = folder.get_file(file_name)
|
||||
if file:
|
||||
folder.remove_file(file)
|
||||
self.sys_log.info(f"Deleted file /{file.path}")
|
||||
|
||||
folder = FileSystemFolder(name=folder_name)
|
||||
def move_file(self, src_folder_name: str, src_file_name: str, dst_folder_name):
|
||||
file = self.get_file(folder_name=src_folder_name, file_name=src_file_name)
|
||||
if file:
|
||||
src_folder = file.folder
|
||||
|
||||
self.folders[folder.uuid] = folder
|
||||
return folder
|
||||
# remove file from src
|
||||
src_folder.remove_file(file)
|
||||
dst_folder = self.get_folder(folder_name=dst_folder_name)
|
||||
if not dst_folder:
|
||||
dst_folder = self.create_folder(dst_folder_name)
|
||||
# add file to dst
|
||||
dst_folder.add_file(file)
|
||||
|
||||
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]
|
||||
"""
|
||||
# iterate through folders to delete the item with the matching uuid
|
||||
for key in self.folders:
|
||||
self.get_folder_by_id(key).remove_file(file)
|
||||
|
||||
def delete_folder(self, folder: FileSystemFolder):
|
||||
"""
|
||||
Deletes a folder, removes it from the folders list and removes any child folders and files.
|
||||
|
||||
:param folder: The folder to remove
|
||||
:type folder: FileSystemFolder
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
Moves a file from one folder to another.
|
||||
|
||||
can provide
|
||||
|
||||
: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")
|
||||
|
||||
if file is None:
|
||||
raise Exception("File to be moved is None")
|
||||
|
||||
# check if file with name already exists
|
||||
if target_folder.get_file_by_name(file.name):
|
||||
raise Exception(f'Folder with name "{file.name}" already exists.')
|
||||
|
||||
# remove file from src
|
||||
src_folder.remove_file(file)
|
||||
|
||||
# add file to target
|
||||
target_folder.add_file(file)
|
||||
|
||||
def copy_file(self, file: FileSystemFile, src_folder: FileSystemFolder, target_folder: FileSystemFolder):
|
||||
def copy_file(self, src_folder_name: str, src_file_name: str, dst_folder_name):
|
||||
"""
|
||||
Copies a file from one folder to another.
|
||||
|
||||
can provide
|
||||
|
||||
:param: file: The file to move
|
||||
:type: file: FileSystemFile
|
||||
:param file: The file to move
|
||||
:type: file: File
|
||||
|
||||
:param: src_folder: The folder where the file is located
|
||||
:type: FileSystemFolder
|
||||
:param src_folder: The folder where the file is located
|
||||
:type: Folder
|
||||
|
||||
:param: target_folder: The folder where the file should be moved to
|
||||
:type: FileSystemFolder
|
||||
:param target_folder: The folder where the file should be moved to
|
||||
:type: Folder
|
||||
"""
|
||||
if src_folder is None:
|
||||
raise Exception("Source folder not provided")
|
||||
file = self.get_file(folder_name=src_folder_name, file_name=src_file_name)
|
||||
if file:
|
||||
dst_folder = self.get_folder(folder_name=dst_folder_name)
|
||||
if not dst_folder:
|
||||
dst_folder = self.create_folder(dst_folder_name)
|
||||
new_file = file.make_copy(dst_folder=dst_folder)
|
||||
dst_folder.add_file(new_file)
|
||||
|
||||
if target_folder is None:
|
||||
raise Exception("Target folder not provided")
|
||||
|
||||
if file is None:
|
||||
raise Exception("File to be moved is None")
|
||||
|
||||
# check if file with name already exists
|
||||
if target_folder.get_file_by_name(file.name):
|
||||
raise Exception(f'Folder with name "{file.name}" already exists.')
|
||||
|
||||
# add file to target
|
||||
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."""
|
||||
for key in self.folders:
|
||||
file = self.folders[key].get_file_by_id(file_id=file_id)
|
||||
if file is not None:
|
||||
return file
|
||||
|
||||
def get_folder_by_name(self, folder_name: str) -> Optional[FileSystemFolder]:
|
||||
def get_folder(self, folder_name: str) -> Optional[Folder]:
|
||||
"""
|
||||
Returns a the first folder with a matching name.
|
||||
Get a folder by its name if it exists.
|
||||
|
||||
:return: Returns the first FileSydtemFolder with a matching name
|
||||
:param folder_name: The folder name.
|
||||
:return: The matching Folder.
|
||||
"""
|
||||
matching_folder = None
|
||||
for key in self.folders:
|
||||
if self.folders[key].name == folder_name:
|
||||
matching_folder = self.folders[key]
|
||||
break
|
||||
return matching_folder
|
||||
return self._folders_by_name.get(folder_name)
|
||||
|
||||
def get_folder_by_id(self, folder_id: str) -> FileSystemFolder:
|
||||
def get_folder_by_id(self, folder_uuid: str) -> Optional[Folder]:
|
||||
"""
|
||||
Checks if the folder exists.
|
||||
Get a folder by its uuid if it exists.
|
||||
|
||||
:param: folder_id: The id of the folder to find
|
||||
:type: folder_id: str
|
||||
:param folder_uuid: The folder uuid.
|
||||
:return: The matching Folder.
|
||||
"""
|
||||
return self.folders[folder_id]
|
||||
return self.folders.get(folder_uuid)
|
||||
|
||||
def get_random_file_type(self) -> FileSystemFileType:
|
||||
"""
|
||||
Returns a random FileSystemFileTypeEnum.
|
||||
|
||||
:return: A random file type Enum
|
||||
class Folder(FileSystemItemABC):
|
||||
"""Simulation Folder."""
|
||||
|
||||
fs: FileSystem
|
||||
"The FileSystem the Folder is in."
|
||||
files: Dict[str, File] = {}
|
||||
"Files stored in the folder."
|
||||
_files_by_name: Dict[str, File] = {}
|
||||
"Files by their name as <file name>.<file type>."
|
||||
is_quarantined: bool = False
|
||||
"Flag that marks the folder as quarantined if true."
|
||||
|
||||
def describe_state(self) -> Dict:
|
||||
"""
|
||||
return choice(list(FileSystemFileType))
|
||||
Produce a dictionary describing the current state of this object.
|
||||
|
||||
:return: Current state of this object and child objects.
|
||||
"""
|
||||
state = super().describe_state()
|
||||
state["files"] = {file.name: file.describe_state() for uuid, file in self.files.items()}
|
||||
state["is_quarantined"] = self.is_quarantined
|
||||
return state
|
||||
|
||||
def show(self, markdown: bool = False):
|
||||
"""Prints a of the Folder"""
|
||||
table = PrettyTable(["File", "Size"])
|
||||
if markdown:
|
||||
table.set_style(MARKDOWN)
|
||||
table.align = "l"
|
||||
table.title = f"{self.fs.sys_log.hostname} File System Folder ({self.name})"
|
||||
for file in self.files.values():
|
||||
table.add_row([file.name, file.size_str])
|
||||
print(table.get_string(sortby="File"))
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
return sum(file.size for file in self.files.values() if file.size is not None)
|
||||
|
||||
def get_file(self, file_name: str) -> Optional[File]:
|
||||
"""
|
||||
Get a file by its name.
|
||||
|
||||
File name must be the filename and prefix, like 'memo.docx'.
|
||||
|
||||
:param file_name: The file name.
|
||||
:return: The matching File.
|
||||
"""
|
||||
# TODO: Increment read count?
|
||||
return self._files_by_name.get(file_name)
|
||||
|
||||
def get_file_by_id(self, file_uuid: str) -> File:
|
||||
"""
|
||||
Get a file by its uuid.
|
||||
|
||||
|
||||
:param file_uuid: The file uuid.
|
||||
:return: The matching File.
|
||||
"""
|
||||
return self.files.get(file_uuid)
|
||||
|
||||
def add_file(self, file: File):
|
||||
"""Adds a file to the folder list."""
|
||||
if file is None or not isinstance(file, File):
|
||||
raise Exception(f"Invalid file: {file}")
|
||||
|
||||
# check if file with id already exists in folder
|
||||
if file.uuid in self.files:
|
||||
_LOGGER.debug(f"File with id {file.uuid} already exists in folder")
|
||||
else:
|
||||
# add to list
|
||||
self.files[file.uuid] = file
|
||||
self._files_by_name[file.name] = file
|
||||
file.folder = self
|
||||
|
||||
def remove_file(self, file: Optional[File]):
|
||||
"""
|
||||
Removes a file from the folder list.
|
||||
|
||||
The method can take a File object or a file id.
|
||||
|
||||
:param file: The file to remove
|
||||
:type: Optional[File]
|
||||
"""
|
||||
if file is None or not isinstance(file, File):
|
||||
raise Exception(f"Invalid file: {file}")
|
||||
|
||||
if self.files.get(file.uuid):
|
||||
self.files.pop(file.uuid)
|
||||
self._files_by_name.pop(file.name)
|
||||
else:
|
||||
_LOGGER.debug(f"File with UUID {file.uuid} was not found.")
|
||||
|
||||
def quarantine(self):
|
||||
"""Quarantines the File System Folder."""
|
||||
if not self.is_quarantined:
|
||||
self.is_quarantined = True
|
||||
self.fs.sys_log.info(f"Quarantined folder ./{self.name}")
|
||||
|
||||
def unquarantine(self):
|
||||
"""Unquarantine of the File System Folder."""
|
||||
if self.is_quarantined:
|
||||
self.is_quarantined = False
|
||||
self.fs.sys_log.info(f"Quarantined folder ./{self.name}")
|
||||
|
||||
def quarantine_status(self) -> bool:
|
||||
"""Returns true if the folder is being quarantined."""
|
||||
return self.is_quarantined
|
||||
|
||||
|
||||
class File(FileSystemItemABC):
|
||||
"""Class that represents a file in the simulation."""
|
||||
|
||||
folder: Folder
|
||||
"The Folder the File is in."
|
||||
file_type: FileType
|
||||
"The type of File."
|
||||
sim_size: Optional[int] = None
|
||||
"The simulated file size."
|
||||
real: bool = False
|
||||
"Indicates whether the File is actually a real file in the Node sim fs output."
|
||||
sim_path: Optional[Path] = None
|
||||
"The Path if real is True."
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""
|
||||
Initialise File class.
|
||||
|
||||
:param name: The name of the file.
|
||||
:param file_type: The FileType of the file
|
||||
:param size: The size of the FileSystemItemABC
|
||||
"""
|
||||
has_extension = "." in kwargs["name"]
|
||||
|
||||
# Attempt to use the file type extension to set/override the FileType
|
||||
if has_extension:
|
||||
extension = kwargs["name"].split(".")[-1]
|
||||
kwargs["file_type"] = get_file_type_from_extension(extension)
|
||||
else:
|
||||
# If the file name does not have a extension, override file type to FileType.UNKNOWN
|
||||
if not kwargs["file_type"]:
|
||||
kwargs["file_type"] = FileType.UNKNOWN
|
||||
if kwargs["file_type"] != FileType.UNKNOWN:
|
||||
kwargs["name"] = f"{kwargs['name']}.{kwargs['file_type'].name.lower()}"
|
||||
|
||||
# set random file size if none provided
|
||||
if not kwargs.get("sim_size"):
|
||||
kwargs["sim_size"] = kwargs["file_type"].default_size
|
||||
super().__init__(**kwargs)
|
||||
if self.real:
|
||||
self.sim_path = self.folder.fs.sim_root / self.path
|
||||
if not self.sim_path.exists():
|
||||
self.sim_path.parent.mkdir(exist_ok=True, parents=True)
|
||||
with open(self.sim_path, mode="a"):
|
||||
pass
|
||||
|
||||
def make_copy(self, dst_folder: Folder) -> File:
|
||||
return File(folder=dst_folder, **self.model_dump(exclude={"uuid", "folder"}))
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
"""The path of the file in the FileSystem."""
|
||||
return f"{self.folder.name}/{self.name}"
|
||||
|
||||
@property
|
||||
def size(self) -> int:
|
||||
"""The file size in Bytes."""
|
||||
if self.real:
|
||||
return os.path.getsize(self.sim_path)
|
||||
return self.sim_size
|
||||
|
||||
def describe_state(self) -> Dict:
|
||||
"""Produce a dictionary describing the current state of this object."""
|
||||
state = super().describe_state()
|
||||
state["size"] = self.size
|
||||
state["file_type"] = self.file_type.name
|
||||
return state
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
from random import choice
|
||||
from typing import Dict
|
||||
|
||||
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(FileSystemItem):
|
||||
"""Class that represents a file in the simulation."""
|
||||
|
||||
file_type: FileSystemFileType = None
|
||||
"""The type of the FileSystemFile"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""
|
||||
Initialise FileSystemFile class.
|
||||
|
||||
:param name: The name of the file.
|
||||
:type name: str
|
||||
|
||||
:param file_type: The FileSystemFileType of the file
|
||||
:type file_type: Optional[FileSystemFileType]
|
||||
|
||||
:param size: The size of the FileSystemItem
|
||||
:type size: Optional[float]
|
||||
"""
|
||||
# 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))
|
||||
|
||||
# set random file size if none provided
|
||||
if kwargs.get("size") is None:
|
||||
kwargs["size"] = file_type_sizes_KB[kwargs["file_type"]]
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def describe_state(self) -> Dict:
|
||||
"""
|
||||
Produce a dictionary describing the current state of this object.
|
||||
|
||||
Please see :py:meth:`primaite.simulator.core.SimComponent.describe_state` for a more detailed explanation.
|
||||
|
||||
:return: Current state of this object and child objects.
|
||||
:rtype: Dict
|
||||
"""
|
||||
state = super().describe_state()
|
||||
state.update(
|
||||
{
|
||||
"uuid": self.uuid,
|
||||
"file_type": self.file_type.name,
|
||||
}
|
||||
)
|
||||
return state
|
||||
@@ -1,132 +0,0 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class FileSystemFileType(str, Enum):
|
||||
"""An enumeration of common file types."""
|
||||
|
||||
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."
|
||||
|
||||
# Database file types
|
||||
MDF = 32
|
||||
"MS SQL Server primary database file"
|
||||
NDF = 33
|
||||
"MS SQL Server secondary database file"
|
||||
LDF = 34
|
||||
"MS SQL Server transaction log"
|
||||
|
||||
|
||||
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,
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
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: Dict[str, FileSystemFile] = {}
|
||||
"""List of files stored in the folder."""
|
||||
|
||||
is_quarantined: bool = False
|
||||
"""Flag that marks the folder as quarantined if true."""
|
||||
|
||||
def describe_state(self) -> Dict:
|
||||
"""
|
||||
Produce a dictionary describing the current state of this object.
|
||||
|
||||
Please see :py:meth:`primaite.simulator.core.SimComponent.describe_state` for a more detailed explanation.
|
||||
|
||||
:return: Current state of this object and child objects.
|
||||
:rtype: Dict
|
||||
"""
|
||||
state = super().describe_state()
|
||||
state.update(
|
||||
{
|
||||
"files": {uuid: file.describe_state() for uuid, file in self.files.items()},
|
||||
"is_quarantined": self.is_quarantined,
|
||||
}
|
||||
)
|
||||
return state
|
||||
|
||||
def get_file_by_id(self, file_id: str) -> FileSystemFile:
|
||||
"""Return a FileSystemFile with the matching id."""
|
||||
return self.files.get(file_id)
|
||||
|
||||
def get_file_by_name(self, file_name: str) -> FileSystemFile:
|
||||
"""Return a FileSystemFile with the matching id."""
|
||||
return next((f for f in list(self.files) if f.name == file_name), None)
|
||||
|
||||
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}")
|
||||
|
||||
# check if file with id already exists in folder
|
||||
if file.uuid in self.files:
|
||||
_LOGGER.debug(f"File with id {file.uuid} already exists in folder")
|
||||
else:
|
||||
# add to list
|
||||
self.files[file.uuid] = file
|
||||
self.size += file.size
|
||||
|
||||
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]
|
||||
"""
|
||||
if file is None or not isinstance(file, FileSystemFile):
|
||||
raise Exception(f"Invalid file: {file}")
|
||||
|
||||
if self.files.get(file.uuid):
|
||||
del self.files[file.uuid]
|
||||
|
||||
self.size -= file.size
|
||||
else:
|
||||
_LOGGER.debug(f"File with UUID {file.uuid} was not found.")
|
||||
|
||||
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
|
||||
@@ -1,31 +0,0 @@
|
||||
from typing import Dict
|
||||
|
||||
from primaite.simulator.core import SimComponent
|
||||
|
||||
|
||||
class FileSystemItem(SimComponent):
|
||||
"""Abstract base class for FileSystemItems used in the file system simulation."""
|
||||
|
||||
name: str
|
||||
"""The name of the FileSystemItem."""
|
||||
|
||||
size: float = 0
|
||||
"""The size the item takes up on disk."""
|
||||
|
||||
def describe_state(self) -> Dict:
|
||||
"""
|
||||
Produce a dictionary describing the current state of this object.
|
||||
|
||||
Please see :py:meth:`primaite.simulator.core.SimComponent.describe_state` for a more detailed explanation.
|
||||
|
||||
:return: Current state of this object and child objects.
|
||||
:rtype: Dict
|
||||
"""
|
||||
state = super().describe_state()
|
||||
state.update(
|
||||
{
|
||||
"name": self.name,
|
||||
"size": self.size,
|
||||
}
|
||||
)
|
||||
return state
|
||||
@@ -4,12 +4,14 @@ import re
|
||||
import secrets
|
||||
from enum import Enum
|
||||
from ipaddress import IPv4Address, IPv4Network
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
|
||||
from prettytable import MARKDOWN, PrettyTable
|
||||
|
||||
from primaite import getLogger
|
||||
from primaite.exceptions import NetworkError
|
||||
from primaite.simulator import SIM_OUTPUT
|
||||
from primaite.simulator.core import SimComponent
|
||||
from primaite.simulator.domain.account import Account
|
||||
from primaite.simulator.file_system.file_system import FileSystem
|
||||
@@ -890,6 +892,8 @@ class Node(SimComponent):
|
||||
"All processes on the node."
|
||||
file_system: FileSystem
|
||||
"The nodes file system."
|
||||
root: Path
|
||||
"Root directory for simulation output."
|
||||
sys_log: SysLog
|
||||
arp: ARPCache
|
||||
icmp: ICMP
|
||||
@@ -921,8 +925,10 @@ class Node(SimComponent):
|
||||
kwargs["software_manager"] = SoftwareManager(
|
||||
sys_log=kwargs.get("sys_log"), session_manager=kwargs.get("session_manager")
|
||||
)
|
||||
if not kwargs.get("root"):
|
||||
kwargs["root"] = SIM_OUTPUT / kwargs["hostname"]
|
||||
if not kwargs.get("file_system"):
|
||||
kwargs["file_system"] = FileSystem()
|
||||
kwargs["file_system"] = FileSystem(sys_log=kwargs["sys_log"], sim_root=kwargs["root"] / "fs")
|
||||
super().__init__(**kwargs)
|
||||
self.arp.nics = self.nics
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import logging
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from primaite.simulator import TEMP_SIM_OUTPUT
|
||||
from primaite.simulator import SIM_OUTPUT
|
||||
|
||||
|
||||
class _JSONFilter(logging.Filter):
|
||||
@@ -62,7 +62,7 @@ class PacketCapture:
|
||||
|
||||
def _get_log_path(self) -> Path:
|
||||
"""Get the path for the log file."""
|
||||
root = TEMP_SIM_OUTPUT / self.hostname
|
||||
root = SIM_OUTPUT / self.hostname
|
||||
root.mkdir(exist_ok=True, parents=True)
|
||||
return root / f"{self._logger_name}.log"
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ from pathlib import Path
|
||||
|
||||
from prettytable import MARKDOWN, PrettyTable
|
||||
|
||||
from primaite.simulator import TEMP_SIM_OUTPUT
|
||||
from primaite.simulator import SIM_OUTPUT
|
||||
|
||||
|
||||
class _NotJSONFilter(logging.Filter):
|
||||
@@ -81,7 +81,7 @@ class SysLog:
|
||||
|
||||
:return: Path object representing the location of the log file.
|
||||
"""
|
||||
root = TEMP_SIM_OUTPUT / self.hostname
|
||||
root = SIM_OUTPUT / self.hostname
|
||||
root.mkdir(exist_ok=True, parents=True)
|
||||
return root / f"{self.hostname}_sys.log"
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
from typing import Dict
|
||||
|
||||
from primaite.simulator.file_system.file_system_file_type import FileSystemFileType
|
||||
from primaite.simulator.file_system.file_type import FileType
|
||||
from primaite.simulator.network.hardware.base import Node
|
||||
from primaite.simulator.system.services.service import Service
|
||||
|
||||
|
||||
class DatabaseService(Service):
|
||||
"""Service loosely modelled on Microsoft SQL Server."""
|
||||
"""A generic SQL Server Service."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def describe_state(self) -> Dict:
|
||||
"""
|
||||
@@ -19,6 +22,10 @@ class DatabaseService(Service):
|
||||
"""
|
||||
return super().describe_state()
|
||||
|
||||
@classmethod
|
||||
def install(cls, node: Node):
|
||||
|
||||
|
||||
def uninstall(self) -> None:
|
||||
"""
|
||||
Undo installation procedure.
|
||||
@@ -42,19 +49,10 @@ class DatabaseService(Service):
|
||||
|
||||
def _setup_files(
|
||||
self,
|
||||
db_size: int = 1000,
|
||||
use_secondary_db_file: bool = False,
|
||||
secondary_db_size: int = 300,
|
||||
folder_name: str = "database",
|
||||
):
|
||||
"""Set up files that are required by the database on the parent host.
|
||||
|
||||
:param db_size: Initial file size of the main database file, defaults to 1000
|
||||
:type db_size: int, optional
|
||||
:param use_secondary_db_file: Whether to use a secondary database file, defaults to False
|
||||
:type use_secondary_db_file: bool, optional
|
||||
:param secondary_db_size: Size of the secondary db file, defaults to None
|
||||
:type secondary_db_size: int, optional
|
||||
:param folder_name: Name of the folder which will be setup to hold the db files, defaults to "database"
|
||||
:type folder_name: str, optional
|
||||
"""
|
||||
@@ -63,14 +61,14 @@ class DatabaseService(Service):
|
||||
self.parent: Node
|
||||
self.folder = self.parent.file_system.create_folder(folder_name)
|
||||
self.primary_store = self.parent.file_system.create_file(
|
||||
"db_primary_store", db_size, FileSystemFileType.MDF, folder=self.folder
|
||||
"db_primary_store", db_size, FileType.MDF, folder=self.folder
|
||||
)
|
||||
self.transaction_log = self.parent.file_system.create_file(
|
||||
"db_transaction_log", "1", FileSystemFileType.LDF, folder=self.folder
|
||||
"db_transaction_log", "1", FileType.LDF, folder=self.folder
|
||||
)
|
||||
if use_secondary_db_file:
|
||||
self.secondary_store = self.parent.file_system.create_file(
|
||||
"db_secondary_store", secondary_db_size, FileSystemFileType.NDF, folder=self.folder
|
||||
"db_secondary_store", secondary_db_size, FileType.NDF, folder=self.folder
|
||||
)
|
||||
else:
|
||||
self.secondary_store = None
|
||||
|
||||
@@ -53,4 +53,4 @@ def test_uninstalling_database():
|
||||
node.uninstall_service(db)
|
||||
|
||||
assert db not in node
|
||||
assert node.file_system.get_folder_by_name("database") is None
|
||||
assert node.file_system.get_folder("database") is None
|
||||
|
||||
@@ -1,132 +1,137 @@
|
||||
from primaite.simulator.file_system.file_system import FileSystem
|
||||
from primaite.simulator.file_system.file_system_file import FileSystemFile
|
||||
from primaite.simulator.file_system.file_system_folder import FileSystemFolder
|
||||
import pytest
|
||||
|
||||
from primaite.simulator.file_system.file_system import File, FileSystem, Folder
|
||||
from primaite.simulator.file_system.file_type import FileType
|
||||
from primaite.simulator.network.hardware.base import Node
|
||||
|
||||
|
||||
def test_create_folder_and_file():
|
||||
@pytest.fixture(scope="function")
|
||||
def file_system() -> FileSystem:
|
||||
return Node(hostname="fs_node").file_system
|
||||
|
||||
|
||||
def test_create_folder_and_file(file_system):
|
||||
"""Test creating a folder and a file."""
|
||||
file_system = FileSystem()
|
||||
folder = file_system.create_folder(folder_name="test_folder")
|
||||
assert len(file_system.folders) is 1
|
||||
assert len(file_system.folders) == 1
|
||||
test_folder = file_system.create_folder(folder_name="test_folder")
|
||||
|
||||
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 len(file_system.folders) is 2
|
||||
file_system.create_file(file_name="test_file.txt", folder_name="test_folder")
|
||||
|
||||
assert file_system.get_file_by_id(file.uuid).name is "test_file"
|
||||
assert file_system.get_file_by_id(file.uuid).size == 10
|
||||
assert len(file_system.get_folder("test_folder").files) == 1
|
||||
|
||||
assert file_system.get_folder("test_folder").get_file("test_file.txt")
|
||||
|
||||
|
||||
def test_create_file():
|
||||
def test_create_file_no_folder(file_system):
|
||||
"""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", size=10)
|
||||
file = file_system.create_file(file_name="test_file.txt", 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
|
||||
assert file_system.get_folder("root").get_file("test_file.txt") == file
|
||||
assert file_system.get_folder("root").get_file("test_file.txt").file_type == FileType.TXT
|
||||
assert file_system.get_folder("root").get_file("test_file.txt").size == 10
|
||||
|
||||
|
||||
def test_delete_file():
|
||||
def test_create_file_no_extension(file_system):
|
||||
"""Tests that creating a file without an extension sets the file type to FileType.UNKNOWN."""
|
||||
file = file_system.create_file(file_name="test_file")
|
||||
assert len(file_system.folders) is 1
|
||||
assert file_system.get_folder("root").get_file("test_file") == file
|
||||
assert file_system.get_folder("root").get_file("test_file").file_type == FileType.UNKNOWN
|
||||
assert file_system.get_folder("root").get_file("test_file").size == 0
|
||||
|
||||
|
||||
def test_delete_file(file_system):
|
||||
"""Tests that a file can be deleted."""
|
||||
file_system = FileSystem()
|
||||
file_system.create_file(file_name="test_file.txt")
|
||||
assert len(file_system.folders) == 1
|
||||
assert len(file_system.get_folder("root").files) == 1
|
||||
|
||||
file = file_system.create_file(file_name="test_file", size=10)
|
||||
assert len(file_system.folders) is 1
|
||||
|
||||
folder_id = list(file_system.folders.keys())[0]
|
||||
folder = file_system.get_folder_by_id(folder_id)
|
||||
assert folder.get_file_by_id(file.uuid) is file
|
||||
|
||||
file_system.delete_file(file=file)
|
||||
assert len(file_system.folders) is 1
|
||||
assert len(folder.files) is 0
|
||||
file_system.delete_file(folder_name="root", file_name="test_file.txt")
|
||||
assert len(file_system.folders) == 1
|
||||
assert len(file_system.get_folder("root").files) == 0
|
||||
|
||||
|
||||
def test_delete_non_existent_file():
|
||||
def test_delete_non_existent_file(file_system):
|
||||
"""Tests deleting a non existent file."""
|
||||
file_system = FileSystem()
|
||||
|
||||
file = file_system.create_file(file_name="test_file", size=10)
|
||||
not_added_file = FileSystemFile(name="not_added")
|
||||
file_system.create_file(file_name="test_file.txt")
|
||||
# folder should be created
|
||||
assert len(file_system.folders) is 1
|
||||
assert len(file_system.folders) == 1
|
||||
# should only have 1 file in the file system
|
||||
folder_id = list(file_system.folders.keys())[0]
|
||||
folder = file_system.get_folder_by_id(folder_id)
|
||||
assert len(list(folder.files)) is 1
|
||||
|
||||
assert folder.get_file_by_id(file.uuid) is file
|
||||
assert len(file_system.get_folder("root").files) == 1
|
||||
|
||||
# deleting should not change how many files are in folder
|
||||
file_system.delete_file(file=not_added_file)
|
||||
assert len(file_system.folders) is 1
|
||||
assert len(list(folder.files)) is 1
|
||||
file_system.delete_file(folder_name="root", file_name="does_not_exist!")
|
||||
|
||||
# should still only be one folder
|
||||
assert len(file_system.folders) == 1
|
||||
# The folder should still have 1 file
|
||||
assert len(file_system.get_folder("root").files) == 1
|
||||
|
||||
|
||||
def test_delete_folder():
|
||||
file_system = FileSystem()
|
||||
folder = file_system.create_folder(folder_name="test_folder")
|
||||
assert len(file_system.folders) is 1
|
||||
def test_delete_folder(file_system):
|
||||
file_system.create_folder(folder_name="test_folder")
|
||||
assert len(file_system.folders) == 2
|
||||
|
||||
file_system.delete_folder(folder)
|
||||
assert len(file_system.folders) is 0
|
||||
file_system.delete_folder(folder_name="test_folder")
|
||||
assert len(file_system.folders) == 1
|
||||
|
||||
|
||||
def test_deleting_a_non_existent_folder():
|
||||
file_system = FileSystem()
|
||||
folder = file_system.create_folder(folder_name="test_folder")
|
||||
not_added_folder = FileSystemFolder(name="fake_folder")
|
||||
assert len(file_system.folders) is 1
|
||||
def test_deleting_a_non_existent_folder(file_system):
|
||||
file_system.create_folder(folder_name="test_folder")
|
||||
assert len(file_system.folders) == 2
|
||||
|
||||
file_system.delete_folder(not_added_folder)
|
||||
assert len(file_system.folders) is 1
|
||||
file_system.delete_folder(folder_name="does not exist!")
|
||||
assert len(file_system.folders) == 2
|
||||
|
||||
|
||||
def test_move_file():
|
||||
def test_deleting_root_folder_fails(file_system):
|
||||
assert len(file_system.folders) == 1
|
||||
|
||||
file_system.delete_folder(folder_name="root")
|
||||
assert len(file_system.folders) == 1
|
||||
|
||||
|
||||
def test_move_file(file_system):
|
||||
"""Tests the file move function."""
|
||||
file_system = FileSystem()
|
||||
src_folder = file_system.create_folder(folder_name="test_folder_1")
|
||||
assert len(file_system.folders) is 1
|
||||
file_system.create_folder(folder_name="src_folder")
|
||||
file_system.create_folder(folder_name="dst_folder")
|
||||
|
||||
target_folder = file_system.create_folder(folder_name="test_folder_2")
|
||||
assert len(file_system.folders) is 2
|
||||
file = file_system.create_file(file_name="test_file.txt", size=10, folder_name="src_folder")
|
||||
original_uuid = file.uuid
|
||||
|
||||
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
|
||||
assert len(file_system.get_folder("src_folder").files) == 1
|
||||
assert len(file_system.get_folder("dst_folder").files) == 0
|
||||
|
||||
file_system.move_file(file=file, src_folder=src_folder, target_folder=target_folder)
|
||||
file_system.move_file(src_folder_name="src_folder", src_file_name="test_file.txt", dst_folder_name="dst_folder")
|
||||
|
||||
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
|
||||
assert len(file_system.get_folder("src_folder").files) == 0
|
||||
assert len(file_system.get_folder("dst_folder").files) == 1
|
||||
assert file_system.get_file("dst_folder", "test_file.txt").uuid == original_uuid
|
||||
|
||||
|
||||
def test_copy_file():
|
||||
def test_copy_file(file_system):
|
||||
"""Tests the file copy function."""
|
||||
file_system = FileSystem()
|
||||
src_folder = file_system.create_folder(folder_name="test_folder_1")
|
||||
assert len(file_system.folders) is 1
|
||||
file_system.create_folder(folder_name="src_folder")
|
||||
file_system.create_folder(folder_name="dst_folder")
|
||||
|
||||
target_folder = file_system.create_folder(folder_name="test_folder_2")
|
||||
assert len(file_system.folders) is 2
|
||||
file = file_system.create_file(file_name="test_file.txt", size=10, folder_name="src_folder")
|
||||
original_uuid = file.uuid
|
||||
|
||||
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
|
||||
assert len(file_system.get_folder("src_folder").files) == 1
|
||||
assert len(file_system.get_folder("dst_folder").files) == 0
|
||||
|
||||
file_system.copy_file(file=file, src_folder=src_folder, target_folder=target_folder)
|
||||
file_system.copy_file(src_folder_name="src_folder", src_file_name="test_file.txt", dst_folder_name="dst_folder")
|
||||
|
||||
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
|
||||
assert len(file_system.get_folder("src_folder").files) == 1
|
||||
assert len(file_system.get_folder("dst_folder").files) == 1
|
||||
assert file_system.get_file("dst_folder", "test_file.txt").uuid != original_uuid
|
||||
|
||||
|
||||
def test_serialisation():
|
||||
@pytest.mark.skip(reason="Skipping until we tackle serialisation")
|
||||
def test_serialisation(file_system):
|
||||
"""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.folders) is 1
|
||||
|
||||
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
|
||||
file_system.create_file(file_name="test_file.txt")
|
||||
|
||||
serialised_file_sys = file_system.model_dump_json()
|
||||
deserialised_file_sys = FileSystem.model_validate_json(serialised_file_sys)
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
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 import File
|
||||
from primaite.simulator.file_system.file_type import FileType
|
||||
|
||||
|
||||
def test_file_type():
|
||||
"""Tests tha the FileSystemFile type is set correctly."""
|
||||
file = FileSystemFile(name="test", file_type=FileSystemFileType.DOC)
|
||||
assert file.file_type is FileSystemFileType.DOC
|
||||
"""Tests tha the File type is set correctly."""
|
||||
file = File(name="test", file_type=FileType.DOC)
|
||||
assert file.file_type is FileType.DOC
|
||||
|
||||
|
||||
def test_get_size():
|
||||
"""Tests that the file size is being returned properly."""
|
||||
file = FileSystemFile(name="test", size=1.5)
|
||||
file = File(name="test", size=1.5)
|
||||
assert file.size == 1.5
|
||||
|
||||
|
||||
def test_serialisation():
|
||||
"""Test to check that the object serialisation works correctly."""
|
||||
file = FileSystemFile(name="test", size=1.5, file_type=FileSystemFileType.DOC)
|
||||
file = File(name="test", size=1.5, file_type=FileType.DOC)
|
||||
serialised_file = file.model_dump_json()
|
||||
deserialised_file = FileSystemFile.model_validate_json(serialised_file)
|
||||
deserialised_file = File.model_validate_json(serialised_file)
|
||||
|
||||
assert file.model_dump_json() == deserialised_file.model_dump_json()
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
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
|
||||
from primaite.simulator.file_system.file_system import File
|
||||
from primaite.simulator.file_system.file_system_folder import Folder
|
||||
from primaite.simulator.file_system.file_type import FileType
|
||||
|
||||
|
||||
def test_adding_removing_file():
|
||||
"""Test the adding and removing of a file from a folder."""
|
||||
folder = FileSystemFolder(name="test")
|
||||
folder = Folder(name="test")
|
||||
|
||||
file = FileSystemFile(name="test_file", size=10, file_type=FileSystemFileType.DOC)
|
||||
file = File(name="test_file", size=10, file_type=FileType.DOC)
|
||||
|
||||
folder.add_file(file)
|
||||
assert folder.size == 10
|
||||
@@ -20,10 +20,10 @@ def test_adding_removing_file():
|
||||
|
||||
def test_remove_non_existent_file():
|
||||
"""Test the removing of a file that does not exist."""
|
||||
folder = FileSystemFolder(name="test")
|
||||
folder = Folder(name="test")
|
||||
|
||||
file = FileSystemFile(name="test_file", size=10, file_type=FileSystemFileType.DOC)
|
||||
not_added_file = FileSystemFile(name="fake_file", size=10, file_type=FileSystemFileType.DOC)
|
||||
file = File(name="test_file", size=10, file_type=FileType.DOC)
|
||||
not_added_file = File(name="fake_file", size=10, file_type=FileType.DOC)
|
||||
|
||||
folder.add_file(file)
|
||||
assert folder.size == 10
|
||||
@@ -36,10 +36,10 @@ def test_remove_non_existent_file():
|
||||
|
||||
def test_get_file_by_id():
|
||||
"""Test to make sure that the correct file is returned."""
|
||||
folder = FileSystemFolder(name="test")
|
||||
folder = Folder(name="test")
|
||||
|
||||
file = FileSystemFile(name="test_file", size=10, file_type=FileSystemFileType.DOC)
|
||||
file2 = FileSystemFile(name="test_file_2", size=10, file_type=FileSystemFileType.DOC)
|
||||
file = File(name="test_file", size=10, file_type=FileType.DOC)
|
||||
file2 = File(name="test_file_2", size=10, file_type=FileType.DOC)
|
||||
|
||||
folder.add_file(file)
|
||||
folder.add_file(file2)
|
||||
@@ -51,25 +51,25 @@ def test_get_file_by_id():
|
||||
|
||||
def test_folder_quarantine_state():
|
||||
"""Tests the changing of folder quarantine status."""
|
||||
folder = FileSystemFolder(name="test")
|
||||
folder = Folder(name="test")
|
||||
|
||||
assert folder.quarantine_status() is False
|
||||
|
||||
folder.quarantine()
|
||||
assert folder.quarantine_status() is True
|
||||
|
||||
folder.end_quarantine()
|
||||
folder.unquarantine()
|
||||
assert folder.quarantine_status() is False
|
||||
|
||||
|
||||
def test_serialisation():
|
||||
"""Test to check that the object serialisation works correctly."""
|
||||
folder = FileSystemFolder(name="test")
|
||||
file = FileSystemFile(name="test_file", size=10, file_type=FileSystemFileType.DOC)
|
||||
folder = Folder(name="test")
|
||||
file = File(name="test_file", size=10, file_type=FileType.DOC)
|
||||
folder.add_file(file)
|
||||
|
||||
serialised_folder = folder.model_dump_json()
|
||||
|
||||
deserialised_folder = FileSystemFolder.model_validate_json(serialised_folder)
|
||||
deserialised_folder = Folder.model_validate_json(serialised_folder)
|
||||
|
||||
assert folder.model_dump_json() == deserialised_folder.model_dump_json()
|
||||
|
||||
Reference in New Issue
Block a user