1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-30 08:42:55 +08:00

Merge branch 'master' into rebind-song-select

This commit is contained in:
Jack Boswell 2020-06-07 20:31:26 +12:00 committed by GitHub
commit c331575cd0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 185 additions and 38 deletions

View File

@ -1,4 +1,4 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System; using System;
@ -12,6 +12,7 @@ using NUnit.Framework;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.IPC; using osu.Game.IPC;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Formats;
@ -22,6 +23,7 @@ using SharpCompress.Archives;
using SharpCompress.Archives.Zip; using SharpCompress.Archives.Zip;
using SharpCompress.Common; using SharpCompress.Common;
using SharpCompress.Writers.Zip; using SharpCompress.Writers.Zip;
using FileInfo = System.IO.FileInfo;
namespace osu.Game.Tests.Beatmaps.IO namespace osu.Game.Tests.Beatmaps.IO
{ {
@ -93,6 +95,166 @@ namespace osu.Game.Tests.Beatmaps.IO
} }
} }
[Test]
public async Task TestImportThenImportWithReZip()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithReZip)))
{
try
{
var osu = loadOsu(host);
var temp = TestResources.GetTestBeatmapForImport();
string extractedFolder = $"{temp}_extracted";
Directory.CreateDirectory(extractedFolder);
try
{
var imported = await LoadOszIntoOsu(osu);
string hashBefore = hashFile(temp);
using (var zip = ZipArchive.Open(temp))
zip.WriteToDirectory(extractedFolder);
using (var zip = ZipArchive.Create())
{
zip.AddAllFromDirectory(extractedFolder);
zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
}
// zip files differ because different compression or encoder.
Assert.AreNotEqual(hashBefore, hashFile(temp));
var importedSecondTime = await osu.Dependencies.Get<BeatmapManager>().Import(temp);
ensureLoaded(osu);
// but contents doesn't, so existing should still be used.
Assert.IsTrue(imported.ID == importedSecondTime.ID);
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
}
finally
{
Directory.Delete(extractedFolder, true);
}
}
finally
{
host.Exit();
}
}
}
private string hashFile(string filename)
{
using (var s = File.OpenRead(filename))
return s.ComputeMD5Hash();
}
[Test]
public async Task TestImportThenImportWithChangedFile()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithChangedFile)))
{
try
{
var osu = loadOsu(host);
var temp = TestResources.GetTestBeatmapForImport();
string extractedFolder = $"{temp}_extracted";
Directory.CreateDirectory(extractedFolder);
try
{
var imported = await LoadOszIntoOsu(osu);
using (var zip = ZipArchive.Open(temp))
zip.WriteToDirectory(extractedFolder);
// arbitrary write to non-hashed file
using (var sw = new FileInfo(Directory.GetFiles(extractedFolder, "*.mp3").First()).AppendText())
sw.WriteLine("text");
using (var zip = ZipArchive.Create())
{
zip.AddAllFromDirectory(extractedFolder);
zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
}
var importedSecondTime = await osu.Dependencies.Get<BeatmapManager>().Import(temp);
ensureLoaded(osu);
// check the newly "imported" beatmap is not the original.
Assert.IsTrue(imported.ID != importedSecondTime.ID);
Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID);
}
finally
{
Directory.Delete(extractedFolder, true);
}
}
finally
{
host.Exit();
}
}
}
[Test]
public async Task TestImportThenImportWithDifferentFilename()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithDifferentFilename)))
{
try
{
var osu = loadOsu(host);
var temp = TestResources.GetTestBeatmapForImport();
string extractedFolder = $"{temp}_extracted";
Directory.CreateDirectory(extractedFolder);
try
{
var imported = await LoadOszIntoOsu(osu);
using (var zip = ZipArchive.Open(temp))
zip.WriteToDirectory(extractedFolder);
// change filename
var firstFile = new FileInfo(Directory.GetFiles(extractedFolder).First());
firstFile.MoveTo(Path.Combine(firstFile.DirectoryName, $"{firstFile.Name}-changed{firstFile.Extension}"));
using (var zip = ZipArchive.Create())
{
zip.AddAllFromDirectory(extractedFolder);
zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
}
var importedSecondTime = await osu.Dependencies.Get<BeatmapManager>().Import(temp);
ensureLoaded(osu);
// check the newly "imported" beatmap is not the original.
Assert.IsTrue(imported.ID != importedSecondTime.ID);
Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID);
}
finally
{
Directory.Delete(extractedFolder, true);
}
}
finally
{
host.Exit();
}
}
}
[Test] [Test]
public async Task TestImportCorruptThenImport() public async Task TestImportCorruptThenImport()
{ {
@ -212,37 +374,6 @@ namespace osu.Game.Tests.Beatmaps.IO
} }
} }
[Test]
public async Task TestImportThenImportDifferentHash()
{
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportDifferentHash)))
{
try
{
var osu = loadOsu(host);
var manager = osu.Dependencies.Get<BeatmapManager>();
var imported = await LoadOszIntoOsu(osu);
imported.Hash += "-changed";
manager.Update(imported);
var importedSecondTime = await LoadOszIntoOsu(osu);
Assert.IsTrue(imported.ID != importedSecondTime.ID);
Assert.IsTrue(imported.Beatmaps.First().ID < importedSecondTime.Beatmaps.First().ID);
// only one beatmap will exist as the online set ID matched, causing purging of the first import.
checkBeatmapSetCount(osu, 1);
}
finally
{
host.Exit();
}
}
}
[Test] [Test]
public async Task TestImportThenDeleteThenImport() public async Task TestImportThenDeleteThenImport()
{ {

View File

@ -258,9 +258,9 @@ namespace osu.Game.Beatmaps
/// <returns>The first result for the provided query, or null if no results were found.</returns> /// <returns>The first result for the provided query, or null if no results were found.</returns>
public BeatmapSetInfo QueryBeatmapSet(Expression<Func<BeatmapSetInfo, bool>> query) => beatmaps.ConsumableItems.AsNoTracking().FirstOrDefault(query); public BeatmapSetInfo QueryBeatmapSet(Expression<Func<BeatmapSetInfo, bool>> query) => beatmaps.ConsumableItems.AsNoTracking().FirstOrDefault(query);
protected override bool CanUndelete(BeatmapSetInfo existing, BeatmapSetInfo import) protected override bool CanReuseExisting(BeatmapSetInfo existing, BeatmapSetInfo import)
{ {
if (!base.CanUndelete(existing, import)) if (!base.CanReuseExisting(existing, import))
return false; return false;
var existingIds = existing.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i); var existingIds = existing.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i);

View File

@ -276,7 +276,7 @@ namespace osu.Game.Database
// for now, concatenate all .osu files in the set to create a unique hash. // for now, concatenate all .osu files in the set to create a unique hash.
MemoryStream hashable = new MemoryStream(); MemoryStream hashable = new MemoryStream();
foreach (TFileModel file in item.Files.Where(f => HashableFileTypes.Any(f.Filename.EndsWith))) foreach (TFileModel file in item.Files.Where(f => HashableFileTypes.Any(f.Filename.EndsWith)).OrderBy(f => f.Filename))
{ {
using (Stream s = Files.Store.GetStream(file.FileInfo.StoragePath)) using (Stream s = Files.Store.GetStream(file.FileInfo.StoragePath))
s.CopyTo(hashable); s.CopyTo(hashable);
@ -332,7 +332,7 @@ namespace osu.Game.Database
if (existing != null) if (existing != null)
{ {
if (CanUndelete(existing, item)) if (CanReuseExisting(existing, item))
{ {
Undelete(existing); Undelete(existing);
LogForModel(item, $"Found existing {HumanisedModelName} for {item} (ID {existing.ID}) skipping import."); LogForModel(item, $"Found existing {HumanisedModelName} for {item} (ID {existing.ID}) skipping import.");
@ -660,13 +660,29 @@ namespace osu.Game.Database
protected TModel CheckForExisting(TModel model) => model.Hash == null ? null : ModelStore.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash); protected TModel CheckForExisting(TModel model) => model.Hash == null ? null : ModelStore.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash);
/// <summary> /// <summary>
/// After an existing <typeparamref name="TModel"/> is found during an import process, the default behaviour is to restore the existing /// After an existing <typeparamref name="TModel"/> is found during an import process, the default behaviour is to use/restore the existing
/// item and skip the import. This method allows changing that behaviour. /// item and skip the import. This method allows changing that behaviour.
/// </summary> /// </summary>
/// <param name="existing">The existing model.</param> /// <param name="existing">The existing model.</param>
/// <param name="import">The newly imported model.</param> /// <param name="import">The newly imported model.</param>
/// <returns>Whether the existing model should be restored and used. Returning false will delete the existing and force a re-import.</returns> /// <returns>Whether the existing model should be restored and used. Returning false will delete the existing and force a re-import.</returns>
protected virtual bool CanUndelete(TModel existing, TModel import) => true; protected virtual bool CanReuseExisting(TModel existing, TModel import) =>
// for the best or worst, we copy and import files of a new import before checking whether
// it is a duplicate. so to check if anything has changed, we can just compare all FileInfo IDs.
getIDs(existing.Files).SequenceEqual(getIDs(import.Files)) &&
getFilenames(existing.Files).SequenceEqual(getFilenames(import.Files));
private IEnumerable<long> getIDs(List<TFileModel> files)
{
foreach (var f in files.OrderBy(f => f.Filename))
yield return f.FileInfo.ID;
}
private IEnumerable<string> getFilenames(List<TFileModel> files)
{
foreach (var f in files.OrderBy(f => f.Filename))
yield return f.Filename;
}
private DbSet<TModel> queryModel() => ContextFactory.Get().Set<TModel>(); private DbSet<TModel> queryModel() => ContextFactory.Get().Set<TModel>();