Files
2023-12-30 12:30:40 +01:00

314 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Newtonsoft.Json;
using T3.Core.Logging;
using T3.Core.Operator;
using T3.Core.Resource;
using T3.Core.Stats;
// ReSharper disable RedundantNameQualifier
namespace T3.Core.Model;
public partial class SymbolData
{
public Assembly OperatorsAssembly { get; }
public SymbolData(Assembly operatorAssembly)
{
OperatorsAssembly = operatorAssembly;
_updateCounter = new OpUpdateCounter();
}
public virtual void Load(bool enableLog)
{
Log.Debug("Loading symbols...");
var symbolFiles = Directory.GetFiles(OperatorTypesFolder, $"*{SymbolExtension}", SearchOption.AllDirectories);
var symbolsRead = symbolFiles.AsParallel()
.Select(JsonFileResult<Symbol>.ReadAndCreate)
.Select(ReadSymbolFromJsonFileResult)
.Where(symbolReadResult => symbolReadResult.Symbol is not null)
.ToList(); // Execute and bring back to main thread
Log.Debug("Registering loaded symbols...");
// Check if there are symbols without a file, if yes add these
var instanceTypesWithoutFile = OperatorsAssembly.ExportedTypes.AsParallel()
.Where(type => type.IsSubclassOf(typeof(Instance)))
.Where(type => !type.IsGenericType)
.ToHashSet();
foreach (var readSymbolResult in symbolsRead)
{
var symbol = readSymbolResult.Symbol;
if (!SymbolRegistry.Entries.TryAdd(symbol.Id, symbol))
{
var existingSymbol = SymbolRegistry.Entries[symbol.Id];
Log.Error($"Symbol {existingSymbol.Name} {symbol.Id} exists multiple times in database.");
continue;
}
instanceTypesWithoutFile.Remove(symbol.InstanceType);
}
foreach (var newType in instanceTypesWithoutFile)
{
RegisterTypeWithoutFile(newType);
}
Log.Debug("Applying symbol children...");
Parallel.ForEach(symbolsRead, ReadAndApplyChildren);
void ReadAndApplyChildren(SymbolJson.SymbolReadResult readSymbolResult)
{
var gotSymbolChildren = SymbolJson.TryReadAndApplySymbolChildren(readSymbolResult);
if (!gotSymbolChildren)
{
Log.Error($"Problem obtaining children of {readSymbolResult.Symbol.Name} ({readSymbolResult.Symbol.Id})");
}
}
SymbolJson.SymbolReadResult ReadSymbolFromJsonFileResult(JsonFileResult<Symbol> jsonInfo)
{
var result = SymbolJson.ReadSymbolRoot(jsonInfo.Guid, jsonInfo.JToken, allowNonOperatorInstanceType: false);
jsonInfo.Object = result.Symbol;
return result;
}
void RegisterTypeWithoutFile(Type newType)
{
var typeNamespace = newType.Namespace;
if (string.IsNullOrWhiteSpace(typeNamespace))
{
Log.Error($"Null or empty namespace of type {newType.Name}");
return;
}
var @namespace = _innerNamespace.Replace(newType.Namespace ?? string.Empty, "").ToLower();
var idFromNamespace = _idFromNamespace
.Match(newType.Namespace ?? string.Empty).Value
.Replace('_', '-');
Debug.Assert(!string.IsNullOrWhiteSpace(idFromNamespace));
var symbol = new Symbol(newType, Guid.Parse(idFromNamespace))
{
Namespace = @namespace,
Name = newType.Name
};
var added = SymbolRegistry.Entries.TryAdd(symbol.Id, symbol);
if (!added)
{
Log.Error($"Ignoring redefinition symbol {symbol.Name}. Please fix multiple definitions in Operators/Types/ folder");
return;
}
if (enableLog)
Log.Debug($"new added symbol: {newType}");
}
}
private readonly Regex _innerNamespace = new(@".Id_(\{){0,1}[0-9a-fA-F]{8}_[0-9a-fA-F]{4}_[0-9a-fA-F]{4}_[0-9a-fA-F]{4}_[0-9a-fA-F]{12}(\}){0,1}",
RegexOptions.IgnoreCase);
private readonly Regex _idFromNamespace = new(@"(\{){0,1}[0-9a-fA-F]{8}_[0-9a-fA-F]{4}_[0-9a-fA-F]{4}_[0-9a-fA-F]{4}_[0-9a-fA-F]{12}(\}){0,1}",
RegexOptions.IgnoreCase);
public virtual void SaveAll()
{
ResourceFileWatcher.DisableOperatorFileWatcher(); // don't update ops if file is written during save
// Todo: this sounds like a dangerous step. we should overwrite these files by default and can check which files are not overwritten to delete others?
RemoveAllSymbolFiles();
SortAllSymbolSourceFiles();
SaveSymbolDefinitionAndSourceFiles(SymbolRegistry.Entries.Values);
ResourceFileWatcher.EnableOperatorFileWatcher();
}
protected static void SaveSymbolDefinitionAndSourceFiles(IEnumerable<Symbol> valueCollection)
{
foreach (var symbol in valueCollection)
{
var filepath = BuildFilepathForSymbol(symbol, SymbolExtension);
using (var sw = new StreamWriter(filepath))
using (var writer = new JsonTextWriter(sw) { Formatting = Formatting.Indented })
{
SymbolJson.WriteSymbol(symbol, writer);
}
if (!string.IsNullOrEmpty(symbol.PendingSource))
{
WriteSymbolSourceToFile(symbol);
}
}
}
private static void SortAllSymbolSourceFiles()
{
// Move existing source files to correct namespace folder
var sourceFiles = Directory.GetFiles(OperatorTypesFolder, $"*{SourceExtension}", SearchOption.AllDirectories);
foreach (var sourceFilePath in sourceFiles)
{
var classname = Path.GetFileNameWithoutExtension(sourceFilePath);
var symbol = SymbolRegistry.Entries.Values.SingleOrDefault(s => s.Name == classname);
if (symbol == null)
{
// This happens when renaming symbols.
Log.Debug($"Skipping unregistered source file {sourceFilePath}");
continue;
}
var targetFilepath = BuildFilepathForSymbol(symbol, SourceExtension);
if (sourceFilePath == targetFilepath)
continue;
Log.Debug($" Moving {sourceFilePath} -> {targetFilepath} ...");
try
{
File.Move(sourceFilePath, targetFilepath);
}
catch (Exception e)
{
Log.Warning("Failed to write source file '" + sourceFilePath + "': " + e);
}
}
}
private static void RemoveAllSymbolFiles()
{
// Remove all old t3 files before storing to get rid off invalid ones
var symbolFiles = Directory.GetFiles(OperatorTypesFolder, $"*{SymbolExtension}", SearchOption.AllDirectories);
foreach (var symbolFilePath in symbolFiles)
{
try
{
File.Delete(symbolFilePath);
}
catch (Exception e)
{
Log.Warning("Failed to deleted file '" + symbolFilePath + "': " + e);
}
}
}
// public void SaveModifiedSymbol(Symbol symbol)
// {
// RemoveObsoleteSymbolFiles(symbol);
//
// var symbolJson = new SymbolJson();
//
// var filepath = BuildFilepathForSymbol(symbol, SymbolExtension);
//
// using (var sw = new StreamWriter(filepath))
// using (var writer = new JsonTextWriter(sw))
// {
// symbolJson.Writer = writer;
// symbolJson.Writer.Formatting = Formatting.Indented;
// symbolJson.WriteSymbol(symbol);
// }
//
// if (!string.IsNullOrEmpty(symbol.PendingSource))
// {
// WriteSymbolSourceToFile(symbol);
// }
// }
private static void RemoveDeprecatedSymbolFiles(Symbol symbol)
{
if (string.IsNullOrEmpty(symbol.DeprecatedSourcePath))
return;
foreach (var fileExtension in OperatorFileExtensions)
{
var sourceFilepath = Path.Combine(OperatorTypesFolder, symbol.DeprecatedSourcePath + "_" + symbol.Id + fileExtension);
try
{
File.Delete(sourceFilepath);
}
catch (Exception e)
{
Log.Warning("Failed to deleted file '" + sourceFilepath + "': " + e);
}
}
symbol.DeprecatedSourcePath = String.Empty;
}
private static void WriteSymbolSourceToFile(Symbol symbol)
{
var sourcePath = BuildFilepathForSymbol(symbol, SourceExtension);
using (var sw = new StreamWriter(sourcePath))
{
sw.Write(symbol.PendingSource);
}
// Remove old source file and its entry in project
if (!string.IsNullOrEmpty(symbol.DeprecatedSourcePath))
{
if (symbol.DeprecatedSourcePath == SymbolData.BuildFilepathForSymbol(symbol, SourceExtension))
{
Log.Warning($"Attempted to deprecated valid source file: {symbol.DeprecatedSourcePath}");
symbol.DeprecatedSourcePath = string.Empty;
return;
}
File.Delete(symbol.DeprecatedSourcePath);
// Adjust path of file resource
ResourceManager.RenameOperatorResource(symbol.DeprecatedSourcePath, sourcePath);
symbol.DeprecatedSourcePath = string.Empty;
}
symbol.PendingSource = null;
}
#region File path handling
private static string GetSubDirectoryFromNamespace(string symbolNamespace)
{
var trimmed = symbolNamespace.Trim().Replace(".", "\\");
return trimmed;
}
private static string BuildAndCreateFolderFromNamespace(string symbolNamespace)
{
var directory = Path.Combine(OperatorTypesFolder, GetSubDirectoryFromNamespace(symbolNamespace));
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
return directory;
}
public static string BuildFilepathForSymbol(Symbol symbol, string extension)
{
var dir = BuildAndCreateFolderFromNamespace(symbol.Namespace);
return extension == SourceExtension
? Path.Combine(dir, symbol.Name + extension)
: Path.Combine(dir, symbol.Name + "_" + symbol.Id + extension);
}
#endregion
private static OpUpdateCounter _updateCounter;
public const string SourceExtension = ".cs";
private const string SymbolExtension = ".t3";
protected const string SymbolUiExtension = ".t3ui";
public const string OperatorTypesFolder = @"Operators\Types\";
private static readonly List<string> OperatorFileExtensions = new()
{
SymbolExtension,
SymbolUiExtension,
SourceExtension,
};
}