CompileController.js 8.0 KB

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