@Override
 public IFile createFile(String path) {
   File file = new File(context.absPath(path));
   String name = file.getName();
   String parentPath = file.getParent();
   try {
     VfsUtil.createDirectories(parentPath);
   } catch (IOException e) {
     Flog.error("Create directories error %s", e);
     context.errorMessage("The Floobits plugin was unable to create directories for file.");
     return null;
   }
   VirtualFile parent = LocalFileSystem.getInstance().findFileByPath(parentPath);
   if (parent == null) {
     Flog.error("Virtual file is null? %s", parentPath);
     return null;
   }
   VirtualFile newFile;
   try {
     newFile = parent.findOrCreateChildData(context, name);
   } catch (Throwable e) {
     Flog.error("Create file error %s", e);
     context.errorMessage(
         String.format("The Floobits plugin was unable to create a file: %s.", path));
     return null;
   }
   return new FileImpl(newFile);
 }
 @Override
 public void _on_data(String name, JsonObject obj) {
   Flog.info("on_data %s %s", obj, name);
   if (!name.equals("create_user")) {
     return;
   }
   FloorcJson floorcJson = FloorcJson.getFloorcJsonFromSettings();
   HashMap<String, String> auth_host = floorcJson.auth.get(host);
   if (floorcJson.auth == null) {
     floorcJson.auth = new HashMap<String, HashMap<String, String>>();
   }
   if (auth_host == null) {
     auth_host = new HashMap<String, String>();
     floorcJson.auth.put(host, auth_host);
   }
   for (Map.Entry<String, JsonElement> thing : obj.entrySet()) {
     String key = thing.getKey();
     if (key.equals("name")) {
       continue;
     }
     auth_host.put(key, thing.getValue().getAsString());
   }
   PersistentJson p = PersistentJson.getInstance();
   Settings.write(context, floorcJson);
   p.auto_generated_account = true;
   p.disable_account_creation = true;
   p.save();
   context.statusMessage(
       String.format(
           "Successfully created new Floobits account with username %s. "
               + "You can now share a project or join a workspace.",
           auth_host.get("username")));
   Flog.info("All setup");
   context.shutdown();
 }
 public void write() {
   if (!isPopulated()) {
     Flog.warn("Unable to write %s because it's not populated yet.", path);
     return;
   }
   VirtualFile virtualFile = getVirtualFile();
   if (virtualFile == null) {
     virtualFile = createFile();
     if (virtualFile == null) {
       context.errorMessage("The Floobits plugin was unable to write to a file.");
       return;
     }
   }
   Document d = Buf.getDocumentForVirtualFile(virtualFile);
   if (d != null) {
     try {
       Listener.flooDisable();
       d.setReadOnly(false);
       d.setText(buf);
     } finally {
       Listener.flooEnable();
     }
     return;
   }
   Flog.warn("Tried to write to null document: %s", path);
   try {
     virtualFile.setBinaryContent(buf.getBytes());
   } catch (IOException e) {
     Flog.warn(e);
     context.errorMessage("The Floobits plugin was unable to write to a file.");
   }
 }
 public void write(Serializable obj) {
   // TODO: threading issue. lock channel
   if (channel == null) {
     Flog.error("not writing because no channel");
     return;
   }
   String data = new Gson().toJson(obj);
   if (channel == null) {
     Flog.error("Lost connection? Not writing because no channel. Also, race condition!");
     return;
   }
   channel.writeAndFlush(data + "\n");
 }
 public void read() {
   VirtualFile virtualFile = this.getVirtualFile();
   if (virtualFile == null) {
     Flog.warn("Can't get virtual file to read from disk %s", this);
     return;
   }
   Document d = Buf.getDocumentForVirtualFile(virtualFile);
   if (d == null) {
     Flog.warn("Can't get document to read from disk %s", this);
     return;
   }
   this.buf = d.getText();
   this.md5 = DigestUtils.md5Hex(this.buf);
 }
  @Override
  public IFile createDirectories(String path) {
    VirtualFile directory = null;
    try {
      directory = VfsUtil.createDirectories(path);
    } catch (IOException e) {
      Flog.error(e);
    }

    if (directory == null) {
      Flog.warn("Failed to create directories %s %s", path);
      return null;
    }
    return new FileImpl(directory);
  }
 public void send_patch(VirtualFile virtualFile) {
   Document d = Buf.getDocumentForVirtualFile(virtualFile);
   if (d == null) {
     Flog.warn("Can't get document to read from disk for sending patch %s", path);
     return;
   }
   send_patch(d.getText());
 }
 protected void reconnect() {
   if (retries <= 0) {
     Flog.log("Giving up!");
     context.shutdown();
     return;
   }
   delay = Math.min(10000, Math.round((float) 1.5 * delay));
   Flog.log("Connection lost. Reconnecting in %sms", delay);
   context.setTimeout(
       delay,
       new Runnable() {
         @Override
         public void run() {
           Flog.log("Attempting to reconnect.");
           connect();
         }
       });
 }
 public void shutdown() {
   retries = -1;
   if (channel != null) {
     try {
       channel.disconnect();
       channel.close();
     } catch (Throwable e) {
       Flog.error(e);
     }
     channel = null;
   }
 }
 @Override
 public void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
   JsonObject obj = (JsonObject) new JsonParser().parse(msg);
   JsonElement name = obj.get("name");
   if (name == null) {
     Flog.warn("No name for receive, ignoring");
     return;
   }
   String requestName = name.getAsString();
   retries = MAX_RETRIES;
   delay = INITIAL_RECONNECT_DELAY;
   handler.on_data(requestName, obj);
 }
  protected void connect() {
    if (retries <= 0) {
      Flog.error("I give up connecting.");
      return;
    }
    retries -= 1;
    FlooUrl flooUrl = handler.getUrl();
    final String host;
    final int port;

    if (flooUrl.host.equals(Constants.floobitsDomain) && retries % 4 == 0) {
      host = Constants.OUTBOUND_FILTER_PROXY_HOST;
      port = Constants.OUTBOUND_FILTER_PROXY_PORT;
    } else {
      host = flooUrl.host;
      port = flooUrl.port;
    }

    if (channel == null) {
      _connect(host, port);
      return;
    }
    try {
      channel
          .close()
          .addListener(
              new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture channelFuture) throws Exception {
                  channel = null;
                  _connect(host, port);
                }
              });
    } catch (Throwable e) {
      Flog.error(e);
      reconnect();
    }
  }
 @Override
 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
   if (retries < 0) {
     return;
   }
   if (cause instanceof ConnectException) {
     Flog.error("Failed to connect: " + cause.getMessage());
     return;
   }
   if (cause instanceof IOException) {
     Flog.error(cause);
     return;
   }
   if (cause instanceof TooLongFrameException) {
     Flog.error(String.format("Took too long: %s", cause.getMessage()));
     return;
   }
   //        if (cause instanceof SSLHandshakeException) {
   //            Flog.error(String.format("SSL Handshake failed: %s", cause.getMessage()));
   //            return;
   //        }
   API.uploadCrash(handler, context, cause);
 }
  protected void _connect(String host, int port) {
    Bootstrap b = new Bootstrap();

    if (!context.addGroup(b)) {
      Flog.warn("no loopgroup, will not reconnect");
      return;
    }
    b.channel(NioSocketChannel.class);
    b.option(ChannelOption.SO_KEEPALIVE, true);
    b.option(ChannelOption.TCP_NODELAY, true);
    b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 15 * 1000);
    b.handler(new FlooChannelInitializer(this));

    try {
      ChannelFuture connect = b.connect(host, port);
      channel = connect.channel();
    } catch (RejectedExecutionException e) {
      context.errorMessage("Can not connect to floobits!");
      context.shutdown();
    } catch (Throwable e) {
      Flog.error(e);
      reconnect();
    }
  }
  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);
  }
 @Override
 public IDoc getDocument(IFile virtualFile) {
   if (virtualFile == null) {
     return null;
   }
   Document document;
   try {
     document =
         FileDocumentManager.getInstance().getDocument(((FileImpl) virtualFile).virtualFile);
   } catch (RuntimeException e) {
     // We've seen an java.io.EOFException here before.
     Flog.error(e);
     return null;
   }
   if (document == null) {
     return null;
   }
   return new DocImpl(context, document);
 }
 @Override
 public void on_connect() {
   Flog.error("Connected.");
   conn.setRetries(-1);
   conn.write(new NewAccount());
 }
 @Override
 public void channelUnregistered(final ChannelHandlerContext ctx) {
   Flog.log("Disconnected from %s", ctx.channel().remoteAddress());
   reconnect();
 }
 @Override
 public void channelInactive(final ChannelHandlerContext ctx) throws Exception {
   Flog.log("Channel is now inactive.");
 }
 @Override
 public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
   Flog.log("%s", evt.toString());
 }
  public void patch(final FlooPatch res) {
    final TextBuf b = this;
    Flog.info("Got _on_patch");

    String text;
    String md5FromDoc;
    final Document d;

    String oldText = buf;
    VirtualFile virtualFile = b.getVirtualFile();
    if (virtualFile == null) {
      Flog.warn("VirtualFile is null, no idea what do do. Aborting everything %s", this);
      getBuf();
      return;
    }
    d = Buf.getDocumentForVirtualFile(virtualFile);
    if (d == null) {
      Flog.warn("Document not found for %s", virtualFile);
      getBuf();
      return;
    }
    String viewText;
    if (virtualFile.exists()) {
      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);
      }
    } else {
      viewText = oldText;
    }

    b.cancelTimeout();

    String md5Before = DigestUtils.md5Hex(viewText);
    if (!md5Before.equals(res.md5_before)) {
      Flog.warn("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.isWritable()) {
      d.setReadOnly(false);
    }
    if (!ReadonlyStatusHandler.ensureDocumentWritable(context.project, d)) {
      Flog.info("Document: %s is not writable.", d);
      return;
    }

    final Editor[] editors = EditorFactory.getInstance().getEditors(d, context.project);
    final HashMap<ScrollingModel, Integer[]> original = new HashMap<ScrollingModel, Integer[]>();
    for (Editor editor : editors) {
      if (editor.isDisposed()) {
        continue;
      }
      ScrollingModel scrollingModel = editor.getScrollingModel();
      original.put(
          scrollingModel,
          new Integer[] {
            scrollingModel.getHorizontalScrollOffset(), scrollingModel.getVerticalScrollOffset()
          });
    }
    for (FlooPatchPosition flooPatchPosition : positions) {
      int start = Math.max(0, flooPatchPosition.start);
      int end_ld = Math.max(start + flooPatchPosition.end, start);
      end_ld = Math.min(end_ld, d.getTextLength());
      String contents = NEW_LINE.matcher(flooPatchPosition.text).replaceAll("\n");
      Throwable e = null;
      try {
        Listener.flooDisable();
        d.replaceString(start, end_ld, contents);
      } catch (Throwable exception) {
        e = exception;
      } finally {
        Listener.flooEnable();
      }

      if (e != null) {
        Flog.warn(e);
        getBuf();
        return;
      }
    }
    text = d.getText();
    md5FromDoc = DigestUtils.md5Hex(text);
    if (!md5FromDoc.equals(res.md5_after)) {
      Flog.info("md5FromDoc mismatch (ours %s remote %s)", md5FromDoc, res.md5_after);
      b.setGetBufTimeout();
    }

    for (Map.Entry<ScrollingModel, Integer[]> entry : original.entrySet()) {
      ScrollingModel model = entry.getKey();
      Integer[] offsets = entry.getValue();
      model.scrollHorizontally(offsets[0]);
      model.scrollVertically(offsets[1]);
    }

    b.set(text, md5FromDoc);
    Flog.log("Patched %s", res.path);
  }
 @Override
 public void channelActive(ChannelHandlerContext ctx) throws Exception {
   Flog.log("Connected to %s", ctx.channel().remoteAddress());
   handler.on_connect();
 }