diff --git a/osu-framework b/osu-framework
index 5a9ca94fc3..2204764944 160000
--- a/osu-framework
+++ b/osu-framework
@@ -1 +1 @@
-Subproject commit 5a9ca94fc31bc796b45572eb3d0b27b46556c586
+Subproject commit 2204764944ff693e857649253547054cc91764a0
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index a23fab5393..5cc2a2a430 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -20,7 +20,6 @@ using osu.Game.IPC;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets;
using SQLite.Net;
-using FileInfo = osu.Game.IO.FileInfo;
namespace osu.Game.Beatmaps
{
@@ -170,7 +169,7 @@ namespace osu.Game.Beatmaps
if (!beatmaps.Delete(beatmapSet)) return;
if (!beatmapSet.Protected)
- files.Dereference(beatmapSet.Files);
+ files.Dereference(beatmapSet.Files.Select(f => f.FileInfo));
}
///
@@ -183,7 +182,8 @@ namespace osu.Game.Beatmaps
lock (beatmaps)
if (!beatmaps.Undelete(beatmapSet)) return;
- files.Reference(beatmapSet.Files);
+ if (!beatmapSet.Protected)
+ files.Reference(beatmapSet.Files.Select(f => f.FileInfo));
}
///
@@ -318,12 +318,16 @@ namespace osu.Game.Beatmaps
return beatmapSet;
}
- List fileInfos = new List();
+ List fileInfos = new List();
// import files to manager
foreach (string file in reader.Filenames)
using (Stream s = reader.GetStream(file))
- fileInfos.Add(files.Add(s, file));
+ fileInfos.Add(new BeatmapSetFileInfo
+ {
+ Filename = file,
+ FileInfo = files.Add(s)
+ });
BeatmapMetadata metadata;
@@ -422,7 +426,7 @@ namespace osu.Game.Beatmaps
catch { return null; }
}
- private string getPathForFile(string filename) => BeatmapSetInfo.Files.First(f => f.Filename == filename).StoragePath;
+ private string getPathForFile(string filename) => BeatmapSetInfo.Files.First(f => f.Filename == filename).FileInfo.StoragePath;
protected override Texture GetBackground()
{
diff --git a/osu.Game/Beatmaps/BeatmapSetFileInfo.cs b/osu.Game/Beatmaps/BeatmapSetFileInfo.cs
index d18b1e833b..a05362b32d 100644
--- a/osu.Game/Beatmaps/BeatmapSetFileInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapSetFileInfo.cs
@@ -2,16 +2,26 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.IO;
+using SQLite.Net.Attributes;
using SQLiteNetExtensions.Attributes;
namespace osu.Game.Beatmaps
{
public class BeatmapSetFileInfo
{
- [ForeignKey(typeof(BeatmapSetInfo))]
+ [PrimaryKey, AutoIncrement]
+ public int ID { get; set; }
+
+ [ForeignKey(typeof(BeatmapSetInfo)), NotNull]
public int BeatmapSetInfoID { get; set; }
- [ForeignKey(typeof(FileInfo))]
+ [ForeignKey(typeof(FileInfo)), NotNull]
public int FileInfoID { get; set; }
+
+ [OneToOne(CascadeOperations = CascadeOperation.CascadeRead)]
+ public FileInfo FileInfo { get; set; }
+
+ [NotNull]
+ public string Filename { get; set; }
}
}
\ No newline at end of file
diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs
index a99ba94e9a..f47affcab8 100644
--- a/osu.Game/Beatmaps/BeatmapSetInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs
@@ -3,7 +3,6 @@
using System.Collections.Generic;
using System.Linq;
-using osu.Game.IO;
using SQLite.Net.Attributes;
using SQLiteNetExtensions.Attributes;
@@ -37,8 +36,8 @@ namespace osu.Game.Beatmaps
public string StoryboardFile => Files.FirstOrDefault(f => f.Filename.EndsWith(".osb"))?.Filename;
- [ManyToMany(typeof(BeatmapSetFileInfo), CascadeOperations = CascadeOperation.CascadeRead)]
- public List Files { get; set; }
+ [OneToMany(CascadeOperations = CascadeOperation.All)]
+ public List Files { get; set; }
public bool Protected { get; set; }
}
diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs
index 102900ae81..97cfdcf31b 100644
--- a/osu.Game/Beatmaps/BeatmapStore.cs
+++ b/osu.Game/Beatmaps/BeatmapStore.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Beatmaps
/// The current version of this store. Used for migrations (see ).
/// The initial version is 1.
///
- protected override int StoreVersion => 1;
+ protected override int StoreVersion => 2;
public BeatmapStore(SQLiteConnection connection)
: base(connection)
@@ -52,7 +52,11 @@ namespace osu.Game.Beatmaps
Connection.CreateTable();
Connection.CreateTable();
Connection.CreateTable();
+ }
+ protected override void StartupTasks()
+ {
+ base.StartupTasks();
cleanupPendingDeletions();
}
@@ -60,24 +64,19 @@ namespace osu.Game.Beatmaps
/// Perform migrations between two store versions.
///
/// The current store version. This will be zero on a fresh database initialisation.
- /// The target version which we are migrating to (equal to the current ).
- protected override void PerformMigration(int currentVersion, int newVersion)
+ /// The target version which we are migrating to (equal to the current ).
+ protected override void PerformMigration(int currentVersion, int targetVersion)
{
- base.PerformMigration(currentVersion, newVersion);
+ base.PerformMigration(currentVersion, targetVersion);
- while (currentVersion++ < newVersion)
+ while (currentVersion++ < targetVersion)
{
switch (currentVersion)
{
case 1:
- // initialising from a version before we had versioning (or a fresh install).
-
- // force adding of Protected column (not automatically migrated).
- Connection.MigrateTable();
-
- // remove all existing beatmaps.
- foreach (var b in Connection.GetAllWithChildren(null, true))
- Connection.Delete(b, true);
+ case 2:
+ // cannot migrate; breaking underlying changes.
+ Reset();
break;
}
}
diff --git a/osu.Game/Database/DatabaseBackedStore.cs b/osu.Game/Database/DatabaseBackedStore.cs
index bd25acc41b..e3afd9688a 100644
--- a/osu.Game/Database/DatabaseBackedStore.cs
+++ b/osu.Game/Database/DatabaseBackedStore.cs
@@ -51,14 +51,30 @@ namespace osu.Game.Database
PerformMigration(reportedVersion.Version, reportedVersion.Version = StoreVersion);
Connection.InsertOrReplace(reportedVersion);
+
+ StartupTasks();
}
- protected virtual void PerformMigration(int currentVersion, int newVersion)
+ ///
+ /// Called when the database version of this store doesn't match the local version.
+ /// Any manual migration operations should be performed in this.
+ ///
+ /// The current store version. This will be zero on a fresh database initialisation.
+ /// The target version which we are migrating to (equal to the current ).
+ protected virtual void PerformMigration(int currentVersion, int targetVersion)
{
}
///
- /// Prepare this database for use.
+ /// Perform any common startup tasks. Runs after and .
+ ///
+ protected virtual void StartupTasks()
+ {
+
+ }
+
+ ///
+ /// Prepare this database for use. Tables should be created here.
///
protected abstract void Prepare(bool reset = false);
@@ -101,7 +117,6 @@ namespace osu.Game.Database
public void Populate(T item, bool recursive = true)
{
checkType(item.GetType());
-
Connection.GetChildren(item, recursive);
}
diff --git a/osu.Game/IO/FileInfo.cs b/osu.Game/IO/FileInfo.cs
index 6f4c4b26e8..367fd68f7b 100644
--- a/osu.Game/IO/FileInfo.cs
+++ b/osu.Game/IO/FileInfo.cs
@@ -11,8 +11,6 @@ namespace osu.Game.IO
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
- public string Filename { get; set; }
-
[Indexed(Unique = true)]
public string Hash { get; set; }
diff --git a/osu.Game/IO/FileStore.cs b/osu.Game/IO/FileStore.cs
index e8cabafe17..e55af2e23a 100644
--- a/osu.Game/IO/FileStore.cs
+++ b/osu.Game/IO/FileStore.cs
@@ -23,6 +23,8 @@ namespace osu.Game.IO
public readonly ResourceStore Store;
+ protected override int StoreVersion => 2;
+
public FileStore(SQLiteConnection connection, Storage storage) : base(connection, storage)
{
Store = new NamespacedResourceStore(new StorageBackedResourceStore(storage), prefix);
@@ -35,22 +37,53 @@ namespace osu.Game.IO
protected override void Prepare(bool reset = false)
{
if (reset)
+ {
+ // in earlier versions we stored beatmaps as solid archives, but not any more.
+ if (Storage.ExistsDirectory("beatmaps"))
+ Storage.DeleteDirectory("beatmaps");
+
+ if (Storage.ExistsDirectory(prefix))
+ Storage.DeleteDirectory(prefix);
+
Connection.DropTable();
+ }
Connection.CreateTable();
+ }
+ protected override void StartupTasks()
+ {
+ base.StartupTasks();
deletePending();
}
- public FileInfo Add(Stream data, string filename = null)
+ ///
+ /// Perform migrations between two store versions.
+ ///
+ /// The current store version. This will be zero on a fresh database initialisation.
+ /// The target version which we are migrating to (equal to the current ).
+ protected override void PerformMigration(int currentVersion, int targetVersion)
+ {
+ base.PerformMigration(currentVersion, targetVersion);
+
+ while (currentVersion++ < targetVersion)
+ {
+ switch (currentVersion)
+ {
+ case 1:
+ case 2:
+ // cannot migrate; breaking underlying changes.
+ Reset();
+ break;
+ }
+ }
+ }
+
+ public FileInfo Add(Stream data)
{
string hash = data.ComputeSHA2Hash();
- var info = new FileInfo
- {
- Filename = filename,
- Hash = hash,
- };
+ var info = new FileInfo { Hash = hash };
var existing = Connection.Table().FirstOrDefault(f => f.Hash == info.Hash);
@@ -79,20 +112,32 @@ namespace osu.Game.IO
public void Reference(IEnumerable files)
{
- foreach (var f in files)
+ Connection.RunInTransaction(() =>
{
- f.ReferenceCount++;
- Connection.Update(f);
- }
+ var incrementedFiles = files.GroupBy(f => f.ID).Select(f =>
+ {
+ var accurateRefCount = Connection.Get(f.First().ID);
+ accurateRefCount.ReferenceCount += f.Count();
+ return accurateRefCount;
+ });
+
+ Connection.UpdateAll(incrementedFiles);
+ });
}
public void Dereference(IEnumerable files)
{
- foreach (var f in files)
+ Connection.RunInTransaction(() =>
{
- f.ReferenceCount--;
- Connection.Update(f);
- }
+ var incrementedFiles = files.GroupBy(f => f.ID).Select(f =>
+ {
+ var accurateRefCount = Connection.Get(f.First().ID);
+ accurateRefCount.ReferenceCount -= f.Count();
+ return accurateRefCount;
+ });
+
+ Connection.UpdateAll(incrementedFiles);
+ });
}
private void deletePending()
@@ -102,7 +147,7 @@ namespace osu.Game.IO
try
{
Connection.Delete(f);
- Storage.Delete(Path.Combine(prefix, f.Hash));
+ Storage.Delete(Path.Combine(prefix, f.StoragePath));
}
catch (Exception e)
{