Add telnet interface for FreeDOS users
Telnet server with text-based menu for all Git operations. Enabled via webgit.telnet.enabled=true property, defaults to port 2323. Includes unit tests for session and server. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -15,4 +15,5 @@ public class WebgitProperties
|
||||
{
|
||||
private Path worktreePath;
|
||||
private Path gitDirPath;
|
||||
private Integer telnetPort;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package be.seeseepuff.webgit.telnet;
|
||||
|
||||
import be.seeseepuff.webgit.config.WebgitProperties;
|
||||
import be.seeseepuff.webgit.service.GitService;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
|
||||
@Component
|
||||
@ConditionalOnProperty(name = "webgit.telnet.enabled", havingValue = "true")
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class TelnetServer
|
||||
{
|
||||
private final GitService gitService;
|
||||
private final WebgitProperties properties;
|
||||
private ServerSocket serverSocket;
|
||||
private Thread acceptThread;
|
||||
|
||||
@PostConstruct
|
||||
public void start() throws IOException
|
||||
{
|
||||
int port = properties.getTelnetPort() != null ? properties.getTelnetPort() : 2323;
|
||||
serverSocket = new ServerSocket(port);
|
||||
log.info("Telnet server listening on port {}", port);
|
||||
|
||||
acceptThread = new Thread(this::acceptConnections, "telnet-accept");
|
||||
acceptThread.setDaemon(true);
|
||||
acceptThread.start();
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void stop() throws IOException
|
||||
{
|
||||
if (serverSocket != null && !serverSocket.isClosed())
|
||||
{
|
||||
serverSocket.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void acceptConnections()
|
||||
{
|
||||
while (!serverSocket.isClosed())
|
||||
{
|
||||
try
|
||||
{
|
||||
Socket socket = serverSocket.accept();
|
||||
log.info("Telnet connection from {}", socket.getRemoteSocketAddress());
|
||||
|
||||
Thread handler = new Thread(() -> handleConnection(socket), "telnet-client");
|
||||
handler.setDaemon(true);
|
||||
handler.start();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
if (!serverSocket.isClosed())
|
||||
{
|
||||
log.error("Error accepting telnet connection", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleConnection(Socket socket)
|
||||
{
|
||||
try (socket;
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
PrintWriter out = new PrintWriter(socket.getOutputStream(), true))
|
||||
{
|
||||
new TelnetSession(gitService, in, out).run();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
log.error("Error handling telnet connection", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
package be.seeseepuff.webgit.telnet;
|
||||
|
||||
import be.seeseepuff.webgit.service.GitService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class TelnetSession implements Runnable
|
||||
{
|
||||
private final GitService gitService;
|
||||
private final BufferedReader in;
|
||||
private final PrintWriter out;
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
out.println("Welcome to WebGit Telnet Interface");
|
||||
out.println("==================================");
|
||||
mainMenu();
|
||||
}
|
||||
catch (IOException | GitAPIException e)
|
||||
{
|
||||
out.println("Error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
void mainMenu() throws IOException, GitAPIException
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
out.println();
|
||||
out.println("Main Menu:");
|
||||
out.println(" 1. List repositories");
|
||||
out.println(" 2. Clone a repository");
|
||||
out.println(" 3. Open a repository");
|
||||
out.println(" q. Quit");
|
||||
out.print("> ");
|
||||
out.flush();
|
||||
|
||||
String choice = in.readLine();
|
||||
if (choice == null || "q".equalsIgnoreCase(choice))
|
||||
{
|
||||
out.println("Goodbye!");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (choice.trim())
|
||||
{
|
||||
case "1" -> listRepositories();
|
||||
case "2" -> cloneRepository();
|
||||
case "3" -> openRepository();
|
||||
default -> out.println("Invalid choice.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void listRepositories() throws IOException
|
||||
{
|
||||
List<String> repos = gitService.listRepositories();
|
||||
if (repos.isEmpty())
|
||||
{
|
||||
out.println("No repositories cloned yet.");
|
||||
}
|
||||
else
|
||||
{
|
||||
out.println("Repositories:");
|
||||
for (int i = 0; i < repos.size(); i++)
|
||||
{
|
||||
out.println(" " + (i + 1) + ". " + repos.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void cloneRepository() throws IOException, GitAPIException
|
||||
{
|
||||
out.print("URL: ");
|
||||
out.flush();
|
||||
String url = in.readLine();
|
||||
if (url == null || url.isBlank())
|
||||
return;
|
||||
|
||||
out.print("Name: ");
|
||||
out.flush();
|
||||
String name = in.readLine();
|
||||
if (name == null || name.isBlank())
|
||||
return;
|
||||
|
||||
gitService.cloneRepository(url.trim(), name.trim());
|
||||
out.println("Cloned successfully.");
|
||||
}
|
||||
|
||||
private void openRepository() throws IOException, GitAPIException
|
||||
{
|
||||
List<String> repos = gitService.listRepositories();
|
||||
if (repos.isEmpty())
|
||||
{
|
||||
out.println("No repositories cloned yet.");
|
||||
return;
|
||||
}
|
||||
|
||||
out.println("Repositories:");
|
||||
for (int i = 0; i < repos.size(); i++)
|
||||
{
|
||||
out.println(" " + (i + 1) + ". " + repos.get(i));
|
||||
}
|
||||
out.print("Enter number: ");
|
||||
out.flush();
|
||||
|
||||
String input = in.readLine();
|
||||
if (input == null || input.isBlank())
|
||||
return;
|
||||
|
||||
int index;
|
||||
try
|
||||
{
|
||||
index = Integer.parseInt(input.trim()) - 1;
|
||||
}
|
||||
catch (NumberFormatException e)
|
||||
{
|
||||
out.println("Invalid number.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (index < 0 || index >= repos.size())
|
||||
{
|
||||
out.println("Invalid selection.");
|
||||
return;
|
||||
}
|
||||
|
||||
repoMenu(repos.get(index));
|
||||
}
|
||||
|
||||
void repoMenu(String name) throws IOException, GitAPIException
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
out.println();
|
||||
out.println("Repository: " + name);
|
||||
out.println(" Branch: " + gitService.getCurrentBranch(name));
|
||||
out.println();
|
||||
out.println(" 1. List branches");
|
||||
out.println(" 2. Checkout branch");
|
||||
out.println(" 3. Create new branch");
|
||||
out.println(" 4. Show modified files");
|
||||
out.println(" 5. Show staged files");
|
||||
out.println(" 6. Stage files");
|
||||
out.println(" 7. Commit");
|
||||
out.println(" 8. Push");
|
||||
out.println(" 9. Pull");
|
||||
out.println(" b. Back");
|
||||
out.print("> ");
|
||||
out.flush();
|
||||
|
||||
String choice = in.readLine();
|
||||
if (choice == null || "b".equalsIgnoreCase(choice))
|
||||
return;
|
||||
|
||||
switch (choice.trim())
|
||||
{
|
||||
case "1" -> listBranches(name);
|
||||
case "2" -> checkoutBranch(name);
|
||||
case "3" -> createBranch(name);
|
||||
case "4" -> showModifiedFiles(name);
|
||||
case "5" -> showStagedFiles(name);
|
||||
case "6" -> stageFiles(name);
|
||||
case "7" -> commitChanges(name);
|
||||
case "8" -> push(name);
|
||||
case "9" -> pull(name);
|
||||
default -> out.println("Invalid choice.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void listBranches(String name) throws IOException, GitAPIException
|
||||
{
|
||||
List<String> branches = gitService.listBranches(name);
|
||||
String current = gitService.getCurrentBranch(name);
|
||||
out.println("Branches:");
|
||||
for (String branch : branches)
|
||||
{
|
||||
out.println(branch.equals(current) ? " * " + branch : " " + branch);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkoutBranch(String name) throws IOException, GitAPIException
|
||||
{
|
||||
out.print("Branch name: ");
|
||||
out.flush();
|
||||
String branch = in.readLine();
|
||||
if (branch == null || branch.isBlank())
|
||||
return;
|
||||
gitService.checkoutBranch(name, branch.trim());
|
||||
out.println("Switched to " + branch.trim());
|
||||
}
|
||||
|
||||
private void createBranch(String name) throws IOException, GitAPIException
|
||||
{
|
||||
out.print("New branch name: ");
|
||||
out.flush();
|
||||
String branch = in.readLine();
|
||||
if (branch == null || branch.isBlank())
|
||||
return;
|
||||
gitService.createAndCheckoutBranch(name, branch.trim());
|
||||
out.println("Created and switched to " + branch.trim());
|
||||
}
|
||||
|
||||
private void showModifiedFiles(String name) throws IOException, GitAPIException
|
||||
{
|
||||
List<String> files = gitService.getModifiedFiles(name);
|
||||
if (files.isEmpty())
|
||||
{
|
||||
out.println("No modified files.");
|
||||
}
|
||||
else
|
||||
{
|
||||
out.println("Modified files:");
|
||||
for (int i = 0; i < files.size(); i++)
|
||||
{
|
||||
out.println(" " + (i + 1) + ". " + files.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showStagedFiles(String name) throws IOException, GitAPIException
|
||||
{
|
||||
List<String> files = gitService.getStagedFiles(name);
|
||||
if (files.isEmpty())
|
||||
{
|
||||
out.println("No staged files.");
|
||||
}
|
||||
else
|
||||
{
|
||||
out.println("Staged files:");
|
||||
for (String file : files)
|
||||
{
|
||||
out.println(" " + file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void stageFiles(String name) throws IOException, GitAPIException
|
||||
{
|
||||
List<String> files = gitService.getModifiedFiles(name);
|
||||
if (files.isEmpty())
|
||||
{
|
||||
out.println("No modified files to stage.");
|
||||
return;
|
||||
}
|
||||
|
||||
out.println("Modified files:");
|
||||
for (int i = 0; i < files.size(); i++)
|
||||
{
|
||||
out.println(" " + (i + 1) + ". " + files.get(i));
|
||||
}
|
||||
out.print("Enter numbers to stage (comma-separated, or 'a' for all): ");
|
||||
out.flush();
|
||||
|
||||
String input = in.readLine();
|
||||
if (input == null || input.isBlank())
|
||||
return;
|
||||
|
||||
List<String> toStage;
|
||||
if ("a".equalsIgnoreCase(input.trim()))
|
||||
{
|
||||
toStage = files;
|
||||
}
|
||||
else
|
||||
{
|
||||
toStage = new java.util.ArrayList<>();
|
||||
for (String part : input.split(","))
|
||||
{
|
||||
try
|
||||
{
|
||||
int idx = Integer.parseInt(part.trim()) - 1;
|
||||
if (idx >= 0 && idx < files.size())
|
||||
toStage.add(files.get(idx));
|
||||
}
|
||||
catch (NumberFormatException ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!toStage.isEmpty())
|
||||
{
|
||||
gitService.stageFiles(name, toStage);
|
||||
out.println("Staged " + toStage.size() + " file(s).");
|
||||
}
|
||||
}
|
||||
|
||||
private void commitChanges(String name) throws IOException, GitAPIException
|
||||
{
|
||||
out.print("Commit message: ");
|
||||
out.flush();
|
||||
String message = in.readLine();
|
||||
if (message == null || message.isBlank())
|
||||
return;
|
||||
gitService.commit(name, message.trim());
|
||||
out.println("Committed.");
|
||||
}
|
||||
|
||||
private void push(String name) throws IOException, GitAPIException
|
||||
{
|
||||
gitService.push(name);
|
||||
out.println("Pushed.");
|
||||
}
|
||||
|
||||
private void pull(String name) throws IOException, GitAPIException
|
||||
{
|
||||
gitService.pull(name);
|
||||
out.println("Pulled.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user