ResourceStateManager.coffee 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. Path = require "path"
  2. fs = require "fs"
  3. logger = require "logger-sharelatex"
  4. settings = require("settings-sharelatex")
  5. Errors = require "./Errors"
  6. SafeReader = require "./SafeReader"
  7. module.exports = ResourceStateManager =
  8. # The sync state is an identifier which must match for an
  9. # incremental update to be allowed.
  10. #
  11. # The initial value is passed in and stored on a full
  12. # compile, along with the list of resources..
  13. #
  14. # Subsequent incremental compiles must come with the same value - if
  15. # not they will be rejected with a 409 Conflict response. The
  16. # previous list of resources is returned.
  17. #
  18. # An incremental compile can only update existing files with new
  19. # content. The sync state identifier must change if any docs or
  20. # files are moved, added, deleted or renamed.
  21. SYNC_STATE_FILE: ".project-sync-state"
  22. SYNC_STATE_MAX_SIZE: 128*1024
  23. saveProjectState: (state, resources, basePath, callback = (error) ->) ->
  24. stateFile = Path.join(basePath, @SYNC_STATE_FILE)
  25. if not state? # remove the file if no state passed in
  26. logger.log state:state, basePath:basePath, "clearing sync state"
  27. fs.unlink stateFile, (err) ->
  28. if err? and err.code isnt 'ENOENT'
  29. return callback(err)
  30. else
  31. return callback()
  32. else
  33. logger.log state:state, basePath:basePath, "writing sync state"
  34. resourceList = (resource.path for resource in resources)
  35. fs.writeFile stateFile, [resourceList..., "stateHash:#{state}"].join("\n"), callback
  36. checkProjectStateMatches: (state, basePath, callback = (error, resources) ->) ->
  37. stateFile = Path.join(basePath, @SYNC_STATE_FILE)
  38. size = @SYNC_STATE_MAX_SIZE
  39. SafeReader.readFile stateFile, size, 'utf8', (err, result, bytesRead) ->
  40. return callback(err) if err?
  41. if bytesRead is size
  42. logger.error file:stateFile, size:size, bytesRead:bytesRead, "project state file truncated"
  43. [resourceList..., oldState] = result?.toString()?.split("\n") or []
  44. newState = "stateHash:#{state}"
  45. logger.log state:state, oldState: oldState, basePath:basePath, stateMatches: (newState is oldState), "checking sync state"
  46. if newState isnt oldState
  47. return callback new Errors.FilesOutOfSyncError("invalid state for incremental update")
  48. else
  49. resources = ({path: path} for path in resourceList)
  50. callback(null, resources)
  51. checkResourceFiles: (resources, allFiles, basePath, callback = (error) ->) ->
  52. # check the paths are all relative to current directory
  53. for file in resources or []
  54. for dir in file?.path?.split('/')
  55. if dir == '..'
  56. return callback new Error("relative path in resource file list")
  57. # check if any of the input files are not present in list of files
  58. seenFile = {}
  59. for file in allFiles
  60. seenFile[file] = true
  61. missingFiles = (resource.path for resource in resources when not seenFile[resource.path])
  62. if missingFiles?.length > 0
  63. logger.err missingFiles:missingFiles, basePath:basePath, allFiles:allFiles, resources:resources, "missing input files for project"
  64. return callback new Errors.FilesOutOfSyncError("resource files missing in incremental update")
  65. else
  66. callback()