OutputFileOptimiser.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. /* eslint-disable
  2. no-return-assign,
  3. no-undef,
  4. no-unused-vars,
  5. n/no-deprecated-api,
  6. */
  7. // TODO: This file was created by bulk-decaffeinate.
  8. // Fix any style issues and re-enable lint.
  9. /*
  10. * decaffeinate suggestions:
  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 OutputFileOptimiser
  16. const fs = require('node:fs')
  17. const Path = require('node:path')
  18. const { spawn } = require('node:child_process')
  19. const logger = require('@overleaf/logger')
  20. const Metrics = require('./Metrics')
  21. const _ = require('lodash')
  22. module.exports = OutputFileOptimiser = {
  23. optimiseFile(src, dst, callback) {
  24. // check output file (src) and see if we can optimise it, storing
  25. // the result in the build directory (dst)
  26. if (callback == null) {
  27. callback = function () {}
  28. }
  29. if (src.match(/\/output\.pdf$/)) {
  30. return OutputFileOptimiser.checkIfPDFIsOptimised(
  31. src,
  32. function (err, isOptimised) {
  33. if (err != null || isOptimised) {
  34. return callback(null)
  35. }
  36. return OutputFileOptimiser.optimisePDF(src, dst, callback)
  37. }
  38. )
  39. } else {
  40. return callback(null)
  41. }
  42. },
  43. checkIfPDFIsOptimised(file, callback) {
  44. const SIZE = 16 * 1024 // check the header of the pdf
  45. const result = Buffer.alloc(SIZE) // fills with zeroes by default
  46. return fs.open(file, 'r', function (err, fd) {
  47. if (err != null) {
  48. return callback(err)
  49. }
  50. return fs.read(fd, result, 0, SIZE, 0, (errRead, bytesRead, buffer) =>
  51. fs.close(fd, function (errClose) {
  52. if (errRead != null) {
  53. return callback(errRead)
  54. }
  55. if (typeof errReadClose !== 'undefined' && errReadClose !== null) {
  56. return callback(errClose)
  57. }
  58. const isOptimised =
  59. buffer.toString('ascii').indexOf('/Linearized 1') >= 0
  60. return callback(null, isOptimised)
  61. })
  62. )
  63. })
  64. },
  65. optimisePDF(src, dst, callback) {
  66. if (callback == null) {
  67. callback = function () {}
  68. }
  69. const tmpOutput = dst + '.opt'
  70. const args = ['--linearize', '--newline-before-endstream', src, tmpOutput]
  71. logger.debug({ args }, 'running qpdf command')
  72. const timer = new Metrics.Timer('qpdf')
  73. const proc = spawn('qpdf', args, { stdio: 'ignore' })
  74. callback = _.once(callback) // avoid double call back for error and close event
  75. proc.on('error', function (err) {
  76. logger.warn({ err, args }, 'qpdf failed')
  77. return callback(null)
  78. }) // ignore the error
  79. return proc.on('close', function (code) {
  80. timer.done()
  81. if (code !== 0) {
  82. logger.warn({ code, args }, 'qpdf returned error')
  83. return callback(null) // ignore the error
  84. }
  85. return fs.rename(tmpOutput, dst, function (err) {
  86. if (err != null) {
  87. logger.warn(
  88. { tmpOutput, dst },
  89. 'failed to rename output of qpdf command'
  90. )
  91. }
  92. return callback(null)
  93. })
  94. })
  95. }, // ignore the error
  96. }