diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs
index ddd2bc5d1e..fc217d3058 100644
--- a/osu.Game/Database/ArchiveModelManager.cs
+++ b/osu.Game/Database/ArchiveModelManager.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Database
///
/// The model type.
/// The associated file join type.
- public abstract class ArchiveModelManager : ICanAcceptFiles, IModelManager
+ public abstract class ArchiveModelManager : ICanAcceptFiles, IModelManager, IModelFileManager
where TModel : class, IHasFiles, IHasPrimaryKey, ISoftDelete
where TFileModel : class, INamedFileInfo, new()
{
@@ -135,7 +135,7 @@ namespace osu.Game.Database
return Import(notification, tasks);
}
- protected async Task> Import(ProgressNotification notification, params ImportTask[] tasks)
+ public async Task> Import(ProgressNotification notification, params ImportTask[] tasks)
{
if (tasks.Length == 0)
{
@@ -227,7 +227,7 @@ namespace osu.Game.Database
/// Whether this is a low priority import.
/// An optional cancellation token.
/// The imported model, if successful.
- internal async Task Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default)
+ public async Task Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
@@ -479,7 +479,7 @@ namespace osu.Game.Database
///
/// The item to export.
/// The output stream to export to.
- protected virtual void ExportModelTo(TModel model, Stream outputStream)
+ public virtual void ExportModelTo(TModel model, Stream outputStream)
{
using (var archive = ZipArchive.Create())
{
@@ -745,9 +745,6 @@ namespace osu.Game.Database
/// Whether to perform deletion.
protected virtual bool ShouldDeleteArchive(string path) => false;
- ///
- /// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future.
- ///
public Task ImportFromStableAsync(StableStorage stableStorage)
{
var storage = PrepareStableStorage(stableStorage);
diff --git a/osu.Game/Database/DownloadableArchiveModelManager.cs b/osu.Game/Database/DownloadableArchiveModelManager.cs
index da3144e8d0..e6d5b44b65 100644
--- a/osu.Game/Database/DownloadableArchiveModelManager.cs
+++ b/osu.Game/Database/DownloadableArchiveModelManager.cs
@@ -54,12 +54,6 @@ namespace osu.Game.Database
/// The request object.
protected abstract ArchiveDownloadRequest CreateDownloadRequest(TModel model, bool minimiseDownloadSize);
- ///
- /// Begin a download for the requested .
- ///
- /// The to be downloaded.
- /// Whether this download should be optimised for slow connections. Generally means extras are not included in the download bundle.
- /// Whether the download was started.
public bool Download(TModel model, bool minimiseDownloadSize = false)
{
if (!canDownload(model)) return false;
diff --git a/osu.Game/Database/IModelFileManager.cs b/osu.Game/Database/IModelFileManager.cs
new file mode 100644
index 0000000000..c74b945eb7
--- /dev/null
+++ b/osu.Game/Database/IModelFileManager.cs
@@ -0,0 +1,36 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.IO;
+
+namespace osu.Game.Database
+{
+ public interface IModelFileManager
+ where TModel : class
+ where TFileModel : class
+ {
+ ///
+ /// Replace an existing file with a new version.
+ ///
+ /// The item to operate on.
+ /// The existing file to be replaced.
+ /// The new file contents.
+ /// An optional filename for the new file. Will use the previous filename if not specified.
+ void ReplaceFile(TModel model, TFileModel file, Stream contents, string filename = null);
+
+ ///
+ /// Delete an existing file.
+ ///
+ /// The item to operate on.
+ /// The existing file to be deleted.
+ void DeleteFile(TModel model, TFileModel file);
+
+ ///
+ /// Add a new file.
+ ///
+ /// The item to operate on.
+ /// The new file contents.
+ /// The filename for the new file.
+ void AddFile(TModel model, Stream contents, string filename);
+ }
+}
diff --git a/osu.Game/Database/IModelManager.cs b/osu.Game/Database/IModelManager.cs
index 8c314f1617..8f0c6e1561 100644
--- a/osu.Game/Database/IModelManager.cs
+++ b/osu.Game/Database/IModelManager.cs
@@ -1,8 +1,15 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . 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.Threading;
+using System.Threading.Tasks;
using osu.Framework.Bindables;
+using osu.Game.IO;
+using osu.Game.IO.Archives;
+using osu.Game.Overlays.Notifications;
namespace osu.Game.Database
{
@@ -24,5 +31,97 @@ namespace osu.Game.Database
/// This is not thread-safe and should be scheduled locally if consumed from a drawable component.
///
IBindable> ItemRemoved { get; }
+
+ ///
+ /// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future.
+ ///
+ Task ImportFromStableAsync(StableStorage stableStorage);
+
+ ///
+ /// Exports an item to a legacy (.zip based) package.
+ ///
+ /// The item to export.
+ void Export(TModel item);
+
+ ///
+ /// Exports an item to the given output stream.
+ ///
+ /// The item to export.
+ /// The output stream to export to.
+ void ExportModelTo(TModel model, Stream outputStream);
+
+ ///
+ /// Perform an update of the specified item.
+ /// TODO: Support file additions/removals.
+ ///
+ /// The item to update.
+ void Update(TModel item);
+
+ ///
+ /// Delete an item from the manager.
+ /// Is a no-op for already deleted items.
+ ///
+ /// The item to delete.
+ /// false if no operation was performed
+ bool Delete(TModel item);
+
+ ///
+ /// Delete multiple items.
+ /// This will post notifications tracking progress.
+ ///
+ void Delete(List items, bool silent = false);
+
+ ///
+ /// Restore multiple items that were previously deleted.
+ /// This will post notifications tracking progress.
+ ///
+ void Undelete(List items, bool silent = false);
+
+ ///
+ /// Restore an item that was previously deleted. Is a no-op if the item is not in a deleted state, or has its protected flag set.
+ ///
+ /// The item to restore
+ void Undelete(TModel item);
+
+ ///
+ /// Import one or more items from filesystem .
+ ///
+ ///
+ /// This will be treated as a low priority import if more than one path is specified; use to always import at standard priority.
+ /// This will post notifications tracking progress.
+ ///
+ /// One or more archive locations on disk.
+ Task Import(params string[] paths);
+
+ Task Import(params ImportTask[] tasks);
+
+ Task> Import(ProgressNotification notification, params ImportTask[] tasks);
+
+ ///
+ /// Import one from the filesystem and delete the file on success.
+ /// Note that this bypasses the UI flow and should only be used for special cases or testing.
+ ///
+ /// The containing data about the to import.
+ /// Whether this is a low priority import.
+ /// An optional cancellation token.
+ /// The imported model, if successful.
+ Task Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default);
+
+ ///
+ /// Silently import an item from an .
+ ///
+ /// The archive to be imported.
+ /// Whether this is a low priority import.
+ /// An optional cancellation token.
+ Task Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default);
+
+ ///
+ /// Silently import an item from a .
+ ///
+ /// The model to be imported.
+ /// An optional archive to use for model population.
+ /// Whether this is a low priority import.
+ /// An optional cancellation token.
+ Task Import(TModel item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default);
}
}
diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs
index 81e701f001..56c346d177 100644
--- a/osu.Game/Scoring/ScoreManager.cs
+++ b/osu.Game/Scoring/ScoreManager.cs
@@ -78,7 +78,7 @@ namespace osu.Game.Scoring
protected override Task Populate(ScoreInfo model, ArchiveReader archive, CancellationToken cancellationToken = default)
=> Task.CompletedTask;
- protected override void ExportModelTo(ScoreInfo model, Stream outputStream)
+ public override void ExportModelTo(ScoreInfo model, Stream outputStream)
{
var file = model.Files.SingleOrDefault();
if (file == null)