diff options
| author | Bobby <[email protected]> | 2024-12-24 06:50:59 -0500 |
|---|---|---|
| committer | Bobby <[email protected]> | 2024-12-24 06:50:59 -0500 |
| commit | 6df9f0dc40501e8f55bcc883dfe5be65e60d3c3d (patch) | |
| tree | 273148d564c11ae46e9f96c0231ce57427f5591f /internal | |
| parent | 4feba2452a151ed999d52d4a0d53b0b0584bf70e (diff) | |
| download | thatcomputerscientist-6df9f0dc40501e8f55bcc883dfe5be65e60d3c3d.tar.xz thatcomputerscientist-6df9f0dc40501e8f55bcc883dfe5be65e60d3c3d.zip | |
bucket load of things
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/admin_utilities.py | 145 | ||||
| -rw-r--r-- | internal/cache_utils.py | 118 |
2 files changed, 263 insertions, 0 deletions
diff --git a/internal/admin_utilities.py b/internal/admin_utilities.py new file mode 100644 index 00000000..c8014fe1 --- /dev/null +++ b/internal/admin_utilities.py @@ -0,0 +1,145 @@ +from typing import List, Dict, Union +from io import BytesIO +from uuid import uuid4 +from django.conf import settings +from minio import Minio +from minio.error import S3Error + + +class MinioFileManager: + def __init__(self): + """Initialize MinIO client with settings credentials.""" + self.client = Minio( + endpoint=settings.MINIO_ENDPOINT, + access_key=settings.MINIO_ACCESS_KEY, + secret_key=settings.MINIO_SECRET_KEY, + secure=getattr(settings, "MINIO_SECURE", True), + ) + + def list_buckets(self) -> List[Dict[str, str]]: + """List all buckets with creation dates.""" + try: + buckets = self.client.list_buckets() + return [ + { + "name": bucket.name, + "created": ( + bucket.creation_date.isoformat() + if bucket.creation_date + else None + ), + } + for bucket in buckets + ] + except S3Error: + return [] + + def list_objects( + self, bucket: str, prefix: str = "" + ) -> List[Dict[str, Union[str, bool]]]: + """List objects in a directory within bucket.""" + try: + if not self.client.bucket_exists(bucket): + return [] + + # Ensure prefix ends with slash for directory listing + prefix = prefix.rstrip("/") + "/" if prefix else "" + objects = self.client.list_objects(bucket, prefix=prefix, recursive=False) + + result = [] + seen_prefixes = set() + + for obj in objects: + # Skip the directory object itself + if obj.object_name == prefix: + continue + + # Get relative path from prefix + name = obj.object_name[len(prefix) :] + if not name: + continue + + # Handle nested directories + if "/" in name: + dir_name = name.split("/")[0] + "/" + if dir_name not in seen_prefixes: + seen_prefixes.add(dir_name) + result.append( + { + "name": dir_name.rstrip("/"), + "path": (prefix + dir_name).rstrip("/"), + "type": "folder", + "size": 0, + "last_modified": None, + } + ) + else: + result.append( + { + "name": name, + "path": obj.object_name, + "type": "file", + "size": obj.size, + "last_modified": ( + obj.last_modified.isoformat() + if obj.last_modified + else None + ), + } + ) + + return result + except S3Error as e: + print(f"MinIO Error: {e}") + return [] + + def create_folder(self, bucket: str, folder_path: str) -> bool: + """Create a folder in the specified bucket.""" + try: + if not self.client.bucket_exists(bucket): + self.client.make_bucket(bucket) + + folder_path = folder_path.rstrip("/") + "/" + self.client.put_object(bucket, folder_path, BytesIO(b""), 0) + return True + except S3Error: + return False + + def upload_file(self, bucket: str, file_path: str, object_name: str = None) -> bool: + """Upload a file to MinIO.""" + try: + if not self.client.bucket_exists(bucket): + self.client.make_bucket(bucket) + + object_name = object_name or str(uuid4()) + self.client.fput_object(bucket, object_name, file_path) + return True + except S3Error: + return False + + def delete_object( + self, bucket: str, object_path: str, recursive: bool = False + ) -> bool: + """Delete a file or folder from bucket.""" + try: + if recursive and not object_path.endswith("/"): + object_path += "/" + objects = self.client.list_objects( + bucket, prefix=object_path, recursive=True + ) + for obj in objects: + self.client.remove_object(bucket, obj.object_name) + + self.client.remove_object(bucket, object_path) + return True + except S3Error: + return False + + def generate_presigned_url( + self, bucket: str, object_name: str, expiry: int = 3600 + ) -> str: + """Generate a presigned URL for file access.""" + try: + return self.client.presigned_get_object(bucket, object_name, expiry) + except S3Error: + return "" diff --git a/internal/cache_utils.py b/internal/cache_utils.py new file mode 100644 index 00000000..7be70e57 --- /dev/null +++ b/internal/cache_utils.py @@ -0,0 +1,118 @@ +from django.core.cache import cache +from django_redis import get_redis_connection +from functools import wraps +import hashlib +import logging + +logger = logging.getLogger(__name__) + + +def generate_cache_key(*args, prefix="", **kwargs): + """Generate a unique cache key based on args and kwargs""" + # Sort kwargs to ensure consistent key generation + sorted_kwargs = sorted(kwargs.items()) + + # Convert all arguments to strings and join them + key_parts = [str(arg) for arg in args] + [f"{k}:{v}" for k, v in sorted_kwargs] + key_string = ":".join(key_parts) + + # Create an MD5 hash of the key string to ensure safe key length + hash_object = hashlib.md5(key_string.encode()) + hashed_key = hash_object.hexdigest() + + return f"{prefix}:{hashed_key}" if prefix else hashed_key + + +def safe_cache_get(key): + """Safely get data from cache with error handling""" + try: + return cache.get(key) + except Exception as e: + logger.error(f"Cache get error for key {key}: {str(e)}") + return None + + +def safe_cache_set(key, value, timeout=None): + """Safely set data in cache with error handling""" + try: + cache.set(key, value, timeout) + return True + except Exception as e: + logger.error(f"Cache set error for key {key}: {str(e)}") + return False + + +def cache_data(prefix, timeout=60 * 15): + """ + Generic cache decorator that can be used for any function. + + Args: + prefix (str): Prefix for the cache key (e.g., 'anime_data', 'streaming_data') + timeout (int): Cache timeout in seconds + """ + + def decorator(func): + @wraps(func) + def wrapper(*args, **kwargs): + # Generate cache key using all arguments + cache_key = generate_cache_key(*args, prefix=prefix, **kwargs) + + # Try to get from cache + cached_data = safe_cache_get(cache_key) + if cached_data is not None: + return cached_data + + try: + # Get fresh data + result = func(*args, **kwargs) + # Cache the result + safe_cache_set(cache_key, result, timeout) + return result + except Exception as e: + logger.error( + f"Error in {prefix} for args {args}, kwargs {kwargs}: {str(e)}" + ) + # On error, return fresh data without caching + return func(*args, **kwargs) + + return wrapper + + return decorator + + +def clear_cache(pattern=None, prefix=None): + """ + Clear cache entries based on a pattern or prefix. + + Args: + pattern (str): Direct Redis key pattern to match (e.g., '*anime_id*') + prefix (str): Cache prefix to clear (e.g., 'anime_data') + """ + try: + redis_conn = get_redis_connection("default") + + if not pattern and not prefix: + # Clear all known prefixes if neither pattern nor prefix specified + prefixes = ["anime_data:*", "streaming_data:*", "search_results:*"] + total_cleared = 0 + for p in prefixes: + keys = redis_conn.keys(p) + if keys: + redis_conn.delete(*keys) + total_cleared += len(keys) + logger.info(f"Cleared {len(keys)} entries for pattern {p}") + return total_cleared + + if prefix: + pattern = f"{prefix}:*" + + keys = redis_conn.keys(pattern) + if keys: + redis_conn.delete(*keys) + logger.info(f"Cleared {len(keys)} cache entries matching {pattern}") + return len(keys) + return 0 + + except Exception as e: + logger.error(f"Error clearing cache with pattern {pattern}: {str(e)}") + return 0 |
