package floobits.common.protocol.buf; import floobits.common.Constants; import floobits.common.Encoding; import floobits.common.OutboundRequestHandler; import floobits.common.dmp.FlooDmp; import floobits.common.dmp.FlooPatchPosition; import floobits.common.dmp.diff_match_patch; import floobits.common.interfaces.IContext; import floobits.common.interfaces.IDoc; import floobits.common.interfaces.IFile; import floobits.common.protocol.FlooPatch; import floobits.utilities.Flog; import org.apache.commons.codec.digest.DigestUtils; import java.util.LinkedList; import java.util.List; public class TextBuf extends Buf<String> { protected static FlooDmp dmp = new FlooDmp(); public TextBuf(String path, Integer id, String buf, String md5, IContext context, OutboundRequestHandler outbound) { super(path, id, buf, md5, context, outbound); if (buf != null) { this.buf = Constants.NEW_LINE.matcher(buf).replaceAll("\n"); } this.encoding = Encoding.UTF8; } public void read () { IDoc d = getVirtualDoc(); if (d == null) { return; } this.buf = d.getText(); this.md5 = DigestUtils.md5Hex(this.buf); } public void write() { if (!isPopulated()) { Flog.warn("Unable to write %s because it's not populated yet.", path); return; } IDoc d = getVirtualDoc(); if (d != null) { synchronized (context) { try { context.setListener(false); d.setReadOnly(false); d.setText(buf); } finally { context.setListener(true); } return; } } Flog.warn("Tried to write to null document: %s", path); IFile virtualFile = getOrCreateFile(); try { virtualFile.setBytes(buf.getBytes("UTF-8")); } catch (Throwable e) { Flog.error(e); context.errorMessage("The Floobits plugin was unable to write to a file."); } } synchronized public void set(String s, String newMD5) { buf = s == null ? null : Constants.NEW_LINE.matcher(s).replaceAll("\n"); md5 = newMD5; } public String serialize() { return buf; } @Override public void send_patch(IFile virtualFile) { IDoc d = context.iFactory.getDocument(virtualFile); if (d == null) { Flog.warn("Can't get document to read from disk for sending patch %s", path); return; } send_patch(d.getText()); } public void send_patch(String current) { String before_md5; String textPatch; String after_md5; String previous = buf; before_md5 = md5; after_md5 = DigestUtils.md5Hex(current); LinkedList<diff_match_patch.Patch> patches = dmp.patch_make(previous, current); textPatch = dmp.patch_toText(patches); set(current, after_md5); if (before_md5.equals(after_md5)) { Flog.log("Not patching %s because no change.", path); return; } outbound.patch(textPatch, before_md5, this); } private void getBuf() { cancelTimeout(); outbound.getBuf(id); } private void setGetBufTimeout() { final int buf_id = id; cancelTimeout(); timeout = context.setTimeout(2000, new Runnable() { @Override public void run() { Flog.info("Sending get buf after timeout."); outbound.getBuf(buf_id); } }); } public void patch(final FlooPatch res) { final TextBuf b = this; Flog.info("Got _on_patch"); String oldText = buf; IFile virtualFile = b.getVirtualFile(); if (virtualFile == null) { Flog.warn("VirtualFile is null, no idea what do do. Aborting everything %s", this); getBuf(); return; } IDoc d = context.iFactory.getDocument(virtualFile); if (d == null) { Flog.warn("Document not found for %s", virtualFile); getBuf(); return; } String viewText; if (!virtualFile.exists()) { viewText = oldText; } else { viewText = d.getText(); if (viewText.equals(oldText)) { b.forced_patch = false; } else if (!b.forced_patch) { b.forced_patch = true; oldText = viewText; b.send_patch(viewText); Flog.warn("Sending force patch for %s. this is dangerous!", b.path); } } b.cancelTimeout(); String md5Before = DigestUtils.md5Hex(viewText); if (!md5Before.equals(res.md5_before)) { Flog.error("starting md5s don't match for %s. this is dangerous!", b.path); } List<diff_match_patch.Patch> patches = dmp.patch_fromText(res.patch); final Object[] results = dmp.patch_apply((LinkedList<diff_match_patch.Patch>) patches, oldText); final String patchedContents = (String) results[0]; final boolean[] patchesClean = (boolean[]) results[1]; final FlooPatchPosition[] positions = (FlooPatchPosition[]) results[2]; for (boolean clean : patchesClean) { if (!clean) { Flog.log("Patch not clean for %s. Sending get_buf and setting readonly.", d); getBuf(); return; } } // XXX: If patchedContents have carriage returns this will be a problem: String md5After = DigestUtils.md5Hex(patchedContents); if (!md5After.equals(res.md5_after)) { Flog.info("MD5 after mismatch (ours %s remote %s)", md5After, res.md5_after); } if (!d.makeWritable()) { Flog.info("Document: %s is not writable.", d); return; } String text = d.patch(positions); if (text == null) { getBuf(); return; } String md5FromDoc = DigestUtils.md5Hex(text); if (!md5FromDoc.equals(res.md5_after)) { Flog.info("md5FromDoc mismatch (ours %s remote %s)", md5FromDoc, res.md5_after); b.setGetBufTimeout(); } b.set(text, md5FromDoc); Flog.log("Patched %s", res.path); } }