# Copyright (c) 2025 Patrick Motsch # All rights reserved. import logging import json from typing import Dict, Any from modules.datamodels.datamodelChat import ActionResult, ActionDocument logger = logging.getLogger(__name__) async def copyFile(self, parameters: Dict[str, Any]) -> ActionResult: try: connectionReference = parameters.get("connectionReference") if not connectionReference: return ActionResult.isFailure(error="connectionReference parameter is required") # Set SharePoint access token first – required before siteDiscovery/sharepoint calls connection = self.connection.getMicrosoftConnection(connectionReference) if not connection: return ActionResult.isFailure(error="No valid Microsoft connection found for the provided connection reference") sourcePath = (parameters.get("sourcePath") or parameters.get("sourcePathQuery") or "").strip() destPath = (parameters.get("destPath") or parameters.get("destPathQuery") or "").strip() siteId = None sourceFolder = None sourceFile = None destFolder = None destFile = None if sourcePath and destPath and sourcePath.startswith("/sites/") and destPath.startswith("/sites/"): parsedSource = self.services.sharepoint.extractSiteFromStandardPath(sourcePath) parsedDest = self.services.sharepoint.extractSiteFromStandardPath(destPath) if parsedSource and parsedDest: innerSrc = (parsedSource.get("innerPath") or "").strip().rstrip("/") innerDest = (parsedDest.get("innerPath") or "").strip().rstrip("/") if innerSrc: if "/" in innerSrc: sourceFolder = innerSrc.rsplit("/", 1)[0] sourceFile = innerSrc.rsplit("/", 1)[-1] else: sourceFolder = "" sourceFile = innerSrc destFolder = innerDest destFile = sourceFile sites, _ = await self.siteDiscovery.resolveSitesFromPathQuery(sourcePath) if sites: siteId = sites[0].get("id") if not siteId or not sourceFolder or not sourceFile or not destFolder: siteIdParam = parameters.get("siteId") sourceFolder = parameters.get("sourceFolder") sourceFile = parameters.get("sourceFile") destFolder = parameters.get("destFolder") destFile = parameters.get("destFile") if not siteIdParam: return ActionResult.isFailure(error="Either sourcePath+destPath or siteId, sourceFolder, sourceFile, destFolder, destFile are required") if not sourceFolder or not sourceFile or not destFolder or not destFile: return ActionResult.isFailure(error="sourceFolder, sourceFile, destFolder, and destFile are required") if not destFile: destFile = sourceFile if isinstance(siteIdParam, str): from modules.datamodels.datamodelDocref import DocumentReferenceList try: docList = DocumentReferenceList.from_string_list([siteIdParam]) chatDocuments = self.services.chat.getChatDocumentsFromDocumentList(docList) if chatDocuments and len(chatDocuments) > 0: siteInfoJson = json.loads(chatDocuments[0].documentData) siteId = siteInfoJson.get("id") except Exception: pass if not siteId: siteId = siteIdParam else: siteId = siteIdParam if not siteId: return ActionResult.isFailure(error="Could not resolve siteId") # Copy file await self.services.sharepoint.copyFileAsync( siteId=siteId, sourceFolder=sourceFolder, sourceFile=sourceFile, destFolder=destFolder, destFile=destFile ) logger.info(f"Copied file in SharePoint: {sourceFolder}/{sourceFile} -> {destFolder}/{destFile}") # Generate filename workflowContext = self.services.chat.getWorkflowContext() if hasattr(self.services, 'chat') else None filename = self._generateMeaningfulFileName( "file_copy_result", "json", workflowContext, "copyFile" ) result = { "success": True, "siteId": siteId, "sourcePath": f"{sourceFolder}/{sourceFile}", "destPath": f"{destFolder}/{destFile}" } validationMetadata = self._createValidationMetadata( "copyFile", siteId=siteId, sourcePath=f"{sourceFolder}/{sourceFile}", destPath=f"{destFolder}/{destFile}" ) document = ActionDocument( documentName=filename, documentData=json.dumps(result, indent=2), mimeType="application/json", validationMetadata=validationMetadata ) return ActionResult.isSuccess(documents=[document]) except Exception as e: # Handle file not found gracefully if "itemNotFound" in str(e) or "404" in str(e): logger.warning(f"File not found for copy: {parameters.get('sourceFolder')}/{parameters.get('sourceFile')}") # Return success with skipped status workflowContext = self.services.chat.getWorkflowContext() if hasattr(self.services, 'chat') else None filename = self._generateMeaningfulFileName( "file_copy_result", "json", workflowContext, "copyFile" ) result = { "success": True, "skipped": True, "reason": "File not found (may not exist yet)" } validationMetadata = self._createValidationMetadata( "copyFile", skipped=True ) document = ActionDocument( documentName=filename, documentData=json.dumps(result, indent=2), mimeType="application/json", validationMetadata=validationMetadata ) return ActionResult.isSuccess(documents=[document]) errorMsg = f"Error copying file in SharePoint: {str(e)}" logger.error(errorMsg) return ActionResult.isFailure(error=errorMsg)