ResourceWriter.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. /* eslint-disable
  2. camelcase,
  3. handle-callback-err,
  4. no-return-assign,
  5. no-unused-vars,
  6. no-useless-escape,
  7. */
  8. // TODO: This file was created by bulk-decaffeinate.
  9. // Fix any style issues and re-enable lint.
  10. /*
  11. * decaffeinate suggestions:
  12. * DS101: Remove unnecessary use of Array.from
  13. * DS102: Remove unnecessary code created because of implicit returns
  14. * DS207: Consider shorter variations of null checks
  15. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  16. */
  17. let ResourceWriter
  18. const UrlCache = require('./UrlCache')
  19. const Path = require('path')
  20. const fs = require('fs')
  21. const async = require('async')
  22. const mkdirp = require('mkdirp')
  23. const OutputFileFinder = require('./OutputFileFinder')
  24. const ResourceStateManager = require('./ResourceStateManager')
  25. const Metrics = require('./Metrics')
  26. const logger = require('logger-sharelatex')
  27. const settings = require('settings-sharelatex')
  28. const parallelFileDownloads = settings.parallelFileDownloads || 1
  29. module.exports = ResourceWriter = {
  30. syncResourcesToDisk(request, basePath, callback) {
  31. if (callback == null) {
  32. callback = function(error, resourceList) {}
  33. }
  34. if (request.syncType === 'incremental') {
  35. logger.log(
  36. { project_id: request.project_id, user_id: request.user_id },
  37. 'incremental sync'
  38. )
  39. return ResourceStateManager.checkProjectStateMatches(
  40. request.syncState,
  41. basePath,
  42. function(error, resourceList) {
  43. if (error != null) {
  44. return callback(error)
  45. }
  46. return ResourceWriter._removeExtraneousFiles(
  47. resourceList,
  48. basePath,
  49. function(error, outputFiles, allFiles) {
  50. if (error != null) {
  51. return callback(error)
  52. }
  53. return ResourceStateManager.checkResourceFiles(
  54. resourceList,
  55. allFiles,
  56. basePath,
  57. function(error) {
  58. if (error != null) {
  59. return callback(error)
  60. }
  61. return ResourceWriter.saveIncrementalResourcesToDisk(
  62. request.project_id,
  63. request.resources,
  64. basePath,
  65. function(error) {
  66. if (error != null) {
  67. return callback(error)
  68. }
  69. return callback(null, resourceList)
  70. }
  71. )
  72. }
  73. )
  74. }
  75. )
  76. }
  77. )
  78. } else {
  79. logger.log(
  80. { project_id: request.project_id, user_id: request.user_id },
  81. 'full sync'
  82. )
  83. return this.saveAllResourcesToDisk(
  84. request.project_id,
  85. request.resources,
  86. basePath,
  87. function(error) {
  88. if (error != null) {
  89. return callback(error)
  90. }
  91. return ResourceStateManager.saveProjectState(
  92. request.syncState,
  93. request.resources,
  94. basePath,
  95. function(error) {
  96. if (error != null) {
  97. return callback(error)
  98. }
  99. return callback(null, request.resources)
  100. }
  101. )
  102. }
  103. )
  104. }
  105. },
  106. saveIncrementalResourcesToDisk(project_id, resources, basePath, callback) {
  107. if (callback == null) {
  108. callback = function(error) {}
  109. }
  110. return this._createDirectory(basePath, error => {
  111. if (error != null) {
  112. return callback(error)
  113. }
  114. const jobs = Array.from(resources).map(resource =>
  115. (resource => {
  116. return callback =>
  117. this._writeResourceToDisk(project_id, resource, basePath, callback)
  118. })(resource)
  119. )
  120. return async.parallelLimit(jobs, parallelFileDownloads, callback)
  121. })
  122. },
  123. saveAllResourcesToDisk(project_id, resources, basePath, callback) {
  124. if (callback == null) {
  125. callback = function(error) {}
  126. }
  127. return this._createDirectory(basePath, error => {
  128. if (error != null) {
  129. return callback(error)
  130. }
  131. return this._removeExtraneousFiles(resources, basePath, error => {
  132. if (error != null) {
  133. return callback(error)
  134. }
  135. const jobs = Array.from(resources).map(resource =>
  136. (resource => {
  137. return callback =>
  138. this._writeResourceToDisk(
  139. project_id,
  140. resource,
  141. basePath,
  142. callback
  143. )
  144. })(resource)
  145. )
  146. return async.parallelLimit(jobs, parallelFileDownloads, callback)
  147. })
  148. })
  149. },
  150. _createDirectory(basePath, callback) {
  151. if (callback == null) {
  152. callback = function(error) {}
  153. }
  154. return fs.mkdir(basePath, function(err) {
  155. if (err != null) {
  156. if (err.code === 'EEXIST') {
  157. return callback()
  158. } else {
  159. logger.log({ err, dir: basePath }, 'error creating directory')
  160. return callback(err)
  161. }
  162. } else {
  163. return callback()
  164. }
  165. })
  166. },
  167. _removeExtraneousFiles(resources, basePath, _callback) {
  168. if (_callback == null) {
  169. _callback = function(error, outputFiles, allFiles) {}
  170. }
  171. const timer = new Metrics.Timer('unlink-output-files')
  172. const callback = function(error, ...result) {
  173. timer.done()
  174. return _callback(error, ...Array.from(result))
  175. }
  176. return OutputFileFinder.findOutputFiles(resources, basePath, function(
  177. error,
  178. outputFiles,
  179. allFiles
  180. ) {
  181. if (error != null) {
  182. return callback(error)
  183. }
  184. const jobs = []
  185. for (const file of Array.from(outputFiles || [])) {
  186. ;(function(file) {
  187. const { path } = file
  188. let should_delete = true
  189. if (
  190. path.match(/^output\./) ||
  191. path.match(/\.aux$/) ||
  192. path.match(/^cache\//)
  193. ) {
  194. // knitr cache
  195. should_delete = false
  196. }
  197. if (path.match(/^output-.*/)) {
  198. // Tikz cached figures (default case)
  199. should_delete = false
  200. }
  201. if (path.match(/\.(pdf|dpth|md5)$/)) {
  202. // Tikz cached figures (by extension)
  203. should_delete = false
  204. }
  205. if (
  206. path.match(/\.(pygtex|pygstyle)$/) ||
  207. path.match(/(^|\/)_minted-[^\/]+\//)
  208. ) {
  209. // minted files/directory
  210. should_delete = false
  211. }
  212. if (
  213. path.match(/\.md\.tex$/) ||
  214. path.match(/(^|\/)_markdown_[^\/]+\//)
  215. ) {
  216. // markdown files/directory
  217. should_delete = false
  218. }
  219. if (path.match(/-eps-converted-to\.pdf$/)) {
  220. // Epstopdf generated files
  221. should_delete = false
  222. }
  223. if (
  224. path === 'output.pdf' ||
  225. path === 'output.dvi' ||
  226. path === 'output.log' ||
  227. path === 'output.xdv'
  228. ) {
  229. should_delete = true
  230. }
  231. if (path === 'output.tex') {
  232. // created by TikzManager if present in output files
  233. should_delete = true
  234. }
  235. if (should_delete) {
  236. return jobs.push(callback =>
  237. ResourceWriter._deleteFileIfNotDirectory(
  238. Path.join(basePath, path),
  239. callback
  240. )
  241. )
  242. }
  243. })(file)
  244. }
  245. return async.series(jobs, function(error) {
  246. if (error != null) {
  247. return callback(error)
  248. }
  249. return callback(null, outputFiles, allFiles)
  250. })
  251. })
  252. },
  253. _deleteFileIfNotDirectory(path, callback) {
  254. if (callback == null) {
  255. callback = function(error) {}
  256. }
  257. return fs.stat(path, function(error, stat) {
  258. if (error != null && error.code === 'ENOENT') {
  259. return callback()
  260. } else if (error != null) {
  261. logger.err(
  262. { err: error, path },
  263. 'error stating file in deleteFileIfNotDirectory'
  264. )
  265. return callback(error)
  266. } else if (stat.isFile()) {
  267. return fs.unlink(path, function(error) {
  268. if (error != null) {
  269. logger.err(
  270. { err: error, path },
  271. 'error removing file in deleteFileIfNotDirectory'
  272. )
  273. return callback(error)
  274. } else {
  275. return callback()
  276. }
  277. })
  278. } else {
  279. return callback()
  280. }
  281. })
  282. },
  283. _writeResourceToDisk(project_id, resource, basePath, callback) {
  284. if (callback == null) {
  285. callback = function(error) {}
  286. }
  287. return ResourceWriter.checkPath(basePath, resource.path, function(
  288. error,
  289. path
  290. ) {
  291. if (error != null) {
  292. return callback(error)
  293. }
  294. return mkdirp(Path.dirname(path), function(error) {
  295. if (error != null) {
  296. return callback(error)
  297. }
  298. // TODO: Don't overwrite file if it hasn't been modified
  299. if (resource.url != null) {
  300. return UrlCache.downloadUrlToFile(
  301. project_id,
  302. resource.url,
  303. path,
  304. resource.modified,
  305. function(err) {
  306. if (err != null) {
  307. logger.err(
  308. {
  309. err,
  310. project_id,
  311. path,
  312. resource_url: resource.url,
  313. modified: resource.modified
  314. },
  315. 'error downloading file for resources'
  316. )
  317. }
  318. return callback()
  319. }
  320. ) // try and continue compiling even if http resource can not be downloaded at this time
  321. } else {
  322. const process = require('process')
  323. fs.writeFile(path, resource.content, callback)
  324. try {
  325. let result
  326. return (result = fs.lstatSync(path))
  327. } catch (e) {}
  328. }
  329. })
  330. })
  331. },
  332. checkPath(basePath, resourcePath, callback) {
  333. const path = Path.normalize(Path.join(basePath, resourcePath))
  334. if (path.slice(0, basePath.length + 1) !== basePath + '/') {
  335. return callback(new Error('resource path is outside root directory'))
  336. } else {
  337. return callback(null, path)
  338. }
  339. }
  340. }