diff --git a/node/ImportHandler.js b/node/ImportHandler.js new file mode 100644 index 00000000..c7e29483 --- /dev/null +++ b/node/ImportHandler.js @@ -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; + }); +} diff --git a/node/Models/Pad.js b/node/Models/Pad.js index 41d91f3d..a2353a44 100644 --- a/node/Models/Pad.js +++ b/node/Models/Pad.js @@ -191,6 +191,20 @@ Class('Pad', { 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) { this.chatHead++; diff --git a/node/PadMessageHandler.js b/node/PadMessageHandler.js index 00902b1a..5d43fa6b 100644 --- a/node/PadMessageHandler.js +++ b/node/PadMessageHandler.js @@ -433,71 +433,7 @@ function handleUserChanges(client, message) pad.appendRevision(nlChangeset); } - //ex. updatePadClients - - //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); + exports.updatePadClients(pad, callback); } ], 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... */ diff --git a/node/server.js b/node/server.js index 2bc57a2a..30f869c5 100644 --- a/node/server.js +++ b/node/server.js @@ -32,6 +32,7 @@ var express = require('express'); var path = require('path'); var minify = require('./minify'); var exportHandler; +var importHandler; var exporthtml; var readOnlyManager; @@ -51,7 +52,7 @@ catch(e) var serverName = "Etherpad-Lite " + version + " (http://j.mp/ep-lite)"; -//cache a week +//cache 6 hours exports.maxAge = 1000*60*60*6; async.waterfall([ @@ -70,6 +71,7 @@ async.waterfall([ readOnlyManager = require("./ReadOnlyManager"); exporthtml = require("./exporters/exporthtml"); exportHandler = require('./ExportHandler'); + importHandler = require('./ImportHandler'); //set logging if(settings.logHTTP) @@ -215,6 +217,20 @@ async.waterfall([ 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 / app.get('/', function(req, res) { diff --git a/package.json b/package.json index 6bc04413..3a45955a 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "keywords" : ["etherpad", "realtime", "collaborative", "editor"], "author" : "Peter 'Pita' Martischka ", "contributors": [ - { "name": "Hans Pinckaers"} + { "name": "John McLear", + "name": "Hans Pinckaers"} ], "dependencies" : { "socket.io" : "0.7.7", @@ -15,7 +16,8 @@ "express" : "2.4.2", "clean-css" : "0.2.4", "uglify-js" : "1.0.4", - "gzip" : "0.1.0" + "gzip" : "0.1.0", + "formidable" : "1.0.2" }, "version" : "0.0.4" } diff --git a/static/js/pad_impexp.js b/static/js/pad_impexp.js index 308a6d9d..261a3138 100644 --- a/static/js/pad_impexp.js +++ b/static/js/pad_impexp.js @@ -24,9 +24,9 @@ var padimpexp = (function() function addImportFrames() { - $("#impexp-import .importframe").remove(); - $('#impexp-import').append( - $('')); + $("#import .importframe").remove(); + var iframe = $(''); + $('#import').append(iframe); } function fileInputUpdated() @@ -64,7 +64,7 @@ var padimpexp = (function() $('#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?"); if (ret) - { + { hidePanelCall = paddocbar.hideLaterIfNoOtherInteraction(); currentImportTimer = window.setTimeout(function() { @@ -88,6 +88,11 @@ var padimpexp = (function() }, 0); $('#importarrow').stop(true, true).hide(); $('#importstatusball').show(); + + $("#import .importframe").load(function() + { + importDone(); + }); } return ret; } @@ -225,6 +230,8 @@ var padimpexp = (function() var self = { init: function() { + $("#importform").get(0).setAttribute('action', document.location.href + "/import"); + $("#impexp-close").click(function() { paddocbar.setShownPanel(null); @@ -249,13 +256,13 @@ var padimpexp = (function() disable: function() { $("#impexp-disabled-clickcatcher").show(); - $("#impexp-import").css('opacity', 0.5); + $("#import").css('opacity', 0.5); $("#impexp-export").css('opacity', 0.5); }, enable: function() { $("#impexp-disabled-clickcatcher").hide(); - $("#impexp-import").css('opacity', 1); + $("#import").css('opacity', 1); $("#impexp-export").css('opacity', 1); } }; diff --git a/static/pad.html b/static/pad.html index 3e2161fe..09251abd 100644 --- a/static/pad.html +++ b/static/pad.html @@ -208,7 +208,7 @@ We removed this feature cause its not worth the space it needs in the editbar
Import from text file, HTML, Word, or RTF:

-
+
@@ -222,7 +222,7 @@ We removed this feature cause its not worth the space it needs in the editbar
-
+