CompileController.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. /* eslint-disable
  2. camelcase,
  3. no-unused-vars,
  4. */
  5. // TODO: This file was created by bulk-decaffeinate.
  6. // Fix any style issues and re-enable lint.
  7. /*
  8. * decaffeinate suggestions:
  9. * DS101: Remove unnecessary use of Array.from
  10. * DS102: Remove unnecessary code created because of implicit returns
  11. * DS207: Consider shorter variations of null checks
  12. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  13. */
  14. let CompileController
  15. const RequestParser = require('./RequestParser')
  16. const CompileManager = require('./CompileManager')
  17. const Settings = require('@overleaf/settings')
  18. const Metrics = require('./Metrics')
  19. const ProjectPersistenceManager = require('./ProjectPersistenceManager')
  20. const logger = require('@overleaf/logger')
  21. const Errors = require('./Errors')
  22. function isImageNameAllowed(imageName) {
  23. const ALLOWED_IMAGES =
  24. Settings.clsi && Settings.clsi.docker && Settings.clsi.docker.allowedImages
  25. return !ALLOWED_IMAGES || ALLOWED_IMAGES.includes(imageName)
  26. }
  27. module.exports = CompileController = {
  28. lastSuccessfulCompile: 0,
  29. compile(req, res, next) {
  30. if (next == null) {
  31. next = function () {}
  32. }
  33. const timer = new Metrics.Timer('compile-request')
  34. return RequestParser.parse(req.body, function (error, request) {
  35. if (error != null) {
  36. return next(error)
  37. }
  38. timer.opts = request.metricsOpts
  39. request.project_id = req.params.project_id
  40. if (req.params.user_id != null) {
  41. request.user_id = req.params.user_id
  42. }
  43. return ProjectPersistenceManager.markProjectAsJustAccessed(
  44. request.project_id,
  45. function (error) {
  46. if (error != null) {
  47. return next(error)
  48. }
  49. return CompileManager.doCompileWithLock(
  50. request,
  51. function (error, outputFiles, stats, timings) {
  52. let code, status
  53. if (outputFiles == null) {
  54. outputFiles = []
  55. }
  56. if (error instanceof Errors.AlreadyCompilingError) {
  57. code = 423 // Http 423 Locked
  58. status = 'compile-in-progress'
  59. } else if (error instanceof Errors.FilesOutOfSyncError) {
  60. code = 409 // Http 409 Conflict
  61. status = 'retry'
  62. } else if (error && error.code === 'EPIPE') {
  63. // docker returns EPIPE when shutting down
  64. code = 503 // send 503 Unavailable response
  65. status = 'unavailable'
  66. } else if (error != null ? error.terminated : undefined) {
  67. status = 'terminated'
  68. } else if (error != null ? error.validate : undefined) {
  69. status = `validation-${error.validate}`
  70. } else if (error != null ? error.timedout : undefined) {
  71. status = 'timedout'
  72. logger.debug(
  73. { err: error, project_id: request.project_id },
  74. 'timeout running compile'
  75. )
  76. } else if (error != null) {
  77. status = 'error'
  78. code = 500
  79. logger.warn(
  80. { err: error, project_id: request.project_id },
  81. 'error running compile'
  82. )
  83. } else {
  84. let file
  85. status = 'failure'
  86. for (file of Array.from(outputFiles)) {
  87. if (file.path === 'output.pdf' && file.size > 0) {
  88. status = 'success'
  89. CompileController.lastSuccessfulCompile = Date.now()
  90. }
  91. }
  92. if (status === 'failure') {
  93. logger.warn(
  94. { project_id: request.project_id, outputFiles },
  95. 'project failed to compile successfully, no output.pdf generated'
  96. )
  97. }
  98. // log an error if any core files are found
  99. for (file of Array.from(outputFiles)) {
  100. if (file.path === 'core') {
  101. logger.error(
  102. { project_id: request.project_id, req, outputFiles },
  103. 'core file found in output'
  104. )
  105. }
  106. }
  107. }
  108. if (error != null) {
  109. outputFiles = error.outputFiles || []
  110. }
  111. timer.done()
  112. return res.status(code || 200).send({
  113. compile: {
  114. status,
  115. error: (error != null ? error.message : undefined) || error,
  116. stats,
  117. timings,
  118. outputUrlPrefix: Settings.apis.clsi.outputUrlPrefix,
  119. outputFiles: outputFiles.map(file => {
  120. return {
  121. url:
  122. `${Settings.apis.clsi.url}/project/${request.project_id}` +
  123. (request.user_id != null
  124. ? `/user/${request.user_id}`
  125. : '') +
  126. (file.build != null ? `/build/${file.build}` : '') +
  127. `/output/${file.path}`,
  128. ...file,
  129. }
  130. }),
  131. },
  132. })
  133. }
  134. )
  135. }
  136. )
  137. })
  138. },
  139. stopCompile(req, res, next) {
  140. const { project_id, user_id } = req.params
  141. return CompileManager.stopCompile(project_id, user_id, function (error) {
  142. if (error != null) {
  143. return next(error)
  144. }
  145. return res.sendStatus(204)
  146. })
  147. },
  148. clearCache(req, res, next) {
  149. if (next == null) {
  150. next = function () {}
  151. }
  152. return ProjectPersistenceManager.clearProject(
  153. req.params.project_id,
  154. req.params.user_id,
  155. function (error) {
  156. if (error != null) {
  157. return next(error)
  158. }
  159. return res.sendStatus(204)
  160. }
  161. )
  162. }, // No content
  163. syncFromCode(req, res, next) {
  164. if (next == null) {
  165. next = function () {}
  166. }
  167. const { file } = req.query
  168. const line = parseInt(req.query.line, 10)
  169. const column = parseInt(req.query.column, 10)
  170. const { imageName } = req.query
  171. const { project_id } = req.params
  172. const { user_id } = req.params
  173. if (imageName && !isImageNameAllowed(imageName)) {
  174. return res.status(400).send('invalid image')
  175. }
  176. return CompileManager.syncFromCode(
  177. project_id,
  178. user_id,
  179. file,
  180. line,
  181. column,
  182. imageName,
  183. function (error, pdfPositions) {
  184. if (error != null) {
  185. return next(error)
  186. }
  187. return res.json({
  188. pdf: pdfPositions,
  189. })
  190. }
  191. )
  192. },
  193. syncFromPdf(req, res, next) {
  194. if (next == null) {
  195. next = function () {}
  196. }
  197. const page = parseInt(req.query.page, 10)
  198. const h = parseFloat(req.query.h)
  199. const v = parseFloat(req.query.v)
  200. const { imageName } = req.query
  201. const { project_id } = req.params
  202. const { user_id } = req.params
  203. if (imageName && !isImageNameAllowed(imageName)) {
  204. return res.status(400).send('invalid image')
  205. }
  206. return CompileManager.syncFromPdf(
  207. project_id,
  208. user_id,
  209. page,
  210. h,
  211. v,
  212. imageName,
  213. function (error, codePositions) {
  214. if (error != null) {
  215. return next(error)
  216. }
  217. return res.json({
  218. code: codePositions,
  219. })
  220. }
  221. )
  222. },
  223. wordcount(req, res, next) {
  224. if (next == null) {
  225. next = function () {}
  226. }
  227. const file = req.query.file || 'main.tex'
  228. const { project_id } = req.params
  229. const { user_id } = req.params
  230. const { image } = req.query
  231. if (image && !isImageNameAllowed(image)) {
  232. return res.status(400).send('invalid image')
  233. }
  234. logger.debug({ image, file, project_id }, 'word count request')
  235. return CompileManager.wordcount(
  236. project_id,
  237. user_id,
  238. file,
  239. image,
  240. function (error, result) {
  241. if (error != null) {
  242. return next(error)
  243. }
  244. return res.json({
  245. texcount: result,
  246. })
  247. }
  248. )
  249. },
  250. status(req, res, next) {
  251. if (next == null) {
  252. next = function () {}
  253. }
  254. return res.send('OK')
  255. },
  256. }