mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-04-22 08:26:16 -04:00
Feature: Copy Pad without history (#4295)
New feature to copy a pad without copying entire history. This is useful to perform a low CPU intensive operation while still copying current pad state.
This commit is contained in:
parent
b80a37173e
commit
8c04fe8775
5 changed files with 259 additions and 36 deletions
|
@ -597,6 +597,21 @@ exports.copyPad = async function(sourceID, destinationID, force)
|
|||
await pad.copy(destinationID, force);
|
||||
}
|
||||
|
||||
/**
|
||||
copyPadWithoutHistory(sourceID, destinationID[, force=false]) copies a pad. If force is true,
|
||||
the destination will be overwritten if it exists.
|
||||
|
||||
Example returns:
|
||||
|
||||
{code: 0, message:"ok", data: {padID: destinationID}}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.copyPadWithoutHistory = async function(sourceID, destinationID, force)
|
||||
{
|
||||
let pad = await getPadSafe(sourceID, true);
|
||||
await pad.copyPadWithoutHistory(destinationID, force);
|
||||
}
|
||||
|
||||
/**
|
||||
movePad(sourceID, destinationID[, force=false]) moves a pad. If force is true,
|
||||
the destination will be overwritten if it exists.
|
||||
|
|
|
@ -357,16 +357,9 @@ Pad.prototype.init = async function init(text) {
|
|||
}
|
||||
|
||||
Pad.prototype.copy = async function copy(destinationID, force) {
|
||||
|
||||
let destGroupID;
|
||||
let sourceID = this.id;
|
||||
|
||||
// allow force to be a string
|
||||
if (typeof force === "string") {
|
||||
force = (force.toLowerCase() === "true");
|
||||
} else {
|
||||
force = !!force;
|
||||
}
|
||||
|
||||
// Kick everyone from this pad.
|
||||
// This was commented due to https://github.com/ether/etherpad-lite/issues/3183.
|
||||
// Do we really need to kick everyone out?
|
||||
|
@ -375,31 +368,15 @@ Pad.prototype.copy = async function copy(destinationID, force) {
|
|||
// flush the source pad:
|
||||
this.saveToDatabase();
|
||||
|
||||
// if it's a group pad, let's make sure the group exists.
|
||||
let destGroupID;
|
||||
if (destinationID.indexOf("$") >= 0) {
|
||||
|
||||
destGroupID = destinationID.split("$")[0]
|
||||
let groupExists = await groupManager.doesGroupExist(destGroupID);
|
||||
try {
|
||||
// if it's a group pad, let's make sure the group exists.
|
||||
destGroupID = await this.checkIfGroupExistAndReturnIt(destinationID);
|
||||
|
||||
// group does not exist
|
||||
if (!groupExists) {
|
||||
throw new customError("groupID does not exist for destinationID", "apierror");
|
||||
}
|
||||
}
|
||||
|
||||
// if the pad exists, we should abort, unless forced.
|
||||
let exists = await padManager.doesPadExist(destinationID);
|
||||
|
||||
if (exists) {
|
||||
if (!force) {
|
||||
console.error("erroring out without force");
|
||||
throw new customError("destinationID already exists", "apierror");
|
||||
}
|
||||
|
||||
// exists and forcing
|
||||
let pad = await padManager.getPad(destinationID);
|
||||
await pad.remove();
|
||||
// if force is true and already exists a Pad with the same id, remove that Pad
|
||||
await this.removePadIfForceIsTrueAndAlreadyExist(destinationID, force);
|
||||
} catch(err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
// copy the 'pad' entry
|
||||
|
@ -427,10 +404,7 @@ Pad.prototype.copy = async function copy(destinationID, force) {
|
|||
promises.push(p);
|
||||
}
|
||||
|
||||
// add the new pad to all authors who contributed to the old one
|
||||
this.getAllAuthors().forEach(authorID => {
|
||||
authorManager.addPad(authorID, destinationID);
|
||||
});
|
||||
this.copyAuthorInfoToDestinationPad(destinationID);
|
||||
|
||||
// wait for the above to complete
|
||||
await Promise.all(promises);
|
||||
|
@ -452,6 +426,110 @@ Pad.prototype.copy = async function copy(destinationID, force) {
|
|||
return { padID: destinationID };
|
||||
}
|
||||
|
||||
Pad.prototype.checkIfGroupExistAndReturnIt = async function checkIfGroupExistAndReturnIt(destinationID) {
|
||||
let destGroupID = false;
|
||||
|
||||
if (destinationID.indexOf("$") >= 0) {
|
||||
|
||||
destGroupID = destinationID.split("$")[0]
|
||||
let groupExists = await groupManager.doesGroupExist(destGroupID);
|
||||
|
||||
// group does not exist
|
||||
if (!groupExists) {
|
||||
throw new customError("groupID does not exist for destinationID", "apierror");
|
||||
}
|
||||
}
|
||||
return destGroupID;
|
||||
}
|
||||
|
||||
Pad.prototype.removePadIfForceIsTrueAndAlreadyExist = async function removePadIfForceIsTrueAndAlreadyExist(destinationID, force) {
|
||||
// if the pad exists, we should abort, unless forced.
|
||||
let exists = await padManager.doesPadExist(destinationID);
|
||||
|
||||
// allow force to be a string
|
||||
if (typeof force === "string") {
|
||||
force = (force.toLowerCase() === "true");
|
||||
} else {
|
||||
force = !!force;
|
||||
}
|
||||
|
||||
if (exists) {
|
||||
if (!force) {
|
||||
console.error("erroring out without force");
|
||||
throw new customError("destinationID already exists", "apierror");
|
||||
}
|
||||
|
||||
// exists and forcing
|
||||
let pad = await padManager.getPad(destinationID);
|
||||
await pad.remove();
|
||||
}
|
||||
}
|
||||
|
||||
Pad.prototype.copyAuthorInfoToDestinationPad = function copyAuthorInfoToDestinationPad(destinationID) {
|
||||
// add the new sourcePad to all authors who contributed to the old one
|
||||
this.getAllAuthors().forEach(authorID => {
|
||||
authorManager.addPad(authorID, destinationID);
|
||||
});
|
||||
}
|
||||
|
||||
Pad.prototype.copyPadWithoutHistory = async function copyPadWithoutHistory(destinationID, force) {
|
||||
let destGroupID;
|
||||
let sourceID = this.id;
|
||||
|
||||
// flush the source pad
|
||||
this.saveToDatabase();
|
||||
|
||||
try {
|
||||
// if it's a group pad, let's make sure the group exists.
|
||||
destGroupID = await this.checkIfGroupExistAndReturnIt(destinationID);
|
||||
|
||||
// if force is true and already exists a Pad with the same id, remove that Pad
|
||||
await this.removePadIfForceIsTrueAndAlreadyExist(destinationID, force);
|
||||
} catch(err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
let sourcePad = await padManager.getPad(sourceID);
|
||||
|
||||
// add the new sourcePad to all authors who contributed to the old one
|
||||
this.copyAuthorInfoToDestinationPad(destinationID);
|
||||
|
||||
// Group pad? Add it to the group's list
|
||||
if (destGroupID) {
|
||||
await db.setSub("group:" + destGroupID, ["pads", destinationID], 1);
|
||||
}
|
||||
|
||||
// initialize the pad with a new line to avoid getting the defaultText
|
||||
let newPad = await padManager.getPad(destinationID, '\n');
|
||||
|
||||
let oldAText = this.atext;
|
||||
let newPool = newPad.pool;
|
||||
newPool.fromJsonable(sourcePad.pool.toJsonable()); // copy that sourceId pool to the new pad
|
||||
|
||||
// based on Changeset.makeSplice
|
||||
let assem = Changeset.smartOpAssembler();
|
||||
assem.appendOpWithText('=', '');
|
||||
Changeset.appendATextToAssembler(oldAText, assem);
|
||||
assem.endDocument();
|
||||
|
||||
// although we have instantiated the newPad with '\n', an additional '\n' is
|
||||
// added internally, so the pad text on the revision 0 is "\n\n"
|
||||
let oldLength = 2;
|
||||
|
||||
let newLength = assem.getLengthChange();
|
||||
let newText = oldAText.text;
|
||||
|
||||
// create a changeset that removes the previous text and add the newText with
|
||||
// all atributes present on the source pad
|
||||
let changeset = Changeset.pack(oldLength, newLength, assem.toString(), newText);
|
||||
newPad.appendRevision(changeset);
|
||||
|
||||
hooks.callAll('padCopy', { 'originalPad': this, 'destinationID': destinationID });
|
||||
|
||||
return { padID: destinationID };
|
||||
}
|
||||
|
||||
|
||||
Pad.prototype.remove = async function remove() {
|
||||
var padID = this.id;
|
||||
|
||||
|
|
|
@ -142,8 +142,13 @@ version["1.2.14"] = Object.assign({}, version["1.2.13"],
|
|||
}
|
||||
);
|
||||
|
||||
version["1.2.15"] = Object.assign({}, version["1.2.14"],
|
||||
{ "copyPadWithoutHistory" : ["sourceID", "destinationID", "force"]
|
||||
}
|
||||
);
|
||||
|
||||
// set the latest available API version here
|
||||
exports.latestApiVersion = '1.2.14';
|
||||
exports.latestApiVersion = '1.2.15';
|
||||
|
||||
// exports the versions so it can be used by the new Swagger endpoint
|
||||
exports.version = version;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue