Merge branch 'master' into bypass-local-metadata-cache

This commit is contained in:
Dean Herbert
2022-07-29 16:05:54 +09:00
committed by GitHub
85 changed files with 1366 additions and 1282 deletions

View File

@ -0,0 +1,169 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Game.Collections;
using osu.Game.IO.Legacy;
using osu.Game.Overlays.Notifications;
namespace osu.Game.Database
{
public class LegacyCollectionImporter
{
public Action<Notification>? PostNotification { protected get; set; }
private readonly RealmAccess realm;
private const string database_name = "collection.db";
public LegacyCollectionImporter(RealmAccess realm)
{
this.realm = realm;
}
public Task<int> GetAvailableCount(Storage storage)
{
if (!storage.Exists(database_name))
return Task.FromResult(0);
return Task.Run(() =>
{
using (var stream = storage.GetStream(database_name))
return readCollections(stream).Count;
});
}
/// <summary>
/// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future.
/// </summary>
public Task ImportFromStorage(Storage storage)
{
if (!storage.Exists(database_name))
{
// This handles situations like when the user does not have a collections.db file
Logger.Log($"No {database_name} available in osu!stable installation", LoggingTarget.Information, LogLevel.Error);
return Task.CompletedTask;
}
return Task.Run(async () =>
{
using (var stream = storage.GetStream(database_name))
await Import(stream).ConfigureAwait(false);
});
}
public async Task Import(Stream stream)
{
var notification = new ProgressNotification
{
State = ProgressNotificationState.Active,
Text = "Collections import is initialising..."
};
PostNotification?.Invoke(notification);
var importedCollections = readCollections(stream, notification);
await importCollections(importedCollections).ConfigureAwait(false);
notification.CompletionText = $"Imported {importedCollections.Count} collections";
notification.State = ProgressNotificationState.Completed;
}
private Task importCollections(List<BeatmapCollection> newCollections)
{
var tcs = new TaskCompletionSource<bool>();
try
{
realm.Write(r =>
{
foreach (var collection in newCollections)
{
var existing = r.All<BeatmapCollection>().FirstOrDefault(c => c.Name == collection.Name);
if (existing != null)
{
foreach (string newBeatmap in existing.BeatmapMD5Hashes)
{
if (!existing.BeatmapMD5Hashes.Contains(newBeatmap))
existing.BeatmapMD5Hashes.Add(newBeatmap);
}
}
else
r.Add(collection);
}
});
tcs.SetResult(true);
}
catch (Exception e)
{
Logger.Error(e, "Failed to import collection.");
tcs.SetException(e);
}
return tcs.Task;
}
private List<BeatmapCollection> readCollections(Stream stream, ProgressNotification? notification = null)
{
if (notification != null)
{
notification.Text = "Reading collections...";
notification.Progress = 0;
}
var result = new List<BeatmapCollection>();
try
{
using (var sr = new SerializationReader(stream))
{
sr.ReadInt32(); // Version
int collectionCount = sr.ReadInt32();
result.Capacity = collectionCount;
for (int i = 0; i < collectionCount; i++)
{
if (notification?.CancellationToken.IsCancellationRequested == true)
return result;
var collection = new BeatmapCollection(sr.ReadString());
int mapCount = sr.ReadInt32();
for (int j = 0; j < mapCount; j++)
{
if (notification?.CancellationToken.IsCancellationRequested == true)
return result;
string checksum = sr.ReadString();
collection.BeatmapMD5Hashes.Add(checksum);
}
if (notification != null)
{
notification.Text = $"Imported {i + 1} of {collectionCount} collections";
notification.Progress = (float)(i + 1) / collectionCount;
}
result.Add(collection);
}
}
}
catch (Exception e)
{
Logger.Error(e, "Failed to read collection database.");
}
return result;
}
}
}

View File

@ -13,7 +13,6 @@ using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
using osu.Framework.Platform;
using osu.Game.Beatmaps;
using osu.Game.Collections;
using osu.Game.IO;
using osu.Game.Overlays;
using osu.Game.Overlays.Settings.Sections.Maintenance;
@ -36,15 +35,15 @@ namespace osu.Game.Database
[Resolved]
private ScoreManager scores { get; set; }
[Resolved]
private CollectionManager collections { get; set; }
[Resolved(canBeNull: true)]
private OsuGame game { get; set; }
[Resolved]
private IDialogOverlay dialogOverlay { get; set; }
[Resolved]
private RealmAccess realmAccess { get; set; }
[Resolved(canBeNull: true)]
private DesktopGameHost desktopGameHost { get; set; }
@ -72,7 +71,7 @@ namespace osu.Game.Database
return await new LegacySkinImporter(skins).GetAvailableCount(stableStorage);
case StableContent.Collections:
return await collections.GetAvailableCount(stableStorage);
return await new LegacyCollectionImporter(realmAccess).GetAvailableCount(stableStorage);
case StableContent.Scores:
return await new LegacyScoreImporter(scores).GetAvailableCount(stableStorage);
@ -109,7 +108,7 @@ namespace osu.Game.Database
importTasks.Add(new LegacySkinImporter(skins).ImportFromStableAsync(stableStorage));
if (content.HasFlagFast(StableContent.Collections))
importTasks.Add(beatmapImportTask.ContinueWith(_ => collections.ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion));
importTasks.Add(beatmapImportTask.ContinueWith(_ => new LegacyCollectionImporter(realmAccess).ImportFromStorage(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion));
if (content.HasFlagFast(StableContent.Scores))
importTasks.Add(beatmapImportTask.ContinueWith(_ => new LegacyScoreImporter(scores).ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion));

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using JetBrains.Annotations;
namespace osu.Game.Database
{
@ -18,19 +19,19 @@ namespace osu.Game.Database
/// Perform a read operation on this live object.
/// </summary>
/// <param name="perform">The action to perform.</param>
public abstract void PerformRead(Action<T> perform);
public abstract void PerformRead([InstantHandle] Action<T> perform);
/// <summary>
/// Perform a read operation on this live object.
/// </summary>
/// <param name="perform">The action to perform.</param>
public abstract TReturn PerformRead<TReturn>(Func<T, TReturn> perform);
public abstract TReturn PerformRead<TReturn>([InstantHandle] Func<T, TReturn> perform);
/// <summary>
/// Perform a write operation on this live object.
/// </summary>
/// <param name="perform">The action to perform.</param>
public abstract void PerformWrite(Action<T> perform);
public abstract void PerformWrite([InstantHandle] Action<T> perform);
/// <summary>
/// Whether this instance is tracking data which is managed by the database backing.

View File

@ -14,6 +14,7 @@ using System.Threading.Tasks;
using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Development;
using osu.Framework.Extensions;
using osu.Framework.Input.Bindings;
using osu.Framework.Logging;
using osu.Framework.Platform;
@ -64,8 +65,9 @@ namespace osu.Game.Database
/// 18 2022-07-19 Added OnlineMD5Hash and LastOnlineUpdate to BeatmapInfo.
/// 19 2022-07-19 Added DateSubmitted and DateRanked to BeatmapSetInfo.
/// 20 2022-07-21 Added LastAppliedDifficultyVersion to RulesetInfo, changed default value of BeatmapInfo.StarRating to -1.
/// 21 2022-07-27 Migrate collections to realm (BeatmapCollection).
/// </summary>
private const int schema_version = 20;
private const int schema_version = 21;
/// <summary>
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
@ -790,6 +792,27 @@ namespace osu.Game.Database
beatmap.StarRating = -1;
break;
case 21:
try
{
// Migrate collections from external file to inside realm.
// We use the "legacy" importer because that is how things were actually being saved out until now.
var legacyCollectionImporter = new LegacyCollectionImporter(this);
if (legacyCollectionImporter.GetAvailableCount(storage).GetResultSafely() > 0)
{
legacyCollectionImporter.ImportFromStorage(storage);
storage.Delete("collection.db");
}
}
catch (Exception e)
{
// can be removed 20221027 (just for initial safety).
Logger.Error(e, "Collections could not be migrated to realm. Please provide your \"collection.db\" to the dev team.");
}
break;
}
}