From 833d0487f2dcffba16db3279dde5d1c9ccec99e8 Mon Sep 17 00:00:00 2001 From: Shamus Taylor Date: Fri, 7 Jul 2023 19:30:53 -0500 Subject: [PATCH 1/6] Implement MySql storage for package contents Add a new storage type option that stores package contents in MySql along with the package metadata. Though MySql is not the best for storing large file contents, this is an alternative to storing package contents in the file system, which should be better for highly-available/distributed use cases. --- .../Entities/IPackageContentsContext.cs | 13 ++++ src/BaGet.Core/Entities/PackageContents.cs | 11 +++ .../Extensions/BaGetApplicationExtensions.cs | 6 ++ .../DependencyInjectionExtensions.cs | 6 ++ .../Storage/DatabaseStorageService.cs | 69 +++++++++++++++++++ .../MySqlApplicationExtensions.cs | 2 + src/BaGet.Database.MySql/MySqlContext.cs | 21 +++++- 7 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 src/BaGet.Core/Entities/IPackageContentsContext.cs create mode 100644 src/BaGet.Core/Entities/PackageContents.cs create mode 100644 src/BaGet.Core/Storage/DatabaseStorageService.cs diff --git a/src/BaGet.Core/Entities/IPackageContentsContext.cs b/src/BaGet.Core/Entities/IPackageContentsContext.cs new file mode 100644 index 000000000..add10b6a7 --- /dev/null +++ b/src/BaGet.Core/Entities/IPackageContentsContext.cs @@ -0,0 +1,13 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; + +namespace BaGet.Core +{ + public interface IPackageContentsContext + { + DbSet PackageContents { get; set; } + + Task SaveChangesAsync(CancellationToken cancellationToken); + } +} diff --git a/src/BaGet.Core/Entities/PackageContents.cs b/src/BaGet.Core/Entities/PackageContents.cs new file mode 100644 index 000000000..6c5a9ec2f --- /dev/null +++ b/src/BaGet.Core/Entities/PackageContents.cs @@ -0,0 +1,11 @@ +namespace BaGet.Core +{ + public class PackageContents + { + public int Key { get; set; } + + public string Path { get; set; } + + public byte[] Data { get; set; } + } +} diff --git a/src/BaGet.Core/Extensions/BaGetApplicationExtensions.cs b/src/BaGet.Core/Extensions/BaGetApplicationExtensions.cs index 7dc2967f3..822c56112 100644 --- a/src/BaGet.Core/Extensions/BaGetApplicationExtensions.cs +++ b/src/BaGet.Core/Extensions/BaGetApplicationExtensions.cs @@ -22,6 +22,12 @@ public static BaGetApplication AddFileStorage( return app; } + public static BaGetApplication AddMySqlStorage(this BaGetApplication app) + { + app.Services.TryAddTransient(provider => provider.GetRequiredService()); + return app; + } + public static BaGetApplication AddNullStorage(this BaGetApplication app) { app.Services.TryAddTransient(provider => provider.GetRequiredService()); diff --git a/src/BaGet.Core/Extensions/DependencyInjectionExtensions.cs b/src/BaGet.Core/Extensions/DependencyInjectionExtensions.cs index a56c41eab..0aa8de2a0 100644 --- a/src/BaGet.Core/Extensions/DependencyInjectionExtensions.cs +++ b/src/BaGet.Core/Extensions/DependencyInjectionExtensions.cs @@ -100,6 +100,7 @@ private static void AddBaGetServices(this IServiceCollection services) services.TryAddTransient(); services.TryAddTransient(); + services.TryAddTransient(); services.TryAddTransient(); services.TryAddTransient(); services.TryAddTransient(); @@ -133,6 +134,11 @@ private static void AddDefaultProviders(this IServiceCollection services) return provider.GetRequiredService(); } + if (configuration.HasStorageType("mysql")) + { + return provider.GetRequiredService(); + } + if (configuration.HasStorageType("null")) { return provider.GetRequiredService(); diff --git a/src/BaGet.Core/Storage/DatabaseStorageService.cs b/src/BaGet.Core/Storage/DatabaseStorageService.cs new file mode 100644 index 000000000..7f876d980 --- /dev/null +++ b/src/BaGet.Core/Storage/DatabaseStorageService.cs @@ -0,0 +1,69 @@ +using System.Linq; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; + +namespace BaGet.Core +{ + public class DatabaseStorageService : IStorageService + { + private readonly IPackageContentsContext _context; + + public DatabaseStorageService(IPackageContentsContext context) + { + if (context == null) throw new ArgumentException(nameof(context)); + + _context = context; + } + + public async Task DeleteAsync(string path, CancellationToken cancellationToken = default) + { + var contents = await _context.PackageContents.SingleOrDefaultAsync(p => p.Path == path, cancellationToken); + if (contents != null) + { + _context.PackageContents.Remove(contents); + await _context.SaveChangesAsync(cancellationToken); + } + } + + public async Task GetAsync(string path, CancellationToken cancellationToken = default) + { + var contents = await _context.PackageContents.SingleOrDefaultAsync(p => p.Path == path, cancellationToken); + var ms = new MemoryStream(contents.Data); + return ms; + } + + public Task GetDownloadUriAsync(string path, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public async Task PutAsync(string path, Stream content, string contentType, CancellationToken cancellationToken = default) + { + byte[] newData; + using (var binaryReader = new BinaryReader(content)) + { + newData = binaryReader.ReadBytes((int)content.Length); + } + + var existingContents = await _context.PackageContents.SingleOrDefaultAsync(p => p.Path == path, cancellationToken); + if (existingContents != null) + { + return existingContents.Data.SequenceEqual(newData) + ? StoragePutResult.AlreadyExists + : StoragePutResult.Success; + } + + _context.PackageContents.Add(new PackageContents + { + Path = path, + Data = newData, + }); + await _context.SaveChangesAsync(cancellationToken); + + return StoragePutResult.Success; + } + } +} diff --git a/src/BaGet.Database.MySql/MySqlApplicationExtensions.cs b/src/BaGet.Database.MySql/MySqlApplicationExtensions.cs index 2156d90c3..c0fb5f2ee 100644 --- a/src/BaGet.Database.MySql/MySqlApplicationExtensions.cs +++ b/src/BaGet.Database.MySql/MySqlApplicationExtensions.cs @@ -18,6 +18,8 @@ public static BaGetApplication AddMySqlDatabase(this BaGetApplication app) options.UseMySql(databaseOptions.Value.ConnectionString); }); + app.Services.AddTransient(services => services.GetRequiredService()); + return app; } diff --git a/src/BaGet.Database.MySql/MySqlContext.cs b/src/BaGet.Database.MySql/MySqlContext.cs index 49bbb65fb..e297cde79 100644 --- a/src/BaGet.Database.MySql/MySqlContext.cs +++ b/src/BaGet.Database.MySql/MySqlContext.cs @@ -1,16 +1,19 @@ using BaGet.Core; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; using MySql.Data.MySqlClient; namespace BaGet.Database.MySql { - public class MySqlContext : AbstractContext + public class MySqlContext : AbstractContext, IPackageContentsContext { /// /// The MySQL Server error code for when a unique constraint is violated. /// private const int UniqueConstraintViolationErrorCode = 1062; + public DbSet PackageContents { get; set; } + public MySqlContext(DbContextOptions options) : base(options) { } @@ -26,5 +29,21 @@ public override bool IsUniqueConstraintViolationException(DbUpdateException exce /// See: https://dev.mysql.com/doc/refman/8.0/en/subquery-restrictions.html /// public override bool SupportsLimitInSubqueries => false; + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + builder.Entity(BuildPackageContentsEntity); + } + + private void BuildPackageContentsEntity(EntityTypeBuilder packageContents) + { + packageContents.HasKey(p => p.Key); + packageContents.HasIndex(p => new { p.Path }).IsUnique(); + + packageContents.Property(p => p.Path) + .HasMaxLength((MaxPackageIdLength + MaxPackageVersionLength) * 2 + 20); + } } } From f56a58e7753730ef093729d55e0dd7d52395a3df Mon Sep 17 00:00:00 2001 From: Shamus Taylor Date: Mon, 10 Jul 2023 14:58:55 +0000 Subject: [PATCH 2/6] Add migration --- ...506_CreatePackageContentsTable.Designer.cs | 261 ++++++++++++++++++ ...230710145506_CreatePackageContentsTable.cs | 58 ++++ .../Migrations/MySqlContextModelSnapshot.cs | 21 ++ 3 files changed, 340 insertions(+) create mode 100644 src/BaGet.Database.MySql/Migrations/20230710145506_CreatePackageContentsTable.Designer.cs create mode 100644 src/BaGet.Database.MySql/Migrations/20230710145506_CreatePackageContentsTable.cs diff --git a/src/BaGet.Database.MySql/Migrations/20230710145506_CreatePackageContentsTable.Designer.cs b/src/BaGet.Database.MySql/Migrations/20230710145506_CreatePackageContentsTable.Designer.cs new file mode 100644 index 000000000..85a39ed56 --- /dev/null +++ b/src/BaGet.Database.MySql/Migrations/20230710145506_CreatePackageContentsTable.Designer.cs @@ -0,0 +1,261 @@ +// +using System; +using BaGet.Database.MySql; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace BaGet.Database.MySql.Migrations +{ + [DbContext(typeof(MySqlContext))] + [Migration("20230710145506_CreatePackageContentsTable")] + partial class CreatePackageContentsTable + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.18") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("BaGet.Core.Package", b => + { + b.Property("Key") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Authors") + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(4000); + + b.Property("Description") + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(4000); + + b.Property("Downloads") + .HasColumnType("bigint"); + + b.Property("HasEmbeddedIcon") + .HasColumnType("tinyint(1)"); + + b.Property("HasReadme") + .HasColumnType("tinyint(1)"); + + b.Property("IconUrl") + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(4000); + + b.Property("Id") + .IsRequired() + .HasColumnType("varchar(128) CHARACTER SET utf8mb4") + .HasMaxLength(128); + + b.Property("IsPrerelease") + .HasColumnType("tinyint(1)"); + + b.Property("Language") + .HasColumnType("varchar(20) CHARACTER SET utf8mb4") + .HasMaxLength(20); + + b.Property("LicenseUrl") + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(4000); + + b.Property("Listed") + .HasColumnType("tinyint(1)"); + + b.Property("MinClientVersion") + .HasColumnType("varchar(44) CHARACTER SET utf8mb4") + .HasMaxLength(44); + + b.Property("NormalizedVersionString") + .IsRequired() + .HasColumnName("Version") + .HasColumnType("varchar(64) CHARACTER SET utf8mb4") + .HasMaxLength(64); + + b.Property("OriginalVersionString") + .HasColumnName("OriginalVersion") + .HasColumnType("varchar(64) CHARACTER SET utf8mb4") + .HasMaxLength(64); + + b.Property("ProjectUrl") + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(4000); + + b.Property("Published") + .HasColumnType("datetime(6)"); + + b.Property("ReleaseNotes") + .HasColumnName("ReleaseNotes") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("RepositoryType") + .HasColumnType("varchar(100) CHARACTER SET utf8mb4") + .HasMaxLength(100); + + b.Property("RepositoryUrl") + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(4000); + + b.Property("RequireLicenseAcceptance") + .HasColumnType("tinyint(1)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("timestamp(6)"); + + b.Property("SemVerLevel") + .HasColumnType("int"); + + b.Property("Summary") + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(4000); + + b.Property("Tags") + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(4000); + + b.Property("Title") + .HasColumnType("varchar(256) CHARACTER SET utf8mb4") + .HasMaxLength(256); + + b.HasKey("Key"); + + b.HasIndex("Id"); + + b.HasIndex("Id", "NormalizedVersionString") + .IsUnique(); + + b.ToTable("Packages"); + }); + + modelBuilder.Entity("BaGet.Core.PackageContents", b => + { + b.Property("Key") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Data") + .HasColumnType("longblob"); + + b.Property("Path") + .HasColumnType("varchar(404) CHARACTER SET utf8mb4") + .HasMaxLength(404); + + b.HasKey("Key"); + + b.HasIndex("Path") + .IsUnique(); + + b.ToTable("PackageContents"); + }); + + modelBuilder.Entity("BaGet.Core.PackageDependency", b => + { + b.Property("Key") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Id") + .HasColumnType("varchar(128) CHARACTER SET utf8mb4") + .HasMaxLength(128); + + b.Property("PackageKey") + .HasColumnType("int"); + + b.Property("TargetFramework") + .HasColumnType("varchar(256) CHARACTER SET utf8mb4") + .HasMaxLength(256); + + b.Property("VersionRange") + .HasColumnType("varchar(256) CHARACTER SET utf8mb4") + .HasMaxLength(256); + + b.HasKey("Key"); + + b.HasIndex("Id"); + + b.HasIndex("PackageKey"); + + b.ToTable("PackageDependencies"); + }); + + modelBuilder.Entity("BaGet.Core.PackageType", b => + { + b.Property("Key") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Name") + .HasColumnType("varchar(512) CHARACTER SET utf8mb4") + .HasMaxLength(512); + + b.Property("PackageKey") + .HasColumnType("int"); + + b.Property("Version") + .HasColumnType("varchar(64) CHARACTER SET utf8mb4") + .HasMaxLength(64); + + b.HasKey("Key"); + + b.HasIndex("Name"); + + b.HasIndex("PackageKey"); + + b.ToTable("PackageTypes"); + }); + + modelBuilder.Entity("BaGet.Core.TargetFramework", b => + { + b.Property("Key") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Moniker") + .HasColumnType("varchar(256) CHARACTER SET utf8mb4") + .HasMaxLength(256); + + b.Property("PackageKey") + .HasColumnType("int"); + + b.HasKey("Key"); + + b.HasIndex("Moniker"); + + b.HasIndex("PackageKey"); + + b.ToTable("TargetFrameworks"); + }); + + modelBuilder.Entity("BaGet.Core.PackageDependency", b => + { + b.HasOne("BaGet.Core.Package", "Package") + .WithMany("Dependencies") + .HasForeignKey("PackageKey"); + }); + + modelBuilder.Entity("BaGet.Core.PackageType", b => + { + b.HasOne("BaGet.Core.Package", "Package") + .WithMany("PackageTypes") + .HasForeignKey("PackageKey") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("BaGet.Core.TargetFramework", b => + { + b.HasOne("BaGet.Core.Package", "Package") + .WithMany("TargetFrameworks") + .HasForeignKey("PackageKey") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/BaGet.Database.MySql/Migrations/20230710145506_CreatePackageContentsTable.cs b/src/BaGet.Database.MySql/Migrations/20230710145506_CreatePackageContentsTable.cs new file mode 100644 index 000000000..1e8cc1161 --- /dev/null +++ b/src/BaGet.Database.MySql/Migrations/20230710145506_CreatePackageContentsTable.cs @@ -0,0 +1,58 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace BaGet.Database.MySql.Migrations +{ + public partial class CreatePackageContentsTable : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "RowVersion", + table: "Packages", + rowVersion: true, + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp(6)", + oldNullable: true) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.ComputedColumn); + + migrationBuilder.CreateTable( + name: "PackageContents", + columns: table => new + { + Key = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Path = table.Column(maxLength: 404, nullable: true), + Data = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PackageContents", x => x.Key); + }); + + migrationBuilder.CreateIndex( + name: "IX_PackageContents_Path", + table: "PackageContents", + column: "Path", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "PackageContents"); + + migrationBuilder.AlterColumn( + name: "RowVersion", + table: "Packages", + type: "timestamp(6)", + nullable: true, + oldClrType: typeof(DateTime), + oldRowVersion: true, + oldNullable: true) + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.ComputedColumn); + } + } +} diff --git a/src/BaGet.Database.MySql/Migrations/MySqlContextModelSnapshot.cs b/src/BaGet.Database.MySql/Migrations/MySqlContextModelSnapshot.cs index d9a6e93c8..8c35aaa55 100644 --- a/src/BaGet.Database.MySql/Migrations/MySqlContextModelSnapshot.cs +++ b/src/BaGet.Database.MySql/Migrations/MySqlContextModelSnapshot.cs @@ -130,6 +130,27 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Packages"); }); + modelBuilder.Entity("BaGet.Core.PackageContents", b => + { + b.Property("Key") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Data") + .HasColumnType("longblob"); + + b.Property("Path") + .HasColumnType("varchar(404) CHARACTER SET utf8mb4") + .HasMaxLength(404); + + b.HasKey("Key"); + + b.HasIndex("Path") + .IsUnique(); + + b.ToTable("PackageContents"); + }); + modelBuilder.Entity("BaGet.Core.PackageDependency", b => { b.Property("Key") From e79e65c630da991799e345ec7f9bec0d35caf387 Mon Sep 17 00:00:00 2001 From: Shamus Taylor Date: Mon, 10 Jul 2023 15:23:14 +0000 Subject: [PATCH 3/6] fix migration; add valid storage type --- ...=> 20230710152239_CreatePackageContentsTable.Designer.cs} | 5 ++--- ...Table.cs => 20230710152239_CreatePackageContentsTable.cs} | 2 +- .../Migrations/MySqlContextModelSnapshot.cs | 3 +-- src/BaGet.Database.MySql/MySqlContext.cs | 2 +- src/BaGet/ConfigureBaGetOptions.cs | 1 + 5 files changed, 6 insertions(+), 7 deletions(-) rename src/BaGet.Database.MySql/Migrations/{20230710145506_CreatePackageContentsTable.Designer.cs => 20230710152239_CreatePackageContentsTable.Designer.cs} (98%) rename src/BaGet.Database.MySql/Migrations/{20230710145506_CreatePackageContentsTable.cs => 20230710152239_CreatePackageContentsTable.cs} (95%) diff --git a/src/BaGet.Database.MySql/Migrations/20230710145506_CreatePackageContentsTable.Designer.cs b/src/BaGet.Database.MySql/Migrations/20230710152239_CreatePackageContentsTable.Designer.cs similarity index 98% rename from src/BaGet.Database.MySql/Migrations/20230710145506_CreatePackageContentsTable.Designer.cs rename to src/BaGet.Database.MySql/Migrations/20230710152239_CreatePackageContentsTable.Designer.cs index 85a39ed56..b039b6fa2 100644 --- a/src/BaGet.Database.MySql/Migrations/20230710145506_CreatePackageContentsTable.Designer.cs +++ b/src/BaGet.Database.MySql/Migrations/20230710152239_CreatePackageContentsTable.Designer.cs @@ -9,7 +9,7 @@ namespace BaGet.Database.MySql.Migrations { [DbContext(typeof(MySqlContext))] - [Migration("20230710145506_CreatePackageContentsTable")] + [Migration("20230710152239_CreatePackageContentsTable")] partial class CreatePackageContentsTable { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -142,8 +142,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasColumnType("longblob"); b.Property("Path") - .HasColumnType("varchar(404) CHARACTER SET utf8mb4") - .HasMaxLength(404); + .HasColumnType("varchar(255)"); b.HasKey("Key"); diff --git a/src/BaGet.Database.MySql/Migrations/20230710145506_CreatePackageContentsTable.cs b/src/BaGet.Database.MySql/Migrations/20230710152239_CreatePackageContentsTable.cs similarity index 95% rename from src/BaGet.Database.MySql/Migrations/20230710145506_CreatePackageContentsTable.cs rename to src/BaGet.Database.MySql/Migrations/20230710152239_CreatePackageContentsTable.cs index 1e8cc1161..3d4fdd72e 100644 --- a/src/BaGet.Database.MySql/Migrations/20230710145506_CreatePackageContentsTable.cs +++ b/src/BaGet.Database.MySql/Migrations/20230710152239_CreatePackageContentsTable.cs @@ -24,7 +24,7 @@ protected override void Up(MigrationBuilder migrationBuilder) { Key = table.Column(nullable: false) .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), - Path = table.Column(maxLength: 404, nullable: true), + Path = table.Column(type: "varchar(255)", nullable: true), Data = table.Column(nullable: true) }, constraints: table => diff --git a/src/BaGet.Database.MySql/Migrations/MySqlContextModelSnapshot.cs b/src/BaGet.Database.MySql/Migrations/MySqlContextModelSnapshot.cs index 8c35aaa55..85314793b 100644 --- a/src/BaGet.Database.MySql/Migrations/MySqlContextModelSnapshot.cs +++ b/src/BaGet.Database.MySql/Migrations/MySqlContextModelSnapshot.cs @@ -140,8 +140,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("longblob"); b.Property("Path") - .HasColumnType("varchar(404) CHARACTER SET utf8mb4") - .HasMaxLength(404); + .HasColumnType("varchar(255)"); b.HasKey("Key"); diff --git a/src/BaGet.Database.MySql/MySqlContext.cs b/src/BaGet.Database.MySql/MySqlContext.cs index e297cde79..55426281d 100644 --- a/src/BaGet.Database.MySql/MySqlContext.cs +++ b/src/BaGet.Database.MySql/MySqlContext.cs @@ -43,7 +43,7 @@ private void BuildPackageContentsEntity(EntityTypeBuilder packa packageContents.HasIndex(p => new { p.Path }).IsUnique(); packageContents.Property(p => p.Path) - .HasMaxLength((MaxPackageIdLength + MaxPackageVersionLength) * 2 + 20); + .HasColumnType("varchar(255)"); } } } diff --git a/src/BaGet/ConfigureBaGetOptions.cs b/src/BaGet/ConfigureBaGetOptions.cs index b28a658d4..254afaf28 100644 --- a/src/BaGet/ConfigureBaGetOptions.cs +++ b/src/BaGet/ConfigureBaGetOptions.cs @@ -41,6 +41,7 @@ private static readonly HashSet ValidStorageTypes "AzureBlobStorage", "Filesystem", "GoogleCloud", + "MySql", "Null", }; From d77ef4df5565217385dc9f858b417d4b2e8defbc Mon Sep 17 00:00:00 2001 From: Shamus Taylor Date: Mon, 10 Jul 2023 16:02:01 +0000 Subject: [PATCH 4/6] error handling --- src/BaGet.Core/Storage/DatabaseStorageService.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/BaGet.Core/Storage/DatabaseStorageService.cs b/src/BaGet.Core/Storage/DatabaseStorageService.cs index 7f876d980..31395ea80 100644 --- a/src/BaGet.Core/Storage/DatabaseStorageService.cs +++ b/src/BaGet.Core/Storage/DatabaseStorageService.cs @@ -31,6 +31,10 @@ public async Task DeleteAsync(string path, CancellationToken cancellationToken = public async Task GetAsync(string path, CancellationToken cancellationToken = default) { var contents = await _context.PackageContents.SingleOrDefaultAsync(p => p.Path == path, cancellationToken); + if (contents == null) + { + throw new Exception($"PackageContents record not found for path: {path}"); + } var ms = new MemoryStream(contents.Data); return ms; } From b96b0f85e44d921dc844d41991afcc5466c34a35 Mon Sep 17 00:00:00 2001 From: Shamus Taylor Date: Tue, 11 Jul 2023 13:35:47 -0500 Subject: [PATCH 5/6] Use conflict --- src/BaGet.Core/Storage/DatabaseStorageService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BaGet.Core/Storage/DatabaseStorageService.cs b/src/BaGet.Core/Storage/DatabaseStorageService.cs index 31395ea80..7206cfc87 100644 --- a/src/BaGet.Core/Storage/DatabaseStorageService.cs +++ b/src/BaGet.Core/Storage/DatabaseStorageService.cs @@ -57,7 +57,7 @@ public async Task PutAsync(string path, Stream content, string { return existingContents.Data.SequenceEqual(newData) ? StoragePutResult.AlreadyExists - : StoragePutResult.Success; + : StoragePutResult.Conflict; } _context.PackageContents.Add(new PackageContents From 23fbe04c62b31477cbe901e7cdca46fb8a720bc7 Mon Sep 17 00:00:00 2001 From: Shamus Taylor Date: Tue, 11 Jul 2023 15:00:52 -0500 Subject: [PATCH 6/6] addmysqlstorage --- src/BaGet/Startup.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/BaGet/Startup.cs b/src/BaGet/Startup.cs index f637267bb..ff70fb35c 100644 --- a/src/BaGet/Startup.cs +++ b/src/BaGet/Startup.cs @@ -69,6 +69,7 @@ private void ConfigureBaGetApplication(BaGetApplication app) app.AddAwsS3Storage(); app.AddAzureBlobStorage(); app.AddGoogleCloudStorage(); + app.AddMySqlStorage(); // Add search providers. app.AddAzureSearch();