| | | 1 | | using pva.Helpers.Extensions; |
| | | 2 | | using pva.SuperV.Common; |
| | | 3 | | using pva.SuperV.Engine.Exceptions; |
| | | 4 | | using pva.SuperV.Engine.HistoryRetrieval; |
| | | 5 | | using pva.SuperV.Engine.HistoryStorage; |
| | | 6 | | using pva.SuperV.Engine.Processing; |
| | | 7 | | using System.Reflection; |
| | | 8 | | using System.Runtime.CompilerServices; |
| | | 9 | | using System.Text; |
| | | 10 | | using System.Text.Json.Serialization; |
| | | 11 | | |
| | | 12 | | namespace pva.SuperV.Engine |
| | | 13 | | { |
| | | 14 | | /// <summary> |
| | | 15 | | /// A runnable project. It allows to create new instances. However its definitions are fixed and can't be changed. |
| | | 16 | | /// </summary> |
| | | 17 | | /// <seealso cref="pva.SuperV.Engine.Project" /> |
| | | 18 | | public class RunnableProject : Project |
| | | 19 | | { |
| | | 20 | | /// <summary> |
| | | 21 | | /// The project assembly loader. |
| | | 22 | | /// </summary> |
| | | 23 | | [JsonIgnore] |
| | | 24 | | private ProjectAssemblyLoader? projectAssemblyLoader; |
| | | 25 | | |
| | | 26 | | [JsonIgnore] |
| | | 27 | | public WeakReference? ProjectAssemblyLoaderWeakRef |
| | | 28 | | { |
| | 15 | 29 | | get => projectAssemblyLoader == null |
| | 15 | 30 | | ? null |
| | 15 | 31 | | : new WeakReference(projectAssemblyLoader, trackResurrection: true); |
| | | 32 | | } |
| | | 33 | | |
| | | 34 | | /// <summary> |
| | | 35 | | /// Gets the instances. |
| | | 36 | | /// </summary> |
| | | 37 | | /// <value> |
| | | 38 | | /// The instances. |
| | | 39 | | /// </value> |
| | | 40 | | [JsonIgnore] |
| | 1001 | 41 | | public Dictionary<string, Instance> Instances { get; } = new(StringComparer.OrdinalIgnoreCase); |
| | | 42 | | |
| | | 43 | | /// <summary> |
| | | 44 | | /// Initializes a new instance of the <see cref="RunnableProject"/> class. |
| | | 45 | | /// </summary> |
| | 13 | 46 | | public RunnableProject() |
| | 13 | 47 | | { |
| | 13 | 48 | | } |
| | | 49 | | |
| | | 50 | | /// <summary> |
| | | 51 | | /// Initializes a new instance of the <see cref="RunnableProject"/> class from a <see cref="WipProject"/>. |
| | | 52 | | /// </summary> |
| | | 53 | | /// <param name="wipProject">The wip project.</param> |
| | 134 | 54 | | public RunnableProject(WipProject wipProject) |
| | 134 | 55 | | { |
| | 134 | 56 | | Name = wipProject.Name; |
| | 134 | 57 | | Version = wipProject.Version; |
| | 134 | 58 | | Classes = new(wipProject.Classes); |
| | 134 | 59 | | FieldFormatters = new(wipProject.FieldFormatters); |
| | 134 | 60 | | HistoryStorageEngineConnectionString = wipProject.HistoryStorageEngineConnectionString; |
| | 134 | 61 | | HistoryStorageEngine = wipProject.HistoryStorageEngine; |
| | 134 | 62 | | HistoryRepositories = new(wipProject.HistoryRepositories); |
| | 134 | 63 | | TopicsChannels = new(wipProject.TopicsChannels); |
| | 134 | 64 | | ScriptDefinitions = new(wipProject.ScriptDefinitions); |
| | 134 | 65 | | CreateHistoryRepositories(HistoryStorageEngine); |
| | 133 | 66 | | CreateHistoryClassTimeSeries(); |
| | 132 | 67 | | SetupProjectAssemblyLoader(); |
| | 132 | 68 | | RecreateInstances(wipProject); |
| | 132 | 69 | | RegisterScripts(); |
| | 132 | 70 | | } |
| | | 71 | | |
| | | 72 | | /// <summary> |
| | | 73 | | /// Registers the scripts. |
| | | 74 | | /// </summary> |
| | | 75 | | private void RegisterScripts() |
| | 132 | 76 | | { |
| | 132 | 77 | | ScriptDefinitions.Values.ForEach(scriptDefinition => |
| | 247 | 78 | | CreateAndRegisterScript(scriptDefinition)); |
| | 132 | 79 | | } |
| | | 80 | | |
| | | 81 | | /// <summary> |
| | | 82 | | /// Creates and registers a script for field value changes. |
| | | 83 | | /// </summary> |
| | | 84 | | /// <param name="scriptDefinition">The script definition to register.</param> |
| | | 85 | | private void CreateAndRegisterScript(ScriptDefinition scriptDefinition) |
| | 115 | 86 | | { |
| | 115 | 87 | | Type? scriptType = GetType($"{scriptDefinition.Name}Class"); |
| | 115 | 88 | | ScriptBase script = CreateScript(scriptType!, this, scriptDefinition); |
| | 230 | 89 | | Task.Run(async () => await script.RegisterForTopicNotification().AsTask()); |
| | 115 | 90 | | } |
| | | 91 | | |
| | | 92 | | public override string GetId() |
| | 742 | 93 | | { |
| | 742 | 94 | | return $"{Name!}"; |
| | 742 | 95 | | } |
| | | 96 | | |
| | | 97 | | /// <summary> |
| | | 98 | | /// Setups the project assembly loader. Compiles the project classes and scripts and generates an asse;bly |
| | | 99 | | /// </summary> |
| | | 100 | | private void SetupProjectAssemblyLoader() |
| | 210 | 101 | | { |
| | 210 | 102 | | if (projectAssemblyLoader == null) |
| | 144 | 103 | | { |
| | 288 | 104 | | Task.Run(async () => await ProjectBuilder.BuildAsync(this)).Wait(); |
| | 144 | 105 | | projectAssemblyLoader ??= new(); |
| | 144 | 106 | | projectAssemblyLoader.LoadFromAssemblyPath(GetAssemblyFileName()); |
| | 144 | 107 | | } |
| | 210 | 108 | | } |
| | | 109 | | |
| | | 110 | | /// <summary> |
| | | 111 | | /// Creates the history class time series. |
| | | 112 | | /// </summary> |
| | | 113 | | private void CreateHistoryClassTimeSeries() |
| | 133 | 114 | | { |
| | 133 | 115 | | Classes.Values.ForEach(clazz => |
| | 482 | 116 | | { |
| | 482 | 117 | | clazz.FieldDefinitions.Values.ForEach(fieldDefinition => |
| | 2637 | 118 | | { |
| | 2637 | 119 | | fieldDefinition.ValuePostChangeProcessings |
| | 2637 | 120 | | .OfType<IHistorizationProcessing>() |
| | 2637 | 121 | | .ForEach(hp => |
| | 4272 | 122 | | hp.UpsertInHistoryStorage(Name!, clazz.Name!)); |
| | 3118 | 123 | | }); |
| | 614 | 124 | | }); |
| | 132 | 125 | | } |
| | | 126 | | |
| | | 127 | | /// <summary> |
| | | 128 | | /// Creates the history repositories. |
| | | 129 | | /// </summary> |
| | | 130 | | /// <param name="historyStorageEngine">The history storage engine.</param> |
| | | 131 | | /// <exception cref="NoHistoryStorageEngineException"></exception> |
| | | 132 | | private void CreateHistoryRepositories(IHistoryStorageEngine? historyStorageEngine) |
| | 134 | 133 | | { |
| | 134 | 134 | | if (HistoryRepositories.Keys.Count == 0) |
| | 14 | 135 | | { |
| | 14 | 136 | | return; |
| | | 137 | | } |
| | 120 | 138 | | if (historyStorageEngine is null) |
| | 1 | 139 | | { |
| | 1 | 140 | | throw new NoHistoryStorageEngineException(Name); |
| | | 141 | | } |
| | | 142 | | |
| | 119 | 143 | | HistoryRepositories.Values.ForEach(repository => |
| | 119 | 144 | | { |
| | 119 | 145 | | repository.HistoryStorageEngine = historyStorageEngine; |
| | 119 | 146 | | repository.UpsertRepository(Name!, historyStorageEngine); |
| | 238 | 147 | | }); |
| | 133 | 148 | | } |
| | | 149 | | |
| | | 150 | | /// <summary> |
| | | 151 | | /// Creates an instance. |
| | | 152 | | /// </summary> |
| | | 153 | | /// <param name="className">Name of the class.</param> |
| | | 154 | | /// <param name="instanceName">Name of the instance.</param> |
| | | 155 | | /// <param name="addToRunningInstances">Indicates if the created instances should be added to running instances |
| | | 156 | | /// <returns>The newly created <see cref="Instance"/>.</returns> |
| | | 157 | | /// <exception cref="EntityAlreadyExistException"></exception> |
| | | 158 | | public Instance? CreateInstance(string className, string instanceName, bool addToRunningInstances = true) |
| | 78 | 159 | | { |
| | 78 | 160 | | SetupProjectAssemblyLoader(); |
| | 78 | 161 | | if (Instances.ContainsKey(instanceName)) |
| | 1 | 162 | | { |
| | 1 | 163 | | throw new EntityAlreadyExistException("Instance", instanceName); |
| | | 164 | | } |
| | | 165 | | |
| | 77 | 166 | | Class clazz = GetClass(className); |
| | 77 | 167 | | Type? classType = GetType(clazz.Name); |
| | | 168 | | |
| | 77 | 169 | | Instance? instance = CreateInstance(classType!); |
| | 77 | 170 | | if (instance is null) |
| | 0 | 171 | | { |
| | 0 | 172 | | return instance; |
| | | 173 | | } |
| | 77 | 174 | | instance.Name = instanceName; |
| | 77 | 175 | | instance.Class = clazz; |
| | 77 | 176 | | Class? currentClass = clazz; |
| | 184 | 177 | | while (currentClass is not null) |
| | 107 | 178 | | { |
| | 107 | 179 | | currentClass.FieldDefinitions.ForEach((k, v) => |
| | 559 | 180 | | { |
| | 559 | 181 | | instance!.Fields.TryGetValue(k, out IField? field); |
| | 559 | 182 | | field!.FieldDefinition = v; |
| | 666 | 183 | | }); |
| | 107 | 184 | | currentClass = currentClass.BaseClass; |
| | 107 | 185 | | } |
| | 77 | 186 | | if (addToRunningInstances) |
| | 77 | 187 | | { |
| | 77 | 188 | | Instances.Add(instanceName, instance); |
| | 77 | 189 | | } |
| | 77 | 190 | | return instance; |
| | 77 | 191 | | } |
| | | 192 | | |
| | | 193 | | /// <summary> |
| | | 194 | | /// Gets the type for a name in project assembly. |
| | | 195 | | /// </summary> |
| | | 196 | | /// <param name="name">The name of the type.</param> |
| | | 197 | | /// <returns>The type</returns> |
| | | 198 | | private Type? GetType(string name) |
| | 192 | 199 | | { |
| | 192 | 200 | | string typeFullName = $"{GetNamespace()}.{name}"; |
| | 192 | 201 | | return projectAssemblyLoader!.Assemblies.First()?.GetType(typeFullName!); |
| | 192 | 202 | | } |
| | | 203 | | |
| | | 204 | | /// <summary> |
| | | 205 | | /// Creates an instance for targetType's <see cref="Class"/>. |
| | | 206 | | /// </summary> |
| | | 207 | | /// <param name="targetType">Type of the target.</param> |
| | | 208 | | /// <returns>Created <see cref="Instance"/>.</returns> |
| | | 209 | | private static Instance CreateInstance(Type targetType) |
| | 77 | 210 | | { |
| | 77 | 211 | | var ctor = GetConstructor(targetType); |
| | 77 | 212 | | return (Instance)ctor.Invoke([]); |
| | 77 | 213 | | } |
| | | 214 | | |
| | | 215 | | /// <summary> |
| | | 216 | | /// Creates an instance for targetType's <see cref="ScriptBase"/>. |
| | | 217 | | /// </summary> |
| | | 218 | | /// <param name="targetType">Type of the target.</param> |
| | | 219 | | /// <param name="project">Project in which to create script instance.</param> |
| | | 220 | | /// <param name="scriptDefinition">Script definitoin of script.</param> |
| | | 221 | | /// <returns>Created <see cref="ScriptBase"/>.</returns> |
| | | 222 | | private static ScriptBase CreateScript(Type targetType, RunnableProject project, ScriptDefinition scriptDefiniti |
| | 115 | 223 | | { |
| | 115 | 224 | | var ctor = GetConstructor(targetType, [typeof(RunnableProject), typeof(ScriptDefinition)]); |
| | 115 | 225 | | return (ScriptBase)ctor.Invoke([project, scriptDefinition]); |
| | 115 | 226 | | } |
| | | 227 | | |
| | | 228 | | /// <summary> |
| | | 229 | | /// Gets the empty constructor for targetType. |
| | | 230 | | /// </summary> |
| | | 231 | | /// <param name="targetType">Type of the target.</param> |
| | | 232 | | /// <returns>Constructor info of the type.</returns> |
| | | 233 | | /// <exception cref="InvalidOperationException">No constructor found for targetType.</exception> |
| | | 234 | | private static ConstructorInfo GetConstructor(Type targetType) |
| | 77 | 235 | | { |
| | 77 | 236 | | return GetConstructor(targetType, Type.EmptyTypes); |
| | 77 | 237 | | } |
| | | 238 | | |
| | | 239 | | /// <summary> |
| | | 240 | | /// Gets the empty constructor for targetType. |
| | | 241 | | /// </summary> |
| | | 242 | | /// <param name="targetType">Type of the target.</param> |
| | | 243 | | /// <param name="ctorArgTypes">Types for constructor arguments</param> |
| | | 244 | | /// <returns>Constructor info of the type.</returns> |
| | | 245 | | /// <exception cref="InvalidOperationException">No constructor found for targetType.</exception> |
| | | 246 | | private static ConstructorInfo GetConstructor(Type targetType, Type[] ctorArgTypes) |
| | 192 | 247 | | { |
| | 192 | 248 | | return targetType.GetConstructor(ctorArgTypes) |
| | 192 | 249 | | ?? throw new InvalidOperationException($"No constructor found for {targetType.Name}."); |
| | 192 | 250 | | } |
| | | 251 | | |
| | | 252 | | /// <summary> |
| | | 253 | | /// Removes an instance by its name. |
| | | 254 | | /// </summary> |
| | | 255 | | /// <param name="instanceName">Name of the instance.</param> |
| | | 256 | | public void RemoveInstance(string instanceName) |
| | 1 | 257 | | { |
| | 1 | 258 | | Instances.Remove(instanceName); |
| | 1 | 259 | | } |
| | | 260 | | |
| | | 261 | | /// <summary> |
| | | 262 | | /// Gets an instance by its name. |
| | | 263 | | /// </summary> |
| | | 264 | | /// <param name="instanceName">Name of the instance.</param> |
| | | 265 | | /// <returns>The <see cref="Instance"/></returns> |
| | | 266 | | /// <exception cref="UnknownEntityException">Indicates that entity wasn't found.</exception> |
| | | 267 | | public Instance GetInstance(string instanceName) |
| | 294 | 268 | | { |
| | 294 | 269 | | if (Instances.TryGetValue(instanceName, out var instance)) |
| | 293 | 270 | | { |
| | 293 | 271 | | return instance; |
| | | 272 | | } |
| | | 273 | | |
| | 1 | 274 | | throw new UnknownEntityException("Instance", instanceName); |
| | 293 | 275 | | } |
| | | 276 | | |
| | | 277 | | /// <summary> |
| | | 278 | | /// Recreates the instances from a <see cref="WipProject"/>. |
| | | 279 | | /// </summary> |
| | | 280 | | /// <param name="wipProject">The wip project.</param> |
| | | 281 | | private void RecreateInstances(WipProject wipProject) |
| | 132 | 282 | | { |
| | 132 | 283 | | wipProject.ToLoadInstances |
| | 132 | 284 | | .ForEach((k, v) => |
| | 2 | 285 | | { |
| | 2 | 286 | | string instanceName = k; |
| | 2 | 287 | | Instance oldInstance = v; |
| | 2 | 288 | | Instance? newInstance = CreateInstance(oldInstance.Class.Name, instanceName); |
| | 2 | 289 | | Dictionary<string, IField> newFields = new(newInstance!.Fields.Count); |
| | 2 | 290 | | newInstance.Fields |
| | 2 | 291 | | .ForEach((k1, v2) => |
| | 14 | 292 | | { |
| | 14 | 293 | | string fieldName = k1; |
| | 14 | 294 | | newFields.Add(fieldName, |
| | 14 | 295 | | oldInstance.Fields.GetValueOrDefault(fieldName, v2)); |
| | 16 | 296 | | }); |
| | 2 | 297 | | newInstance.Fields = newFields; |
| | 134 | 298 | | }); |
| | 132 | 299 | | } |
| | | 300 | | |
| | | 301 | | /// <summary> |
| | | 302 | | /// Unloads the project. Clears all instances. |
| | | 303 | | /// </summary> |
| | | 304 | | [MethodImpl(MethodImplOptions.NoInlining)] |
| | | 305 | | public override void Unload() |
| | 143 | 306 | | { |
| | 143 | 307 | | Instances.Values.ForEach(instance => |
| | 216 | 308 | | instance.Dispose()); |
| | 143 | 309 | | Instances.Clear(); |
| | 143 | 310 | | HistoryStorageEngine?.Dispose(); |
| | 143 | 311 | | TopicsChannels.Values.ForEach(topicChannel => |
| | 377 | 312 | | topicChannel.Writer.TryComplete()); |
| | 143 | 313 | | TopicsChannels.Clear(); |
| | 143 | 314 | | base.Unload(); |
| | 143 | 315 | | Projects.Remove(GetId(), out _); |
| | 143 | 316 | | projectAssemblyLoader?.Unload(); |
| | 143 | 317 | | projectAssemblyLoader = null; |
| | 143 | 318 | | } |
| | | 319 | | |
| | | 320 | | /// <summary> |
| | | 321 | | /// Gets the C# code for generating the project's assembly with <see cref="Project.BuildAsync(WipProject)"/>. |
| | | 322 | | /// </summary> |
| | | 323 | | /// <returns>C# code.</returns> |
| | | 324 | | public string GetCode() |
| | 132 | 325 | | { |
| | 132 | 326 | | StringBuilder codeBuilder = new(); |
| | 132 | 327 | | codeBuilder = codeBuilder |
| | 132 | 328 | | .AppendLine("using System.Collections.Generic;") |
| | 132 | 329 | | .AppendLine("using System.Reflection;") |
| | 132 | 330 | | .AppendLine("using System.Threading.Tasks;") |
| | 132 | 331 | | .AppendLine($"using {GetType().Namespace};") |
| | 132 | 332 | | .AppendLine("using pva.SuperV.Engine.Processing;") |
| | 132 | 333 | | .AppendLine($"[assembly: AssemblyProduct(\"{Name}\")]") |
| | 132 | 334 | | .AppendLine($"[assembly: AssemblyTitle(\"{Description}\")]") |
| | 132 | 335 | | .AppendLine($"[assembly: AssemblyVersion(\"{Version}\")]") |
| | 132 | 336 | | .AppendLine($"[assembly: AssemblyFileVersion(\"{Version}\")]") |
| | 132 | 337 | | .AppendLine($"[assembly: AssemblyInformationalVersion(\"{Version}\")]") |
| | 132 | 338 | | .AppendLine($"namespace {GetNamespace()}") |
| | 132 | 339 | | .AppendLine("{"); |
| | 132 | 340 | | Classes |
| | 612 | 341 | | .ForEach((_, v) => codeBuilder.AppendLine(v.GetCode())); |
| | 132 | 342 | | ScriptDefinitions |
| | 247 | 343 | | .ForEach((_, v) => codeBuilder.AppendLine(v.GetCode())); |
| | 132 | 344 | | codeBuilder.AppendLine("}"); |
| | 132 | 345 | | return codeBuilder.ToString(); |
| | 132 | 346 | | } |
| | | 347 | | |
| | | 348 | | private string GetNamespace() |
| | 324 | 349 | | { |
| | 324 | 350 | | return $"{Name}.V{Version}"; |
| | 324 | 351 | | } |
| | | 352 | | |
| | | 353 | | public void SetInstanceValue<T>(string instanceName, string fieldName, T fieldValue) |
| | 12 | 354 | | { |
| | 12 | 355 | | SetInstanceValue(instanceName, fieldName, fieldValue, DateTime.UtcNow, QualityLevel.Good); |
| | 12 | 356 | | } |
| | | 357 | | |
| | | 358 | | public void SetInstanceValue<T>(string instanceName, string fieldName, T fieldValue, DateTime timestamp) |
| | 48 | 359 | | { |
| | 48 | 360 | | SetInstanceValue(instanceName, fieldName, fieldValue, timestamp, QualityLevel.Good); |
| | 48 | 361 | | } |
| | | 362 | | |
| | | 363 | | public void SetInstanceValue<T>(string instanceName, string fieldName, T fieldValue, QualityLevel qualityLevel) |
| | 0 | 364 | | { |
| | 0 | 365 | | SetInstanceValue(instanceName, fieldName, fieldValue, DateTime.UtcNow, qualityLevel); |
| | 0 | 366 | | } |
| | | 367 | | |
| | | 368 | | public void SetInstanceValue<T>(string instanceName, string fieldName, T fieldValue, DateTime timestamp, |
| | | 369 | | QualityLevel qualityLevel) |
| | 60 | 370 | | { |
| | 60 | 371 | | Instance instance = GetInstance(instanceName); |
| | 60 | 372 | | IField field = instance.GetField(fieldName); |
| | 60 | 373 | | if (field is not Field<T> typedField) |
| | 0 | 374 | | { |
| | 0 | 375 | | throw new WrongFieldTypeException(fieldName, fieldValue!.GetType(), field.GetType()); |
| | | 376 | | } |
| | | 377 | | |
| | 60 | 378 | | typedField.SetValue(fieldValue, timestamp, qualityLevel); |
| | 60 | 379 | | } |
| | | 380 | | |
| | | 381 | | public List<HistoryRow> GetHistoryValues(string instanceName, HistoryTimeRange query, List<string> fieldNames) |
| | 3 | 382 | | { |
| | 3 | 383 | | Instance instance = GetInstance(instanceName); |
| | 3 | 384 | | InstanceTimeSerieParameters instanceTimeSerieParameters = GetHistoryParametersForFields(instance, fieldNames |
| | | 385 | | |
| | 3 | 386 | | return GetHistoryValues(instanceName, query, instanceTimeSerieParameters); |
| | 2 | 387 | | } |
| | | 388 | | |
| | | 389 | | public List<HistoryRow> GetHistoryValues(string instanceName, HistoryTimeRange query, InstanceTimeSerieParameter |
| | 31 | 390 | | { |
| | 31 | 391 | | ValidateTimeRange(query); |
| | 30 | 392 | | return HistoryStorageEngine!.GetHistoryValues(instanceName, query, instanceTimeSerieParameters, instanceTime |
| | 30 | 393 | | } |
| | | 394 | | |
| | | 395 | | public List<HistoryStatisticRow> GetHistoryStatistics(string instanceName, HistoryStatisticTimeRange query, List |
| | 3 | 396 | | { |
| | 3 | 397 | | Instance instance = GetInstance(instanceName); |
| | 7 | 398 | | InstanceTimeSerieParameters instanceTimeSerieParameters = GetHistoryParametersForFields(instance, [.. fieldN |
| | 3 | 399 | | List<HistoryStatisticField> statFields = []; |
| | 14 | 400 | | for (int index = 0; index < fieldNames.Count; index++) |
| | 4 | 401 | | { |
| | 4 | 402 | | statFields.Add(new(instanceTimeSerieParameters.Fields[index], fieldNames[index].StatisticFunction)); |
| | 4 | 403 | | } |
| | 3 | 404 | | return GetHistoryStatistics(instanceName, query, statFields, instanceTimeSerieParameters); |
| | 1 | 405 | | } |
| | | 406 | | |
| | | 407 | | public List<HistoryStatisticRow> GetHistoryStatistics(string instanceName, HistoryStatisticTimeRange query, List |
| | | 408 | | InstanceTimeSerieParameters instanceTimeSerieParameters) |
| | 27 | 409 | | { |
| | | 410 | | // if query range is a multiple of interval, remove 1 microsecond to end time (To) to avoid returning a new |
| | 27 | 411 | | if ((query.To - query.From).Ticks % query.Interval.Ticks == 0) |
| | 14 | 412 | | { |
| | 14 | 413 | | query = query with { To = query.To.AddMicroseconds(-1) }; |
| | 14 | 414 | | } |
| | 27 | 415 | | ValidateTimeRange(query); |
| | 25 | 416 | | return HistoryStorageEngine!.GetHistoryStatistics(instanceName, query, instanceTimeSerieParameters, fields); |
| | 25 | 417 | | } |
| | | 418 | | |
| | | 419 | | private static void ValidateTimeRange(HistoryTimeRange timeRange) |
| | 58 | 420 | | { |
| | 58 | 421 | | if (timeRange.From >= timeRange.To) |
| | 2 | 422 | | { |
| | 2 | 423 | | throw new BadHistoryStartTimeException(timeRange.From, timeRange.To); |
| | | 424 | | } |
| | 56 | 425 | | } |
| | | 426 | | |
| | | 427 | | private static void ValidateTimeRange(HistoryStatisticTimeRange timeRange) |
| | 27 | 428 | | { |
| | 27 | 429 | | ValidateTimeRange(timeRange as HistoryTimeRange); |
| | 26 | 430 | | if (timeRange.Interval.Add(TimeSpan.FromMinutes(-1)) > timeRange.To - timeRange.From) |
| | 1 | 431 | | { |
| | 1 | 432 | | throw new BadHistoryIntervalException(timeRange.Interval, timeRange.From, timeRange.To); |
| | | 433 | | } |
| | 25 | 434 | | } |
| | | 435 | | |
| | | 436 | | public static InstanceTimeSerieParameters GetHistoryParametersForFields(Instance instance, List<string> fieldNam |
| | 58 | 437 | | { |
| | 58 | 438 | | List<IFieldDefinition> fields = []; |
| | 58 | 439 | | IHistorizationProcessing? historizationProcessing = null; |
| | 58 | 440 | | List<IHistorizationProcessing>? historizationProcessings = null; |
| | 350 | 441 | | foreach (string fieldName in fieldNames) |
| | 88 | 442 | | { |
| | 88 | 443 | | IFieldDefinition field = instance.Class.GetField(fieldName); |
| | 88 | 444 | | IHistorizationProcessing? hp = field.ValuePostChangeProcessings |
| | 88 | 445 | | .OfType<IHistorizationProcessing>() |
| | 88 | 446 | | .FirstOrDefault(); |
| | 88 | 447 | | if (hp != null) |
| | 59 | 448 | | { |
| | 59 | 449 | | historizationProcessing = hp; |
| | 59 | 450 | | } |
| | | 451 | | else |
| | 29 | 452 | | { |
| | 29 | 453 | | if (historizationProcessing == null) |
| | 29 | 454 | | { |
| | 29 | 455 | | historizationProcessings = []; |
| | 29 | 456 | | instance.Class.FieldDefinitions.Values.ForEach(f => |
| | 377 | 457 | | historizationProcessings.AddRange([.. f.ValuePostChangeProcessings.OfType<IHistorizationProc |
| | 29 | 458 | | ); |
| | 29 | 459 | | } |
| | 58 | 460 | | hp = historizationProcessings!.FirstOrDefault(op => op.FieldsToHistorize.Contains(field)); |
| | 29 | 461 | | if (hp != null && historizationProcessing != null && hp.Name != historizationProcessing.Name) |
| | 0 | 462 | | { |
| | 0 | 463 | | throw new MixedHistoryProcessingException(field.Name); |
| | | 464 | | } |
| | | 465 | | |
| | 29 | 466 | | } |
| | 88 | 467 | | fields.Add(field); |
| | 88 | 468 | | } |
| | 58 | 469 | | if (historizationProcessing is null) |
| | 0 | 470 | | { |
| | 0 | 471 | | throw new NoHistoryStorageEngineException(); |
| | | 472 | | } |
| | 58 | 473 | | return new InstanceTimeSerieParameters(fields, historizationProcessing); |
| | 58 | 474 | | } |
| | | 475 | | } |
| | | 476 | | } |