fvarrui / JavaPackager

:package: Gradle/Maven plugin to package Java applications as native Windows, MacOS, or Linux executables and create installers for them.
GNU General Public License v3.0
1.07k stars 133 forks source link

How can I set the singleInstance property of launch4j through a plugin #406

Closed Mr-YiQiJia closed 6 months ago

Mr-YiQiJia commented 6 months ago

I'm submitting a… 

org.springframework.boot spring-boot-maven-plugin io.github.fvarrui javapackager 1.7.0 package package true ${basedir}\jre ${project.version} ${exec.mainClass} CQDXER ${name} x64 CQDXER, Inc. https://github.com/Mr-YiQiJia/serial_desktop ${basedir}/license ${basedir}/src/main/resources/images/favicon.ico compiler:Default.isl compiler:Languages\ChineseSimplified.isl launch4j true false false false false

 What is the expected behavior? I want my program to run as a single instance, double clicking again to display the previous running instance  What is the current behavior? Each time you double-click to run the program, it will open another instance   Do you have outputs, screenshots, demos or samples which demonstrate the problem or enhancement?  图片

  What is the motivation / use case for changing the behavior? I want to control the excessive opening of the program   Please tell us about your environment: 

fvarrui commented 6 months ago

Launch4j's singleInstance property is not supported by JavaPackager, but maybe you can try this pure Java solution (cross-platform) from SO:

public class Main {

    public static boolean lockInstance(final String lockFile) {
        try {
            final File file = new File(lockFile);
            final RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
            final FileLock fileLock = randomAccessFile.getChannel().tryLock();
            if (fileLock != null) {
                Runtime.getRuntime().addShutdownHook(new Thread() {
                    public void run() {
                        try {
                            fileLock.release();
                            randomAccessFile.close();
                            file.delete();
                        } catch (Exception e) {
                            System.err.println("Unable to remove lock file: " + lockFile);
                            e.printStackTrace();
                        }
                    }
                });
                return true;
            }
        } catch (Exception e) {
            System.err.println("Unable to create and/or lock file: " + lockFile);
            e.printStackTrace();
        }
        return false;
    }

    public static void main(String[] args) throws IOException {
        if (lockInstance(System.getProperty("java.io.tmpdir") + File.separator + Main.class.getName() + ".lock")) {
            HelloWorldFrame.main(args);
        }
    }

}

I've just tried it and it seems to be working fine and is valid for all platforms, not only Windows.

Anyway, we can add support for Launch4j's singleInstance in JP if you really need it.

valimaties commented 6 months ago

This solution works for me:

private static final int PORT = <WhateverPortYouWant>;
private static final String HOST = "localhost";
private ServerSocket serverSocket;
private Thread serverThread;

public static void main(String[] args) {
  try (Socket socket = new Socket(HOST, PORT)) {
      System.out.println("Application already running");
      ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
      oos.writeObject("bringToFront");
      oos.flush();
      ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
      ois.readObject();
      oos.close();
      ois.close();
      socket.close();
      System.exit(0);
  } catch (Exception e) {
      launch(args);
  }
}

@Override
public void start(Stage stage) {
    startServer();
    // your code here for your stage
}

private void startServer() {
  try {
    serverSocket = new ServerSocket(PORT);
    serverThread = new Thread(() -> {
        while (!serverThread.isInterrupted()) {
            try (Socket clientSocket = serverSocket.accept();
                 ObjectInputStream in = new ObjectInputStream(clientSocket.getInputStream());
                 ObjectOutputStream out = new ObjectOutputStream(clientSocket.getOutputStream())) {
                Object message = in.readObject();
                if ("bringToFront".equals(message)) {
                    Platform.runLater(() -> {
                        primaryStage.show();
                        primaryStage.toFront();
                    });
                }
                out.writeObject("done");
            } catch (IOException | ClassNotFoundException e) {
                // Handle exception
                System.exit(0);
                break;
            }
        }
    });
          serverThread.start();

          // Add shutdown hook to close the server socket and interrupt the thread
          Runtime.getRuntime().addShutdownHook(new Thread(() -> {
              try {
                  if (serverThread != null)
                      serverThread.interrupt();
                  if (serverSocket != null)
                      serverSocket.close();
              } catch (IOException e) {
                  // Handle exception
                  System.exit(0);
              }
          }));
      } catch (BindException e) {
          // Close second instance
          System.exit(0);
      } catch (IOException e) {
          System.out.println("Failed to start server socket");
          System.exit(0);
      }
  }
Mr-YiQiJia commented 6 months ago

The current requirement for the program is to have multiple instances. If the program is required to have a single instance in the future, it would be very convenient to have a simple setting. But it's not necessary, it's just an idea. Your ideas are very good, and I can refer to your ideas. Thank you very much for providing me with help @fvarrui @valimaties