diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs | 55 | ||||
| -rw-r--r-- | src/WixToolset.Core.WindowsInstaller/MsiBackend.cs | 18 | ||||
| -rw-r--r-- | src/WixToolset.Core.WindowsInstaller/MsmBackend.cs | 17 | ||||
| -rw-r--r-- | src/WixToolset.Core.WindowsInstaller/MspBackend.cs | 17 | ||||
| -rw-r--r-- | src/WixToolset.Core/BindResult.cs | 31 | ||||
| -rw-r--r-- | src/WixToolset.Core/CommandLine/BuildCommand.cs | 81 |
6 files changed, 120 insertions, 99 deletions
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs index 1bcaf209..a90517da 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs | |||
| @@ -17,13 +17,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind | |||
| 17 | /// <summary> | 17 | /// <summary> |
| 18 | /// Binds a databse. | 18 | /// Binds a databse. |
| 19 | /// </summary> | 19 | /// </summary> |
| 20 | internal class BindDatabaseCommand : IDisposable | 20 | internal class BindDatabaseCommand |
| 21 | { | 21 | { |
| 22 | // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs. | 22 | // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs. |
| 23 | internal static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}"); | 23 | internal static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}"); |
| 24 | 24 | ||
| 25 | private bool disposed; | ||
| 26 | |||
| 27 | public BindDatabaseCommand(IBindContext context, IEnumerable<IWindowsInstallerBackendBinderExtension> backendExtension, Validator validator):this(context, backendExtension, null, validator) | 25 | public BindDatabaseCommand(IBindContext context, IEnumerable<IWindowsInstallerBackendBinderExtension> backendExtension, Validator validator):this(context, backendExtension, null, validator) |
| 28 | { | 26 | { |
| 29 | } | 27 | } |
| @@ -97,13 +95,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind | |||
| 97 | 95 | ||
| 98 | private Validator Validator { get; } | 96 | private Validator Validator { get; } |
| 99 | 97 | ||
| 100 | public IEnumerable<IFileTransfer> FileTransfers { get; private set; } | 98 | public IBindResult Execute() |
| 101 | |||
| 102 | public IEnumerable<ITrackedFile> TrackedFiles { get; private set; } | ||
| 103 | |||
| 104 | public WixOutput Wixout { get; private set; } | ||
| 105 | |||
| 106 | public void Execute() | ||
| 107 | { | 99 | { |
| 108 | var section = this.Intermediate.Sections.Single(); | 100 | var section = this.Intermediate.Sections.Single(); |
| 109 | 101 | ||
| @@ -218,7 +210,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind | |||
| 218 | 210 | ||
| 219 | if (this.Messaging.EncounteredError) | 211 | if (this.Messaging.EncounteredError) |
| 220 | { | 212 | { |
| 221 | return; | 213 | return null; |
| 222 | } | 214 | } |
| 223 | 215 | ||
| 224 | // Call extension | 216 | // Call extension |
| @@ -290,7 +282,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind | |||
| 290 | // stop processing if an error previously occurred | 282 | // stop processing if an error previously occurred |
| 291 | if (this.Messaging.EncounteredError) | 283 | if (this.Messaging.EncounteredError) |
| 292 | { | 284 | { |
| 293 | return; | 285 | return null; |
| 294 | } | 286 | } |
| 295 | 287 | ||
| 296 | // Gather information about files that do not come from merge modules. | 288 | // Gather information about files that do not come from merge modules. |
| @@ -322,7 +314,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind | |||
| 322 | // stop processing if an error previously occurred | 314 | // stop processing if an error previously occurred |
| 323 | if (this.Messaging.EncounteredError) | 315 | if (this.Messaging.EncounteredError) |
| 324 | { | 316 | { |
| 325 | return; | 317 | return null; |
| 326 | } | 318 | } |
| 327 | 319 | ||
| 328 | // Now that the variable cache is populated, resolve any delayed fields. | 320 | // Now that the variable cache is populated, resolve any delayed fields. |
| @@ -347,7 +339,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind | |||
| 347 | // stop processing if an error previously occurred | 339 | // stop processing if an error previously occurred |
| 348 | if (this.Messaging.EncounteredError) | 340 | if (this.Messaging.EncounteredError) |
| 349 | { | 341 | { |
| 350 | return; | 342 | return null; |
| 351 | } | 343 | } |
| 352 | 344 | ||
| 353 | // Time to create the output object. Try to put as much above here as possible, updating the IR is better. | 345 | // Time to create the output object. Try to put as much above here as possible, updating the IR is better. |
| @@ -425,7 +417,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind | |||
| 425 | // Stop processing if an error previously occurred. | 417 | // Stop processing if an error previously occurred. |
| 426 | if (this.Messaging.EncounteredError) | 418 | if (this.Messaging.EncounteredError) |
| 427 | { | 419 | { |
| 428 | return; | 420 | return null; |
| 429 | } | 421 | } |
| 430 | 422 | ||
| 431 | // Ensure the intermediate folder is created since delta patches will be | 423 | // Ensure the intermediate folder is created since delta patches will be |
| @@ -479,7 +471,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind | |||
| 479 | // stop processing if an error previously occurred | 471 | // stop processing if an error previously occurred |
| 480 | if (this.Messaging.EncounteredError) | 472 | if (this.Messaging.EncounteredError) |
| 481 | { | 473 | { |
| 482 | return; | 474 | return null; |
| 483 | } | 475 | } |
| 484 | 476 | ||
| 485 | // Generate database file. | 477 | // Generate database file. |
| @@ -496,7 +488,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind | |||
| 496 | // Stop processing if an error previously occurred. | 488 | // Stop processing if an error previously occurred. |
| 497 | if (this.Messaging.EncounteredError) | 489 | if (this.Messaging.EncounteredError) |
| 498 | { | 490 | { |
| 499 | return; | 491 | return null; |
| 500 | } | 492 | } |
| 501 | 493 | ||
| 502 | // Merge modules. | 494 | // Merge modules. |
| @@ -533,7 +525,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind | |||
| 533 | 525 | ||
| 534 | if (this.Messaging.EncounteredError) | 526 | if (this.Messaging.EncounteredError) |
| 535 | { | 527 | { |
| 536 | return; | 528 | return null; |
| 537 | } | 529 | } |
| 538 | 530 | ||
| 539 | #if TODO_FINISH_VALIDATION | 531 | #if TODO_FINISH_VALIDATION |
| @@ -580,10 +572,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind | |||
| 580 | trackedFiles.AddRange(fileFacades.Select(f => this.BackendHelper.TrackFile(f.SourcePath, TrackedFileType.Input, f.SourceLineNumber))); | 572 | trackedFiles.AddRange(fileFacades.Select(f => this.BackendHelper.TrackFile(f.SourcePath, TrackedFileType.Input, f.SourceLineNumber))); |
| 581 | } | 573 | } |
| 582 | 574 | ||
| 583 | this.Wixout = this.CreateWixout(trackedFiles, this.Intermediate, output); | 575 | var result = this.ServiceProvider.GetService<IBindResult>(); |
| 576 | result.FileTransfers = fileTransfers; | ||
| 577 | result.TrackedFiles = trackedFiles; | ||
| 578 | result.Wixout = this.CreateWixout(trackedFiles, this.Intermediate, output); | ||
| 584 | 579 | ||
| 585 | this.FileTransfers = fileTransfers; | 580 | return result; |
| 586 | this.TrackedFiles = trackedFiles; | ||
| 587 | } | 581 | } |
| 588 | 582 | ||
| 589 | private WixOutput CreateWixout(List<ITrackedFile> trackedFiles, Intermediate intermediate, WindowsInstallerData output) | 583 | private WixOutput CreateWixout(List<ITrackedFile> trackedFiles, Intermediate intermediate, WindowsInstallerData output) |
| @@ -984,24 +978,5 @@ namespace WixToolset.Core.WindowsInstaller.Bind | |||
| 984 | 978 | ||
| 985 | return command.GeneratedTemporaryFiles; | 979 | return command.GeneratedTemporaryFiles; |
| 986 | } | 980 | } |
| 987 | |||
| 988 | #region IDisposable Support | ||
| 989 | |||
| 990 | public void Dispose() => this.Dispose(true); | ||
| 991 | |||
| 992 | protected virtual void Dispose(bool disposing) | ||
| 993 | { | ||
| 994 | if (!this.disposed) | ||
| 995 | { | ||
| 996 | if (disposing) | ||
| 997 | { | ||
| 998 | this.Wixout?.Dispose(); | ||
| 999 | } | ||
| 1000 | |||
| 1001 | this.disposed = true; | ||
| 1002 | } | ||
| 1003 | } | ||
| 1004 | |||
| 1005 | #endregion | ||
| 1006 | } | 981 | } |
| 1007 | } | 982 | } |
diff --git a/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs b/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs index 86c0fbfe..778720b9 100644 --- a/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs +++ b/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs | |||
| @@ -25,22 +25,24 @@ namespace WixToolset.Core.WindowsInstaller | |||
| 25 | 25 | ||
| 26 | var validator = Validator.CreateFromContext(context, "darice.cub"); | 26 | var validator = Validator.CreateFromContext(context, "darice.cub"); |
| 27 | 27 | ||
| 28 | using (var command = new BindDatabaseCommand(context, backendExtensions, validator)) | 28 | IBindResult result = null; |
| 29 | try | ||
| 29 | { | 30 | { |
| 30 | command.Execute(); | 31 | var command = new BindDatabaseCommand(context, backendExtensions, validator); |
| 31 | 32 | result = command.Execute(); | |
| 32 | var result = context.ServiceProvider.GetService<IBindResult>(); | ||
| 33 | result.FileTransfers = command.FileTransfers; | ||
| 34 | result.TrackedFiles = command.TrackedFiles; | ||
| 35 | result.Wixout = command.Wixout; | ||
| 36 | 33 | ||
| 37 | foreach (var extension in backendExtensions) | 34 | foreach (var extension in backendExtensions) |
| 38 | { | 35 | { |
| 39 | extension.PostBackendBind(result, command.Wixout); | 36 | extension.PostBackendBind(result); |
| 40 | } | 37 | } |
| 41 | 38 | ||
| 42 | return result; | 39 | return result; |
| 43 | } | 40 | } |
| 41 | catch | ||
| 42 | { | ||
| 43 | result?.Dispose(); | ||
| 44 | throw; | ||
| 45 | } | ||
| 44 | } | 46 | } |
| 45 | 47 | ||
| 46 | public IDecompileResult Decompile(IDecompileContext context) | 48 | public IDecompileResult Decompile(IDecompileContext context) |
diff --git a/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs b/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs index f048b4e2..4860bf81 100644 --- a/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs +++ b/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs | |||
| @@ -24,21 +24,24 @@ namespace WixToolset.Core.WindowsInstaller | |||
| 24 | 24 | ||
| 25 | var validator = Validator.CreateFromContext(context, "mergemod.cub"); | 25 | var validator = Validator.CreateFromContext(context, "mergemod.cub"); |
| 26 | 26 | ||
| 27 | using (var command = new BindDatabaseCommand(context, backendExtensions, validator)) | 27 | IBindResult result = null; |
| 28 | try | ||
| 28 | { | 29 | { |
| 29 | command.Execute(); | 30 | var command = new BindDatabaseCommand(context, backendExtensions, validator); |
| 30 | 31 | result = command.Execute(); | |
| 31 | var result = context.ServiceProvider.GetService<IBindResult>(); | ||
| 32 | result.FileTransfers = command.FileTransfers; | ||
| 33 | result.TrackedFiles = command.TrackedFiles; | ||
| 34 | 32 | ||
| 35 | foreach (var extension in backendExtensions) | 33 | foreach (var extension in backendExtensions) |
| 36 | { | 34 | { |
| 37 | extension.PostBackendBind(result, command.Wixout); | 35 | extension.PostBackendBind(result); |
| 38 | } | 36 | } |
| 39 | 37 | ||
| 40 | return result; | 38 | return result; |
| 41 | } | 39 | } |
| 40 | catch | ||
| 41 | { | ||
| 42 | result?.Dispose(); | ||
| 43 | throw; | ||
| 44 | } | ||
| 42 | } | 45 | } |
| 43 | 46 | ||
| 44 | public IDecompileResult Decompile(IDecompileContext context) | 47 | public IDecompileResult Decompile(IDecompileContext context) |
diff --git a/src/WixToolset.Core.WindowsInstaller/MspBackend.cs b/src/WixToolset.Core.WindowsInstaller/MspBackend.cs index 8aa450bf..5dc64445 100644 --- a/src/WixToolset.Core.WindowsInstaller/MspBackend.cs +++ b/src/WixToolset.Core.WindowsInstaller/MspBackend.cs | |||
| @@ -47,21 +47,24 @@ namespace WixToolset.Core.WindowsInstaller | |||
| 47 | 47 | ||
| 48 | // Create WindowsInstallerData with patch metdata and transforms as sub-storages | 48 | // Create WindowsInstallerData with patch metdata and transforms as sub-storages |
| 49 | // Create MSP from WindowsInstallerData | 49 | // Create MSP from WindowsInstallerData |
| 50 | using (var command = new BindDatabaseCommand(context, backendExtensions, subStorages, null)) | 50 | IBindResult result = null; |
| 51 | try | ||
| 51 | { | 52 | { |
| 52 | command.Execute(); | 53 | var command = new BindDatabaseCommand(context, backendExtensions, subStorages, null); |
| 53 | 54 | result = command.Execute(); | |
| 54 | var result = context.ServiceProvider.GetService<IBindResult>(); | ||
| 55 | result.FileTransfers = command.FileTransfers; | ||
| 56 | result.TrackedFiles = command.TrackedFiles; | ||
| 57 | 55 | ||
| 58 | foreach (var extension in backendExtensions) | 56 | foreach (var extension in backendExtensions) |
| 59 | { | 57 | { |
| 60 | extension.PostBackendBind(result, command.Wixout); | 58 | extension.PostBackendBind(result); |
| 61 | } | 59 | } |
| 62 | 60 | ||
| 63 | return result; | 61 | return result; |
| 64 | } | 62 | } |
| 63 | catch | ||
| 64 | { | ||
| 65 | result?.Dispose(); | ||
| 66 | throw; | ||
| 67 | } | ||
| 65 | } | 68 | } |
| 66 | 69 | ||
| 67 | public IDecompileResult Decompile(IDecompileContext context) => throw new NotImplementedException(); | 70 | public IDecompileResult Decompile(IDecompileContext context) => throw new NotImplementedException(); |
diff --git a/src/WixToolset.Core/BindResult.cs b/src/WixToolset.Core/BindResult.cs index c711b540..4edade7a 100644 --- a/src/WixToolset.Core/BindResult.cs +++ b/src/WixToolset.Core/BindResult.cs | |||
| @@ -2,16 +2,47 @@ | |||
| 2 | 2 | ||
| 3 | namespace WixToolset.Core | 3 | namespace WixToolset.Core |
| 4 | { | 4 | { |
| 5 | using System; | ||
| 5 | using System.Collections.Generic; | 6 | using System.Collections.Generic; |
| 6 | using WixToolset.Data; | 7 | using WixToolset.Data; |
| 7 | using WixToolset.Extensibility.Data; | 8 | using WixToolset.Extensibility.Data; |
| 8 | 9 | ||
| 9 | internal class BindResult : IBindResult | 10 | internal class BindResult : IBindResult |
| 10 | { | 11 | { |
| 12 | private bool disposed; | ||
| 13 | |||
| 11 | public IEnumerable<IFileTransfer> FileTransfers { get; set; } | 14 | public IEnumerable<IFileTransfer> FileTransfers { get; set; } |
| 12 | 15 | ||
| 13 | public IEnumerable<ITrackedFile> TrackedFiles { get; set; } | 16 | public IEnumerable<ITrackedFile> TrackedFiles { get; set; } |
| 14 | 17 | ||
| 15 | public WixOutput Wixout { get; set; } | 18 | public WixOutput Wixout { get; set; } |
| 19 | |||
| 20 | #region IDisposable Support | ||
| 21 | /// <summary> | ||
| 22 | /// Disposes of the internal state of the file structure. | ||
| 23 | /// </summary> | ||
| 24 | public void Dispose() | ||
| 25 | { | ||
| 26 | this.Dispose(true); | ||
| 27 | GC.SuppressFinalize(this); | ||
| 28 | } | ||
| 29 | |||
| 30 | /// <summary> | ||
| 31 | /// Disposes of the internsl state of the file structure. | ||
| 32 | /// </summary> | ||
| 33 | /// <param name="disposing">True if disposing.</param> | ||
| 34 | protected virtual void Dispose(bool disposing) | ||
| 35 | { | ||
| 36 | if (!this.disposed) | ||
| 37 | { | ||
| 38 | if (disposing) | ||
| 39 | { | ||
| 40 | this.Wixout?.Dispose(); | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | this.disposed = true; | ||
| 45 | } | ||
| 46 | #endregion | ||
| 16 | } | 47 | } |
| 17 | } | 48 | } |
diff --git a/src/WixToolset.Core/CommandLine/BuildCommand.cs b/src/WixToolset.Core/CommandLine/BuildCommand.cs index 0868a3b6..a55fb55b 100644 --- a/src/WixToolset.Core/CommandLine/BuildCommand.cs +++ b/src/WixToolset.Core/CommandLine/BuildCommand.cs | |||
| @@ -294,47 +294,54 @@ namespace WixToolset.Core.CommandLine | |||
| 294 | return; | 294 | return; |
| 295 | } | 295 | } |
| 296 | 296 | ||
| 297 | IBindResult bindResult; | 297 | IBindResult bindResult = null; |
| 298 | try | ||
| 298 | { | 299 | { |
| 299 | var context = this.ServiceProvider.GetService<IBindContext>(); | 300 | { |
| 300 | //context.CabbingThreadCount = this.CabbingThreadCount; | 301 | var context = this.ServiceProvider.GetService<IBindContext>(); |
| 301 | context.BurnStubPath = burnStubPath; | 302 | //context.CabbingThreadCount = this.CabbingThreadCount; |
| 302 | context.CabCachePath = cabCachePath; | 303 | context.BurnStubPath = burnStubPath; |
| 303 | context.Codepage = resolveResult.Codepage; | 304 | context.CabCachePath = cabCachePath; |
| 304 | //context.DefaultCompressionLevel = this.DefaultCompressionLevel; | 305 | context.Codepage = resolveResult.Codepage; |
| 305 | context.DelayedFields = resolveResult.DelayedFields; | 306 | //context.DefaultCompressionLevel = this.DefaultCompressionLevel; |
| 306 | context.ExpectedEmbeddedFiles = resolveResult.ExpectedEmbeddedFiles; | 307 | context.DelayedFields = resolveResult.DelayedFields; |
| 307 | context.Extensions = this.ExtensionManager.GetServices<IBinderExtension>(); | 308 | context.ExpectedEmbeddedFiles = resolveResult.ExpectedEmbeddedFiles; |
| 308 | context.Ices = Array.Empty<string>(); // TODO: set this correctly | 309 | context.Extensions = this.ExtensionManager.GetServices<IBinderExtension>(); |
| 309 | context.IntermediateFolder = intermediateFolder; | 310 | context.Ices = Array.Empty<string>(); // TODO: set this correctly |
| 310 | context.IntermediateRepresentation = resolveResult.IntermediateRepresentation; | 311 | context.IntermediateFolder = intermediateFolder; |
| 311 | context.OutputPath = this.OutputFile; | 312 | context.IntermediateRepresentation = resolveResult.IntermediateRepresentation; |
| 312 | context.OutputPdbPath = Path.ChangeExtension(this.OutputFile, ".wixpdb"); | 313 | context.OutputPath = this.OutputFile; |
| 313 | context.SuppressIces = Array.Empty<string>(); // TODO: set this correctly | 314 | context.OutputPdbPath = Path.ChangeExtension(this.OutputFile, ".wixpdb"); |
| 314 | context.SuppressValidation = true; // TODO: set this correctly | 315 | context.SuppressIces = Array.Empty<string>(); // TODO: set this correctly |
| 315 | 316 | context.SuppressValidation = true; // TODO: set this correctly | |
| 316 | var binder = this.ServiceProvider.GetService<IBinder>(); | 317 | |
| 317 | bindResult = binder.Bind(context); | 318 | var binder = this.ServiceProvider.GetService<IBinder>(); |
| 318 | } | 319 | bindResult = binder.Bind(context); |
| 320 | } | ||
| 319 | 321 | ||
| 320 | if (this.Messaging.EncounteredError) | 322 | if (this.Messaging.EncounteredError) |
| 321 | { | 323 | { |
| 322 | return; | 324 | return; |
| 323 | } | 325 | } |
| 324 | 326 | ||
| 327 | { | ||
| 328 | var context = this.ServiceProvider.GetService<ILayoutContext>(); | ||
| 329 | context.Extensions = this.ExtensionManager.GetServices<ILayoutExtension>(); | ||
| 330 | context.TrackedFiles = bindResult.TrackedFiles; | ||
| 331 | context.FileTransfers = bindResult.FileTransfers; | ||
| 332 | context.IntermediateFolder = intermediateFolder; | ||
| 333 | context.ContentsFile = this.ContentsFile; | ||
| 334 | context.OutputsFile = this.OutputsFile; | ||
| 335 | context.BuiltOutputsFile = this.BuiltOutputsFile; | ||
| 336 | context.SuppressAclReset = false; // TODO: correctly set SuppressAclReset | ||
| 337 | |||
| 338 | var layout = this.ServiceProvider.GetService<ILayoutCreator>(); | ||
| 339 | layout.Layout(context); | ||
| 340 | } | ||
| 341 | } | ||
| 342 | finally | ||
| 325 | { | 343 | { |
| 326 | var context = this.ServiceProvider.GetService<ILayoutContext>(); | 344 | bindResult?.Dispose(); |
| 327 | context.Extensions = this.ExtensionManager.GetServices<ILayoutExtension>(); | ||
| 328 | context.TrackedFiles = bindResult.TrackedFiles; | ||
| 329 | context.FileTransfers = bindResult.FileTransfers; | ||
| 330 | context.IntermediateFolder = intermediateFolder; | ||
| 331 | context.ContentsFile = this.ContentsFile; | ||
| 332 | context.OutputsFile = this.OutputsFile; | ||
| 333 | context.BuiltOutputsFile = this.BuiltOutputsFile; | ||
| 334 | context.SuppressAclReset = false; // TODO: correctly set SuppressAclReset | ||
| 335 | |||
| 336 | var layout = this.ServiceProvider.GetService<ILayoutCreator>(); | ||
| 337 | layout.Layout(context); | ||
| 338 | } | 345 | } |
| 339 | } | 346 | } |
| 340 | 347 | ||
