diff --git a/src/murfey/instrument_server/api.py b/src/murfey/instrument_server/api.py index 1b987b8c3..117993ec2 100644 --- a/src/murfey/instrument_server/api.py +++ b/src/murfey/instrument_server/api.py @@ -425,6 +425,91 @@ def get_possible_otf_dirs( return candidates +class OTFDirectoryUploadInfo(BaseModel): + dir_path: Path # Full client-side path to OTF directory + visit_path: str # Path fragment from rsync module to visit directory + destination_dir: str = "setup" # Folder in visit directory to save to + + +@router.post("/instruments/{instrument_name}/sessions/{session_id}/upload_otf_dir") +def upload_otf_dir( + instrument_name: str, + session_id: MurfeySessionID, + otf_dir_info: OTFDirectoryUploadInfo, +): + # Sanitise incoming values + otf_dir_path = sanitise(str(otf_dir_info.dir_path)) + visit_path = sanitise(otf_dir_info.visit_path) + destination_dir = sanitise(otf_dir_info.destination_dir) + + # Load machine config and other needed properties + machine_config_url_path = url_path_for( + "session_control.router", + "machine_info_by_instrument", + instrument_name=sanitise_nonpath(instrument_name), + ) + machine_config: dict[str, Any] = requests.get( + f"{_get_murfey_url()}{machine_config_url_path}", + headers={"Authorization": f"Bearer {tokens[session_id]}"}, + ).json() + + # Validate that file passed is from the gain reference directory + otf_dir = machine_config.get("gain_reference_directory", "") + if not otf_dir_path.startswith(otf_dir): + raise ValueError( + "Gain reference file does not originate from the gain reference directory " + f"{otf_dir!r}" + ) + + # Construct the rsync path to use + # Return the rsync URL if set, otherwise assume you are syncing via Murfey + rsync_url = urlparse( + str(machine_config["rsync_url"]) + if machine_config.get("rsync_url", "") + else _get_murfey_url() + ) + rsync_module = machine_config.get("rsync_module", "data") + rsync_path = f"{rsync_url.hostname}::{rsync_module}/{visit_path}/{destination_dir}" + + # Construct the expected destination path on the server side + rsync_basepath: str | None = machine_config.get("rsync_basepath") + if not rsync_basepath: + logger.error(f"No rsync base path was configured for {instrument_name}") + return {"success": False} + destination_path = ( + f"{rsync_basepath}/{visit_path}/{destination_dir}/" + f"{secure_filename(otf_dir_info.dir_path.name)}" + ) + + # Run rsync subprocess to transfer OTF directory and contents + cmd = [ + "rsync", + "-a", # Sync folder contents + ] + if rsync_chmod := machine_config.get("rsync_chmod"): + cmd.append(f"--chmod={rsync_chmod}") + cmd.extend([posix_path(Path(otf_dir_path)), rsync_path]) + process = subprocess.run( + cmd, + capture_output=True, + text=True, + ) + if process.returncode: + logger.warning( + f"Failed to transfer OTF directory {otf_dir_path!r} to {f'{destination_path}'!r} \n" + f"Executed the following command: {' '.join(cmd)!r} \n" + f"Returned the following error: \n" + f"{process.stderr}" + ) + return {"success": False} + + # Return the full path as part of the message + return { + "success": True, + "destination_path": str(destination_path), + } + + @router.get( "/instruments/{instrument_name}/sessions/{session_id}/possible_gain_references" ) diff --git a/src/murfey/server/api/instrument.py b/src/murfey/server/api/instrument.py index 7f97423a8..a92d1de79 100644 --- a/src/murfey/server/api/instrument.py +++ b/src/murfey/server/api/instrument.py @@ -3,6 +3,7 @@ import asyncio import datetime import logging +import os from pathlib import Path from typing import Annotated, Any, List, Optional from urllib.parse import quote @@ -408,6 +409,82 @@ async def get_possible_otf_dirs( return data +class OTFDirectoryUploadRequest(BaseModel): + dir_path: Path + + +@router.post("/sessions/{session_id}/upload_otf_dir") +async def request_otf_dir_upload( + session_id: MurfeySessionID, + otf_dir_request: OTFDirectoryUploadRequest, + db=murfey_db, +): + # Load session information from database + murfey_session = db.exec(select(Session).where(Session.id == session_id)).one() + visit_name = murfey_session.visit + instrument_name = murfey_session.instrument_name + + # Load machine config + machine_config = get_machine_config(instrument_name=instrument_name)[ + instrument_name + ] + + # Default data to return + data: dict[str, Any] = {"success": False} + + # Load the rsync basepath + rsync_basepath = machine_config.rsync_basepath + if rsync_basepath is None: + log.error(f"No rsync basepath was configured for instrument {instrument_name}") + return data + + # Construct the partial and full paths to the server-side visit directory + visit_path = f"{datetime.datetime.now().year}/{visit_name}" + visit_dir = rsync_basepath / visit_path + if not visit_dir.exists(): # Check previous year in case of rollover + visit_dir_prev = visit_dir + visit_path = f"{datetime.datetime.now().year - 1}/{visit_name}" + visit_dir = rsync_basepath / visit_path + if not visit_dir.exists(): + log.error( + "Unable to find visit directory under " + f"{str(visit_dir_prev)} or {str(visit_dir)}" + ) + return data + + # Ensure that the OTF destination directory exists + otf_dir = visit_dir / machine_config.gain_directory_name + otf_dir.mkdir(exist_ok=True) + os.chmod(otf_dir, mode=machine_config.mkdir_chmod) # Set permissions + + if machine_config.instrument_server_url: + async with aiohttp.ClientSession() as clientsession: + url_path = url_path_for( + "api.router", + "upload_otf_dir", + instrument_name=instrument_name, + session_id=session_id, + ) + payload = { + "dir_path": str(otf_dir_request.dir_path), + "visit_path": visit_path, + "destination_dir": machine_config.gain_directory_name, + } + async with clientsession.post( + f"{machine_config.instrument_server_url}{url_path}", + json=payload, + headers={ + "Authorization": f"Bearer {instrument_server_tokens[session_id]['access_token']}" + }, + ) as resp: + data = await resp.json() + else: + log.error( + f"No instrument server URL was configured for instrument {instrument_name}" + ) + return data + + @router.get( "/instruments/{instrument_name}/sessions/{session_id}/possible_gain_references" ) diff --git a/src/murfey/server/api/workflow_sim.py b/src/murfey/server/api/workflow_sim.py index ff1e4aae6..4008de2dc 100644 --- a/src/murfey/server/api/workflow_sim.py +++ b/src/murfey/server/api/workflow_sim.py @@ -1,5 +1,6 @@ import json import logging +import re from pathlib import Path from typing import Any @@ -44,10 +45,40 @@ def request_sim_reconstruction( ).one() instrument_name = murfey_session.instrument_name visit_name = murfey_session.visit + otf_dir = Path(murfey_session.current_gain_ref) except Exception: logger.error("Error querying session information from database", exc_info=True) return None + # Look for OTF files in the saved directory and match them to wavelengths + COLOR_LOOKUP = { + 452: "blue", + 525: "green", + 605: "red", + 655: "far_red", + } + otf_files: dict[str, Path] = {} + pattern = r"(?