main
py 622 lines 17.6 KB
Raw
1 import os
2 import shutil
3 import fnmatch
4 import base64
5 import tempfile
6 import zipfile
7 from helpers import runtime
8
9
10 def get_abs_path(*relative_paths):
11 """Convert relative paths to absolute paths based on the base directory."""
12 if not relative_paths:
13 return os.path.abspath(os.path.dirname(__file__) + "/../..")
14
15 base_dir = os.path.abspath(os.path.dirname(__file__) + "/../..")
16 return os.path.join(base_dir, *relative_paths)
17
18
19 # =====================================================
20 # RFC-ENABLED FILESYSTEM OPERATIONS
21 # =====================================================
22
23 def read_file_bin(relative_path: str, backup_dirs=None) -> bytes:
24 """
25 Read binary file content.
26
27 Args:
28 relative_path: Path to the file relative to base directory
29 backup_dirs: List of backup directories to search in
30
31 Returns:
32 File content as bytes
33 """
34 if backup_dirs is None:
35 backup_dirs = []
36
37 # Find the file in directories
38 absolute_path = find_file_in_dirs(relative_path, backup_dirs)
39
40 # Use RFC routing for development mode
41 b64_content = runtime.call_development_function_sync(
42 _read_file_binary_impl, absolute_path
43 )
44 return base64.b64decode(b64_content)
45
46
47 def read_file_base64(relative_path: str, backup_dirs=None) -> str:
48 """
49 Read file content and return as base64 string.
50
51 Args:
52 relative_path: Path to the file relative to base directory
53 backup_dirs: List of backup directories to search in
54
55 Returns:
56 File content as base64 encoded string
57 """
58 if backup_dirs is None:
59 backup_dirs = []
60
61 # Find the file in directories
62 absolute_path = find_file_in_dirs(relative_path, backup_dirs)
63
64 # Use RFC routing for development mode
65 return runtime.call_development_function_sync(
66 _read_file_as_base64_impl, absolute_path
67 )
68
69
70 def write_file_binary(relative_path: str, content: bytes) -> bool:
71 """
72 Write binary content to a file.
73
74 Args:
75 relative_path: Path to the file relative to base directory
76 content: Binary content to write
77
78 Returns:
79 True if successful
80 """
81 abs_path = get_abs_path(relative_path)
82
83 # Use RFC routing for development mode
84 b64_content = base64.b64encode(content).decode('utf-8')
85 return runtime.call_development_function_sync(
86 _write_file_binary_impl, abs_path, b64_content
87 )
88
89
90 def write_file_base64(relative_path: str, content: str) -> bool:
91 """
92 Write base64 content to a file.
93
94 Args:
95 relative_path: Path to the file relative to base directory
96 content: Base64 encoded content to write
97
98 Returns:
99 True if successful
100 """
101 abs_path = get_abs_path(relative_path)
102
103 # Use RFC routing for development mode
104 return runtime.call_development_function_sync(
105 _write_file_from_base64_impl, abs_path, content
106 )
107
108
109 def delete_file(relative_path: str) -> bool:
110 """
111 Delete a file.
112
113 Args:
114 relative_path: Path to the file relative to base directory
115
116 Returns:
117 True if successful
118 """
119 abs_path = get_abs_path(relative_path)
120
121 # Use RFC routing for development mode
122 return runtime.call_development_function_sync(
123 _delete_file_impl, abs_path
124 )
125
126
127 def delete_directory(relative_path: str) -> bool:
128 """
129 Delete a directory recursively.
130
131 Args:
132 relative_path: Path to the directory relative to base directory
133
134 Returns:
135 True if successful
136 """
137 abs_path = get_abs_path(relative_path)
138
139 # Use RFC routing for development mode
140 return runtime.call_development_function_sync(
141 _delete_folder_impl, abs_path
142 )
143
144
145 def list_directory(relative_path: str, include_hidden: bool = False) -> list:
146 """
147 List directory contents.
148
149 Args:
150 relative_path: Path to the directory relative to base directory
151 include_hidden: Whether to include hidden files/folders
152
153 Returns:
154 List of directory items with metadata
155 """
156 abs_path = get_abs_path(relative_path)
157
158 # Use RFC routing for development mode
159 return runtime.call_development_function_sync(
160 _list_folder_impl, abs_path, include_hidden
161 )
162
163
164 def make_directories(relative_path: str) -> bool:
165 """
166 Create directories recursively.
167
168 Args:
169 relative_path: Path to create relative to base directory
170
171 Returns:
172 True if successful
173 """
174 abs_path = get_abs_path(relative_path)
175
176 # Use RFC routing for development mode
177 return runtime.call_development_function_sync(
178 _make_dirs_impl, abs_path
179 )
180
181
182 def path_exists(relative_path: str) -> bool:
183 """
184 Check if a path exists.
185
186 Args:
187 relative_path: Path to check relative to base directory
188
189 Returns:
190 True if path exists
191 """
192 abs_path = get_abs_path(relative_path)
193
194 # Use RFC routing for development mode
195 return runtime.call_development_function_sync(
196 _path_exists_impl, abs_path
197 )
198
199
200 def file_exists(relative_path: str) -> bool:
201 """
202 Check if a file exists.
203
204 Args:
205 relative_path: Path to check relative to base directory
206
207 Returns:
208 True if file exists
209 """
210 abs_path = get_abs_path(relative_path)
211
212 # Use RFC routing for development mode
213 return runtime.call_development_function_sync(
214 _file_exists_impl, abs_path
215 )
216
217
218 def folder_exists(relative_path: str) -> bool:
219 """
220 Check if a folder exists.
221
222 Args:
223 relative_path: Path to check relative to base directory
224
225 Returns:
226 True if folder exists
227 """
228 abs_path = get_abs_path(relative_path)
229
230 # Use RFC routing for development mode
231 return runtime.call_development_function_sync(
232 _folder_exists_impl, abs_path
233 )
234
235
236 def get_subdirectories(relative_path: str, include: str | list[str] = "*", exclude: str | list[str] | None = None) -> list[str]:
237 """
238 Get subdirectories in a directory.
239
240 Args:
241 relative_path: Path to the directory relative to base directory
242 include: Pattern(s) to include
243 exclude: Pattern(s) to exclude
244
245 Returns:
246 List of subdirectory names
247 """
248 abs_path = get_abs_path(relative_path)
249
250 # Use RFC routing for development mode
251 return runtime.call_development_function_sync(
252 _get_subdirectories_impl, abs_path, include, exclude
253 )
254
255
256 def zip_directory(relative_path: str) -> str:
257 """
258 Create a zip archive of a directory.
259
260 Args:
261 relative_path: Path to the directory relative to base directory
262
263 Returns:
264 Path to the created zip file
265 """
266 abs_path = get_abs_path(relative_path)
267
268 # Use RFC routing for development mode
269 return runtime.call_development_function_sync(
270 _zip_dir_impl, abs_path
271 )
272
273
274 def move_file(source_path: str, destination_path: str) -> bool:
275 """
276 Move a file from source to destination.
277
278 Args:
279 source_path: Source path relative to base directory
280 destination_path: Destination path relative to base directory
281
282 Returns:
283 True if successful
284 """
285 source_abs = get_abs_path(source_path)
286 dest_abs = get_abs_path(destination_path)
287
288 # Use RFC routing for development mode
289 return runtime.call_development_function_sync(
290 _move_file_impl, source_abs, dest_abs
291 )
292
293
294 def read_directory_as_zip(relative_path: str) -> bytes:
295 """
296 Read entire directory contents as a zip file.
297
298 Args:
299 relative_path: Path to the directory relative to base directory
300
301 Returns:
302 Zip file content as bytes
303 """
304 abs_path = get_abs_path(relative_path)
305
306 # Use RFC routing for development mode
307 b64_zip = runtime.call_development_function_sync(
308 _read_directory_impl, abs_path
309 )
310 return base64.b64decode(b64_zip)
311
312
313 def find_file_in_dirs(file_path: str, backup_dirs: list[str]) -> str:
314 """
315 Find a file in the main directory or backup directories.
316
317 Args:
318 file_path: Relative file path to search for
319 backup_dirs: List of backup directories to search in
320
321 Returns:
322 Absolute path to the found file
323
324 Raises:
325 FileNotFoundError: If file is not found in any directory
326 """
327 # Try the main path first
328 main_path = get_abs_path(file_path)
329 if runtime.call_development_function_sync(_file_exists_impl, main_path):
330 return main_path
331
332 # Try backup directories
333 for backup_dir in backup_dirs:
334 backup_path = os.path.join(backup_dir, file_path)
335 if runtime.call_development_function_sync(_file_exists_impl, backup_path):
336 return backup_path
337
338 # File not found anywhere
339 raise FileNotFoundError(f"File not found: {file_path}")
340
341
342 # =====================================================
343 # IMPLEMENTATION FUNCTIONS (Container Operations)
344 # =====================================================
345
346 def _read_file_binary_impl(file_path: str) -> str:
347 """
348 Implementation function to read a file in binary mode.
349 Returns base64 encoded content for RFC transport.
350 """
351 if not os.path.exists(file_path):
352 raise FileNotFoundError(f"File not found: {file_path}")
353
354 if not os.path.isfile(file_path):
355 raise Exception(f"Path is not a file: {file_path}")
356
357 try:
358 with open(file_path, 'rb') as file:
359 content = file.read()
360 return base64.b64encode(content).decode('utf-8')
361 except Exception as e:
362 raise Exception(f"Failed to read file {file_path}: {str(e)}")
363
364
365 def _write_file_binary_impl(file_path: str, b64_content: str) -> bool:
366 """
367 Implementation function to write binary content to a file.
368 Expects base64 encoded content from RFC transport.
369 """
370 try:
371 # Ensure b64_content is properly UTF-8 encoded before base64 decoding
372 if isinstance(b64_content, str):
373 b64_content_bytes = b64_content.encode('utf-8')
374 else:
375 b64_content_bytes = b64_content
376
377 # Decode base64 content
378 content = base64.b64decode(b64_content_bytes)
379
380 # Create directory if it doesn't exist
381 os.makedirs(os.path.dirname(file_path), exist_ok=True)
382
383 # Write file
384 with open(file_path, 'wb') as file:
385 file.write(content)
386
387 return True
388 except Exception as e:
389 raise Exception(f"Failed to write file {file_path}: {str(e)}")
390
391
392 def _delete_file_impl(file_path: str) -> bool:
393 """
394 Implementation function to delete a file.
395 """
396 if not os.path.exists(file_path):
397 raise FileNotFoundError(f"File not found: {file_path}")
398
399 if not os.path.isfile(file_path):
400 raise Exception(f"Path is not a file: {file_path}")
401
402 try:
403 os.remove(file_path)
404 return True
405 except Exception as e:
406 raise Exception(f"Failed to delete file {file_path}: {str(e)}")
407
408
409 def _delete_folder_impl(folder_path: str) -> bool:
410 """
411 Implementation function to delete a folder recursively.
412 """
413 if not os.path.exists(folder_path):
414 raise FileNotFoundError(f"Folder not found: {folder_path}")
415
416 if not os.path.isdir(folder_path):
417 raise Exception(f"Path is not a directory: {folder_path}")
418
419 try:
420 shutil.rmtree(folder_path)
421 return True
422 except Exception as e:
423 raise Exception(f"Failed to delete folder {folder_path}: {str(e)}")
424
425
426 def _list_folder_impl(folder_path: str, include_hidden: bool = False) -> list:
427 """
428 Implementation function to list folder contents.
429 """
430 if not os.path.exists(folder_path):
431 raise FileNotFoundError(f"Folder not found: {folder_path}")
432
433 if not os.path.isdir(folder_path):
434 raise Exception(f"Path is not a directory: {folder_path}")
435
436 try:
437 items = []
438 for item_name in os.listdir(folder_path):
439 # Skip hidden files if not requested
440 if not include_hidden and item_name.startswith('.'):
441 continue
442
443 item_path = os.path.join(folder_path, item_name)
444 stat_info = os.stat(item_path)
445
446 item_info = {
447 "name": item_name,
448 "path": item_path,
449 "is_file": os.path.isfile(item_path),
450 "is_dir": os.path.isdir(item_path),
451 "size": stat_info.st_size,
452 "modified": stat_info.st_mtime
453 }
454 items.append(item_info)
455
456 # Sort by name for consistent output
457 items.sort(key=lambda x: str(x["name"]).lower())
458 return items
459
460 except Exception as e:
461 raise Exception(f"Failed to list folder {folder_path}: {str(e)}")
462
463
464 def _make_dirs_impl(folder_path: str) -> bool:
465 """
466 Implementation function to create directories.
467 """
468 try:
469 os.makedirs(folder_path, exist_ok=True)
470 return True
471 except Exception as e:
472 raise Exception(f"Failed to create directories {folder_path}: {str(e)}")
473
474
475 def _path_exists_impl(file_path: str) -> bool:
476 """Implementation function to check if path exists."""
477 return os.path.exists(file_path)
478
479
480 def _file_exists_impl(file_path: str) -> bool:
481 """Implementation function to check if file exists."""
482 return os.path.exists(file_path) and os.path.isfile(file_path)
483
484
485 def _folder_exists_impl(folder_path: str) -> bool:
486 """Implementation function to check if folder exists."""
487 return os.path.exists(folder_path) and os.path.isdir(folder_path)
488
489
490 def _get_subdirectories_impl(folder_path: str, include: str | list[str], exclude: str | list[str] | None) -> list[str]:
491 """
492 Implementation function to get subdirectories.
493 """
494 if not os.path.exists(folder_path):
495 return []
496
497 if isinstance(include, str):
498 include = [include]
499 if isinstance(exclude, str):
500 exclude = [exclude]
501
502 return [
503 subdir
504 for subdir in os.listdir(folder_path)
505 if os.path.isdir(os.path.join(folder_path, subdir))
506 and any(fnmatch.fnmatch(subdir, inc) for inc in include)
507 and (exclude is None or not any(fnmatch.fnmatch(subdir, exc) for exc in exclude))
508 ]
509
510
511 def _zip_dir_impl(folder_path: str) -> str:
512 """
513 Implementation function to create a zip archive of a directory.
514 """
515 zip_file_path = tempfile.NamedTemporaryFile(suffix=".zip", delete=False).name
516 base_name = os.path.basename(folder_path)
517
518 with zipfile.ZipFile(zip_file_path, "w", compression=zipfile.ZIP_DEFLATED) as zip_file:
519 for root, _, files in os.walk(folder_path):
520 for file in files:
521 file_path = os.path.join(root, file)
522 rel_path = os.path.relpath(file_path, folder_path)
523 zip_file.write(file_path, os.path.join(base_name, rel_path))
524
525 return zip_file_path
526
527
528 def _move_file_impl(source_path: str, destination_path: str) -> bool:
529 """
530 Implementation function to move a file.
531 """
532 try:
533 os.makedirs(os.path.dirname(destination_path), exist_ok=True)
534 os.rename(source_path, destination_path)
535 return True
536 except Exception as e:
537 raise Exception(f"Failed to move file {source_path} to {destination_path}: {str(e)}")
538
539
540 def _read_directory_impl(dir_path: str) -> str:
541 """
542 Implementation function to zip a directory and return base64 encoded zip.
543 """
544 if not os.path.exists(dir_path):
545 raise FileNotFoundError(f"Directory not found: {dir_path}")
546
547 if not os.path.isdir(dir_path):
548 raise Exception(f"Path is not a directory: {dir_path}")
549
550 temp_zip_path = None
551 try:
552 # Create temporary zip file
553 with tempfile.NamedTemporaryFile(suffix='.zip', delete=False) as temp_zip:
554 temp_zip_path = temp_zip.name
555
556 # Create zip archive
557 with zipfile.ZipFile(temp_zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
558 for root, dirs, files in os.walk(dir_path):
559 for file in files:
560 file_path = os.path.join(root, file)
561 arcname = os.path.relpath(file_path, dir_path)
562 zipf.write(file_path, arcname)
563
564 # Read zip file and encode as base64
565 with open(temp_zip_path, 'rb') as zipf:
566 zip_content = zipf.read()
567 b64_zip = base64.b64encode(zip_content).decode('utf-8')
568
569 # Clean up temporary file
570 os.unlink(temp_zip_path)
571
572 return b64_zip
573
574 except Exception as e:
575 # Clean up temporary file if it exists
576 if temp_zip_path is not None and os.path.exists(temp_zip_path):
577 os.unlink(temp_zip_path)
578 raise Exception(f"Failed to zip directory {dir_path}: {str(e)}")
579
580
581 def _read_file_as_base64_impl(file_path: str) -> str:
582 """
583 Implementation function to read a file and return its content as base64.
584 """
585 if not os.path.exists(file_path):
586 raise FileNotFoundError(f"File not found: {file_path}")
587
588 if not os.path.isfile(file_path):
589 raise Exception(f"Path is not a file: {file_path}")
590
591 try:
592 with open(file_path, 'rb') as file:
593 content = file.read()
594 return base64.b64encode(content).decode('utf-8')
595 except Exception as e:
596 raise Exception(f"Failed to read file {file_path}: {str(e)}")
597
598
599 def _write_file_from_base64_impl(file_path: str, content: str) -> bool:
600 """
601 Implementation function to write base64 content to a file.
602 """
603 try:
604 # Ensure content is properly UTF-8 encoded before base64 decoding
605 if isinstance(content, str):
606 content_bytes = content.encode('utf-8')
607 else:
608 content_bytes = content
609
610 # Decode base64 content
611 decoded_content = base64.b64decode(content_bytes)
612
613 # Create directory if it doesn't exist
614 os.makedirs(os.path.dirname(file_path), exist_ok=True)
615
616 # Write file
617 with open(file_path, 'wb') as file:
618 file.write(decoded_content)
619
620 return True
621 except Exception as e:
622 raise Exception(f"Failed to write file {file_path}: {str(e)}")