| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124 |
- logger = require("logger-sharelatex")
- fs = require("fs")
- request = require("request")
- settings = require("settings-sharelatex")
- Async = require('async')
- FileHashManager = require("./FileHashManager")
- File = require('../../models/File').File
- oneMinInMs = 60 * 1000
- fiveMinsInMs = oneMinInMs * 5
- module.exports = FileStoreHandler =
- RETRY_ATTEMPTS: 3
- uploadFileFromDisk: (project_id, file_args, fsPath, callback = (error, url, fileRef) ->)->
- fs.lstat fsPath, (err, stat)->
- if err?
- logger.err err:err, project_id:project_id, file_args:file_args, fsPath:fsPath, "error stating file"
- callback(err)
- if !stat?
- logger.err project_id:project_id, file_args:file_args, fsPath:fsPath, "stat is not available, can not check file from disk"
- return callback(new Error("error getting stat, not available"))
- if !stat.isFile()
- logger.log project_id:project_id, file_args:file_args, fsPath:fsPath, "tried to upload symlink, not contining"
- return callback(new Error("can not upload symlink"))
- Async.retry FileStoreHandler.RETRY_ATTEMPTS, (cb, results) ->
- FileStoreHandler._doUploadFileFromDisk project_id, file_args, fsPath, cb
- , (err, result) ->
- if err?
- logger.err {err, project_id, file_args}, "Error uploading file, retries failed"
- return callback(err)
- callback(err, result.url, result.fileRef)
- _doUploadFileFromDisk: (project_id, file_args, fsPath, callback = (err, result)->) ->
- _cb = callback
- callback = (err, result...) ->
- callback = -> # avoid double callbacks
- _cb(err, result...)
- FileHashManager.computeHash fsPath, (err, hashValue) ->
- return callback(err) if err?
- fileRef = new File(Object.assign({}, file_args, {hash: hashValue}))
- file_id = fileRef._id
- logger.log project_id:project_id, file_id:file_id, fsPath:fsPath, hash: hashValue, fileRef:fileRef, "uploading file from disk"
- readStream = fs.createReadStream(fsPath)
- readStream.on "error", (err)->
- logger.err err:err, project_id:project_id, file_id:file_id, fsPath:fsPath, "something went wrong on the read stream of uploadFileFromDisk"
- callback err
- readStream.on "open", () ->
- url = FileStoreHandler._buildUrl(project_id, file_id)
- opts =
- method: "post"
- uri: url
- timeout:fiveMinsInMs
- headers:
- "X-File-Hash-From-Web": hashValue # send the hash to the filestore as a custom header so it can be checked
- writeStream = request(opts)
- writeStream.on "error", (err)->
- logger.err err:err, project_id:project_id, file_id:file_id, fsPath:fsPath, "something went wrong on the write stream of uploadFileFromDisk"
- callback err
- writeStream.on 'response', (response) ->
- if response.statusCode not in [200, 201]
- err = new Error("non-ok response from filestore for upload: #{response.statusCode}")
- logger.err {err, statusCode: response.statusCode}, "error uploading to filestore"
- callback(err)
- else
- callback(null, {url, fileRef}) # have to pass back an object because async.retry only accepts a single result argument
- readStream.pipe writeStream
- getFileStream: (project_id, file_id, query, callback)->
- logger.log project_id:project_id, file_id:file_id, query:query, "getting file stream from file store"
- queryString = ""
- if query? and query["format"]?
- queryString = "?format=#{query['format']}"
- opts =
- method : "get"
- uri: "#{@_buildUrl(project_id, file_id)}#{queryString}"
- timeout:fiveMinsInMs
- headers: {}
- if query? and query['range']?
- rangeText = query['range']
- if rangeText && rangeText.match? && rangeText.match(/\d+-\d+/)
- opts.headers['range'] = "bytes=#{query['range']}"
- readStream = request(opts)
- readStream.on "error", (err) ->
- logger.err {err, project_id, file_id, query, opts}, "error in file stream"
- callback(null, readStream)
- deleteFile: (project_id, file_id, callback)->
- logger.log project_id:project_id, file_id:file_id, "telling file store to delete file"
- opts =
- method : "delete"
- uri: @_buildUrl(project_id, file_id)
- timeout:fiveMinsInMs
- request opts, (err, response)->
- if err?
- logger.err err:err, project_id:project_id, file_id:file_id, "something went wrong deleting file from filestore"
- callback(err)
- copyFile: (oldProject_id, oldFile_id, newProject_id, newFile_id, callback)->
- logger.log oldProject_id:oldProject_id, oldFile_id:oldFile_id, newProject_id:newProject_id, newFile_id:newFile_id, "telling filestore to copy a file"
- opts =
- method : "put"
- json:
- source:
- project_id:oldProject_id
- file_id:oldFile_id
- uri: @_buildUrl(newProject_id, newFile_id)
- timeout:fiveMinsInMs
- request opts, (err, response)->
- if err?
- logger.err err:err, oldProject_id:oldProject_id, oldFile_id:oldFile_id, newProject_id:newProject_id, newFile_id:newFile_id, "something went wrong telling filestore api to copy file"
- callback(err)
- else if 200 <= response.statusCode < 300
- # successful response
- callback(null, opts.uri)
- else
- err = new Error("non-ok response from filestore for copyFile: #{response.statusCode}")
- logger.err {uri: opts.uri, statusCode: response.statusCode}, "error uploading to filestore"
- callback(err)
- _buildUrl: (project_id, file_id)->
- return "#{settings.apis.filestore.url}/project/#{project_id}/file/#{file_id}"
|