Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

cached tab completion package ids

  • Loading branch information...
commit 46067ed5d98e581e114a96075ce4692d58adc331 1 parent 0e788f0
@half-ogre half-ogre authored
Showing with 625 additions and 54 deletions.
  1. +2 −1  .gitignore
  2. +80 −0 Facts/Controllers/ApiControllerFacts.cs
  3. +1 −0  Facts/Facts.csproj
  4. +167 −0 Facts/Infrastructure/PackageCacheFacts.cs
  5. +27 −2 Facts/Services/PackageServiceFacts.cs
  6. +11 −3 Scripts/Package.ps1
  7. +16 −0 Website/App_Start/Configuration.cs
  8. +36 −4 Website/App_Start/ContainerBindings.cs
  9. +2 −0  Website/App_Start/IConfiguration.cs
  10. +6 −5 Website/Controllers/ApiController.cs
  11. +1 −1  Website/Controllers/AppController.cs
  12. +10 −0 Website/ExtensionMethods.cs
  13. +35 −0 Website/Infrastructure/AzureCache.cs
  14. +11 −0 Website/Infrastructure/ICache.cs
  15. +10 −0 Website/Infrastructure/ITaskFactory.cs
  16. +4 −4 Website/Infrastructure/JsonNetResult.cs
  17. +35 −0 Website/Infrastructure/LocalCache.cs
  18. +85 −0 Website/Infrastructure/PackageCache.cs
  19. +20 −0 Website/Infrastructure/TaskFactoryWrapper.cs
  20. +1 −0  Website/PackagesController.generated.cs
  21. +16 −22 Website/Queries/PackageIdsQuery.cs
  22. +5 −1 Website/Services/PackageService.cs
  23. +8 −11 Website/T4MVC.cs
  24. +2 −0  Website/Web.config
  25. +34 −0 Website/Website.csproj
View
3  .gitignore
@@ -9,4 +9,5 @@ _AzurePackage
*.bak
NuGetGallery.sln.docstates
_ReSharper.NuGetGallery
-_PackagedWebsite
+_PackagedWebsite
+TestResults
View
80 Facts/Controllers/ApiControllerFacts.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
@@ -525,6 +526,85 @@ public void GetPackageReturnsRedirectResultWhenExternalPackageUrlIsNotNull()
}
}
+ public class TheGetPackageIdsAction
+ {
+ [Fact]
+ public void WillReturnThePackageIdsFromThePackageRepository()
+ {
+ var controller = new TestableApiController();
+ controller.StubPackageIds = new[] { "theFirstPackageId", "theSecondPackageId", "theLastPackageId" };
+
+ var result = controller.GetPackageIds(null, null) as JsonNetResult;
+
+ Assert.Equal(controller.StubPackageIds, result.Data);
+ }
+
+ [Theory]
+ [InlineData(null, false)]
+ [InlineData(true, true)]
+ [InlineData(false, false)]
+ public void WillFilterByTheIsPrereleaseParam(bool? isPrereleaseParam, bool expectedFilter)
+ {
+ var controller = new TestableApiController();
+ controller.StubPackageIds = new[] { "aFirstPackageId", "aSecondPackageId", "aLastPackageId" };
+
+ var result = controller.GetPackageIds(null, isPrereleaseParam) as JsonNetResult;
+
+ controller.StubPackageRepository.Verify(stub => stub.GetPackageIds(expectedFilter));
+ }
+
+ [Fact]
+ public void WillFilterThePackageIdsByPartialId()
+ {
+ var controller = new TestableApiController();
+ controller.StubPackageIds = new[] { "theFirstPackageId", "theSecondPackageId", "theLastPackageId" };
+
+ var result = controller.GetPackageIds("theFirst", null) as JsonNetResult;
+
+ var data = (string[])result.Data;
+ Assert.Equal(1, data.Length);
+ Assert.Equal("theFirstPackageId", data[0]);
+ }
+
+ [Fact]
+ public void WillLimitResultTo30PackageIds()
+ {
+ var controller = new TestableApiController();
+ var packageIds = new List<string>();
+ for (var n = 0; n < 50; n++)
+ packageIds.Add(string.Format("packageId{0}", n));
+ controller.StubPackageIds = packageIds.ToArray();
+
+ var result = controller.GetPackageIds(null, null) as JsonNetResult;
+
+ var data = (string[])result.Data;
+ Assert.Equal(30, data.Length);
+ }
+
+ public class TestableApiController : ApiController
+ {
+ public TestableApiController()
+ : base(new Mock<IPackageService>().Object, new Mock<IPackageFileService>().Object, new Mock<IUserService>().Object, new Mock<INuGetExeDownloaderService>().Object)
+ {
+ StubPackageRepository = new Mock<IPackageCache>();
+ StubPackageRepository
+ .Setup(stub => stub.GetPackageIds(It.IsAny<bool>()))
+ .Returns(() => StubPackageIds.AsQueryable());
+ }
+
+ public string[] StubPackageIds { get; set; }
+ public Mock<IPackageCache> StubPackageRepository { get; set; }
+
+ protected override T GetService<T>()
+ {
+ if (typeof(T) == typeof(IPackageCache))
+ return (T)StubPackageRepository.Object;
+
+ throw new Exception("Tried to get an unexpected service.");
+ }
+ }
+ }
+
private static void AssertStatusCodeResult(ActionResult result, int statusCode, string statusDesc)
{
Assert.IsType<HttpStatusCodeWithBodyResult>(result);
View
1  Facts/Facts.csproj
@@ -131,6 +131,7 @@
<Compile Include="Infrastructure\CookieTempDataProviderFacts.cs" />
<Compile Include="Infrastructure\Jobs\UpdateStatisticsJobFacts.cs" />
<Compile Include="Infrastructure\LuceneIndexingServiceFacts.cs" />
+ <Compile Include="Infrastructure\PackageCacheFacts.cs" />
<Compile Include="PackageCurators\WebMatrixPackageCuratorFacts.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RequireRemoteHttpsAttributeFacts.cs" />
View
167 Facts/Infrastructure/PackageCacheFacts.cs
@@ -0,0 +1,167 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Moq;
+using Xunit;
+using Xunit.Extensions;
+
+namespace NuGetGallery.Infrastructure
+{
+ public class PackageCacheFacts
+ {
+ public class TheAddPackageMethod
+ {
+ [Fact]
+ public void WillRemoveTheReleasedPackageIdsListFromCache()
+ {
+ var packageCache = new TestablePackageCache();
+
+ packageCache.AddPackage(new Package());
+
+ packageCache.StubCache.Verify(stub => stub.Remove("released-package-ids"));
+ }
+
+ [Fact]
+ public void WillStartATaskToReadAndCacheReleasedPackageIds()
+ {
+ var packageCache = new TestablePackageCache();
+
+ packageCache.AddPackage(new Package());
+
+ Assert.False(packageCache.ReadAndCachePackageIds_IncludePrereleaseArg.ElementAt(0));
+ }
+
+ [Fact]
+ public void WillRemoveTheAllPackageIdsListFromCache()
+ {
+ var packageCache = new TestablePackageCache();
+
+ packageCache.AddPackage(new Package());
+
+ packageCache.StubCache.Verify(stub => stub.Remove("all-package-ids"));
+ }
+
+ [Fact]
+ public void WillStartATaskToReadAndCacheAllPackageIds()
+ {
+ var packageCache = new TestablePackageCache();
+
+ packageCache.AddPackage(new Package());
+
+ Assert.True(packageCache.ReadAndCachePackageIds_IncludePrereleaseArg.ElementAt(1));
+ }
+ }
+
+ public class GetPackageIdsMethod
+ {
+ [Theory]
+ [InlineData(true, "all-package-ids")]
+ [InlineData(false, "released-package-ids")]
+ public void WillReturnCachedPackageIdsWhenAlreadyCached(
+ bool includePrerelease,
+ string cacheKey)
+ {
+ var packageCache = new TestablePackageCache();
+ packageCache.StubCache
+ .Setup(stub => stub.Get<string[]>(cacheKey))
+ .Returns(new[] { "theFirstPackageId", "theSecondPackageId", "theLastPackageId" });
+
+ var result = packageCache.GetPackageIds(includePrerelease);
+
+ Assert.Equal("theFirstPackageId", result.ElementAt(0));
+ Assert.Equal("theSecondPackageId", result.ElementAt(1));
+ Assert.Equal("theLastPackageId", result.ElementAt(2));
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void WillReadPackageIdsWhenNotAlreadyCached(bool includePrerelease)
+ {
+ var packageCache = new TestablePackageCache();
+ packageCache.StubPackageIdsQuery
+ .Setup(stub => stub.Execute((bool?)includePrerelease))
+ .Returns(new[] { "theFirstPackageId", "theSecondPackageId", "theLastPackageId" });
+
+ var result = packageCache.GetPackageIds(includePrerelease);
+
+ Assert.Equal("theFirstPackageId", result.ElementAt(0));
+ Assert.Equal("theSecondPackageId", result.ElementAt(1));
+ Assert.Equal("theLastPackageId", result.ElementAt(2));
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void WillCacheTheReadPackageIds(bool includePrerelease)
+ {
+ var packageCache = new TestablePackageCache();
+ packageCache.StubPackageIdsQuery
+ .Setup(stub => stub.Execute(It.IsAny<bool?>()))
+ .Returns(new[] { "theFirstPackageId", "theSecondPackageId", "theLastPackageId" });
+
+ var result = packageCache.GetPackageIds(includePrerelease);
+
+ Assert.Equal(includePrerelease, packageCache.CachePackageIds_IncludesPrereleaseArg);
+ Assert.Equal("theFirstPackageId", packageCache.CachePackageIds_PackageIdsArg.ElementAt(0));
+ Assert.Equal("theSecondPackageId", packageCache.CachePackageIds_PackageIdsArg.ElementAt(1));
+ Assert.Equal("theLastPackageId", packageCache.CachePackageIds_PackageIdsArg.ElementAt(2));
+ }
+ }
+
+ public class TestablePackageCache : PackageCache
+ {
+ public TestablePackageCache() : base(null)
+ {
+ StubCache = new Mock<ICache>();
+ StubPackageIdsQuery = new Mock<IPackageIdsQuery>();
+ StubTaskFactory = new Mock<ITaskFactory>();
+
+ ReadAndCachePackageIds_IncludePrereleaseArg = new Queue<bool>();
+
+ StubCache
+ .Setup(stub => stub.Get<string[]>(It.IsAny<string>()))
+ .Returns((string[])null);
+ StubTaskFactory
+ .Setup(stub => stub.StartNew(It.IsAny<Action>()))
+ .Callback<Action>(action => action())
+ .Returns(new Task(() => {}));
+
+ Cache = StubCache.Object;
+ }
+
+ public bool CachePackageIds_IncludesPrereleaseArg { get; private set; }
+ public string[] CachePackageIds_PackageIdsArg { get; private set; }
+ public Queue<bool> ReadAndCachePackageIds_IncludePrereleaseArg { get; private set; }
+ public Mock<ICache> StubCache { get; set; }
+ public Mock<IPackageIdsQuery> StubPackageIdsQuery { get; set; }
+ public Mock<ITaskFactory> StubTaskFactory { get; set; }
+
+ protected override void CachePackageIds(
+ string[] packageIds,
+ bool includesPrerelease = false)
+ {
+ CachePackageIds_PackageIdsArg = packageIds;
+ CachePackageIds_IncludesPrereleaseArg = includesPrerelease;
+ }
+
+ protected override T GetService<T>()
+ {
+ if (typeof(T) == typeof(IPackageIdsQuery))
+ return (T)StubPackageIdsQuery.Object;
+
+ if (typeof(T) == typeof(ITaskFactory))
+ return (T)StubTaskFactory.Object;
+
+ throw new Exception("Tried to get unexpected service");
+ }
+
+ protected override string[] ReadAndCachePackageIds(bool includePrerelease = false)
+ {
+ ReadAndCachePackageIds_IncludePrereleaseArg.Enqueue(includePrerelease);
+ return new string[] { };
+ }
+ }
+ }
+}
View
29 Facts/Services/PackageServiceFacts.cs
@@ -466,6 +466,28 @@ void WillNotSaveAnySuuportedFrameworksWhenThereIsANullTargetFramework()
Assert.Empty(package.SupportedFrameworks);
}
+
+ [Fact]
+ void WillAddPackageToThePackageCache()
+ {
+ var packageRegistrationRepo = new Mock<IEntityRepository<PackageRegistration>>();
+ var packageCache = new Mock<IPackageCache>();
+ var service = CreateService(
+ packageRegistrationRepo: packageRegistrationRepo,
+ setup: mockPackageSvc =>
+ {
+ mockPackageSvc.Setup(x => x.FindPackageRegistrationById(It.IsAny<string>())).Returns((PackageRegistration)null);
+ },
+ packageCache: packageCache);
+ var nugetPackage = CreateNuGetPackage();
+ var currentUser = new User();
+
+ var package = service.CreatePackage(
+ nugetPackage.Object,
+ currentUser);
+
+ packageCache.Verify(x => x.AddPackage(package));
+ }
}
public class TheDeletePackageMethod
@@ -1293,7 +1315,8 @@ static Mock<IPackage> CreateNuGetPackage(Action<Mock<IPackage>> setup = null)
Mock<IPackageFileService> packageFileSvc = null,
Mock<IEntityRepository<PackageOwnerRequest>> packageOwnerRequestRepo = null,
Mock<IIndexingService> indexingSvc = null,
- Action<Mock<PackageService>> setup = null)
+ Action<Mock<PackageService>> setup = null,
+ Mock<IPackageCache> packageCache = null)
{
if (cryptoSvc == null)
{
@@ -1308,6 +1331,7 @@ static Mock<IPackage> CreateNuGetPackage(Action<Mock<IPackage>> setup = null)
packageStatsRepo = packageStatsRepo ?? new Mock<IEntityRepository<PackageStatistics>>();
packageOwnerRequestRepo = packageOwnerRequestRepo ?? new Mock<IEntityRepository<PackageOwnerRequest>>();
indexingSvc = indexingSvc ?? new Mock<IIndexingService>();
+ packageCache = packageCache ?? new Mock<IPackageCache>();
var packageSvc = new Mock<PackageService>(
cryptoSvc.Object,
@@ -1316,7 +1340,8 @@ static Mock<IPackage> CreateNuGetPackage(Action<Mock<IPackage>> setup = null)
packageStatsRepo.Object,
packageFileSvc.Object,
packageOwnerRequestRepo.Object,
- indexingSvc.Object);
+ indexingSvc.Object,
+ packageCache.Object);
packageSvc.CallBase = true;
View
14 Scripts/Package.ps1
@@ -1,4 +1,6 @@
param(
+ $azureCacheServiceUrl = $env:NUGET_GALLERY_AZURE_CACHE_SERVICE_URL,
+ $azureCacheAuthenticationToken = $env:NUGET_GALLERY_AZURE_CACHE_AUTHENTICATION_TOKEN,
$azureStorageAccessKey = $env:NUGET_GALLERY_AZURE_STORAGE_ACCESS_KEY,
$azureStorageAccountName = $env:NUGET_GALLERY_AZURE_STORAGE_ACCOUNT_NAME,
$azureStorageBlobUrl = $env:NUGET_GALLERY_AZURE_STORAGE_BLOB_URL,
@@ -21,6 +23,8 @@ $ScriptRoot = (Split-Path -parent $MyInvocation.MyCommand.Definition)
. $ScriptRoot\_Common.ps1
#Validate Sutff
+require-param -value $azureCacheServiceUrl -paramName "azureCacheServiceUrl"
+require-param -value $azureCacheAuthenticationToken -paramName "azureCacheAuthenticationToken"
require-param -value $azureStorageAccessKey -paramName "azureStorageAccessKey"
require-param -value $azureStorageAccountName -paramName "azureStorageAccountName"
require-param -value $azureStorageBlobUrl -paramName "azureStorageBlobUrl"
@@ -150,13 +154,17 @@ set-certificatethumbprint -path $cscfgPath -name "nuget.org" -value $sslCertific
set-releasemode $webConfigPath
set-machinekey $webConfigPath
-#Release Tag stuff
-print-message("Setting the release tags")
+#Azure Configuration
+set-appsetting -path $webConfigPath -name "Gallery:AzureCacheServiceUrl" -value $azureCacheServiceUrl
+set-appsetting -path $webConfigPath -name "Gallery:AzureCacheAuthenticationToken" -value $azureCacheAuthenticationToken
set-appsetting -path $webConfigPath -name "Gallery:AzureStorageAccessKey" -value $azureStorageAccessKey
set-appsetting -path $webConfigPath -name "Gallery:AzureStorageAccountName" -value $azureStorageAccountName
set-appsetting -path $webConfigPath -name "Gallery:AzureStorageBlobUrl" -value $azureStorageBlobUrl
-set-appsetting -path $webConfigPath -name "Gallery:GoogleAnalyticsPropertyId" -value $googleAnalyticsPropertyId
set-appsetting -path $webConfigPath -name "Gallery:PackageStoreType" -value "AzureStorageBlob"
+set-appsetting -path $webConfigPath -name "Gallery:GoogleAnalyticsPropertyId" -value $googleAnalyticsPropertyId
+
+#Release Tag stuff
+print-message("Setting the release tags")
set-appsetting -path $webConfigPath -name "Gallery:ReleaseBranch" -value $commitBranch
set-appsetting -path $webConfigPath -name "Gallery:ReleaseName" -value "NuGet 1.6 'Hershey'"
set-appsetting -path $webConfigPath -name "Gallery:ReleaseSha" -value $commitSha
View
16 Website/App_Start/Configuration.cs
@@ -38,6 +38,22 @@ public static string ReadAppSettings(string key)
return (T)_configThunks[key].Value;
}
+ public string AzureCacheAuthenticationToken
+ {
+ get
+ {
+ return ReadAppSettings("AzureCacheAuthenticationToken");
+ }
+ }
+
+ public string AzureCacheServiceUrl
+ {
+ get
+ {
+ return ReadAppSettings("AzureCacheServiceUrl");
+ }
+ }
+
public string AzureStorageAccessKey
{
get
View
40 Website/App_Start/ContainerBindings.cs
@@ -2,11 +2,14 @@
using System.Linq;
using System.Net;
using System.Net.Mail;
+using System.Security;
using System.Security.Principal;
+using System.Threading.Tasks;
using System.Web;
using System.Web.Hosting;
using System.Web.Mvc;
using AnglicanGeek.MarkdownMailer;
+using Microsoft.ApplicationServer.Caching;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;
using Ninject;
@@ -16,6 +19,22 @@ namespace NuGetGallery
{
public class ContainerBindings : NinjectModule
{
+ Func<IConfiguration, AzureCache> getAzureCache = new Func<IConfiguration, AzureCache>(configuration =>
+ {
+ var servers = new DataCacheServerEndpoint[1];
+ servers[0] = new DataCacheServerEndpoint(configuration.AzureCacheServiceUrl, 22233);
+ var authenticationToken = new SecureString();
+ foreach (char @char in configuration.AzureCacheAuthenticationToken)
+ authenticationToken.AppendChar(@char);
+ authenticationToken.MakeReadOnly();
+ DataCacheSecurity factorySecurity = new DataCacheSecurity(authenticationToken);
+ DataCacheFactoryConfiguration factoryConfig = new DataCacheFactoryConfiguration();
+ factoryConfig.Servers = servers;
+ factoryConfig.SecurityProperties = factorySecurity;
+ DataCacheFactory cacheFactory = new DataCacheFactory(factoryConfig);
+ return new AzureCache(cacheFactory.GetDefaultCache());
+ });
+
public override void Load()
{
IConfiguration configuration = new Configuration();
@@ -33,6 +52,9 @@ public override void Load()
Bind<GallerySetting>().ToMethod(c => gallerySetting.Value);
+ Bind<ITaskFactory>()
+ .ToMethod(context => new TaskFactoryWrapper(Task.Factory));
+
Bind<ISearchService>()
.To<LuceneSearchService>()
.InRequestScope();
@@ -143,6 +165,9 @@ public override void Load()
Bind<IFileStorageService>()
.To<FileSystemFileStorageService>()
.InSingletonScope();
+ Bind<ICache>()
+ .To<LocalCache>()
+ .InSingletonScope();
break;
case PackageStoreType.AzureStorageBlob:
Bind<ICloudBlobClient>()
@@ -153,6 +178,9 @@ public override void Load()
Bind<IFileStorageService>()
.To<CloudBlobFileStorageService>()
.InSingletonScope();
+ Bind<ICache>()
+ .ToMethod(context => getAzureCache(configuration))
+ .InSingletonScope();
break;
}
@@ -170,6 +198,10 @@ public override void Load()
Bind<IUploadFileService>()
.To<UploadFileService>();
+ Bind<IAggregateStatsService>()
+ .To<AggregateStatsService>()
+ .InRequestScope();
+
// todo: bind all package curators by convention
Bind<IAutomaticPackageCurator>()
.To<WebMatrixPackageCurator>();
@@ -207,16 +239,16 @@ public override void Load()
Bind<IUserByUsernameQuery>()
.To<UserByUsernameQuery>()
.InRequestScope();
-
- Bind<IAggregateStatsService>()
- .To<AggregateStatsService>()
- .InRequestScope();
Bind<IPackageIdsQuery>()
.To<PackageIdsQuery>()
.InRequestScope();
Bind<IPackageVersionsQuery>()
.To<PackageVersionsQuery>()
.InRequestScope();
+
+ Bind<IPackageCache>()
+ .To<PackageCache>()
+ .InSingletonScope();
}
}
}
View
2  Website/App_Start/IConfiguration.cs
@@ -3,6 +3,8 @@ namespace NuGetGallery
{
public interface IConfiguration
{
+ string AzureCacheAuthenticationToken { get; }
+ string AzureCacheServiceUrl { get; }
string AzureStorageAccessKey { get; }
string AzureStorageAccountName { get; }
string AzureStorageBlobUrl { get; }
View
11 Website/Controllers/ApiController.cs
@@ -1,11 +1,8 @@
using System;
-using System.Collections.Generic;
-using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
-using System.Runtime.Serialization;
using System.Web.Mvc;
using System.Web.UI;
using NuGet;
@@ -198,8 +195,12 @@ protected internal virtual IPackage ReadPackageFromRequest()
string partialId,
bool? includePrerelease)
{
- var qry = GetService<IPackageIdsQuery>();
- return new JsonNetResult(qry.Execute(partialId, includePrerelease).ToArray());
+ var packageRepo = GetService<IPackageCache>();
+ var packageIds = packageRepo.GetPackageIds(includePrerelease ?? false);
+ if (!string.IsNullOrWhiteSpace(partialId))
+ packageIds = packageIds.Where(packageId => packageId.StartsWith(partialId, StringComparison.OrdinalIgnoreCase));
+ packageIds = packageIds.Take(30);
+ return new JsonNetResult(packageIds.ToArray());
}
[ActionName("PackageVersions"), HttpGet]
View
2  Website/Controllers/AppController.cs
@@ -3,7 +3,7 @@
namespace NuGetGallery
{
- public abstract class AppController : Controller
+ public abstract partial class AppController : Controller
{
protected virtual IIdentity Identity
{
View
10 Website/ExtensionMethods.cs
@@ -9,9 +9,11 @@
using System.Security.Principal;
using System.ServiceModel.Activation;
using System.Text;
+using System.Threading.Tasks;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.WebPages;
+using Elmah;
using NuGet;
namespace NuGetGallery
@@ -237,5 +239,13 @@ public static string ToFriendlyName(this FrameworkName frameworkName)
sb.AppendFormat(" {0}", frameworkName.Profile);
return sb.ToString();
}
+
+ public static Task LogExceptions(this Task task)
+ {
+ task.ContinueWith(
+ t => { ErrorSignal.FromCurrentContext().Raise(t.Exception); },
+ TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously);
+ return task;
+ }
}
}
View
35 Website/Infrastructure/AzureCache.cs
@@ -0,0 +1,35 @@
+using Microsoft.ApplicationServer.Caching;
+
+namespace NuGetGallery
+{
+ public class AzureCache : ICache
+ {
+ DataCache _dataCache;
+
+ public AzureCache(DataCache dataCache)
+ {
+ _dataCache = dataCache;
+ }
+
+ public T Get<T>(string key)
+ {
+ return (T)_dataCache.Get(key);
+ }
+
+ public void Remove(string key)
+ {
+ _dataCache.Remove(key);
+ }
+
+ public void Set(string key, object value)
+ {
+ // DataCache doesn't allow null values, so we remove instead in that case.
+ if (value == null)
+ _dataCache.Remove(key);
+ else
+ _dataCache.Put(
+ key,
+ value);
+ }
+ }
+}
View
11 Website/Infrastructure/ICache.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace NuGetGallery
+{
+ public interface ICache
+ {
+ T Get<T>(string key);
+ void Remove(string key);
+ void Set(string key, object value);
+ }
+}
View
10 Website/Infrastructure/ITaskFactory.cs
@@ -0,0 +1,10 @@
+using System;
+using System.Threading.Tasks;
+
+namespace NuGetGallery
+{
+ public interface ITaskFactory
+ {
+ Task StartNew(Action action);
+ }
+}
View
8 Website/Infrastructure/JsonNetResult.cs
@@ -6,16 +6,16 @@ namespace NuGetGallery
{
public class JsonNetResult : ActionResult
{
- private readonly object _data;
-
public JsonNetResult(object data)
{
if (data == null)
throw new ArgumentNullException("data");
- _data = data;
+ Data = data;
}
+ public object Data { get; private set; }
+
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
@@ -25,7 +25,7 @@ public override void ExecuteResult(ControllerContext context)
response.ContentType = "application/json";
var writer = new JsonTextWriter(response.Output);
var serializer = JsonSerializer.Create(new JsonSerializerSettings());
- serializer.Serialize(writer, _data);
+ serializer.Serialize(writer, Data);
writer.Flush();
}
}
View
35 Website/Infrastructure/LocalCache.cs
@@ -0,0 +1,35 @@
+using System.Collections.Generic;
+
+namespace NuGetGallery
+{
+ public class LocalCache : ICache
+ {
+ Dictionary<string, object> _cache;
+
+ public LocalCache()
+ {
+ _cache = new Dictionary<string, object>();
+ }
+
+ public T Get<T>(string key)
+ {
+ if (_cache.ContainsKey(key))
+ return (T)_cache[key];
+
+ return default(T);
+ }
+
+ public void Remove(string key)
+ {
+ if (_cache.ContainsKey(key))
+ _cache.Remove(key);
+ }
+
+ public void Set(
+ string key,
+ object value)
+ {
+ _cache[key] = value;
+ }
+ }
+}
View
85 Website/Infrastructure/PackageCache.cs
@@ -0,0 +1,85 @@
+using System.Linq;
+using System.Threading.Tasks;
+using System.Web.Mvc;
+
+namespace NuGetGallery
+{
+ public interface IPackageCache
+ {
+ void AddPackage(NuGetGallery.Package package);
+ IQueryable<string> GetPackageIds(bool includePrerelease = false);
+ }
+
+ // This cache is simply used for tab completion data for now, but the goal
+ // is to serve the feed and entire site from cache, so this class will
+ // get much more complicated.
+ public class PackageCache : IPackageCache
+ {
+ const string _allPackageIdsCacheKey = "all-package-ids";
+ const string _releasedPackageIdsCacheKey = "released-package-ids";
+
+ public PackageCache(ICache cache)
+ {
+ Cache = cache;
+ }
+
+ protected ICache Cache { get; set; }
+
+ public void AddPackage(Package package)
+ {
+ // For now, this is just used to invalidate the cache.
+ // But, as more stuff is moved to the cache, this repo
+ // will grow to be much more sophisticated.
+ var taskFactory = GetService<ITaskFactory>();
+ Cache.Remove(_releasedPackageIdsCacheKey);
+ taskFactory.StartNew(() => ReadAndCachePackageIds(includePrerelease: false)).LogExceptions();
+ Cache.Remove(_allPackageIdsCacheKey);
+ taskFactory.StartNew(() => ReadAndCachePackageIds(includePrerelease: true)).LogExceptions();
+ }
+
+ protected virtual void CachePackageIds(
+ string[] packageIds,
+ bool includesPrerelease = false)
+ {
+ var cacheKey = GetPackageIdsCacheKey(includesPrerelease);
+ Cache.Set(cacheKey, packageIds);
+ }
+
+ public IQueryable<string> GetPackageIds(bool includePrerelease = false)
+ {
+ var cacheKey = GetPackageIdsCacheKey(includePrerelease);
+ var packageIds = Cache.Get<string[]>(cacheKey);
+ if (packageIds == null)
+ {
+ packageIds = ReadPackageIds(includePrerelease);
+ GetService<ITaskFactory>().StartNew(() => CachePackageIds(packageIds, includePrerelease)).LogExceptions();
+ }
+ return packageIds.AsQueryable();
+ }
+
+ static string GetPackageIdsCacheKey(bool includePrerelease = false)
+ {
+ if (!includePrerelease)
+ return _releasedPackageIdsCacheKey;
+
+ return _allPackageIdsCacheKey;
+ }
+
+ protected virtual T GetService<T>()
+ {
+ return DependencyResolver.Current.GetService<T>();
+ }
+
+ string[] ReadPackageIds(bool includePrerelease = false)
+ {
+ return GetService<IPackageIdsQuery>().Execute(includePrerelease).ToArray();
+ }
+
+ protected virtual string[] ReadAndCachePackageIds(bool includePrerelease = false)
+ {
+ var packageIds = ReadPackageIds(includePrerelease);
+ CachePackageIds(packageIds, includePrerelease);
+ return packageIds;
+ }
+ }
+}
View
20 Website/Infrastructure/TaskFactoryWrapper.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Threading.Tasks;
+
+namespace NuGetGallery
+{
+ public class TaskFactoryWrapper : ITaskFactory
+ {
+ readonly TaskFactory _taskFactory;
+
+ public TaskFactoryWrapper(TaskFactory taskFactory)
+ {
+ _taskFactory = taskFactory;
+ }
+
+ public Task StartNew(Action action)
+ {
+ return _taskFactory.StartNew(action);
+ }
+ }
+}
View
1  Website/PackagesController.generated.cs
@@ -105,6 +105,7 @@ public class ActionNamesClass {
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public class ViewNames {
public readonly string _ListPackage = "~/Views/Packages/_ListPackage.cshtml";
+ public readonly string _PackageDependencies = "~/Views/Packages/_PackageDependencies.cshtml";
public readonly string ConfirmOwner = "~/Views/Packages/ConfirmOwner.cshtml";
public readonly string ContactOwners = "~/Views/Packages/ContactOwners.cshtml";
public readonly string Delete = "~/Views/Packages/Delete.cshtml";
View
38 Website/Queries/PackageIdsQuery.cs
@@ -1,29 +1,28 @@
using System.Collections.Generic;
using System.Data.Entity;
+using System.Linq;
namespace NuGetGallery
{
public interface IPackageIdsQuery
{
- IEnumerable<string> Execute(
- string partialId,
- bool? includePrerelease = false);
+ IEnumerable<string> Execute(bool? includePrerelease);
}
public class PackageIdsQuery : IPackageIdsQuery
{
- const string _partialIdSqlFormat = @"SELECT TOP 30 pr.ID
+ const string _sqlFormat =
+@"SELECT
+ pr.ID
FROM Packages p
JOIN PackageRegistrations pr on pr.[Key] = p.PackageRegistrationKey
-WHERE pr.ID LIKE {{0}}
- {0}
-GROUP BY pr.ID
-ORDER BY pr.ID";
- private const string _noPartialIdSql = @"SELECT TOP 30 pr.ID
-FROM Packages p
- JOIN PackageRegistrations pr on pr.[Key] = p.PackageRegistrationKey
-GROUP BY pr.ID
-ORDER BY MAX(pr.DownloadCount) DESC";
+WHERE
+ p.Listed = 1
+ {1}
+GROUP BY
+ pr.ID
+ORDER BY
+ MAX(pr.DownloadCount) DESC";
private readonly IEntitiesContext _entities;
public PackageIdsQuery(IEntitiesContext entities)
@@ -31,19 +30,14 @@ public PackageIdsQuery(IEntitiesContext entities)
_entities = entities;
}
- public IEnumerable<string> Execute(
- string partialId,
- bool? includePrerelease = false)
+ public IEnumerable<string> Execute(bool? includePrerelease = false)
{
var dbContext = (DbContext)_entities;
- if (string.IsNullOrWhiteSpace(partialId))
- return dbContext.Database.SqlQuery<string>(_noPartialIdSql);
-
var prereleaseFilter = string.Empty;
- if (!includePrerelease.HasValue || !includePrerelease.Value)
- prereleaseFilter = "AND p.IsPrerelease = {1}";
- return dbContext.Database.SqlQuery<string>(string.Format(_partialIdSqlFormat, prereleaseFilter), partialId + "%", includePrerelease ?? false);
+ if ((includePrerelease ?? false) == false)
+ prereleaseFilter = "AND p.IsPrerelease = 0";
+ return dbContext.Database.SqlQuery<string>(string.Format(_sqlFormat, prereleaseFilter));
}
}
}
View
6 Website/Services/PackageService.cs
@@ -18,6 +18,7 @@ public class PackageService : IPackageService
private readonly IPackageFileService packageFileSvc;
private readonly IEntityRepository<PackageOwnerRequest> packageOwnerRequestRepository;
private readonly IIndexingService indexingSvc;
+ private readonly IPackageCache packageCache;
public PackageService(
ICryptographyService cryptoSvc,
@@ -26,7 +27,8 @@ public class PackageService : IPackageService
IEntityRepository<PackageStatistics> packageStatsRepo,
IPackageFileService packageFileSvc,
IEntityRepository<PackageOwnerRequest> packageOwnerRequestRepository,
- IIndexingService indexingSvc)
+ IIndexingService indexingSvc,
+ IPackageCache packageCache)
{
this.cryptoSvc = cryptoSvc;
this.packageRegistrationRepo = packageRegistrationRepo;
@@ -35,6 +37,7 @@ public class PackageService : IPackageService
this.packageFileSvc = packageFileSvc;
this.packageOwnerRequestRepository = packageOwnerRequestRepository;
this.indexingSvc = indexingSvc;
+ this.packageCache = packageCache;
}
public Package CreatePackage(IPackage nugetPackage, User currentUser)
@@ -57,6 +60,7 @@ public Package CreatePackage(IPackage nugetPackage, User currentUser)
}
}
+ packageCache.AddPackage(package);
NotifyIndexingService();
return package;
View
19 Website/T4MVC.cs
@@ -253,8 +253,7 @@ public static class T4Extensions {
result.RouteValueDictionary.Add("Action", action);
}
- public static bool FileExists(string virtualPath)
- {
+ public static bool FileExists(string virtualPath) {
if (!HostingEnvironment.IsHosted) return false;
string filePath = HostingEnvironment.MapPath(virtualPath);
return System.IO.File.Exists(filePath);
@@ -304,28 +303,24 @@ public class T4MVC_ActionResult : System.Web.Mvc.ActionResult, IT4MVCActionResul
public RouteValueDictionary RouteValueDictionary { get; set; }
}
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
-public class T4MVC_JsonResult : System.Web.Mvc.JsonResult, IT4MVCActionResult
-{
- public T4MVC_JsonResult(string area, string controller, string action)
- : base()
- {
+public class T4MVC_JsonResult : System.Web.Mvc.JsonResult, IT4MVCActionResult {
+ public T4MVC_JsonResult(string area, string controller, string action): base() {
this.InitMVCT4Result(area, controller, action);
}
-
+
public string Controller { get; set; }
public string Action { get; set; }
public RouteValueDictionary RouteValueDictionary { get; set; }
}
+
namespace Links {
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public static class Scripts {
private const string URLPATH = "~/Scripts";
public static string Url() { return T4MVCHelpers.ProcessVirtualPath(URLPATH); }
public static string Url(string fileName) { return T4MVCHelpers.ProcessVirtualPath(URLPATH + "/" + fileName); }
- public static readonly string Home_js = T4MVCHelpers.IsProduction() && T4Extensions.FileExists(URLPATH + "/Home.min.js") ? Url("Home.min.js") : Url("Home.js");
-
public static readonly string jquery_1_6_2_vsdoc_js = T4MVCHelpers.IsProduction() && T4Extensions.FileExists(URLPATH + "/jquery-1.6.2-vsdoc.min.js") ? Url("jquery-1.6.2-vsdoc.min.js") : Url("jquery-1.6.2-vsdoc.js");
public static readonly string jquery_1_6_2_js = T4MVCHelpers.IsProduction() && T4Extensions.FileExists(URLPATH + "/jquery-1.6.2.min.js") ? Url("jquery-1.6.2.min.js") : Url("jquery-1.6.2.js");
@@ -341,8 +336,10 @@ public static class Scripts {
public static readonly string modernizr_2_0_6_development_only_js = T4MVCHelpers.IsProduction() && T4Extensions.FileExists(URLPATH + "/modernizr-2.0.6-development-only.min.js") ? Url("modernizr-2.0.6-development-only.min.js") : Url("modernizr-2.0.6-development-only.js");
+ public static readonly string stats_js = T4MVCHelpers.IsProduction() && T4Extensions.FileExists(URLPATH + "/stats.min.js") ? Url("stats.min.js") : Url("stats.js");
+
public static readonly string ZeroClipboard_js = T4MVCHelpers.IsProduction() && T4Extensions.FileExists(URLPATH + "/ZeroClipboard.min.js") ? Url("ZeroClipboard.min.js") : Url("ZeroClipboard.js");
-
+
public static readonly string ZeroClipboard_swf = Url("ZeroClipboard.swf");
}
View
2  Website/Web.config
@@ -27,6 +27,8 @@
<add key="Gallery:ReleaseSha" value="" />
<add key="Gallery:ReleaseTime" value="" />
<add key="Gallery:SiteRoot" value="http://nuget.org/" />
+ <add key="Gallery:AzureCacheServiceUrl" value="" />
+ <add key="Gallery:AzureCacheAuthenticationToken" value="" />
</appSettings>
<connectionStrings>
<add name="NuGetGallery" connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=NuGetGallery;Integrated Security=SSPI" providerName="System.Data.SqlClient" />
View
34 Website/Website.csproj
@@ -26,6 +26,10 @@
<UpgradeBackupLocation>
</UpgradeBackupLocation>
<OldToolsVersion>4.0</OldToolsVersion>
+ <IISExpressSSLPort />
+ <IISExpressAnonymousAuthentication />
+ <IISExpressWindowsAuthentication />
+ <IISExpressUseClassicPipelineMode />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -66,14 +70,29 @@
<HintPath>..\packages\EntityFramework.4.3.1\lib\net40\EntityFramework.dll</HintPath>
<EmbedInteropTypes>False</EmbedInteropTypes>
</Reference>
+ <Reference Include="Enyim.Caching">
+ <HintPath>..\packages\EnyimMemcached.2.11\lib\net35\Enyim.Caching.dll</HintPath>
+ </Reference>
<Reference Include="Lucene.Net">
<HintPath>..\packages\Lucene.Net.2.9.2.2\lib\net40\Lucene.Net.dll</HintPath>
</Reference>
<Reference Include="MarkdownSharp, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\MarkdownSharp.1.13.0.0\lib\35\MarkdownSharp.dll</HintPath>
</Reference>
+ <Reference Include="Microsoft.ApplicationServer.Caching.Client, Version=101.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\..\Users\drewmi\Desktop\WindowsAzure.Caching.1.7.0.0.nupkg\lib\net35-full\Microsoft.ApplicationServer.Caching.Client.dll</HintPath>
+ </Reference>
+ <Reference Include="Microsoft.ApplicationServer.Caching.Core, Version=101.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\..\Users\drewmi\Desktop\WindowsAzure.Caching.1.7.0.0.nupkg\lib\net35-full\Microsoft.ApplicationServer.Caching.Core.dll</HintPath>
+ </Reference>
<Reference Include="Microsoft.Build.Framework" />
<Reference Include="Microsoft.Build.Utilities.v4.0" />
+ <Reference Include="Microsoft.Web.DistributedCache, Version=101.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\..\Users\drewmi\Desktop\WindowsAzure.Caching.1.7.0.0.nupkg\lib\net35-full\Microsoft.Web.DistributedCache.dll</HintPath>
+ </Reference>
<Reference Include="Microsoft.Web.Helpers">
<HintPath>..\packages\microsoft-web-helpers.1.15\lib\Microsoft.Web.Helpers.dll</HintPath>
</Reference>
@@ -88,6 +107,14 @@
<Reference Include="Microsoft.WindowsAzure.StorageClient, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\WindowsAzure.Storage.1.6\lib\net35-full\Microsoft.WindowsAzure.StorageClient.dll</HintPath>
</Reference>
+ <Reference Include="Microsoft.WindowsFabric.Common, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\..\Users\drewmi\Desktop\WindowsAzure.Caching.1.7.0.0.nupkg\lib\net35-full\Microsoft.WindowsFabric.Common.dll</HintPath>
+ </Reference>
+ <Reference Include="Microsoft.WindowsFabric.Data.Common, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\..\Users\drewmi\Desktop\WindowsAzure.Caching.1.7.0.0.nupkg\lib\net35-full\Microsoft.WindowsFabric.Data.Common.dll</HintPath>
+ </Reference>
<Reference Include="MiniProfiler">
<HintPath>..\packages\MiniProfiler.2.0.0-rc1\lib\net40\MiniProfiler.dll</HintPath>
</Reference>
@@ -239,10 +266,16 @@
<Compile Include="Entities\CuratedPackage.cs" />
<Compile Include="Entities\GallerySetting.cs" />
<Compile Include="Entities\PackageFramework.cs" />
+ <Compile Include="Infrastructure\AzureCache.cs" />
+ <Compile Include="Infrastructure\CachedPackage.cs" />
<Compile Include="Infrastructure\HintAttribute.cs" />
<Compile Include="Infrastructure\HttpHeaderValueProvider.cs" />
<Compile Include="Infrastructure\HttpHeaderValueProviderFactory.cs" />
<Compile Include="Infrastructure\HttpStatusCodeWithBodyResult.cs" />
+ <Compile Include="Infrastructure\ICache.cs" />
+ <Compile Include="Infrastructure\ITaskFactory.cs" />
+ <Compile Include="Infrastructure\LocalCache.cs" />
+ <Compile Include="Infrastructure\PackageCache.cs" />
<Compile Include="Infrastructure\JsonNetResult.cs" />
<Compile Include="Infrastructure\Lucene\LuceneFileSystem.cs" />
<Compile Include="Infrastructure\Lucene\LuceneCommon.cs" />
@@ -251,6 +284,7 @@
<Compile Include="Infrastructure\Jobs\WorkItemCleanupJob.cs" />
<Compile Include="Infrastructure\Jobs\UpdateStatisticsJob.cs" />
<Compile Include="Infrastructure\Lucene\PackageIndexEntity.cs" />
+ <Compile Include="Infrastructure\TaskFactoryWrapper.cs" />
<Compile Include="JsonApiController.generated.cs">
<DependentUpon>T4MVC.tt</DependentUpon>
</Compile>
Please sign in to comment.
Something went wrong with that request. Please try again.