CompileController.js 8.2 KB

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