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:
commit
c331575cd0
@ -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()
|
||||||
{
|
{
|
||||||
|
@ -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);
|
||||||
|
@ -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>();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user