CompileController.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  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('logger-sharelatex')
  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. compile(req, res, next) {
  29. if (next == null) {
  30. next = function () {}
  31. }
  32. const timer = new Metrics.Timer('compile-request')
  33. return RequestParser.parse(req.body, function (error, request) {
  34. if (error != null) {
  35. return next(error)
  36. }
  37. request.project_id = req.params.project_id
  38. if (req.params.user_id != null) {
  39. request.user_id = req.params.user_id
  40. }
  41. return ProjectPersistenceManager.markProjectAsJustAccessed(
  42. request.project_id,
  43. function (error) {
  44. if (error != null) {
  45. return next(error)
  46. }
  47. return CompileManager.doCompileWithLock(
  48. request,
  49. function (error, outputFiles, stats, timings) {
  50. let code, status
  51. if (outputFiles == null) {
  52. outputFiles = []
  53. }
  54. if (error instanceof Errors.AlreadyCompilingError) {
  55. code = 423 // Http 423 Locked
  56. status = 'compile-in-progress'
  57. } else if (error instanceof Errors.FilesOutOfSyncError) {
  58. code = 409 // Http 409 Conflict
  59. status = 'retry'
  60. } else if (error && error.code === 'EPIPE') {
  61. // docker returns EPIPE when shutting down
  62. code = 503 // send 503 Unavailable response
  63. status = 'unavailable'
  64. } else if (error != null ? error.terminated : undefined) {
  65. status = 'terminated'
  66. } else if (error != null ? error.validate : undefined) {
  67. status = `validation-${error.validate}`
  68. } else if (error != null ? error.timedout : undefined) {
  69. status = 'timedout'
  70. logger.log(
  71. { err: error, project_id: request.project_id },
  72. 'timeout running compile'
  73. )
  74. } else if (error != null) {
  75. status = 'error'
  76. code = 500
  77. logger.warn(
  78. { err: error, project_id: request.project_id },
  79. 'error running compile'
  80. )
  81. } else {
  82. let file
  83. status = 'failure'
  84. for (file of Array.from(outputFiles)) {
  85. if (file.path === 'output.pdf' && file.size > 0) {
  86. status = 'success'
  87. }
  88. }
  89. if (status === 'failure') {
  90. logger.warn(
  91. { project_id: request.project_id, outputFiles },
  92. 'project failed to compile successfully, no output.pdf generated'
  93. )
  94. }
  95. // log an error if any core files are found
  96. for (file of Array.from(outputFiles)) {
  97. if (file.path === 'core') {
  98. logger.error(
  99. { project_id: request.project_id, req, outputFiles },
  100. 'core file found in output'
  101. )
  102. }
  103. }
  104. }
  105. if (error != null) {
  106. outputFiles = error.outputFiles || []
  107. }
  108. timer.done()
  109. return res.status(code || 200).send({
  110. compile: {
  111. status,
  112. error: (error != null ? error.message : undefined) || error,
  113. stats,
  114. timings,
  115. outputFiles: outputFiles.map(file => {
  116. return {
  117. url:
  118. `${Settings.apis.clsi.url}/project/${request.project_id}` +
  119. (request.user_id != null
  120. ? `/user/${request.user_id}`
  121. : '') +
  122. (file.build != null ? `/build/${file.build}` : '') +
  123. `/output/${file.path}`,
  124. ...file,
  125. }
  126. }),
  127. },
  128. })
  129. }
  130. )
  131. }
  132. )
  133. })
  134. },
  135. stopCompile(req, res, next) {
  136. const { project_id, user_id } = req.params
  137. return CompileManager.stopCompile(project_id, user_id, function (error) {
  138. if (error != null) {
  139. return next(error)
  140. }
  141. return res.sendStatus(204)
  142. })
  143. },
  144. clearCache(req, res, next) {
  145. if (next == null) {
  146. next = function () {}
  147. }
  148. return ProjectPersistenceManager.clearProject(
  149. req.params.project_id,
  150. req.params.user_id,
  151. function (error) {
  152. if (error != null) {
  153. return next(error)
  154. }
  155. return res.sendStatus(204)
  156. }
  157. )
  158. }, // No content
  159. syncFromCode(req, res, next) {
  160. if (next == null) {
  161. next = function () {}
  162. }
  163. const { file } = req.query
  164. const line = parseInt(req.query.line, 10)
  165. const column = parseInt(req.query.column, 10)
  166. const { imageName } = req.query
  167. const { project_id } = req.params
  168. const { user_id } = req.params
  169. if (imageName && !isImageNameAllowed(imageName)) {
  170. return res.status(400).send('invalid image')
  171. }
  172. return CompileManager.syncFromCode(
  173. project_id,
  174. user_id,
  175. file,
  176. line,
  177. column,
  178. imageName,
  179. function (error, pdfPositions) {
  180. if (error != null) {
  181. return next(error)
  182. }
  183. return res.json({
  184. pdf: pdfPositions,
  185. })
  186. }
  187. )
  188. },
  189. syncFromPdf(req, res, next) {
  190. if (next == null) {
  191. next = function () {}
  192. }
  193. const page = parseInt(req.query.page, 10)
  194. const h = parseFloat(req.query.h)
  195. const v = parseFloat(req.query.v)
  196. const { imageName } = req.query
  197. const { project_id } = req.params
  198. const { user_id } = req.params
  199. if (imageName && !isImageNameAllowed(imageName)) {
  200. return res.status(400).send('invalid image')
  201. }
  202. return CompileManager.syncFromPdf(
  203. project_id,
  204. user_id,
  205. page,
  206. h,
  207. v,
  208. imageName,
  209. function (error, codePositions) {
  210. if (error != null) {
  211. return next(error)
  212. }
  213. return res.json({
  214. code: codePositions,
  215. })
  216. }
  217. )
  218. },
  219. wordcount(req, res, next) {
  220. if (next == null) {
  221. next = function () {}
  222. }
  223. const file = req.query.file || 'main.tex'
  224. const { project_id } = req.params
  225. const { user_id } = req.params
  226. const { image } = req.query
  227. if (image && !isImageNameAllowed(image)) {
  228. return res.status(400).send('invalid image')
  229. }
  230. logger.log({ image, file, project_id }, 'word count request')
  231. return CompileManager.wordcount(
  232. project_id,
  233. user_id,
  234. file,
  235. image,
  236. function (error, result) {
  237. if (error != null) {
  238. return next(error)
  239. }
  240. return res.json({
  241. texcount: result,
  242. })
  243. }
  244. )
  245. },
  246. status(req, res, next) {
  247. if (next == null) {
  248. next = function () {}
  249. }
  250. return res.send('OK')
  251. },
  252. }