Added support for document export, no link from the pad so far

This commit is contained in:
Peter 'Pita' Martischka 2011-07-19 19:48:11 +01:00
parent 83829759e9
commit eeddf1348f
5 changed files with 244 additions and 2 deletions

93
node/Abiword.js Normal file
View File

@ -0,0 +1,93 @@
/**
* Controls the communication with the Abiword application
*/
/*
* 2011 Peter 'Pita' Martischka
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var util = require('util');
var spawn = require('child_process').spawn;
var async = require("async");
var settings = require("./settings");
//Queue with the converts we have to do
var queue = async.queue(doConvertTask, 1);
//spawn the abiword process
var abiword = spawn(settings.abiword, ["--plugin", "AbiCommand"]);
//output error messages to stderr
abiword.stderr.on('data', function (data)
{
console.error("Abiword: " + data);
});
//throw exceptions if abiword is dieing
abiword.on('exit', function (code)
{
throw "Abiword died with exit code " + code;
});
//delegate the processing of stdout to a other function
abiword.stdout.on('data',onAbiwordStdout);
var stdoutCallback = null;
var stdoutBuffer = "";
var firstPrompt = true;
function onAbiwordStdout(data)
{
//add data to buffer
stdoutBuffer+=data.toString();
//we're searching for the prompt, cause this means everything we need is in the buffer
if(stdoutBuffer.search("AbiWord:>") != -1)
{
//filter the feedback message
var lines = stdoutBuffer.split("\n");
var err = lines [1] == "OK" ? null : lines[1];
//reset the buffer
stdoutBuffer = "";
//call the callback with the error message
//skip the first prompt
if(stdoutCallback != null && !firstPrompt)
{
stdoutCallback(err);
stdoutCallback = null;
}
firstPrompt = false;
}
}
function doConvertTask(task, callback)
{
abiword.stdin.write("convert " + task.srcFile + " " + task.destFile + " " + task.type + "\n");
//create a callback that calls the task callback and the caller callback
stdoutCallback = function (err)
{
callback();
task.callback(err);
};
}
exports.convertFile = function(srcFile, destFile, type, callback)
{
queue.push({"srcFile": srcFile, "destFile": destFile, "type": type, "callback": callback});
};

116
node/ExportHandler.js Normal file
View File

@ -0,0 +1,116 @@
/**
* Handles the export requests
*/
/*
* 2011 Peter 'Pita' Martischka
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var exporthtml = require("./exporters/exporthtml");
var padManager = require("./PadManager");
var async = require("async");
var fs = require("fs");
var settings = require('./settings');
//load abiword only if its enabled
if(settings.abiword != null)
var abiword = require("./Abiword");
/**
* do a requested export
*/
exports.doExport = function(req, res, padId, type)
{
//tell the browser that this is a downloadable file
res.attachment(padId + "." + type);
//if this is a plain text export, we can do this directly
if(type == "txt")
{
padManager.getPad(padId, function(err, pad)
{
if(err)
throw err;
res.send(pad.text());
});
}
else
{
var html;
var randNum;
var srcFile, destFile;
async.series([
//render the html document
function(callback)
{
exporthtml.getPadHTMLDocument(padId, null, false, function(err, _html)
{
html = _html;
callback(err);
});
},
//decide what to do with the html export
function(callback)
{
//if this is a html export, we can send this from here directly
if(type == "html")
{
res.send(html);
callback("stop");
}
//write the html export to a file
else
{
randNum = Math.floor(Math.random()*new Date().getTime());
srcFile = "/tmp/eplite_export_" + randNum + ".html";
fs.writeFile(srcFile, html, callback);
}
},
//send the convert job to abiword
function(callback)
{
//ensure html can be collected by the garbage collector
html = null;
destFile = "/tmp/eplite_export_" + randNum + "." + type;
abiword.convertFile(srcFile, destFile, type, callback);
},
//send the file
function(callback)
{
res.sendfile(destFile, null, callback);
},
//clean up temporary files
function(callback)
{
async.parallel([
function(callback)
{
fs.unlink(srcFile, callback);
},
function(callback)
{
fs.unlink(destFile, callback);
}
], callback);
}
], function(err)
{
if(err && err != "stop") throw err;
})
}
};

View File

@ -31,6 +31,7 @@ var async = require('async');
var express = require('express');
var path = require('path');
var minify = require('./minify');
var exportHandler;
var exporthtml;
var readOnlyManager;
@ -68,6 +69,7 @@ async.waterfall([
//load modules that needs a initalized db
readOnlyManager = require("./ReadOnlyManager");
exporthtml = require("./exporters/exporthtml");
exportHandler = require('./ExportHandler');
//set logging
if(settings.logHTTP)
@ -191,6 +193,28 @@ async.waterfall([
res.sendfile(filePath, { maxAge: exports.maxAge });
});
//serve timeslider.html under /p/$padname/timeslider
app.get('/p/:pad/export/:type', function(req, res, next)
{
var types = ["pdf", "doc", "txt", "html", "odt"];
//send a 404 if we don't support this filetype
if(types.indexOf(req.params.type) == -1)
{
next();
return;
}
//if abiword is disabled, and this is a format we only support with abiword, output a message
if(settings.abiword == null && req.params.type != "html" && req.params.type != "txt" )
{
res.send("Abiword is not enabled at this Etherpad Lite instance. Set the path to Abiword in settings.json to enable this feature");
return;
}
res.header("Server", serverName);
exportHandler.doExport(req, res, req.params.pad, req.params.type);
});
//serve index.html under /
app.get('/', function(req, res)
{

View File

@ -46,6 +46,11 @@ exports.defaultPadText = "Welcome to Etherpad Lite!\n\nThis pad text is synchron
*/
exports.minify = true;
/**
* The path of the abiword executable
*/
exports.abiword = null;
//read the settings sync
var settingsStr = fs.readFileSync("../settings.json").toString();
@ -75,7 +80,7 @@ for(var i in settings)
}
//we know this setting, so we overwrite it
if(exports[i])
if(exports[i] !== undefined)
{
exports[i] = settings[i];
}

View File

@ -31,5 +31,9 @@
/* if true, all css & js will be minified before sending to the client. This will improve the loading performance massivly,
but makes it impossible to debug the javascript/css */
"minify" : true
"minify" : true,
/* This is the path to the Abiword executable. Setting it to null, disables abiword.
Abiword is needed to enable the import/export of pads*/
"abiword" : null
}