1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-15 11:52:56 +08:00

Simplify beatmap import process

This commit is contained in:
Dean Herbert 2018-02-09 17:22:48 +09:00
parent 0ba76bbaa1
commit 3b7018fcd6

View File

@ -194,50 +194,74 @@ namespace osu.Game.Beatmaps
} }
private readonly object importContextLock = new object(); private readonly object importContextLock = new object();
private Lazy<OsuDbContext> importContext; private Lazy<OsuDbContext> importContext;
/// <summary> /// <summary>
/// Import a beatmap from an <see cref="ArchiveReader"/>. /// Import a beatmap from an <see cref="ArchiveReader"/>.
/// </summary> /// </summary>
/// <param name="archiveReader">The beatmap to be imported.</param> /// <param name="archive">The beatmap to be imported.</param>
public BeatmapSetInfo Import(ArchiveReader archiveReader) public BeatmapSetInfo Import(ArchiveReader archive)
{ {
// let's only allow one concurrent import at a time for now. // let's only allow one concurrent import at a time for now
lock (importContextLock) lock (importContextLock)
{ {
var context = importContext.Value; var context = importContext.Value;
using (var transaction = context.BeginTransaction()) using (var transaction = context.BeginTransaction())
{ {
// create local stores so we can isolate and thread safely, and share a context/transaction. // create a new set info (don't yet add to database)
var iFiles = new FileStore(() => context, storage); var beatmapSet = createBeatmapSetInfo(archive);
var iBeatmaps = createBeatmapStore(() => context);
BeatmapSetInfo set = importToStorage(iFiles, iBeatmaps, archiveReader); // check if this beatmap has already been imported and exit early if so
var existingHashMatch = beatmaps.BeatmapSets.FirstOrDefault(b => b.Hash == beatmapSet.Hash);
if (set.ID == 0) if (existingHashMatch != null)
{ {
iBeatmaps.Add(set); undelete(beatmaps, files, existingHashMatch);
context.SaveChanges(); return existingHashMatch;
} }
// check if a set already exists with the same online id
if (beatmapSet.OnlineBeatmapSetID != null)
{
var existingOnlineId = beatmaps.BeatmapSets.FirstOrDefault(b => b.OnlineBeatmapSetID == beatmapSet.OnlineBeatmapSetID);
if (existingOnlineId != null)
Delete(existingOnlineId);
}
beatmapSet.Files = createFileInfos(archive, new FileStore(() => context, storage));
beatmapSet.Beatmaps = createBeatmapDifficulties(archive);
// remove metadata from difficulties where it matches the set
foreach (BeatmapInfo b in beatmapSet.Beatmaps)
if (beatmapSet.Metadata.Equals(b.Metadata))
b.Metadata = null;
// import to beatmap store
import(beatmapSet, context);
context.SaveChanges(transaction); context.SaveChanges(transaction);
return set; return beatmapSet;
} }
} }
} }
/// <summary> /// <summary>
/// Import a beatmap from a <see cref="BeatmapSetInfo"/>. /// Import a beatmap from a <see cref="BeatmapSetInfo"/>.
/// </summary> /// </summary>
/// <param name="beatmapSetInfo">The beatmap to be imported.</param> /// <param name="beatmapSetInfo">The beatmap to be imported.</param>
public void Import(BeatmapSetInfo beatmapSetInfo) public void Import(BeatmapSetInfo beatmapSetInfo)
{ {
// If we have an ID then we already exist in the database. lock (importContextLock)
if (beatmapSetInfo.ID != 0) return; {
var context = importContext.Value;
createBeatmapStore(createContext).Add(beatmapSetInfo); using (var transaction = context.BeginTransaction())
{
import(beatmapSetInfo, context);
context.SaveChanges(transaction);
}
}
} }
/// <summary> /// <summary>
@ -495,6 +519,8 @@ namespace osu.Game.Beatmaps
/// <returns>Results from the provided query.</returns> /// <returns>Results from the provided query.</returns>
public IEnumerable<BeatmapInfo> QueryBeatmaps(Expression<Func<BeatmapInfo, bool>> query) => beatmaps.Beatmaps.AsNoTracking().Where(query); public IEnumerable<BeatmapInfo> QueryBeatmaps(Expression<Func<BeatmapInfo, bool>> query) => beatmaps.Beatmaps.AsNoTracking().Where(query);
private void import(BeatmapSetInfo beatmapSet, OsuDbContext context) => createBeatmapStore(() => context).Add(beatmapSet);
/// <summary> /// <summary>
/// Creates an <see cref="ArchiveReader"/> from a valid storage path. /// Creates an <see cref="ArchiveReader"/> from a valid storage path.
/// </summary> /// </summary>
@ -508,49 +534,43 @@ namespace osu.Game.Beatmaps
return new LegacyFilesystemReader(path); return new LegacyFilesystemReader(path);
} }
/// <summary> private string computeBeatmapSetHash(ArchiveReader reader)
/// Import a beamap into our local <see cref="FileStore"/> storage.
/// If the beatmap is already imported, the existing instance will be returned.
/// </summary>
/// <param name="files">The store to import beatmap files to.</param>
/// <param name="beatmaps">The store to import beatmaps to.</param>
/// <param name="reader">The beatmap archive to be read.</param>
/// <returns>The imported beatmap, or an existing instance if it is already present.</returns>
private BeatmapSetInfo importToStorage(FileStore files, BeatmapStore beatmaps, ArchiveReader reader)
{ {
// let's make sure there are actually .osu files to import.
string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu"));
if (string.IsNullOrEmpty(mapName))
throw new InvalidOperationException("No beatmap files found in the map folder.");
// 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 (string file in reader.Filenames.Where(f => f.EndsWith(".osu"))) foreach (string file in reader.Filenames.Where(f => f.EndsWith(".osu")))
using (Stream s = reader.GetStream(file)) using (Stream s = reader.GetStream(file))
s.CopyTo(hashable); s.CopyTo(hashable);
var hash = hashable.ComputeSHA2Hash(); return hashable.ComputeSHA2Hash();
}
// check if this beatmap has already been imported and exit early if so. /// <summary>
var beatmapSet = beatmaps.BeatmapSets.FirstOrDefault(b => b.Hash == hash); ///
/// </summary>
/// <param name="reader"></param>
/// <returns></returns>
private BeatmapSetInfo createBeatmapSetInfo(ArchiveReader reader)
{
// let's make sure there are actually .osu files to import.
string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu"));
if (string.IsNullOrEmpty(mapName)) throw new InvalidOperationException("No beatmap files found in the map folder.");
if (beatmapSet != null) BeatmapMetadata metadata;
using (var stream = new StreamReader(reader.GetStream(mapName)))
metadata = Decoder.GetDecoder(stream).DecodeBeatmap(stream).Metadata;
return new BeatmapSetInfo
{ {
undelete(beatmaps, files, beatmapSet); OnlineBeatmapSetID = metadata.OnlineBeatmapSetID,
Beatmaps = new List<BeatmapInfo>(),
// ensure all files are present and accessible Hash = computeBeatmapSetHash(reader),
foreach (var f in beatmapSet.Files) Metadata = metadata
{ };
if (!storage.Exists(f.FileInfo.StoragePath)) }
using (Stream s = reader.GetStream(f.Filename))
files.Add(s, false);
}
// todo: delete any files which shouldn't exist any more.
return beatmapSet;
}
private List<BeatmapSetFileInfo> createFileInfos(ArchiveReader reader, FileStore files)
{
List<BeatmapSetFileInfo> fileInfos = new List<BeatmapSetFileInfo>(); List<BeatmapSetFileInfo> fileInfos = new List<BeatmapSetFileInfo>();
// import files to manager // import files to manager
@ -562,28 +582,20 @@ namespace osu.Game.Beatmaps
FileInfo = files.Add(s) FileInfo = files.Add(s)
}); });
BeatmapMetadata metadata; return fileInfos;
}
using (var stream = new StreamReader(reader.GetStream(mapName))) /// <summary>
metadata = Decoder.GetDecoder(stream).DecodeBeatmap(stream).Metadata; /// Import a beamap into our local <see cref="FileStore"/> storage.
/// If the beatmap is already imported, the existing instance will be returned.
/// </summary>
/// <param name="reader">The beatmap archive to be read.</param>
/// <returns>The imported beatmap, or an existing instance if it is already present.</returns>
private List<BeatmapInfo> createBeatmapDifficulties(ArchiveReader reader)
{
var beatmapInfos = new List<BeatmapInfo>();
// check if a set already exists with the same online id. foreach (var name in reader.Filenames.Where(f => f.EndsWith(".osu")))
if (metadata.OnlineBeatmapSetID != null)
beatmapSet = beatmaps.BeatmapSets.FirstOrDefault(b => b.OnlineBeatmapSetID == metadata.OnlineBeatmapSetID);
if (beatmapSet == null)
beatmapSet = new BeatmapSetInfo
{
OnlineBeatmapSetID = metadata.OnlineBeatmapSetID,
Beatmaps = new List<BeatmapInfo>(),
Hash = hash,
Files = fileInfos,
Metadata = metadata
};
var mapNames = reader.Filenames.Where(f => f.EndsWith(".osu"));
foreach (var name in mapNames)
{ {
using (var raw = reader.GetStream(name)) using (var raw = reader.GetStream(name))
using (var ms = new MemoryStream()) //we need a memory stream so we can seek and shit using (var ms = new MemoryStream()) //we need a memory stream so we can seek and shit
@ -599,36 +611,24 @@ namespace osu.Game.Beatmaps
beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash(); beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash();
beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash(); beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash();
var existing = beatmaps.Beatmaps.FirstOrDefault(b => b.Hash == beatmap.BeatmapInfo.Hash || beatmap.BeatmapInfo.OnlineBeatmapID != null && b.OnlineBeatmapID == beatmap.BeatmapInfo.OnlineBeatmapID); RulesetInfo ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID);
if (existing == null) // TODO: this should be done in a better place once we actually need to dynamically update it.
{ beatmap.BeatmapInfo.Ruleset = ruleset;
// Exclude beatmap-metadata if it's equal to beatmapset-metadata beatmap.BeatmapInfo.StarDifficulty = ruleset?.CreateInstance()?.CreateDifficultyCalculator(beatmap).Calculate() ?? 0;
if (metadata.Equals(beatmap.Metadata))
beatmap.BeatmapInfo.Metadata = null;
RulesetInfo ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID); beatmapInfos.Add(beatmap.BeatmapInfo);
// TODO: this should be done in a better place once we actually need to dynamically update it.
beatmap.BeatmapInfo.Ruleset = ruleset;
beatmap.BeatmapInfo.StarDifficulty = ruleset?.CreateInstance()?.CreateDifficultyCalculator(beatmap).Calculate() ?? 0;
beatmapSet.Beatmaps.Add(beatmap.BeatmapInfo);
}
} }
} }
return beatmapSet; return beatmapInfos;
} }
/// <summary> /// <summary>
/// Returns a list of all usable <see cref="BeatmapSetInfo"/>s. /// Returns a list of all usable <see cref="BeatmapSetInfo"/>s.
/// </summary> /// </summary>
/// <returns>A list of available <see cref="BeatmapSetInfo"/>.</returns> /// <returns>A list of available <see cref="BeatmapSetInfo"/>.</returns>
public List<BeatmapSetInfo> GetAllUsableBeatmapSets() public List<BeatmapSetInfo> GetAllUsableBeatmapSets() => beatmaps.BeatmapSets.Where(s => !s.DeletePending).ToList();
{
return beatmaps.BeatmapSets.Where(s => !s.DeletePending).ToList();
}
protected class BeatmapManagerWorkingBeatmap : WorkingBeatmap protected class BeatmapManagerWorkingBeatmap : WorkingBeatmap
{ {