#nullable enable using System.Diagnostics; using System.IO; using T3.Core.Compilation; using T3.Core.Operator; using T3.Core.Resource; using T3.Core.Resource.Assets; using T3.Core.SystemUi; using T3.Core.Settings; using T3.Editor.Compilation; using T3.Editor.Gui.Interaction.Variations.Model; using T3.Editor.Gui.UiHelpers.Thumbnails; namespace T3.Editor.UiModel; /// /// EditableSymbolProject is an that contains runtime package manipulation logic. /// [DebuggerDisplay("{DisplayName}")] internal sealed partial class EditableSymbolProject : EditorSymbolPackage { public override string DisplayName { get; } /// /// Create a new using the given . /// public EditableSymbolProject(CsProjectFile csProjectFile) : base(assembly: AssemblyInformation.CreateUninitialized(), directory: csProjectFile.Directory, false) { AssemblyInformation.Initialize(csProjectFile.GetBuildTargetDirectory(), false); CsProjectFile = csProjectFile; //Log.Info($"Adding project {csProjectFile.Name}..."); _csFileWatcher = new CodeFileWatcher(this, OnFileChanged, OnCodeFileRenamed); _csFileWatcher.EnableRaisingEvents = true; DisplayName = $"{csProjectFile.Name} ({CsProjectFile.RootNamespace})"; SymbolUpdated += OnSymbolUpdated; SymbolRemoved += OnSymbolRemoved; InitializeAssets(); _allProjectsCache = null; } public void OpenProjectInCodeEditor() { CoreUi.Instance.OpenWithDefaultApplication(CsProjectFile.FullPath); } public bool TryOpenCSharpInEditor(Symbol symbol) { var guid = symbol.Id; if (!FilePathHandlers.TryGetValue(guid, out var filePathHandler)) { Log.Error($"No file path handler found for symbol {guid}"); return false; } var sourceCodePath = filePathHandler.SourceCodePath; if (string.IsNullOrWhiteSpace(sourceCodePath)) { Log.Error($"No source code path found for symbol {guid}"); return false; } OpenProjectInCodeEditor(); CoreUi.Instance.OpenWithDefaultApplication(sourceCodePath); return true; } public void ReplaceSymbolUi(SymbolUi symbolUi) { var symbol = symbolUi.Symbol; SymbolUiDict[symbol.Id] = symbolUi; symbolUi.FlagAsModified(); symbolUi.ReadOnly = false; SaveSymbolFile(symbolUi); Log.Debug($"Replaced symbol ui for {symbol.Name}"); } private void GiveSymbolToPackage(Guid id, EditableSymbolProject newDestinationProject) { SymbolDict.Remove(id, out var symbol); SymbolUiDict.Remove(id, out var symbolUi); FilePathHandlers.Remove(id, out var symbolPathHandler); if(_pendingSource.Remove(id, out var source)) newDestinationProject._pendingSource.TryAdd(id, source); Debug.Assert(symbol != null); Debug.Assert(symbolUi != null); Debug.Assert(symbolPathHandler != null); symbol.SymbolPackage = newDestinationProject; symbolUi.UpdateSymbolPackage(newDestinationProject); newDestinationProject.SymbolDict.TryAdd(id, symbol); newDestinationProject.SymbolUiDict.TryAdd(id, symbolUi); newDestinationProject.FilePathHandlers.TryAdd(id, symbolPathHandler); // Copy thumbnails... { var sourceFilePath = Path.Combine(Folder, FileLocations.MetaSubFolder, ThumbnailManager.ThumbnailsSubFolder, id + ".png"); if (File.Exists(sourceFilePath)) { var targetFolder = Path.Combine(newDestinationProject.Folder, FileLocations.MetaSubFolder, ThumbnailManager.ThumbnailsSubFolder); var targetFilePath = Path.Combine(targetFolder, id + ".png"); Directory.CreateDirectory(targetFolder); try { File.Move(sourceFilePath, targetFilePath); } catch (Exception e) { Log.Warning("Can't move thumbnail " + e.Message); } } } // Move variations... { var sourceFilePath = SymbolVariationPool.GetVariationFilePathInPackage(Folder, id); if (File.Exists(sourceFilePath)) { var targetFilePath = SymbolVariationPool.GetVariationFilePathInPackage(newDestinationProject.Folder, id); try { Directory.CreateDirectory(Path.GetDirectoryName(targetFilePath)!); File.Move(sourceFilePath, targetFilePath); } catch (Exception e) { Log.Warning("Can't move variations " + e.Message); } } } // move files to new project - incorrect paths will be corrected by the loading process symbolPathHandler.UpdateFromSymbol(); OnSymbolMoved?.Invoke(id, newDestinationProject); symbolUi.FlagAsModified(); } private static readonly string[] _folderExclusions = ["bin", "obj", "dependencies", FileLocations.ExportSubFolder]; protected override IEnumerable SymbolUiSearchFiles => FindFilesOfType(SymbolUiExtension); protected override IEnumerable SymbolSearchFiles => FindFilesOfType(SymbolExtension); protected override IEnumerable SourceCodeSearchFiles => FindFilesOfType(SourceCodeExtension); private IEnumerable FindFilesOfType(string fileExtension) { var directoryInfo = new DirectoryInfo(Folder); return directoryInfo.EnumerateDirectories() .Where(x => !_folderExclusions.Contains(x.Name)) .SelectMany(x => x.EnumerateFiles($"*{fileExtension}", SearchOption.AllDirectories)) .Concat(directoryInfo.EnumerateFiles($"*{fileExtension}")).Select(x => x.FullName); } protected override void InitializeAssets() { base.InitializeAssets(); _resourceFileWatcher = new ResourceFileWatcher(AssetsFolder); _resourceFileWatcher.FileCreated += (_, path) => { var isDirectory = Directory.Exists(path); FileSystemInfo info = isDirectory ? new DirectoryInfo(path) : new FileInfo(path); AssetRegistry.RegisterPackageEntry(info, this, isDirectory); ResourceFileWatcher.FileStateChangeCounter++; }; _resourceFileWatcher.FileRenamed += (oldPath, newPath) => { AssetRegistry.UpdateMovedAsset(oldPath, newPath); }; _resourceFileWatcher.FileDeleted += (_, path) => { AssetRegistry.UnregisterAbsoluteFilePath(path, this); }; } public override void Dispose() { base.Dispose(); FileWatcher?.Dispose(); ProjectSetup.RemoveSymbolPackage(this, false); _allProjectsCache = null; } /// /// Event raised when a symbol is moved to a different project or deleted /// public event Action? OnSymbolMoved; public readonly CsProjectFile CsProjectFile; private ResourceFileWatcher? _resourceFileWatcher; public override ResourceFileWatcher? FileWatcher => _resourceFileWatcher; public override bool IsReadOnly => false; private static IEnumerable? _allProjectsCache; public static IEnumerable AllProjects { get { _allProjectsCache ??= ProjectSetup .AllPackages .Where(x => x is EditableSymbolProject) .Cast() .OrderByDescending(x => x.CsProjectFile.ModifiedAt) .ToList(); return _allProjectsCache; } } // New helper to force reload internally public void MarkCodeExternallyModified() { CodeExternallyModified = true; } }