0
0
Files
vs-p/out/panels/services/ProjectService.js

930 lines
38 KiB
JavaScript
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.

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProjectService = void 0;
// src/panels/services/ProjectService.ts
const vscode = __importStar(require("vscode"));
const path = __importStar(require("path"));
const fs = __importStar(require("fs"));
const StorageService_1 = require("./StorageService");
/**
* 项目数据管理服务
*/
class ProjectService {
constructor() {
this.projects = [];
this.aircrafts = [];
this.containers = [];
this.configs = [];
this.moduleFolders = [];
this.projectPaths = new Map();
}
/**
* 生成唯一ID
*/
generateUniqueId(prefix, _existingItems = []) {
const randomPart = `${Date.now().toString(36)}${Math.random().toString(36).substring(2, 8)}`;
return `${prefix}${randomPart}`;
}
/**
* 文件名/文件夹名净化:保留大小写,允许中文、字母、数字、-、_、.
*/
sanitizeFileName(name) {
let fileName = name.trim();
// 1. 替换所有空格为下划线
fileName = fileName.replace(/\s+/g, '_');
// 2. 移除特殊字符
fileName = fileName.replace(/[^\u4e00-\u9fa5a-zA-Z0-9_\-\.]/g, '');
// 3. 确保不为空
if (fileName.length === 0) {
return 'config_file';
}
return fileName;
}
// =============== 查重工具方法 (新增) ===============
/** 检查项目名是否重复 */
isProjectNameExists(name) {
return this.projects.some(p => p.name === name);
}
/** 检查指定项目下的飞行器名是否重复 */
isAircraftNameExists(projectId, name) {
return this.aircrafts.some(a => a.projectId === projectId && a.name === name);
}
/** 检查指定飞行器下的容器名是否重复 */
isContainerNameExists(aircraftId, name) {
return this.containers.some(c => c.aircraftId === aircraftId && c.name === name);
}
/** 检查指定容器下的配置名或文件名是否重复 */
isConfigOrFolderConflict(containerId, name, fileName, excludeId) {
const configs = this.getConfigsByContainer(containerId);
const folders = this.getModuleFoldersByContainer(containerId);
// 检查显示名称冲突 (排除自身)
const nameConflict = configs.some(c => c.id !== excludeId && c.name === name) ||
folders.some(f => f.id !== excludeId && f.name === name);
// 检查磁盘文件名/文件夹名冲突 (排除自身)
const fileConflict = configs.some(c => c.id !== excludeId && c.fileName === fileName) ||
folders.some(f => f.id !== excludeId && f.localPath.split('/').pop() === fileName);
return { nameConflict, fileConflict };
}
// =============== 项目相关方法 ===============
getProjects() {
return this.projects;
}
getProjectPaths() {
return this.projectPaths;
}
getProjectPath(projectId) {
return this.projectPaths.get(projectId);
}
setProjectPath(projectId, pathStr) {
this.projectPaths.set(projectId, pathStr);
}
async createProject(name) {
const newId = this.generateUniqueId('p', this.projects);
const newProject = {
id: newId,
name: name
};
this.projects.push(newProject);
return newId;
}
updateProjectName(projectId, newName) {
const project = this.projects.find(p => p.id === projectId);
if (project) {
project.name = newName;
return true;
}
return false;
}
deleteProject(projectId) {
const project = this.projects.find(p => p.id === projectId);
if (!project)
return false;
const relatedAircrafts = this.aircrafts.filter(a => a.projectId === projectId);
const aircraftIds = relatedAircrafts.map(a => a.id);
const relatedContainers = this.containers.filter(c => aircraftIds.includes(c.aircraftId));
const containerIds = relatedContainers.map(c => c.id);
this.projects = this.projects.filter(p => p.id !== projectId);
this.aircrafts = this.aircrafts.filter(a => a.projectId !== projectId);
this.containers = this.containers.filter(c => !aircraftIds.includes(c.aircraftId));
this.configs = this.configs.filter(cfg => !containerIds.includes(cfg.containerId));
this.moduleFolders = this.moduleFolders.filter(folder => !containerIds.includes(folder.containerId));
this.projectPaths.delete(projectId);
return true;
}
// =============== 飞行器相关方法 ===============
getAircraftsByProject(projectId) {
return this.aircrafts.filter(a => a.projectId === projectId);
}
getAircraft(aircraftId) {
return this.aircrafts.find(a => a.id === aircraftId);
}
async createAircraft(name, projectId) {
const newId = this.generateUniqueId('a', this.aircrafts);
const newAircraft = {
id: newId,
name: name,
projectId: projectId
};
this.aircrafts.push(newAircraft);
await this.createAircraftDirectory(newAircraft);
return newId;
}
updateAircraftName(aircraftId, newName) {
const aircraft = this.aircrafts.find(a => a.id === aircraftId);
if (aircraft) {
aircraft.name = newName;
return true;
}
return false;
}
deleteAircraft(aircraftId) {
const aircraft = this.aircrafts.find(a => a.id === aircraftId);
if (!aircraft)
return false;
const relatedContainers = this.containers.filter(c => c.aircraftId === aircraftId);
const containerIds = relatedContainers.map(c => c.id);
this.aircrafts = this.aircrafts.filter(a => a.id !== aircraftId);
this.containers = this.containers.filter(c => c.aircraftId !== aircraftId);
this.configs = this.configs.filter(cfg => !containerIds.includes(cfg.containerId));
this.moduleFolders = this.moduleFolders.filter(folder => !containerIds.includes(folder.containerId));
return true;
}
async importAircraftFromExistingFolder(projectId, aircraftName) {
const existed = this.getAircraftsByProject(projectId).find(a => a.name === aircraftName);
if (existed) {
return existed.id;
}
const newId = this.generateUniqueId('a', this.aircrafts);
const aircraft = {
id: newId,
name: aircraftName,
projectId
};
this.aircrafts.push(aircraft);
const projectPath = this.projectPaths.get(projectId);
if (!projectPath) {
return newId;
}
const aircraftDir = path.join(projectPath, aircraftName);
if (!fs.existsSync(aircraftDir)) {
return newId;
}
const entries = await fs.promises.readdir(aircraftDir, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isDirectory())
continue;
if (entry.name === '.git')
continue;
await this.importContainerFromExistingFolder(newId, entry.name);
}
return newId;
}
// =============== 容器相关方法 ===============
getContainersByAircraft(aircraftId) {
return this.containers.filter(c => c.aircraftId === aircraftId);
}
getContainer(containerId) {
return this.containers.find(c => c.id === containerId);
}
async createContainer(name, aircraftId) {
const newId = this.generateUniqueId('c', this.containers);
const newContainer = {
id: newId,
name: name,
aircraftId: aircraftId
};
this.containers.push(newContainer);
await this.createContainerDirectory(newContainer);
await this.createDefaultConfigs(newContainer);
return newId;
}
updateContainerName(containerId, newName) {
const container = this.containers.find(c => c.id === containerId);
if (container) {
container.name = newName;
return true;
}
return false;
}
deleteContainer(containerId) {
const container = this.containers.find(c => c.id === containerId);
if (!container)
return false;
this.containers = this.containers.filter(c => c.id !== containerId);
this.configs = this.configs.filter(cfg => cfg.containerId !== containerId);
this.moduleFolders = this.moduleFolders.filter(folder => folder.containerId !== containerId);
return true;
}
async importContainerFromExistingFolder(aircraftId, containerName) {
if (containerName === '.git') {
throw new Error('不能将 .git 导入为容器');
}
const existed = this.getContainersByAircraft(aircraftId).find(c => c.name === containerName);
if (existed) {
return existed.id;
}
const newId = this.generateUniqueId('c', this.containers);
const container = {
id: newId,
name: containerName,
aircraftId
};
this.containers.push(container);
await this.scanContainerModuleFolders(container);
return newId;
}
async scanContainerModuleFolders(container) {
try {
const aircraft = this.aircrafts.find(a => a.id === container.aircraftId);
if (!aircraft)
return;
const projectPath = this.projectPaths.get(aircraft.projectId);
if (!projectPath)
return;
const containerDir = path.join(projectPath, aircraft.name, container.name);
if (!fs.existsSync(containerDir)) {
return;
}
const entries = await fs.promises.readdir(containerDir, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isDirectory())
continue;
if (entry.name === '.git')
continue;
const folderId = this.generateUniqueId('local-', this.moduleFolders);
const relativePath = `/${aircraft.projectId}/${aircraft.name}/${container.name}/${entry.name}`;
const moduleFolder = {
id: folderId,
name: entry.name,
type: 'local',
localPath: relativePath,
containerId: container.id
};
this.moduleFolders.push(moduleFolder);
}
}
catch (error) {
console.error('扫描容器目录生成模块文件夹失败:', error);
}
}
// =============== 配置相关方法 ===============
getConfigsByContainer(containerId) {
return this.configs.filter(cfg => cfg.containerId === containerId);
}
getConfig(configId) {
return this.configs.find(c => c.id === configId);
}
async createConfig(name, containerId) {
const newId = this.generateUniqueId('cfg', this.configs);
const newFileName = this.sanitizeFileName(name);
const newConfig = {
id: newId,
name: name,
fileName: newFileName,
containerId: containerId
};
this.configs.push(newConfig);
await this.ensureContainerDirectoryExists(containerId);
const filePath = this.getConfigFilePath(newId);
if (filePath) {
try {
const fileUri = vscode.Uri.file(filePath);
await vscode.workspace.fs.writeFile(fileUri, new Uint8Array());
console.log(`✅ 磁盘文件已真实创建: ${filePath}`);
}
catch (error) {
console.error(`❌ 磁盘文件创建失败: ${error}`);
}
}
return newId;
}
updateConfigName(configId, newName, newFileName) {
const config = this.configs.find(c => c.id === configId);
if (config) {
config.name = newName;
config.fileName = newFileName;
return true;
}
return false;
}
deleteConfig(configId) {
const config = this.configs.find(c => c.id === configId);
if (!config)
return false;
this.configs = this.configs.filter(c => c.id !== configId);
return true;
}
async renameConfigFileOnDisk(configId, newFileName) {
const config = this.configs.find(c => c.id === configId);
if (!config)
return false;
const oldFilePath = this.getConfigFilePath(configId);
const oldFileName = config.fileName;
config.fileName = newFileName;
const newFilePath = this.getConfigFilePath(configId);
config.fileName = oldFileName;
if (!oldFilePath || !newFilePath) {
return false;
}
if (!fs.existsSync(oldFilePath)) {
console.warn(`旧配置文件不存在,跳过磁盘重命名: ${oldFilePath}`);
return false;
}
try {
await fs.promises.rename(oldFilePath, newFilePath);
console.log(`✅ 已重命名配置文件: ${oldFilePath} -> ${newFilePath}`);
return true;
}
catch (error) {
console.error(`重命名配置文件失败: ${error}`);
throw error;
}
}
// =============== 模块文件夹相关方法 ===============
getModuleFoldersByContainer(containerId) {
return this.moduleFolders.filter(folder => folder.containerId === containerId);
}
getModuleFolder(folderId) {
return this.moduleFolders.find(f => f.id === folderId);
}
addModuleFolder(folder) {
this.moduleFolders.push(folder);
}
updateModuleFolder(folderId, updates) {
const folderIndex = this.moduleFolders.findIndex(f => f.id === folderId);
if (folderIndex === -1)
return false;
this.moduleFolders[folderIndex] = {
...this.moduleFolders[folderIndex],
...updates
};
return true;
}
deleteModuleFolder(folderId) {
const folder = this.moduleFolders.find(f => f.id === folderId);
if (!folder)
return false;
this.moduleFolders = this.moduleFolders.filter(f => f.id !== folderId);
return true;
}
// =============== 文件系统操作 ===============
getProjectOldAndNewPaths(projectId, newName) {
const project = this.projects.find(p => p.id === projectId);
const oldPath = this.projectPaths.get(projectId);
if (!project || !oldPath)
return null;
const parentDir = path.dirname(oldPath);
const newPath = path.join(parentDir, newName);
return { oldPath, newPath };
}
getAircraftDirectoryPath(aircraftId) {
const aircraft = this.getAircraft(aircraftId);
if (!aircraft)
return null;
const projectPath = this.projectPaths.get(aircraft.projectId);
if (!projectPath)
return null;
return path.join(projectPath, aircraft.name);
}
getAircraftOldAndNewPaths(aircraftId, newName) {
const aircraft = this.aircrafts.find(a => a.id === aircraftId);
if (!aircraft)
return null;
const projectPath = this.projectPaths.get(aircraft.projectId);
if (!projectPath)
return null;
const oldPath = path.join(projectPath, aircraft.name);
const newPath = path.join(projectPath, newName);
return { oldPath, newPath };
}
getContainerDirectoryPath(containerId) {
const container = this.getContainer(containerId);
if (!container)
return null;
const aircraft = this.getAircraft(container.aircraftId);
if (!aircraft)
return null;
const projectPath = this.projectPaths.get(aircraft.projectId);
if (!projectPath)
return null;
return path.join(projectPath, aircraft.name, container.name);
}
getContainerOldAndNewPaths(containerId, newName) {
const container = this.containers.find(c => c.id === containerId);
if (!container)
return null;
const aircraft = this.aircrafts.find(a => a.id === container.aircraftId);
if (!aircraft)
return null;
const projectPath = this.projectPaths.get(aircraft.projectId);
if (!projectPath)
return null;
const aircraftDir = path.join(projectPath, aircraft.name);
const oldPath = path.join(aircraftDir, container.name);
const newPath = path.join(aircraftDir, newName);
return { oldPath, newPath };
}
async deleteDirectoryFromDisk(directoryPath) {
if (!directoryPath)
return false;
try {
if (fs.existsSync(directoryPath)) {
await fs.promises.rm(directoryPath, { recursive: true, force: true });
console.log(`✅ 已删除目录: ${directoryPath}`);
return true;
}
return false;
}
catch (error) {
console.error(`删除目录失败: ${error}`);
throw error;
}
}
async renameDirectoryOnDisk(oldPath, newPath) {
if (!oldPath || !newPath)
return false;
try {
if (fs.existsSync(oldPath)) {
await fs.promises.rename(oldPath, newPath);
console.log(`✅ 已重命名目录: ${oldPath} -> ${newPath}`);
return true;
}
console.warn(`目录不存在,跳过重命名: ${oldPath}`);
return false;
}
catch (error) {
console.error(`重命名目录失败: ${error}`);
throw error;
}
}
async createAircraftDirectory(aircraft) {
try {
const projectPath = this.projectPaths.get(aircraft.projectId);
if (!projectPath) {
console.warn('未找到项目路径,跳过创建飞行器目录');
return;
}
const aircraftDir = vscode.Uri.joinPath(vscode.Uri.file(projectPath), aircraft.name);
await vscode.workspace.fs.createDirectory(aircraftDir);
console.log(`✅ 创建飞行器目录: ${aircraftDir.fsPath}`);
}
catch (error) {
console.error(`创建飞行器目录失败: ${error}`);
}
}
async createContainerDirectory(container) {
try {
const aircraft = this.aircrafts.find(a => a.id === container.aircraftId);
if (!aircraft) {
console.warn('未找到对应的飞行器,跳过创建容器目录');
return;
}
const projectPath = this.projectPaths.get(aircraft.projectId);
if (!projectPath) {
console.warn('未找到项目路径,跳过创建容器目录');
return;
}
const aircraftDir = vscode.Uri.joinPath(vscode.Uri.file(projectPath), aircraft.name);
const containerDir = vscode.Uri.joinPath(aircraftDir, container.name);
await vscode.workspace.fs.createDirectory(aircraftDir);
await vscode.workspace.fs.createDirectory(containerDir);
console.log(`✅ 创建容器目录: ${containerDir.fsPath}`);
}
catch (error) {
console.error(`创建容器目录失败: ${error}`);
}
}
async ensureContainerDirectoryExists(containerId) {
try {
const container = this.containers.find(c => c.id === containerId);
if (!container)
return;
const aircraft = this.aircrafts.find(a => a.id === container.aircraftId);
if (!aircraft)
return;
const projectPath = this.projectPaths.get(aircraft.projectId);
if (!projectPath)
return;
const aircraftDir = vscode.Uri.joinPath(vscode.Uri.file(projectPath), aircraft.name);
const containerDir = vscode.Uri.joinPath(aircraftDir, container.name);
await vscode.workspace.fs.createDirectory(aircraftDir);
await vscode.workspace.fs.createDirectory(containerDir);
}
catch (error) {
console.error(`确保容器目录存在失败: ${error}`);
}
}
async createDefaultConfigs(container) {
this.configs.push({
id: this.generateUniqueId('cfg', this.configs),
name: '配置1',
fileName: 'dockerfile',
containerId: container.id
});
this.configs.push({
id: this.generateUniqueId('cfg', this.configs),
name: '配置2',
fileName: 'docker-compose.yml',
containerId: container.id
});
}
// =============== 数据持久化 ===============
async saveCurrentProjectData(projectId) {
try {
const projectPath = this.projectPaths.get(projectId);
if (!projectPath) {
console.warn('未找到项目存储路径,数据将不会保存');
return false;
}
const data = this.getProjectData(projectId);
return await StorageService_1.StorageService.saveProjectData(projectPath, data);
}
catch (error) {
console.error('保存项目数据失败:', error);
return false;
}
}
async loadProjectData(projectPath) {
try {
const data = await StorageService_1.StorageService.loadProjectData(projectPath);
if (!data) {
return null;
}
const projectId = data.projects[0]?.id;
if (!projectId) {
return null;
}
const existingAircrafts = this.aircrafts.filter(a => a.projectId === projectId);
const aircraftIds = existingAircrafts.map(a => a.id);
const existingContainers = this.containers.filter(c => aircraftIds.includes(c.aircraftId));
const containerIds = existingContainers.map(c => c.id);
this.projects = this.projects.filter(p => p.id !== projectId);
this.aircrafts = this.aircrafts.filter(a => a.projectId !== projectId);
this.containers = this.containers.filter(c => !aircraftIds.includes(c.aircraftId));
this.configs = this.configs.filter(cfg => !containerIds.includes(cfg.containerId));
this.moduleFolders = this.moduleFolders.filter(folder => !containerIds.includes(folder.containerId));
const cleanedContainers = data.containers.filter(c => c.name !== '.git');
this.projects.push(...data.projects);
this.aircrafts.push(...data.aircrafts);
this.containers.push(...cleanedContainers);
this.configs.push(...data.configs);
this.moduleFolders.push(...data.moduleFolders);
this.projectPaths.set(projectId, projectPath);
return projectId;
}
catch (error) {
console.error('加载项目数据失败:', error);
return null;
}
}
getProjectData(projectId) {
return {
projects: [this.projects.find(p => p.id === projectId)],
aircrafts: this.aircrafts.filter(a => a.projectId === projectId),
containers: this.containers.filter(c => {
const aircraft = this.aircrafts.find(a => a.id === c.aircraftId);
return aircraft && aircraft.projectId === projectId;
}),
configs: this.configs.filter(cfg => {
const container = this.containers.find(c => c.id === cfg.containerId);
if (!container)
return false;
const aircraft = this.aircrafts.find(a => a.id === container.aircraftId);
return aircraft && aircraft.projectId === projectId;
}),
moduleFolders: this.moduleFolders.filter(folder => {
const container = this.containers.find(c => c.id === folder.containerId);
if (!container)
return false;
const aircraft = this.aircrafts.find(a => a.id === container.aircraftId);
return aircraft && aircraft.projectId === projectId;
})
};
}
// =============== 工具方法 ===============
getModuleFolderFullPath(folder) {
const container = this.containers.find(c => c.id === folder.containerId);
if (!container)
return null;
const aircraft = this.aircrafts.find(a => a.id === container.aircraftId);
if (!aircraft)
return null;
const projectPath = this.projectPaths.get(aircraft.projectId);
if (!projectPath)
return null;
const pathParts = folder.localPath.split('/').filter(part => part);
if (pathParts.length < 4)
return null;
const folderName = pathParts[pathParts.length - 1];
return path.join(projectPath, aircraft.name, container.name, folderName);
}
getConfigFilePath(configId) {
const config = this.configs.find(c => c.id === configId);
if (!config)
return null;
const container = this.containers.find(c => c.id === config.containerId);
if (!container)
return null;
const aircraft = this.aircrafts.find(a => a.id === container.aircraftId);
if (!aircraft)
return null;
const projectPath = this.projectPaths.get(aircraft.projectId);
if (!projectPath)
return null;
return path.join(projectPath, aircraft.name, container.name, config.fileName);
}
async deleteConfigFileFromDisk(configId) {
const filePath = this.getConfigFilePath(configId);
if (!filePath)
return false;
try {
if (fs.existsSync(filePath)) {
await fs.promises.unlink(filePath);
console.log(`✅ 已删除配置文件: ${filePath}`);
return true;
}
return false;
}
catch (error) {
console.error(`删除配置文件失败: ${error}`);
return false;
}
}
registerConfigFromDisk(name, fileName, containerId) {
// 查重:只要同一个容器下有相同的 fileName就视为已存在
const existing = this.configs.find(c => c.containerId === containerId && c.fileName === fileName);
if (existing)
return false;
const newId = this.generateUniqueId('cfg', this.configs);
this.configs.push({
id: newId,
name: name,
fileName: fileName,
containerId: containerId
});
return true;
}
/**
* [新方法] 仅在内存中注册模块文件夹(用于监测到磁盘文件夹创建时)
*/
registerModuleFolderFromDisk(folderName, containerId, aircraftId, projectId, aircraftName, containerName, fullPath) {
const relativePath = `/${projectId}/${aircraftName}/${containerName}/${folderName}`;
// 查重:只要同一个容器下,有任何一个模块指向了相同的文件夹名,就视为已存在
// 这样即使旧数据的 localPath 路径字符串是过时的,也不会重复添加
const existing = this.moduleFolders.find(f => {
if (f.containerId !== containerId)
return false;
// 从 localPath 中提取最后一部分(文件夹名)进行比对
const existingFolderName = f.localPath.split('/').pop();
return existingFolderName === folderName;
});
if (existing)
return false;
// 智能检测:如果文件夹下有 .git则标记为 git 类型
let type = 'local';
if (fullPath) {
const gitDir = path.join(fullPath, '.git');
if (fs.existsSync(gitDir)) {
type = 'git';
}
}
const folderId = this.generateUniqueId(type === 'git' ? 'git-' : 'local-', this.moduleFolders);
this.moduleFolders.push({
id: folderId,
name: folderName,
type: type,
localPath: relativePath,
containerId: containerId,
uploaded: type === 'git'
});
return true;
}
/**
* [新方法] 处理磁盘删除事件:根据路径移除对应的 Config 或 ModuleFolder
*/
removeEntityByPath(filePath, projectId) {
// 这里的 filePath 是绝对路径,需要反解出是哪个 Config 或 Folder
// 这是一个比较繁琐的过程,简化逻辑如下:
let changed = false;
const projectPath = this.projectPaths.get(projectId);
if (!projectPath || !filePath.startsWith(projectPath))
return false;
// 尝试匹配 Config
const configToDelete = this.configs.find(c => {
const p = this.getConfigFilePath(c.id);
return p && path.normalize(p) === path.normalize(filePath);
});
if (configToDelete) {
this.deleteConfig(configToDelete.id);
return true;
}
// 尝试匹配 ModuleFolder
const folderToDelete = this.moduleFolders.find(f => {
const p = this.getModuleFolderFullPath(f);
return p && path.normalize(p) === path.normalize(filePath);
});
if (folderToDelete) {
this.deleteModuleFolder(folderToDelete.id);
return true;
}
return false;
}
/**
* 刷新项目内容:同步飞行器列表
*/
async refreshProjectContent(projectId) {
const projectPath = this.projectPaths.get(projectId);
if (!projectPath || !fs.existsSync(projectPath))
return false;
let changed = false;
// 1. 扫描磁盘,添加缺失的飞行器
try {
const entries = await fs.promises.readdir(projectPath, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isDirectory())
continue;
if (entry.name.startsWith('.') || entry.name === 'dcsp-data.json')
continue;
// 检查内存中是否存在
const exists = this.getAircraftsByProject(projectId).find(a => a.name === entry.name);
if (!exists) {
// 不存在则导入
await this.importAircraftFromExistingFolder(projectId, entry.name);
changed = true;
}
}
}
catch (e) {
console.error('扫描项目目录失败:', e);
}
// 2. 检查内存,移除磁盘不存在的飞行器 (清理无效数据)
const currentAircrafts = this.getAircraftsByProject(projectId);
for (const aircraft of currentAircrafts) {
const aircraftPath = path.join(projectPath, aircraft.name);
if (!fs.existsSync(aircraftPath)) {
this.deleteAircraft(aircraft.id);
changed = true;
}
}
return changed;
}
/**
* 刷新飞行器内容:同步容器列表
*/
async refreshAircraftContent(aircraftId) {
const dirPath = this.getAircraftDirectoryPath(aircraftId);
if (!dirPath || !fs.existsSync(dirPath))
return false;
let changed = false;
// 1. 扫描磁盘,添加缺失的容器
try {
const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isDirectory())
continue;
if (entry.name.startsWith('.'))
continue; // 忽略 .git 等
const exists = this.getContainersByAircraft(aircraftId).find(c => c.name === entry.name);
if (!exists) {
await this.importContainerFromExistingFolder(aircraftId, entry.name);
changed = true;
}
}
}
catch (e) {
console.error('扫描飞行器目录失败:', e);
}
// 2. 检查内存,移除磁盘不存在的容器
const currentContainers = this.getContainersByAircraft(aircraftId);
for (const container of currentContainers) {
const containerPath = path.join(dirPath, container.name);
if (!fs.existsSync(containerPath)) {
this.deleteContainer(container.id);
changed = true;
}
}
return changed;
}
/**
* 刷新容器内容:同步配置和模块列表
*/
async refreshContainerContent(containerId) {
const dirPath = this.getContainerDirectoryPath(containerId);
if (!dirPath || !fs.existsSync(dirPath))
return false;
const container = this.getContainer(containerId);
if (!container)
return false;
const aircraft = this.getAircraft(container.aircraftId);
if (!aircraft)
return false;
let changed = false;
// 1. 扫描磁盘,添加缺失的配置和模块
try {
const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });
for (const entry of entries) {
if (entry.name.startsWith('.') || entry.name === 'dcsp-data.json')
continue;
if (entry.isFile()) {
// 尝试注册配置 (registerConfigFromDisk 内部会自动去重)
const isNew = this.registerConfigFromDisk(entry.name, entry.name, containerId);
if (isNew)
changed = true;
}
else if (entry.isDirectory()) {
// 尝试注册模块
const isNew = this.registerModuleFolderFromDisk(entry.name, containerId, aircraft.id, aircraft.projectId, aircraft.name, container.name);
if (isNew)
changed = true;
}
}
}
catch (e) {
console.error('扫描容器目录失败:', e);
}
// 2. 检查内存,移除磁盘不存在的 配置
const currentConfigs = this.getConfigsByContainer(containerId);
for (const config of currentConfigs) {
const configPath = path.join(dirPath, config.fileName);
if (!fs.existsSync(configPath)) {
this.deleteConfig(config.id);
changed = true;
}
}
// 3. 检查内存,移除磁盘不存在的 模块文件夹
const currentModules = this.getModuleFoldersByContainer(containerId);
for (const folder of currentModules) {
// 注意Git 类型的模块文件夹,其 localPath 也是指向这个目录的
// 这里我们简单拼接路径来检查
// folder.localPath 格式: /projectId/aircraft/container/folderName
const folderName = folder.localPath.split('/').pop();
if (folderName) {
const folderPath = path.join(dirPath, folderName);
if (!fs.existsSync(folderPath)) {
this.deleteModuleFolder(folder.id);
changed = true;
}
}
}
return changed;
}
/**
* [新增] 递归清理目录下所有的嵌套 .git 文件夹
* 作用:保留 rootDir 下的 .git但删除所有子目录中的 .git
* 解决父级上传时因包含子级仓库导致的冲突
*/
async cleanNestedGitFolders(rootDir) {
if (!fs.existsSync(rootDir))
return;
try {
const entries = await fs.promises.readdir(rootDir, { withFileTypes: true });
for (const entry of entries) {
// 1. 如果遍历到的是当前目录下的 .git直接跳过这是我们要保留的主仓库
if (entry.name === '.git')
continue;
// 2. 只处理目录
if (entry.isDirectory()) {
const subDir = path.join(rootDir, entry.name);
const nestedGit = path.join(subDir, '.git');
// 检查子目录下是否有 .git
if (fs.existsSync(nestedGit)) {
console.log(`🧹 发现嵌套 Git 仓库,正在移除以支持父级上传: ${nestedGit}`);
// 使用您之前实现的强力删除方法(带权限处理)
await this.deleteDirectoryFromDisk(nestedGit);
}
// 继续递归,防止有多层嵌套
await this.cleanNestedGitFolders(subDir);
}
}
}
catch (error) {
console.error(`清理嵌套 Git 失败: ${error}`);
// 不抛出错误,尽力而为,以免打断上传流程
}
}
}
exports.ProjectService = ProjectService;
//# sourceMappingURL=ProjectService.js.map