code: /* 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-.*/)) {
              // 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' ||
              path === 'output.stdout' ||
              path === 'output.stderr'
            ) {
              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)
      }
    }
  }
  