Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 20 additions & 95 deletions ASGE.csproj
Original file line number Diff line number Diff line change
@@ -1,95 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{84285BB3-2556-45F3-965F-4667855A6EB6}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ASGE</RootNamespace>
<AssemblyName>ASGE</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="CommandLine, Version=1.9.71.2, Culture=neutral, PublicKeyToken=de6f01bd326f8c32, processorArchitecture=MSIL">
<HintPath>packages\CommandLineParser.1.9.71\lib\net45\CommandLine.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Azure.KeyVault.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>packages\Microsoft.Azure.KeyVault.Core.1.0.0\lib\net40\Microsoft.Azure.KeyVault.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Data.Edm, Version=5.6.4.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>packages\Microsoft.Data.Edm.5.6.4\lib\net40\Microsoft.Data.Edm.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Data.OData, Version=5.6.4.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>packages\Microsoft.Data.OData.5.6.4\lib\net40\Microsoft.Data.OData.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Data.Services.Client, Version=5.6.4.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>packages\Microsoft.Data.Services.Client.5.6.4\lib\net40\Microsoft.Data.Services.Client.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.WindowsAzure.Storage, Version=7.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>packages\WindowsAzure.Storage.7.0.0\lib\net40\Microsoft.WindowsAzure.Storage.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Spatial, Version=5.6.4.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>packages\System.Spatial.5.6.4\lib\net40\System.Spatial.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Options.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Utility.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.7.82" />
<PackageReference Include="Microsoft.Azure.KeyVault.Core" Version="3.0.4" />
<PackageReference Include="Microsoft.Data.Edm" Version="5.8.4" />
<PackageReference Include="Microsoft.Data.Services.Client" Version="5.8.4" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Serilog" Version="2.9.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="System.Spatial" Version="5.8.4" />
<PackageReference Include="WindowsAzure.Storage" Version="9.3.3" />
</ItemGroup>

</Project>
17 changes: 10 additions & 7 deletions ASGE.sln
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25123.0
# Visual Studio Version 16
VisualStudioVersion = 16.0.29613.14
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ASGE", "ASGE.csproj", "{84285BB3-2556-45F3-965F-4667855A6EB6}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ASGE", "ASGE.csproj", "{729E9638-9F5D-459B-8A3C-48218587C394}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{84285BB3-2556-45F3-965F-4667855A6EB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{84285BB3-2556-45F3-965F-4667855A6EB6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{84285BB3-2556-45F3-965F-4667855A6EB6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{84285BB3-2556-45F3-965F-4667855A6EB6}.Release|Any CPU.Build.0 = Release|Any CPU
{729E9638-9F5D-459B-8A3C-48218587C394}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{729E9638-9F5D-459B-8A3C-48218587C394}.Debug|Any CPU.Build.0 = Debug|Any CPU
{729E9638-9F5D-459B-8A3C-48218587C394}.Release|Any CPU.ActiveCfg = Release|Any CPU
{729E9638-9F5D-459B-8A3C-48218587C394}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1E19E560-5C8D-423E-A1F1-853F39E51721}
EndGlobalSection
EndGlobal
26 changes: 26 additions & 0 deletions EnumerableExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ASGE
{
public static class EnumerableExtensions
{
/// <summary>
/// From https://devblogs.microsoft.com/pfxteam/implementing-a-simple-foreachasync-part-2/.
/// </summary>
public static Task ForEachAsync<T>(this IEnumerable<T> source, Func<T, Task> body, int dop = 4)
{
return Task.WhenAll(
from partition in Partitioner.Create(source).GetPartitions(dop)
select Task.Run(async delegate
{
using (partition)
while (partition.MoveNext())
await body(partition.Current);
}));
}
}
}
47 changes: 19 additions & 28 deletions Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,27 @@ namespace ASGE
{
class Options
{
[Option('a', "account", Required = false, MutuallyExclusiveSet = "Account and Key",
[Option('a', "account", Required = false, SetName = "Account and Key",
HelpText = "Storage account host. [mystorage]")]
public string StorageAccount { get; set; }

[Option('k', "key", Required = false, MutuallyExclusiveSet = "Account and Key",
[Option('k', "key", Required = false, SetName = "Account and Key",
HelpText = "Storage account key.")]
public string StorageKey { get; set; }

[Option('c', "connectionstring", Required = false, MutuallyExclusiveSet = "ConnectionString",
[Option('c', "connectionstring", Required = false, SetName = "ConnectionString",
HelpText = "Storage account connection string.")]
public string ConnectionString { get; set; }

[OptionArray('e', "extensions", Required = true,
HelpText = "Extensions to operate on. [.js, .css, .dat]")]
public string[] Extensions { get; set; }
[Option('i', "include", Required = true,
HelpText = "Regular expressions to match files to operate on. [*\\.js, \\.css, \\.dat]")]
public IEnumerable<string> Include { get; set; }

[Option('r', "replace", Required = false, DefaultValue = false,
[Option('r', "replace", Required = false, Default = false,
HelpText = "Replace existing files in-place.")]
public bool Replace { get; set; }

[Option('s', "simulate", Required = false, DefaultValue = false,
[Option('s', "simulate", Required = false, Default = false,
HelpText = "Do everything except write to blob store.")]
public bool Simulate { get; set; }

Expand All @@ -42,32 +42,23 @@ class Options
HelpText = "Container to search in.")]
public string Container { get; set; }

[Option('x', "cacheage", Required = false, DefaultValue = 2592000,
HelpText = "Duration for cache control max age header, in seconds. Default 2592000 (30 days).")]
public int MaxAgeSeconds { get; set; }
[Option('h', "cachecontrol", Required = false, Default = null,
HelpText = "Cache-Control header to be sent for the resource from the server.")]
public string CacheControlHeader { get; set; }

[Option('w', "wildcardcors", Required = false, DefaultValue = false,
[Option('w', "wildcardcors", Required = false, Default = false,
HelpText = "Enable wildcard CORS for this storage account.")]
public bool wildcard { get; set; }


[HelpOption]
public string GetUsage()
[Usage(ApplicationAlias = "ASGE")]
public static IEnumerable<Example> Examples
{
var help = new HelpText
get
{
Heading = new HeadingInfo("Azure Storage GZip Encoder", "1.0"),
Copyright = new CopyrightInfo("Stefan Gordon", 2016),
AdditionalNewLineAfterOption = true,
AddDashesToOption = true
};
help.AddPreOptionsLine("https://github.com/stefangordon/azure-storage-gzip-encoding\n");
help.AddPreOptionsLine("\nExample 1: asge --extensions .css .js --container assets --replace --connectionstring <conn string>");
help.AddPreOptionsLine("Example 2: asge --extensions .css .js --container assets --newextension .gz --account mystorage --key <storage key>");
help.AddPreOptionsLine("\nUse either connection string (-c) or account and key (-a and -k).");
help.AddOptions(this);
return help;
return new List<Example>() {
new Example("Azure Storage GZip Encoding", new Options { })
};
}
}

}
}
95 changes: 54 additions & 41 deletions Program.cs
Original file line number Diff line number Diff line change
@@ -1,60 +1,73 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CommandLine;
using Microsoft.Azure;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Auth;
using Microsoft.WindowsAzure.Storage.Blob;
using Serilog;

namespace ASGE
{
class Program
{
static void Main(string[] args)
static async Task Main(string[] args)
{
var options = new Options();
if (CommandLine.Parser.Default.ParseArguments(args, options))
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.CreateLogger();

var result = await CommandLine.Parser.Default.ParseArguments<Options>(args)
.MapResult(
async opts => await RunOptionsAndReturnExitCode(opts),
async errs => await HandleParseError(errs));
Log.Information("Return code= {0}", result);
}
static async Task<int> RunOptionsAndReturnExitCode(Options options)
{
if (string.IsNullOrEmpty(options.NewExtension) && !options.Replace)
{
Log.Error("Must provide either -r (in-place replacement) or -n (new extension/postfix to append to compressed version).");
return -1;
}

CloudStorageAccount storageAccount;

if (!string.IsNullOrEmpty(options.ConnectionString))
{
storageAccount = CloudStorageAccount.Parse(options.ConnectionString);
}
else if (!string.IsNullOrEmpty(options.StorageAccount) && !string.IsNullOrEmpty(options.StorageKey))
{
storageAccount = new CloudStorageAccount(new StorageCredentials(options.StorageAccount, options.StorageKey), true);
}
else
{
Log.Error("Must provide either storagAccount+storageKey or connectionString.");
return -1;
}

CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
CloudBlobContainer blobContainer = blobClient.GetContainerReference(options.Container);

// Do the compression work
await Utility.EnsureGzipFiles(blobContainer, options.Include, options.Replace, options.NewExtension, options.CacheControlHeader, options.Simulate);

// Enable CORS if appropriate
if (options.wildcard)
{
if (string.IsNullOrEmpty(options.NewExtension) && !options.Replace)
{
Console.WriteLine("Must provide either -r (in-place replacement) or -n (new extension/postfix to append to compressed version).");
return;
}

CloudStorageAccount storageAccount;

if (!string.IsNullOrEmpty(options.ConnectionString))
{
storageAccount = CloudStorageAccount.Parse(options.ConnectionString);
}
else if (!string.IsNullOrEmpty(options.StorageAccount) && !String.IsNullOrEmpty(options.StorageKey))
{
storageAccount = new CloudStorageAccount(new StorageCredentials(options.StorageAccount, options.StorageKey), true);
}
else
{
options.GetUsage();
return;
}

CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
CloudBlobContainer blobContainer = blobClient.GetContainerReference(options.Container);

// Do the compression work
Utility.EnsureGzipFiles(blobContainer, options.Extensions, options.Replace, options.NewExtension, options.MaxAgeSeconds, options.Simulate);

// Enable CORS if appropriate
if (options.wildcard)
{
Utility.SetWildcardCorsOnBlobService(storageAccount);
}

Trace.TraceInformation("Complete.");
await Utility.SetWildcardCorsOnBlobService(storageAccount);
}

Log.Information("Complete.");
return 0;
}

//in case of errors or --help or --version
static async Task<int> HandleParseError(IEnumerable<Error> errs)
{
return await Task.FromResult(-1);
}
}
}
Loading