| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116 |
- const Path = require('path')
- const fs = require('fs')
- const logger = require('@overleaf/logger')
- const Errors = require('./Errors')
- const SafeReader = require('./SafeReader')
- module.exports = {
- // The sync state is an identifier which must match for an
- // incremental update to be allowed.
- //
- // The initial value is passed in and stored on a full
- // compile, along with the list of resources..
- //
- // Subsequent incremental compiles must come with the same value - if
- // not they will be rejected with a 409 Conflict response. The
- // previous list of resources is returned.
- //
- // An incremental compile can only update existing files with new
- // content. The sync state identifier must change if any docs or
- // files are moved, added, deleted or renamed.
- SYNC_STATE_FILE: '.project-sync-state',
- SYNC_STATE_MAX_SIZE: 128 * 1024,
- saveProjectState(state, resources, basePath, callback) {
- const stateFile = Path.join(basePath, this.SYNC_STATE_FILE)
- if (state == null) {
- // remove the file if no state passed in
- logger.debug({ state, basePath }, 'clearing sync state')
- fs.unlink(stateFile, function (err) {
- if (err && err.code !== 'ENOENT') {
- return callback(err)
- } else {
- return callback()
- }
- })
- } else {
- logger.debug({ state, basePath }, 'writing sync state')
- const resourceList = resources.map(resource => resource.path)
- fs.writeFile(
- stateFile,
- [...resourceList, `stateHash:${state}`].join('\n'),
- callback
- )
- }
- },
- checkProjectStateMatches(state, basePath, callback) {
- const stateFile = Path.join(basePath, this.SYNC_STATE_FILE)
- const size = this.SYNC_STATE_MAX_SIZE
- SafeReader.readFile(
- stateFile,
- size,
- 'utf8',
- function (err, result, bytesRead) {
- if (err) {
- return callback(err)
- }
- if (bytesRead === size) {
- logger.error(
- { file: stateFile, size, bytesRead },
- 'project state file truncated'
- )
- }
- const array = result ? result.toString().split('\n') : []
- const adjustedLength = Math.max(array.length, 1)
- const resourceList = array.slice(0, adjustedLength - 1)
- const oldState = array[adjustedLength - 1]
- const newState = `stateHash:${state}`
- logger.debug(
- { state, oldState, basePath, stateMatches: newState === oldState },
- 'checking sync state'
- )
- if (newState !== oldState) {
- return callback(
- new Errors.FilesOutOfSyncError(
- 'invalid state for incremental update'
- )
- )
- } else {
- const resources = resourceList.map(path => ({ path }))
- callback(null, resources)
- }
- }
- )
- },
- checkResourceFiles(resources, allFiles, basePath, callback) {
- // check the paths are all relative to current directory
- const containsRelativePath = resource => {
- const dirs = resource.path.split('/')
- return dirs.indexOf('..') !== -1
- }
- if (resources.some(containsRelativePath)) {
- return callback(new Error('relative path in resource file list'))
- }
- // check if any of the input files are not present in list of files
- const seenFiles = new Set(allFiles)
- const missingFiles = resources
- .map(resource => resource.path)
- .filter(path => !seenFiles.has(path))
- if (missingFiles.length > 0) {
- logger.err(
- { missingFiles, basePath, allFiles, resources },
- 'missing input files for project'
- )
- return callback(
- new Errors.FilesOutOfSyncError(
- 'resource files missing in incremental update'
- )
- )
- } else {
- callback()
- }
- },
- }
|