mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 21:52:55 +08:00
Merge pull request #3339 from peppy/add-skin-import-delete
Add skin import and mass delete options
This commit is contained in:
commit
20719439c6
@ -63,6 +63,8 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
public override string[] HandledExtensions => new[] { ".osz" };
|
public override string[] HandledExtensions => new[] { ".osz" };
|
||||||
|
|
||||||
|
protected override string ImportFromStablePath => "Songs";
|
||||||
|
|
||||||
private readonly RulesetStore rulesets;
|
private readonly RulesetStore rulesets;
|
||||||
|
|
||||||
private readonly BeatmapStore beatmaps;
|
private readonly BeatmapStore beatmaps;
|
||||||
@ -73,11 +75,6 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
private readonly List<DownloadBeatmapSetRequest> currentDownloads = new List<DownloadBeatmapSetRequest>();
|
private readonly List<DownloadBeatmapSetRequest> currentDownloads = new List<DownloadBeatmapSetRequest>();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set a storage with access to an osu-stable install for import purposes.
|
|
||||||
/// </summary>
|
|
||||||
public Func<Storage> GetStableStorage { private get; set; }
|
|
||||||
|
|
||||||
public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, APIAccess api, AudioManager audioManager, IIpcHost importHost = null)
|
public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, APIAccess api, AudioManager audioManager, IIpcHost importHost = null)
|
||||||
: base(storage, contextFactory, new BeatmapStore(contextFactory), importHost)
|
: base(storage, contextFactory, new BeatmapStore(contextFactory), importHost)
|
||||||
{
|
{
|
||||||
@ -318,27 +315,6 @@ namespace osu.Game.Beatmaps
|
|||||||
/// <returns>Results from the provided query.</returns>
|
/// <returns>Results from the provided query.</returns>
|
||||||
public IQueryable<BeatmapInfo> QueryBeatmaps(Expression<Func<BeatmapInfo, bool>> query) => beatmaps.Beatmaps.AsNoTracking().Where(query);
|
public IQueryable<BeatmapInfo> QueryBeatmaps(Expression<Func<BeatmapInfo, bool>> query) => beatmaps.Beatmaps.AsNoTracking().Where(query);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Denotes whether an osu-stable installation is present to perform automated imports from.
|
|
||||||
/// </summary>
|
|
||||||
public bool StableInstallationAvailable => GetStableStorage?.Invoke() != null;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future.
|
|
||||||
/// </summary>
|
|
||||||
public Task ImportFromStable()
|
|
||||||
{
|
|
||||||
var stable = GetStableStorage?.Invoke();
|
|
||||||
|
|
||||||
if (stable == null)
|
|
||||||
{
|
|
||||||
Logger.Log("No osu!stable installation available!", LoggingTarget.Information, LogLevel.Error);
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.Factory.StartNew(() => Import(stable.GetDirectories("Songs").Select(f => stable.GetFullPath(f)).ToArray()), TaskCreationOptions.LongRunning);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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>
|
||||||
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using osu.Framework.IO.File;
|
using osu.Framework.IO.File;
|
||||||
@ -129,7 +130,6 @@ namespace osu.Game.Database
|
|||||||
List<TModel> imported = new List<TModel>();
|
List<TModel> imported = new List<TModel>();
|
||||||
|
|
||||||
int current = 0;
|
int current = 0;
|
||||||
int errors = 0;
|
|
||||||
foreach (string path in paths)
|
foreach (string path in paths)
|
||||||
{
|
{
|
||||||
if (notification.State == ProgressNotificationState.Cancelled)
|
if (notification.State == ProgressNotificationState.Cancelled)
|
||||||
@ -162,18 +162,25 @@ namespace osu.Game.Database
|
|||||||
{
|
{
|
||||||
e = e.InnerException ?? e;
|
e = e.InnerException ?? e;
|
||||||
Logger.Error(e, $@"Could not import ({Path.GetFileName(path)})");
|
Logger.Error(e, $@"Could not import ({Path.GetFileName(path)})");
|
||||||
errors++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
notification.CompletionText = errors > 0 ? $"Import complete with {errors} errors" : "Import successful!";
|
if (imported.Count == 0)
|
||||||
notification.CompletionClickAction += () =>
|
|
||||||
{
|
{
|
||||||
if (imported.Count > 0)
|
notification.Text = "Import failed!";
|
||||||
PresentCompletedImport(imported);
|
notification.State = ProgressNotificationState.Cancelled;
|
||||||
return true;
|
}
|
||||||
};
|
else
|
||||||
notification.State = ProgressNotificationState.Completed;
|
{
|
||||||
|
notification.CompletionText = $"Imported {current} {typeof(TModel).Name.Replace("Info", "").ToLower()}s!";
|
||||||
|
notification.CompletionClickAction += () =>
|
||||||
|
{
|
||||||
|
if (imported.Count > 0)
|
||||||
|
PresentCompletedImport(imported);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
notification.State = ProgressNotificationState.Completed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void PresentCompletedImport(IEnumerable<TModel> imported)
|
protected virtual void PresentCompletedImport(IEnumerable<TModel> imported)
|
||||||
@ -293,7 +300,7 @@ namespace osu.Game.Database
|
|||||||
var notification = new ProgressNotification
|
var notification = new ProgressNotification
|
||||||
{
|
{
|
||||||
Progress = 0,
|
Progress = 0,
|
||||||
CompletionText = "Deleted all beatmaps!",
|
CompletionText = $"Deleted all {typeof(TModel).Name.Replace("Info", "").ToLower()}s!",
|
||||||
State = ProgressNotificationState.Active,
|
State = ProgressNotificationState.Active,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -395,6 +402,41 @@ namespace osu.Game.Database
|
|||||||
return fileInfos;
|
return fileInfos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region osu-stable import
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set a storage with access to an osu-stable install for import purposes.
|
||||||
|
/// </summary>
|
||||||
|
public Func<Storage> GetStableStorage { private get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Denotes whether an osu-stable installation is present to perform automated imports from.
|
||||||
|
/// </summary>
|
||||||
|
public bool StableInstallationAvailable => GetStableStorage?.Invoke() != null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The relative path from osu-stable's data directory to import items from.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual string ImportFromStablePath => null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future.
|
||||||
|
/// </summary>
|
||||||
|
public Task ImportFromStableAsync()
|
||||||
|
{
|
||||||
|
var stable = GetStableStorage?.Invoke();
|
||||||
|
|
||||||
|
if (stable == null)
|
||||||
|
{
|
||||||
|
Logger.Log("No osu!stable installation available!", LoggingTarget.Information, LogLevel.Error);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.Factory.StartNew(() => Import(stable.GetDirectories(ImportFromStablePath).Select(f => stable.GetFullPath(f)).ToArray()), TaskCreationOptions.LongRunning);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a barebones model from the provided archive.
|
/// 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.
|
/// Actual expensive population should be done in <see cref="Populate"/>; this should just prepare for duplicate checking.
|
||||||
|
@ -299,11 +299,13 @@ namespace osu.Game
|
|||||||
// This prevents the cursor from showing until we have a screen with CursorVisible = true
|
// This prevents the cursor from showing until we have a screen with CursorVisible = true
|
||||||
MenuCursorContainer.CanShowCursor = currentScreen?.CursorVisible ?? false;
|
MenuCursorContainer.CanShowCursor = currentScreen?.CursorVisible ?? false;
|
||||||
|
|
||||||
// hook up notifications to components.
|
// todo: all archive managers should be able to be looped here.
|
||||||
SkinManager.PostNotification = n => notifications?.Post(n);
|
SkinManager.PostNotification = n => notifications?.Post(n);
|
||||||
BeatmapManager.PostNotification = n => notifications?.Post(n);
|
SkinManager.GetStableStorage = GetStorageForStableInstall;
|
||||||
|
|
||||||
|
BeatmapManager.PostNotification = n => notifications?.Post(n);
|
||||||
BeatmapManager.GetStableStorage = GetStorageForStableInstall;
|
BeatmapManager.GetStableStorage = GetStorageForStableInstall;
|
||||||
|
|
||||||
BeatmapManager.PresentBeatmap = PresentBeatmap;
|
BeatmapManager.PresentBeatmap = PresentBeatmap;
|
||||||
|
|
||||||
AddRange(new Drawable[]
|
AddRange(new Drawable[]
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||||
{
|
{
|
||||||
@ -20,7 +21,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
protected override string Header => "General";
|
protected override string Header => "General";
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(BeatmapManager beatmaps, DialogOverlay dialogOverlay)
|
private void load(BeatmapManager beatmaps, SkinManager skins, DialogOverlay dialogOverlay)
|
||||||
{
|
{
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -30,7 +31,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
Action = () =>
|
Action = () =>
|
||||||
{
|
{
|
||||||
importButton.Enabled.Value = false;
|
importButton.Enabled.Value = false;
|
||||||
beatmaps.ImportFromStable().ContinueWith(t => Schedule(() => importButton.Enabled.Value = true));
|
beatmaps.ImportFromStableAsync().ContinueWith(t => Schedule(() => importButton.Enabled.Value = true));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
deleteButton = new DangerousSettingsButton
|
deleteButton = new DangerousSettingsButton
|
||||||
@ -45,6 +46,27 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
importButton = new SettingsButton
|
||||||
|
{
|
||||||
|
Text = "Import skins from stable",
|
||||||
|
Action = () =>
|
||||||
|
{
|
||||||
|
importButton.Enabled.Value = false;
|
||||||
|
skins.ImportFromStableAsync().ContinueWith(t => Schedule(() => importButton.Enabled.Value = true));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deleteButton = new DangerousSettingsButton
|
||||||
|
{
|
||||||
|
Text = "Delete ALL skins",
|
||||||
|
Action = () =>
|
||||||
|
{
|
||||||
|
dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() =>
|
||||||
|
{
|
||||||
|
deleteButton.Enabled.Value = false;
|
||||||
|
Task.Run(() => skins.Delete(skins.GetAllUserSkins())).ContinueWith(t => Schedule(() => deleteButton.Enabled.Value = true));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
},
|
||||||
restoreButton = new SettingsButton
|
restoreButton = new SettingsButton
|
||||||
{
|
{
|
||||||
Text = "Restore all hidden difficulties",
|
Text = "Restore all hidden difficulties",
|
||||||
|
@ -51,10 +51,10 @@ namespace osu.Game.Overlays.Settings.Sections
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
skins.ItemAdded += onItemsChanged;
|
skins.ItemAdded += itemAdded;
|
||||||
skins.ItemRemoved += onItemsChanged;
|
skins.ItemRemoved += itemRemoved;
|
||||||
|
|
||||||
reloadSkins();
|
skinDropdown.Items = skins.GetAllUsableSkins().Select(s => new KeyValuePair<string, int>(s.ToString(), s.ID));
|
||||||
|
|
||||||
var skinBindable = config.GetBindable<int>(OsuSetting.Skin);
|
var skinBindable = config.GetBindable<int>(OsuSetting.Skin);
|
||||||
|
|
||||||
@ -65,9 +65,8 @@ namespace osu.Game.Overlays.Settings.Sections
|
|||||||
skinDropdown.Bindable = skinBindable;
|
skinDropdown.Bindable = skinBindable;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reloadSkins() => skinDropdown.Items = skins.GetAllUsableSkins().Select(s => new KeyValuePair<string, int>(s.ToString(), s.ID));
|
private void itemRemoved(SkinInfo s) => skinDropdown.Items = skinDropdown.Items.Where(i => i.Value != s.ID);
|
||||||
|
private void itemAdded(SkinInfo s) => skinDropdown.Items = skinDropdown.Items.Append(new KeyValuePair<string, int>(s.ToString(), s.ID));
|
||||||
private void onItemsChanged(SkinInfo _) => Schedule(reloadSkins);
|
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
@ -75,8 +74,8 @@ namespace osu.Game.Overlays.Settings.Sections
|
|||||||
|
|
||||||
if (skins != null)
|
if (skins != null)
|
||||||
{
|
{
|
||||||
skins.ItemAdded -= onItemsChanged;
|
skins.ItemAdded -= itemAdded;
|
||||||
skins.ItemRemoved -= onItemsChanged;
|
skins.ItemRemoved -= itemRemoved;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ namespace osu.Game.Screens.Select
|
|||||||
public ImportFromStablePopup(Action importFromStable)
|
public ImportFromStablePopup(Action importFromStable)
|
||||||
{
|
{
|
||||||
HeaderText = @"You have no beatmaps!";
|
HeaderText = @"You have no beatmaps!";
|
||||||
BodyText = "An existing copy of osu! was found, though.\nWould you like to import your beatmaps?";
|
BodyText = "An existing copy of osu! was found, though.\nWould you like to import your beatmaps (and skins)?";
|
||||||
|
|
||||||
Icon = FontAwesome.fa_plane;
|
Icon = FontAwesome.fa_plane;
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using OpenTK.Input;
|
using OpenTK.Input;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
@ -20,6 +19,7 @@ using osu.Game.Rulesets.Mods;
|
|||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Screens.Ranking;
|
using osu.Game.Screens.Ranking;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Select
|
namespace osu.Game.Screens.Select
|
||||||
{
|
{
|
||||||
@ -55,7 +55,7 @@ namespace osu.Game.Screens.Select
|
|||||||
private readonly Bindable<IEnumerable<Mod>> selectedMods = new Bindable<IEnumerable<Mod>>(new Mod[] { });
|
private readonly Bindable<IEnumerable<Mod>> selectedMods = new Bindable<IEnumerable<Mod>>(new Mod[] { });
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(OsuColour colours, AudioManager audio, BeatmapManager beatmaps, DialogOverlay dialogOverlay, Bindable<IEnumerable<Mod>> selectedMods)
|
private void load(OsuColour colours, AudioManager audio, BeatmapManager beatmaps, SkinManager skins, DialogOverlay dialogOverlay, Bindable<IEnumerable<Mod>> selectedMods)
|
||||||
{
|
{
|
||||||
if (selectedMods != null) this.selectedMods.BindTo(selectedMods);
|
if (selectedMods != null) this.selectedMods.BindTo(selectedMods);
|
||||||
|
|
||||||
@ -78,7 +78,10 @@ namespace osu.Game.Screens.Select
|
|||||||
// if we have no beatmaps but osu-stable is found, let's prompt the user to import.
|
// if we have no beatmaps but osu-stable is found, let's prompt the user to import.
|
||||||
if (!beatmaps.GetAllUsableBeatmapSets().Any() && beatmaps.StableInstallationAvailable)
|
if (!beatmaps.GetAllUsableBeatmapSets().Any() && beatmaps.StableInstallationAvailable)
|
||||||
dialogOverlay.Push(new ImportFromStablePopup(() =>
|
dialogOverlay.Push(new ImportFromStablePopup(() =>
|
||||||
Task.Factory.StartNew(beatmaps.ImportFromStable, TaskCreationOptions.LongRunning)));
|
{
|
||||||
|
beatmaps.ImportFromStableAsync();
|
||||||
|
skins.ImportFromStableAsync();
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,17 +26,25 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
public override string[] HandledExtensions => new[] { ".osk" };
|
public override string[] HandledExtensions => new[] { ".osk" };
|
||||||
|
|
||||||
|
protected override string ImportFromStablePath => "Skins";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a list of all usable <see cref="SkinInfo"/>s.
|
/// Returns a list of all usable <see cref="SkinInfo"/>s. Includes the special default skin plus all skins from <see cref="GetAllUserSkins"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>A list of available <see cref="SkinInfo"/>.</returns>
|
/// <returns>A list of available <see cref="SkinInfo"/>.</returns>
|
||||||
public List<SkinInfo> GetAllUsableSkins()
|
public List<SkinInfo> GetAllUsableSkins()
|
||||||
{
|
{
|
||||||
var userSkins = ModelStore.ConsumableItems.Where(s => !s.DeletePending).ToList();
|
var userSkins = GetAllUserSkins();
|
||||||
userSkins.Insert(0, SkinInfo.Default);
|
userSkins.Insert(0, SkinInfo.Default);
|
||||||
return userSkins;
|
return userSkins;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a list of all usable <see cref="SkinInfo"/>s that have been loaded by the user.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A list of available <see cref="SkinInfo"/>.</returns>
|
||||||
|
public List<SkinInfo> GetAllUserSkins() => ModelStore.ConsumableItems.Where(s => !s.DeletePending).ToList();
|
||||||
|
|
||||||
protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo
|
protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo
|
||||||
{
|
{
|
||||||
Name = archive.Name
|
Name = archive.Name
|
||||||
@ -85,6 +93,13 @@ namespace osu.Game.Skinning
|
|||||||
{
|
{
|
||||||
this.audio = audio;
|
this.audio = audio;
|
||||||
|
|
||||||
|
ItemRemoved += removedInfo =>
|
||||||
|
{
|
||||||
|
// check the removed skin is not the current user choice. if it is, switch back to default.
|
||||||
|
if (removedInfo.ID == CurrentSkinInfo.Value.ID)
|
||||||
|
CurrentSkinInfo.Value = SkinInfo.Default;
|
||||||
|
};
|
||||||
|
|
||||||
CurrentSkinInfo.ValueChanged += info => CurrentSkin.Value = GetSkin(info);
|
CurrentSkinInfo.ValueChanged += info => CurrentSkin.Value = GetSkin(info);
|
||||||
CurrentSkin.ValueChanged += skin =>
|
CurrentSkin.ValueChanged += skin =>
|
||||||
{
|
{
|
||||||
@ -93,16 +108,6 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
SourceChanged?.Invoke();
|
SourceChanged?.Invoke();
|
||||||
};
|
};
|
||||||
|
|
||||||
// migrate older imports which didn't have access to skin.ini
|
|
||||||
using (ContextFactory.GetForWrite())
|
|
||||||
{
|
|
||||||
foreach (var skinInfo in ModelStore.ConsumableItems.Where(s => s.Name.EndsWith(".osk")))
|
|
||||||
{
|
|
||||||
populate(skinInfo);
|
|
||||||
Update(skinInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
Loading…
Reference in New Issue
Block a user