ResourceStateManager.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  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. * DS201: Simplify complex destructure assignments
  10. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  11. */
  12. let ResourceStateManager
  13. const Path = require('path')
  14. const fs = require('fs')
  15. const logger = require('logger-sharelatex')
  16. const settings = require('settings-sharelatex')
  17. const Errors = require('./Errors')
  18. const SafeReader = require('./SafeReader')
  19. module.exports = ResourceStateManager = {
  20. // The sync state is an identifier which must match for an
  21. // incremental update to be allowed.
  22. //
  23. // The initial value is passed in and stored on a full
  24. // compile, along with the list of resources..
  25. //
  26. // Subsequent incremental compiles must come with the same value - if
  27. // not they will be rejected with a 409 Conflict response. The
  28. // previous list of resources is returned.
  29. //
  30. // An incremental compile can only update existing files with new
  31. // content. The sync state identifier must change if any docs or
  32. // files are moved, added, deleted or renamed.
  33. SYNC_STATE_FILE: '.project-sync-state',
  34. SYNC_STATE_MAX_SIZE: 128 * 1024,
  35. saveProjectState(state, resources, basePath, callback) {
  36. const stateFile = Path.join(basePath, this.SYNC_STATE_FILE)
  37. if (state == null) {
  38. // remove the file if no state passed in
  39. logger.log({ state, basePath }, 'clearing sync state')
  40. fs.unlink(stateFile, function (err) {
  41. if (err && err.code !== 'ENOENT') {
  42. return callback(err)
  43. } else {
  44. return callback()
  45. }
  46. })
  47. } else {
  48. logger.log({ state, basePath }, 'writing sync state')
  49. const resourceList = resources.map((resource) => resource.path)
  50. fs.writeFile(
  51. stateFile,
  52. [...resourceList, `stateHash:${state}`].join('\n'),
  53. callback
  54. )
  55. }
  56. },
  57. checkProjectStateMatches(state, basePath, callback) {
  58. const stateFile = Path.join(basePath, this.SYNC_STATE_FILE)
  59. const size = this.SYNC_STATE_MAX_SIZE
  60. SafeReader.readFile(stateFile, size, 'utf8', function (
  61. err,
  62. result,
  63. bytesRead
  64. ) {
  65. if (err) {
  66. return callback(err)
  67. }
  68. if (bytesRead === size) {
  69. logger.error(
  70. { file: stateFile, size, bytesRead },
  71. 'project state file truncated'
  72. )
  73. }
  74. const array = result.toString().split('\n')
  75. const adjustedLength = Math.max(array.length, 1)
  76. const resourceList = array.slice(0, adjustedLength - 1)
  77. const oldState = array[adjustedLength - 1]
  78. const newState = `stateHash:${state}`
  79. logger.log(
  80. { state, oldState, basePath, stateMatches: newState === oldState },
  81. 'checking sync state'
  82. )
  83. if (newState !== oldState) {
  84. return callback(
  85. new Errors.FilesOutOfSyncError('invalid state for incremental update')
  86. )
  87. } else {
  88. const resources = resourceList.map((path) => ({ path }))
  89. callback(null, resources)
  90. }
  91. })
  92. },
  93. checkResourceFiles(resources, allFiles, basePath, callback) {
  94. // check the paths are all relative to current directory
  95. let file
  96. for (file of resources || []) {
  97. if (file && file.path) {
  98. const dirs = file.path.split('/')
  99. if (dirs.indexOf('..') !== -1) {
  100. return callback(new Error('relative path in resource file list'))
  101. }
  102. }
  103. }
  104. // check if any of the input files are not present in list of files
  105. const seenFile = {}
  106. for (file of allFiles) {
  107. seenFile[file] = true
  108. }
  109. const missingFiles = resources
  110. .filter((resource) => !seenFile[resource.path])
  111. .map((resource) => resource.path)
  112. if (missingFiles && missingFiles.length > 0) {
  113. logger.err(
  114. { missingFiles, basePath, allFiles, resources },
  115. 'missing input files for project'
  116. )
  117. return callback(
  118. new Errors.FilesOutOfSyncError(
  119. 'resource files missing in incremental update'
  120. )
  121. )
  122. } else {
  123. callback()
  124. }
  125. },
  126. }