( attachment: File, config: FilesApiConfig, )
| 217 | * @returns Download result with success/failure status |
| 218 | */ |
| 219 | export async function downloadAndSaveFile( |
| 220 | attachment: File, |
| 221 | config: FilesApiConfig, |
| 222 | ): Promise<DownloadResult> { |
| 223 | const { fileId, relativePath } = attachment |
| 224 | const fullPath = buildDownloadPath(getCwd(), config.sessionId, relativePath) |
| 225 | |
| 226 | if (!fullPath) { |
| 227 | return { |
| 228 | fileId, |
| 229 | path: '', |
| 230 | success: false, |
| 231 | error: `Invalid file path: ${relativePath}`, |
| 232 | } |
| 233 | } |
| 234 | |
| 235 | try { |
| 236 | // Download the file content |
| 237 | const content = await downloadFile(fileId, config) |
| 238 | |
| 239 | // Ensure the parent directory exists |
| 240 | const parentDir = path.dirname(fullPath) |
| 241 | await fs.mkdir(parentDir, { recursive: true }) |
| 242 | |
| 243 | // Write the file |
| 244 | await fs.writeFile(fullPath, content) |
| 245 | |
| 246 | logDebug(`Saved file ${fileId} to ${fullPath} (${content.length} bytes)`) |
| 247 | |
| 248 | return { |
| 249 | fileId, |
| 250 | path: fullPath, |
| 251 | success: true, |
| 252 | bytesWritten: content.length, |
| 253 | } |
| 254 | } catch (error) { |
| 255 | logDebugError(`Failed to download file ${fileId}: ${errorMessage(error)}`) |
| 256 | if (error instanceof Error) { |
| 257 | logError(error) |
| 258 | } |
| 259 | |
| 260 | return { |
| 261 | fileId, |
| 262 | path: fullPath, |
| 263 | success: false, |
| 264 | error: errorMessage(error), |
| 265 | } |
| 266 | } |
| 267 | } |
| 268 | |
| 269 | // Default concurrency limit for parallel downloads |
| 270 | const DEFAULT_CONCURRENCY = 5 |
no test coverage detected