129 lines
6 KiB
Python
129 lines
6 KiB
Python
# 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)
|
||
|