264 lines
9.8 KiB
Python

import streamlit as st
from pathlib import Path
WORKSPACE = Path("workspace")
WORKSPACE.mkdir(exist_ok=True)
class FileManager:
def __init__(self, base_path=Path("workspace")) -> None:
self.base_path = Path(base_path)
self.base_path.mkdir(exist_ok=True)
def create_folder(self, relative_path: str, name: str) -> bool:
"""
Creates a new folder at the specified relative path.
The relative_path should be the path to the folder relative to the base path,
and name should be the name of the new folder (without any slashes).
Args:
relative_path (str): The relative path (without base path) where the new folder should be created.
name (str): The name of the new folder to create (should not contain slashes).
Returns:
bool: True if folder was created successfully, False otherwise.
"""
if not name:
st.error(f"Invalid folder name: {name}")
return False
if "/" in name or "\\" in name:
st.error(f"Invalid folder name (no slashes allowed): {name}")
return False
name = Path(name)
if relative_path:
relative_path = Path(relative_path)
else:
relative_path = Path()
folder_path = (self.base_path / relative_path / name).resolve()
if not str(folder_path).startswith(str(self.base_path.resolve())):
st.error(f"Access denied: {relative_path}")
return False
try:
folder_path.mkdir(exist_ok=False)
return True
except FileExistsError:
st.warning(f"Folder already exists: {relative_path}")
return False
except Exception as e:
st.error(f"Error creating folder {relative_path}: {str(e)}")
return False
def create_file(self, relative_path: str, name: str) -> bool:
"""
Creates a new file at the specified relative path.
The relative_path should be the path to the folder relative to the base path where the file should be created,
and name should be the name of the new file (without any slashes).
Args:
relative_path (str): The relative path (without base path) where the new file should be created.
name (str): The name of the new file to create (should not contain slashes).
Returns:
bool: True if file was created successfully, False otherwise.
"""
if not name or name.strip() == "" :
st.error(f"Invalid file name: {name}")
return False
name = Path(name)
if not name.suffix:
name = name.with_suffix(".txt") # Default to .txt if no extension provided
if relative_path:
relative_path = Path(relative_path)
else:
relative_path = Path()
file_path = (self.base_path / relative_path / name).resolve()
if not str(file_path).startswith(str(self.base_path.resolve())):
st.error(f"Access denied: {relative_path}")
return False
try:
file_path.touch(exist_ok=False)
return True
except FileExistsError:
st.warning(f"File already exists: {relative_path}")
return False
except Exception as e:
st.error(f"Error creating file {relative_path}: {str(e)}")
return False
def read_file(self, relative_path: Path) -> str:
"""
Reads the content of a file.
The relative_path should be the path to the file relative to the base path.
Args:
relative_path (str): The relative path (without base path) to the file to read, including the file name
Returns:
str: The content of the file, or an empty string if there was an error.
"""
file_path = (relative_path).resolve()
if not file_path.exists():
st.error(f"File not found: {relative_path}")
return ""
if not file_path.is_file():
st.error(f"Path is not a file: {relative_path}")
return ""
if not str(file_path).startswith(str(self.base_path.resolve())):
st.error(f"Access denied: {relative_path}")
return ""
try:
with open(file_path, "r") as f:
return f.read()
except FileNotFoundError:
st.error(f"File not found: {relative_path}")
return ""
except Exception as e:
st.error(f"Error reading file {relative_path}: {str(e)}")
return ""
def save_file(self, relative_path: str, content: str) -> bool:
"""
Saves content to a file.
The relative_path should be the path to the file relative to the base path.
Args:
relative_path (str): The relative path(without base path) to the file to save, including the file name
content (str): The content to write to the file
Returns:
bool: True if save was successful, False otherwise.
"""
file_path = (Path(relative_path)).resolve()
if not str(file_path).startswith(str(self.base_path.resolve())):
st.error(f"Access denied: {relative_path}")
return False
try:
with open(file_path, "w") as f:
f.write(content)
return True
except Exception as e:
st.error(f"Error saving file {relative_path}: {str(e)}")
return False
def rename_file(self, old_relative_path: str, new_name: str) -> bool:
"""
Renames a file while keeping the same extension.
The new_name should not include the extension, as it will be preserved from the old name.
Args:
old_relative_path (str): The current relative path (without base path) of the file to rename, including the file name.
new_name (str): The new name for the file, without extension.
Returns:
bool: True if rename was successful, False otherwise.
"""
if not new_name or new_name.strip() == "":
st.error(f"Invalid file name: {new_name}")
return False
file_type = Path(old_relative_path).suffix
new_name = Path(new_name)
if not Path(new_name).suffix == file_type:
new_name = Path(new_name).with_suffix(file_type) # Ensure the file extension remains the same
old_file_path = (Path(self.base_path / old_relative_path)).resolve()
new_file_path = old_file_path.parent / new_name
if not str(old_file_path).startswith(str(self.base_path.resolve())) or not str(new_file_path).startswith(str(self.base_path.resolve())):
st.error(f"Access denied: {old_relative_path}")
return False
try:
old_file_path.rename(new_file_path)
return True
except FileNotFoundError:
st.error(f"File not found: {old_relative_path}")
return False
def delete_folder(self, relative_path: str) -> bool:
"""Deletes a folder and all its contents.
The relative_path should be the path to the folder relative to the base path.
Args:
relative_path (str): The relative path (without base path) to the folder to delete.
Returns:
bool: True if deletion was successful, False otherwise.
"""
folder_path = (self.base_path / relative_path).resolve()
if not str(folder_path).startswith(str(self.base_path.resolve())):
st.error(f"Access denied: {relative_path}")
return False
if not folder_path.exists():
st.error(f"Folder not found: {relative_path}")
return False
try:
import shutil
shutil.rmtree(folder_path)
return True
except Exception as e:
st.error(f"Error deleting folder {relative_path}: {str(e)}")
return False
def delete_file(self, relative_path: str) -> bool:
"""Deletes a file.
The relative_path should be the path to the file relative to the base path.
Args:
relative_path (str): The relative path (without base path) to the file to delete, including the file name.
Returns:
bool: True if deletion was successful, False otherwise.
"""
file_path = Path(relative_path)
abs_file_path = (Path(self.base_path) / file_path).resolve()
print(f"Absolute file path resolved to: {abs_file_path}") # Debugging info
if not str(abs_file_path).startswith(str(self.base_path.resolve())):
st.error(f"Access denied: {relative_path}")
return False
try:
abs_file_path.unlink()
return True
except FileNotFoundError:
st.error(f"File not found: {relative_path}")
return False
except Exception as e:
st.error(f"Error deleting file {relative_path}: {str(e)}")
return False
def get_file_tree(self):
"""
Builds a nested dictionary representing the file tree starting from the base path.
Directories are represented as keys with dictionary values,
and files are represented as keys with None
Returns:
dict: A nested dictionary representing the file tree.
"""
def build_tree(path: Path):
tree = {}
for item in sorted(path.iterdir()):
if item.is_dir():
tree[item.name] = build_tree(item)
else:
tree[item.name] = None
return tree
return build_tree(self.base_path)
if __name__ == "__main__":
FileManager()