SSHRemotePlugin/src/main/java/com/example/plugin/SshSyncStartupActivity.java
2026-05-14 20:08:57 +03:00

174 lines
8.0 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.example.plugin;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.project.ProjectManagerListener;
import com.intellij.openapi.startup.StartupActivity;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vfs.*;
import com.jcraft.jsch.*;
import org.jetbrains.annotations.NotNull;
import java.nio.file.Paths;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@SuppressWarnings("deprecation")
public class SshSyncStartupActivity implements StartupActivity {
private static final ExecutorService uploadExecutor = Executors.newSingleThreadExecutor();
@Override
public void runActivity(@NotNull Project project) {
// При закрытии проекта разрываем SFTP-подключение
project.getMessageBus().connect().subscribe(ProjectManager.TOPIC, new ProjectManagerListener() {
@Override
public void projectClosed(@NotNull Project closedProject) {
if (closedProject.equals(project)) {
SftpSessionManager.getInstance(project).disconnect();
}
}
});
// Слушатель изменений файлов для синхронизации
VirtualFile baseDir = project.getBaseDir();
if (baseDir != null) {
VirtualFileManager.getInstance().addVirtualFileListener(new VirtualFileListener() {
@Override
public void contentsChanged(@NotNull VirtualFileEvent event) {
syncFile(event.getFile(), project);
}
@Override
public void fileCreated(@NotNull VirtualFileEvent event) {
syncFile(event.getFile(), project);
}
private void syncFile(VirtualFile file, Project project) {
if (!file.isDirectory()) {
uploadFile(file, project);
}
}
}, project);
}
// === Автосинхронизация при открытии проекта ===
String projectPath = project.getBasePath();
if (projectPath != null) {
String serverId = ProjectServerMapping.getInstance().getServerIdForProject(projectPath);
if (serverId != null) {
Optional<SshServer> maybeServer = SshServerManager.getInstance().findServer(serverId);
if (maybeServer.isPresent()) {
SshServer server = maybeServer.get();
// Модальный диалог блокирует IDE до завершения синхронизации
ApplicationManager.getApplication().invokeLater(() -> {
ProgressManager.getInstance().run(new Task.Modal(project, "Synchronizing with " + server.name, true) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
try {
String localRoot = Paths.get(System.getProperty("user.home"), "ssh-remote-projects",
sanitize(server.host), sanitize(server.remoteProjectPath))
.toString().replace('\\', '/');
ConnectToRemoteAction.syncProject(server, localRoot, indicator);
// Обновляем виртуальную файловую систему, чтобы IDE увидела изменения
ApplicationManager.getApplication().invokeLater(() -> {
VirtualFile rootDir = LocalFileSystem.getInstance().findFileByPath(localRoot);
if (rootDir != null) {
VfsUtil.markDirtyAndRefresh(true, true, true, rootDir);
}
});
} catch (Exception e) {
ApplicationManager.getApplication().invokeLater(() ->
Messages.showErrorDialog("Sync failed: " + e.getMessage(), "Sync Error"));
}
}
});
});
}
}
}
}
private void uploadFile(VirtualFile localFile, Project project) {
String projectPath = project.getBasePath();
if (projectPath == null) return;
// Определяем привязанный сервер
String serverId = ProjectServerMapping.getInstance().getServerIdForProject(projectPath);
if (serverId == null) return;
Optional<SshServer> maybeServer = SshServerManager.getInstance().findServer(serverId);
if (maybeServer.isEmpty()) return;
SshServer server = maybeServer.get();
// Вычисляем локальный корень проекта (как он был сохранен при создании)
String localRoot = Paths.get(System.getProperty("user.home"), "ssh-remote-projects",
sanitize(server.host), sanitize(server.remoteProjectPath))
.toString().replace('\\', '/');
String localFilePath = localFile.getPath(); // всегда с прямыми слешами
// Игнорируем служебные файлы IDE
if (localFilePath.contains("/.idea/") || localFile.getName().equals("remote.iml")) {
return;
}
// Проверяем, что файл принадлежит проекту
if (!localFilePath.startsWith(localRoot)) {
return;
}
// Относительный путь и удалённый путь
String relativePath = localFilePath.substring(localRoot.length());
if (relativePath.startsWith("/")) {
relativePath = relativePath.substring(1);
}
String remoteFilePath = server.remoteProjectPath + "/" + relativePath;
// Отправляем файл в отдельном потоке через общее SFTP-соединение
uploadExecutor.submit(() -> {
try {
ChannelSftp sftp = SftpSessionManager.getInstance(project).getChannel(server);
String remoteParent = remoteFilePath.substring(0, remoteFilePath.lastIndexOf('/'));
createRemoteDirs(sftp, remoteParent);
sftp.put(localFilePath, remoteFilePath);
} catch (Exception e) {
ApplicationManager.getApplication().invokeLater(() ->
com.intellij.openapi.ui.Messages.showErrorDialog(
"Sync error " + localFile.getName() + ": " + e.getMessage(),
"SFTP Error"
)
);
}
});
}
private void createRemoteDirs(ChannelSftp sftp, String dir) throws SftpException {
if (dir.isEmpty() || dir.equals("/")) return;
String[] parts = dir.split("/");
StringBuilder path = new StringBuilder();
for (String part : parts) {
if (part.isEmpty()) continue;
path.append("/").append(part);
try {
sftp.stat(path.toString());
} catch (SftpException e) {
if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
sftp.mkdir(path.toString());
} else {
throw e;
}
}
}
}
private String sanitize(String input) {
return input.replaceAll("[^a-zA-Z0-9.-]", "_");
}
}