UrlFetcher.js 1.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
  1. const fs = require('fs')
  2. const logger = require('@overleaf/logger')
  3. const Settings = require('@overleaf/settings')
  4. const { fetchStream } = require('@overleaf/fetch-utils')
  5. const { URL } = require('url')
  6. const { pipeline } = require('stream/promises')
  7. async function pipeUrlToFileWithRetry(url, filePath) {
  8. let remainingAttempts = 3
  9. let lastErr
  10. while (remainingAttempts-- > 0) {
  11. try {
  12. await pipeUrlToFile(url, filePath)
  13. return
  14. } catch (err) {
  15. logger.warn(
  16. { err, url, filePath, remainingAttempts },
  17. 'error downloading url'
  18. )
  19. lastErr = err
  20. }
  21. }
  22. throw lastErr
  23. }
  24. async function pipeUrlToFile(url, filePath) {
  25. const u = new URL(url)
  26. if (
  27. Settings.filestoreDomainOveride &&
  28. u.host !== Settings.apis.clsiPerf.host
  29. ) {
  30. url = `${Settings.filestoreDomainOveride}${u.pathname}${u.search}`
  31. }
  32. const stream = await fetchStream(url, {
  33. signal: AbortSignal.timeout(60 * 1000),
  34. })
  35. const atomicWrite = filePath + '~'
  36. try {
  37. await pipeline(stream, fs.createWriteStream(atomicWrite))
  38. await fs.promises.rename(atomicWrite, filePath)
  39. } catch (err) {
  40. try {
  41. await fs.promises.unlink(atomicWrite)
  42. } catch (e) {}
  43. throw err
  44. }
  45. }
  46. module.exports.promises = {
  47. pipeUrlToFileWithRetry,
  48. }