File size: 5,143 Bytes
d54d7d2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
"""File management for code storage."""

from __future__ import annotations

import logging
import uuid
from pathlib import Path

from app.config import Settings, get_settings
from app.models import FileResult

logger = logging.getLogger(__name__)


class FileManager:
    """Manages code files in the storage directory."""

    def __init__(self, settings: Settings | None = None):
        self._settings = settings or get_settings()
        self._storage = Path(self._settings.code_storage_dir)
        self._storage.mkdir(parents=True, exist_ok=True)

    def _generate_filename(self, base: str | None = None) -> str:
        """Generate a unique filename."""
        suffix = uuid.uuid4().hex[:8]
        base = base or "code"
        # Sanitize
        safe_base = "".join(c for c in base if c.isalnum() or c in ("_", "-"))[:100]
        if not safe_base:
            safe_base = "code"
        return f"{safe_base}_{suffix}.py"

    def _validate_path(self, file_path: str) -> Path | None:
        """Validate that a file path is within the storage directory."""
        try:
            path = Path(file_path).resolve()
            storage = self._storage.resolve()
            if not str(path).startswith(str(storage)):
                return None
            return path
        except (ValueError, OSError):
            return None

    def create_file(self, content: str, filename: str | None = None) -> FileResult:
        """Create a new Python file with content."""
        try:
            if len(content.encode("utf-8")) > self._settings.max_file_size:
                return FileResult(
                    success=False,
                    message=f"Content exceeds maximum file size of {self._settings.max_file_size} bytes",
                )

            name = self._generate_filename(filename)
            file_path = self._storage / name
            file_path.write_text(content, encoding="utf-8")

            logger.info("Created file: %s", file_path)
            return FileResult(
                success=True,
                file_path=str(file_path),
                message=f"File created: {name}",
            )
        except Exception as e:
            logger.exception("Error creating file")
            return FileResult(success=False, message=f"Error creating file: {str(e)}")

    def append_to_file(self, file_path: str, content: str) -> FileResult:
        """Append content to an existing file."""
        try:
            path = self._validate_path(file_path)
            if path is None:
                return FileResult(
                    success=False,
                    file_path=file_path,
                    message="Invalid file path: must be within code storage directory",
                )

            if not path.exists():
                return FileResult(
                    success=False,
                    file_path=file_path,
                    message=f"File not found: {file_path}",
                )

            current_size = path.stat().st_size
            append_size = len(content.encode("utf-8"))
            if current_size + append_size > self._settings.max_file_size:
                return FileResult(
                    success=False,
                    file_path=file_path,
                    message=f"Appending would exceed maximum file size of {self._settings.max_file_size} bytes",
                )

            with path.open("a", encoding="utf-8") as f:
                f.write(content)

            logger.info("Appended to file: %s (%d bytes)", file_path, append_size)
            return FileResult(
                success=True,
                file_path=file_path,
                message=f"Appended {append_size} bytes to file",
            )
        except Exception as e:
            logger.exception("Error appending to file")
            return FileResult(
                success=False,
                file_path=file_path,
                message=f"Error appending to file: {str(e)}",
            )

    def read_file(self, file_path: str) -> FileResult:
        """Read content of an existing file."""
        try:
            path = self._validate_path(file_path)
            if path is None:
                return FileResult(
                    success=False,
                    file_path=file_path,
                    message="Invalid file path: must be within code storage directory",
                )

            if not path.exists():
                return FileResult(
                    success=False,
                    file_path=file_path,
                    message=f"File not found: {file_path}",
                )

            content = path.read_text(encoding="utf-8")
            return FileResult(
                success=True,
                file_path=file_path,
                message="File read successfully",
                content=content,
            )
        except Exception as e:
            logger.exception("Error reading file")
            return FileResult(
                success=False,
                file_path=file_path,
                message=f"Error reading file: {str(e)}",
            )