<commit_before>/*
 * Copyright 2000-2016 JetBrains s.r.o.
 *
 * 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.
 */
package com.intellij.idea;

import com.intellij.ide.IdeBundle;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.intellij.openapi.application.JetBrainsProtocolHandler;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Consumer;
import com.intellij.util.NotNullProducer;
import com.intellij.util.PlatformUtils;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.io.BuiltInServer;
import org.jetbrains.io.MessageDecoder;

import java.io.*;
import java.lang.management.ManagementFactory;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.file.Files;
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Pattern;

/**
 * @author mike
 */
public final class SocketLock {
  public enum ActivateStatus {ACTIVATED, NO_INSTANCE, CANNOT_ACTIVATE}

  private static final String PORT_FILE = "port";
  private static final String PORT_LOCK_FILE = "port.lock";
  private static final String TOKEN_FILE = "token";

  private static final String ACTIVATE_COMMAND = "activate ";
  private static final String PID_COMMAND = "pid";
  private static final String PATHS_EOT_RESPONSE = "---";
  private static final String OK_RESPONSE = "ok";

  private final String myConfigPath;
  private final String mySystemPath;
  private final AtomicReference<Consumer<List<String>>> myActivateListener = new AtomicReference<Consumer<List<String>>>();
  private String myToken;
  private BuiltInServer myServer;

  public SocketLock(@NotNull String configPath, @NotNull String systemPath) {
    myConfigPath = canonicalPath(configPath);
    mySystemPath = canonicalPath(systemPath);
  }

  public void setExternalInstanceListener(@Nullable Consumer<List<String>> consumer) {
    myActivateListener.set(consumer);
  }

  public void dispose() {
    log("enter: dispose()");

    BuiltInServer server = myServer;
    if (server == null) return;

    try {
      Disposer.dispose(server);
    }
    finally {
      try {
        underLocks(new Callable<Void>() {
          @Override
          public Void call() throws Exception {
            FileUtil.delete(new File(myConfigPath, PORT_FILE));
            FileUtil.delete(new File(mySystemPath, PORT_FILE));
            FileUtil.delete(new File(mySystemPath, TOKEN_FILE));
            return null;
          }
        });
      }
      catch (Exception e) {
        Logger.getInstance(SocketLock.class).warn(e);
      }
    }
  }

  @Nullable
  public BuiltInServer getServer() {
    return myServer;
  }

  @NotNull
  public ActivateStatus lock() throws Exception {
    return lock(ArrayUtil.EMPTY_STRING_ARRAY);
  }

  @NotNull
  public ActivateStatus lock(@NotNull final String[] args) throws Exception {
    log("enter: lock(config=%s system=%s)", myConfigPath, mySystemPath);

    return underLocks(() -> {
      File portMarkerC = new File(myConfigPath, PORT_FILE);
      File portMarkerS = new File(mySystemPath, PORT_FILE);

      MultiMap<Integer, String> portToPath = MultiMap.createSmart();
      addExistingPort(portMarkerC, myConfigPath, portToPath);
      addExistingPort(portMarkerS, mySystemPath, portToPath);
      if (!portToPath.isEmpty()) {
        for (Map.Entry<Integer, Collection<String>> entry : portToPath.entrySet()) {
          ActivateStatus status = tryActivate(entry.getKey(), entry.getValue(), args);
          if (status != ActivateStatus.NO_INSTANCE) {
            return status;
          }
        }
      }

      if (isShutdownCommand()) {
        System.exit(0);
      }

      myToken = UUID.randomUUID().toString();
      String[] lockedPaths = {myConfigPath, mySystemPath};
      int workerCount = PlatformUtils.isIdeaCommunity() || PlatformUtils.isDatabaseIDE() || PlatformUtils.isCidr() ? 1 : 2;
      NotNullProducer<ChannelHandler> handler = () -> new MyChannelInboundHandler(lockedPaths, myActivateListener, myToken);
      myServer = BuiltInServer.startNioOrOio(workerCount, 6942, 50, false, handler);

      byte[] portBytes = Integer.toString(myServer.getPort()).getBytes(CharsetToolkit.UTF8_CHARSET);
      FileUtil.writeToFile(portMarkerC, portBytes);
      FileUtil.writeToFile(portMarkerS, portBytes);

      File tokenFile = new File(mySystemPath, TOKEN_FILE);
      FileUtil.writeToFile(tokenFile, myToken.getBytes(CharsetToolkit.UTF8_CHARSET));
      PosixFileAttributeView view = Files.getFileAttributeView(tokenFile.toPath(), PosixFileAttributeView.class);
      if (view != null) {
        try {
          view.setPermissions(ContainerUtil.newHashSet(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE));
        }
        catch (IOException e) {
          log(e);
        }
      }

      log("exit: lock(): succeed");
      return ActivateStatus.NO_INSTANCE;
    });
  }

  private <V> V underLocks(@NotNull Callable<V> action) throws Exception {
    FileUtilRt.createDirectory(new File(myConfigPath));
    FileOutputStream lock1 = new FileOutputStream(new File(myConfigPath, PORT_LOCK_FILE), true);
    try {
      FileUtilRt.createDirectory(new File(mySystemPath));
      FileOutputStream lock2 = new FileOutputStream(new File(mySystemPath, PORT_LOCK_FILE), true);
      try {
        return action.call();
      }
      finally {
        lock2.close();
      }
    }
    finally {
      lock1.close();
    }
  }

  private static void addExistingPort(@NotNull File portMarker, @NotNull String path, @NotNull MultiMap<Integer, String> portToPath) {
    if (portMarker.exists()) {
      try {
        portToPath.putValue(Integer.parseInt(FileUtilRt.loadFile(portMarker)), path);
      }
      catch (Exception e) {
        log(e);
        // don't delete - we overwrite it on write in any case
      }
    }
  }

  @NotNull
  private ActivateStatus tryActivate(int portNumber, @NotNull Collection<String> paths, @NotNull String[] args) {
    log("trying: port=%s", portNumber);
    args = checkForJetBrainsProtocolCommand(args);
    try {
      Socket socket = new Socket(InetAddress.getLoopbackAddress(), portNumber);
      try {
        socket.setSoTimeout(1000);

        boolean result = false;
        @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") DataInputStream in = new DataInputStream(socket.getInputStream());
        while (true) {
          try {
            String path = in.readUTF();
            log("read: path=%s", path);<commit_msg>Cleanup (formatting)<commit_after>/*
 * Copyright 2000-2016 JetBrains s.r.o.
 *
 * 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.
 */
package com.intellij.idea;

import com.intellij.ide.IdeBundle;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.intellij.openapi.application.JetBrainsProtocolHandler;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Consumer;
import com.intellij.util.NotNullProducer;
import com.intellij.util.PlatformUtils;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.io.BuiltInServer;
import org.jetbrains.io.MessageDecoder;

import java.io.*;
import java.lang.management.ManagementFactory;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.file.Files;
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Pattern;

/**
 * @author mike
 */
public final class SocketLock {
  public enum ActivateStatus {ACTIVATED, NO_INSTANCE, CANNOT_ACTIVATE}

  private static final String PORT_FILE = "port";
  private static final String PORT_LOCK_FILE = "port.lock";
  private static final String TOKEN_FILE = "token";

  private static final String ACTIVATE_COMMAND = "activate ";
  private static final String PID_COMMAND = "pid";
  private static final String PATHS_EOT_RESPONSE = "---";
  private static final String OK_RESPONSE = "ok";

  private final String myConfigPath;
  private final String mySystemPath;
  private final AtomicReference<Consumer<List<String>>> myActivateListener = new AtomicReference<Consumer<List<String>>>();
  private String myToken;
  private BuiltInServer myServer;

  public SocketLock(@NotNull String configPath, @NotNull String systemPath) {
    myConfigPath = canonicalPath(configPath);
    mySystemPath = canonicalPath(systemPath);
  }

  public void setExternalInstanceListener(@Nullable Consumer<List<String>> consumer) {
    myActivateListener.set(consumer);
  }

  public void dispose() {
    log("enter: dispose()");

    BuiltInServer server = myServer;
    if (server == null) return;

    try {
      Disposer.dispose(server);
    }
    finally {
      try {
        underLocks(new Callable<Void>() {
          @Override
          public Void call() throws Exception {
            FileUtil.delete(new File(myConfigPath, PORT_FILE));
            FileUtil.delete(new File(mySystemPath, PORT_FILE));
            FileUtil.delete(new File(mySystemPath, TOKEN_FILE));
            return null;
          }
        });
      }
      catch (Exception e) {
        Logger.getInstance(SocketLock.class).warn(e);
      }
    }
  }

  @Nullable
  public BuiltInServer getServer() {
    return myServer;
  }

  @NotNull
  public ActivateStatus lock() throws Exception {
    return lock(ArrayUtil.EMPTY_STRING_ARRAY);
  }

  @NotNull
  public ActivateStatus lock(@NotNull String[] args) throws Exception {
    log("enter: lock(config=%s system=%s)", myConfigPath, mySystemPath);

    return underLocks(() -> {
      File portMarkerC = new File(myConfigPath, PORT_FILE);
      File portMarkerS = new File(mySystemPath, PORT_FILE);

      MultiMap<Integer, String> portToPath = MultiMap.createSmart();
      addExistingPort(portMarkerC, myConfigPath, portToPath);
      addExistingPort(portMarkerS, mySystemPath, portToPath);
      if (!portToPath.isEmpty()) {
        for (Map.Entry<Integer, Collection<String>> entry : portToPath.entrySet()) {
          ActivateStatus status = tryActivate(entry.getKey(), entry.getValue(), args);
          if (status != ActivateStatus.NO_INSTANCE) {
            log("exit: lock(): " + status);
            return status;
          }
        }
      }

      if (isShutdownCommand()) {
        System.exit(0);
      }

      myToken = UUID.randomUUID().toString();
      String[] lockedPaths = {myConfigPath, mySystemPath};
      int workerCount = PlatformUtils.isIdeaCommunity() || PlatformUtils.isDatabaseIDE() || PlatformUtils.isCidr() ? 1 : 2;
      NotNullProducer<ChannelHandler> handler = () -> new MyChannelInboundHandler(lockedPaths, myActivateListener, myToken);
      myServer = BuiltInServer.startNioOrOio(workerCount, 6942, 50, false, handler);

      byte[] portBytes = Integer.toString(myServer.getPort()).getBytes(CharsetToolkit.UTF8_CHARSET);
      FileUtil.writeToFile(portMarkerC, portBytes);
      FileUtil.writeToFile(portMarkerS, portBytes);

      File tokenFile = new File(mySystemPath, TOKEN_FILE);
      FileUtil.writeToFile(tokenFile, myToken.getBytes(CharsetToolkit.UTF8_CHARSET));
      PosixFileAttributeView view = Files.getFileAttributeView(tokenFile.toPath(), PosixFileAttributeView.class);
      if (view != null) {
        try {
          view.setPermissions(ContainerUtil.newHashSet(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE));
        }
        catch (IOException e) {
          log(e);
        }
      }

      log("exit: lock(): succeed");
      return ActivateStatus.NO_INSTANCE;
    });
  }

  private <V> V underLocks(@NotNull Callable<V> action) throws Exception {
    FileUtilRt.createDirectory(new File(myConfigPath));
    try (@SuppressWarnings("unused") FileOutputStream lock1 = new FileOutputStream(new File(myConfigPath, PORT_LOCK_FILE), true)) {
      FileUtilRt.createDirectory(new File(mySystemPath));
      try (@SuppressWarnings("unused") FileOutputStream lock2 = new FileOutputStream(new File(mySystemPath, PORT_LOCK_FILE), true)) {
        return action.call();
      }
    }
  }

  private static void addExistingPort(@NotNull File portMarker, @NotNull String path, @NotNull MultiMap<Integer, String> portToPath) {
    if (portMarker.exists()) {
      try {
        portToPath.putValue(Integer.parseInt(FileUtilRt.loadFile(portMarker)), path);
      }
      catch (Exception e) {
        log(e);
        // don't delete - we overwrite it on write in any case
      }
    }
  }

  @NotNull
  private ActivateStatus tryActivate(int portNumber, @NotNull Collection<String> paths, @NotNull String[] args) {
    log("trying: port=%s", portNumber);
    args = checkForJetBrainsProtocolCommand(args);
    try {
      Socket socket = new Socket(InetAddress.getLoopbackAddress(), portNumber);
      try {
        socket.setSoTimeout(1000);

        boolean result = false;
        @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") DataInputStream in = new DataInputStream(socket.getInputStream());
        while (true) {
          try {
            String path = in.readUTF();
            log("read: path=%s", path);
{'fraction_non_alphanumeric': 0.1047637122793699, 'fraction_numerical': 0.0037957866767887646, 'mean_word_length': 3.8224527150701646, 'pattern_counts': {'":': 0, '<': 37, '<?xml version=': 0, '>': 41, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7485766', 'n_tokens_mistral': 4681, 'n_tokens_neox': 4575, 'n_words': 1058}