/** * Copyright 2009 Google Inc. * * 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. */ import("etherpad.collab.ace.easysync1"); import("etherpad.collab.ace.easysync2"); import("sqlbase.sqlbase"); import("fastJSON"); import("sqlbase.sqlcommon.*"); import("etherpad.collab.ace.contentcollector.sanitizeUnicode"); function _getPadStringArrayNumId(padId, arrayName) { var stmnt = "SELECT NUMID FROM "+btquote("PAD_"+arrayName.toUpperCase()+"_META")+ " WHERE ("+btquote("ID")+" = ?)"; return withConnection(function(conn) { var pstmnt = conn.prepareStatement(stmnt); return closing(pstmnt, function() { pstmnt.setString(1, padId); var resultSet = pstmnt.executeQuery(); return closing(resultSet, function() { if (! resultSet.next()) { return -1; } return resultSet.getInt(1); }); }); }); } function _getEntirePadStringArray(padId, arrayName) { var numId = _getPadStringArrayNumId(padId, arrayName); if (numId < 0) { return []; } var stmnt = "SELECT PAGESTART, OFFSETS, DATA FROM "+btquote("PAD_"+arrayName.toUpperCase()+"_TEXT")+ " WHERE ("+btquote("NUMID")+" = ?)"; return withConnection(function(conn) { var pstmnt = conn.prepareStatement(stmnt); return closing(pstmnt, function() { pstmnt.setInt(1, numId); var resultSet = pstmnt.executeQuery(); return closing(resultSet, function() { var array = []; while (resultSet.next()) { var pageStart = resultSet.getInt(1); var lengthsString = resultSet.getString(2); var dataString = resultSet.getString(3); var dataIndex = 0; var arrayIndex = pageStart; lengthsString.split(',').forEach(function(len) { if (len) { len = Number(len); array[arrayIndex] = dataString.substr(dataIndex, len); dataIndex += len; } arrayIndex++; }); } return array; }); }); }); } function _overwriteEntirePadStringArray(padId, arrayName, array) { var numId = _getPadStringArrayNumId(padId, arrayName); if (numId < 0) { // generate numId withConnection(function(conn) { var ps = conn.prepareStatement("INSERT INTO "+btquote("PAD_"+arrayName.toUpperCase()+"_META")+ " ("+btquote("ID")+") VALUES (?)", java.sql.Statement.RETURN_GENERATED_KEYS); closing(ps, function() { ps.setString(1, padId); ps.executeUpdate(); var keys = ps.getGeneratedKeys(); if ((! keys) || (! keys.next())) { throw new Error("Couldn't generate key for "+arrayName+" table for pad "+padId); } closing(keys, function() { numId = keys.getInt(1); }); }); }); } withConnection(function(conn) { var stmnt1 = "DELETE FROM "+btquote("PAD_"+arrayName.toUpperCase()+"_TEXT")+ " WHERE ("+btquote("NUMID")+" = ?)"; var pstmnt1 = conn.prepareStatement(stmnt1); closing(pstmnt1, function() { pstmnt1.setInt(1, numId); pstmnt1.executeUpdate(); }); var PAGE_SIZE = 20; var numPages = Math.floor((array.length-1) / PAGE_SIZE + 1); var PAGES_PER_BATCH = 20; var curPage = 0; while (curPage < numPages) { var stmnt2 = "INSERT INTO "+btquote("PAD_"+arrayName.toUpperCase()+"_TEXT")+ " ("+btquote("NUMID")+", "+btquote("PAGESTART")+", "+btquote("OFFSETS")+ ", "+btquote("DATA")+") VALUES (?, ?, ?, ?)"; var pstmnt2 = conn.prepareStatement(stmnt2); closing(pstmnt2, function() { for(var n=0;n 0) { //P var diffs = []; //P for(var i=0;i= (c.oldLen() - 1)) { c[c.length-2] = c.oldLen() - c[c.length-3]; } else { c.push(c.oldLen() - 1, 1, ""); } } var isExtraNewlineInOutput = false; if (isExtraNewlineInSource) { cs[1] += 1; // oldLen ++ } if ((cs[cs.length-1] && cs[cs.length-1].slice(-1) != '\n') || ((! cs[cs.length-1]) && inputText.charAt(cs[cs.length-3] + cs[cs.length-2] - 1) != '\n')) { // new text won't end with newline! if (isExtraNewlineInSource) { keepLastCharacter(cs); } else { cs[cs.length-1] += "\n"; } cs[2] += 1; // newLen ++ isExtraNewlineInOutput = true; } var oldLen = cs.oldLen(); var newLen = cs.newLen(); // final-newline-preserving modifications to changeset {{{ // These fixes are required for changesets that don't respect the // new rule that the final newline of the document not be touched, // and also for changesets tweaked above. It is important that the // fixed changesets obey all the constraints on version 1 changesets // so that they may become valid version 2 changesets. { function collapsePotentialEmptyLastTake(c) { if (c[c.length-2] == 0 && c.length > 6) { if (! c[c.length-1]) { // last strip doesn't take or insert now c.length -= 3; } else { // the last two strips should be merged // e.g. fo\n -> rock\nbar\n: then in this block, // "Changeset,3,9,0,0,r,1,1,ck,2,0,\nbar" becomes // "Changeset,3,9,0,0,r,1,1,ck\nbar" c[c.length-4] += c[c.length-1]; c.length -= 3; } } } var lastStripStart = cs[cs.length-3]; var lastStripTake = cs[cs.length-2]; var lastStripInsert = cs[cs.length-1]; if (lastStripStart + lastStripTake == oldLen && lastStripInsert) { // an insert at end // e.g. foo\n -> foo\nbar\n: // "Changeset,4,8,0,4,bar\n" becomes "Changeset,4,8,0,3,\nbar,3,1," // first make the previous newline part of the insertion cs[cs.length-2] -= 1; cs[cs.length-1] = '\n'+cs[cs.length-1].slice(0,-1); collapsePotentialEmptyLastTake(cs); keepLastCharacter(cs); } else if (lastStripStart + lastStripTake < oldLen && ! lastStripInsert) { // ends with pure deletion cs[cs.length-2] -= 1; collapsePotentialEmptyLastTake(cs); keepLastCharacter(cs); } else if (lastStripStart + lastStripTake < oldLen) { // ends with replacement cs[cs.length-1] = cs[cs.length-1].slice(0,-1); keepLastCharacter(cs); } } // }}} var ops = []; var lastOpcode = ''; function appendOp(opcode, text, startChar, endChar) { function num(n) { return easysync2.Changeset.numToString(n); } var lines = 0; var lastNewlineEnd = startChar; for (;;) { var index = text.indexOf('\n', lastNewlineEnd); if (index < 0 || index >= endChar) { break; } lines++; lastNewlineEnd = index+1; } var a = (opcode == '+' ? attribs : ''); var multilineChars = (lastNewlineEnd - startChar); var seqLength = endChar - startChar; var op = ''; if (lines > 0) { op = [a, '|', num(lines), opcode, num(multilineChars)].join(''); } if (multilineChars < seqLength) { op += [a, opcode, num(seqLength - multilineChars)].join(''); } if (op) { // we reorder a single - and a single + if (opcode == '-' && lastOpcode == '+') { ops.splice(ops.length-1, 0, op); } else { ops.push(op); lastOpcode = opcode; } } } var oldPos = 0; var textPieces = []; var charBankPieces = []; cs.eachStrip(function(start, take, insert) { if (start > oldPos) { appendOp('-', inputText, oldPos, start); } if (take) { if (start+take < oldLen || insert) { appendOp('=', inputText, start, start+take); } textPieces.push(inputText.substring(start, start+take)); } if (insert) { appendOp('+', insert, 0, insert.length); textPieces.push(insert); charBankPieces.push(insert); } oldPos = start+take; }); // ... and no final deletions after the newline fixing. var newCs = easysync2.Changeset.pack(oldLen, newLen, ops.join(''), sanitizeUnicode(charBankPieces.join(''))); var newText = textPieces.join(''); return [newCs, newText, isExtraNewlineInOutput]; } //////////////////////////////////////////////////////////////////////////////// // unicode issues: 5SaYQp7cKV // // hard-coded just for testing; any pad is allowed to have corruption. // var newlineCorruptedPads = [ // '0OCGFKkjDv', '14dWjOiOxP', '1LL8XQCBjC', '1jMnjEEK6e', '21', // '23DytOPN7d', '32YzfdT2xS', '3E6GB7l7FZ', '3Un8qaCfJh', '3YAj3rC9em', // '3vY2eaHSw5', '4834RRTLlg', '4Fm1iVSTWI', '5NpTNqWHGC', '7FYNSdYQVa', // '7RZCbvgw1z', '8EVpyN6HyY', '8P5mPRxPVr', '8aHFRmLxKR', '8dsj9eGQfP', // 'BSoGobOJZZ', 'Bf0uVghKy0', 'C2f3umStKd', 'CHlu2CA8F3', 'D2WEwgvg1W', // 'DNLTpuP2wl', 'DwNpm2TDgu', 'EKPByZ3EGZ', 'FwQxu6UKQx', 'HUn9O34rFl', // 'JKZhxMo20E', 'JVjuukL42N', 'JVuBlWxaxL', 'Jmw5lPNYcl', 'KnZHz6jE2P', // 'Luyp6ylbgR', 'MB6lPoN1eI', 'McsCrQUM6c', 'NWIuVobIw9', 'OKERTLQCCn', // 'OchiOchi', 'OfhKHCB8jJ', 'OkM3Jv3XY9', 'PX5Z89mx29', 'PdmKQIvOEd', // 'R9NQNB66qt', 'RvULFSvCbV', 'RyLJC6Qo1x', 'SBlKLwr2Ag', 'SavD72Q9P7', // 'SfXyxseAeF', 'TTGZ4yO2PI', 'U3U7rT3d6w', 'UFmqpQIDAi', 'V7Or0QQk4m', // 'VPCM5ReAQm', 'VvIYHzIJUY', 'W0Ccc3BVGb', 'Wv3cGgSgjg', 'WwVPgaZUK5', // 'WyIFUJXfm5', 'XxESEsgQ6R', 'Yc5Yq3WCuU', 'ZRqCFaRx6h', 'ZepX6TLFbD', // 'bSeImT5po4', 'bqIlTkFDiH', 'btt9vNPSQ9', 'c97YJj8PSN', 'd9YV3sypKF', // 'eDzzkrwDRU', 'eFQJZWclzo', 'eaz44OhFDu', 'ehKkx1YpLA', 'ep', // 'foNq3v3e9T', 'form6rooma', 'fqhtIHG0Ii', 'fvZyCRZjv2', 'gZnadICPYV', // 'gvGXtMKhQk', 'h7AYuTxUOd', 'hc1UZSti3J', 'hrFQtae2jW', 'i8rENUZUMu', // 'iFW9dceEmh', 'iRNEc8SlOc', 'jEDsDgDlaK', 'jo8ngXlSJh', 'kgJrB9Gh2M', // 'klassennetz76da2661f8ceccfe74faf97d25a4b418', // 'klassennetzf06d4d8176d0804697d9650f836cb1f7', 'lDHgmfyiSu', // 'mA1cbvxFwA', 'mSJpW1th29', 'mXHAqv1Emu', 'monocles12', 'n0NhU3FxxT', // 'ng7AlzPb5b', 'ntbErnnuyz', 'oVnMO0dX80', 'omOTPVY3Gl', 'p5aNFCfYG9', // 'pYxjVCILuL', 'phylab', 'pjVBFmnhf1', 'qGohFW3Lbr', 'qYlbjeIHDs', // 'qgf4OwkFI6', 'qsi', 'rJQ09pRexM', 'snNjlS1aLC', 'tYKC53TDF9', // 'u1vZmL8Yjv', 'ur4sb7DBJB', 'vesti', 'w9NJegEAZt', 'wDwlSCby2s', // 'wGFJJRT514', 'wTgEoQGqng', 'xomMZGhius', 'yFEFYWBSvr', 'z7tGFKsGk6', // 'zIJWNK8Z4i', 'zNMGJYI7hq']; // function _time(f) { // var t1 = +(new Date); // f(); // var t2 = +(new Date); // return t2 - t1; // } // function listAllRevisionCounts() { // var padList = sqlbase.getAllJSONKeys("PAD_META"); // //padList.length = 10; // padList = padList.slice(68000, 68100); // padList.forEach(function(id) { // model.accessPadGlobal(id, function(pad) { // System.out.println((new java.lang.Integer(pad.getHeadRevisionNumber()).toString())+ // " "+id); // dbwriter.writePadNow(pad, true); // }, 'r'); // }); // } // function verifyAllPads() { // //var padList = sqlbase.getAllJSONKeys("PAD_META"); // //padList = newlineCorruptedPads; // var padList = ['0OCGFKkjDv']; // //padList = ['form6rooma']; // //padList.length = 10; // var numOks = 0; // var numErrors = 0; // var numNewlineBugs = 0; // var longestPad; // var longestPadTime = -1; // System.out.println(padList.length+" pads."); // var totalTime = _time(function() { // padList.forEach(function(id) { // model.accessPadGlobal(id, function(pad) { // var padTime = _time(function() { // System.out.print(id+"... "); // try { // verifyMigration(pad); // System.out.println("OK ("+(++numOks)+")"); // } // catch (e) { // System.out.println("ERROR ("+(++numErrors)+")"+(e.finalNewlineMissing?" [newline]":"")); // System.out.println(e.toString()); // if (e.finalNewlineMissing) { // numNewlineBugs++; // } // } // }); // if (padTime > longestPadTime) { // longestPadTime = padTime; // longestPad = id; // } // }, 'r'); // }); // }); // System.out.println("finished verifyAllPads in "+(totalTime/1000)+" seconds."); // System.out.println(numOks+" OK"); // System.out.println(numErrors+" ERROR"); // System.out.println("Most time-consuming pad: "+longestPad+" / "+longestPadTime+" ms"); // } // function _literal(v) { // if ((typeof v) == "string") { // return '"'+v.replace(/[\\\"]/g, '\\$1').replace(/\n/g, '\\n')+'"'; // } // else return v.toSource(); // } // function _putFile(str, path) { // var writer = new java.io.FileWriter(path); // writer.write(str); // writer.close(); // }