diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 99117afe35..a23fab5393 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -17,6 +17,7 @@ using osu.Game.Beatmaps.Formats;
using osu.Game.Beatmaps.IO;
using osu.Game.IO;
using osu.Game.IPC;
+using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets;
using SQLite.Net;
using FileInfo = osu.Game.IO.FileInfo;
@@ -54,6 +55,11 @@ namespace osu.Game.Beatmaps
// ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised)
private BeatmapIPCChannel ipc;
+ ///
+ /// Set an endpoint for notifications to be posted to.
+ ///
+ public Action PostNotification { private get; set; }
+
public BeatmapManager(Storage storage, FileStore files, SQLiteConnection connection, RulesetStore rulesets, IIpcHost importHost = null)
{
beatmaps = new BeatmapStore(connection);
@@ -69,29 +75,48 @@ namespace osu.Game.Beatmaps
}
///
- /// Import multiple from filesystem .
+ /// Import one or more from filesystem .
+ /// This will post a notification tracking import progress.
///
- /// Multiple locations on disk.
+ /// One or more beatmap locations on disk.
public void Import(params string[] paths)
{
+ var notification = new ProgressNotification
+ {
+ Text = "Beatmap import is initialising...",
+ Progress = 0,
+ State = ProgressNotificationState.Active,
+ };
+
+ PostNotification?.Invoke(notification);
+
+ 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))
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
{
- File.Delete(path);
+ if (File.Exists(path))
+ File.Delete(path);
}
catch (Exception e)
{
- Logger.Error(e, $@"Could not delete file at {path}");
+ Logger.Error(e, $@"Could not delete original file after import ({Path.GetFileName(path)})");
}
}
catch (Exception e)
@@ -100,17 +125,25 @@ namespace osu.Game.Beatmaps
Logger.Error(e, @"Could not import beatmap set");
}
}
+
+ notification.State = ProgressNotificationState.Completed;
}
+ private readonly object importLock = new object();
+
///
/// Import a beatmap from an .
///
/// The beatmap to be imported.
public BeatmapSetInfo Import(ArchiveReader archiveReader)
{
- BeatmapSetInfo set = importToStorage(archiveReader);
- Import(set);
- return set;
+ // let's only allow one concurrent import at a time for now.
+ lock (importLock)
+ {
+ BeatmapSetInfo set = importToStorage(archiveReader);
+ Import(set);
+ return set;
+ }
}
///
@@ -122,7 +155,8 @@ namespace osu.Game.Beatmaps
// If we have an ID then we already exist in the database.
if (beatmapSetInfo.ID != 0) return;
- beatmaps.Add(beatmapSetInfo);
+ lock (beatmaps)
+ beatmaps.Add(beatmapSetInfo);
}
///
@@ -132,7 +166,8 @@ namespace osu.Game.Beatmaps
/// The beatmap to delete.
public void Delete(BeatmapSetInfo beatmapSet)
{
- if (!beatmaps.Delete(beatmapSet)) return;
+ lock (beatmaps)
+ if (!beatmaps.Delete(beatmapSet)) return;
if (!beatmapSet.Protected)
files.Dereference(beatmapSet.Files);
@@ -145,7 +180,8 @@ namespace osu.Game.Beatmaps
/// The beatmap to restore.
public void Undelete(BeatmapSetInfo beatmapSet)
{
- if (!beatmaps.Undelete(beatmapSet)) return;
+ lock (beatmaps)
+ if (!beatmaps.Undelete(beatmapSet)) return;
files.Reference(beatmapSet.Files);
}
@@ -161,7 +197,8 @@ namespace osu.Game.Beatmaps
if (beatmapInfo == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo)
return DefaultBeatmap;
- beatmaps.Populate(beatmapInfo);
+ lock (beatmaps)
+ beatmaps.Populate(beatmapInfo);
if (beatmapInfo.BeatmapSet == null)
throw new InvalidOperationException($@"Beatmap set {beatmapInfo.BeatmapSetInfoID} is not in the local database.");
@@ -181,7 +218,8 @@ namespace osu.Game.Beatmaps
///
public void Reset()
{
- beatmaps.Reset();
+ lock (beatmaps)
+ beatmaps.Reset();
}
///
@@ -191,12 +229,15 @@ namespace osu.Game.Beatmaps
/// The first result for the provided query, or null if no results were found.
public BeatmapSetInfo QueryBeatmapSet(Func query)
{
- BeatmapSetInfo set = beatmaps.Query().FirstOrDefault(query);
+ lock (beatmaps)
+ {
+ BeatmapSetInfo set = beatmaps.Query().FirstOrDefault(query);
- if (set != null)
- beatmaps.Populate(set);
+ if (set != null)
+ beatmaps.Populate(set);
- return set;
+ return set;
+ }
}
///
@@ -204,7 +245,10 @@ namespace osu.Game.Beatmaps
///
/// The query.
/// Results from the provided query.
- public List QueryBeatmapSets(Expression> query) => beatmaps.QueryAndPopulate(query);
+ public List QueryBeatmapSets(Expression> query)
+ {
+ lock (beatmaps) return beatmaps.QueryAndPopulate(query);
+ }
///
/// Perform a lookup query on available s.
@@ -213,12 +257,15 @@ namespace osu.Game.Beatmaps
/// The first result for the provided query, or null if no results were found.
public BeatmapInfo QueryBeatmap(Func query)
{
- BeatmapInfo set = beatmaps.Query().FirstOrDefault(query);
+ lock (beatmaps)
+ {
+ BeatmapInfo set = beatmaps.Query().FirstOrDefault(query);
- if (set != null)
- beatmaps.Populate(set);
+ if (set != null)
+ beatmaps.Populate(set);
- return set;
+ return set;
+ }
}
///
@@ -226,7 +273,10 @@ namespace osu.Game.Beatmaps
///
/// The query.
/// Results from the provided query.
- public List QueryBeatmaps(Expression> query) => beatmaps.QueryAndPopulate(query);
+ public List QueryBeatmaps(Expression> query)
+ {
+ lock (beatmaps) return beatmaps.QueryAndPopulate(query);
+ }
///
/// Creates an from a valid storage path.
@@ -258,7 +308,10 @@ namespace osu.Game.Beatmaps
var hash = hashable.ComputeSHA2Hash();
// check if this beatmap has already been imported and exit early if so.
- var beatmapSet = beatmaps.QueryAndPopulate().FirstOrDefault(b => b.Hash == hash);
+ BeatmapSetInfo beatmapSet;
+ lock (beatmaps)
+ beatmapSet = beatmaps.QueryAndPopulate(b => b.Hash == hash).FirstOrDefault();
+
if (beatmapSet != null)
{
Undelete(beatmapSet);
@@ -325,10 +378,13 @@ namespace osu.Game.Beatmaps
/// A list of available .
public List GetAllUsableBeatmapSets(bool populate = true)
{
- if (populate)
- return beatmaps.QueryAndPopulate(b => !b.DeletePending).ToList();
- else
- return beatmaps.Query(b => !b.DeletePending).ToList();
+ lock (beatmaps)
+ {
+ if (populate)
+ return beatmaps.QueryAndPopulate(b => !b.DeletePending).ToList();
+ else
+ return beatmaps.Query(b => !b.DeletePending).ToList();
+ }
}
protected class BeatmapManagerWorkingBeatmap : WorkingBeatmap
@@ -390,5 +446,50 @@ namespace osu.Game.Beatmaps
catch { return new TrackVirtual(); }
}
}
+
+ public void ImportFromStable()
+ {
+ string stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!", "Songs");
+ if (!Directory.Exists(stableInstallPath))
+ stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".osu", "Songs");
+
+ if (!Directory.Exists(stableInstallPath))
+ {
+ Logger.Log("Couldn't find an osu!stable installation!", LoggingTarget.Information, LogLevel.Error);
+ return;
+ }
+
+ Import(Directory.GetDirectories(stableInstallPath));
+ }
+
+ public void DeleteAll()
+ {
+ var maps = GetAllUsableBeatmapSets().ToArray();
+
+ if (maps.Length == 0) return;
+
+ var notification = new ProgressNotification
+ {
+ Progress = 0,
+ State = ProgressNotificationState.Active,
+ };
+
+ PostNotification?.Invoke(notification);
+
+ int i = 0;
+
+ foreach (var b in maps)
+ {
+ if (notification.State == ProgressNotificationState.Cancelled)
+ // user requested abort
+ return;
+
+ notification.Text = $"Deleting ({i} of {maps.Length})";
+ notification.Progress = (float)++i / maps.Length;
+ Delete(b);
+ }
+
+ notification.State = ProgressNotificationState.Completed;
+ }
}
}
diff --git a/osu.Game/Database/DatabaseBackedStore.cs b/osu.Game/Database/DatabaseBackedStore.cs
index bb61fc1870..bd25acc41b 100644
--- a/osu.Game/Database/DatabaseBackedStore.cs
+++ b/osu.Game/Database/DatabaseBackedStore.cs
@@ -83,9 +83,9 @@ namespace osu.Game.Database
///
/// Query and populate results.
///
- /// An optional filter to refine results.
+ /// An filter to refine results.
///
- public List QueryAndPopulate(Expression> filter = null)
+ public List QueryAndPopulate(Expression> filter)
where T : class
{
checkType(typeof(T));
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index fe6d2dbb41..4082ed4ecd 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -149,6 +149,9 @@ namespace osu.Game
{
base.LoadComplete();
+ // hook up notifications to components.
+ BeatmapManager.PostNotification = n => notificationOverlay?.Post(n);
+
AddRange(new Drawable[] {
new VolumeControlReceptor
{
diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs
index 88f499f9a6..3dd514edeb 100644
--- a/osu.Game/Overlays/Music/PlaylistList.cs
+++ b/osu.Game/Overlays/Music/PlaylistList.cs
@@ -80,7 +80,7 @@ namespace osu.Game.Overlays.Music
public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet)
{
- PlaylistItem itemToRemove = items.Children.FirstOrDefault(item => item.BeatmapSetInfo == beatmapSet);
+ PlaylistItem itemToRemove = items.Children.FirstOrDefault(item => item.BeatmapSetInfo.ID == beatmapSet.ID);
if (itemToRemove != null) items.Remove(itemToRemove);
}
diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs
index 31fe755d2b..100b397890 100644
--- a/osu.Game/Overlays/Music/PlaylistOverlay.cs
+++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs
@@ -77,11 +77,11 @@ namespace osu.Game.Overlays.Music
},
};
+ beatmaps.BeatmapSetAdded += s => Schedule(() => list.AddBeatmapSet(s));
+ beatmaps.BeatmapSetRemoved += s => Schedule(() => list.RemoveBeatmapSet(s));
+
list.BeatmapSets = BeatmapSets = beatmaps.GetAllUsableBeatmapSets();
- // todo: these should probably be above the query.
- beatmaps.BeatmapSetAdded += s => list.AddBeatmapSet(s);
- beatmaps.BeatmapSetRemoved += s => list.RemoveBeatmapSet(s);
beatmapBacking.BindTo(game.Beatmap);
diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs
new file mode 100644
index 0000000000..9d13a2ae2f
--- /dev/null
+++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs
@@ -0,0 +1,47 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Threading.Tasks;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Game.Beatmaps;
+using osu.Game.Graphics.UserInterface;
+
+namespace osu.Game.Overlays.Settings.Sections.Maintenance
+{
+ public class GeneralSettings : SettingsSubsection
+ {
+ private OsuButton importButton;
+ private OsuButton deleteButton;
+
+ protected override string Header => "General";
+
+ [BackgroundDependencyLoader]
+ private void load(BeatmapManager beatmaps)
+ {
+ Children = new Drawable[]
+ {
+ importButton = new OsuButton
+ {
+ RelativeSizeAxes = Axes.X,
+ Text = "Import beatmaps from stable",
+ Action = () =>
+ {
+ importButton.Enabled.Value = false;
+ Task.Run(() => beatmaps.ImportFromStable()).ContinueWith(t => Schedule(() => importButton.Enabled.Value = true));
+ }
+ },
+ deleteButton = new OsuButton
+ {
+ RelativeSizeAxes = Axes.X,
+ Text = "Delete ALL beatmaps",
+ Action = () =>
+ {
+ deleteButton.Enabled.Value = false;
+ Task.Run(() => beatmaps.DeleteAll()).ContinueWith(t => Schedule(() => deleteButton.Enabled.Value = true));
+ }
+ },
+ };
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs
index 529cec79c1..b42c64d324 100644
--- a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs
@@ -3,6 +3,7 @@
using osu.Framework.Graphics;
using osu.Game.Graphics;
+using osu.Game.Overlays.Settings.Sections.Maintenance;
using OpenTK;
namespace osu.Game.Overlays.Settings.Sections
@@ -17,6 +18,7 @@ namespace osu.Game.Overlays.Settings.Sections
FlowContent.Spacing = new Vector2(0, 5);
Children = new Drawable[]
{
+ new GeneralSettings()
};
}
}
diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs
index 9e5446a573..264636b258 100644
--- a/osu.Game/Screens/Select/BeatmapCarousel.cs
+++ b/osu.Game/Screens/Select/BeatmapCarousel.cs
@@ -107,6 +107,14 @@ namespace osu.Game.Screens.Select
});
}
+ public void RemoveBeatmap(BeatmapSetInfo beatmapSet)
+ {
+ Schedule(delegate
+ {
+ removeGroup(groups.Find(b => b.BeatmapSet.ID == beatmapSet.ID));
+ });
+ }
+
public void SelectBeatmap(BeatmapInfo beatmap, bool animated = true)
{
if (beatmap == null)
@@ -128,8 +136,6 @@ namespace osu.Game.Screens.Select
}
}
- public void RemoveBeatmap(BeatmapSetInfo info) => removeGroup(groups.Find(b => b.BeatmapSet.ID == info.ID));
-
public Action SelectionChanged;
public Action StartRequested;
diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs
index fd314e1559..217baccf58 100644
--- a/osu.Game/Screens/Select/SongSelect.cs
+++ b/osu.Game/Screens/Select/SongSelect.cs
@@ -285,7 +285,7 @@ namespace osu.Game.Screens.Select
carousel.Filter(criteria, debounce);
}
- private void onBeatmapSetAdded(BeatmapSetInfo s) => carousel.AddBeatmap(s);
+ private void onBeatmapSetAdded(BeatmapSetInfo s) => Schedule(() => addBeatmapSet(s));
private void onBeatmapSetRemoved(BeatmapSetInfo s) => Schedule(() => removeBeatmapSet(s));
@@ -380,6 +380,11 @@ namespace osu.Game.Screens.Select
}
}
+ private void addBeatmapSet(BeatmapSetInfo beatmapSet)
+ {
+ carousel.AddBeatmap(beatmapSet);
+ }
+
private void removeBeatmapSet(BeatmapSetInfo beatmapSet)
{
carousel.RemoveBeatmap(beatmapSet);
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index fa4665fd7d..8b462b5287 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -104,6 +104,7 @@
+