Import works now on the server side

pull/35/head
Peter 'Pita' Martischka 2011-07-21 20:13:58 +01:00
parent 4b268f9579
commit b13fbbfd73
7 changed files with 235 additions and 76 deletions

117
node/ImportHandler.js Normal file
View File

@ -0,0 +1,117 @@
/**
* Handles the import 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 padManager = require("./PadManager");
var padMessageHandler = require("./PadMessageHandler");
var async = require("async");
var fs = require("fs");
var settings = require('./settings');
var formidable = require('formidable');
//load abiword only if its enabled
if(settings.abiword != null)
var abiword = require("./Abiword");
/**
* do a requested import
*/
exports.doImport = function(req, res, padId)
{
//pipe to a file
//convert file to text via abiword
//set text in the pad
var srcFile, destFile;
var pad;
var text;
async.series([
//save the uploaded file to /tmp
function(callback)
{
var form = new formidable.IncomingForm();
form.keepExtensions = true;
form.parse(req, function(err, fields, files)
{
//save the path of the uploaded file
srcFile = files.file.path;
callback(err);
});
},
//convert file to text
function(callback)
{
var randNum = Math.floor(Math.random()*new Date().getTime());
destFile = "/tmp/eplite_import_" + randNum + ".txt";
abiword.convertFile(srcFile, destFile, "txt", callback);
},
//get the pad object
function(callback)
{
padManager.getPad(padId, function(err, _pad)
{
pad = _pad;
callback(err);
});
},
//read the text
function(callback)
{
fs.readFile(destFile, "utf8", function(err, _text)
{
text = _text;
callback(err);
});
},
//change text of the pad and broadcast the changeset
function(callback)
{
pad.setText(text);
padMessageHandler.updatePadClients(pad, callback);
},
//clean up temporary files
function(callback)
{
async.parallel([
function(callback)
{
fs.unlink(srcFile, callback);
},
function(callback)
{
fs.unlink(destFile, callback);
}
], callback);
}
], function(err)
{
//close the connection
res.send("ok");
if(err) throw err;
});
}

View File

@ -191,6 +191,20 @@ Class('Pad', {
return this.atext.text; return this.atext.text;
}, },
setText : function(newText)
{
//clean the new text
newText = exports.cleanText(newText);
var oldText = this.text();
//create the changeset
var changeset = Changeset.makeSplice(oldText, 0, oldText.length-1, newText);
//append the changeset
this.appendRevision(changeset);
},
appendChatMessage: function(text, userId, time) appendChatMessage: function(text, userId, time)
{ {
this.chatHead++; this.chatHead++;

View File

@ -433,71 +433,7 @@ function handleUserChanges(client, message)
pad.appendRevision(nlChangeset); pad.appendRevision(nlChangeset);
} }
//ex. updatePadClients exports.updatePadClients(pad, callback);
//go trough all sessions on this pad
async.forEach(pad2sessions[pad.id], function(session, callback)
{
var lastRev = sessioninfos[session].rev;
//https://github.com/caolan/async#whilst
//send them all new changesets
async.whilst(
function (){ return lastRev < pad.getHeadRevisionNumber()},
function(callback)
{
var author, revChangeset;
var r = ++lastRev;
async.parallel([
function (callback)
{
pad.getRevisionAuthor(r, function(err, value)
{
author = value;
callback(err);
});
},
function (callback)
{
pad.getRevisionChangeset(r, function(err, value)
{
revChangeset = value;
callback(err);
});
}
], function(err)
{
if(err)
{
callback(err);
return;
}
if(author == sessioninfos[session].author)
{
socketio.sockets.sockets[session].json.send({"type":"COLLABROOM","data":{type:"ACCEPT_COMMIT", newRev:r}});
}
else
{
var forWire = Changeset.prepareForWire(revChangeset, pad.pool);
var wireMsg = {"type":"COLLABROOM","data":{type:"NEW_CHANGES", newRev:r,
changeset: forWire.translated,
apool: forWire.pool,
author: author}};
socketio.sockets.sockets[session].json.send(wireMsg);
}
callback(null);
});
},
callback
);
sessioninfos[session].rev = pad.getHeadRevisionNumber();
},callback);
} }
], function(err) ], function(err)
{ {
@ -505,6 +441,73 @@ function handleUserChanges(client, message)
}); });
} }
exports.updatePadClients = function(pad, callback)
{
//go trough all sessions on this pad
async.forEach(pad2sessions[pad.id], function(session, callback)
{
var lastRev = sessioninfos[session].rev;
//https://github.com/caolan/async#whilst
//send them all new changesets
async.whilst(
function (){ return lastRev < pad.getHeadRevisionNumber()},
function(callback)
{
var author, revChangeset;
var r = ++lastRev;
async.parallel([
function (callback)
{
pad.getRevisionAuthor(r, function(err, value)
{
author = value;
callback(err);
});
},
function (callback)
{
pad.getRevisionChangeset(r, function(err, value)
{
revChangeset = value;
callback(err);
});
}
], function(err)
{
if(err)
{
callback(err);
return;
}
if(author == sessioninfos[session].author)
{
socketio.sockets.sockets[session].json.send({"type":"COLLABROOM","data":{type:"ACCEPT_COMMIT", newRev:r}});
}
else
{
var forWire = Changeset.prepareForWire(revChangeset, pad.pool);
var wireMsg = {"type":"COLLABROOM","data":{type:"NEW_CHANGES", newRev:r,
changeset: forWire.translated,
apool: forWire.pool,
author: author}};
socketio.sockets.sockets[session].json.send(wireMsg);
}
callback(null);
});
},
callback
);
sessioninfos[session].rev = pad.getHeadRevisionNumber();
},callback);
}
/** /**
* Copied from the Etherpad Source Code. Don't know what this methode does excatly... * Copied from the Etherpad Source Code. Don't know what this methode does excatly...
*/ */

View File

@ -32,6 +32,7 @@ var express = require('express');
var path = require('path'); var path = require('path');
var minify = require('./minify'); var minify = require('./minify');
var exportHandler; var exportHandler;
var importHandler;
var exporthtml; var exporthtml;
var readOnlyManager; var readOnlyManager;
@ -51,7 +52,7 @@ catch(e)
var serverName = "Etherpad-Lite " + version + " (http://j.mp/ep-lite)"; var serverName = "Etherpad-Lite " + version + " (http://j.mp/ep-lite)";
//cache a week //cache 6 hours
exports.maxAge = 1000*60*60*6; exports.maxAge = 1000*60*60*6;
async.waterfall([ async.waterfall([
@ -70,6 +71,7 @@ async.waterfall([
readOnlyManager = require("./ReadOnlyManager"); readOnlyManager = require("./ReadOnlyManager");
exporthtml = require("./exporters/exporthtml"); exporthtml = require("./exporters/exporthtml");
exportHandler = require('./ExportHandler'); exportHandler = require('./ExportHandler');
importHandler = require('./ImportHandler');
//set logging //set logging
if(settings.logHTTP) if(settings.logHTTP)
@ -215,6 +217,20 @@ async.waterfall([
exportHandler.doExport(req, res, req.params.pad, req.params.type); exportHandler.doExport(req, res, req.params.pad, req.params.type);
}); });
//handle import requests
app.post('/p/:pad/import', function(req, res, next)
{
//if abiword is disabled, skip handling this request
if(settings.abiword == null)
{
next();
return;
}
res.header("Server", serverName);
importHandler.doImport(req, res, req.params.pad);
});
//serve index.html under / //serve index.html under /
app.get('/', function(req, res) app.get('/', function(req, res)
{ {

View File

@ -5,7 +5,8 @@
"keywords" : ["etherpad", "realtime", "collaborative", "editor"], "keywords" : ["etherpad", "realtime", "collaborative", "editor"],
"author" : "Peter 'Pita' Martischka <petermartischka@googlemail.com>", "author" : "Peter 'Pita' Martischka <petermartischka@googlemail.com>",
"contributors": [ "contributors": [
{ "name": "Hans Pinckaers"} { "name": "John McLear",
"name": "Hans Pinckaers"}
], ],
"dependencies" : { "dependencies" : {
"socket.io" : "0.7.7", "socket.io" : "0.7.7",
@ -15,7 +16,8 @@
"express" : "2.4.2", "express" : "2.4.2",
"clean-css" : "0.2.4", "clean-css" : "0.2.4",
"uglify-js" : "1.0.4", "uglify-js" : "1.0.4",
"gzip" : "0.1.0" "gzip" : "0.1.0",
"formidable" : "1.0.2"
}, },
"version" : "0.0.4" "version" : "0.0.4"
} }

View File

@ -24,9 +24,9 @@ var padimpexp = (function()
function addImportFrames() function addImportFrames()
{ {
$("#impexp-import .importframe").remove(); $("#import .importframe").remove();
$('#impexp-import').append( var iframe = $('<iframe style="display: none;" name="importiframe" class="importframe"></iframe>');
$('<iframe style="display: none;" name="importiframe" class="importframe"></iframe>')); $('#import').append(iframe);
} }
function fileInputUpdated() function fileInputUpdated()
@ -64,7 +64,7 @@ var padimpexp = (function()
$('#importmessagefail').fadeOut("fast"); $('#importmessagefail').fadeOut("fast");
var ret = window.confirm("Importing a file will overwrite the current text of the pad." + " Are you sure you want to proceed?"); var ret = window.confirm("Importing a file will overwrite the current text of the pad." + " Are you sure you want to proceed?");
if (ret) if (ret)
{ {
hidePanelCall = paddocbar.hideLaterIfNoOtherInteraction(); hidePanelCall = paddocbar.hideLaterIfNoOtherInteraction();
currentImportTimer = window.setTimeout(function() currentImportTimer = window.setTimeout(function()
{ {
@ -88,6 +88,11 @@ var padimpexp = (function()
}, 0); }, 0);
$('#importarrow').stop(true, true).hide(); $('#importarrow').stop(true, true).hide();
$('#importstatusball').show(); $('#importstatusball').show();
$("#import .importframe").load(function()
{
importDone();
});
} }
return ret; return ret;
} }
@ -225,6 +230,8 @@ var padimpexp = (function()
var self = { var self = {
init: function() init: function()
{ {
$("#importform").get(0).setAttribute('action', document.location.href + "/import");
$("#impexp-close").click(function() $("#impexp-close").click(function()
{ {
paddocbar.setShownPanel(null); paddocbar.setShownPanel(null);
@ -249,13 +256,13 @@ var padimpexp = (function()
disable: function() disable: function()
{ {
$("#impexp-disabled-clickcatcher").show(); $("#impexp-disabled-clickcatcher").show();
$("#impexp-import").css('opacity', 0.5); $("#import").css('opacity', 0.5);
$("#impexp-export").css('opacity', 0.5); $("#impexp-export").css('opacity', 0.5);
}, },
enable: function() enable: function()
{ {
$("#impexp-disabled-clickcatcher").hide(); $("#impexp-disabled-clickcatcher").hide();
$("#impexp-import").css('opacity', 1); $("#import").css('opacity', 1);
$("#impexp-export").css('opacity', 1); $("#impexp-export").css('opacity', 1);
} }
}; };

View File

@ -208,7 +208,7 @@ We removed this feature cause its not worth the space it needs in the editbar
<div id="import"> <div id="import">
Import from text file, HTML, Word, or RTF:<br/><br/> Import from text file, HTML, Word, or RTF:<br/><br/>
<form id="importform" method="post" action="import" target="imporiframe" enctype="multipart/form-data"> <form id="importform" method="post" action="" target="importiframe" enctype="multipart/form-data">
<div class="importformdiv" id="importformfilediv"> <div class="importformdiv" id="importformfilediv">
<input type="file" name="file" size="20" id="importfileinput" /> <input type="file" name="file" size="20" id="importfileinput" />
<div class="importmessage" id="importmessagefail"></div> <div class="importmessage" id="importmessagefail"></div>
@ -222,7 +222,7 @@ We removed this feature cause its not worth the space it needs in the editbar
<img alt="" id="importarrow" src="/static/img/leftarrow.png" align="top" /> <img alt="" id="importarrow" src="/static/img/leftarrow.png" align="top" />
</span> </span>
</div> </div>
</form> </form>
</div> </div>
<div id="importexportline"></div> <div id="importexportline"></div>