UrlCache.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. /* eslint-disable
  2. no-return-assign,
  3. */
  4. // TODO: This file was created by bulk-decaffeinate.
  5. // Fix any style issues and re-enable lint.
  6. /*
  7. * decaffeinate suggestions:
  8. * DS101: Remove unnecessary use of Array.from
  9. * DS102: Remove unnecessary code created because of implicit returns
  10. * DS207: Consider shorter variations of null checks
  11. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  12. */
  13. const UrlFetcher = require('./UrlFetcher')
  14. const Settings = require('@overleaf/settings')
  15. const fs = require('node:fs')
  16. const Path = require('node:path')
  17. const { callbackify } = require('node:util')
  18. const Metrics = require('./Metrics')
  19. const PENDING_DOWNLOADS = new Map()
  20. function getProjectDir(projectId) {
  21. return Path.join(Settings.path.clsiCacheDir, projectId)
  22. }
  23. function getCachePath(projectId, url, lastModified) {
  24. // The url is a filestore URL.
  25. // It is sufficient to look at the path and mtime for uniqueness.
  26. const mtime = (lastModified && lastModified.getTime()) || 0
  27. const key = new URL(url).pathname.replace(/\//g, '-') + '-' + mtime
  28. return Path.join(getProjectDir(projectId), key)
  29. }
  30. async function clearProject(projectId, options) {
  31. const timer = new Metrics.Timer('url_cache', {
  32. status: options?.reason || 'unknown',
  33. path: 'delete',
  34. })
  35. await fs.promises.rm(getProjectDir(projectId), {
  36. force: true,
  37. recursive: true,
  38. })
  39. timer.done()
  40. }
  41. async function createProjectDir(projectId) {
  42. await fs.promises.mkdir(getProjectDir(projectId), { recursive: true })
  43. }
  44. async function downloadUrlToFile(
  45. projectId,
  46. url,
  47. fallbackURL,
  48. destPath,
  49. lastModified
  50. ) {
  51. const cachePath = getCachePath(projectId, url, lastModified)
  52. try {
  53. const timer = new Metrics.Timer('url_cache', {
  54. status: 'cache-hit',
  55. path: 'copy',
  56. })
  57. try {
  58. await fs.promises.copyFile(cachePath, destPath)
  59. } catch (err) {
  60. if (err.code === 'ENOENT' && fallbackURL) {
  61. const fallbackPath = getCachePath(projectId, fallbackURL, lastModified)
  62. await fs.promises.copyFile(fallbackPath, destPath)
  63. } else {
  64. throw err
  65. }
  66. }
  67. // the metric is only updated if the file is present in the cache
  68. timer.done()
  69. return
  70. } catch (e) {
  71. if (e.code !== 'ENOENT') {
  72. throw e
  73. }
  74. }
  75. // time the download
  76. {
  77. const timer = new Metrics.Timer('url_cache', {
  78. status: 'cache-miss',
  79. path: 'download',
  80. })
  81. try {
  82. await download(url, fallbackURL, cachePath)
  83. } finally {
  84. timer.done()
  85. }
  86. }
  87. // time the file copy
  88. {
  89. const timer = new Metrics.Timer('url_cache', {
  90. status: 'cache-miss',
  91. path: 'copy',
  92. })
  93. await fs.promises.copyFile(cachePath, destPath)
  94. timer.done()
  95. }
  96. }
  97. async function download(url, fallbackURL, cachePath) {
  98. let pending = PENDING_DOWNLOADS.get(cachePath)
  99. if (pending) {
  100. return pending
  101. }
  102. pending = UrlFetcher.promises.pipeUrlToFileWithRetry(
  103. url,
  104. fallbackURL,
  105. cachePath
  106. )
  107. PENDING_DOWNLOADS.set(cachePath, pending)
  108. try {
  109. await pending
  110. } finally {
  111. PENDING_DOWNLOADS.delete(cachePath)
  112. }
  113. }
  114. module.exports = {
  115. clearProject: callbackify(clearProject),
  116. createProjectDir: callbackify(createProjectDir),
  117. downloadUrlToFile: callbackify(downloadUrlToFile),
  118. promises: {
  119. clearProject,
  120. createProjectDir,
  121. downloadUrlToFile,
  122. },
  123. }