mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #66 from sharelatex/bg-compile-from-redis
Write files incrementally
This commit is contained in:
commit
43f424295e
12 changed files with 272 additions and 15 deletions
|
@ -4,6 +4,7 @@ Settings = require "settings-sharelatex"
|
||||||
Metrics = require "./Metrics"
|
Metrics = require "./Metrics"
|
||||||
ProjectPersistenceManager = require "./ProjectPersistenceManager"
|
ProjectPersistenceManager = require "./ProjectPersistenceManager"
|
||||||
logger = require "logger-sharelatex"
|
logger = require "logger-sharelatex"
|
||||||
|
Errors = require "./Errors"
|
||||||
|
|
||||||
module.exports = CompileController =
|
module.exports = CompileController =
|
||||||
compile: (req, res, next = (error) ->) ->
|
compile: (req, res, next = (error) ->) ->
|
||||||
|
@ -15,7 +16,10 @@ module.exports = CompileController =
|
||||||
ProjectPersistenceManager.markProjectAsJustAccessed request.project_id, (error) ->
|
ProjectPersistenceManager.markProjectAsJustAccessed request.project_id, (error) ->
|
||||||
return next(error) if error?
|
return next(error) if error?
|
||||||
CompileManager.doCompile request, (error, outputFiles = []) ->
|
CompileManager.doCompile request, (error, outputFiles = []) ->
|
||||||
if error?.terminated
|
if error instanceof Errors.FilesOutOfSyncError
|
||||||
|
code = 409 # Http 409 Conflict
|
||||||
|
status = "retry"
|
||||||
|
else if error?.terminated
|
||||||
status = "terminated"
|
status = "terminated"
|
||||||
else if error?.validate
|
else if error?.validate
|
||||||
status = "validation-#{error.validate}"
|
status = "validation-#{error.validate}"
|
||||||
|
|
|
@ -31,13 +31,17 @@ module.exports = CompileManager =
|
||||||
|
|
||||||
timer = new Metrics.Timer("write-to-disk")
|
timer = new Metrics.Timer("write-to-disk")
|
||||||
logger.log project_id: request.project_id, user_id: request.user_id, "syncing resources to disk"
|
logger.log project_id: request.project_id, user_id: request.user_id, "syncing resources to disk"
|
||||||
ResourceWriter.syncResourcesToDisk request.project_id, request.resources, compileDir, (error) ->
|
ResourceWriter.syncResourcesToDisk request, compileDir, (error, resourceList) ->
|
||||||
if error?
|
# NOTE: resourceList is insecure, it should only be used to exclude files from the output list
|
||||||
|
if error? and error instanceof Errors.FilesOutOfSyncError
|
||||||
|
logger.warn project_id: request.project_id, user_id: request.user_id, "files out of sync, please retry"
|
||||||
|
return callback(error)
|
||||||
|
else if error?
|
||||||
logger.err err:error, project_id: request.project_id, user_id: request.user_id, "error writing resources to disk"
|
logger.err err:error, project_id: request.project_id, user_id: request.user_id, "error writing resources to disk"
|
||||||
return callback(error)
|
return callback(error)
|
||||||
logger.log project_id: request.project_id, user_id: request.user_id, time_taken: Date.now() - timer.start, "written files to disk"
|
logger.log project_id: request.project_id, user_id: request.user_id, time_taken: Date.now() - timer.start, "written files to disk"
|
||||||
timer.done()
|
timer.done()
|
||||||
|
|
||||||
injectDraftModeIfRequired = (callback) ->
|
injectDraftModeIfRequired = (callback) ->
|
||||||
if request.draft
|
if request.draft
|
||||||
DraftModeManager.injectDraftMode Path.join(compileDir, request.rootResourcePath), callback
|
DraftModeManager.injectDraftMode Path.join(compileDir, request.rootResourcePath), callback
|
||||||
|
@ -45,7 +49,7 @@ module.exports = CompileManager =
|
||||||
callback()
|
callback()
|
||||||
|
|
||||||
createTikzFileIfRequired = (callback) ->
|
createTikzFileIfRequired = (callback) ->
|
||||||
if TikzManager.needsOutputFile(request.rootResourcePath, request.resources)
|
if TikzManager.needsOutputFile(request.rootResourcePath, resourceList)
|
||||||
TikzManager.injectOutputFile compileDir, request.rootResourcePath, callback
|
TikzManager.injectOutputFile compileDir, request.rootResourcePath, callback
|
||||||
else
|
else
|
||||||
callback()
|
callback()
|
||||||
|
@ -91,7 +95,7 @@ module.exports = CompileManager =
|
||||||
error.validate = "fail"
|
error.validate = "fail"
|
||||||
# compile was killed by user, was a validation, or a compile which failed validation
|
# compile was killed by user, was a validation, or a compile which failed validation
|
||||||
if error?.terminated or error?.validate
|
if error?.terminated or error?.validate
|
||||||
OutputFileFinder.findOutputFiles request.resources, compileDir, (err, outputFiles) ->
|
OutputFileFinder.findOutputFiles resourceList, compileDir, (err, outputFiles) ->
|
||||||
return callback(err) if err?
|
return callback(err) if err?
|
||||||
callback(error, outputFiles) # return output files so user can check logs
|
callback(error, outputFiles) # return output files so user can check logs
|
||||||
return
|
return
|
||||||
|
@ -111,7 +115,7 @@ module.exports = CompileManager =
|
||||||
if stats?["latex-runs"] > 0 and timings?["cpu-time"] > 0
|
if stats?["latex-runs"] > 0 and timings?["cpu-time"] > 0
|
||||||
Metrics.timing("run-compile-cpu-time-per-pass", timings["cpu-time"] / stats["latex-runs"])
|
Metrics.timing("run-compile-cpu-time-per-pass", timings["cpu-time"] / stats["latex-runs"])
|
||||||
|
|
||||||
OutputFileFinder.findOutputFiles request.resources, compileDir, (error, outputFiles) ->
|
OutputFileFinder.findOutputFiles resourceList, compileDir, (error, outputFiles) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
OutputCacheManager.saveOutputFiles outputFiles, compileDir, (error, newOutputFiles) ->
|
OutputCacheManager.saveOutputFiles outputFiles, compileDir, (error, newOutputFiles) ->
|
||||||
callback null, newOutputFiles
|
callback null, newOutputFiles
|
||||||
|
|
|
@ -5,6 +5,9 @@ module.exports = DraftModeManager =
|
||||||
injectDraftMode: (filename, callback = (error) ->) ->
|
injectDraftMode: (filename, callback = (error) ->) ->
|
||||||
fs.readFile filename, "utf8", (error, content) ->
|
fs.readFile filename, "utf8", (error, content) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
|
# avoid adding draft mode more than once
|
||||||
|
if content?.indexOf("\\documentclass\[draft") >= 0
|
||||||
|
return callback()
|
||||||
modified_content = DraftModeManager._injectDraftOption content
|
modified_content = DraftModeManager._injectDraftOption content
|
||||||
logger.log {
|
logger.log {
|
||||||
content: content.slice(0,1024), # \documentclass is normally v near the top
|
content: content.slice(0,1024), # \documentclass is normally v near the top
|
||||||
|
@ -18,4 +21,4 @@ module.exports = DraftModeManager =
|
||||||
# With existing options (must be first, otherwise both are applied)
|
# With existing options (must be first, otherwise both are applied)
|
||||||
.replace(/\\documentclass\[/g, "\\documentclass[draft,")
|
.replace(/\\documentclass\[/g, "\\documentclass[draft,")
|
||||||
# Without existing options
|
# Without existing options
|
||||||
.replace(/\\documentclass\{/g, "\\documentclass[draft]{")
|
.replace(/\\documentclass\{/g, "\\documentclass[draft]{")
|
||||||
|
|
|
@ -5,6 +5,13 @@ NotFoundError = (message) ->
|
||||||
return error
|
return error
|
||||||
NotFoundError.prototype.__proto__ = Error.prototype
|
NotFoundError.prototype.__proto__ = Error.prototype
|
||||||
|
|
||||||
|
FilesOutOfSyncError = (message) ->
|
||||||
|
error = new Error(message)
|
||||||
|
error.name = "FilesOutOfSyncError"
|
||||||
|
error.__proto__ = FilesOutOfSyncError.prototype
|
||||||
|
return error
|
||||||
|
FilesOutOfSyncError.prototype.__proto__ = Error.prototype
|
||||||
|
|
||||||
module.exports = Errors =
|
module.exports = Errors =
|
||||||
NotFoundError: NotFoundError
|
NotFoundError: NotFoundError
|
||||||
|
FilesOutOfSyncError: FilesOutOfSyncError
|
||||||
|
|
|
@ -32,6 +32,27 @@ module.exports = RequestParser =
|
||||||
compile.options.check,
|
compile.options.check,
|
||||||
type: "string"
|
type: "string"
|
||||||
|
|
||||||
|
# The syncType specifies whether the request contains all
|
||||||
|
# resources (full) or only those resources to be updated
|
||||||
|
# in-place (incremental).
|
||||||
|
response.syncType = @_parseAttribute "syncType",
|
||||||
|
compile.options.syncType,
|
||||||
|
validValues: ["full", "incremental"]
|
||||||
|
type: "string"
|
||||||
|
|
||||||
|
# The syncState is an identifier passed in with the request
|
||||||
|
# which has the property that it changes when any resource is
|
||||||
|
# added, deleted, moved or renamed.
|
||||||
|
#
|
||||||
|
# on syncType full the syncState identifier is passed in and
|
||||||
|
# stored
|
||||||
|
#
|
||||||
|
# on syncType incremental the syncState identifier must match
|
||||||
|
# the stored value
|
||||||
|
response.syncState = @_parseAttribute "syncState",
|
||||||
|
compile.options.syncState,
|
||||||
|
type: "string"
|
||||||
|
|
||||||
if response.timeout > RequestParser.MAX_TIMEOUT
|
if response.timeout > RequestParser.MAX_TIMEOUT
|
||||||
response.timeout = RequestParser.MAX_TIMEOUT
|
response.timeout = RequestParser.MAX_TIMEOUT
|
||||||
response.timeout = response.timeout * 1000 # milliseconds
|
response.timeout = response.timeout * 1000 # milliseconds
|
||||||
|
|
25
services/clsi/app/coffee/ResourceListManager.coffee
Normal file
25
services/clsi/app/coffee/ResourceListManager.coffee
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
Path = require "path"
|
||||||
|
fs = require "fs"
|
||||||
|
logger = require "logger-sharelatex"
|
||||||
|
settings = require("settings-sharelatex")
|
||||||
|
SafeReader = require "./SafeReader"
|
||||||
|
|
||||||
|
module.exports = ResourceListManager =
|
||||||
|
|
||||||
|
# This file is a list of the input files for the project, one per
|
||||||
|
# line, used to identify output files (i.e. files not on this list)
|
||||||
|
# when the incoming request is incremental.
|
||||||
|
RESOURCE_LIST_FILE: ".project-resource-list"
|
||||||
|
|
||||||
|
saveResourceList: (resources, basePath, callback = (error) ->) ->
|
||||||
|
resourceListFile = Path.join(basePath, @RESOURCE_LIST_FILE)
|
||||||
|
resourceList = (resource.path for resource in resources)
|
||||||
|
fs.writeFile resourceListFile, resourceList.join("\n"), callback
|
||||||
|
|
||||||
|
loadResourceList: (basePath, callback = (error) ->) ->
|
||||||
|
resourceListFile = Path.join(basePath, @RESOURCE_LIST_FILE)
|
||||||
|
# limit file to 128K, compile directory is user accessible
|
||||||
|
SafeReader.readFile resourceListFile, 128*1024, 'utf8', (err, resourceList) ->
|
||||||
|
return callback(err) if err?
|
||||||
|
resources = ({path: path} for path in resourceList?.toString()?.split("\n") or [])
|
||||||
|
callback(null, resources)
|
45
services/clsi/app/coffee/ResourceStateManager.coffee
Normal file
45
services/clsi/app/coffee/ResourceStateManager.coffee
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
Path = require "path"
|
||||||
|
fs = require "fs"
|
||||||
|
logger = require "logger-sharelatex"
|
||||||
|
settings = require("settings-sharelatex")
|
||||||
|
Errors = require "./Errors"
|
||||||
|
SafeReader = require "./SafeReader"
|
||||||
|
|
||||||
|
module.exports = ResourceStateManager =
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
# Subsequent incremental compiles must come with the same value - if
|
||||||
|
# not they will be rejected with a 409 Conflict response.
|
||||||
|
#
|
||||||
|
# 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"
|
||||||
|
|
||||||
|
saveProjectStateHash: (state, basePath, callback) ->
|
||||||
|
stateFile = Path.join(basePath, @SYNC_STATE_FILE)
|
||||||
|
if not state? # remove the file if no state passed in
|
||||||
|
logger.log state:state, basePath:basePath, "clearing sync state"
|
||||||
|
fs.unlink stateFile, (err) ->
|
||||||
|
if err? and err.code isnt 'ENOENT'
|
||||||
|
return callback(err)
|
||||||
|
else
|
||||||
|
return callback()
|
||||||
|
else
|
||||||
|
logger.log state:state, basePath:basePath, "writing sync state"
|
||||||
|
fs.writeFile stateFile, state, {encoding: 'ascii'}, callback
|
||||||
|
|
||||||
|
checkProjectStateHashMatches: (state, basePath, callback) ->
|
||||||
|
stateFile = Path.join(basePath, @SYNC_STATE_FILE)
|
||||||
|
SafeReader.readFile stateFile, 64, 'ascii', (err, oldState) ->
|
||||||
|
return callback(err) if err?
|
||||||
|
if state isnt oldState
|
||||||
|
return callback new Errors.FilesOutOfSyncError("invalid state for incremental update")
|
||||||
|
else
|
||||||
|
callback(null)
|
|
@ -4,6 +4,8 @@ fs = require "fs"
|
||||||
async = require "async"
|
async = require "async"
|
||||||
mkdirp = require "mkdirp"
|
mkdirp = require "mkdirp"
|
||||||
OutputFileFinder = require "./OutputFileFinder"
|
OutputFileFinder = require "./OutputFileFinder"
|
||||||
|
ResourceStateManager = require "./ResourceStateManager"
|
||||||
|
ResourceListManager = require "./ResourceListManager"
|
||||||
Metrics = require "./Metrics"
|
Metrics = require "./Metrics"
|
||||||
logger = require "logger-sharelatex"
|
logger = require "logger-sharelatex"
|
||||||
settings = require("settings-sharelatex")
|
settings = require("settings-sharelatex")
|
||||||
|
@ -11,7 +13,36 @@ settings = require("settings-sharelatex")
|
||||||
parallelFileDownloads = settings.parallelFileDownloads or 1
|
parallelFileDownloads = settings.parallelFileDownloads or 1
|
||||||
|
|
||||||
module.exports = ResourceWriter =
|
module.exports = ResourceWriter =
|
||||||
syncResourcesToDisk: (project_id, resources, basePath, callback = (error) ->) ->
|
|
||||||
|
syncResourcesToDisk: (request, basePath, callback = (error, resourceList) ->) ->
|
||||||
|
if request.syncType is "incremental"
|
||||||
|
ResourceStateManager.checkProjectStateHashMatches request.syncState, basePath, (error) ->
|
||||||
|
return callback(error) if error?
|
||||||
|
ResourceListManager.loadResourceList basePath, (error, resourceList) ->
|
||||||
|
return callback(error) if error?
|
||||||
|
ResourceWriter._removeExtraneousFiles resourceList, basePath, (error) =>
|
||||||
|
return callback(error) if error?
|
||||||
|
ResourceWriter.saveIncrementalResourcesToDisk request.project_id, request.resources, basePath, (error) ->
|
||||||
|
return callback(error) if error?
|
||||||
|
callback(null, resourceList)
|
||||||
|
else
|
||||||
|
@saveAllResourcesToDisk request.project_id, request.resources, basePath, (error) ->
|
||||||
|
return callback(error) if error?
|
||||||
|
ResourceStateManager.saveProjectStateHash request.syncState, basePath, (error) ->
|
||||||
|
return callback(error) if error?
|
||||||
|
ResourceListManager.saveResourceList request.resources, basePath, (error) =>
|
||||||
|
return callback(error) if error?
|
||||||
|
callback(null, request.resources)
|
||||||
|
|
||||||
|
saveIncrementalResourcesToDisk: (project_id, resources, basePath, callback = (error) ->) ->
|
||||||
|
@_createDirectory basePath, (error) =>
|
||||||
|
return callback(error) if error?
|
||||||
|
jobs = for resource in resources
|
||||||
|
do (resource) =>
|
||||||
|
(callback) => @_writeResourceToDisk(project_id, resource, basePath, callback)
|
||||||
|
async.parallelLimit jobs, parallelFileDownloads, callback
|
||||||
|
|
||||||
|
saveAllResourcesToDisk: (project_id, resources, basePath, callback = (error) ->) ->
|
||||||
@_createDirectory basePath, (error) =>
|
@_createDirectory basePath, (error) =>
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
@_removeExtraneousFiles resources, basePath, (error) =>
|
@_removeExtraneousFiles resources, basePath, (error) =>
|
||||||
|
@ -48,6 +79,8 @@ module.exports = ResourceWriter =
|
||||||
should_delete = true
|
should_delete = true
|
||||||
if path.match(/^output\./) or path.match(/\.aux$/) or path.match(/^cache\//) # knitr cache
|
if path.match(/^output\./) or path.match(/\.aux$/) or path.match(/^cache\//) # knitr cache
|
||||||
should_delete = false
|
should_delete = false
|
||||||
|
if path in ['.project-resource-list', '.project-sync-state']
|
||||||
|
should_delete = false
|
||||||
if path == "output.pdf" or path == "output.dvi" or path == "output.log" or path == "output.xdv"
|
if path == "output.pdf" or path == "output.dvi" or path == "output.log" or path == "output.xdv"
|
||||||
should_delete = true
|
should_delete = true
|
||||||
if path == "output.tex" # created by TikzManager if present in output files
|
if path == "output.tex" # created by TikzManager if present in output files
|
||||||
|
|
24
services/clsi/app/coffee/SafeReader.coffee
Normal file
24
services/clsi/app/coffee/SafeReader.coffee
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
fs = require "fs"
|
||||||
|
|
||||||
|
module.exports = SafeReader =
|
||||||
|
|
||||||
|
# safely read up to size bytes from a file and return result as a
|
||||||
|
# string
|
||||||
|
|
||||||
|
readFile: (file, size, encoding, callback = (error, result) ->) ->
|
||||||
|
fs.open file, 'r', (err, fd) ->
|
||||||
|
return callback() if err? and err.code is 'ENOENT'
|
||||||
|
return callback(err) if err?
|
||||||
|
|
||||||
|
# safely return always closing the file
|
||||||
|
callbackWithClose = (err, result) ->
|
||||||
|
fs.close fd, (err1) ->
|
||||||
|
return callback(err) if err?
|
||||||
|
return callback(err1) if err1?
|
||||||
|
callback(null, result)
|
||||||
|
|
||||||
|
buff = new Buffer(size, 0) # fill with zeros
|
||||||
|
fs.read fd, buff, 0, buff.length, 0, (err, bytesRead, buffer) ->
|
||||||
|
return callbackWithClose(err) if err?
|
||||||
|
result = buffer.toString(encoding, 0, bytesRead)
|
||||||
|
callbackWithClose(null, result)
|
|
@ -51,7 +51,7 @@ describe "CompileManager", ->
|
||||||
@env = {}
|
@env = {}
|
||||||
@Settings.compileDir = "compiles"
|
@Settings.compileDir = "compiles"
|
||||||
@compileDir = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}"
|
@compileDir = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}"
|
||||||
@ResourceWriter.syncResourcesToDisk = sinon.stub().callsArg(3)
|
@ResourceWriter.syncResourcesToDisk = sinon.stub().callsArgWith(2, null, @resources)
|
||||||
@LatexRunner.runLatex = sinon.stub().callsArg(2)
|
@LatexRunner.runLatex = sinon.stub().callsArg(2)
|
||||||
@OutputFileFinder.findOutputFiles = sinon.stub().callsArgWith(2, null, @output_files)
|
@OutputFileFinder.findOutputFiles = sinon.stub().callsArgWith(2, null, @output_files)
|
||||||
@OutputCacheManager.saveOutputFiles = sinon.stub().callsArgWith(2, null, @build_files)
|
@OutputCacheManager.saveOutputFiles = sinon.stub().callsArgWith(2, null, @build_files)
|
||||||
|
@ -64,7 +64,7 @@ describe "CompileManager", ->
|
||||||
|
|
||||||
it "should write the resources to disk", ->
|
it "should write the resources to disk", ->
|
||||||
@ResourceWriter.syncResourcesToDisk
|
@ResourceWriter.syncResourcesToDisk
|
||||||
.calledWith(@project_id, @resources, @compileDir)
|
.calledWith(@request, @compileDir)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
it "should run LaTeX", ->
|
it "should run LaTeX", ->
|
||||||
|
|
|
@ -242,3 +242,13 @@ describe "RequestParser", ->
|
||||||
it "should return an error", ->
|
it "should return an error", ->
|
||||||
@callback.calledWith("relative path in root resource")
|
@callback.calledWith("relative path in root resource")
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
|
describe "with an unknown syncType", ->
|
||||||
|
beforeEach ->
|
||||||
|
@validRequest.compile.options.syncType = "unexpected"
|
||||||
|
@RequestParser.parse @validRequest, @callback
|
||||||
|
@data = @callback.args[0][1]
|
||||||
|
|
||||||
|
it "should return an error", ->
|
||||||
|
@callback.calledWith("syncType attribute should be one of: full, incremental")
|
||||||
|
.should.equal true
|
||||||
|
|
|
@ -7,7 +7,11 @@ path = require "path"
|
||||||
describe "ResourceWriter", ->
|
describe "ResourceWriter", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@ResourceWriter = SandboxedModule.require modulePath, requires:
|
@ResourceWriter = SandboxedModule.require modulePath, requires:
|
||||||
"fs": @fs = { mkdir: sinon.stub().callsArg(1) }
|
"fs": @fs =
|
||||||
|
mkdir: sinon.stub().callsArg(1)
|
||||||
|
unlink: sinon.stub().callsArg(1)
|
||||||
|
"./ResourceListManager": @ResourceListManager = {}
|
||||||
|
"./ResourceStateManager": @ResourceStateManager = {}
|
||||||
"wrench": @wrench = {}
|
"wrench": @wrench = {}
|
||||||
"./UrlCache" : @UrlCache = {}
|
"./UrlCache" : @UrlCache = {}
|
||||||
"mkdirp" : @mkdirp = sinon.stub().callsArg(1)
|
"mkdirp" : @mkdirp = sinon.stub().callsArg(1)
|
||||||
|
@ -20,7 +24,7 @@ describe "ResourceWriter", ->
|
||||||
@basePath = "/path/to/write/files/to"
|
@basePath = "/path/to/write/files/to"
|
||||||
@callback = sinon.stub()
|
@callback = sinon.stub()
|
||||||
|
|
||||||
describe "syncResourcesToDisk", ->
|
describe "syncResourcesToDisk on a full request", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@resources = [
|
@resources = [
|
||||||
"resource-1-mock"
|
"resource-1-mock"
|
||||||
|
@ -29,7 +33,62 @@ describe "ResourceWriter", ->
|
||||||
]
|
]
|
||||||
@ResourceWriter._writeResourceToDisk = sinon.stub().callsArg(3)
|
@ResourceWriter._writeResourceToDisk = sinon.stub().callsArg(3)
|
||||||
@ResourceWriter._removeExtraneousFiles = sinon.stub().callsArg(2)
|
@ResourceWriter._removeExtraneousFiles = sinon.stub().callsArg(2)
|
||||||
@ResourceWriter.syncResourcesToDisk(@project_id, @resources, @basePath, @callback)
|
@ResourceStateManager.checkProjectStateHashMatches = sinon.stub().callsArg(2)
|
||||||
|
@ResourceStateManager.saveProjectStateHash = sinon.stub().callsArg(2)
|
||||||
|
@ResourceListManager.saveResourceList = sinon.stub().callsArg(2)
|
||||||
|
@ResourceListManager.loadResourceList = sinon.stub().callsArg(1)
|
||||||
|
@ResourceWriter.syncResourcesToDisk({
|
||||||
|
project_id: @project_id
|
||||||
|
syncState: @syncState = "0123456789abcdef"
|
||||||
|
resources: @resources
|
||||||
|
}, @basePath, @callback)
|
||||||
|
|
||||||
|
it "should remove old files", ->
|
||||||
|
@ResourceWriter._removeExtraneousFiles
|
||||||
|
.calledWith(@resources, @basePath)
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
|
it "should write each resource to disk", ->
|
||||||
|
for resource in @resources
|
||||||
|
@ResourceWriter._writeResourceToDisk
|
||||||
|
.calledWith(@project_id, resource, @basePath)
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
|
it "should store the sync state", ->
|
||||||
|
@ResourceStateManager.saveProjectStateHash
|
||||||
|
.calledWith(@syncState, @basePath)
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
|
it "should save the resource list", ->
|
||||||
|
@ResourceListManager.saveResourceList
|
||||||
|
.calledWith(@resources, @basePath)
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
|
it "should call the callback", ->
|
||||||
|
@callback.called.should.equal true
|
||||||
|
|
||||||
|
describe "syncResourcesToDisk on an incremental update", ->
|
||||||
|
beforeEach ->
|
||||||
|
@resources = [
|
||||||
|
"resource-1-mock"
|
||||||
|
]
|
||||||
|
@ResourceWriter._writeResourceToDisk = sinon.stub().callsArg(3)
|
||||||
|
@ResourceWriter._removeExtraneousFiles = sinon.stub().callsArg(2)
|
||||||
|
@ResourceStateManager.checkProjectStateHashMatches = sinon.stub().callsArg(2)
|
||||||
|
@ResourceStateManager.saveProjectStateHash = sinon.stub().callsArg(2)
|
||||||
|
@ResourceListManager.saveResourceList = sinon.stub().callsArg(2)
|
||||||
|
@ResourceListManager.loadResourceList = sinon.stub().callsArgWith(1, null, @resources)
|
||||||
|
@ResourceWriter.syncResourcesToDisk({
|
||||||
|
project_id: @project_id,
|
||||||
|
syncType: "incremental",
|
||||||
|
syncState: @syncState = "1234567890abcdef",
|
||||||
|
resources: @resources
|
||||||
|
}, @basePath, @callback)
|
||||||
|
|
||||||
|
it "should check the sync state matches", ->
|
||||||
|
@ResourceStateManager.checkProjectStateHashMatches
|
||||||
|
.calledWith(@syncState, @basePath)
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
it "should remove old files", ->
|
it "should remove old files", ->
|
||||||
@ResourceWriter._removeExtraneousFiles
|
@ResourceWriter._removeExtraneousFiles
|
||||||
|
@ -45,6 +104,28 @@ describe "ResourceWriter", ->
|
||||||
it "should call the callback", ->
|
it "should call the callback", ->
|
||||||
@callback.called.should.equal true
|
@callback.called.should.equal true
|
||||||
|
|
||||||
|
describe "syncResourcesToDisk on an incremental update when the state does not match", ->
|
||||||
|
beforeEach ->
|
||||||
|
@resources = [
|
||||||
|
"resource-1-mock"
|
||||||
|
]
|
||||||
|
@ResourceStateManager.checkProjectStateHashMatches = sinon.stub().callsArgWith(2, @error = new Error())
|
||||||
|
@ResourceWriter.syncResourcesToDisk({
|
||||||
|
project_id: @project_id,
|
||||||
|
syncType: "incremental",
|
||||||
|
syncState: @syncState = "1234567890abcdef",
|
||||||
|
resources: @resources
|
||||||
|
}, @basePath, @callback)
|
||||||
|
|
||||||
|
it "should check whether the sync state matches", ->
|
||||||
|
@ResourceStateManager.checkProjectStateHashMatches
|
||||||
|
.calledWith(@syncState, @basePath)
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
|
it "should call the callback with an error", ->
|
||||||
|
@callback.calledWith(@error).should.equal true
|
||||||
|
|
||||||
|
|
||||||
describe "_removeExtraneousFiles", ->
|
describe "_removeExtraneousFiles", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@output_files = [{
|
@output_files = [{
|
||||||
|
|
Loading…
Reference in a new issue