download.ts 1.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
  1. import { createWriteStream } from 'node:fs'
  2. import { request } from 'node:https'
  3. import type { IncomingMessage } from 'node:http'
  4. import { basename } from 'node:path'
  5. import { pipeline } from 'node:stream/promises'
  6. import { Transform } from 'node:stream'
  7. export async function downloadFile(task, url: string, path: string): Promise<void> {
  8. const fileName = basename(path)
  9. task.title = `Starting download: ${fileName}`
  10. await new Promise<void>((resolve, reject) => {
  11. const req = request(url, (res: IncomingMessage) => {
  12. if (res.statusCode !== 200) {
  13. reject(new Error(`Failed to download file. Status code: ${res.statusCode}`))
  14. return
  15. }
  16. const totalSize = Number.parseInt(res.headers['content-length'] || '0', 10)
  17. let downloadedSize = 0
  18. // Create a transform stream that updates progress
  19. const progressStream = new Transform({
  20. transform(chunk, encoding, callback) {
  21. downloadedSize += chunk.length
  22. if (totalSize) {
  23. const ratio = downloadedSize / totalSize
  24. const progress = Math.round(ratio * 100)
  25. const progressBarCount = Math.floor(ratio * 20)
  26. const progressBar = `[${'='.repeat(progressBarCount)}${' '.repeat(20 - progressBarCount)}]`
  27. task.title = `Downloading ${fileName}: ${progressBar} ${progress}% (${(downloadedSize / (1024 * 1024)).toFixed(2)} MB / ${(totalSize / (1024 * 1024)).toFixed(2)} MB)`
  28. }
  29. else {
  30. task.title = `Downloading ${fileName}: ${(downloadedSize / (1024 * 1024)).toFixed(2)} MB downloaded`
  31. }
  32. callback(null, chunk)
  33. },
  34. })
  35. const fileStream = createWriteStream(path)
  36. // Use pipeline to handle piping and errors automatically.
  37. pipeline(res, progressStream, fileStream)
  38. .then(resolve)
  39. .catch(reject)
  40. })
  41. req.on('error', reject)
  42. req.end()
  43. })
  44. }