diff --git a/application/storage/local.py b/application/storage/local.py index b4530501..89b49bee 100644 --- a/application/storage/local.py +++ b/application/storage/local.py @@ -114,3 +114,27 @@ class LocalStorage(BaseStorage): """ full_path = self._get_full_path(path) return os.path.isdir(full_path) + + def remove_directory(self, directory: str) -> bool: + """ + Remove a directory and all its contents from local storage. + + Args: + directory: Directory path to remove + + Returns: + bool: True if removal was successful, False otherwise + """ + full_path = self._get_full_path(directory) + + if not os.path.exists(full_path): + return False + + if not os.path.isdir(full_path): + return False + + try: + shutil.rmtree(full_path) + return True + except (OSError, PermissionError) as e: + return False diff --git a/application/storage/s3.py b/application/storage/s3.py index 36333f1c..31be844c 100644 --- a/application/storage/s3.py +++ b/application/storage/s3.py @@ -134,24 +134,73 @@ class S3Storage(BaseStorage): def is_directory(self, path: str) -> bool: """ Check if a path is a directory in S3 storage. - + In S3, directories are virtual concepts. A path is considered a directory if there are objects with the path as a prefix. - + Args: path: Path to check - + Returns: bool: True if the path is a directory, False otherwise """ # Ensure path ends with a slash if not empty if path and not path.endswith('/'): path += '/' - + response = self.s3.list_objects_v2( Bucket=self.bucket_name, Prefix=path, MaxKeys=1 ) - + return 'Contents' in response + + def remove_directory(self, directory: str) -> bool: + """ + Remove a directory and all its contents from S3 storage. + + In S3, this removes all objects with the directory path as a prefix. + Since S3 doesn't have actual directories, this effectively removes + all files within the virtual directory structure. + + Args: + directory: Directory path to remove + + Returns: + bool: True if removal was successful, False otherwise + """ + # Ensure directory ends with a slash if not empty + if directory and not directory.endswith('/'): + directory += '/' + + try: + # Get all objects with the directory prefix + objects_to_delete = [] + paginator = self.s3.get_paginator('list_objects_v2') + pages = paginator.paginate(Bucket=self.bucket_name, Prefix=directory) + + for page in pages: + if 'Contents' in page: + for obj in page['Contents']: + objects_to_delete.append({'Key': obj['Key']}) + + if not objects_to_delete: + return False + + batch_size = 1000 + for i in range(0, len(objects_to_delete), batch_size): + batch = objects_to_delete[i:i + batch_size] + + response = self.s3.delete_objects( + Bucket=self.bucket_name, + Delete={'Objects': batch} + ) + + if 'Errors' in response and response['Errors']: + return False + + return True + + except ClientError: + return False