<commit_before>﻿using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Smidge.CompositeFiles;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using System.Runtime.CompilerServices;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.FileProviders;
using Smidge.Models;
using Microsoft.Extensions.Options;
using Smidge.Options;
using Smidge.FileProcessors;
using Smidge.Hashing;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Smidge.Cache;

[assembly: InternalsVisibleTo("Smidge.Tests")]

namespace Smidge
{
    public static class SmidgeStartup
    {
        public static IServiceCollection AddSmidge(this IServiceCollection services, 
            IConfiguration smidgeConfiguration = null, 
            IFileProvider fileProvider = null)
        {            
            services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
            services.TryAddSingleton<IActionContextAccessor, ActionContextAccessor>();
            
            services.AddTransient<IConfigureOptions<SmidgeOptions>, SmidgeOptionsSetup>();

            services.AddSingleton<PreProcessManager>();
            services.AddSingleton<IRequestHelper, RequestHelper>();
            services.AddSingleton<IWebsiteInfo, AutoWebsiteInfo>();
            services.AddSingleton<IBundleFileSetGenerator, BundleFileSetGenerator>();
            services.AddSingleton<IHasher, Crc32Hasher>();
            services.AddSingleton<IBundleManager, BundleManager>();
            services.AddSingleton<PreProcessPipelineFactory>();
            services.AddSingleton<FileSystemHelper>(p =>
            {
                var hosting = p.GetRequiredService<IHostingEnvironment>();
                var provider = fileProvider ?? hosting.WebRootFileProvider;
                return new FileSystemHelper(hosting, p.GetRequiredService<ISmidgeConfig>(), provider, p.GetRequiredService<IHasher>());
            });            
            services.AddSingleton<ISmidgeConfig>((p) =>
            {
                if (smidgeConfiguration == null)
                {
                    return new SmidgeConfig(p.GetRequiredService<IHostingEnvironment>());
                }
                return new SmidgeConfig(smidgeConfiguration);
            });
            
            services.AddSingleton<ICacheBuster, ConfigCacheBuster>();
            services.AddSingleton<ICacheBuster, AppDomainLifetimeCacheBuster>();
            services.AddSingleton<CacheBusterResolver>();

            //These all execute as part of the request/scope            
            services.AddScoped<DynamicallyRegisteredWebFiles>();
            services.AddScoped<SmidgeHelper>();
            services.AddScoped<IUrlManager, DefaultUrlManager>();            

            //pre-processors
            services.AddSingleton<IPreProcessor, JsMinifier>();
            services.AddSingleton<IPreProcessor, CssMinifier>();            
            services.AddSingleton<IPreProcessor, CssImportProcessor>();
            services.AddSingleton<IPreProcessor, CssUrlProcessor>();
            services.AddSingleton<Lazy<IEnumerable<IPreProcessor>>>(provider => new Lazy<IEnumerable<IPreProcessor>>(provider.GetRequiredService<IEnumerable<IPreProcessor>>));

            //conventions
            services.AddSingleton<FileProcessingConventions>();
            services.AddSingleton<IFileProcessingConvention, MinifiedFilePathConvention>();

            //Add the controller models as DI services - these get auto created for model binding
            services.AddTransient<BundleRequestModel>();
            services.AddTransient<CompositeFileModel>();

            return services;
        }
        
        public static void UseSmidge(this IApplicationBuilder app, Action<IBundleManager> configureBundles = null)
        {
#if NETCORE3_0
            //It's no longer polite to call UseMVC as it enables things that the developer may not need.
            //caveat here is that if a developer disables endpointrouting in their call to 
            //services.AddMvc or services.AddControllersWithViews then this will fail.
            //There doesn't appear to be an easy way to handle both endpointrouting enabled and disabled

            app.UseEndpoints(endpoints =>
            {
                var options = app.ApplicationServices.GetRequiredService<IOptions<SmidgeOptions>>();
                endpoints.MapControllerRoute("SmidgeComposite", options.Value.UrlOptions.CompositeFilePath + "/{file}", "{controller=Smidge}/{action=Composite}");
                endpoints.MapControllerRoute("SmidgeBundle", options.Value.UrlOptions.BundleFilePath + "/{bundle}", "{controller=Smidge}/{action=Bundle}");
            });
#else
            //Create custom route
            app.UseMvc(routes =>
            {
                var options = app.ApplicationServices.GetRequiredService<IOptions<SmidgeOptions>>();

                routes.MapRoute(
                    "SmidgeComposite",
                    options.Value.UrlOptions.CompositeFilePath + "/{file}",                    
                    new { controller = "Smidge", action = "Composite" });

                routes.MapRoute(
                    "SmidgeBundle",
                    options.Value.UrlOptions.BundleFilePath + "/{bundle}",
                    new { controller = "Smidge", action = "Bundle" });
            });
#endif
            if (configureBundles != null)
            {
                var bundleManager = app.ApplicationServices.GetRequiredService<IBundleManager>();
                configureBundles(bundleManager);

                var cacheBusterResolver = app.ApplicationServices.GetRequiredService<CacheBusterResolver>();

                //TODO: Now that they are configured we need to wire up the file watching event handlers
                // to the bundle manager, currently these are on the Bundle, but that is not good enough
                // since we need the bundle name
                foreach (var webFileType in new[] {WebFileType.Css, WebFileType.Js })
                {
                    var names = bundleManager.GetBundleNames(webFileType);
                    foreach (var cssName in names)
                    {
                        var bundle = bundleManager.GetBundle(cssName);
                        WireUpFileWatchEventHandlers(bundleManager, cacheBusterResolver, cssName, bundle);
                    }
                }
            }    

        }

        private static void WireUpFileWatchEventHandlers(IBundleManager bundleManager, CacheBusterResolver cacheBusterResolver, string bundleName, Bundle bundle)
        {
            if (bundle.BundleOptions == null) return;

            if (bundle.BundleOptions.DebugOptions.FileWatchOptions.Enabled)
            {
                bundle.BundleOptions.DebugOptions.FileWatchOptions.FileModified += FileWatchOptions_FileModified(bundleManager, cacheBusterResolver, bundleName, bundle, true);
            }
            if (bundle.BundleOptions.ProductionOptions.FileWatchOptions.Enabled)
            {
                bundle.BundleOptions.ProductionOptions.FileWatchOptions.FileModified += FileWatchOptions_FileModified(bundleManager, cacheBusterResolver, bundleName, bundle, false);
            }
        }

        private static EventHandler<FileWatchEventArgs> FileWatchOptions_FileModified(IBundleManager bundleManager, CacheBusterResolver cacheBusterResolver, string bundleName, Bundle bundle, bool debug)
        {
            return (sender, args) =>
            {
                FileWatchOptions_FileModified(bundleManager, cacheBusterResolver, bundleName, bundle, debug, args);
            };<commit_msg>Works with .net core 3.0 preview 5<commit_after>﻿using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Options;
using Smidge.Cache;
using Smidge.CompositeFiles;
using Smidge.FileProcessors;
using Smidge.Hashing;
using Smidge.Models;
using Smidge.Options;
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("Smidge.Tests")]

namespace Smidge
{
    public static class SmidgeStartup
    {
        //For .net core 3.0, call this before AddControllers/AddControllersWithViews etc
        //If you call it after, then if AddControllersAsServices was used before you need
        //to tell smidge to do so

#if NETCORE3_0
        public static IServiceCollection AddSmidge(this IServiceCollection services,
            IConfiguration smidgeConfiguration = null,
            IFileProvider fileProvider = null,
            bool regControllersAsService = false)
#else

        public static IServiceCollection AddSmidge(this IServiceCollection services,
            IConfiguration smidgeConfiguration = null,
            IFileProvider fileProvider = null)
#endif
        {
            services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
            services.TryAddSingleton<IActionContextAccessor, ActionContextAccessor>();

            services.AddTransient<IConfigureOptions<SmidgeOptions>, SmidgeOptionsSetup>();

            services.AddSingleton<PreProcessManager>();
            services.AddSingleton<IRequestHelper, RequestHelper>();
            services.AddSingleton<IWebsiteInfo, AutoWebsiteInfo>();
            services.AddSingleton<IBundleFileSetGenerator, BundleFileSetGenerator>();
            services.AddSingleton<IHasher, Crc32Hasher>();
            services.AddSingleton<IBundleManager, BundleManager>();
            services.AddSingleton<PreProcessPipelineFactory>();
            services.AddSingleton<FileSystemHelper>(p =>
            {
                var hosting = p.GetRequiredService<IHostingEnvironment>();
                var provider = fileProvider ?? hosting.WebRootFileProvider;
                return new FileSystemHelper(hosting, p.GetRequiredService<ISmidgeConfig>(), provider, p.GetRequiredService<IHasher>());
            });
            services.AddSingleton<ISmidgeConfig>((p) =>
            {
                if (smidgeConfiguration == null)
                {
                    return new SmidgeConfig(p.GetRequiredService<IHostingEnvironment>());
                }
                return new SmidgeConfig(smidgeConfiguration);
            });

            services.AddSingleton<ICacheBuster, ConfigCacheBuster>();
            services.AddSingleton<ICacheBuster, AppDomainLifetimeCacheBuster>();
            services.AddSingleton<CacheBusterResolver>();

            //These all execute as part of the request/scope            
            services.AddScoped<DynamicallyRegisteredWebFiles>();
            services.AddScoped<SmidgeHelper>();
            services.AddScoped<IUrlManager, DefaultUrlManager>();

            //pre-processors
            services.AddSingleton<IPreProcessor, JsMinifier>();
            services.AddSingleton<IPreProcessor, CssMinifier>();
            services.AddSingleton<IPreProcessor, CssImportProcessor>();
            services.AddSingleton<IPreProcessor, CssUrlProcessor>();
            services.AddSingleton<Lazy<IEnumerable<IPreProcessor>>>(provider => new Lazy<IEnumerable<IPreProcessor>>(provider.GetRequiredService<IEnumerable<IPreProcessor>>));

            //conventions
            services.AddSingleton<FileProcessingConventions>();
            services.AddSingleton<IFileProcessingConvention, MinifiedFilePathConvention>();

            //Add the controller models as DI services - these get auto created for model binding
            services.AddTransient<BundleRequestModel>();
            services.AddTransient<CompositeFileModel>();

#if NETCORE3_0
            //tell .net core 3.0 where to find our controller
            var assembly = typeof(SmidgeStartup).Assembly;

            var builder = services.AddControllers()
                    .AddApplicationPart(assembly);

            //if AddControllersAsServices has already been then you must do this 
            //Since there is no way to detect this, we allow the caller to tell us.
            if (regControllersAsService)
                builder.AddControllersAsServices(); 
#endif
            return services;
        }



#if NETCORE3_0
        public static void UseSmidge(this IApplicationBuilder app, Action<IBundleManager> configureBundles = null, bool useEndpointRouting = true)
#else
        public static void UseSmidge(this IApplicationBuilder app, Action<IBundleManager> configureBundles = null)
#endif
        {
            //Creates custom routes 
            var options = app.ApplicationServices.GetRequiredService<IOptions<SmidgeOptions>>();
#if NETCORE3_0
            //NOTE: It's no longer polite to just call UseMVC as it enables things that the developer may 
            //not need and the dev must disable EndpointRouting - so we let the dev decide.
            //with core 3.0 you have to explicitly disable EndpointRouting se we default to on here 
            if (useEndpointRouting)
            {
                app.UseEndpoints(endpoints =>
                {
                    endpoints.MapControllerRoute(
                            name: "SmidgeComposite",
                            pattern: options.Value.UrlOptions.CompositeFilePath + "/{file}",
                            defaults: new { controller = "Smidge", action = "Composite" });
                    endpoints.MapControllerRoute(
                            name: "SmidgeBundle",
                            pattern: options.Value.UrlOptions.BundleFilePath + "/{bundle}",
                            defaults: new { controller = "Smidge", action = "Bundle" });
                });

            }
            else
            {
#endif
                app.UseMvc(routes =>
                {
                    routes.MapRoute(
                        "SmidgeComposite",
                        options.Value.UrlOptions.CompositeFilePath + "/{file}",
                        new { controller = "Smidge", action = "Composite" });

                    routes.MapRoute(
                        "SmidgeBundle",
                        options.Value.UrlOptions.BundleFilePath + "/{bundle}",
                        new { controller = "Smidge", action = "Bundle" });
                });
#if NETCORE3_0
            }
#endif
            if (configureBundles != null)
            {
                var bundleManager = app.ApplicationServices.GetRequiredService<IBundleManager>();
                configureBundles(bundleManager);

                var cacheBusterResolver = app.ApplicationServices.GetRequiredService<CacheBusterResolver>();

                //TODO: Now that they are configured we need to wire up the file watching event handlers
                // to the bundle manager, currently these are on the Bundle, but that is not good enough
                // since we need the bundle name
                foreach (var webFileType in new[] {WebFileType.Css, WebFileType.Js })
                {
                    var names = bundleManager.GetBundleNames(webFileType);
                    foreach (var cssName in names)
                    {
                        var bundle = bundleManager.GetBundle(cssName);
                        WireUpFileWatchEventHandlers(bundleManager, cacheBusterResolver, cssName, bundle);
                    }
                }
            }    
        }

        private static void WireUpFileWatchEventHandlers(IBundleManager bundleManager, CacheBusterResolver cacheBusterResolver, string bundleName, Bundle bundle)
        {
            if (bundle.BundleOptions == null) return;

            if (bundle.BundleOptions.DebugOptions.FileWatchOptions.Enabled)
            {
                bundle.BundleOptions.DebugOptions.FileWatchOptions.FileModified += FileWatchOptions_FileModified(bundleManager, cacheBusterResolver, bundleName, bundle, true);
            }
            if (bundle.BundleOptions.ProductionOptions.FileWatchOptions.Enabled)
            {
                bundle.BundleOptions.ProductionOptions.FileWatchOptions.FileModified += FileWatchOptions_FileModified(bundleManager, cacheBusterResolver, bundleName, bundle, false);
            }
        }

        private static EventHandler<FileWatchEventArgs> FileWatchOptions_FileModified(IBundleManager bundleManager, CacheBusterResolver cacheBusterResolver, string bundleName, Bundle bundle, bool debug)
        {
            return (sender, args) =>
            {
                FileWatchOptions_FileModified(bundleManager, cacheBusterResolver, bundleName, bundle, debug, args);
            };
{'fraction_non_alphanumeric': 0.08032246675297164, 'fraction_numerical': 0.0014711074496881252, 'mean_word_length': 2.3901855176540994, 'pattern_counts': {'":': 0, '<': 94, '<?xml version=': 0, '>': 106, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '213566', 'n_tokens_mistral': 4030, 'n_tokens_neox': 3905, 'n_words': 882}