ResourceStateManager.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. /* eslint-disable
  2. handle-callback-err,
  3. no-unused-vars,
  4. */
  5. // TODO: This file was created by bulk-decaffeinate.
  6. // Fix any style issues and re-enable lint.
  7. /*
  8. * decaffeinate suggestions:
  9. * DS101: Remove unnecessary use of Array.from
  10. * DS102: Remove unnecessary code created because of implicit returns
  11. * DS103: Rewrite code to no longer use __guard__
  12. * DS201: Simplify complex destructure assignments
  13. * DS207: Consider shorter variations of null checks
  14. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  15. */
  16. let ResourceStateManager
  17. const Path = require('path')
  18. const fs = require('fs')
  19. const logger = require('logger-sharelatex')
  20. const settings = require('settings-sharelatex')
  21. const Errors = require('./Errors')
  22. const SafeReader = require('./SafeReader')
  23. module.exports = ResourceStateManager = {
  24. // The sync state is an identifier which must match for an
  25. // incremental update to be allowed.
  26. //
  27. // The initial value is passed in and stored on a full
  28. // compile, along with the list of resources..
  29. //
  30. // Subsequent incremental compiles must come with the same value - if
  31. // not they will be rejected with a 409 Conflict response. The
  32. // previous list of resources is returned.
  33. //
  34. // An incremental compile can only update existing files with new
  35. // content. The sync state identifier must change if any docs or
  36. // files are moved, added, deleted or renamed.
  37. SYNC_STATE_FILE: '.project-sync-state',
  38. SYNC_STATE_MAX_SIZE: 128 * 1024,
  39. saveProjectState(state, resources, basePath, callback) {
  40. if (callback == null) {
  41. callback = function (error) {}
  42. }
  43. const stateFile = Path.join(basePath, this.SYNC_STATE_FILE)
  44. if (state == null) {
  45. // remove the file if no state passed in
  46. logger.log({ state, basePath }, 'clearing sync state')
  47. return fs.unlink(stateFile, function (err) {
  48. if (err != null && err.code !== 'ENOENT') {
  49. return callback(err)
  50. } else {
  51. return callback()
  52. }
  53. })
  54. } else {
  55. logger.log({ state, basePath }, 'writing sync state')
  56. const resourceList = Array.from(resources).map(
  57. (resource) => resource.path
  58. )
  59. return fs.writeFile(
  60. stateFile,
  61. [...Array.from(resourceList), `stateHash:${state}`].join('\n'),
  62. callback
  63. )
  64. }
  65. },
  66. checkProjectStateMatches(state, basePath, callback) {
  67. if (callback == null) {
  68. callback = function (error, resources) {}
  69. }
  70. const stateFile = Path.join(basePath, this.SYNC_STATE_FILE)
  71. const size = this.SYNC_STATE_MAX_SIZE
  72. return SafeReader.readFile(stateFile, size, 'utf8', function (
  73. err,
  74. result,
  75. bytesRead
  76. ) {
  77. if (err != null) {
  78. return callback(err)
  79. }
  80. if (bytesRead === size) {
  81. logger.error(
  82. { file: stateFile, size, bytesRead },
  83. 'project state file truncated'
  84. )
  85. }
  86. const array =
  87. __guard__(result != null ? result.toString() : undefined, (x) =>
  88. x.split('\n')
  89. ) || []
  90. const adjustedLength = Math.max(array.length, 1)
  91. const resourceList = array.slice(0, adjustedLength - 1)
  92. const oldState = array[adjustedLength - 1]
  93. const newState = `stateHash:${state}`
  94. logger.log(
  95. { state, oldState, basePath, stateMatches: newState === oldState },
  96. 'checking sync state'
  97. )
  98. if (newState !== oldState) {
  99. return callback(
  100. new Errors.FilesOutOfSyncError('invalid state for incremental update')
  101. )
  102. } else {
  103. const resources = Array.from(resourceList).map((path) => ({ path }))
  104. return callback(null, resources)
  105. }
  106. })
  107. },
  108. checkResourceFiles(resources, allFiles, basePath, callback) {
  109. // check the paths are all relative to current directory
  110. let file
  111. if (callback == null) {
  112. callback = function (error) {}
  113. }
  114. for (file of Array.from(resources || [])) {
  115. for (const dir of Array.from(
  116. __guard__(file != null ? file.path : undefined, (x) => x.split('/'))
  117. )) {
  118. if (dir === '..') {
  119. return callback(new Error('relative path in resource file list'))
  120. }
  121. }
  122. }
  123. // check if any of the input files are not present in list of files
  124. const seenFile = {}
  125. for (file of Array.from(allFiles)) {
  126. seenFile[file] = true
  127. }
  128. const missingFiles = Array.from(resources)
  129. .filter((resource) => !seenFile[resource.path])
  130. .map((resource) => resource.path)
  131. if ((missingFiles != null ? missingFiles.length : undefined) > 0) {
  132. logger.err(
  133. { missingFiles, basePath, allFiles, resources },
  134. 'missing input files for project'
  135. )
  136. return callback(
  137. new Errors.FilesOutOfSyncError(
  138. 'resource files missing in incremental update'
  139. )
  140. )
  141. } else {
  142. return callback()
  143. }
  144. }
  145. }
  146. function __guard__(value, transform) {
  147. return typeof value !== 'undefined' && value !== null
  148. ? transform(value)
  149. : undefined
  150. }