using System; using System.Threading; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Runtime; using Umbraco.Cms.Infrastructure.ModelsBuilder.Building; using Umbraco.Extensions; using Umbraco.Cms.Core.Configuration; namespace Umbraco.Cms.Infrastructure.ModelsBuilder { /// /// Notification handlers used by . /// /// /// supports mode but not mode. /// public sealed class AutoModelsNotificationHandler : INotificationHandler, INotificationHandler, INotificationHandler, INotificationHandler { private static int s_req; private readonly ILogger _logger; private readonly ModelsBuilderSettings _config; private readonly ModelsGenerator _modelGenerator; private readonly ModelsGenerationError _mbErrors; private readonly IMainDom _mainDom; /// /// Initializes a new instance of the class. /// public AutoModelsNotificationHandler( ILogger logger, IOptions config, ModelsGenerator modelGenerator, ModelsGenerationError mbErrors, IMainDom mainDom) { _logger = logger; _config = config.Value ?? throw new ArgumentNullException(nameof(config)); _modelGenerator = modelGenerator; _mbErrors = mbErrors; _mainDom = mainDom; } // we do not manage InMemory models here internal bool IsEnabled => _config.ModelsMode.IsAutoNotInMemory(); /// /// Handles the notification /// public void Handle(UmbracoApplicationStartingNotification notification) => Install(); private void Install() { // don't run if not enabled if (!IsEnabled) { return; } } // NOTE // CacheUpdated triggers within some asynchronous backend task where // we have no HttpContext. So we use a static (non request-bound) // var to register that models // need to be generated. Could be by another request. Anyway. We could // have collisions but... you know the risk. private void RequestModelsGeneration() { if (!_mainDom.IsMainDom) { return; } _logger.LogDebug("Requested to generate models."); Interlocked.Exchange(ref s_req, 1); } private void GenerateModelsIfRequested() { if (Interlocked.Exchange(ref s_req, 0) == 0) { return; } // cannot proceed unless we are MainDom if (_mainDom.IsMainDom) { try { _logger.LogDebug("Generate models..."); _logger.LogInformation("Generate models now."); _modelGenerator.GenerateModels(); _mbErrors.Clear(); _logger.LogInformation("Generated."); } catch (TimeoutException) { _logger.LogWarning("Timeout, models were NOT generated."); } catch (Exception e) { _mbErrors.Report("Failed to build Live models.", e); _logger.LogError("Failed to generate models.", e); } } else { // this will only occur if this appdomain was MainDom and it has // been released while trying to regenerate models. _logger.LogWarning("Cannot generate models while app is shutting down"); } } public void Handle(UmbracoRequestEndNotification notification) { if (IsEnabled && _mainDom.IsMainDom) { GenerateModelsIfRequested(); } } public void Handle(ContentTypeCacheRefresherNotification notification) => RequestModelsGeneration(); public void Handle(DataTypeCacheRefresherNotification notification) => RequestModelsGeneration(); } }