diff --git a/services/web/app/coffee/Features/LinkedFiles/ProjectOutputFileAgent.coffee b/services/web/app/coffee/Features/LinkedFiles/ProjectOutputFileAgent.coffee new file mode 100644 index 0000000000..4850abd0e0 --- /dev/null +++ b/services/web/app/coffee/Features/LinkedFiles/ProjectOutputFileAgent.coffee @@ -0,0 +1,102 @@ +FileWriter = require('../../infrastructure/FileWriter') +AuthorizationManager = require('../Authorization/AuthorizationManager') +ProjectGetter = require('../Project/ProjectGetter') +FileWriter = require('../../infrastructure/FileWriter') +Settings = require 'settings-sharelatex' +CompileManager = require '../Compile/CompileManager' +CompileController = require '../Compile/CompileController' +ClsiCookieManager = require '../Compile/ClsiCookieManager' +_ = require "underscore" +request = require "request" + + +BadDataError = (message) -> + error = new Error(message) + error.name = 'BadData' + error.__proto__ = BadDataError.prototype + return error +BadDataError.prototype.__proto__ = Error.prototype + + +ProjectNotFoundError = (message) -> + error = new Error(message) + error.name = 'ProjectNotFound' + error.__proto__ = ProjectNotFoundError.prototype + return error +ProjectNotFoundError.prototype.__proto__ = Error.prototype + + +OutputFileFetchFailedError = (message) -> + error = new Error(message) + error.name = 'OutputFileFetchFailedError' + error.__proto__ = OutputFileFetchFailedError.prototype + return error +OutputFileFetchFailedError.prototype.__proto__ = Error.prototype + + +module.exports = ProjectOutputFileAgent = { + + sanitizeData: (data) -> + return { + source_project_id: data.source_project_id, + source_output_file_path: data.source_output_file_path + } + + canCreate: (data) -> true + + decorateLinkedFileData: (data, callback = (err, newData) ->) -> + callback = _.once(callback) + ProjectGetter.getProject data.source_project_id, {name: 1}, (err, project) -> + return callback(err) if err? + if !project? + return callback(new ProjectNotFoundError()) + callback(err, _.extend(data, {source_project_display_name: project.name})) + + checkAuth: (project_id, data, current_user_id, callback = (error, allowed)->) -> + callback = _.once(callback) + { source_project_id } = data + AuthorizationManager.canUserReadProject current_user_id, source_project_id, null, (err, canRead) -> + return callback(err) if err? + callback(null, canRead) + + _validate: (data) -> + data.source_project_id? && data.source_output_file_path? + + writeIncomingFileToDisk: (project_id, data, current_user_id, callback = (error, fsPath) ->) -> + callback = _.once(callback) + # TODO: + # - Compile project + # - Get output file content + # - Write to disk + # - callback with fs-path + if !ProjectOutputFileAgent._validate(data) + return callback(new BadDataError()) + { source_project_id, source_output_file_path } = data + CompileManager.compile source_project_id, null, {}, (err) -> + return callback(err) if err? + url = "#{Settings.apis.clsi.url}/project/#{source_project_id}/output/#{source_output_file_path}" + ClsiCookieManager.getCookieJar source_project_id, (err, jar)-> + return callback(err) if err? + oneMinute = 60 * 1000 + # the base request + options = { url: url, method: "GET", timeout: oneMinute, jar : jar } + readStream = request(options) + readStream.on "error", callback + readStream.on "response", (response) -> + if 200 <= response.statusCode < 300 + FileWriter.writeStreamToDisk project_id, readStream, callback + else + error = new OutputFileFetchFailedError("Output file fetch failed: #{url}") + error.statusCode = response.statusCode + callback(error) + + handleError: (error, req, res, next) -> + if error instanceof BadDataError + res.status(400).send("The submitted data is not valid") + else if error instanceof SourceFileNotFoundError + res.status(404).send("Source file not found") + else if error instanceof ProjectNotFoundError + res.status(404).send("Project not found") + else + next(error) +} diff --git a/services/web/app/views/project/editor/binary-file.pug b/services/web/app/views/project/editor/binary-file.pug index f4ccf8c9dc..bd0e4b9732 100644 --- a/services/web/app/views/project/editor/binary-file.pug +++ b/services/web/app/views/project/editor/binary-file.pug @@ -38,6 +38,7 @@ div.binary-file.full-size( ) #{translate("no_preview_available")} div.binary-file-footer + // Linked Files: URL div(ng-if="openFile.linkedFileData.provider == 'url'") p i.fa.fa-fw.fa-external-link-square.fa-rotate-180.linked-file-icon @@ -47,6 +48,7 @@ div.binary-file.full-size( | | at {{ openFile.created | formatDate:'h:mm a' }} {{ openFile.created | relativeDate }} + // Linked Files: Project File div(ng-if="openFile.linkedFileData.provider == 'project_file'") p i.fa.fa-fw.fa-external-link-square.fa-rotate-180.linked-file-icon @@ -61,7 +63,23 @@ div.binary-file.full-size( | | at {{ openFile.created | formatDate:'h:mm a' }} {{ openFile.created | relativeDate }} - span(ng-if="openFile.linkedFileData.provider == 'url' || openFile.linkedFileData.provider == 'project_file'") + // Linked Files: Project Output File + div(ng-if="openFile.linkedFileData.provider == 'project_output_file'") + p + i.fa.fa-fw.fa-external-link-square.fa-rotate-180.linked-file-icon + | Imported from the output of + | + a(ng-if='!openFile.linkedFileData.v1_source_doc_id' + ng-href='/project/{{openFile.linkedFileData.source_project_id}}' target="_blank") + | {{ openFile.linkedFileData.source_project_display_name }} + span(ng-if='openFile.linkedFileData.v1_source_doc_id') + | {{ openFile.linkedFileData.source_project_display_name }} + | : {{ openFile.linkedFileData.source_output_file_path }}, + | + | at {{ openFile.created | formatDate:'h:mm a' }} {{ openFile.created | relativeDate }} + + // Bottom Controls + span(ng-if="openFile.linkedFileData.provider") button.btn.btn-success( href, ng-click="refreshFile(openFile)", ng-disabled="refreshing"