/* eslint-disable camelcase, handle-callback-err, no-return-assign, no-unused-vars, no-useless-escape, */ // TODO: This file was created by bulk-decaffeinate. // Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS101: Remove unnecessary use of Array.from * DS102: Remove unnecessary code created because of implicit returns * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ let ResourceWriter const UrlCache = require('./UrlCache') const Path = require('path') const fs = require('fs') const async = require('async') const OutputFileFinder = require('./OutputFileFinder') const ResourceStateManager = require('./ResourceStateManager') const Metrics = require('./Metrics') const logger = require('logger-sharelatex') const settings = require('settings-sharelatex') const parallelFileDownloads = settings.parallelFileDownloads || 1 module.exports = ResourceWriter = { syncResourcesToDisk(request, basePath, callback) { if (callback == null) { callback = function(error, resourceList) {} } if (request.syncType === 'incremental') { logger.log( { project_id: request.project_id, user_id: request.user_id }, 'incremental sync' ) return ResourceStateManager.checkProjectStateMatches( request.syncState, basePath, function(error, resourceList) { if (error != null) { return callback(error) } return ResourceWriter._removeExtraneousFiles( resourceList, basePath, function(error, outputFiles, allFiles) { if (error != null) { return callback(error) } return ResourceStateManager.checkResourceFiles( resourceList, allFiles, basePath, function(error) { if (error != null) { return callback(error) } return ResourceWriter.saveIncrementalResourcesToDisk( request.project_id, request.resources, basePath, function(error) { if (error != null) { return callback(error) } return callback(null, resourceList) } ) } ) } ) } ) } else { logger.log( { project_id: request.project_id, user_id: request.user_id }, 'full sync' ) return this.saveAllResourcesToDisk( request.project_id, request.resources, basePath, function(error) { if (error != null) { return callback(error) } return ResourceStateManager.saveProjectState( request.syncState, request.resources, basePath, function(error) { if (error != null) { return callback(error) } return callback(null, request.resources) } ) } ) } }, saveIncrementalResourcesToDisk(project_id, resources, basePath, callback) { if (callback == null) { callback = function(error) {} } return this._createDirectory(basePath, error => { if (error != null) { return callback(error) } const jobs = Array.from(resources).map(resource => (resource => { return callback => this._writeResourceToDisk(project_id, resource, basePath, callback) })(resource) ) return async.parallelLimit(jobs, parallelFileDownloads, callback) }) }, saveAllResourcesToDisk(project_id, resources, basePath, callback) { if (callback == null) { callback = function(error) {} } return this._createDirectory(basePath, error => { if (error != null) { return callback(error) } return this._removeExtraneousFiles(resources, basePath, error => { if (error != null) { return callback(error) } const jobs = Array.from(resources).map(resource => (resource => { return callback => this._writeResourceToDisk( project_id, resource, basePath, callback ) })(resource) ) return async.parallelLimit(jobs, parallelFileDownloads, callback) }) }) }, _createDirectory(basePath, callback) { if (callback == null) { callback = function(error) {} } return fs.mkdir(basePath, function(err) { if (err != null) { if (err.code === 'EEXIST') { return callback() } else { logger.log({ err, dir: basePath }, 'error creating directory') return callback(err) } } else { return callback() } }) }, _removeExtraneousFiles(resources, basePath, _callback) { if (_callback == null) { _callback = function(error, outputFiles, allFiles) {} } const timer = new Metrics.Timer('unlink-output-files') const callback = function(error, ...result) { timer.done() return _callback(error, ...Array.from(result)) } return OutputFileFinder.findOutputFiles(resources, basePath, function( error, outputFiles, allFiles ) { if (error != null) { return callback(error) } const jobs = [] for (const file of Array.from(outputFiles || [])) { ;(function(file) { const { path } = file let should_delete = true if ( path.match(/^output\./) || path.match(/\.aux$/) || path.match(/^cache\//) ) { // knitr cache should_delete = false } if (path.match(/^output.(stdout|stderr)$/)) { // latexmk output should_delete = true } if (path.match(/^output-.*/)) { // Tikz cached figures (default case) should_delete = false } if (path.match(/\.(pdf|dpth|md5)$/)) { // Tikz cached figures (by extension) should_delete = false } if ( path.match(/\.(pygtex|pygstyle)$/) || path.match(/(^|\/)_minted-[^\/]+\//) ) { // minted files/directory should_delete = false } if ( path.match(/\.md\.tex$/) || path.match(/(^|\/)_markdown_[^\/]+\//) ) { // markdown files/directory should_delete = false } if (path.match(/-eps-converted-to\.pdf$/)) { // Epstopdf generated files should_delete = false } if ( path === 'output.pdf' || path === 'output.dvi' || path === 'output.log' || path === 'output.xdv' ) { should_delete = true } if (path === 'output.tex') { // created by TikzManager if present in output files should_delete = true } if (should_delete) { return jobs.push(callback => ResourceWriter._deleteFileIfNotDirectory( Path.join(basePath, path), callback ) ) } })(file) } return async.series(jobs, function(error) { if (error != null) { return callback(error) } return callback(null, outputFiles, allFiles) }) }) }, _deleteFileIfNotDirectory(path, callback) { if (callback == null) { callback = function(error) {} } return fs.stat(path, function(error, stat) { if (error != null && error.code === 'ENOENT') { return callback() } else if (error != null) { logger.err( { err: error, path }, 'error stating file in deleteFileIfNotDirectory' ) return callback(error) } else if (stat.isFile()) { return fs.unlink(path, function(error) { if (error != null) { logger.err( { err: error, path }, 'error removing file in deleteFileIfNotDirectory' ) return callback(error) } else { return callback() } }) } else { return callback() } }) }, _writeResourceToDisk(project_id, resource, basePath, callback) { if (callback == null) { callback = function(error) {} } return ResourceWriter.checkPath(basePath, resource.path, function( error, path ) { if (error != null) { return callback(error) } return fs.mkdir(Path.dirname(path), { recursive: true }, function(error) { if (error != null) { return callback(error) } // TODO: Don't overwrite file if it hasn't been modified if (resource.url != null) { return UrlCache.downloadUrlToFile( project_id, resource.url, path, resource.modified, function(err) { if (err != null) { logger.err( { err, project_id, path, resource_url: resource.url, modified: resource.modified }, 'error downloading file for resources' ) Metrics.inc('download-failed') } return callback() } ) // try and continue compiling even if http resource can not be downloaded at this time } else { const process = require('process') fs.writeFile(path, resource.content, callback) try { let result return (result = fs.lstatSync(path)) } catch (e) {} } }) }) }, checkPath(basePath, resourcePath, callback) { const path = Path.normalize(Path.join(basePath, resourcePath)) if (path.slice(0, basePath.length + 1) !== basePath + '/') { return callback(new Error('resource path is outside root directory')) } else { return callback(null, path) } } }