mirror of
https://github.com/osukey/osukey.git
synced 2025-06-23 12:18:03 +09:00
Move import logic to shared implementation
This commit is contained in:
parent
af61a524b5
commit
e0d28564d0
@ -111,14 +111,23 @@ namespace osu.Desktop
|
|||||||
{
|
{
|
||||||
var filePaths = new [] { e.FileName };
|
var filePaths = new [] { e.FileName };
|
||||||
|
|
||||||
if (filePaths.All(f => Path.GetExtension(f) == @".osz"))
|
var firstExtension = Path.GetExtension(filePaths.First());
|
||||||
Task.Factory.StartNew(() => BeatmapManager.Import(filePaths), TaskCreationOptions.LongRunning);
|
|
||||||
else if (filePaths.All(f => Path.GetExtension(f) == @".osr"))
|
if (filePaths.Any(f => Path.GetExtension(f) != firstExtension)) return;
|
||||||
Task.Run(() =>
|
|
||||||
{
|
switch (firstExtension)
|
||||||
var score = ScoreStore.ReadReplayFile(filePaths.First());
|
{
|
||||||
Schedule(() => LoadScore(score));
|
case ".osz":
|
||||||
});
|
Task.Factory.StartNew(() => BeatmapManager.Import(filePaths), TaskCreationOptions.LongRunning);
|
||||||
|
return;
|
||||||
|
case ".osr":
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
var score = ScoreStore.ReadReplayFile(filePaths.First());
|
||||||
|
Schedule(() => LoadScore(score));
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly string[] allowed_extensions = { @".osz", @".osr" };
|
private static readonly string[] allowed_extensions = { @".osz", @".osr" };
|
||||||
|
@ -22,7 +22,7 @@ namespace osu.Desktop
|
|||||||
{
|
{
|
||||||
if (!host.IsPrimaryInstance)
|
if (!host.IsPrimaryInstance)
|
||||||
{
|
{
|
||||||
var importer = new BeatmapIPCChannel(host);
|
var importer = new ArchiveImportIPCChannel(host);
|
||||||
// Restore the cwd so relative paths given at the command line work correctly
|
// Restore the cwd so relative paths given at the command line work correctly
|
||||||
Directory.SetCurrentDirectory(cwd);
|
Directory.SetCurrentDirectory(cwd);
|
||||||
foreach (var file in args)
|
foreach (var file in args)
|
||||||
|
@ -165,7 +165,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
var temp = prepareTempCopy(osz_path);
|
var temp = prepareTempCopy(osz_path);
|
||||||
Assert.IsTrue(File.Exists(temp));
|
Assert.IsTrue(File.Exists(temp));
|
||||||
|
|
||||||
var importer = new BeatmapIPCChannel(client);
|
var importer = new ArchiveImportIPCChannel(client);
|
||||||
if (!importer.ImportAsync(temp).Wait(10000))
|
if (!importer.ImportAsync(temp).Wait(10000))
|
||||||
Assert.Fail(@"IPC took too long to send");
|
Assert.Fail(@"IPC took too long to send");
|
||||||
|
|
||||||
@ -209,7 +209,11 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
|
|
||||||
Assert.IsTrue(File.Exists(temp));
|
Assert.IsTrue(File.Exists(temp));
|
||||||
|
|
||||||
var imported = osu.Dependencies.Get<BeatmapManager>().Import(temp);
|
var manager = osu.Dependencies.Get<BeatmapManager>();
|
||||||
|
|
||||||
|
manager.Import(temp);
|
||||||
|
|
||||||
|
var imported = manager.GetAllUsableBeatmapSets();
|
||||||
|
|
||||||
ensureLoaded(osu);
|
ensureLoaded(osu);
|
||||||
|
|
||||||
|
181
osu.Game/Beatmaps/ArchiveModelImportManager.cs
Normal file
181
osu.Game/Beatmaps/ArchiveModelImportManager.cs
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using Ionic.Zip;
|
||||||
|
using osu.Framework.Logging;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Game.Beatmaps.IO;
|
||||||
|
using osu.Game.Database;
|
||||||
|
using osu.Game.IO;
|
||||||
|
using osu.Game.IPC;
|
||||||
|
using osu.Game.Overlays.Notifications;
|
||||||
|
using FileInfo = osu.Game.IO.FileInfo;
|
||||||
|
|
||||||
|
namespace osu.Game.Beatmaps
|
||||||
|
{
|
||||||
|
public abstract class ArchiveModelImportManager<TModel, TFileModel> : ICanImportArchives
|
||||||
|
where TModel : class, IHasFiles<TFileModel>
|
||||||
|
where TFileModel : INamedFileInfo, new()
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Set an endpoint for notifications to be posted to.
|
||||||
|
/// </summary>
|
||||||
|
public Action<Notification> PostNotification { protected get; set; }
|
||||||
|
|
||||||
|
public virtual string[] HandledExtensions => new[] { ".zip" };
|
||||||
|
|
||||||
|
protected readonly FileStore Files;
|
||||||
|
|
||||||
|
protected readonly IDatabaseContextFactory ContextFactory;
|
||||||
|
|
||||||
|
protected readonly IAddableStore<TModel> ModelStore;
|
||||||
|
|
||||||
|
// ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised)
|
||||||
|
private ArchiveImportIPCChannel ipc;
|
||||||
|
|
||||||
|
protected ArchiveModelImportManager(Storage storage, IDatabaseContextFactory contextFactory, IAddableStore<TModel> modelStore, IIpcHost importHost = null)
|
||||||
|
{
|
||||||
|
ContextFactory = contextFactory;
|
||||||
|
ModelStore = modelStore;
|
||||||
|
Files = new FileStore(contextFactory, storage);
|
||||||
|
|
||||||
|
if (importHost != null)
|
||||||
|
ipc = new ArchiveImportIPCChannel(importHost, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Import one or more <see cref="BeatmapSetInfo"/> from filesystem <paramref name="paths"/>.
|
||||||
|
/// This will post notifications tracking progress.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="paths">One or more beatmap locations on disk.</param>
|
||||||
|
public void Import(params string[] paths)
|
||||||
|
{
|
||||||
|
var notification = new ProgressNotification
|
||||||
|
{
|
||||||
|
Text = "Import is initialising...",
|
||||||
|
CompletionText = "Import successful!",
|
||||||
|
Progress = 0,
|
||||||
|
State = ProgressNotificationState.Active,
|
||||||
|
};
|
||||||
|
|
||||||
|
PostNotification?.Invoke(notification);
|
||||||
|
|
||||||
|
List<TModel> imported = new List<TModel>();
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
foreach (string path in paths)
|
||||||
|
{
|
||||||
|
if (notification.State == ProgressNotificationState.Cancelled)
|
||||||
|
// user requested abort
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
notification.Text = $"Importing ({i} of {paths.Length})\n{Path.GetFileName(path)}";
|
||||||
|
using (ArchiveReader reader = getReaderFrom(path))
|
||||||
|
imported.Add(Import(reader));
|
||||||
|
|
||||||
|
notification.Progress = (float)++i / paths.Length;
|
||||||
|
|
||||||
|
// We may or may not want to delete the file depending on where it is stored.
|
||||||
|
// e.g. reconstructing/repairing database with beatmaps from default storage.
|
||||||
|
// Also, not always a single file, i.e. for LegacyFilesystemReader
|
||||||
|
// TODO: Add a check to prevent files from storage to be deleted.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(path))
|
||||||
|
File.Delete(path);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Error(e, $@"Could not delete original file after import ({Path.GetFileName(path)})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
e = e.InnerException ?? e;
|
||||||
|
Logger.Error(e, $@"Could not import beatmap set ({Path.GetFileName(path)})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notification.State = ProgressNotificationState.Completed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Import a model from an <see cref="ArchiveReader"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="archive">The beatmap to be imported.</param>
|
||||||
|
public TModel Import(ArchiveReader archive)
|
||||||
|
{
|
||||||
|
using (ContextFactory.GetForWrite()) // used to share a context for full import. keep in mind this will block all writes.
|
||||||
|
{
|
||||||
|
// create a new set info (don't yet add to database)
|
||||||
|
var model = CreateModel(archive);
|
||||||
|
|
||||||
|
var existing = CheckForExisting(model);
|
||||||
|
|
||||||
|
if (existing != null) return existing;
|
||||||
|
|
||||||
|
model.Files = createFileInfos(archive, Files);
|
||||||
|
|
||||||
|
Populate(model, archive);
|
||||||
|
|
||||||
|
// import to store
|
||||||
|
ModelStore.Add(model);
|
||||||
|
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create all required <see cref="FileInfo"/>s for the provided archive, adding them to the global file store.
|
||||||
|
/// </summary>
|
||||||
|
private List<TFileModel> createFileInfos(ArchiveReader reader, FileStore files)
|
||||||
|
{
|
||||||
|
var fileInfos = new List<TFileModel>();
|
||||||
|
|
||||||
|
// import files to manager
|
||||||
|
foreach (string file in reader.Filenames)
|
||||||
|
using (Stream s = reader.GetStream(file))
|
||||||
|
fileInfos.Add(new TFileModel
|
||||||
|
{
|
||||||
|
Filename = file,
|
||||||
|
FileInfo = files.Add(s)
|
||||||
|
});
|
||||||
|
|
||||||
|
return fileInfos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a barebones model from the provided archive.
|
||||||
|
/// Actual expensive population should be done in <see cref="Populate"/>; this should just prepare for duplicate checking.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="archive"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected abstract TModel CreateModel(ArchiveReader archive);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Populate the provided model completely from the given archive.
|
||||||
|
/// After this method, the model should be in a state ready to commit to a store.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="model">The model to populate.</param>
|
||||||
|
/// <param name="archive">The archive to use as a reference for population.</param>
|
||||||
|
protected virtual void Populate(TModel model, ArchiveReader archive)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual TModel CheckForExisting(TModel beatmapSet) => null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an <see cref="ArchiveReader"/> from a valid storage path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">A file or folder path resolving the beatmap content.</param>
|
||||||
|
/// <returns>A reader giving access to the beatmap's content.</returns>
|
||||||
|
private ArchiveReader getReaderFrom(string path)
|
||||||
|
{
|
||||||
|
if (ZipFile.IsZipFile(path))
|
||||||
|
return new OszArchiveReader(Files.Storage.GetStream(path));
|
||||||
|
return new LegacyFilesystemReader(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,6 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Ionic.Zip;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
@ -16,8 +15,6 @@ using osu.Game.Beatmaps.Formats;
|
|||||||
using osu.Game.Beatmaps.IO;
|
using osu.Game.Beatmaps.IO;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.IO;
|
|
||||||
using osu.Game.IPC;
|
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
@ -28,7 +25,7 @@ namespace osu.Game.Beatmaps
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles the storage and retrieval of Beatmaps/WorkingBeatmaps.
|
/// Handles the storage and retrieval of Beatmaps/WorkingBeatmaps.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class BeatmapManager
|
public partial class BeatmapManager : ArchiveModelImportManager<BeatmapSetInfo, BeatmapSetFileInfo>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fired when a new <see cref="BeatmapSetInfo"/> becomes available in the database.
|
/// Fired when a new <see cref="BeatmapSetInfo"/> becomes available in the database.
|
||||||
@ -60,9 +57,7 @@ namespace osu.Game.Beatmaps
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public WorkingBeatmap DefaultBeatmap { private get; set; }
|
public WorkingBeatmap DefaultBeatmap { private get; set; }
|
||||||
|
|
||||||
private readonly IDatabaseContextFactory contextFactory;
|
public override string[] HandledExtensions => new[] { ".osz" };
|
||||||
|
|
||||||
private readonly FileStore files;
|
|
||||||
|
|
||||||
private readonly RulesetStore rulesets;
|
private readonly RulesetStore rulesets;
|
||||||
|
|
||||||
@ -72,142 +67,58 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
private readonly List<DownloadBeatmapSetRequest> currentDownloads = new List<DownloadBeatmapSetRequest>();
|
private readonly List<DownloadBeatmapSetRequest> currentDownloads = new List<DownloadBeatmapSetRequest>();
|
||||||
|
|
||||||
// ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised)
|
|
||||||
private BeatmapIPCChannel ipc;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set an endpoint for notifications to be posted to.
|
|
||||||
/// </summary>
|
|
||||||
public Action<Notification> PostNotification { private get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set a storage with access to an osu-stable install for import purposes.
|
/// Set a storage with access to an osu-stable install for import purposes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Func<Storage> GetStableStorage { private get; set; }
|
public Func<Storage> GetStableStorage { private get; set; }
|
||||||
|
|
||||||
public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, APIAccess api, IIpcHost importHost = null)
|
public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, APIAccess api, IIpcHost importHost = null)
|
||||||
|
: base(storage, contextFactory, new BeatmapStore(contextFactory), importHost)
|
||||||
{
|
{
|
||||||
this.contextFactory = contextFactory;
|
beatmaps = (BeatmapStore)ModelStore;
|
||||||
|
|
||||||
beatmaps = new BeatmapStore(contextFactory);
|
|
||||||
|
|
||||||
beatmaps.BeatmapSetAdded += s => BeatmapSetAdded?.Invoke(s);
|
beatmaps.BeatmapSetAdded += s => BeatmapSetAdded?.Invoke(s);
|
||||||
beatmaps.BeatmapSetRemoved += s => BeatmapSetRemoved?.Invoke(s);
|
beatmaps.BeatmapSetRemoved += s => BeatmapSetRemoved?.Invoke(s);
|
||||||
beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b);
|
beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b);
|
||||||
beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b);
|
beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b);
|
||||||
|
|
||||||
files = new FileStore(contextFactory, storage);
|
|
||||||
|
|
||||||
this.rulesets = rulesets;
|
this.rulesets = rulesets;
|
||||||
this.api = api;
|
this.api = api;
|
||||||
|
|
||||||
if (importHost != null)
|
|
||||||
ipc = new BeatmapIPCChannel(importHost, this);
|
|
||||||
|
|
||||||
beatmaps.Cleanup();
|
beatmaps.Cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
protected override void Populate(BeatmapSetInfo model, ArchiveReader archive)
|
||||||
/// Import one or more <see cref="BeatmapSetInfo"/> from filesystem <paramref name="paths"/>.
|
|
||||||
/// This will post notifications tracking progress.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="paths">One or more beatmap locations on disk.</param>
|
|
||||||
public List<BeatmapSetInfo> Import(params string[] paths)
|
|
||||||
{
|
{
|
||||||
var notification = new ProgressNotification
|
model.Beatmaps = createBeatmapDifficulties(archive);
|
||||||
{
|
|
||||||
Text = "Beatmap import is initialising...",
|
|
||||||
CompletionText = "Import successful!",
|
|
||||||
Progress = 0,
|
|
||||||
State = ProgressNotificationState.Active,
|
|
||||||
};
|
|
||||||
|
|
||||||
PostNotification?.Invoke(notification);
|
// remove metadata from difficulties where it matches the set
|
||||||
|
foreach (BeatmapInfo b in model.Beatmaps)
|
||||||
List<BeatmapSetInfo> imported = new List<BeatmapSetInfo>();
|
if (model.Metadata.Equals(b.Metadata))
|
||||||
|
b.Metadata = null;
|
||||||
int i = 0;
|
|
||||||
foreach (string path in paths)
|
|
||||||
{
|
|
||||||
if (notification.State == ProgressNotificationState.Cancelled)
|
|
||||||
// user requested abort
|
|
||||||
return imported;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
notification.Text = $"Importing ({i} of {paths.Length})\n{Path.GetFileName(path)}";
|
|
||||||
using (ArchiveReader reader = getReaderFrom(path))
|
|
||||||
imported.Add(Import(reader));
|
|
||||||
|
|
||||||
notification.Progress = (float)++i / paths.Length;
|
|
||||||
|
|
||||||
// We may or may not want to delete the file depending on where it is stored.
|
|
||||||
// e.g. reconstructing/repairing database with beatmaps from default storage.
|
|
||||||
// Also, not always a single file, i.e. for LegacyFilesystemReader
|
|
||||||
// TODO: Add a check to prevent files from storage to be deleted.
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (File.Exists(path))
|
|
||||||
File.Delete(path);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logger.Error(e, $@"Could not delete original file after import ({Path.GetFileName(path)})");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
e = e.InnerException ?? e;
|
|
||||||
Logger.Error(e, $@"Could not import beatmap set ({Path.GetFileName(path)})");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
notification.State = ProgressNotificationState.Completed;
|
|
||||||
return imported;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
protected override BeatmapSetInfo CheckForExisting(BeatmapSetInfo beatmapSet)
|
||||||
/// Import a beatmap from an <see cref="ArchiveReader"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="archive">The beatmap to be imported.</param>
|
|
||||||
public BeatmapSetInfo Import(ArchiveReader archive)
|
|
||||||
{
|
{
|
||||||
using (contextFactory.GetForWrite()) // used to share a context for full import. keep in mind this will block all writes.
|
// check if this beatmap has already been imported and exit early if so
|
||||||
|
var existingHashMatch = beatmaps.BeatmapSets.FirstOrDefault(b => b.Hash == beatmapSet.Hash);
|
||||||
|
if (existingHashMatch != null)
|
||||||
{
|
{
|
||||||
// create a new set info (don't yet add to database)
|
Undelete(existingHashMatch);
|
||||||
var beatmapSet = createBeatmapSetInfo(archive);
|
return existingHashMatch;
|
||||||
|
|
||||||
// check if this beatmap has already been imported and exit early if so
|
|
||||||
var existingHashMatch = beatmaps.BeatmapSets.FirstOrDefault(b => b.Hash == beatmapSet.Hash);
|
|
||||||
if (existingHashMatch != null)
|
|
||||||
{
|
|
||||||
Undelete(existingHashMatch);
|
|
||||||
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);
|
|
||||||
beatmaps.Cleanup(s => s.ID == existingOnlineId.ID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
beatmapSet.Files = createFileInfos(archive, files);
|
|
||||||
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);
|
|
||||||
return beatmapSet;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
beatmaps.Cleanup(s => s.ID == existingOnlineId.ID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -313,7 +224,7 @@ namespace osu.Game.Beatmaps
|
|||||||
/// <param name="beatmapSet">The beatmap set to delete.</param>
|
/// <param name="beatmapSet">The beatmap set to delete.</param>
|
||||||
public void Delete(BeatmapSetInfo beatmapSet)
|
public void Delete(BeatmapSetInfo beatmapSet)
|
||||||
{
|
{
|
||||||
using (var usage = contextFactory.GetForWrite())
|
using (var usage = ContextFactory.GetForWrite())
|
||||||
{
|
{
|
||||||
var context = usage.Context;
|
var context = usage.Context;
|
||||||
|
|
||||||
@ -325,7 +236,7 @@ namespace osu.Game.Beatmaps
|
|||||||
if (beatmaps.Delete(beatmapSet))
|
if (beatmaps.Delete(beatmapSet))
|
||||||
{
|
{
|
||||||
if (!beatmapSet.Protected)
|
if (!beatmapSet.Protected)
|
||||||
files.Dereference(beatmapSet.Files.Select(f => f.FileInfo).ToArray());
|
Files.Dereference(beatmapSet.Files.Select(f => f.FileInfo).ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
context.ChangeTracker.AutoDetectChangesEnabled = true;
|
context.ChangeTracker.AutoDetectChangesEnabled = true;
|
||||||
@ -376,14 +287,14 @@ namespace osu.Game.Beatmaps
|
|||||||
if (beatmapSet.Protected)
|
if (beatmapSet.Protected)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
using (var usage = contextFactory.GetForWrite())
|
using (var usage = ContextFactory.GetForWrite())
|
||||||
{
|
{
|
||||||
usage.Context.ChangeTracker.AutoDetectChangesEnabled = false;
|
usage.Context.ChangeTracker.AutoDetectChangesEnabled = false;
|
||||||
|
|
||||||
if (!beatmaps.Undelete(beatmapSet)) return;
|
if (!beatmaps.Undelete(beatmapSet)) return;
|
||||||
|
|
||||||
if (!beatmapSet.Protected)
|
if (!beatmapSet.Protected)
|
||||||
files.Reference(beatmapSet.Files.Select(f => f.FileInfo).ToArray());
|
Files.Reference(beatmapSet.Files.Select(f => f.FileInfo).ToArray());
|
||||||
|
|
||||||
usage.Context.ChangeTracker.AutoDetectChangesEnabled = true;
|
usage.Context.ChangeTracker.AutoDetectChangesEnabled = true;
|
||||||
}
|
}
|
||||||
@ -415,7 +326,7 @@ namespace osu.Game.Beatmaps
|
|||||||
if (beatmapInfo.Metadata == null)
|
if (beatmapInfo.Metadata == null)
|
||||||
beatmapInfo.Metadata = beatmapInfo.BeatmapSet.Metadata;
|
beatmapInfo.Metadata = beatmapInfo.BeatmapSet.Metadata;
|
||||||
|
|
||||||
WorkingBeatmap working = new BeatmapManagerWorkingBeatmap(files.Store, beatmapInfo);
|
WorkingBeatmap working = new BeatmapManagerWorkingBeatmap(Files.Store, beatmapInfo);
|
||||||
|
|
||||||
previous?.TransferTo(working);
|
previous?.TransferTo(working);
|
||||||
|
|
||||||
@ -519,19 +430,6 @@ namespace osu.Game.Beatmaps
|
|||||||
notification.State = ProgressNotificationState.Completed;
|
notification.State = ProgressNotificationState.Completed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates an <see cref="ArchiveReader"/> from a valid storage path.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path">A file or folder path resolving the beatmap content.</param>
|
|
||||||
/// <returns>A reader giving access to the beatmap's content.</returns>
|
|
||||||
private ArchiveReader getReaderFrom(string path)
|
|
||||||
{
|
|
||||||
if (ZipFile.IsZipFile(path))
|
|
||||||
// ReSharper disable once InconsistentlySynchronizedField
|
|
||||||
return new OszArchiveReader(files.Storage.GetStream(path));
|
|
||||||
return new LegacyFilesystemReader(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a SHA-2 hash from the provided archive based on contained beatmap (.osu) file content.
|
/// Create a SHA-2 hash from the provided archive based on contained beatmap (.osu) file content.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -546,10 +444,7 @@ namespace osu.Game.Beatmaps
|
|||||||
return hashable.ComputeSHA2Hash();
|
return hashable.ComputeSHA2Hash();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
protected override BeatmapSetInfo CreateModel(ArchiveReader reader)
|
||||||
/// Create a <see cref="BeatmapSetInfo"/> from a provided archive.
|
|
||||||
/// </summary>
|
|
||||||
private BeatmapSetInfo createBeatmapSetInfo(ArchiveReader reader)
|
|
||||||
{
|
{
|
||||||
// let's make sure there are actually .osu files to import.
|
// let's make sure there are actually .osu files to import.
|
||||||
string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu"));
|
string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu"));
|
||||||
@ -568,25 +463,6 @@ namespace osu.Game.Beatmaps
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create all required <see cref="FileInfo"/>s for the provided archive, adding them to the global file store.
|
|
||||||
/// </summary>
|
|
||||||
private List<BeatmapSetFileInfo> createFileInfos(ArchiveReader reader, FileStore files)
|
|
||||||
{
|
|
||||||
List<BeatmapSetFileInfo> fileInfos = new List<BeatmapSetFileInfo>();
|
|
||||||
|
|
||||||
// import files to manager
|
|
||||||
foreach (string file in reader.Filenames)
|
|
||||||
using (Stream s = reader.GetStream(file))
|
|
||||||
fileInfos.Add(new BeatmapSetFileInfo
|
|
||||||
{
|
|
||||||
Filename = file,
|
|
||||||
FileInfo = files.Add(s)
|
|
||||||
});
|
|
||||||
|
|
||||||
return fileInfos;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create all required <see cref="BeatmapInfo"/>s for the provided archive.
|
/// Create all required <see cref="BeatmapInfo"/>s for the provided archive.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using osu.Game.Database;
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps
|
namespace osu.Game.Beatmaps
|
||||||
{
|
{
|
||||||
public class BeatmapSetFileInfo
|
public class BeatmapSetFileInfo : INamedFileInfo
|
||||||
{
|
{
|
||||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||||
public int ID { get; set; }
|
public int ID { get; set; }
|
||||||
|
@ -5,10 +5,11 @@ using System.Collections.Generic;
|
|||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
|
using osu.Game.IO;
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps
|
namespace osu.Game.Beatmaps
|
||||||
{
|
{
|
||||||
public class BeatmapSetInfo : IHasPrimaryKey
|
public class BeatmapSetInfo : IHasPrimaryKey, IHasFiles<BeatmapSetFileInfo>
|
||||||
{
|
{
|
||||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||||
public int ID { get; set; }
|
public int ID { get; set; }
|
||||||
|
@ -6,13 +6,14 @@ using System.Linq;
|
|||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
|
using osu.Game.IO;
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps
|
namespace osu.Game.Beatmaps
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles the storage and retrieval of Beatmaps/BeatmapSets to the database backing
|
/// Handles the storage and retrieval of Beatmaps/BeatmapSets to the database backing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class BeatmapStore : DatabaseBackedStore
|
public class BeatmapStore : DatabaseBackedStore, IAddableStore<BeatmapSetInfo>
|
||||||
{
|
{
|
||||||
public event Action<BeatmapSetInfo> BeatmapSetAdded;
|
public event Action<BeatmapSetInfo> BeatmapSetAdded;
|
||||||
public event Action<BeatmapSetInfo> BeatmapSetRemoved;
|
public event Action<BeatmapSetInfo> BeatmapSetRemoved;
|
||||||
|
9
osu.Game/Beatmaps/ICanImportArchives.cs
Normal file
9
osu.Game/Beatmaps/ICanImportArchives.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace osu.Game.Beatmaps
|
||||||
|
{
|
||||||
|
public interface ICanImportArchives
|
||||||
|
{
|
||||||
|
void Import(params string[] paths);
|
||||||
|
|
||||||
|
string[] HandledExtensions { get; }
|
||||||
|
}
|
||||||
|
}
|
13
osu.Game/Database/INamedFileInfo.cs
Normal file
13
osu.Game/Database/INamedFileInfo.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using osu.Game.IO;
|
||||||
|
|
||||||
|
namespace osu.Game.Database
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represent a join model which gives a filename and scope to a <see cref="FileInfo"/>.
|
||||||
|
/// </summary>
|
||||||
|
public interface INamedFileInfo
|
||||||
|
{
|
||||||
|
FileInfo FileInfo { get; set; }
|
||||||
|
string Filename { get; set; }
|
||||||
|
}
|
||||||
|
}
|
14
osu.Game/IO/IAddableStore.cs
Normal file
14
osu.Game/IO/IAddableStore.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
namespace osu.Game.IO
|
||||||
|
{
|
||||||
|
public interface IAddableStore<in T>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Add an object to the store.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="object">The object to add.</param>
|
||||||
|
void Add(T item);
|
||||||
|
}
|
||||||
|
}
|
9
osu.Game/IO/IHasFiles.cs
Normal file
9
osu.Game/IO/IHasFiles.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace osu.Game.IO
|
||||||
|
{
|
||||||
|
public interface IHasFiles<TFile>
|
||||||
|
{
|
||||||
|
List<TFile> Files { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -2,23 +2,25 @@
|
|||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
|
||||||
namespace osu.Game.IPC
|
namespace osu.Game.IPC
|
||||||
{
|
{
|
||||||
public class BeatmapIPCChannel : IpcChannel<BeatmapImportMessage>
|
public class ArchiveImportIPCChannel : IpcChannel<ArchiveImportMessage>
|
||||||
{
|
{
|
||||||
private readonly BeatmapManager beatmaps;
|
private readonly ICanImportArchives importer;
|
||||||
|
|
||||||
public BeatmapIPCChannel(IIpcHost host, BeatmapManager beatmaps = null)
|
public ArchiveImportIPCChannel(IIpcHost host, ICanImportArchives importer = null)
|
||||||
: base(host)
|
: base(host)
|
||||||
{
|
{
|
||||||
this.beatmaps = beatmaps;
|
this.importer = importer;
|
||||||
MessageReceived += msg =>
|
MessageReceived += msg =>
|
||||||
{
|
{
|
||||||
Debug.Assert(beatmaps != null);
|
Debug.Assert(importer != null);
|
||||||
ImportAsync(msg.Path).ContinueWith(t =>
|
ImportAsync(msg.Path).ContinueWith(t =>
|
||||||
{
|
{
|
||||||
if (t.Exception != null) throw t.Exception;
|
if (t.Exception != null) throw t.Exception;
|
||||||
@ -28,18 +30,19 @@ namespace osu.Game.IPC
|
|||||||
|
|
||||||
public async Task ImportAsync(string path)
|
public async Task ImportAsync(string path)
|
||||||
{
|
{
|
||||||
if (beatmaps == null)
|
if (importer == null)
|
||||||
{
|
{
|
||||||
//we want to contact a remote osu! to handle the import.
|
//we want to contact a remote osu! to handle the import.
|
||||||
await SendMessageAsync(new BeatmapImportMessage { Path = path });
|
await SendMessageAsync(new ArchiveImportMessage { Path = path });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
beatmaps.Import(path);
|
if (importer.HandledExtensions.Contains(Path.GetExtension(path)))
|
||||||
|
importer.Import(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class BeatmapImportMessage
|
public class ArchiveImportMessage
|
||||||
{
|
{
|
||||||
public string Path;
|
public string Path;
|
||||||
}
|
}
|
30
osu.Game/Online/API/APIDownloadRequest.cs
Normal file
30
osu.Game/Online/API/APIDownloadRequest.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
using osu.Framework.IO.Network;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.API
|
||||||
|
{
|
||||||
|
public abstract class APIDownloadRequest : APIRequest
|
||||||
|
{
|
||||||
|
protected override WebRequest CreateWebRequest()
|
||||||
|
{
|
||||||
|
var request = new WebRequest(Uri);
|
||||||
|
request.DownloadProgress += request_Progress;
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void request_Progress(long current, long total) => API.Scheduler.Add(delegate { Progress?.Invoke(current, total); });
|
||||||
|
|
||||||
|
protected APIDownloadRequest()
|
||||||
|
{
|
||||||
|
base.Success += onSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSuccess()
|
||||||
|
{
|
||||||
|
Success?.Invoke(WebRequest.ResponseData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public event APIProgressHandler Progress;
|
||||||
|
|
||||||
|
public new event APISuccessHandler<byte[]> Success;
|
||||||
|
}
|
||||||
|
}
|
@ -27,32 +27,6 @@ namespace osu.Game.Online.API
|
|||||||
public new event APISuccessHandler<T> Success;
|
public new event APISuccessHandler<T> Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class APIDownloadRequest : APIRequest
|
|
||||||
{
|
|
||||||
protected override WebRequest CreateWebRequest()
|
|
||||||
{
|
|
||||||
var request = new WebRequest(Uri);
|
|
||||||
request.DownloadProgress += request_Progress;
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void request_Progress(long current, long total) => API.Scheduler.Add(delegate { Progress?.Invoke(current, total); });
|
|
||||||
|
|
||||||
protected APIDownloadRequest()
|
|
||||||
{
|
|
||||||
base.Success += onSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onSuccess()
|
|
||||||
{
|
|
||||||
Success?.Invoke(WebRequest.ResponseData);
|
|
||||||
}
|
|
||||||
|
|
||||||
public event APIProgressHandler Progress;
|
|
||||||
|
|
||||||
public new event APISuccessHandler<byte[]> Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AN API request with no specified response type.
|
/// AN API request with no specified response type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -243,6 +243,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Audio\SampleInfo.cs" />
|
<Compile Include="Audio\SampleInfo.cs" />
|
||||||
|
<Compile Include="Beatmaps\ArchiveModelImportManager.cs" />
|
||||||
<Compile Include="Beatmaps\Beatmap.cs" />
|
<Compile Include="Beatmaps\Beatmap.cs" />
|
||||||
<Compile Include="Beatmaps\BeatmapConverter.cs" />
|
<Compile Include="Beatmaps\BeatmapConverter.cs" />
|
||||||
<Compile Include="Beatmaps\BeatmapDifficulty.cs" />
|
<Compile Include="Beatmaps\BeatmapDifficulty.cs" />
|
||||||
@ -270,6 +271,7 @@
|
|||||||
<Compile Include="Beatmaps\Formats\JsonBeatmapDecoder.cs" />
|
<Compile Include="Beatmaps\Formats\JsonBeatmapDecoder.cs" />
|
||||||
<Compile Include="Beatmaps\Formats\LegacyDecoder.cs" />
|
<Compile Include="Beatmaps\Formats\LegacyDecoder.cs" />
|
||||||
<Compile Include="Beatmaps\Formats\LegacyStoryboardDecoder.cs" />
|
<Compile Include="Beatmaps\Formats\LegacyStoryboardDecoder.cs" />
|
||||||
|
<Compile Include="Beatmaps\ICanImportArchives.cs" />
|
||||||
<Compile Include="Configuration\DatabasedSetting.cs" />
|
<Compile Include="Configuration\DatabasedSetting.cs" />
|
||||||
<Compile Include="Configuration\SettingsStore.cs" />
|
<Compile Include="Configuration\SettingsStore.cs" />
|
||||||
<Compile Include="Configuration\DatabasedConfigManager.cs" />
|
<Compile Include="Configuration\DatabasedConfigManager.cs" />
|
||||||
@ -278,9 +280,13 @@
|
|||||||
<Compile Include="Database\DatabaseWriteUsage.cs" />
|
<Compile Include="Database\DatabaseWriteUsage.cs" />
|
||||||
<Compile Include="Database\IDatabaseContextFactory.cs" />
|
<Compile Include="Database\IDatabaseContextFactory.cs" />
|
||||||
<Compile Include="Database\IHasPrimaryKey.cs" />
|
<Compile Include="Database\IHasPrimaryKey.cs" />
|
||||||
|
<Compile Include="Database\INamedFileInfo.cs" />
|
||||||
<Compile Include="Database\SingletonContextFactory.cs" />
|
<Compile Include="Database\SingletonContextFactory.cs" />
|
||||||
<Compile Include="Graphics\Containers\LinkFlowContainer.cs" />
|
<Compile Include="Graphics\Containers\LinkFlowContainer.cs" />
|
||||||
<Compile Include="Graphics\Textures\LargeTextureStore.cs" />
|
<Compile Include="Graphics\Textures\LargeTextureStore.cs" />
|
||||||
|
<Compile Include="IO\IAddableStore.cs" />
|
||||||
|
<Compile Include="IO\IHasFiles.cs" />
|
||||||
|
<Compile Include="Online\API\APIDownloadRequest.cs" />
|
||||||
<Compile Include="Online\API\Requests\GetUserRequest.cs" />
|
<Compile Include="Online\API\Requests\GetUserRequest.cs" />
|
||||||
<Compile Include="Migrations\20180125143340_Settings.cs" />
|
<Compile Include="Migrations\20180125143340_Settings.cs" />
|
||||||
<Compile Include="Migrations\20180125143340_Settings.Designer.cs">
|
<Compile Include="Migrations\20180125143340_Settings.Designer.cs">
|
||||||
@ -470,7 +476,7 @@
|
|||||||
<Compile Include="IO\Serialization\Converters\TypedListConverter.cs" />
|
<Compile Include="IO\Serialization\Converters\TypedListConverter.cs" />
|
||||||
<Compile Include="IO\Serialization\Converters\Vector2Converter.cs" />
|
<Compile Include="IO\Serialization\Converters\Vector2Converter.cs" />
|
||||||
<Compile Include="IO\Serialization\IJsonSerializable.cs" />
|
<Compile Include="IO\Serialization\IJsonSerializable.cs" />
|
||||||
<Compile Include="IPC\BeatmapIPCChannel.cs" />
|
<Compile Include="IPC\ArchiveImportIPCChannel.cs" />
|
||||||
<Compile Include="IPC\ScoreIPCChannel.cs" />
|
<Compile Include="IPC\ScoreIPCChannel.cs" />
|
||||||
<Compile Include="Online\API\APIAccess.cs" />
|
<Compile Include="Online\API\APIAccess.cs" />
|
||||||
<Compile Include="Online\API\APIRequest.cs" />
|
<Compile Include="Online\API\APIRequest.cs" />
|
||||||
|
Loading…
x
Reference in New Issue
Block a user