264 lines
9.8 KiB
Python
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() |