diff --git a/services/web/app/coffee/Features/History/RestoreManager.coffee b/services/web/app/coffee/Features/History/RestoreManager.coffee index 0eccb7e390..aa3cf0f8a4 100644 --- a/services/web/app/coffee/Features/History/RestoreManager.coffee +++ b/services/web/app/coffee/Features/History/RestoreManager.coffee @@ -3,6 +3,8 @@ Path = require 'path' FileWriter = require '../../infrastructure/FileWriter' FileSystemImportManager = require '../Uploads/FileSystemImportManager' ProjectLocator = require '../Project/ProjectLocator' +Errors = require '../Errors/Errors' +moment = require 'moment' module.exports = RestoreManager = restoreFile: (user_id, project_id, version, pathname, callback = (error) ->) -> @@ -12,18 +14,41 @@ module.exports = RestoreManager = dirname = Path.dirname(pathname) if dirname == '.' # no directory dirname = '' - ProjectLocator.findElementByPath {project_id, path: dirname}, (error, element, type) -> + RestoreManager._findFolderOrRootFolderId project_id, dirname, (error, parent_folder_id) -> return callback(error) if error? - # We're going to try to recover the file into the folder it was in previously, - # but this is historical, so the folder may not exist anymore. Fallback to the - # root folder if not (parent_folder_id == null will default to this) - if type == 'folder' and element? - parent_folder_id = element._id + RestoreManager._addEntityWithUniqueName user_id, project_id, parent_folder_id, basename, fsPath, callback + + _findFolderOrRootFolderId: (project_id, dirname, callback = (error, folder_id) ->) -> + # We're going to try to recover the file into the folder it was in previously, + # but this is historical, so the folder may not exist anymore. Fallback to the + # root folder if not (folder_id == null) + ProjectLocator.findElementByPath {project_id, path: dirname}, (error, element, type) -> + if error? and not error instanceof Errors.NotFoundError + return callback(error) + if type == 'folder' and element? + return callback(null, element._id) + else + return callback(null, null) + + _addEntityWithUniqueName: (user_id, project_id, parent_folder_id, basename, fsPath, callback = (error) ->) -> + FileSystemImportManager.addEntity user_id, project_id, parent_folder_id, basename, fsPath, false, (error, entity) -> + if error? + console.log "ERROR", error, error instanceof Errors.InvalidNameError + if error instanceof Errors.InvalidNameError + # likely a duplicate name, so try with a prefix + date = moment(new Date()).format('Do MMM YY H:mm:ss') + # Move extension to the end so the file type is preserved + extension = Path.extname(basename) + basename = Path.basename(basename, extension) + basename = "#{basename} (Restored on #{date})" + if extension != '' + basename = "#{basename}#{extension}" + FileSystemImportManager.addEntity user_id, project_id, parent_folder_id, basename, fsPath, false, callback else - parent_folder_id = null - # TODO if we get a name conflict error from here, then retry with a timestamp appended - FileSystemImportManager.addEntity user_id, project_id, parent_folder_id, basename, fsPath, false, callback + callback(error) + else + callback() _writeFileVersionToDisk: (project_id, version, pathname, callback = (error, fsPath) ->) -> - url = "#{Settings.apis.project_history.url}/project/#{project_id}/version/#{version}/#{pathname}" + url = "#{Settings.apis.project_history.url}/project/#{project_id}/version/#{version}/#{encodeURIComponent(pathname)}" FileWriter.writeUrlToDisk project_id, url, callback \ No newline at end of file diff --git a/services/web/test/acceptance/coffee/LinkedFilesTests.coffee b/services/web/test/acceptance/coffee/LinkedFilesTests.coffee index 77af535eb5..54a7f7f562 100644 --- a/services/web/test/acceptance/coffee/LinkedFilesTests.coffee +++ b/services/web/test/acceptance/coffee/LinkedFilesTests.coffee @@ -21,7 +21,7 @@ describe "LinkedFiles", -> before (done) -> LinkedUrlProxy.listen 6543, (error) => return done(error) if error? - @owner = new User() + @owner = new User() @owner.login done describe "creating a URL based linked file", -> diff --git a/services/web/test/acceptance/coffee/RestoringFilesTest.coffee b/services/web/test/acceptance/coffee/RestoringFilesTest.coffee index 8225e7ce47..681f4f2ade 100644 --- a/services/web/test/acceptance/coffee/RestoringFilesTest.coffee +++ b/services/web/test/acceptance/coffee/RestoringFilesTest.coffee @@ -1,11 +1,13 @@ async = require "async" expect = require("chai").expect +_ = require 'underscore' ProjectGetter = require "../../../app/js/Features/Project/ProjectGetter.js" User = require "./helpers/User" MockProjectHistoryApi = require "./helpers/MockProjectHistoryApi" MockDocstoreApi = require "./helpers/MockDocstoreApi" +MockFileStoreApi = require "./helpers/MockFileStoreApi" describe "RestoringFiles", -> before (done) -> @@ -30,7 +32,7 @@ describe "RestoringFiles", -> expect(response.statusCode).to.equal 204 done() - it "should have created a doc", -> + it "should have created a doc", (done) -> @owner.getProject @project_id, (error, project) => throw error if error? doc = _.find project.rootFolder[0].docs, (doc) -> @@ -42,10 +44,111 @@ describe "RestoringFiles", -> done() describe "restoring a binary file", -> - it "should have created a file" + beforeEach (done) -> + MockProjectHistoryApi.addOldFile(@project_id, 42, "image.png", "Mock image.png content") + @owner.request { + method: "POST", + url: "/project/#{@project_id}/restore_file", + json: + pathname: "image.png" + version: 42 + }, (error, response, body) -> + throw error if error? + expect(response.statusCode).to.equal 204 + done() + + it "should have created a file", (done) -> + @owner.getProject @project_id, (error, project) => + throw error if error? + file = _.find project.rootFolder[0].fileRefs, (file) -> + file.name == 'image.png' + file = MockFileStoreApi.files[@project_id][file._id] + expect(file.content).to.equal "Mock image.png content" + done() + + describe "restoring to a directory that exists", -> + beforeEach (done) -> + MockProjectHistoryApi.addOldFile(@project_id, 42, "foldername/foo2.tex", "hello world, this is foo-2.tex!") + @owner.request.post { + uri: "project/#{@project_id}/folder", + json: + name: 'foldername' + }, (error, response, body) => + throw error if error? + expect(response.statusCode).to.equal 200 + @owner.request { + method: "POST", + url: "/project/#{@project_id}/restore_file", + json: + pathname: "foldername/foo2.tex" + version: 42 + }, (error, response, body) -> + throw error if error? + expect(response.statusCode).to.equal 204 + done() + + it "should have created the doc in the named folder", (done) -> + @owner.getProject @project_id, (error, project) => + throw error if error? + folder = _.find project.rootFolder[0].folders, (folder) -> + folder.name == 'foldername' + doc = _.find folder.docs, (doc) -> + doc.name == 'foo2.tex' + doc = MockDocstoreApi.docs[@project_id][doc._id] + expect(doc.lines).to.deep.equal [ + "hello world, this is foo-2.tex!" + ] + done() describe "restoring to a directory that no longer exists", -> - it "should have created the file in the root folder" + beforeEach (done) -> + MockProjectHistoryApi.addOldFile(@project_id, 42, "nothere/foo3.tex", "hello world, this is foo-3.tex!") + @owner.request { + method: "POST", + url: "/project/#{@project_id}/restore_file", + json: + pathname: "nothere/foo3.tex" + version: 42 + }, (error, response, body) -> + throw error if error? + expect(response.statusCode).to.equal 204 + done() + + it "should have created the doc in the root folder", (done) -> + @owner.getProject @project_id, (error, project) => + throw error if error? + doc = _.find project.rootFolder[0].docs, (doc) -> + doc.name == 'foo3.tex' + doc = MockDocstoreApi.docs[@project_id][doc._id] + expect(doc.lines).to.deep.equal [ + "hello world, this is foo-3.tex!" + ] + done() describe "restoring to a filename that already exists", -> - it "should have created the file with a timestamp appended" + it "should have created the file with a timestamp appended", -> + beforeEach (done) -> + MockProjectHistoryApi.addOldFile(@project_id, 42, "main.tex", "hello world, this is main.tex!") + @owner.request { + method: "POST", + url: "/project/#{@project_id}/restore_file", + json: + pathname: "main.tex" + version: 42 + }, (error, response, body) -> + throw error if error? + expect(response.statusCode).to.equal 204 + done() + + it "should have created the doc in the root folder", (done) -> + @owner.getProject @project_id, (error, project) => + throw error if error? + doc = _.find project.rootFolder[0].docs, (doc) -> + doc.name.match(/main \(Restored on/) + expect(doc).to.exist + doc = MockDocstoreApi.docs[@project_id][doc._id] + expect(doc.lines).to.deep.equal [ + "hello world, this is main.tex!" + ] + done() + diff --git a/services/web/test/acceptance/coffee/helpers/MockProjectHistoryApi.coffee b/services/web/test/acceptance/coffee/helpers/MockProjectHistoryApi.coffee index 0bf4c15887..381d7ab272 100644 --- a/services/web/test/acceptance/coffee/helpers/MockProjectHistoryApi.coffee +++ b/services/web/test/acceptance/coffee/helpers/MockProjectHistoryApi.coffee @@ -16,7 +16,6 @@ module.exports = MockProjectHistoryApi = app.get "/project/:project_id/version/:version/:pathname", (req, res, next) => {project_id, version, pathname} = req.params key = "#{project_id}:#{version}:#{pathname}" - console.log key, @oldFiles, @oldFiles[key] if @oldFiles[key]? res.send @oldFiles[key] else