output-files.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. import getMeta from '../../../utils/meta'
  2. import HumanReadableLogs from '../../../ide/human-readable-logs/HumanReadableLogs'
  3. import BibLogParser from '../../../ide/log-parser/bib-log-parser'
  4. import { v4 as uuid } from 'uuid'
  5. import { enablePdfCaching } from './pdf-caching-flags'
  6. import { fetchFromCompileDomain, swapDomain } from './fetchFromCompileDomain'
  7. import { userContentDomainAccessCheckPassed } from '../../user-content-domain-access-check'
  8. // Warnings that may disappear after a second LaTeX pass
  9. const TRANSIENT_WARNING_REGEX = /^(Reference|Citation).+undefined on input line/
  10. export function handleOutputFiles(outputFiles, projectId, data) {
  11. const outputFile = outputFiles.get('output.pdf')
  12. if (!outputFile) return null
  13. // build the URL for viewing the PDF in the preview UI
  14. const params = new URLSearchParams({
  15. compileGroup: data.compileGroup,
  16. })
  17. if (data.clsiServerId) {
  18. params.set('clsiserverid', data.clsiServerId)
  19. }
  20. if (enablePdfCaching) {
  21. // Tag traffic that uses the pdf caching logic.
  22. params.set('enable_pdf_caching', 'true')
  23. }
  24. outputFile.pdfUrl = `${buildURL(
  25. outputFile,
  26. data.pdfDownloadDomain,
  27. data.enableHybridPdfDownload
  28. )}?${params}`
  29. // build the URL for downloading the PDF
  30. params.set('popupDownload', 'true') // save PDF download as file
  31. outputFile.pdfDownloadUrl = `/download/project/${projectId}/build/${outputFile.build}/output/output.pdf?${params}`
  32. return outputFile
  33. }
  34. export const handleLogFiles = async (outputFiles, data, signal) => {
  35. const result = {
  36. log: null,
  37. logEntries: {
  38. errors: [],
  39. warnings: [],
  40. typesetting: [],
  41. },
  42. }
  43. function accumulateResults(newEntries, type) {
  44. for (const key in result.logEntries) {
  45. if (newEntries[key]) {
  46. for (const entry of newEntries[key]) {
  47. if (type) {
  48. entry.type = newEntries.type
  49. }
  50. if (entry.file) {
  51. entry.file = normalizeFilePath(entry.file)
  52. }
  53. entry.key = uuid()
  54. }
  55. result.logEntries[key].push(...newEntries[key])
  56. }
  57. }
  58. }
  59. const logFile = outputFiles.get('output.log')
  60. if (logFile) {
  61. try {
  62. const response = await fetchFromCompileDomain(
  63. buildURL(logFile, data.pdfDownloadDomain, data.enableHybridPdfDownload),
  64. { signal }
  65. )
  66. result.log = await response.text()
  67. let { errors, warnings, typesetting } = HumanReadableLogs.parse(
  68. result.log,
  69. {
  70. ignoreDuplicates: true,
  71. oldRegexes:
  72. getMeta('ol-splitTestVariants')?.['latex-log-parser'] !== 'new',
  73. }
  74. )
  75. if (data.status === 'stopped-on-first-error') {
  76. // Hide warnings that could disappear after a second pass
  77. warnings = warnings.filter(warning => !isTransientWarning(warning))
  78. }
  79. accumulateResults({ errors, warnings, typesetting })
  80. } catch (e) {
  81. console.warn(e) // ignore failure to fetch/parse the log file, but log a warning
  82. }
  83. }
  84. const blgFile = outputFiles.get('output.blg')
  85. if (blgFile) {
  86. try {
  87. const response = await fetchFromCompileDomain(
  88. buildURL(blgFile, data.pdfDownloadDomain, data.enableHybridPdfDownload),
  89. { signal }
  90. )
  91. const log = await response.text()
  92. try {
  93. const { errors, warnings } = new BibLogParser(log, {
  94. maxErrors: 100,
  95. }).parse()
  96. accumulateResults({ errors, warnings }, 'BibTeX:')
  97. } catch (e) {
  98. // BibLog parsing errors are ignored
  99. }
  100. } catch (e) {
  101. console.warn(e) // ignore failure to fetch/parse the log file, but log a warning
  102. }
  103. }
  104. result.logEntries.all = [
  105. ...result.logEntries.errors,
  106. ...result.logEntries.warnings,
  107. ...result.logEntries.typesetting,
  108. ]
  109. return result
  110. }
  111. export function buildLogEntryAnnotations(entries, fileTreeManager) {
  112. const rootDocDirname = fileTreeManager.getRootDocDirname()
  113. const logEntryAnnotations = {}
  114. for (const entry of entries) {
  115. if (entry.file) {
  116. entry.file = normalizeFilePath(entry.file, rootDocDirname)
  117. const entity = fileTreeManager.findEntityByPath(entry.file)
  118. if (entity) {
  119. if (!(entity.id in logEntryAnnotations)) {
  120. logEntryAnnotations[entity.id] = []
  121. }
  122. logEntryAnnotations[entity.id].push({
  123. row: entry.line - 1,
  124. type: entry.level === 'error' ? 'error' : 'warning',
  125. text: entry.message,
  126. source: 'compile', // NOTE: this is used in Ace for filtering the annotations
  127. })
  128. }
  129. }
  130. }
  131. return logEntryAnnotations
  132. }
  133. function buildURL(file, pdfDownloadDomain, enableHybridPdfDownload) {
  134. const userContentDomain = getMeta('ol-compilesUserContentDomain')
  135. if (
  136. enableHybridPdfDownload &&
  137. userContentDomainAccessCheckPassed() &&
  138. file.build &&
  139. userContentDomain
  140. ) {
  141. // This user is enrolled in the hybrid download of compile output.
  142. // The access check passed, so try to use the new user content domain.
  143. // Downloads from the compiles domains must include a build id.
  144. // The build id is used implicitly for access control.
  145. return swapDomain(`${pdfDownloadDomain}${file.url}`, userContentDomain)
  146. }
  147. if (file.build && pdfDownloadDomain) {
  148. // Downloads from the compiles domain must include a build id.
  149. // The build id is used implicitly for access control.
  150. return `${pdfDownloadDomain}${file.url}`
  151. }
  152. // Go through web instead, which uses mongo for checking project access.
  153. return `${window.origin}${file.url}`
  154. }
  155. function normalizeFilePath(path, rootDocDirname) {
  156. path = path.replace(/\/\//g, '/')
  157. path = path.replace(
  158. /^.*\/compiles\/[0-9a-f]{24}(-[0-9a-f]{24})?\/(\.\/)?/,
  159. ''
  160. )
  161. path = path.replace(/^\/compile\//, '')
  162. if (rootDocDirname) {
  163. path = path.replace(/^\.\//, rootDocDirname + '/')
  164. }
  165. return path
  166. }
  167. function isTransientWarning(warning) {
  168. return TRANSIENT_WARNING_REGEX.test(warning.message)
  169. }