# Copyright (c) 2025 Patrick Motsch # All rights reserved. import logging import json import base64 import os from typing import Dict, Any from modules.datamodels.datamodelChat import ActionResult, ActionDocument logger = logging.getLogger(__name__) async def downloadFileByPath(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") pathQuery = (parameters.get("pathQuery") or parameters.get("path") or "").strip() siteIdParam = parameters.get("siteId") filePath = parameters.get("filePath") # If filePath looks like full SharePoint path, use as pathQuery fallback if not pathQuery and filePath and isinstance(filePath, str) and filePath.strip().startswith("/sites/"): pathQuery = filePath.strip() siteId = None innerPath = None # Option 1: pathQuery provided (e.g. /sites/SiteName/Shared Documents/file.pdf) – resolve site and inner path if pathQuery and pathQuery != "*": sites, errorMsg = await self.siteDiscovery.resolveSitesFromPathQuery(pathQuery) if errorMsg: return ActionResult.isFailure(error=errorMsg) if not sites: return ActionResult.isFailure(error="Could not resolve site from pathQuery") parsedPath = self.services.sharepoint.extractSiteFromStandardPath(pathQuery) if not parsedPath: return ActionResult.isFailure(error="pathQuery must be a standard SharePoint path (e.g. /sites/SiteName/Shared Documents/file.pdf)") innerPath = (parsedPath.get("innerPath") or "").strip() if not innerPath: return ActionResult.isFailure(error="pathQuery must include a file path (e.g. /sites/SiteName/Shared Documents/file.pdf)") siteId = sites[0].get("id") filePath = innerPath elif siteIdParam and filePath: # Option 2: siteId + filePath provided directly 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 else: return ActionResult.isFailure(error="Either pathQuery (e.g. /sites/SiteName/Shared Documents/file.pdf) or both siteId and filePath are required") if not siteId or not filePath: return ActionResult.isFailure(error="Could not resolve siteId and file path from parameters") # Download file (connection/token already set above) fileContent = await self.services.sharepoint.downloadFileByPath( siteId=siteId, filePath=filePath ) if fileContent is None: return ActionResult.isFailure(error=f"File not found or could not be downloaded: {filePath}") logger.info(f"Downloaded file from SharePoint: {filePath} ({len(fileContent)} bytes)") # Generate filename from filePath fileName = os.path.basename(filePath) or "downloaded_file" workflowContext = self.services.chat.getWorkflowContext() if hasattr(self.services, 'chat') else None filename = self._generateMeaningfulFileName( fileName.split('.')[0] if '.' in fileName else fileName, fileName.split('.')[-1] if '.' in fileName else "bin", workflowContext, "downloadFileByPath" ) # Save to user's Files (FileItem + FileData) via interfaceDbComponent – appears in Files UI fileItem = None db = getattr(self.services, "interfaceDbComponent", None) if db: try: mimeType = db.getMimeType(filename) if hasattr(db, "getMimeType") else "application/octet-stream" fileItem = db.createFile(name=filename, mimeType=mimeType, content=fileContent) db.createFileData(fileItem.id, fileContent) logger.info(f"Saved SharePoint file to user Files: {filename} (id={fileItem.id})") except Exception as e: logger.warning(f"Could not save to user Files: {e}") # Encode as base64 for workflow context (AI, data nodes) fileBase64 = base64.b64encode(fileContent).decode('utf-8') validationMetadata = self._createValidationMetadata( "downloadFileByPath", siteId=siteId, filePath=filePath, fileSize=len(fileContent) ) if fileItem: validationMetadata["fileId"] = fileItem.id document = ActionDocument( documentName=filename, documentData=fileBase64, mimeType="application/octet-stream", validationMetadata=validationMetadata ) return ActionResult.isSuccess(documents=[document]) except Exception as e: errorMsg = f"Error downloading file from SharePoint: {str(e)}" logger.error(errorMsg) return ActionResult.isFailure(error=errorMsg)