mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge branch 'master' of https://github.com/sharelatex/clsi-sharelatex
This commit is contained in:
commit
d45a5e6d42
10 changed files with 97 additions and 68 deletions
|
@ -84,7 +84,7 @@ if Settings.smokeTest
|
||||||
|
|
||||||
app.get "/health_check", (req, res)->
|
app.get "/health_check", (req, res)->
|
||||||
res.contentType(resCacher?.setContentType)
|
res.contentType(resCacher?.setContentType)
|
||||||
res.send resCacher?.code, resCacher?.body
|
res.status(resCacher?.code).send(resCacher?.body)
|
||||||
|
|
||||||
profiler = require "v8-profiler"
|
profiler = require "v8-profiler"
|
||||||
app.get "/profile", (req, res) ->
|
app.get "/profile", (req, res) ->
|
||||||
|
@ -101,7 +101,7 @@ app.get "/heapdump", (req, res)->
|
||||||
|
|
||||||
app.use (error, req, res, next) ->
|
app.use (error, req, res, next) ->
|
||||||
logger.error err: error, "server error"
|
logger.error err: error, "server error"
|
||||||
res.send error?.statusCode || 500
|
res.sendStatus(error?.statusCode || 500)
|
||||||
|
|
||||||
app.listen port = (Settings.internal?.clsi?.port or 3013), host = (Settings.internal?.clsi?.host or "localhost"), (error) ->
|
app.listen port = (Settings.internal?.clsi?.port or 3013), host = (Settings.internal?.clsi?.host or "localhost"), (error) ->
|
||||||
logger.info "CLSI starting up, listening on #{host}:#{port}"
|
logger.info "CLSI starting up, listening on #{host}:#{port}"
|
||||||
|
|
|
@ -28,7 +28,7 @@ module.exports = CompileController =
|
||||||
status = "success"
|
status = "success"
|
||||||
|
|
||||||
timer.done()
|
timer.done()
|
||||||
res.send (code or 200), {
|
res.status(code or 200).send {
|
||||||
compile:
|
compile:
|
||||||
status: status
|
status: status
|
||||||
error: error?.message or error
|
error: error?.message or error
|
||||||
|
@ -41,7 +41,7 @@ module.exports = CompileController =
|
||||||
clearCache: (req, res, next = (error) ->) ->
|
clearCache: (req, res, next = (error) ->) ->
|
||||||
ProjectPersistenceManager.clearProject req.params.project_id, (error) ->
|
ProjectPersistenceManager.clearProject req.params.project_id, (error) ->
|
||||||
return next(error) if error?
|
return next(error) if error?
|
||||||
res.send 204 # No content
|
res.sendStatus(204) # No content
|
||||||
|
|
||||||
syncFromCode: (req, res, next = (error) ->) ->
|
syncFromCode: (req, res, next = (error) ->) ->
|
||||||
file = req.query.file
|
file = req.query.file
|
||||||
|
|
|
@ -9,7 +9,7 @@ module.exports = OutputFileOptimiser =
|
||||||
optimiseFile: (src, dst, callback = (error) ->) ->
|
optimiseFile: (src, dst, callback = (error) ->) ->
|
||||||
# check output file (src) and see if we can optimise it, storing
|
# check output file (src) and see if we can optimise it, storing
|
||||||
# the result in the build directory (dst)
|
# the result in the build directory (dst)
|
||||||
if src.match(/\.pdf$/)
|
if src.match(/\/output\.pdf$/)
|
||||||
OutputFileOptimiser.optimisePDF src, dst, callback
|
OutputFileOptimiser.optimisePDF src, dst, callback
|
||||||
else
|
else
|
||||||
callback (null)
|
callback (null)
|
||||||
|
|
|
@ -8,11 +8,11 @@ module.exports = ProjectPersistenceManager =
|
||||||
EXPIRY_TIMEOUT: oneDay = 24 * 60 * 60 * 1000 #ms
|
EXPIRY_TIMEOUT: oneDay = 24 * 60 * 60 * 1000 #ms
|
||||||
|
|
||||||
markProjectAsJustAccessed: (project_id, callback = (error) ->) ->
|
markProjectAsJustAccessed: (project_id, callback = (error) ->) ->
|
||||||
db.Project.findOrCreate(project_id: project_id)
|
db.Project.findOrCreate(where: {project_id: project_id})
|
||||||
.success(
|
.spread(
|
||||||
(project) ->
|
(project, created) ->
|
||||||
project.updateAttributes(lastAccessed: new Date())
|
project.updateAttributes(lastAccessed: new Date())
|
||||||
.success(() -> callback())
|
.then(() -> callback())
|
||||||
.error callback
|
.error callback
|
||||||
)
|
)
|
||||||
.error callback
|
.error callback
|
||||||
|
@ -41,14 +41,12 @@ module.exports = ProjectPersistenceManager =
|
||||||
callback()
|
callback()
|
||||||
|
|
||||||
_clearProjectFromDatabase: (project_id, callback = (error) ->) ->
|
_clearProjectFromDatabase: (project_id, callback = (error) ->) ->
|
||||||
db.Project.destroy(project_id: project_id)
|
db.Project.destroy(where: {project_id: project_id})
|
||||||
.success(() -> callback())
|
.then(() -> callback())
|
||||||
.error callback
|
.error callback
|
||||||
|
|
||||||
_findExpiredProjectIds: (callback = (error, project_ids) ->) ->
|
_findExpiredProjectIds: (callback = (error, project_ids) ->) ->
|
||||||
db.Project.findAll(where: ["lastAccessed < ?", new Date(Date.now() - ProjectPersistenceManager.EXPIRY_TIMEOUT)])
|
db.Project.findAll(where: ["lastAccessed < ?", new Date(Date.now() - ProjectPersistenceManager.EXPIRY_TIMEOUT)])
|
||||||
.success(
|
.then((projects) ->
|
||||||
(projects) ->
|
callback null, projects.map((project) -> project.project_id)
|
||||||
callback null, projects.map((project) -> project.project_id)
|
).error callback
|
||||||
)
|
|
||||||
.error callback
|
|
||||||
|
|
|
@ -75,8 +75,9 @@ module.exports = UrlCache =
|
||||||
readStream = fs.createReadStream(from)
|
readStream = fs.createReadStream(from)
|
||||||
writeStream.on "error", callbackOnce
|
writeStream.on "error", callbackOnce
|
||||||
readStream.on "error", callbackOnce
|
readStream.on "error", callbackOnce
|
||||||
writeStream.on "close", () -> callbackOnce()
|
writeStream.on "close", callbackOnce
|
||||||
readStream.pipe(writeStream)
|
writeStream.on "open", () ->
|
||||||
|
readStream.pipe(writeStream)
|
||||||
|
|
||||||
_clearUrlFromCache: (project_id, url, callback = (error) ->) ->
|
_clearUrlFromCache: (project_id, url, callback = (error) ->) ->
|
||||||
UrlCache._clearUrlDetails project_id, url, (error) ->
|
UrlCache._clearUrlDetails project_id, url, (error) ->
|
||||||
|
@ -90,27 +91,27 @@ module.exports = UrlCache =
|
||||||
|
|
||||||
_findUrlDetails: (project_id, url, callback = (error, urlDetails) ->) ->
|
_findUrlDetails: (project_id, url, callback = (error, urlDetails) ->) ->
|
||||||
db.UrlCache.find(where: { url: url, project_id: project_id })
|
db.UrlCache.find(where: { url: url, project_id: project_id })
|
||||||
.success((urlDetails) -> callback null, urlDetails)
|
.then((urlDetails) -> callback null, urlDetails)
|
||||||
.error callback
|
.error callback
|
||||||
|
|
||||||
_updateOrCreateUrlDetails: (project_id, url, lastModified, callback = (error) ->) ->
|
_updateOrCreateUrlDetails: (project_id, url, lastModified, callback = (error) ->) ->
|
||||||
db.UrlCache.findOrCreate(url: url, project_id: project_id)
|
db.UrlCache.findOrCreate(where: {url: url, project_id: project_id})
|
||||||
.success(
|
.spread(
|
||||||
(urlDetails) ->
|
(urlDetails, created) ->
|
||||||
urlDetails.updateAttributes(lastModified: lastModified)
|
urlDetails.updateAttributes(lastModified: lastModified)
|
||||||
.success(() -> callback())
|
.then(() -> callback())
|
||||||
.error(callback)
|
.error(callback)
|
||||||
)
|
)
|
||||||
.error callback
|
.error callback
|
||||||
|
|
||||||
_clearUrlDetails: (project_id, url, callback = (error) ->) ->
|
_clearUrlDetails: (project_id, url, callback = (error) ->) ->
|
||||||
db.UrlCache.destroy(url: url, project_id: project_id)
|
db.UrlCache.destroy(where: {url: url, project_id: project_id})
|
||||||
.success(() -> callback null)
|
.then(() -> callback null)
|
||||||
.error callback
|
.error callback
|
||||||
|
|
||||||
_findAllUrlsInProject: (project_id, callback = (error, urls) ->) ->
|
_findAllUrlsInProject: (project_id, callback = (error, urls) ->) ->
|
||||||
db.UrlCache.findAll(where: { project_id: project_id })
|
db.UrlCache.findAll(where: { project_id: project_id })
|
||||||
.success(
|
.then(
|
||||||
(urlEntries) ->
|
(urlEntries) ->
|
||||||
callback null, urlEntries.map((entry) -> entry.url)
|
callback null, urlEntries.map((entry) -> entry.url)
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,33 +2,55 @@ request = require("request").defaults(jar: false)
|
||||||
fs = require("fs")
|
fs = require("fs")
|
||||||
logger = require "logger-sharelatex"
|
logger = require "logger-sharelatex"
|
||||||
|
|
||||||
|
oneMinute = 60 * 1000
|
||||||
|
|
||||||
module.exports = UrlFetcher =
|
module.exports = UrlFetcher =
|
||||||
pipeUrlToFile: (url, filePath, _callback = (error) ->) ->
|
pipeUrlToFile: (url, filePath, _callback = (error) ->) ->
|
||||||
callbackOnce = (error) ->
|
callbackOnce = (error) ->
|
||||||
cleanUp error, (error) ->
|
clearTimeout timeoutHandler if timeoutHandler?
|
||||||
_callback(error)
|
_callback(error)
|
||||||
_callback = () ->
|
_callback = () ->
|
||||||
|
|
||||||
cleanUp = (error, callback) ->
|
timeoutHandler = setTimeout () ->
|
||||||
if error?
|
timeoutHandler = null
|
||||||
logger.log filePath: filePath, "deleting file from cache due to error"
|
logger.error url:url, filePath: filePath, "Timed out downloading file to cache"
|
||||||
fs.unlink filePath, (err) ->
|
callbackOnce(new Error("Timed out downloading file to cache #{url}"))
|
||||||
if err?
|
# FIXME: maybe need to close fileStream here
|
||||||
logger.err err: err, filePath: filePath, "error deleting file from cache"
|
, 3 * oneMinute
|
||||||
callback(error)
|
|
||||||
else
|
|
||||||
callback()
|
|
||||||
|
|
||||||
fileStream = fs.createWriteStream(filePath)
|
logger.log url:url, filePath: filePath, "started downloading url to cache"
|
||||||
fileStream.on 'error', (error) ->
|
urlStream = request.get({url: url, timeout: oneMinute})
|
||||||
logger.error err: error, url:url, filePath: filePath, "error writing file into cache"
|
urlStream.pause() # stop data flowing until we are ready
|
||||||
callbackOnce(error)
|
|
||||||
|
# attach handlers before setting up pipes
|
||||||
|
urlStream.on "error", (error) ->
|
||||||
|
logger.error err: error, url:url, filePath: filePath, "error downloading url"
|
||||||
|
callbackOnce(error or new Error("Something went wrong downloading the URL #{url}"))
|
||||||
|
|
||||||
|
urlStream.on "end", () ->
|
||||||
|
logger.log url:url, filePath: filePath, "finished downloading file into cache"
|
||||||
|
|
||||||
logger.log url:url, filePath: filePath, "downloading url to cache"
|
|
||||||
urlStream = request.get(url)
|
|
||||||
urlStream.on "response", (res) ->
|
urlStream.on "response", (res) ->
|
||||||
if res.statusCode >= 200 and res.statusCode < 300
|
if res.statusCode >= 200 and res.statusCode < 300
|
||||||
|
fileStream = fs.createWriteStream(filePath)
|
||||||
|
|
||||||
|
# attach handlers before setting up pipes
|
||||||
|
fileStream.on 'error', (error) ->
|
||||||
|
logger.error err: error, url:url, filePath: filePath, "error writing file into cache"
|
||||||
|
fs.unlink filePath, (err) ->
|
||||||
|
if err?
|
||||||
|
logger.err err: err, filePath: filePath, "error deleting file from cache"
|
||||||
|
callbackOnce(error)
|
||||||
|
|
||||||
|
fileStream.on 'finish', () ->
|
||||||
|
logger.log url:url, filePath: filePath, "finished writing file into cache"
|
||||||
|
callbackOnce()
|
||||||
|
|
||||||
|
fileStream.on 'pipe', () ->
|
||||||
|
logger.log url:url, filePath: filePath, "piping into filestream"
|
||||||
|
|
||||||
urlStream.pipe(fileStream)
|
urlStream.pipe(fileStream)
|
||||||
|
urlStream.resume() # now we are ready to handle the data
|
||||||
else
|
else
|
||||||
logger.error statusCode: res.statusCode, url:url, filePath: filePath, "unexpected status code downloading url to cache"
|
logger.error statusCode: res.statusCode, url:url, filePath: filePath, "unexpected status code downloading url to cache"
|
||||||
# https://nodejs.org/api/http.html#http_class_http_clientrequest
|
# https://nodejs.org/api/http.html#http_class_http_clientrequest
|
||||||
|
@ -39,15 +61,5 @@ module.exports = UrlFetcher =
|
||||||
# method. Until the data is consumed, the 'end' event will not
|
# method. Until the data is consumed, the 'end' event will not
|
||||||
# fire. Also, until the data is read it will consume memory
|
# fire. Also, until the data is read it will consume memory
|
||||||
# that can eventually lead to a 'process out of memory' error.
|
# that can eventually lead to a 'process out of memory' error.
|
||||||
urlStream.on 'data', () -> # discard the data
|
urlStream.resume() # discard the data
|
||||||
callbackOnce(new Error("URL returned non-success status code: #{res.statusCode} #{url}"))
|
callbackOnce(new Error("URL returned non-success status code: #{res.statusCode} #{url}"))
|
||||||
|
|
||||||
urlStream.on "error", (error) ->
|
|
||||||
logger.error err: error, url:url, filePath: filePath, "error downloading url"
|
|
||||||
callbackOnce(error or new Error("Something went wrong downloading the URL #{url}"))
|
|
||||||
|
|
||||||
urlStream.on "end", () ->
|
|
||||||
# FIXME: what if we get an error writing the file? Maybe we
|
|
||||||
# should be using the fileStream end event as the point of
|
|
||||||
# callback.
|
|
||||||
callbackOnce()
|
|
||||||
|
|
|
@ -16,11 +16,20 @@ module.exports =
|
||||||
url: Sequelize.STRING
|
url: Sequelize.STRING
|
||||||
project_id: Sequelize.STRING
|
project_id: Sequelize.STRING
|
||||||
lastModified: Sequelize.DATE
|
lastModified: Sequelize.DATE
|
||||||
|
}, {
|
||||||
|
indexes: [
|
||||||
|
{fields: ['url', 'project_id']},
|
||||||
|
{fields: ['project_id']}
|
||||||
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
Project: sequelize.define("Project", {
|
Project: sequelize.define("Project", {
|
||||||
project_id: Sequelize.STRING
|
project_id: {type: Sequelize.STRING, primaryKey: true}
|
||||||
lastAccessed: Sequelize.DATE
|
lastAccessed: Sequelize.DATE
|
||||||
|
}, {
|
||||||
|
indexes: [
|
||||||
|
{fields: ['lastAccessed']}
|
||||||
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
sync: () -> sequelize.sync()
|
sync: () -> sequelize.sync()
|
||||||
|
|
|
@ -11,12 +11,12 @@
|
||||||
"async": "0.2.9",
|
"async": "0.2.9",
|
||||||
"lynx": "0.0.11",
|
"lynx": "0.0.11",
|
||||||
"mkdirp": "0.3.5",
|
"mkdirp": "0.3.5",
|
||||||
"mysql": "2.0.0-alpha7",
|
"mysql": "2.6.2",
|
||||||
"request": "~2.21.0",
|
"request": "~2.21.0",
|
||||||
"logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.0.0",
|
"logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.0.0",
|
||||||
"settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0",
|
"settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0",
|
||||||
"metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git",
|
"metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git",
|
||||||
"sequelize": "2.0.0-beta.2",
|
"sequelize": "^2.1.3",
|
||||||
"wrench": "~1.5.4",
|
"wrench": "~1.5.4",
|
||||||
"smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git",
|
"smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git",
|
||||||
"sqlite3": "~2.2.0",
|
"sqlite3": "~2.2.0",
|
||||||
|
|
|
@ -44,6 +44,7 @@ describe "CompileController", ->
|
||||||
}]
|
}]
|
||||||
@RequestParser.parse = sinon.stub().callsArgWith(1, null, @request)
|
@RequestParser.parse = sinon.stub().callsArgWith(1, null, @request)
|
||||||
@ProjectPersistenceManager.markProjectAsJustAccessed = sinon.stub().callsArg(1)
|
@ProjectPersistenceManager.markProjectAsJustAccessed = sinon.stub().callsArg(1)
|
||||||
|
@res.status = sinon.stub().returnsThis()
|
||||||
@res.send = sinon.stub()
|
@res.send = sinon.stub()
|
||||||
|
|
||||||
describe "successfully", ->
|
describe "successfully", ->
|
||||||
|
@ -67,8 +68,9 @@ describe "CompileController", ->
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
it "should return the JSON response", ->
|
it "should return the JSON response", ->
|
||||||
|
@res.status.calledWith(200).should.equal true
|
||||||
@res.send
|
@res.send
|
||||||
.calledWith(200,
|
.calledWith(
|
||||||
compile:
|
compile:
|
||||||
status: "success"
|
status: "success"
|
||||||
error: null
|
error: null
|
||||||
|
@ -85,8 +87,9 @@ describe "CompileController", ->
|
||||||
@CompileController.compile @req, @res
|
@CompileController.compile @req, @res
|
||||||
|
|
||||||
it "should return the JSON response with the error", ->
|
it "should return the JSON response with the error", ->
|
||||||
|
@res.status.calledWith(500).should.equal true
|
||||||
@res.send
|
@res.send
|
||||||
.calledWith(500,
|
.calledWith(
|
||||||
compile:
|
compile:
|
||||||
status: "error"
|
status: "error"
|
||||||
error: @message
|
error: @message
|
||||||
|
@ -102,8 +105,9 @@ describe "CompileController", ->
|
||||||
@CompileController.compile @req, @res
|
@CompileController.compile @req, @res
|
||||||
|
|
||||||
it "should return the JSON response with the timeout status", ->
|
it "should return the JSON response with the timeout status", ->
|
||||||
|
@res.status.calledWith(200).should.equal true
|
||||||
@res.send
|
@res.send
|
||||||
.calledWith(200,
|
.calledWith(
|
||||||
compile:
|
compile:
|
||||||
status: "timedout"
|
status: "timedout"
|
||||||
error: @message
|
error: @message
|
||||||
|
@ -117,8 +121,9 @@ describe "CompileController", ->
|
||||||
@CompileController.compile @req, @res
|
@CompileController.compile @req, @res
|
||||||
|
|
||||||
it "should return the JSON response with the failure status", ->
|
it "should return the JSON response with the failure status", ->
|
||||||
|
@res.status.calledWith(200).should.equal true
|
||||||
@res.send
|
@res.send
|
||||||
.calledWith(200,
|
.calledWith(
|
||||||
compile:
|
compile:
|
||||||
error: null
|
error: null
|
||||||
status: "failure"
|
status: "failure"
|
||||||
|
|
|
@ -22,25 +22,29 @@ describe "UrlFetcher", ->
|
||||||
@path = "/path/to/file/on/disk"
|
@path = "/path/to/file/on/disk"
|
||||||
@request.get = sinon.stub().returns(@urlStream = new EventEmitter)
|
@request.get = sinon.stub().returns(@urlStream = new EventEmitter)
|
||||||
@urlStream.pipe = sinon.stub()
|
@urlStream.pipe = sinon.stub()
|
||||||
@fs.createWriteStream = sinon.stub().returns(@fileStream = { on: () -> })
|
@urlStream.pause = sinon.stub()
|
||||||
|
@urlStream.resume = sinon.stub()
|
||||||
|
@fs.createWriteStream = sinon.stub().returns(@fileStream = new EventEmitter)
|
||||||
@fs.unlink = (file, callback) -> callback()
|
@fs.unlink = (file, callback) -> callback()
|
||||||
@UrlFetcher.pipeUrlToFile(@url, @path, @callback)
|
@UrlFetcher.pipeUrlToFile(@url, @path, @callback)
|
||||||
|
|
||||||
it "should request the URL", ->
|
it "should request the URL", ->
|
||||||
@request.get
|
@request.get
|
||||||
.calledWith(@url)
|
.calledWith(sinon.match {"url": @url})
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
it "should open the file for writing", ->
|
|
||||||
@fs.createWriteStream
|
|
||||||
.calledWith(@path)
|
|
||||||
.should.equal true
|
|
||||||
|
|
||||||
describe "successfully", ->
|
describe "successfully", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@res = statusCode: 200
|
@res = statusCode: 200
|
||||||
@urlStream.emit "response", @res
|
@urlStream.emit "response", @res
|
||||||
@urlStream.emit "end"
|
@urlStream.emit "end"
|
||||||
|
@fileStream.emit "finish"
|
||||||
|
|
||||||
|
it "should open the file for writing", ->
|
||||||
|
@fs.createWriteStream
|
||||||
|
.calledWith(@path)
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
it "should pipe the URL to the file", ->
|
it "should pipe the URL to the file", ->
|
||||||
@urlStream.pipe
|
@urlStream.pipe
|
||||||
|
|
Loading…
Reference in a new issue