diff --git a/services/filestore/app/coffee/FSPersistorManager.coffee b/services/filestore/app/coffee/FSPersistorManager.coffee new file mode 100644 index 0000000000..a0779e5b61 --- /dev/null +++ b/services/filestore/app/coffee/FSPersistorManager.coffee @@ -0,0 +1,59 @@ +logger = require("logger-sharelatex") +fs = require("fs") +LocalFileWriter = require("./LocalFileWriter") + +module.exports = + + sendFile: ( location, target, source, callback = (err)->) -> + logger.log location:location, target:target, source:source, "sending file" + fs.rename source, "#{location}/#{target}", (err) -> + logger.err err:err, location:location, target:target, source:source, "Error on put of file" + callback err + + sendStream: ( location, target, sourceStream, callback = (err)->) -> + logger.log location:location, target:target, source:sourceStream, "sending file stream" + sourceStream.on "error", (err)-> + logger.err location:location, target:target, source:sourceStream, err:err "error on stream to send" + LocalFileWriter.writeStream sourceStream, null, (err, fsPath)=> + if err? + logger.err location:location, target:target, fsPath:fsPath, err:err, "something went wrong writing stream to disk" + return callback err + @sendFile location, target, fsPath, callback + + getFileStream: (location, name, callback = (err, res)->)-> + logger.log location:location, name:name, "getting file" + sourceStream = fs.createReadStream "#{location}/#{name}" + sourceStream.on 'error', (err) -> + logger.err err:err, location:location, name:name, "Error reading from file" + callback err + callback null,sourceStream + + + copyFile: (location, fromName, toName, callback = (err)->)-> + logger.log location:location, fromName:fromName, toName:toName, "copying file" + sourceStream = fs.createReadStream "#{location}/#{fromName}" + sourceStream.on 'error', (err) -> + logger.err err:err, location:location, key:fromName, "Error reading from file" + callback err + targetStream = fs.createWriteStream "#{location}/#{toName}" + targetStream.on 'error', (err) -> + logger.err err:err, location:location, key:targetKey, "Error writing to file" + callback err + sourceStream.pipe targetStream + + deleteFile: (location, name, callback)-> + logger.log location:location, name:name, "delete file" + fs.unlink "#{location}/#{name}", (err) -> + logger.err err:err, location:location, name:name, "Error on delete." + callback err + + deleteDirectory: (location, name, callback = (err)->)-> + fs.rmdir "#{location}/#{name}", (err) -> + logger.err err:err, location:location, name:name, "Error on rmdir." + callback err + + checkIfFileExists:(location, name, callback = (err,exists)->)-> + logger.log location:location, name:name, "checking if file exists" + fs.exists "#{location}/#{name}", (exists) -> + logger.log location:location, name:name, exists:exists, "checked if file exists" + callback null, exists diff --git a/services/filestore/test/unit/coffee/FSPersistorManagerTests.coffee b/services/filestore/test/unit/coffee/FSPersistorManagerTests.coffee new file mode 100644 index 0000000000..44580d115a --- /dev/null +++ b/services/filestore/test/unit/coffee/FSPersistorManagerTests.coffee @@ -0,0 +1,156 @@ +assert = require("chai").assert +sinon = require('sinon') +chai = require('chai') +should = chai.should +expect = chai.expect +modulePath = "../../../app/js/FSPersistorManager.js" +SandboxedModule = require('sandboxed-module') +fs = require("fs") + +describe "FSPersistorManagerTests", -> + + beforeEach -> + @Fs = + rename:sinon.stub() + createReadStream:sinon.stub() + createWriteStream:sinon.stub() + unlink:sinon.stub() + rmdir:sinon.stub() + exists:sinon.stub() + @LocalFileWriter = + writeStream: sinon.stub() + @requires = + "./LocalFileWriter":@LocalFileWriter + "fs":@Fs + "logger-sharelatex": + log:-> + err:-> + @location = "/tmp" + @name1 = "first_file" + @name2 = "second_file" + @error = "error_message" + @FSPersistorManager = SandboxedModule.require modulePath, requires: @requires + + describe "sendFile", -> + it "should put the file", (done) -> + @Fs.rename.callsArgWith(2,@error) + @FSPersistorManager.sendFile @location, @name1, @name2, (err)=> + @Fs.rename.calledWith( @name2, "#{@location}/#{@name1}" ).should.equal true + err.should.equal @error + done() + + describe "sendStream", -> + beforeEach -> + @FSPersistorManager.sendFile = sinon.stub().callsArgWith(3) + @LocalFileWriter.writeStream.callsArgWith(2, null, @name1) + @SourceStream = + on:-> + + it "should sent stream to LocalFileWriter", (done)-> + @FSPersistorManager.sendStream @location, @name1, @SourceStream, => + @LocalFileWriter.writeStream.calledWith(@SourceStream).should.equal true + done() + + it "should return the error from LocalFileWriter", (done)-> + @LocalFileWriter.writeStream.callsArgWith(2, @error) + @FSPersistorManager.sendStream @location, @name1, @SourceStream, (err)=> + err.should.equal @error + done() + + it "should send the file to the filestore", (done)-> + @LocalFileWriter.writeStream.callsArgWith(2) + @FSPersistorManager.sendStream @location, @name1, @SourceStream, (err)=> + @FSPersistorManager.sendFile.called.should.equal true + done() + + describe "getFileStream", -> + it "should use correct file location", (done) -> + @Fs.createReadStream.returns( + on:-> + ) + @FSPersistorManager.getFileStream @location, @name1, (err,res)=> + @Fs.createReadStream.calledWith("#{@location}/#{@name1}").should.equal.true + done() + + describe "copyFile", -> + beforeEach -> + @ReadStream= + on:-> + pipe:sinon.stub() + @WriteStream= + on:-> + @Fs.createReadStream.returns(@ReadStream) + @Fs.createWriteStream.returns(@WriteStream) + + it "Should open the source for reading", (done) -> + @FSPersistorManager.copyFile @location, @name1, @name2, -> + @Fs.createReadStream.calledWith("#{@location}/#{@name1}").should.equal.true + done() + + it "Should open the target for writing", (done) -> + @FSPersistorManager.copyFile @location, @name1, @name2, -> + @Fs.createWriteStream.calledWith("#{@location}/#{@name2}").should.equal.true + done() + + it "Should pipe the source to the target", (done) -> + @FSPersistorManager.copyFile @location, @name1, @name2, -> + @ReadStream.pipe.calledWith(@WriteStream).should.equal.true + done() + + describe "deleteFile", -> + beforeEach -> + @Fs.unlink.callsArgWith(1,@error) + + it "Should call unlink with correct options", (done) -> + @FSPersistorManager.deleteFile @location, @name1, (err) => + @Fs.unlink.calledWith("#{@location}/#{@name1}").should.equal.true + done() + + it "Should propogate the error", (done) -> + @FSPersistorManager.deleteFile @location, @name1, (err) => + err.should.equal @error + done() + + + describe "deleteDirectory", -> + beforeEach -> + @Fs.rmdir.callsArgWith(1,@error) + + it "Should call rmdir with correct options", (done) -> + @FSPersistorManager.deleteDirectory @location, @name1, (err) => + @Fs.rmdir.calledWith("#{@location}/#{@name1}").should.equal.true + done() + + it "Should propogate the error", (done) -> + @FSPersistorManager.deleteDirectory @location, @name1, (err) => + err.should.equal @error + done() + + describe "checkIfFileExists", -> + beforeEach -> + @Fs.exists.callsArgWith(1,true) + + it "Should call exists with correct options", (done) -> + @FSPersistorManager.checkIfFileExists @location, @name1, (exists) => + @Fs.exists.calledWith("#{@location}/#{@name1}").should.equal.true + done() + + # fs.exists simply returns false on any error, so... + it "should not return an error", (done) -> + @FSPersistorManager.checkIfFileExists @location, @name1, (err,exists) => + expect(err).to.be.null + done() + + it "Should return true for existing files", (done) -> + @Fs.exists.callsArgWith(1,true) + @FSPersistorManager.checkIfFileExists @location, @name1, (err,exists) => + exists.should.be.true + done() + + it "Should return false for non-existing files", (done) -> + @Fs.exists.callsArgWith(1,false) + @FSPersistorManager.checkIfFileExists @location, @name1, (err,exists) => + exists.should.be.false + done() + +