CompileController.js 7.4 KB

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