ResourceStateManager.js 3.7 KB

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