VeuReu commited on
Commit
90784d2
·
verified ·
1 Parent(s): f0b643a

Update storage/files/file_manager.py

Browse files
Files changed (1) hide show
  1. storage/files/file_manager.py +203 -152
storage/files/file_manager.py CHANGED
@@ -1,152 +1,203 @@
1
- """
2
- file_manager.py
3
-
4
- This module provides the FileManager class, a high-level interface for managing
5
- files and directories inside a specified media folder. It centralizes common
6
- operations such as reading, writing, listing, copying, moving, and deleting files,
7
- ensuring that all actions are safely constrained within the defined root directory.
8
- The class is designed to simplify file handling logic in larger applications by
9
- offering a consistent, validated, and extensible API for filesystem interactions.
10
- """
11
-
12
- from pathlib import Path
13
-
14
- class FileManager:
15
- """
16
- FileManager is a utility class that encapsulates common filesystem operations
17
- within a defined media directory. It ensures that all file manipulations remain
18
- inside the configured root folder and provides helper methods to interact with
19
- files and subdirectories in a structured, safe, and predictable manner.
20
-
21
- Typical use cases include managing uploaded media, performing batch operations
22
- on directory contents, and abstracting filesystem complexity behind a clean API.
23
- """
24
-
25
- def __init__(self, media_folder: Path):
26
- """
27
- Initialize the FileManager with a specific root directory for all file
28
- operations.
29
-
30
- Parameters
31
- ----------
32
- media_folder : Path
33
- The base directory where all file and folder operations will be performed.
34
- It must be a valid filesystem path. If the directory does not exist, the
35
- instance will attempt to create it automatically.
36
-
37
- Raises
38
- ------
39
- ValueError
40
- If the provided media_folder path is not a valid directory or cannot be
41
- created.
42
- """
43
- self.media_folder = Path(media_folder)
44
-
45
- if not self.media_folder.exists():
46
- try:
47
- self.media_folder.mkdir(parents=True)
48
- except Exception as exc:
49
- raise ValueError(
50
- f"Unable to create media folder at: {self.media_folder}"
51
- ) from exc
52
-
53
- if not self.media_folder.is_dir():
54
- raise ValueError(f"Media folder is not a directory: {self.media_folder}")
55
-
56
-
57
- def upload_file(self, file_handler, destination: Path):
58
- """
59
- Upload a file to a target destination inside the media folder.
60
-
61
- This method takes a file-like object and writes its contents to the
62
- specified destination within the media folder. The method ensures the path
63
- remains inside the media folder and creates necessary directories. It
64
- returns a structured response indicating whether the operation succeeded
65
- or failed, along with any relevant error message.
66
-
67
- Parameters
68
- ----------
69
- file_handler : file-like object
70
- A file-like object opened in binary mode that provides a `.read()` method.
71
- destination : Path
72
- The relative or absolute path (within the media folder) where the file
73
- should be saved.
74
-
75
- Returns
76
- -------
77
- dict
78
- A dictionary with:
79
- - "operation_success" (bool): True if the file was saved successfully.
80
- - "error" (str): An empty string on success, or the error message on failure.
81
-
82
- Raises
83
- ------
84
- None
85
- Any exceptions are captured and returned inside the result dictionary.
86
- """
87
- try:
88
- destination = self.media_folder / destination
89
- destination = destination.resolve()
90
-
91
- # Ensure the destination lies inside the media folder
92
- if self.media_folder not in destination.parents:
93
- return {
94
- "operation_success": False,
95
- "error": "Destination path is outside the media folder."
96
- }
97
-
98
- # Create parent directories if needed
99
- destination.parent.mkdir(parents=True, exist_ok=True)
100
-
101
- with open(destination, "wb") as f:
102
- f.write(file_handler.read())
103
-
104
- return {"operation_success": True, "error": ""}
105
-
106
- except Exception as exc:
107
- return {
108
- "operation_success": False,
109
- "error": str(exc)
110
- }
111
-
112
- def get_file(self, file_path: Path):
113
- """
114
- Retrieve a file inside the media folder and return an open file handler.
115
-
116
- This method receives a path pointing to a file expected to be located
117
- inside the media folder. If the file exists and is valid, the method
118
- returns a file handler opened in binary read mode. If the file does not
119
- exist or the resolved path escapes the media folder, the method returns
120
- None.
121
-
122
- Parameters
123
- ----------
124
- file_path : Path
125
- The relative or absolute path to the file within the media folder.
126
-
127
- Returns
128
- -------
129
- file object or None
130
- A file handler opened in 'rb' mode if the file exists and is accessible.
131
- Returns None if the file does not exist or the path is invalid.
132
-
133
- Raises
134
- ------
135
- None
136
- All exceptions are handled internally and result in returning None.
137
- """
138
- try:
139
- target = self.media_folder / file_path
140
- target = target.resolve()
141
-
142
- # Ensure the resolved path is inside the media folder
143
- if self.media_folder not in target.parents:
144
- return None
145
-
146
- if not target.exists() or not target.is_file():
147
- return None
148
-
149
- return open(target, "rb")
150
-
151
- except Exception:
152
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ file_manager.py
3
+
4
+ This module provides the FileManager class, a high-level interface for managing
5
+ files and directories inside a specified media folder. It centralizes common
6
+ operations such as reading, writing, listing, copying, moving, and deleting files,
7
+ ensuring that all actions are safely constrained within the defined root directory.
8
+ The class is designed to simplify file handling logic in larger applications by
9
+ offering a consistent, validated, and extensible API for filesystem interactions.
10
+ """
11
+
12
+ from pathlib import Path
13
+
14
+ class FileManager:
15
+ """
16
+ FileManager is a utility class that encapsulates common filesystem operations
17
+ within a defined media directory. It ensures that all file manipulations remain
18
+ inside the configured root folder and provides helper methods to interact with
19
+ files and subdirectories in a structured, safe, and predictable manner.
20
+
21
+ Typical use cases include managing uploaded media, performing batch operations
22
+ on directory contents, and abstracting filesystem complexity behind a clean API.
23
+ """
24
+
25
+ def __init__(self, media_folder: Path):
26
+ """
27
+ Initialize the FileManager with a specific root directory for all file
28
+ operations.
29
+
30
+ Parameters
31
+ ----------
32
+ media_folder : Path
33
+ The base directory where all file and folder operations will be performed.
34
+ It must be a valid filesystem path. If the directory does not exist, the
35
+ instance will attempt to create it automatically.
36
+
37
+ Raises
38
+ ------
39
+ ValueError
40
+ If the provided media_folder path is not a valid directory or cannot be
41
+ created.
42
+ """
43
+ self.media_folder = Path(media_folder)
44
+
45
+ if not self.media_folder.exists():
46
+ try:
47
+ self.media_folder.mkdir(parents=True)
48
+ except Exception as exc:
49
+ raise ValueError(
50
+ f"Unable to create media folder at: {self.media_folder}"
51
+ ) from exc
52
+
53
+ if not self.media_folder.is_dir():
54
+ raise ValueError(f"Media folder is not a directory: {self.media_folder}")
55
+
56
+
57
+ def upload_file(self, file_handler, destination: Path):
58
+ """
59
+ Upload a file to a target destination inside the media folder.
60
+
61
+ This method takes a file-like object and writes its contents to the
62
+ specified destination within the media folder. The method ensures the path
63
+ remains inside the media folder and creates necessary directories. It
64
+ returns a structured response indicating whether the operation succeeded
65
+ or failed, along with any relevant error message.
66
+
67
+ Parameters
68
+ ----------
69
+ file_handler : file-like object
70
+ A file-like object opened in binary mode that provides a `.read()` method.
71
+ destination : Path
72
+ The relative or absolute path (within the media folder) where the file
73
+ should be saved.
74
+
75
+ Returns
76
+ -------
77
+ dict
78
+ A dictionary with:
79
+ - "operation_success" (bool): True if the file was saved successfully.
80
+ - "error" (str): An empty string on success, or the error message on failure.
81
+
82
+ Raises
83
+ ------
84
+ None
85
+ Any exceptions are captured and returned inside the result dictionary.
86
+ """
87
+ try:
88
+ destination = self.media_folder / destination
89
+ destination = destination.resolve()
90
+
91
+ # Ensure the destination lies inside the media folder
92
+ if self.media_folder not in destination.parents:
93
+ return {
94
+ "operation_success": False,
95
+ "error": "Destination path is outside the media folder."
96
+ }
97
+
98
+ # Create parent directories if needed
99
+ destination.parent.mkdir(parents=True, exist_ok=True)
100
+
101
+ with open(destination, "wb") as f:
102
+ f.write(file_handler.read())
103
+
104
+ return {"operation_success": True, "error": ""}
105
+
106
+ except Exception as exc:
107
+ return {
108
+ "operation_success": False,
109
+ "error": str(exc)
110
+ }
111
+
112
+ def get_file(self, file_path: Path):
113
+ """
114
+ Retrieve a file inside the media folder and return an open file handler.
115
+
116
+ This method receives a path pointing to a file expected to be located
117
+ inside the media folder. If the file exists and is valid, the method
118
+ returns a file handler opened in binary read mode. If the file does not
119
+ exist or the resolved path escapes the media folder, the method returns
120
+ None.
121
+
122
+ Parameters
123
+ ----------
124
+ file_path : Path
125
+ The relative or absolute path to the file within the media folder.
126
+
127
+ Returns
128
+ -------
129
+ file object or None
130
+ A file handler opened in 'rb' mode if the file exists and is accessible.
131
+ Returns None if the file does not exist or the path is invalid.
132
+
133
+ Raises
134
+ ------
135
+ None
136
+ All exceptions are handled internally and result in returning None.
137
+ """
138
+ try:
139
+ target = self.media_folder / file_path
140
+ target = target.resolve()
141
+
142
+ # Ensure the resolved path is inside the media folder
143
+ if self.media_folder not in target.parents:
144
+ return None
145
+
146
+ if not target.exists() or not target.is_file():
147
+ return None
148
+
149
+ return open(target, "rb")
150
+
151
+ except Exception:
152
+ return None
153
+
154
+ def compute_sha1(self, file_handler):
155
+ """
156
+ Compute the SHA-1 checksum of a file handler.
157
+
158
+ This method calculates the SHA-1 hash of the content provided by a
159
+ file-like object opened in binary mode. The method preserves the
160
+ current file pointer position by saving it, rewinding to the start
161
+ of the file, computing the hash, and restoring the file pointer to
162
+ its original position.
163
+
164
+ Parameters
165
+ ----------
166
+ file_handler : file-like object
167
+ A file-like object opened in binary mode, providing `.read()` and
168
+ `.seek()` methods.
169
+
170
+ Returns
171
+ -------
172
+ str
173
+ The hexadecimal SHA-1 checksum of the file content.
174
+
175
+ Raises
176
+ ------
177
+ ValueError
178
+ If the provided file handler is invalid or does not support the
179
+ required read/seek operations.
180
+ """
181
+ import hashlib
182
+
183
+ if not hasattr(file_handler, "read") or not hasattr(file_handler, "seek"):
184
+ raise ValueError("The provided file handler does not support read/seek operations.")
185
+
186
+ original_pos = file_handler.tell()
187
+
188
+ try:
189
+ file_handler.seek(0)
190
+ sha1 = hashlib.sha1()
191
+
192
+ for chunk in iter(lambda: file_handler.read(8192), b""):
193
+ sha1.update(chunk)
194
+
195
+ file_handler.seek(original_pos)
196
+ return sha1.hexdigest()
197
+
198
+ except Exception as exc:
199
+ try:
200
+ file_handler.seek(original_pos)
201
+ except Exception:
202
+ pass
203
+ raise ValueError(f"Unable to compute SHA-1 checksum: {exc}") from exc