c1aac7ecac
Co-authored-by: Cursor <cursoragent@cursor.com>
217 lines
6.5 KiB
Python
217 lines
6.5 KiB
Python
import io
|
|
import os
|
|
from ftplib import FTP, FTP_TLS
|
|
|
|
from flask import current_app
|
|
|
|
from app.settings_service import get_settings
|
|
|
|
|
|
def save_photo_file(file_storage, stored_name):
|
|
settings = get_settings()
|
|
upload_dir = current_app.config["UPLOAD_FOLDER"]
|
|
os.makedirs(upload_dir, exist_ok=True)
|
|
local_path = os.path.join(upload_dir, stored_name)
|
|
|
|
file_storage.save(local_path)
|
|
file_size = os.path.getsize(local_path)
|
|
storage_backend = "local"
|
|
|
|
errors = []
|
|
|
|
if settings.s3_enabled:
|
|
ok, err = _upload_s3(local_path, stored_name, settings)
|
|
if ok:
|
|
storage_backend = "s3"
|
|
elif err:
|
|
errors.append(f"S3: {err}")
|
|
|
|
if settings.sftp_enabled:
|
|
ok, err = _upload_sftp(local_path, stored_name, settings)
|
|
if err:
|
|
errors.append(f"SFTP: {err}")
|
|
|
|
if settings.ftp_enabled:
|
|
ok, err = _upload_ftp(local_path, stored_name, settings)
|
|
if err:
|
|
errors.append(f"FTP: {err}")
|
|
|
|
return local_path, file_size, storage_backend, errors
|
|
|
|
|
|
def delete_photo_file(stored_name, storage_backend="local"):
|
|
settings = get_settings()
|
|
upload_dir = current_app.config["UPLOAD_FOLDER"]
|
|
local_path = os.path.join(upload_dir, stored_name)
|
|
|
|
if os.path.exists(local_path):
|
|
os.remove(local_path)
|
|
|
|
if storage_backend == "s3" and settings.s3_enabled:
|
|
_delete_s3(stored_name, settings)
|
|
|
|
if settings.sftp_enabled:
|
|
_delete_sftp(stored_name, settings)
|
|
|
|
if settings.ftp_enabled:
|
|
_delete_ftp(stored_name, settings)
|
|
|
|
|
|
def get_photo_stream(stored_name, storage_backend="local"):
|
|
settings = get_settings()
|
|
upload_dir = current_app.config["UPLOAD_FOLDER"]
|
|
local_path = os.path.join(upload_dir, stored_name)
|
|
|
|
if os.path.exists(local_path):
|
|
return open(local_path, "rb")
|
|
|
|
if storage_backend == "s3" and settings.s3_enabled:
|
|
data = _download_s3(stored_name, settings)
|
|
if data:
|
|
return io.BytesIO(data)
|
|
return None
|
|
|
|
|
|
def _upload_s3(local_path, key, settings):
|
|
try:
|
|
import boto3
|
|
from botocore.config import Config
|
|
|
|
kwargs = {
|
|
"aws_access_key_id": settings.s3_access_key,
|
|
"aws_secret_access_key": settings.s3_secret_key,
|
|
"region_name": settings.s3_region or "us-east-1",
|
|
}
|
|
if settings.s3_endpoint:
|
|
kwargs["endpoint_url"] = settings.s3_endpoint
|
|
|
|
client = boto3.client("s3", config=Config(signature_version="s3v4"), **kwargs)
|
|
client.upload_file(local_path, settings.s3_bucket, key)
|
|
return True, None
|
|
except Exception as exc:
|
|
return False, str(exc)
|
|
|
|
|
|
def _delete_s3(key, settings):
|
|
try:
|
|
import boto3
|
|
|
|
kwargs = {
|
|
"aws_access_key_id": settings.s3_access_key,
|
|
"aws_secret_access_key": settings.s3_secret_key,
|
|
"region_name": settings.s3_region or "us-east-1",
|
|
}
|
|
if settings.s3_endpoint:
|
|
kwargs["endpoint_url"] = settings.s3_endpoint
|
|
client = boto3.client("s3", **kwargs)
|
|
client.delete_object(Bucket=settings.s3_bucket, Key=key)
|
|
except Exception:
|
|
current_app.logger.exception("S3 delete failed")
|
|
|
|
|
|
def _download_s3(key, settings):
|
|
try:
|
|
import boto3
|
|
|
|
kwargs = {
|
|
"aws_access_key_id": settings.s3_access_key,
|
|
"aws_secret_access_key": settings.s3_secret_key,
|
|
"region_name": settings.s3_region or "us-east-1",
|
|
}
|
|
if settings.s3_endpoint:
|
|
kwargs["endpoint_url"] = settings.s3_endpoint
|
|
client = boto3.client("s3", **kwargs)
|
|
obj = client.get_object(Bucket=settings.s3_bucket, Key=key)
|
|
return obj["Body"].read()
|
|
except Exception:
|
|
current_app.logger.exception("S3 download failed")
|
|
return None
|
|
|
|
|
|
def _upload_sftp(local_path, remote_name, settings):
|
|
try:
|
|
import paramiko
|
|
|
|
transport = paramiko.Transport((settings.sftp_host, settings.sftp_port))
|
|
transport.connect(username=settings.sftp_username, password=settings.sftp_password)
|
|
sftp = paramiko.SFTPClient.from_transport(transport)
|
|
remote_dir = settings.sftp_remote_path or "/uploads"
|
|
_sftp_makedirs(sftp, remote_dir)
|
|
sftp.put(local_path, f"{remote_dir.rstrip('/')}/{remote_name}")
|
|
sftp.close()
|
|
transport.close()
|
|
return True, None
|
|
except Exception as exc:
|
|
return False, str(exc)
|
|
|
|
|
|
def _delete_sftp(remote_name, settings):
|
|
try:
|
|
import paramiko
|
|
|
|
transport = paramiko.Transport((settings.sftp_host, settings.sftp_port))
|
|
transport.connect(username=settings.sftp_username, password=settings.sftp_password)
|
|
sftp = paramiko.SFTPClient.from_transport(transport)
|
|
remote_path = f"{settings.sftp_remote_path.rstrip('/')}/{remote_name}"
|
|
sftp.remove(remote_path)
|
|
sftp.close()
|
|
transport.close()
|
|
except Exception:
|
|
current_app.logger.exception("SFTP delete failed")
|
|
|
|
|
|
def _sftp_makedirs(sftp, remote_dir):
|
|
parts = remote_dir.strip("/").split("/")
|
|
path = ""
|
|
for part in parts:
|
|
path += f"/{part}"
|
|
try:
|
|
sftp.stat(path)
|
|
except IOError:
|
|
sftp.mkdir(path)
|
|
|
|
|
|
def _upload_ftp(local_path, remote_name, settings):
|
|
try:
|
|
ftp_cls = FTP_TLS if settings.ftp_use_tls else FTP
|
|
ftp = ftp_cls()
|
|
ftp.connect(settings.ftp_host, settings.ftp_port, timeout=30)
|
|
ftp.login(settings.ftp_username, settings.ftp_password)
|
|
if settings.ftp_use_tls:
|
|
ftp.prot_p()
|
|
remote_dir = settings.ftp_remote_path or "/uploads"
|
|
_ftp_makedirs(ftp, remote_dir)
|
|
ftp.cwd(remote_dir)
|
|
with open(local_path, "rb") as f:
|
|
ftp.storbinary(f"STOR {remote_name}", f)
|
|
ftp.quit()
|
|
return True, None
|
|
except Exception as exc:
|
|
return False, str(exc)
|
|
|
|
|
|
def _delete_ftp(remote_name, settings):
|
|
try:
|
|
ftp_cls = FTP_TLS if settings.ftp_use_tls else FTP
|
|
ftp = ftp_cls()
|
|
ftp.connect(settings.ftp_host, settings.ftp_port, timeout=30)
|
|
ftp.login(settings.ftp_username, settings.ftp_password)
|
|
if settings.ftp_use_tls:
|
|
ftp.prot_p()
|
|
ftp.cwd(settings.ftp_remote_path or "/uploads")
|
|
ftp.delete(remote_name)
|
|
ftp.quit()
|
|
except Exception:
|
|
current_app.logger.exception("FTP delete failed")
|
|
|
|
|
|
def _ftp_makedirs(ftp, remote_dir):
|
|
parts = remote_dir.strip("/").split("/")
|
|
path = ""
|
|
for part in parts:
|
|
path += f"/{part}"
|
|
try:
|
|
ftp.cwd(path)
|
|
except Exception:
|
|
ftp.mkd(path)
|