Skip to content
Merged
2 changes: 1 addition & 1 deletion Refresh.Core/Extensions/PhotoExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public static SerializedPhoto FromGamePhoto(GamePhoto photo, DataContext dataCon
MediumHash = dataContext.Database.GetAssetFromHash(photo.MediumAsset.AssetHash)?.GetAsPhoto(dataContext.Game, dataContext) ?? photo.MediumAsset.AssetHash,
SmallHash = dataContext.Database.GetAssetFromHash(photo.SmallAsset.AssetHash)?.GetAsPhoto(dataContext.Game, dataContext) ?? photo.SmallAsset.AssetHash,
PlanHash = photo.PlanHash,
PhotoSubjects = new List<SerializedPhotoSubject>(photo.Subjects.Count),
PhotoSubjects = [],
};

foreach (GamePhotoSubject subject in photo.Subjects)
Expand Down
185 changes: 143 additions & 42 deletions Refresh.Database/GameDatabaseContext.Photos.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Refresh.Database.Models.Photos;
using Refresh.Database.Query;
using Refresh.Database.Helpers;
using Bunkum.Core;

namespace Refresh.Database;

Expand All @@ -19,14 +20,21 @@ public partial class GameDatabaseContext // Photos
.Include(p => p.Level)
.Include(p => p.Level!.Publisher)
.Include(p => p.Level!.Publisher!.Statistics)
.Include(p => p.Subject1User)
.Include(p => p.Subject1User!.Statistics)
.Include(p => p.Subject2User)
.Include(p => p.Subject2User!.Statistics)
.Include(p => p.Subject3User)
.Include(p => p.Subject3User!.Statistics)
.Include(p => p.Subject4User)
.Include(p => p.Subject4User!.Statistics);
.Include(p => p.Subjects.OrderBy(s => s.PlayerId));

private IQueryable<GamePhotoSubject> GamePhotoSubjectsIncluded => this.GamePhotoSubjects
.Include(p => p.User)
.Include(p => p.User!.Statistics)
.Include(p => p.Photo)
.Include(p => p.Photo!.Publisher)
.Include(p => p.Photo!.Publisher.Statistics)
.Include(p => p.Photo!.Level)
.Include(p => p.Photo!.Level!.Publisher)
.Include(p => p.Photo!.Level!.Publisher!.Statistics)
.Include(p => p.Photo!.LargeAsset)
.Include(p => p.Photo!.MediumAsset)
.Include(p => p.Photo!.SmallAsset)
.Include(p => p.Photo!.Subjects.OrderBy(s => s.PlayerId));

public GamePhoto UploadPhoto(IPhotoUpload photo, IEnumerable<IPhotoUploadSubject> subjects, GameUser publisher, GameLevel? level)
{
Expand All @@ -47,29 +55,6 @@ public GamePhoto UploadPhoto(IPhotoUpload photo, IEnumerable<IPhotoUploadSubject
PublishedAt = this._time.Now,
};

List<GamePhotoSubject> gameSubjects = new(subjects.Count());
foreach (IPhotoUploadSubject subject in subjects)
{
GameUser? subjectUser = null;

if (!string.IsNullOrEmpty(subject.Username))
subjectUser = this.GetUserByUsername(subject.Username);

float[] bounds = PhotoHelper.ParseBoundsList(subject.BoundsList);

gameSubjects.Add(new GamePhotoSubject(subjectUser, subject.DisplayName, bounds));

if (subjectUser != null)
{
this.WriteEnsuringStatistics(subjectUser, () =>
{
subjectUser.Statistics!.PhotosWithUserCount++;
});
}
}

newPhoto.Subjects = gameSubjects;

this.WriteEnsuringStatistics(publisher, () =>
{
this.GamePhotos.Add(newPhoto);
Expand All @@ -87,22 +72,124 @@ public GamePhoto UploadPhoto(IPhotoUpload photo, IEnumerable<IPhotoUploadSubject
}
});
}

List<IPhotoUploadSubject> subjectsList = subjects.ToList();
List<GamePhotoSubject> finalSubjectsList = [];

// Take care of subjects after saving the photo itself to keep the photo, even if its subjects are malformed
for (int i = 0; i < subjectsList.Count; i++)
{
IPhotoUploadSubject subject = subjectsList[i];

float[] bounds = new float[PhotoHelper.SubjectBoundaryCount];
try
{
bounds = PhotoHelper.ParseBoundsList(subject.BoundsList);
}
catch (Exception ex)
{
this._logger.LogWarning(BunkumCategory.UserPhotos, $"Could not parse {subject.DisplayName}'s photo bounds: {ex.GetType()} - {ex.Message}");
}

GameUser? subjectUser = string.IsNullOrWhiteSpace(subject.Username) ? null : this.GetUserByUsername(subject.Username);
finalSubjectsList.Add(new()
{
Photo = newPhoto,
User = subjectUser,
DisplayName = subject.DisplayName,
PlayerId = i + 1, // Player number 1 - 4
Bounds = bounds
});

if (subjectUser != null)
{
this.WriteEnsuringStatistics(subjectUser, () =>
{
subjectUser.Statistics!.PhotosWithUserCount++;
});
}
}

this.GamePhotoSubjects.AddRange(finalSubjectsList);
this.SaveChanges();

this.CreatePhotoUploadEvent(publisher, newPhoto);

newPhoto.Subjects = finalSubjectsList.ToList();
return newPhoto;
}

/// <remarks>
/// Migration only!!
/// </remarks>
public void MigratePhotoSubjects(GamePhoto photo, bool saveChanges)
{
List<GamePhotoSubject> subjects = [];

#pragma warning disable CS0618 // obsoletion

// If DisplayName is not null, there is a subject in that spot
if (photo.Subject1DisplayName != null)
{
subjects.Add(new()
{
Photo = photo,
PlayerId = 1,
DisplayName = photo.Subject1DisplayName,
User = photo.Subject1User,
Bounds = photo.Subject1Bounds,
});
}

if (photo.Subject2DisplayName != null)
{
subjects.Add(new()
{
Photo = photo,
PlayerId = 2,
DisplayName = photo.Subject2DisplayName,
User = photo.Subject2User,
Bounds = photo.Subject2Bounds,
});
}

if (photo.Subject3DisplayName != null)
{
subjects.Add(new()
{
Photo = photo,
PlayerId = 3,
DisplayName = photo.Subject3DisplayName,
User = photo.Subject3User,
Bounds = photo.Subject3Bounds,
});
}

if (photo.Subject4DisplayName != null)
{
subjects.Add(new()
{
Photo = photo,
PlayerId = 4,
DisplayName = photo.Subject4DisplayName,
User = photo.Subject4User,
Bounds = photo.Subject4Bounds,
});
}
#pragma warning restore CS0618

this.GamePhotoSubjects.AddRange(subjects);
if (saveChanges) this.SaveChanges();
}

public void RemovePhoto(GamePhoto photo)
{
foreach (GamePhotoSubject subject in photo.Subjects)
foreach (GameUser subjectUser in this.GetUsersInPhoto(photo).ToArray())
{
if (subject.User != null)
this.WriteEnsuringStatistics(subjectUser, () =>
{
this.WriteEnsuringStatistics(subject.User, () =>
{
subject.User.Statistics!.PhotosWithUserCount--;
});
}
subjectUser.Statistics!.PhotosWithUserCount--;
});
}

if (photo.Level != null)
Expand All @@ -124,6 +211,9 @@ public void RemovePhoto(GamePhoto photo)

// Remove all events referencing the photo
this.Events.RemoveRange(photoEvents);

// Remove all subjects
this.GamePhotoSubjects.RemoveRange(s => s.PhotoId == photo.PhotoId);

// Remove the photo
this.GamePhotos.Remove(photo);
Expand All @@ -132,6 +222,17 @@ public void RemovePhoto(GamePhoto photo)
});
}

public IQueryable<GamePhotoSubject> GetSubjectsInPhoto(GamePhoto photo)
=> this.GamePhotoSubjectsIncluded
.Where(s => s.PhotoId == photo.PhotoId)
.OrderBy(s => s.PlayerId);

public IQueryable<GameUser> GetUsersInPhoto(GamePhoto photo)
=> this.GetSubjectsInPhoto(photo)
.Where(s => s.User != null)
.OrderBy(s => s.PlayerId)
.Select(s => s.User!);

public int GetTotalPhotoCount() => this.GamePhotos.Count();

[Pure]
Expand All @@ -155,14 +256,14 @@ public int GetTotalPhotosByUser(GameUser user)

[Pure]
public DatabaseList<GamePhoto> GetPhotosWithUser(GameUser user, int count, int skip) =>
new(this.GamePhotosIncluded
.Where(p => p.Subject1UserId == user.UserId || p.Subject2UserId == user.UserId || p.Subject3UserId == user.UserId || p.Subject4UserId == user.UserId)
new(this.GamePhotoSubjectsIncluded
.Where(s => s.UserId == user.UserId)
.Select(s => s.Photo)
.OrderByDescending(p => p.TakenAt), skip, count);

[Pure]
public int GetTotalPhotosWithUser(GameUser user)
=> this.GamePhotos
.Count(p => p.Subject1UserId == user.UserId || p.Subject2UserId == user.UserId || p.Subject3UserId == user.UserId || p.Subject4UserId == user.UserId);
=> this.GamePhotoSubjects.Count(s => s.UserId == user.UserId);

[Pure]
public DatabaseList<GamePhoto> GetPhotosInLevel(GameLevel level, int count, int skip)
Expand Down
9 changes: 5 additions & 4 deletions Refresh.Database/GameDatabaseContext.Users.cs
Original file line number Diff line number Diff line change
Expand Up @@ -401,10 +401,11 @@ public void DeleteUser(GameUser user)
user.PsnAuthenticationAllowed = false;
user.RpcnAuthenticationAllowed = false;

foreach (GamePhoto photo in this.GetPhotosWithUser(user, int.MaxValue, 0).Items)
foreach (GamePhotoSubject subject in photo.Subjects.Where(s => s.User?.UserId == user.UserId))
subject.User = null;

foreach (GamePhotoSubject subject in this.GamePhotoSubjects.Where(s => s.UserId == user.UserId).ToList())
{
subject.UserId = null;
}

this.FavouriteLevelRelations.RemoveRange(r => r.User == user);
this.FavouriteUserRelations.RemoveRange(r => r.UserToFavourite == user);
this.FavouriteUserRelations.RemoveRange(r => r.UserFavouriting == user);
Expand Down
1 change: 1 addition & 0 deletions Refresh.Database/GameDatabaseContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public partial class GameDatabaseContext : DbContext, IDatabaseContext
internal DbSet<GameAsset> GameAssets { get; set; }
internal DbSet<GameNotification> GameNotifications { get; set; }
internal DbSet<GamePhoto> GamePhotos { get; set; }
internal DbSet<GamePhotoSubject> GamePhotoSubjects { get; set; }
internal DbSet<GameIpVerificationRequest> GameIpVerificationRequests { get; set; }
internal DbSet<GameAnnouncement> GameAnnouncements { get; set; }
internal DbSet<QueuedRegistration> QueuedRegistrations { get; set; }
Expand Down
2 changes: 1 addition & 1 deletion Refresh.Database/Helpers/PhotoHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public static float[] ParseBoundsList(string input)
string boundaryStr = boundsStr[i];

if (!float.TryParse(boundaryStr, NumberFormatInfo.InvariantInfo, out float f))
throw new FormatException($"Boundary {boundaryStr} ({i+1}/{SubjectBoundaryCount}) is not a float");
throw new FormatException($"Boundary '{boundaryStr}' ({i+1}/{SubjectBoundaryCount}) is not a float");

boundsParsed[i] = f;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;

#nullable disable

namespace Refresh.Database.Migrations
{
/// <inheritdoc />
[DbContext(typeof(GameDatabaseContext))]
[Migration("20260309162631_SplitGamePhotoSubjects")]
public partial class SplitGamePhotoSubjects : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "GamePhotoSubjects",
columns: table => new
{
PhotoId = table.Column<int>(type: "integer", nullable: false),
PlayerId = table.Column<int>(type: "integer", nullable: false),
UserId = table.Column<string>(type: "text", nullable: true),
DisplayName = table.Column<string>(type: "text", nullable: false),
Bounds = table.Column<float[]>(type: "real[]", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_GamePhotoSubjects", x => new { x.PhotoId, x.PlayerId });
table.ForeignKey(
name: "FK_GamePhotoSubjects_GamePhotos_PhotoId",
column: x => x.PhotoId,
principalTable: "GamePhotos",
principalColumn: "PhotoId",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_GamePhotoSubjects_GameUsers_UserId",
column: x => x.UserId,
principalTable: "GameUsers",
principalColumn: "UserId");
});

migrationBuilder.CreateIndex(
name: "IX_GamePhotoSubjects_UserId",
table: "GamePhotoSubjects",
column: "UserId");
}

/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "GamePhotoSubjects");
}
}
}
Loading