Avoid repeating the first-run message if lzma archive is missing

If the LZMA archive is missing, the first-run message is printed every time.
This commit fixes that.

Split the first-run message into two pieces:

- The first-run (welcome and telemetry) message is printed only once, no matter
  whether the cache was primed or not.

- The cache-priming message is printed only if the cache is avaialble.
  Otherwise skip the cache introduction and the ccache priming operation.
This commit is contained in:
Omair Majid 2017-06-14 12:09:06 -04:00
parent 2180d92d10
commit 34b44b999b
10 changed files with 267 additions and 16 deletions

View file

@ -0,0 +1,16 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.DotNet.Cli.Utils
{
public interface IReporter
{
void WriteLine(string message);
void WriteLine();
void Write(string message);
}
}

View file

@ -4,7 +4,7 @@
namespace Microsoft.DotNet.Cli.Utils namespace Microsoft.DotNet.Cli.Utils
{ {
// Stupid-simple console manager // Stupid-simple console manager
public class Reporter public class Reporter : IReporter
{ {
private static readonly Reporter NullReporter = new Reporter(console: null); private static readonly Reporter NullReporter = new Reporter(console: null);
private static object _lock = new object(); private static object _lock = new object();

View file

@ -9,39 +9,74 @@ namespace Microsoft.DotNet.Configurer
{ {
public class DotnetFirstTimeUseConfigurer public class DotnetFirstTimeUseConfigurer
{ {
private IReporter _reporter;
private IEnvironmentProvider _environmentProvider; private IEnvironmentProvider _environmentProvider;
private INuGetCachePrimer _nugetCachePrimer; private INuGetCachePrimer _nugetCachePrimer;
private INuGetCacheSentinel _nugetCacheSentinel; private INuGetCacheSentinel _nugetCacheSentinel;
private IFirstTimeUseNoticeSentinel _firstTimeUseNoticeSentinel;
public DotnetFirstTimeUseConfigurer( public DotnetFirstTimeUseConfigurer(
INuGetCachePrimer nugetCachePrimer, INuGetCachePrimer nugetCachePrimer,
INuGetCacheSentinel nugetCacheSentinel, INuGetCacheSentinel nugetCacheSentinel,
IEnvironmentProvider environmentProvider) IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel,
IEnvironmentProvider environmentProvider,
IReporter reporter)
{ {
_nugetCachePrimer = nugetCachePrimer; _nugetCachePrimer = nugetCachePrimer;
_nugetCacheSentinel = nugetCacheSentinel; _nugetCacheSentinel = nugetCacheSentinel;
_firstTimeUseNoticeSentinel = firstTimeUseNoticeSentinel;
_environmentProvider = environmentProvider; _environmentProvider = environmentProvider;
_reporter = reporter;
} }
public void Configure() public void Configure()
{ {
if(ShouldPrimeNugetCache()) if (ShouldPrintFirstTimeUseNotice())
{ {
PrintFirstTimeUseNotice(); PrintFirstTimeUseNotice();
}
if (ShouldPrimeNugetCache())
{
PrintNugetCachePrimeMessage();
_nugetCachePrimer.PrimeCache(); _nugetCachePrimer.PrimeCache();
} }
} }
private bool ShouldPrintFirstTimeUseNotice()
{
var showFirstTimeUseNotice =
_environmentProvider.GetEnvironmentVariableAsBool("DOTNET_PRINT_TELEMETRY_MESSAGE", true);
return ShouldRunFirstRunExperience() &&
showFirstTimeUseNotice &&
!_firstTimeUseNoticeSentinel.Exists();
}
private void PrintFirstTimeUseNotice() private void PrintFirstTimeUseNotice()
{ {
string firstTimeUseWelcomeMessage = LocalizableStrings.FirstTimeWelcomeMessage; string firstTimeUseWelcomeMessage = LocalizableStrings.FirstTimeWelcomeMessage;
Reporter.Output.WriteLine(); _reporter.WriteLine();
Reporter.Output.WriteLine(firstTimeUseWelcomeMessage); _reporter.WriteLine(firstTimeUseWelcomeMessage);
_firstTimeUseNoticeSentinel.CreateIfNotExists();
} }
private bool ShouldPrimeNugetCache() private bool ShouldPrimeNugetCache()
{
return ShouldRunFirstRunExperience() &&
!_nugetCachePrimer.SkipPrimingTheCache();
}
private void PrintNugetCachePrimeMessage()
{
string cachePrimeMessage = LocalizableStrings.NugetCachePrimeMessage;
_reporter.WriteLine();
_reporter.WriteLine(cachePrimeMessage);
}
private bool ShouldRunFirstRunExperience()
{ {
var skipFirstTimeExperience = var skipFirstTimeExperience =
_environmentProvider.GetEnvironmentVariableAsBool("DOTNET_SKIP_FIRST_TIME_EXPERIENCE", false); _environmentProvider.GetEnvironmentVariableAsBool("DOTNET_SKIP_FIRST_TIME_EXPERIENCE", false);

View file

@ -0,0 +1,49 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.IO;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.Extensions.EnvironmentAbstractions;
using NuGet.Configuration;
namespace Microsoft.DotNet.Configurer
{
public class FirstTimeUseNoticeSentinel : IFirstTimeUseNoticeSentinel
{
public static readonly string SENTINEL = $"{Product.Version}.dotnetFirstUseSentinel";
private readonly IFile _file;
private string _nugetCachePath;
private string SentinelPath => Path.Combine(_nugetCachePath, SENTINEL);
public FirstTimeUseNoticeSentinel(CliFallbackFolderPathCalculator cliFallbackFolderPathCalculator) :
this(cliFallbackFolderPathCalculator.CliFallbackFolderPath, FileSystemWrapper.Default.File)
{
}
internal FirstTimeUseNoticeSentinel(string nugetCachePath, IFile file)
{
_file = file;
_nugetCachePath = nugetCachePath;
}
public bool Exists()
{
return _file.Exists(SentinelPath);
}
public void CreateIfNotExists()
{
if (!Exists())
{
_file.CreateEmptyFile(SentinelPath);
}
}
public void Dispose()
{
}
}
}

View file

@ -0,0 +1,14 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
namespace Microsoft.DotNet.Configurer
{
public interface IFirstTimeUseNoticeSentinel : IDisposable
{
bool Exists();
void CreateIfNotExists();
}
}

View file

@ -6,5 +6,6 @@ namespace Microsoft.DotNet.Configurer
public interface INuGetCachePrimer public interface INuGetCachePrimer
{ {
void PrimeCache(); void PrimeCache();
bool SkipPrimingTheCache();
} }
} }

View file

@ -126,9 +126,10 @@ Telemetry
-------------- --------------
The .NET Core tools collect usage data in order to improve your experience. The data is anonymous and does not include command-line arguments. The data is collected by Microsoft and shared with the community. The .NET Core tools collect usage data in order to improve your experience. The data is anonymous and does not include command-line arguments. The data is collected by Microsoft and shared with the community.
You can opt out of telemetry by setting a DOTNET_CLI_TELEMETRY_OPTOUT environment variable to 1 using your favorite shell. You can opt out of telemetry by setting a DOTNET_CLI_TELEMETRY_OPTOUT environment variable to 1 using your favorite shell.
You can read more about .NET Core tools telemetry @ https://aka.ms/dotnet-cli-telemetry. You can read more about .NET Core tools telemetry @ https://aka.ms/dotnet-cli-telemetry.</value>
</data>
Configuring... <data name="NugetCachePrimeMessage" xml:space="preserve">
<value>Configuring...
------------------- -------------------
A command is running to initially populate your local package cache, to improve restore speed and enable offline access. This command will take up to a minute to complete and will only happen once.</value> A command is running to initially populate your local package cache, to improve restore speed and enable offline access. This command will take up to a minute to complete and will only happen once.</value>
</data> </data>

View file

@ -68,7 +68,7 @@ namespace Microsoft.DotNet.Configurer
_nuGetCacheSentinel.CreateIfNotExists(); _nuGetCacheSentinel.CreateIfNotExists();
} }
private bool SkipPrimingTheCache() public bool SkipPrimingTheCache()
{ {
return !_file.Exists(_nugetPackagesArchiver.NuGetPackagesArchive); return !_file.Exists(_nugetPackagesArchiver.NuGetPackagesArchive);
} }

View file

@ -81,6 +81,7 @@ namespace Microsoft.DotNet.Cli
var lastArg = 0; var lastArg = 0;
var cliFallbackFolderPathCalculator = new CliFallbackFolderPathCalculator(); var cliFallbackFolderPathCalculator = new CliFallbackFolderPathCalculator();
using (INuGetCacheSentinel nugetCacheSentinel = new NuGetCacheSentinel(cliFallbackFolderPathCalculator)) using (INuGetCacheSentinel nugetCacheSentinel = new NuGetCacheSentinel(cliFallbackFolderPathCalculator))
using (IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel = new FirstTimeUseNoticeSentinel(cliFallbackFolderPathCalculator))
{ {
for (; lastArg < args.Length; lastArg++) for (; lastArg < args.Length; lastArg++)
{ {
@ -112,7 +113,7 @@ namespace Microsoft.DotNet.Cli
} }
else else
{ {
ConfigureDotNetForFirstTimeUse(nugetCacheSentinel, cliFallbackFolderPathCalculator); ConfigureDotNetForFirstTimeUse(nugetCacheSentinel, firstTimeUseNoticeSentinel, cliFallbackFolderPathCalculator);
// It's the command, and we're done! // It's the command, and we're done!
command = args[lastArg]; command = args[lastArg];
@ -168,6 +169,7 @@ namespace Microsoft.DotNet.Cli
private static void ConfigureDotNetForFirstTimeUse( private static void ConfigureDotNetForFirstTimeUse(
INuGetCacheSentinel nugetCacheSentinel, INuGetCacheSentinel nugetCacheSentinel,
IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel,
CliFallbackFolderPathCalculator cliFallbackFolderPathCalculator) CliFallbackFolderPathCalculator cliFallbackFolderPathCalculator)
{ {
using (PerfTrace.Current.CaptureTiming()) using (PerfTrace.Current.CaptureTiming())
@ -184,7 +186,9 @@ namespace Microsoft.DotNet.Cli
var dotnetConfigurer = new DotnetFirstTimeUseConfigurer( var dotnetConfigurer = new DotnetFirstTimeUseConfigurer(
nugetCachePrimer, nugetCachePrimer,
nugetCacheSentinel, nugetCacheSentinel,
environmentProvider); firstTimeUseNoticeSentinel,
environmentProvider,
Reporter.Output);
dotnetConfigurer.Configure(); dotnetConfigurer.Configure();
} }

View file

@ -15,17 +15,102 @@ namespace Microsoft.DotNet.Configurer.UnitTests
{ {
private Mock<INuGetCachePrimer> _nugetCachePrimerMock; private Mock<INuGetCachePrimer> _nugetCachePrimerMock;
private Mock<INuGetCacheSentinel> _nugetCacheSentinelMock; private Mock<INuGetCacheSentinel> _nugetCacheSentinelMock;
private Mock<IFirstTimeUseNoticeSentinel> _firstTimeUseNoticeSentinelMock;
private Mock<IEnvironmentProvider> _environmentProviderMock; private Mock<IEnvironmentProvider> _environmentProviderMock;
private Mock<IReporter> _reporterMock;
public GivenADotnetFirstTimeUseConfigurer() public GivenADotnetFirstTimeUseConfigurer()
{ {
_nugetCachePrimerMock = new Mock<INuGetCachePrimer>(); _nugetCachePrimerMock = new Mock<INuGetCachePrimer>();
_nugetCacheSentinelMock = new Mock<INuGetCacheSentinel>(); _nugetCacheSentinelMock = new Mock<INuGetCacheSentinel>();
_firstTimeUseNoticeSentinelMock = new Mock<IFirstTimeUseNoticeSentinel>();
_environmentProviderMock = new Mock<IEnvironmentProvider>(); _environmentProviderMock = new Mock<IEnvironmentProvider>();
_reporterMock = new Mock<IReporter>();
_environmentProviderMock _environmentProviderMock
.Setup(e => e.GetEnvironmentVariableAsBool("DOTNET_SKIP_FIRST_TIME_EXPERIENCE", false)) .Setup(e => e.GetEnvironmentVariableAsBool("DOTNET_SKIP_FIRST_TIME_EXPERIENCE", false))
.Returns(false); .Returns(false);
_environmentProviderMock
.Setup(e => e.GetEnvironmentVariableAsBool("DOTNET_PRINT_TELEMETRY_MESSAGE", true))
.Returns(true);
}
[Fact]
public void It_does_not_print_the_first_time_use_notice_if_the_sentinel_exists()
{
_firstTimeUseNoticeSentinelMock.Setup(n => n.Exists()).Returns(true);
var dotnetFirstTimeUseConfigurer = new DotnetFirstTimeUseConfigurer(
_nugetCachePrimerMock.Object,
_nugetCacheSentinelMock.Object,
_firstTimeUseNoticeSentinelMock.Object,
_environmentProviderMock.Object,
_reporterMock.Object);
dotnetFirstTimeUseConfigurer.Configure();
_reporterMock.Verify(r => r.WriteLine(It.Is<string>(str => str.StartsWith("Welcome to .NET Core!"))), Times.Never);
_reporterMock.Verify(r => r.Write(It.IsAny<string>()), Times.Never);
}
[Fact]
public void It_does_not_print_the_first_time_use_notice_when_the_user_has_set_the_DOTNET_SKIP_FIRST_TIME_EXPERIENCE_environemnt_variable()
{
_firstTimeUseNoticeSentinelMock.Setup(n => n.Exists()).Returns(false);
_environmentProviderMock
.Setup(e => e.GetEnvironmentVariableAsBool("DOTNET_SKIP_FIRST_TIME_EXPERIENCE", false))
.Returns(true);
var dotnetFirstTimeUseConfigurer = new DotnetFirstTimeUseConfigurer(
_nugetCachePrimerMock.Object,
_nugetCacheSentinelMock.Object,
_firstTimeUseNoticeSentinelMock.Object,
_environmentProviderMock.Object,
_reporterMock.Object);
dotnetFirstTimeUseConfigurer.Configure();
_reporterMock.Verify(r => r.WriteLine(It.Is<string>(str => str.StartsWith("Welcome to .NET Core!"))), Times.Never);
_reporterMock.Verify(r => r.Write(It.IsAny<string>()), Times.Never);
}
[Fact]
public void It_does_not_print_the_first_time_use_notice_when_the_user_has_set_the_DOTNET_PRINT_TELEMETRY_MESSAGE_environemnt_variable()
{
_firstTimeUseNoticeSentinelMock.Setup(n => n.Exists()).Returns(false);
_environmentProviderMock
.Setup(e => e.GetEnvironmentVariableAsBool("DOTNET_PRINT_TELEMETRY_MESSAGE", true))
.Returns(false);
var dotnetFirstTimeUseConfigurer = new DotnetFirstTimeUseConfigurer(
_nugetCachePrimerMock.Object,
_nugetCacheSentinelMock.Object,
_firstTimeUseNoticeSentinelMock.Object,
_environmentProviderMock.Object,
_reporterMock.Object);
dotnetFirstTimeUseConfigurer.Configure();
_reporterMock.Verify(r => r.WriteLine(It.Is<string>(str => str.StartsWith("Welcome to .NET Core!"))), Times.Never);
_reporterMock.Verify(r => r.Write(It.IsAny<string>()), Times.Never);
}
[Fact]
public void It_prints_the_telemetry_if_the_sentinel_does_not_exist()
{
_firstTimeUseNoticeSentinelMock.Setup(n => n.Exists()).Returns(false);
var dotnetFirstTimeUseConfigurer = new DotnetFirstTimeUseConfigurer(
_nugetCachePrimerMock.Object,
_nugetCacheSentinelMock.Object,
_firstTimeUseNoticeSentinelMock.Object,
_environmentProviderMock.Object,
_reporterMock.Object);
dotnetFirstTimeUseConfigurer.Configure();
_reporterMock.Verify(r => r.WriteLine(It.Is<string>(str => str.StartsWith("Welcome to .NET Core!"))));
_reporterMock.Verify(r => r.Write(It.IsAny<string>()), Times.Never);
} }
[Fact] [Fact]
@ -36,7 +121,9 @@ namespace Microsoft.DotNet.Configurer.UnitTests
var dotnetFirstTimeUseConfigurer = new DotnetFirstTimeUseConfigurer( var dotnetFirstTimeUseConfigurer = new DotnetFirstTimeUseConfigurer(
_nugetCachePrimerMock.Object, _nugetCachePrimerMock.Object,
_nugetCacheSentinelMock.Object, _nugetCacheSentinelMock.Object,
_environmentProviderMock.Object); _firstTimeUseNoticeSentinelMock.Object,
_environmentProviderMock.Object,
_reporterMock.Object);
dotnetFirstTimeUseConfigurer.Configure(); dotnetFirstTimeUseConfigurer.Configure();
@ -51,7 +138,26 @@ namespace Microsoft.DotNet.Configurer.UnitTests
var dotnetFirstTimeUseConfigurer = new DotnetFirstTimeUseConfigurer( var dotnetFirstTimeUseConfigurer = new DotnetFirstTimeUseConfigurer(
_nugetCachePrimerMock.Object, _nugetCachePrimerMock.Object,
_nugetCacheSentinelMock.Object, _nugetCacheSentinelMock.Object,
_environmentProviderMock.Object); _firstTimeUseNoticeSentinelMock.Object,
_environmentProviderMock.Object,
_reporterMock.Object);
dotnetFirstTimeUseConfigurer.Configure();
_nugetCachePrimerMock.Verify(r => r.PrimeCache(), Times.Never);
}
[Fact]
public void It_does_not_prime_the_cache_if_cache_is_missing()
{
_nugetCachePrimerMock.Setup(n => n.SkipPrimingTheCache()).Returns(true);
var dotnetFirstTimeUseConfigurer = new DotnetFirstTimeUseConfigurer(
_nugetCachePrimerMock.Object,
_nugetCacheSentinelMock.Object,
_firstTimeUseNoticeSentinelMock.Object,
_environmentProviderMock.Object,
_reporterMock.Object);
dotnetFirstTimeUseConfigurer.Configure(); dotnetFirstTimeUseConfigurer.Configure();
@ -69,7 +175,9 @@ namespace Microsoft.DotNet.Configurer.UnitTests
var dotnetFirstTimeUseConfigurer = new DotnetFirstTimeUseConfigurer( var dotnetFirstTimeUseConfigurer = new DotnetFirstTimeUseConfigurer(
_nugetCachePrimerMock.Object, _nugetCachePrimerMock.Object,
_nugetCacheSentinelMock.Object, _nugetCacheSentinelMock.Object,
_environmentProviderMock.Object); _firstTimeUseNoticeSentinelMock.Object,
_environmentProviderMock.Object,
_reporterMock.Object);
dotnetFirstTimeUseConfigurer.Configure(); dotnetFirstTimeUseConfigurer.Configure();
@ -84,11 +192,34 @@ namespace Microsoft.DotNet.Configurer.UnitTests
var dotnetFirstTimeUseConfigurer = new DotnetFirstTimeUseConfigurer( var dotnetFirstTimeUseConfigurer = new DotnetFirstTimeUseConfigurer(
_nugetCachePrimerMock.Object, _nugetCachePrimerMock.Object,
_nugetCacheSentinelMock.Object, _nugetCacheSentinelMock.Object,
_environmentProviderMock.Object); _firstTimeUseNoticeSentinelMock.Object,
_environmentProviderMock.Object,
_reporterMock.Object);
dotnetFirstTimeUseConfigurer.Configure(); dotnetFirstTimeUseConfigurer.Configure();
_nugetCachePrimerMock.Verify(r => r.PrimeCache(), Times.Once); _nugetCachePrimerMock.Verify(r => r.PrimeCache(), Times.Once);
} }
[Fact]
public void It_prints_first_use_notice_and_primes_the_cache_if_the_sentinels_do_not_exist()
{
_nugetCacheSentinelMock.Setup(n => n.Exists()).Returns(false);
_firstTimeUseNoticeSentinelMock.Setup(n => n.Exists()).Returns(false);
var dotnetFirstTimeUseConfigurer = new DotnetFirstTimeUseConfigurer(
_nugetCachePrimerMock.Object,
_nugetCacheSentinelMock.Object,
_firstTimeUseNoticeSentinelMock.Object,
_environmentProviderMock.Object,
_reporterMock.Object);
dotnetFirstTimeUseConfigurer.Configure();
_reporterMock.Verify(r => r.WriteLine(It.Is<string>(str => str.StartsWith("Welcome to .NET Core!"))));
_reporterMock.Verify(r => r.WriteLine(It.Is<string>(str => str.StartsWith("Configuring"))));
_nugetCachePrimerMock.Verify(r => r.PrimeCache(), Times.Once);
_reporterMock.Verify(r => r.Write(It.IsAny<string>()), Times.Never);
}
} }
} }