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) {