22
33import os
44from pathlib import Path
5- from typing import List
5+ from typing import List , Optional
66
77from fastapi import APIRouter , HTTPException , status
88from fastapi .responses import FileResponse
99from pydantic import BaseModel
1010
1111from cloudcasting_backend .services .s3_downloader import (
12- download_s3_folder ,
12+ run_update_job ,
13+ get_download_status ,
1314 get_current_forecast_folder ,
14- convert_zarr_to_geotiffs ,
1515 GEOTIFF_STORAGE_PATH ,
1616)
1717from cloudcasting_backend .settings import settings
@@ -33,6 +33,22 @@ class AvailableLayersResponse(BaseModel):
3333 steps : List [int ]
3434
3535
36+ class BackgroundTaskResponse (BaseModel ):
37+ """Response model for background tasks."""
38+
39+ task_id : str
40+ message : str
41+
42+
43+ class DownloadStatusResponse (BaseModel ):
44+ """Response model for download status."""
45+
46+ is_running : bool
47+ current_task : Optional [str ] = None
48+ last_completed : Optional [str ] = None
49+ error : Optional [str ] = None
50+
51+
3652@router .get ("/status" , response_model = CloudcastingResponse )
3753async def get_cloudcasting_status () -> CloudcastingResponse :
3854 """
@@ -61,48 +77,46 @@ async def get_cloudcasting_status() -> CloudcastingResponse:
6177 )
6278
6379
64- @router .post ("/trigger-download" , response_model = CloudcastingResponse )
65- async def trigger_download () -> CloudcastingResponse :
80+ @router .post ("/trigger-download" , response_model = BackgroundTaskResponse )
81+ async def trigger_download () -> BackgroundTaskResponse :
6682 """
67- Manually trigger a download of the latest cloudcasting data.
68- Downloads the data and converts it to GeoTIFF format, then completes.
83+ Trigger a background download of the latest cloudcasting data.
84+ Downloads the data and converts it to GeoTIFF format in the background.
85+ Returns immediately with a task ID that can be used to check status.
6986 """
7087 try :
71- bucket_name = settings .s3_bucket_name
72- s3_folder = get_current_forecast_folder ()
73- local_dir = settings .zarr_storage_path
74-
75- # Ensure directory exists
76- Path (local_dir ).mkdir (parents = True , exist_ok = True )
77-
78- # Download the data
79- downloaded_zarr_path = download_s3_folder (bucket_name , s3_folder , local_dir )
80-
81- # Convert to GeoTIFF if download was successful
82- if downloaded_zarr_path :
83- convert_zarr_to_geotiffs (downloaded_zarr_path , GEOTIFF_STORAGE_PATH )
84-
85- # Check if data is available and return path
86- latest_path = Path (local_dir ) / "cloudcasting_forecast" / "latest.zarr"
87- if latest_path .exists ():
88- relative_path = latest_path .relative_to (
89- Path (settings .zarr_storage_path ).parent ,
90- )
91- data_path = f"/static/{ relative_path } "
92- else :
93- data_path = ""
94-
95- return CloudcastingResponse (
96- message = "Download and conversion completed successfully" ,
97- data_path = data_path ,
88+ # Directly call run_update_job to start the background process
89+ run_update_job ()
90+
91+ # Get the current status to return task info
92+ status_info = get_download_status ()
93+ task_id = status_info .get ("current_task" , "unknown" )
94+
95+ return BackgroundTaskResponse (
96+ task_id = task_id ,
97+ message = f"Background download started: { task_id } "
98+ )
99+ except RuntimeError as e :
100+ raise HTTPException (
101+ status_code = status .HTTP_409_CONFLICT ,
102+ detail = str (e ),
98103 )
99104 except Exception as e :
100105 raise HTTPException (
101106 status_code = status .HTTP_500_INTERNAL_SERVER_ERROR ,
102- detail = f"Failed to download data : { e !s} " ,
107+ detail = f"Failed to start download : { e !s} " ,
103108 )
104109
105110
111+ @router .get ("/download-status" , response_model = DownloadStatusResponse )
112+ async def get_download_status_endpoint () -> DownloadStatusResponse :
113+ """
114+ Get the current status of background download processes.
115+ """
116+ status_info = get_download_status ()
117+ return DownloadStatusResponse (** status_info )
118+
119+
106120@router .get ("/layers" , response_model = AvailableLayersResponse )
107121async def get_available_layers () -> AvailableLayersResponse :
108122 """
0 commit comments