UrlCache.coffee 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. db = require("./db")
  2. UrlFetcher = require("./UrlFetcher")
  3. Settings = require("settings-sharelatex")
  4. crypto = require("crypto")
  5. fs = require("fs")
  6. logger = require "logger-sharelatex"
  7. async = require "async"
  8. module.exports = UrlCache =
  9. downloadUrlToFile: (project_id, url, destPath, lastModified, callback = (error) ->) ->
  10. UrlCache._ensureUrlIsInCache project_id, url, lastModified, (error, pathToCachedUrl) =>
  11. return callback(error) if error?
  12. UrlCache._copyFile pathToCachedUrl, destPath, (error) ->
  13. if error?
  14. UrlCache._clearUrlDetails project_id, url, () ->
  15. callback(error)
  16. else
  17. callback(error)
  18. clearProject: (project_id, callback = (error) ->) ->
  19. UrlCache._findAllUrlsInProject project_id, (error, urls) ->
  20. logger.log project_id: project_id, url_count: urls.length, "clearing project URLs"
  21. return callback(error) if error?
  22. jobs = for url in (urls or [])
  23. do (url) ->
  24. (callback) ->
  25. UrlCache._clearUrlFromCache project_id, url, (error) ->
  26. if error?
  27. logger.error err: error, project_id: project_id, url: url, "error clearing project URL"
  28. callback()
  29. async.series jobs, callback
  30. _ensureUrlIsInCache: (project_id, url, lastModified, callback = (error, pathOnDisk) ->) ->
  31. if lastModified?
  32. # MYSQL only stores dates to an accuracy of a second but the incoming lastModified might have milliseconds.
  33. # So round down to seconds
  34. lastModified = new Date(Math.floor(lastModified.getTime() / 1000) * 1000)
  35. UrlCache._doesUrlNeedDownloading project_id, url, lastModified, (error, needsDownloading) =>
  36. return callback(error) if error?
  37. if needsDownloading
  38. logger.log url: url, lastModified: lastModified, "downloading URL"
  39. UrlFetcher.pipeUrlToFile url, UrlCache._cacheFilePathForUrl(project_id, url), (error) =>
  40. return callback(error) if error?
  41. UrlCache._updateOrCreateUrlDetails project_id, url, lastModified, (error) =>
  42. return callback(error) if error?
  43. callback null, UrlCache._cacheFilePathForUrl(project_id, url)
  44. else
  45. logger.log url: url, lastModified: lastModified, "URL is up to date in cache"
  46. callback null, UrlCache._cacheFilePathForUrl(project_id, url)
  47. _doesUrlNeedDownloading: (project_id, url, lastModified, callback = (error, needsDownloading) ->) ->
  48. if !lastModified?
  49. return callback null, true
  50. UrlCache._findUrlDetails project_id, url, (error, urlDetails) ->
  51. return callback(error) if error?
  52. if !urlDetails? or !urlDetails.lastModified? or urlDetails.lastModified.getTime() < lastModified.getTime()
  53. return callback null, true
  54. else
  55. return callback null, false
  56. _cacheFileNameForUrl: (project_id, url) ->
  57. project_id + ":" + crypto.createHash("md5").update(url).digest("hex")
  58. _cacheFilePathForUrl: (project_id, url) ->
  59. "#{Settings.path.clsiCacheDir}/#{UrlCache._cacheFileNameForUrl(project_id, url)}"
  60. _copyFile: (from, to, _callback = (error) ->) ->
  61. callbackOnce = (error) ->
  62. if error?
  63. logger.error err: error, from:from, to:to, "error copying file from cache"
  64. _callback(error)
  65. _callback = () ->
  66. writeStream = fs.createWriteStream(to)
  67. readStream = fs.createReadStream(from)
  68. writeStream.on "error", callbackOnce
  69. readStream.on "error", callbackOnce
  70. writeStream.on "close", callbackOnce
  71. writeStream.on "open", () ->
  72. readStream.pipe(writeStream)
  73. _clearUrlFromCache: (project_id, url, callback = (error) ->) ->
  74. UrlCache._clearUrlDetails project_id, url, (error) ->
  75. return callback(error) if error?
  76. UrlCache._deleteUrlCacheFromDisk project_id, url, (error) ->
  77. return callback(error) if error?
  78. callback null
  79. _deleteUrlCacheFromDisk: (project_id, url, callback = (error) ->) ->
  80. fs.unlink UrlCache._cacheFilePathForUrl(project_id, url), callback
  81. _findUrlDetails: (project_id, url, callback = (error, urlDetails) ->) ->
  82. db.UrlCache.find(where: { url: url, project_id: project_id })
  83. .then((urlDetails) -> callback null, urlDetails)
  84. .error callback
  85. _updateOrCreateUrlDetails: (project_id, url, lastModified, callback = (error) ->) ->
  86. db.UrlCache.findOrCreate(where: {url: url, project_id: project_id})
  87. .spread(
  88. (urlDetails, created) ->
  89. urlDetails.updateAttributes(lastModified: lastModified)
  90. .then(() -> callback())
  91. .error(callback)
  92. )
  93. .error callback
  94. _clearUrlDetails: (project_id, url, callback = (error) ->) ->
  95. db.UrlCache.destroy(where: {url: url, project_id: project_id})
  96. .then(() -> callback null)
  97. .error callback
  98. _findAllUrlsInProject: (project_id, callback = (error, urls) ->) ->
  99. db.UrlCache.findAll(where: { project_id: project_id })
  100. .then(
  101. (urlEntries) ->
  102. callback null, urlEntries.map((entry) -> entry.url)
  103. )
  104. .error callback