diff --git a/OsuPlayer.CrashHandler/OsuPlayer.CrashHandler.csproj b/OsuPlayer.CrashHandler/OsuPlayer.CrashHandler.csproj index 5ea0f13e..aac58edb 100644 --- a/OsuPlayer.CrashHandler/OsuPlayer.CrashHandler.csproj +++ b/OsuPlayer.CrashHandler/OsuPlayer.CrashHandler.csproj @@ -40,7 +40,4 @@ CrashHandlerMainWindow.axaml - - - - + \ No newline at end of file diff --git a/OsuPlayer.IO/DbReader/DataModels/DbMapEntry.cs b/OsuPlayer.Data/DataModels/DbMapEntry.cs similarity index 94% rename from OsuPlayer.IO/DbReader/DataModels/DbMapEntry.cs rename to OsuPlayer.Data/DataModels/DbMapEntry.cs index 6f419879..5a9ff371 100644 --- a/OsuPlayer.IO/DbReader/DataModels/DbMapEntry.cs +++ b/OsuPlayer.Data/DataModels/DbMapEntry.cs @@ -1,13 +1,13 @@ using System.Diagnostics; -using OsuPlayer.IO.DbReader.Interfaces; +using OsuPlayer.Data.DataModels.Interfaces; -namespace OsuPlayer.IO.DbReader.DataModels; +namespace OsuPlayer.Data.DataModels; /// /// a full beatmap entry with optionally used data /// only created on a call /// -internal class DbMapEntry : DbMapEntryBase, IMapEntry +public class DbMapEntry : DbMapEntryBase, IMapEntry { public string ArtistUnicode { get; init; } = string.Empty; public string TitleUnicode { get; init; } = string.Empty; diff --git a/OsuPlayer.Data/DataModels/DbMapEntryBase.cs b/OsuPlayer.Data/DataModels/DbMapEntryBase.cs new file mode 100644 index 00000000..1a25baf3 --- /dev/null +++ b/OsuPlayer.Data/DataModels/DbMapEntryBase.cs @@ -0,0 +1,112 @@ +using System.Text; +using Nein.Extensions; +using OsuPlayer.Data.DataModels.Interfaces; + +namespace OsuPlayer.Data.DataModels; + +/// +/// a minimal beatmap entry with only frequently used data +/// +public class DbMapEntryBase : IMapEntryBase +{ + public required IDbReaderFactory DbReaderFactory { get; init; } + + public long DbOffset { get; init; } + public string? OsuPath { get; init; } + public string Artist { get; init; } = string.Empty; + public string Title { get; init; } = string.Empty; + public string Hash { get; init; } = string.Empty; + public int BeatmapSetId { get; init; } + public int TotalTime { get; init; } + public string TotalTimeString => TimeSpan.FromMilliseconds(TotalTime).FormatTime(); + public string SongName => GetSongName(); + public string ArtistString => GetArtist(); + public string TitleString => GetTitle(); + + /// + /// Gets the artist + /// may be overridden for usage with + /// + /// the artist + public virtual string GetArtist() + { + return Artist; + } + + /// + /// Gets the title + /// may be overridden for usage with + /// + /// the title + public virtual string GetTitle() + { + return Title; + } + + public string GetSongName() + { + return $"{GetArtist()} - {GetTitle()}"; + } + + /// + /// Reads a osu!.db map entry and fills a full with data + /// + /// a new generated from osu!.db data + public IMapEntry? ReadFullEntry() + { + if (OsuPath == null) return null; + + var reader = GetReader(); + + if (reader == default) + return null; + + return reader.ReadFullEntry(OsuPath, this, dbOffset: DbOffset); + } + + public IDatabaseReader? GetReader() + { + if (OsuPath == null) + return null; + + return DbReaderFactory.CreateDatabaseReader(OsuPath); + } + + public bool Equals(IMapEntryBase? other) + { + return Hash == other?.Hash; + } + + public int CompareTo(IMapEntryBase? other) + { + return string.Compare(Hash, other?.Hash, StringComparison.OrdinalIgnoreCase); + } + + public override string ToString() + { + return GetSongName(); + } + + public static bool operator ==(DbMapEntryBase? left, IMapEntryBase? right) + { + return left?.Hash == right?.Hash; + } + + public static bool operator !=(DbMapEntryBase? left, IMapEntryBase? right) + { + return left?.Hash != right?.Hash; + } + + public override bool Equals(object? other) + { + if (other is IMapEntryBase map) + return Hash == map.Hash; + + return false; + } + + public override int GetHashCode() + { + return BitConverter.ToInt32(Encoding.UTF8.GetBytes(Hash)); + } +} \ No newline at end of file diff --git a/OsuPlayer.IO/DbReader/DataModels/Extensions/HistoricalMapEntry.cs b/OsuPlayer.Data/DataModels/Extensions/HistoricalMapEntry.cs similarity index 76% rename from OsuPlayer.IO/DbReader/DataModels/Extensions/HistoricalMapEntry.cs rename to OsuPlayer.Data/DataModels/Extensions/HistoricalMapEntry.cs index ad137c31..51c3bc4b 100644 --- a/OsuPlayer.IO/DbReader/DataModels/Extensions/HistoricalMapEntry.cs +++ b/OsuPlayer.Data/DataModels/Extensions/HistoricalMapEntry.cs @@ -1,6 +1,6 @@ -using OsuPlayer.IO.DbReader.Interfaces; +using OsuPlayer.Data.DataModels.Interfaces; -namespace OsuPlayer.IO.DbReader.DataModels.Extensions; +namespace OsuPlayer.Data.DataModels.Extensions; public class HistoricalMapEntry : IComparable { @@ -9,7 +9,7 @@ public class HistoricalMapEntry : IComparable public string TimePlayedString => $"Last Played: {TimePlayed:G}"; - public HistoricalMapEntry(IMapEntryBase mapEntry) :this(mapEntry, DateTimeOffset.Now) + public HistoricalMapEntry(IMapEntryBase mapEntry) : this(mapEntry, DateTimeOffset.Now) { } @@ -26,7 +26,7 @@ public int CompareTo(HistoricalMapEntry other) public override bool Equals(object? obj) { - if(obj is HistoricalMapEntry other) + if (obj is HistoricalMapEntry other) { return MapEntry.Hash == other.MapEntry.Hash; } diff --git a/OsuPlayer.IO/DbReader/DataModels/Extensions/HistoricalMapEntryComparer.cs b/OsuPlayer.Data/DataModels/Extensions/HistoricalMapEntryComparer.cs similarity index 66% rename from OsuPlayer.IO/DbReader/DataModels/Extensions/HistoricalMapEntryComparer.cs rename to OsuPlayer.Data/DataModels/Extensions/HistoricalMapEntryComparer.cs index 0764ecee..3d84cb7f 100644 --- a/OsuPlayer.IO/DbReader/DataModels/Extensions/HistoricalMapEntryComparer.cs +++ b/OsuPlayer.Data/DataModels/Extensions/HistoricalMapEntryComparer.cs @@ -1,12 +1,12 @@ -namespace OsuPlayer.IO.DbReader.DataModels.Extensions; +namespace OsuPlayer.Data.DataModels.Extensions; public class HistoricalMapEntryComparer : IEqualityComparer { public bool Equals(HistoricalMapEntry? x, HistoricalMapEntry? y) { - if(x == null && y == null) return true; - if(x == null || y == null) return false; - + if (x == null && y == null) return true; + if (x == null || y == null) return false; + return x.MapEntry.Hash == y.MapEntry.Hash; } diff --git a/OsuPlayer.Data/DataModels/IDbReaderFactory.cs b/OsuPlayer.Data/DataModels/IDbReaderFactory.cs new file mode 100644 index 00000000..834eee00 --- /dev/null +++ b/OsuPlayer.Data/DataModels/IDbReaderFactory.cs @@ -0,0 +1,11 @@ +using OsuPlayer.Data.DataModels.Interfaces; +using OsuPlayer.Data.Enums; + +namespace OsuPlayer.Data.DataModels; + +public interface IDbReaderFactory +{ + public DbCreationType Type { get; set; } + + public IDatabaseReader CreateDatabaseReader(string path); +} \ No newline at end of file diff --git a/OsuPlayer.IO/DbReader/Interfaces/IDatabaseReader.cs b/OsuPlayer.Data/DataModels/Interfaces/IDatabaseReader.cs similarity index 53% rename from OsuPlayer.IO/DbReader/Interfaces/IDatabaseReader.cs rename to OsuPlayer.Data/DataModels/Interfaces/IDatabaseReader.cs index 36d7bff4..dcc38cb8 100644 --- a/OsuPlayer.IO/DbReader/Interfaces/IDatabaseReader.cs +++ b/OsuPlayer.Data/DataModels/Interfaces/IDatabaseReader.cs @@ -1,4 +1,4 @@ -namespace OsuPlayer.IO.DbReader.Interfaces; +namespace OsuPlayer.Data.DataModels.Interfaces; /// /// Interface used by the database readers used to read the osu databases. @@ -22,6 +22,16 @@ public interface IDatabaseReader : IDisposable /// Reads the osu! collections from the database. /// /// the osu! path - /// a list of s - public Task?> GetCollections(string path); + /// a list of s + public Task?> GetCollections(string path); + + /// + /// Reads the full map entry from the existing + /// + /// the osu! path + /// the corresponding + /// the db offset (if applicable) + /// the database id (if applicable) + /// + public IMapEntry? ReadFullEntry(string path, IMapEntryBase mapEntryBase, long? dbOffset = null, Guid? id = null); } \ No newline at end of file diff --git a/OsuPlayer.IO/DbReader/Interfaces/IMapEntry.cs b/OsuPlayer.Data/DataModels/Interfaces/IMapEntry.cs similarity index 91% rename from OsuPlayer.IO/DbReader/Interfaces/IMapEntry.cs rename to OsuPlayer.Data/DataModels/Interfaces/IMapEntry.cs index 8709fef9..b615a6ec 100644 --- a/OsuPlayer.IO/DbReader/Interfaces/IMapEntry.cs +++ b/OsuPlayer.Data/DataModels/Interfaces/IMapEntry.cs @@ -1,4 +1,4 @@ -namespace OsuPlayer.IO.DbReader.Interfaces; +namespace OsuPlayer.Data.DataModels.Interfaces; public interface IMapEntry : IMapEntryBase { diff --git a/OsuPlayer.IO/DbReader/Interfaces/IMapEntryBase.cs b/OsuPlayer.Data/DataModels/Interfaces/IMapEntryBase.cs similarity index 90% rename from OsuPlayer.IO/DbReader/Interfaces/IMapEntryBase.cs rename to OsuPlayer.Data/DataModels/Interfaces/IMapEntryBase.cs index 9a7ed504..28d23163 100644 --- a/OsuPlayer.IO/DbReader/Interfaces/IMapEntryBase.cs +++ b/OsuPlayer.Data/DataModels/Interfaces/IMapEntryBase.cs @@ -1,9 +1,11 @@ using Nein.Extensions; -namespace OsuPlayer.IO.DbReader.Interfaces; +namespace OsuPlayer.Data.DataModels.Interfaces; public interface IMapEntryBase : IEquatable, IComparable { + public IDbReaderFactory DbReaderFactory { get; init; } + public string Artist { get; } public string Title { get; } public string Hash { get; } @@ -34,7 +36,7 @@ public string GetSongName() /// a full for extended usage. Returns null if the path doesn't exist or the map was not /// found. /// - public Task ReadFullEntry(); + public IMapEntry? ReadFullEntry(); /// /// Gets the corresponding of the beatmap diff --git a/OsuPlayer.Data/DataModels/Interfaces/IUser.cs b/OsuPlayer.Data/DataModels/Interfaces/IUser.cs new file mode 100644 index 00000000..1ad1e83a --- /dev/null +++ b/OsuPlayer.Data/DataModels/Interfaces/IUser.cs @@ -0,0 +1,23 @@ +using Avalonia.Media; + +namespace OsuPlayer.Data.DataModels.Interfaces; + +public interface IUser +{ + public Guid UniqueId { get; } + + public string SongsPlayedString { get; } + public string LevelAndTotalXpString { get; } + public string LevelProgressString { get; } + public Brush RoleColor { get; } + public string RoleString { get; } + public string DescriptionTitleString { get; } + public string LevelString { get; } + public string JoinDateString { get; } + public string TotalXpString { get; } + + public int GetXpNeededForNextLevel(); + public static abstract int GetXpNeededForNextLevel(int level); + public Brush GetRoleColorBrush(); + public string GetRoleString(); +} \ No newline at end of file diff --git a/OsuPlayer/Modules/Services/ObservableSorter.cs b/OsuPlayer.Data/DataModels/ObservableSorter.cs similarity index 87% rename from OsuPlayer/Modules/Services/ObservableSorter.cs rename to OsuPlayer.Data/DataModels/ObservableSorter.cs index 809a7d93..74bf2720 100644 --- a/OsuPlayer/Modules/Services/ObservableSorter.cs +++ b/OsuPlayer.Data/DataModels/ObservableSorter.cs @@ -1,17 +1,12 @@ -using OsuPlayer.IO.DbReader.Interfaces; +using OsuPlayer.Data.DataModels.Interfaces; -namespace OsuPlayer.Modules.Services; +namespace OsuPlayer.Data.DataModels; public class ObservableSorter : IObservable> { - private readonly List>> _observers; + private readonly List>> _observers = new(); private IComparer? _lastComparer; - public ObservableSorter() - { - _observers = new List>>(); - } - public IDisposable Subscribe(IObserver> observer) { if (!_observers.Contains(observer)) diff --git a/OsuPlayer.Network/Online/Article.cs b/OsuPlayer.Data/DataModels/Online/Article.cs similarity index 89% rename from OsuPlayer.Network/Online/Article.cs rename to OsuPlayer.Data/DataModels/Online/Article.cs index ddd5718e..8bcfa1b2 100644 --- a/OsuPlayer.Network/Online/Article.cs +++ b/OsuPlayer.Data/DataModels/Online/Article.cs @@ -1,6 +1,6 @@ using System.Globalization; -namespace OsuPlayer.Network.Online; +namespace OsuPlayer.Data.DataModels.Online; public sealed class Article { diff --git a/OsuPlayer.Network/Online/News.cs b/OsuPlayer.Data/DataModels/Online/News.cs similarity index 89% rename from OsuPlayer.Network/Online/News.cs rename to OsuPlayer.Data/DataModels/Online/News.cs index aa410675..e88a492b 100644 --- a/OsuPlayer.Network/Online/News.cs +++ b/OsuPlayer.Data/DataModels/Online/News.cs @@ -1,6 +1,6 @@ using System.Globalization; -namespace OsuPlayer.Network.Online; +namespace OsuPlayer.Data.DataModels.Online; public sealed class News { diff --git a/OsuPlayer.Network/Online/OnlineUserStatusModelExtended.cs b/OsuPlayer.Data/DataModels/Online/OnlineUserStatusModelExtended.cs similarity index 92% rename from OsuPlayer.Network/Online/OnlineUserStatusModelExtended.cs rename to OsuPlayer.Data/DataModels/Online/OnlineUserStatusModelExtended.cs index faf7eb3d..267ca252 100644 --- a/OsuPlayer.Network/Online/OnlineUserStatusModelExtended.cs +++ b/OsuPlayer.Data/DataModels/Online/OnlineUserStatusModelExtended.cs @@ -1,7 +1,7 @@ using OsuPlayer.Api.Data.API.EntityModels; using OsuPlayer.Api.Data.API.Enums; -namespace OsuPlayer.Network.Online; +namespace OsuPlayer.Data.DataModels.Online; public sealed class OnlineUserStatusModelExtended : UserOnlineStatusModel { diff --git a/OsuPlayer.Network/Online/UserColors.cs b/OsuPlayer.Data/DataModels/Online/UserColors.cs similarity index 92% rename from OsuPlayer.Network/Online/UserColors.cs rename to OsuPlayer.Data/DataModels/Online/UserColors.cs index 74689dda..e9c1f087 100644 --- a/OsuPlayer.Network/Online/UserColors.cs +++ b/OsuPlayer.Data/DataModels/Online/UserColors.cs @@ -1,6 +1,6 @@ using Avalonia.Media; -namespace OsuPlayer.Network.Online; +namespace OsuPlayer.Data.DataModels.Online; /// /// A list of all available role colors diff --git a/OsuPlayer.IO/DbReader/Collection.cs b/OsuPlayer.Data/DataModels/OsuCollection.cs similarity index 63% rename from OsuPlayer.IO/DbReader/Collection.cs rename to OsuPlayer.Data/DataModels/OsuCollection.cs index 12a02fa8..debef5ba 100644 --- a/OsuPlayer.IO/DbReader/Collection.cs +++ b/OsuPlayer.Data/DataModels/OsuCollection.cs @@ -1,18 +1,18 @@ -namespace OsuPlayer.IO.DbReader; +namespace OsuPlayer.Data.DataModels; /// /// Represents a collection from osu! /// -public class Collection +public class OsuCollection { public string Name { get; set; } = string.Empty; public List BeatmapHashes { get; private set; } = new(); - public Collection() + public OsuCollection() { } - public Collection(string name, List beatmapHashes) + public OsuCollection(string name, List beatmapHashes) { Name = name; BeatmapHashes = beatmapHashes; diff --git a/OsuPlayer.IO/DbReader/DataModels/RealmMapEntry.cs b/OsuPlayer.Data/DataModels/RealmMapEntry.cs similarity index 88% rename from OsuPlayer.IO/DbReader/DataModels/RealmMapEntry.cs rename to OsuPlayer.Data/DataModels/RealmMapEntry.cs index 9be8cdd7..56d7c140 100644 --- a/OsuPlayer.IO/DbReader/DataModels/RealmMapEntry.cs +++ b/OsuPlayer.Data/DataModels/RealmMapEntry.cs @@ -1,12 +1,12 @@ -using OsuPlayer.IO.DbReader.Interfaces; +using OsuPlayer.Data.DataModels.Interfaces; -namespace OsuPlayer.IO.DbReader.DataModels; +namespace OsuPlayer.Data.DataModels; /// /// a full beatmap entry with optionally used data /// only created on a call /// -internal class RealmMapEntry : RealmMapEntryBase, IMapEntry +public class RealmMapEntry : RealmMapEntryBase, IMapEntry { public string BackgroundFileLocation { get; init; } = string.Empty; public string ArtistUnicode { get; init; } = string.Empty; diff --git a/OsuPlayer.Data/DataModels/RealmMapEntryBase.cs b/OsuPlayer.Data/DataModels/RealmMapEntryBase.cs new file mode 100644 index 00000000..f94d03d5 --- /dev/null +++ b/OsuPlayer.Data/DataModels/RealmMapEntryBase.cs @@ -0,0 +1,98 @@ +using System.Text; +using Nein.Extensions; +using OsuPlayer.Data.DataModels.Interfaces; + +namespace OsuPlayer.Data.DataModels; + +/// +/// a minimal beatmap entry with only frequently used data +/// +public class RealmMapEntryBase : IMapEntryBase +{ + public required IDbReaderFactory DbReaderFactory { get; init; } + + public Guid Id { get; init; } + public string? OsuPath { get; init; } + public string Artist { get; init; } = string.Empty; + public string Title { get; init; } = string.Empty; + public string Hash { get; init; } = string.Empty; + public int BeatmapSetId { get; init; } + public int TotalTime { get; init; } + public string TotalTimeString => TimeSpan.FromMilliseconds(TotalTime).FormatTime(); + public string SongName => GetSongName(); + public string ArtistString => GetArtist(); + public string TitleString => GetTitle(); + + public virtual string GetArtist() + { + return Artist; + } + + public virtual string GetTitle() + { + return Title; + } + + public virtual string GetSongName() + { + return $"{GetArtist()} - {GetTitle()}"; + } + + public IMapEntry? ReadFullEntry() + { + if (OsuPath == null) return null; + + var reader = GetReader(); + + if (reader == default) + return null; + + return reader.ReadFullEntry(OsuPath, this, id: Id); + } + + public IDatabaseReader? GetReader() + { + if (OsuPath == null) + return null; + + return DbReaderFactory.CreateDatabaseReader(OsuPath); + } + + public bool Equals(IMapEntryBase? other) + { + return Hash == other?.Hash; + } + + public int CompareTo(IMapEntryBase? other) + { + return string.Compare(Hash, other?.Hash, StringComparison.OrdinalIgnoreCase); + } + + public override string ToString() + { + return GetSongName(); + } + + public static bool operator ==(RealmMapEntryBase? left, IMapEntryBase? right) + { + return left?.Hash == right?.Hash; + } + + public static bool operator !=(RealmMapEntryBase? left, IMapEntryBase? right) + { + return left?.Hash != right?.Hash; + } + + public override bool Equals(object? other) + { + if (other is IMapEntryBase map) + return Hash == map.Hash; + + return false; + } + + public override int GetHashCode() + { + return BitConverter.ToInt32(Encoding.UTF8.GetBytes(Hash)); + } +} \ No newline at end of file diff --git a/OsuPlayer.Network/Online/User.cs b/OsuPlayer.Data/DataModels/User.cs similarity index 96% rename from OsuPlayer.Network/Online/User.cs rename to OsuPlayer.Data/DataModels/User.cs index 1b193967..d7d7cd4e 100644 --- a/OsuPlayer.Network/Online/User.cs +++ b/OsuPlayer.Data/DataModels/User.cs @@ -2,13 +2,15 @@ using Avalonia.Media; using OsuPlayer.Api.Data.API.EntityModels; using OsuPlayer.Api.Data.API.Enums; +using OsuPlayer.Data.DataModels.Interfaces; +using OsuPlayer.Data.DataModels.Online; -namespace OsuPlayer.Network.Online; +namespace OsuPlayer.Data.DataModels; /// /// Represents a osu!player user /// -public sealed class User : UserModel +public sealed class User : UserModel, IUser { public string SongsPlayedString { diff --git a/OsuPlayer.Data/Enums/DbCreationType.cs b/OsuPlayer.Data/Enums/DbCreationType.cs new file mode 100644 index 00000000..f2444b36 --- /dev/null +++ b/OsuPlayer.Data/Enums/DbCreationType.cs @@ -0,0 +1,7 @@ +namespace OsuPlayer.Data.Enums; + +public enum DbCreationType +{ + OsuDb, + Realm +} \ No newline at end of file diff --git a/OsuPlayer.Data/Enums/LogType.cs b/OsuPlayer.Data/Enums/LogType.cs new file mode 100644 index 00000000..73eb14a3 --- /dev/null +++ b/OsuPlayer.Data/Enums/LogType.cs @@ -0,0 +1,10 @@ +namespace OsuPlayer.Services; + +public enum LogType +{ + Info, + Success, + Warning, + Error, + Debug +} \ No newline at end of file diff --git a/OsuPlayer.Data/LazerModels/Beatmaps/BeatmapDifficulty.cs b/OsuPlayer.Data/LazerModels/Beatmaps/BeatmapDifficulty.cs index aff4b87c..a305c376 100644 --- a/OsuPlayer.Data/LazerModels/Beatmaps/BeatmapDifficulty.cs +++ b/OsuPlayer.Data/LazerModels/Beatmaps/BeatmapDifficulty.cs @@ -1,6 +1,6 @@ using Realms; -namespace OsuPlayer.IO.Storage.LazerModels.Beatmaps; +namespace OsuPlayer.Data.LazerModels.Beatmaps; // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. diff --git a/OsuPlayer.Data/LazerModels/Beatmaps/BeatmapInfo.cs b/OsuPlayer.Data/LazerModels/Beatmaps/BeatmapInfo.cs index a2e9348f..4f705651 100644 --- a/OsuPlayer.Data/LazerModels/Beatmaps/BeatmapInfo.cs +++ b/OsuPlayer.Data/LazerModels/Beatmaps/BeatmapInfo.cs @@ -1,10 +1,10 @@ using JetBrains.Annotations; using Newtonsoft.Json; -using OsuPlayer.IO.Storage.LazerModels.Extensions; -using OsuPlayer.IO.Storage.LazerModels.Files; +using OsuPlayer.Data.LazerModels.Extensions; +using OsuPlayer.Data.LazerModels.Files; using Realms; -namespace OsuPlayer.IO.Storage.LazerModels.Beatmaps; +namespace OsuPlayer.Data.LazerModels.Beatmaps; // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. @@ -20,7 +20,8 @@ public class BeatmapInfo : RealmObject, IHasGuidPrimaryKey, IBeatmapInfo, IEquat public BeatmapSetInfo? BeatmapSet { get; set; } - [Ignored] public RealmNamedFileUsage? File => BeatmapSet?.Files.FirstOrDefault(f => f.File.Hash == Hash); + [Ignored] + public RealmNamedFileUsage? File => BeatmapSet?.Files.FirstOrDefault(f => f.File.Hash == Hash); [Ignored] public BeatmapOnlineStatus Status @@ -29,13 +30,16 @@ public BeatmapOnlineStatus Status set => StatusInt = (int) value; } - [MapTo(nameof(Status))] public int StatusInt { get; set; } = (int) BeatmapOnlineStatus.None; + [MapTo(nameof(Status))] + public int StatusInt { get; set; } = (int) BeatmapOnlineStatus.None; - [JsonIgnore] public bool Hidden { get; set; } + [JsonIgnore] + public bool Hidden { get; set; } public string DifficultyName { get; set; } = string.Empty; - [Indexed] public int OnlineID { get; set; } = -1; + [Indexed] + public int OnlineID { get; set; } = -1; public double Length { get; set; } @@ -52,7 +56,8 @@ public BeatmapOnlineStatus Status IRulesetInfo IBeatmapInfo.Ruleset => Ruleset; IBeatmapDifficultyInfo IBeatmapInfo.Difficulty => Difficulty; - [PrimaryKey] public Guid ID { get; set; } + [PrimaryKey] + public Guid ID { get; set; } public BeatmapInfo(RulesetInfo? ruleset = null, BeatmapDifficulty? difficulty = null, BeatmapMetadata? metadata = null) { diff --git a/OsuPlayer.Data/LazerModels/Beatmaps/BeatmapMetadata.cs b/OsuPlayer.Data/LazerModels/Beatmaps/BeatmapMetadata.cs index 0a4e898d..b38ea30b 100644 --- a/OsuPlayer.Data/LazerModels/Beatmaps/BeatmapMetadata.cs +++ b/OsuPlayer.Data/LazerModels/Beatmaps/BeatmapMetadata.cs @@ -1,10 +1,10 @@ using JetBrains.Annotations; using Newtonsoft.Json; -using OsuPlayer.IO.Storage.LazerModels.Extensions; -using OsuPlayer.IO.Storage.LazerModels.Interfaces; +using OsuPlayer.Data.LazerModels.Extensions; +using OsuPlayer.Data.LazerModels.Interfaces; using Realms; -namespace OsuPlayer.IO.Storage.LazerModels.Beatmaps; +namespace OsuPlayer.Data.LazerModels.Beatmaps; // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. @@ -16,15 +16,18 @@ public class BeatmapMetadata : RealmObject, IBeatmapMetadataInfo public string Title { get; set; } = string.Empty; - [JsonProperty("title_unicode")] public string TitleUnicode { get; set; } = string.Empty; + [JsonProperty("title_unicode")] + public string TitleUnicode { get; set; } = string.Empty; public string Artist { get; set; } = string.Empty; - [JsonProperty("artist_unicode")] public string ArtistUnicode { get; set; } = string.Empty; + [JsonProperty("artist_unicode")] + public string ArtistUnicode { get; set; } = string.Empty; public string Source { get; set; } = string.Empty; - [JsonProperty(@"tags")] public string Tags { get; set; } = string.Empty; + [JsonProperty(@"tags")] + public string Tags { get; set; } = string.Empty; /// /// The time in milliseconds to begin playing the track for preview purposes. diff --git a/OsuPlayer.Data/LazerModels/Beatmaps/BeatmapOnlineStatus.cs b/OsuPlayer.Data/LazerModels/Beatmaps/BeatmapOnlineStatus.cs index 9ba1f7da..2350b7ab 100644 --- a/OsuPlayer.Data/LazerModels/Beatmaps/BeatmapOnlineStatus.cs +++ b/OsuPlayer.Data/LazerModels/Beatmaps/BeatmapOnlineStatus.cs @@ -1,4 +1,4 @@ -namespace OsuPlayer.IO.Storage.LazerModels.Beatmaps; +namespace OsuPlayer.Data.LazerModels.Beatmaps; // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. diff --git a/OsuPlayer.Data/LazerModels/Beatmaps/BeatmapSetInfo.cs b/OsuPlayer.Data/LazerModels/Beatmaps/BeatmapSetInfo.cs index fb135c92..2f97239c 100644 --- a/OsuPlayer.Data/LazerModels/Beatmaps/BeatmapSetInfo.cs +++ b/OsuPlayer.Data/LazerModels/Beatmaps/BeatmapSetInfo.cs @@ -1,17 +1,18 @@ using JetBrains.Annotations; using Newtonsoft.Json; -using OsuPlayer.IO.Storage.LazerModels.Extensions; -using OsuPlayer.IO.Storage.LazerModels.Files; +using OsuPlayer.Data.LazerModels.Extensions; +using OsuPlayer.Data.LazerModels.Files; using Realms; -namespace OsuPlayer.IO.Storage.LazerModels.Beatmaps; +namespace OsuPlayer.Data.LazerModels.Beatmaps; // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. [MapTo("BeatmapSet")] public class BeatmapSetInfo : RealmObject, IHasRealmFiles, IEquatable, IBeatmapSetInfo { - [PrimaryKey] public Guid ID { get; set; } + [PrimaryKey] + public Guid ID { get; set; } public IList Beatmaps { get; } = null!; @@ -22,7 +23,8 @@ public BeatmapOnlineStatus Status set => StatusInt = (int) value; } - [MapTo(nameof(Status))] public int StatusInt { get; set; } = (int) BeatmapOnlineStatus.None; + [MapTo(nameof(Status))] + public int StatusInt { get; set; } = (int) BeatmapOnlineStatus.None; public bool DeletePending { get; set; } @@ -31,11 +33,13 @@ public BeatmapOnlineStatus Status /// public bool Protected { get; set; } - [Indexed] public int OnlineID { get; set; } = -1; + [Indexed] + public int OnlineID { get; set; } = -1; public DateTimeOffset DateAdded { get; set; } - [JsonIgnore] public IBeatmapMetadataInfo Metadata => Beatmaps.FirstOrDefault()?.Metadata ?? new BeatmapMetadata(); + [JsonIgnore] + public IBeatmapMetadataInfo Metadata => Beatmaps.FirstOrDefault()?.Metadata ?? new BeatmapMetadata(); public double MaxStarDifficulty => Beatmaps.Count == 0 ? 0 : Beatmaps.Max(b => b.StarRating); diff --git a/OsuPlayer.Data/LazerModels/Beatmaps/IBeatmapDifficultyInfo.cs b/OsuPlayer.Data/LazerModels/Beatmaps/IBeatmapDifficultyInfo.cs index 0dad82e7..bcd01e28 100644 --- a/OsuPlayer.Data/LazerModels/Beatmaps/IBeatmapDifficultyInfo.cs +++ b/OsuPlayer.Data/LazerModels/Beatmaps/IBeatmapDifficultyInfo.cs @@ -1,4 +1,4 @@ -namespace OsuPlayer.IO.Storage.LazerModels.Beatmaps; +namespace OsuPlayer.Data.LazerModels.Beatmaps; // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. diff --git a/OsuPlayer.Data/LazerModels/Beatmaps/IBeatmapInfo.cs b/OsuPlayer.Data/LazerModels/Beatmaps/IBeatmapInfo.cs index bbcc17d7..15910207 100644 --- a/OsuPlayer.Data/LazerModels/Beatmaps/IBeatmapInfo.cs +++ b/OsuPlayer.Data/LazerModels/Beatmaps/IBeatmapInfo.cs @@ -1,6 +1,6 @@ -using OsuPlayer.IO.Storage.LazerModels.Interfaces; +using OsuPlayer.Data.LazerModels.Interfaces; -namespace OsuPlayer.IO.Storage.LazerModels.Beatmaps; +namespace OsuPlayer.Data.LazerModels.Beatmaps; // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. diff --git a/OsuPlayer.Data/LazerModels/Beatmaps/IBeatmapMetadataInfo.cs b/OsuPlayer.Data/LazerModels/Beatmaps/IBeatmapMetadataInfo.cs index a3e10206..42c8dbbc 100644 --- a/OsuPlayer.Data/LazerModels/Beatmaps/IBeatmapMetadataInfo.cs +++ b/OsuPlayer.Data/LazerModels/Beatmaps/IBeatmapMetadataInfo.cs @@ -1,6 +1,6 @@ -using OsuPlayer.IO.Storage.LazerModels.Interfaces; +using OsuPlayer.Data.LazerModels.Interfaces; -namespace OsuPlayer.IO.Storage.LazerModels.Beatmaps; +namespace OsuPlayer.Data.LazerModels.Beatmaps; // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. diff --git a/OsuPlayer.Data/LazerModels/Beatmaps/IBeatmapSetInfo.cs b/OsuPlayer.Data/LazerModels/Beatmaps/IBeatmapSetInfo.cs index 6e689949..e6479166 100644 --- a/OsuPlayer.Data/LazerModels/Beatmaps/IBeatmapSetInfo.cs +++ b/OsuPlayer.Data/LazerModels/Beatmaps/IBeatmapSetInfo.cs @@ -1,7 +1,7 @@ -using OsuPlayer.IO.Storage.LazerModels.Files; -using OsuPlayer.IO.Storage.LazerModels.Interfaces; +using OsuPlayer.Data.LazerModels.Files; +using OsuPlayer.Data.LazerModels.Interfaces; -namespace OsuPlayer.IO.Storage.LazerModels.Beatmaps; +namespace OsuPlayer.Data.LazerModels.Beatmaps; // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. diff --git a/OsuPlayer.Data/LazerModels/Beatmaps/IRulesetInfo.cs b/OsuPlayer.Data/LazerModels/Beatmaps/IRulesetInfo.cs index 7cfc5acb..48ac09d4 100644 --- a/OsuPlayer.Data/LazerModels/Beatmaps/IRulesetInfo.cs +++ b/OsuPlayer.Data/LazerModels/Beatmaps/IRulesetInfo.cs @@ -1,6 +1,6 @@ -using OsuPlayer.IO.Storage.LazerModels.Interfaces; +using OsuPlayer.Data.LazerModels.Interfaces; -namespace OsuPlayer.IO.Storage.LazerModels.Beatmaps; +namespace OsuPlayer.Data.LazerModels.Beatmaps; // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. diff --git a/OsuPlayer.Data/LazerModels/Beatmaps/IScoreInfo.cs b/OsuPlayer.Data/LazerModels/Beatmaps/IScoreInfo.cs index c4def931..4a76c82b 100644 --- a/OsuPlayer.Data/LazerModels/Beatmaps/IScoreInfo.cs +++ b/OsuPlayer.Data/LazerModels/Beatmaps/IScoreInfo.cs @@ -1,7 +1,7 @@ -using OsuPlayer.IO.Storage.LazerModels.Files; -using OsuPlayer.IO.Storage.LazerModels.Interfaces; +using OsuPlayer.Data.LazerModels.Files; +using OsuPlayer.Data.LazerModels.Interfaces; -namespace OsuPlayer.IO.Storage.LazerModels.Beatmaps; +namespace OsuPlayer.Data.LazerModels.Beatmaps; // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. diff --git a/OsuPlayer.Data/LazerModels/Beatmaps/RulesetInfo.cs b/OsuPlayer.Data/LazerModels/Beatmaps/RulesetInfo.cs index 1b54193d..812c3b68 100644 --- a/OsuPlayer.Data/LazerModels/Beatmaps/RulesetInfo.cs +++ b/OsuPlayer.Data/LazerModels/Beatmaps/RulesetInfo.cs @@ -1,7 +1,7 @@ using JetBrains.Annotations; using Realms; -namespace OsuPlayer.IO.Storage.LazerModels.Beatmaps; +namespace OsuPlayer.Data.LazerModels.Beatmaps; // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. @@ -10,9 +10,11 @@ public class RulesetInfo : RealmObject, IEquatable, IComparable. Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. public enum ScoreRank { - [Description(@"D")] D, + [Description(@"D")] + D, - [Description(@"C")] C, + [Description(@"C")] + C, - [Description(@"B")] B, + [Description(@"B")] + B, - [Description(@"A")] A, + [Description(@"A")] + A, - [Description(@"S")] S, + [Description(@"S")] + S, - [Description(@"S+")] SH, + [Description(@"S+")] + SH, - [Description(@"SS")] X, + [Description(@"SS")] + X, - [Description(@"SS+")] XH + [Description(@"SS+")] + XH } \ No newline at end of file diff --git a/OsuPlayer.Data/LazerModels/Collections/BeatmapCollection.cs b/OsuPlayer.Data/LazerModels/Collections/BeatmapCollection.cs index 04f0c89e..19f1c18e 100644 --- a/OsuPlayer.Data/LazerModels/Collections/BeatmapCollection.cs +++ b/OsuPlayer.Data/LazerModels/Collections/BeatmapCollection.cs @@ -2,11 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using JetBrains.Annotations; -using OsuPlayer.IO.Storage.LazerModels.Beatmaps; -using OsuPlayer.IO.Storage.LazerModels.Files; +using OsuPlayer.Data.LazerModels.Beatmaps; +using OsuPlayer.Data.LazerModels.Files; using Realms; -namespace OsuPlayer.IO.Storage.LazerModels.Collections; +namespace OsuPlayer.Data.LazerModels.Collections; /// /// A collection of beatmaps grouped by a name. @@ -34,7 +34,8 @@ public class BeatmapCollection : RealmObject, IHasGuidPrimaryKey /// public DateTimeOffset LastModified { get; set; } - [PrimaryKey] public Guid ID { get; set; } + [PrimaryKey] + public Guid ID { get; set; } public BeatmapCollection(string? name = null, IList? beatmapMD5Hashes = null) { diff --git a/OsuPlayer.Data/LazerModels/Extensions/BeatmapInfoExtionsions.cs b/OsuPlayer.Data/LazerModels/Extensions/BeatmapInfoExtionsions.cs index eacb24a3..49b3f223 100644 --- a/OsuPlayer.Data/LazerModels/Extensions/BeatmapInfoExtionsions.cs +++ b/OsuPlayer.Data/LazerModels/Extensions/BeatmapInfoExtionsions.cs @@ -1,6 +1,6 @@ -using OsuPlayer.IO.Storage.LazerModels.Beatmaps; +using OsuPlayer.Data.LazerModels.Beatmaps; -namespace OsuPlayer.IO.Storage.LazerModels.Extensions; +namespace OsuPlayer.Data.LazerModels.Extensions; // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. diff --git a/OsuPlayer.Data/LazerModels/Extensions/BeatmapMetadataInfoExtensions.cs b/OsuPlayer.Data/LazerModels/Extensions/BeatmapMetadataInfoExtensions.cs index 3f32270c..157db15b 100644 --- a/OsuPlayer.Data/LazerModels/Extensions/BeatmapMetadataInfoExtensions.cs +++ b/OsuPlayer.Data/LazerModels/Extensions/BeatmapMetadataInfoExtensions.cs @@ -1,6 +1,6 @@ -using OsuPlayer.IO.Storage.LazerModels.Beatmaps; +using OsuPlayer.Data.LazerModels.Beatmaps; -namespace OsuPlayer.IO.Storage.LazerModels.Extensions; +namespace OsuPlayer.Data.LazerModels.Extensions; // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. diff --git a/OsuPlayer.Data/LazerModels/Extensions/CollectionExtensions.cs b/OsuPlayer.Data/LazerModels/Extensions/CollectionExtensions.cs index f577b0b5..4da7a82a 100644 --- a/OsuPlayer.Data/LazerModels/Extensions/CollectionExtensions.cs +++ b/OsuPlayer.Data/LazerModels/Extensions/CollectionExtensions.cs @@ -1,4 +1,4 @@ -namespace OsuPlayer.IO.Storage.LazerModels.Extensions; +namespace OsuPlayer.Data.LazerModels.Extensions; // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. diff --git a/OsuPlayer.Data/LazerModels/Extensions/ModelExtension.cs b/OsuPlayer.Data/LazerModels/Extensions/ModelExtension.cs index 91cf7258..d5d5bddc 100644 --- a/OsuPlayer.Data/LazerModels/Extensions/ModelExtension.cs +++ b/OsuPlayer.Data/LazerModels/Extensions/ModelExtension.cs @@ -1,8 +1,8 @@ -using OsuPlayer.IO.Storage.LazerModels.Beatmaps; -using OsuPlayer.IO.Storage.LazerModels.Files; -using OsuPlayer.IO.Storage.LazerModels.Interfaces; +using OsuPlayer.Data.LazerModels.Beatmaps; +using OsuPlayer.Data.LazerModels.Files; +using OsuPlayer.Data.LazerModels.Interfaces; -namespace OsuPlayer.IO.Storage.LazerModels.Extensions; +namespace OsuPlayer.Data.LazerModels.Extensions; // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. diff --git a/OsuPlayer.Data/LazerModels/Extensions/ScoreInfoExtensions.cs b/OsuPlayer.Data/LazerModels/Extensions/ScoreInfoExtensions.cs index 823e5169..44d3cdef 100644 --- a/OsuPlayer.Data/LazerModels/Extensions/ScoreInfoExtensions.cs +++ b/OsuPlayer.Data/LazerModels/Extensions/ScoreInfoExtensions.cs @@ -1,6 +1,6 @@ -using OsuPlayer.IO.Storage.LazerModels.Beatmaps; +using OsuPlayer.Data.LazerModels.Beatmaps; -namespace OsuPlayer.IO.Storage.LazerModels.Extensions; +namespace OsuPlayer.Data.LazerModels.Extensions; // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. diff --git a/OsuPlayer.Data/LazerModels/Files/IFileInfo.cs b/OsuPlayer.Data/LazerModels/Files/IFileInfo.cs index ded44752..6b3f227e 100644 --- a/OsuPlayer.Data/LazerModels/Files/IFileInfo.cs +++ b/OsuPlayer.Data/LazerModels/Files/IFileInfo.cs @@ -1,4 +1,4 @@ -namespace OsuPlayer.IO.Storage.LazerModels.Files; +namespace OsuPlayer.Data.LazerModels.Files; // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. diff --git a/OsuPlayer.Data/LazerModels/Files/IHasGuidPrimaryKey.cs b/OsuPlayer.Data/LazerModels/Files/IHasGuidPrimaryKey.cs index 5748e545..848042c1 100644 --- a/OsuPlayer.Data/LazerModels/Files/IHasGuidPrimaryKey.cs +++ b/OsuPlayer.Data/LazerModels/Files/IHasGuidPrimaryKey.cs @@ -1,11 +1,13 @@ using Newtonsoft.Json; using Realms; -namespace OsuPlayer.IO.Storage.LazerModels.Files; +namespace OsuPlayer.Data.LazerModels.Files; // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. public interface IHasGuidPrimaryKey { - [JsonIgnore] [PrimaryKey] Guid ID { get; } + [JsonIgnore] + [PrimaryKey] + Guid ID { get; } } \ No newline at end of file diff --git a/OsuPlayer.Data/LazerModels/Files/IHasNamedFiles.cs b/OsuPlayer.Data/LazerModels/Files/IHasNamedFiles.cs index c3fab856..dadaee0c 100644 --- a/OsuPlayer.Data/LazerModels/Files/IHasNamedFiles.cs +++ b/OsuPlayer.Data/LazerModels/Files/IHasNamedFiles.cs @@ -1,4 +1,4 @@ -namespace OsuPlayer.IO.Storage.LazerModels.Files; +namespace OsuPlayer.Data.LazerModels.Files; // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. diff --git a/OsuPlayer.Data/LazerModels/Files/IHasRealmFiles.cs b/OsuPlayer.Data/LazerModels/Files/IHasRealmFiles.cs index dac595ea..2f7ce483 100644 --- a/OsuPlayer.Data/LazerModels/Files/IHasRealmFiles.cs +++ b/OsuPlayer.Data/LazerModels/Files/IHasRealmFiles.cs @@ -1,4 +1,4 @@ -namespace OsuPlayer.IO.Storage.LazerModels.Files; +namespace OsuPlayer.Data.LazerModels.Files; // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. diff --git a/OsuPlayer.Data/LazerModels/Files/INamedFile.cs b/OsuPlayer.Data/LazerModels/Files/INamedFile.cs index 3d464506..729ef0ee 100644 --- a/OsuPlayer.Data/LazerModels/Files/INamedFile.cs +++ b/OsuPlayer.Data/LazerModels/Files/INamedFile.cs @@ -1,4 +1,4 @@ -namespace OsuPlayer.IO.Storage.LazerModels.Files; +namespace OsuPlayer.Data.LazerModels.Files; // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. diff --git a/OsuPlayer.Data/LazerModels/Files/INamedFileUsage.cs b/OsuPlayer.Data/LazerModels/Files/INamedFileUsage.cs index 236fc22e..846b7b2b 100644 --- a/OsuPlayer.Data/LazerModels/Files/INamedFileUsage.cs +++ b/OsuPlayer.Data/LazerModels/Files/INamedFileUsage.cs @@ -1,4 +1,4 @@ -namespace OsuPlayer.IO.Storage.LazerModels.Files; +namespace OsuPlayer.Data.LazerModels.Files; // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. diff --git a/OsuPlayer.Data/LazerModels/Files/RealmFile.cs b/OsuPlayer.Data/LazerModels/Files/RealmFile.cs index 40b99a4d..c1250d77 100644 --- a/OsuPlayer.Data/LazerModels/Files/RealmFile.cs +++ b/OsuPlayer.Data/LazerModels/Files/RealmFile.cs @@ -1,11 +1,12 @@ using Realms; -namespace OsuPlayer.IO.Storage.LazerModels.Files; +namespace OsuPlayer.Data.LazerModels.Files; // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. [MapTo("File")] public class RealmFile : RealmObject, IFileInfo { - [PrimaryKey] public string Hash { get; set; } = string.Empty; + [PrimaryKey] + public string Hash { get; set; } = string.Empty; } \ No newline at end of file diff --git a/OsuPlayer.Data/LazerModels/Files/RealmNamedFileUsage.cs b/OsuPlayer.Data/LazerModels/Files/RealmNamedFileUsage.cs index 6ef57a2b..2cb9459d 100644 --- a/OsuPlayer.Data/LazerModels/Files/RealmNamedFileUsage.cs +++ b/OsuPlayer.Data/LazerModels/Files/RealmNamedFileUsage.cs @@ -1,7 +1,7 @@ using JetBrains.Annotations; using Realms; -namespace OsuPlayer.IO.Storage.LazerModels.Files; +namespace OsuPlayer.Data.LazerModels.Files; // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. diff --git a/OsuPlayer.Data/LazerModels/Interfaces/IHasOnlineID.cs b/OsuPlayer.Data/LazerModels/Interfaces/IHasOnlineID.cs index 5cbcb700..460ab0c8 100644 --- a/OsuPlayer.Data/LazerModels/Interfaces/IHasOnlineID.cs +++ b/OsuPlayer.Data/LazerModels/Interfaces/IHasOnlineID.cs @@ -1,6 +1,6 @@ // ReSharper disable InconsistentNaming -namespace OsuPlayer.IO.Storage.LazerModels.Interfaces; +namespace OsuPlayer.Data.LazerModels.Interfaces; // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. diff --git a/OsuPlayer.Data/LazerModels/Interfaces/IMapperBase.cs b/OsuPlayer.Data/LazerModels/Interfaces/IMapperBase.cs index c8b5a93e..16371a60 100644 --- a/OsuPlayer.Data/LazerModels/Interfaces/IMapperBase.cs +++ b/OsuPlayer.Data/LazerModels/Interfaces/IMapperBase.cs @@ -1,4 +1,4 @@ -namespace OsuPlayer.IO.Storage.LazerModels.Interfaces; +namespace OsuPlayer.Data.LazerModels.Interfaces; public interface IMapperBase { diff --git a/OsuPlayer.Data/LazerModels/Interfaces/IMappingOperationOptions.cs b/OsuPlayer.Data/LazerModels/Interfaces/IMappingOperationOptions.cs index 42749b13..70b7ec47 100644 --- a/OsuPlayer.Data/LazerModels/Interfaces/IMappingOperationOptions.cs +++ b/OsuPlayer.Data/LazerModels/Interfaces/IMappingOperationOptions.cs @@ -1,4 +1,4 @@ -namespace OsuPlayer.IO.Storage.LazerModels.Interfaces; +namespace OsuPlayer.Data.LazerModels.Interfaces; // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. diff --git a/OsuPlayer.Data/LazerModels/Interfaces/IUser.cs b/OsuPlayer.Data/LazerModels/Interfaces/IUser.cs index 5802fda4..807e4835 100644 --- a/OsuPlayer.Data/LazerModels/Interfaces/IUser.cs +++ b/OsuPlayer.Data/LazerModels/Interfaces/IUser.cs @@ -1,4 +1,4 @@ -namespace OsuPlayer.IO.Storage.LazerModels.Interfaces; +namespace OsuPlayer.Data.LazerModels.Interfaces; // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. diff --git a/OsuPlayer.Data/LazerModels/RealmUser.cs b/OsuPlayer.Data/LazerModels/RealmUser.cs index 6625f0c1..cd4cfea9 100644 --- a/OsuPlayer.Data/LazerModels/RealmUser.cs +++ b/OsuPlayer.Data/LazerModels/RealmUser.cs @@ -1,7 +1,7 @@ -using OsuPlayer.IO.Storage.LazerModels.Interfaces; +using OsuPlayer.Data.LazerModels.Interfaces; using Realms; -namespace OsuPlayer.IO.Storage.LazerModels; +namespace OsuPlayer.Data.LazerModels; // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. diff --git a/OsuPlayer.Data/OsuPlayer.Data.csproj b/OsuPlayer.Data/OsuPlayer.Data.csproj index deb23eb1..2fa92e5f 100644 --- a/OsuPlayer.Data/OsuPlayer.Data.csproj +++ b/OsuPlayer.Data/OsuPlayer.Data.csproj @@ -9,9 +9,11 @@ + + diff --git a/OsuPlayer.IO/DbReader/DataModels/DbMapEntryBase.cs b/OsuPlayer.IO/DbReader/DataModels/DbMapEntryBase.cs deleted file mode 100644 index 3c82f353..00000000 --- a/OsuPlayer.IO/DbReader/DataModels/DbMapEntryBase.cs +++ /dev/null @@ -1,234 +0,0 @@ -using System.Text; -using Nein.Extensions; -using OsuPlayer.IO.DbReader.Interfaces; - -namespace OsuPlayer.IO.DbReader.DataModels; - -/// -/// a minimal beatmap entry with only frequently used data -/// created on call of -/// -public class DbMapEntryBase : IMapEntryBase -{ - public long DbOffset { get; init; } - public string? OsuPath { get; init; } - public string Artist { get; init; } = string.Empty; - public string Title { get; init; } = string.Empty; - public string Hash { get; init; } = string.Empty; - public int BeatmapSetId { get; init; } - public int TotalTime { get; init; } - public string TotalTimeString => TimeSpan.FromMilliseconds(TotalTime).FormatTime(); - public string SongName => GetSongName(); - public string ArtistString => GetArtist(); - public string TitleString => GetTitle(); - - /// - /// Gets the artist - /// may be overridden for usage with - /// - /// the artist - public virtual string GetArtist() - { - return Artist; - } - - /// - /// Gets the title - /// may be overridden for usage with - /// - /// the title - public virtual string GetTitle() - { - return Title; - } - - public string GetSongName() - { - return $"{GetArtist()} - {GetTitle()}"; - } - - /// - /// Reads a osu!.db map entry and fills a full with data - /// - /// a new generated from osu!.db data - public async Task ReadFullEntry() - { - if (OsuPath == null) return null; - - var version = OsuDbReader.OsuDbVersion; - - var dbLoc = Path.Combine(OsuPath, "osu!.db"); - - if (!File.Exists(dbLoc)) return null; - - await using var file = File.OpenRead(dbLoc); - - using var r = new OsuDbReader(file); - - r.BaseStream.Seek(DbOffset, SeekOrigin.Begin); - - r.ReadString(true); //Artist - - var artistUnicode = "Unknown Artist"; - if (version >= 20121008) - artistUnicode = r.ReadString(); - - r.ReadString(true); //Title - - var titleUnicode = "Unknown Title"; - if (version >= 20121008) - titleUnicode = r.ReadString(); - - r.ReadString(true); //Creator - r.ReadString(true); //Difficulty - - var audioFileName = r.ReadString(); - r.ReadString(true); //Hash - - r.ReadString(true); //BeatmapFileName - r.ReadByte(); //RankedStatus - r.ReadUInt16(); //CountHitCircles - r.ReadUInt16(); //CountSliders - r.ReadUInt16(); //CountSpinners - r.ReadDateTime(); //LastModifiedTime - - if (version >= 20140609) - { - r.ReadSingle(); //ApproachRate - r.ReadSingle(); //CircleSize - r.ReadSingle(); //HPDrainRate - r.ReadSingle(); //OverallDifficulty - } - else - { - //Float - r.ReadByte(); //ApproachRate - r.ReadByte(); //CircleSize - r.ReadByte(); //HPDrainRate - r.ReadByte(); //OverallDifficulty - } - - r.ReadDouble(); //SliderVelocity - - if (version >= 20140609) - { - r.ReadStarRating(); - r.ReadStarRating(); - r.ReadStarRating(); - r.ReadStarRating(); - } - - r.ReadInt32(); //DrainTimeSeconds - r.ReadInt32(); //TotalTimeSeconds - - r.ReadInt32(); //AudioPreviewTime - var timingCount = r.ReadInt32(); - - r.BaseStream.Position += 17 * timingCount; - - r.ReadInt32(); - r.ReadInt32(); //beatmapSetId - - r.ReadInt32(); //ThreadId - r.ReadByte(); //GradeStandard - r.ReadByte(); //GradeTaiko - r.ReadByte(); //GradeCtB - r.ReadByte(); //GradeMania - r.ReadInt16(); //OffsetLocal - r.ReadSingle(); //StackLeniency - r.ReadByte(); //GameMode - r.ReadString(true); //SongSource - r.ReadString(true); //SongTags - r.ReadInt16(); //OffsetOnline - r.ReadString(true); //TitleFont - r.ReadBoolean(); //Unplayed - r.ReadDateTime(); //LastPlayed - r.ReadBoolean(); //IsOsz2 - - var folderName = r.ReadString(); - - r.ReadDateTime(); //LastCheckAgainstOsuRepo - r.ReadBoolean(); //IgnoreBeatmapSounds - r.ReadBoolean(); //IgnoreBeatmapSkin - r.ReadBoolean(); //DisableStoryBoard - r.ReadBoolean(); //DisableVideo - r.ReadBoolean(); // - - if (version < 20140609) - r.ReadInt16(); //OldUnknown1 - - r.ReadInt32(); //LastEditTime - r.ReadByte(); //ManiaScrollSpeed - - var fullPath = Path.Combine(OsuPath, "Songs", folderName, audioFileName); - var folderPath = Path.Combine(OsuPath, "Songs", folderName); - - return new DbMapEntry - { - DbOffset = DbOffset, - OsuPath = OsuPath, - Artist = Artist, - ArtistUnicode = artistUnicode, - Title = Title, - TitleUnicode = titleUnicode, - AudioFileName = audioFileName, - BeatmapSetId = BeatmapSetId, - FolderName = folderName, - FolderPath = folderPath, - FullPath = fullPath, - Hash = Hash, - TotalTime = TotalTime - }; - } - - public IDatabaseReader? GetReader() - { - if (OsuPath == null) return null; - - var dbLoc = Path.Combine(OsuPath, "osu!.db"); - - if (!File.Exists(dbLoc)) return null; - - var file = File.OpenRead(dbLoc); - - return new OsuDbReader(file, OsuPath); - } - - public bool Equals(IMapEntryBase? other) - { - return Hash == other?.Hash; - } - - public int CompareTo(IMapEntryBase? other) - { - return string.Compare(Hash, other?.Hash, StringComparison.OrdinalIgnoreCase); - } - - public override string ToString() - { - return GetSongName(); - } - - public static bool operator ==(DbMapEntryBase? left, IMapEntryBase? right) - { - return left?.Hash == right?.Hash; - } - - public static bool operator !=(DbMapEntryBase? left, IMapEntryBase? right) - { - return left?.Hash != right?.Hash; - } - - public override bool Equals(object? other) - { - if (other is IMapEntryBase map) - return Hash == map.Hash; - - return false; - } - - public override int GetHashCode() - { - return BitConverter.ToInt32(Encoding.UTF8.GetBytes(Hash)); - } -} \ No newline at end of file diff --git a/OsuPlayer.IO/DbReader/DataModels/RealmMapEntryBase.cs b/OsuPlayer.IO/DbReader/DataModels/RealmMapEntryBase.cs deleted file mode 100644 index b63b5191..00000000 --- a/OsuPlayer.IO/DbReader/DataModels/RealmMapEntryBase.cs +++ /dev/null @@ -1,148 +0,0 @@ -using System.Text; -using Nein.Extensions; -using OsuPlayer.IO.DbReader.Interfaces; -using OsuPlayer.IO.Storage.LazerModels.Beatmaps; -using OsuPlayer.IO.Storage.LazerModels.Files; -using Realms; -using Realms.Dynamic; - -namespace OsuPlayer.IO.DbReader.DataModels; - -/// -/// a minimal beatmap entry with only frequently used data -/// created on call of -/// -public class RealmMapEntryBase : IMapEntryBase -{ - public Guid Id { get; init; } - public string? OsuPath { get; init; } - public string Artist { get; init; } = string.Empty; - public string Title { get; init; } = string.Empty; - public string Hash { get; init; } = string.Empty; - public int BeatmapSetId { get; init; } - public int TotalTime { get; init; } - public string TotalTimeString => TimeSpan.FromMilliseconds(TotalTime).FormatTime(); - public string SongName => GetSongName(); - public string ArtistString => GetArtist(); - public string TitleString => GetTitle(); - - public virtual string GetArtist() - { - return Artist; - } - - public virtual string GetTitle() - { - return Title; - } - - public virtual string GetSongName() - { - return $"{GetArtist()} - {GetTitle()}"; - } - - public async Task ReadFullEntry() - { - if (OsuPath == null) return null; - - var realmLoc = Path.Combine(OsuPath, "client.realm"); - - var realmConfig = new RealmConfiguration(realmLoc) - { - IsDynamic = true, - IsReadOnly = true - }; - - var realm = await Realm.GetInstanceAsync(realmConfig); - var beatmap = (DynamicRealmObject) realm.DynamicApi.Find("BeatmapSet", Id); - - if (beatmap == default) return null; - - var beatmaps = beatmap.DynamicApi.GetList(nameof(BeatmapSetInfo.Beatmaps)); - var metadata = beatmaps.First().DynamicApi.Get(nameof(BeatmapInfo.Metadata)).DynamicApi; - - var files = (RealmList) beatmap.DynamicApi.GetList(nameof(BeatmapSetInfo.Files)); - - var audioFileName = metadata.Get(nameof(BeatmapMetadata.AudioFile)); - var backgroundFileName = metadata.Get(nameof(BeatmapMetadata.BackgroundFile)); - - var audioFile = (IRealmObjectBase) files.FirstOrDefault(x => - string.Equals(x.DynamicApi.Get(nameof(RealmNamedFileUsage.Filename)), audioFileName, StringComparison.CurrentCultureIgnoreCase)); - var backgroundFile = (IRealmObjectBase) files.FirstOrDefault(x => - string.Equals(x.DynamicApi.Get(nameof(RealmNamedFileUsage.Filename)), backgroundFileName, StringComparison.CurrentCultureIgnoreCase)); - - if (audioFile == null) return null; - - var audioHash = audioFile.DynamicApi.Get(nameof(RealmNamedFileUsage.File)).DynamicApi.Get(nameof(RealmFile.Hash)); - var backgroundHash = backgroundFile?.DynamicApi.Get(nameof(RealmNamedFileUsage.File)).DynamicApi.Get(nameof(RealmFile.Hash)); - - var audioFolderName = Path.Combine($"{audioHash[0]}", $"{audioHash[0]}{audioHash[1]}"); - var backgroundFolderName = Path.Combine($"{backgroundHash?[0]}", $"{backgroundHash?[0]}{backgroundHash?[1]}"); - - var newMap = new RealmMapEntry - { - Id = Id, - OsuPath = string.Intern(OsuPath), - Artist = string.Intern(Artist), - ArtistUnicode = metadata.Get(nameof(BeatmapMetadata.ArtistUnicode)), - Title = Title, - TitleUnicode = metadata.Get(nameof(BeatmapMetadata.TitleUnicode)), - AudioFileName = audioFileName, - BackgroundFileLocation = string.IsNullOrEmpty(backgroundFolderName) - ? string.Empty - : Path.Combine(OsuPath, "files", backgroundFolderName, backgroundHash!), - Hash = Hash, - BeatmapSetId = BeatmapSetId, - FolderName = audioFolderName, - FolderPath = Path.Combine("files", audioFolderName), - FullPath = Path.Combine(OsuPath, "files", audioFolderName, audioHash) - }; - - realm.Dispose(); - - return newMap; - } - - public IDatabaseReader? GetReader() - { - return OsuPath != null ? new RealmReader(OsuPath) : null; - } - - public bool Equals(IMapEntryBase? other) - { - return Hash == other?.Hash; - } - - public int CompareTo(IMapEntryBase? other) - { - return string.Compare(Hash, other?.Hash, StringComparison.OrdinalIgnoreCase); - } - - public override string ToString() - { - return GetSongName(); - } - - public static bool operator ==(RealmMapEntryBase? left, IMapEntryBase? right) - { - return left?.Hash == right?.Hash; - } - - public static bool operator !=(RealmMapEntryBase? left, IMapEntryBase? right) - { - return left?.Hash != right?.Hash; - } - - public override bool Equals(object? other) - { - if (other is IMapEntryBase map) - return Hash == map.Hash; - - return false; - } - - public override int GetHashCode() - { - return BitConverter.ToInt32(Encoding.UTF8.GetBytes(Hash)); - } -} \ No newline at end of file diff --git a/OsuPlayer.IO/DbReader/OsuCollectionReader.cs b/OsuPlayer.IO/DbReader/OsuCollectionReader.cs index 4ebdd00a..e80489ce 100644 --- a/OsuPlayer.IO/DbReader/OsuCollectionReader.cs +++ b/OsuPlayer.IO/DbReader/OsuCollectionReader.cs @@ -1,4 +1,5 @@ using System.Text; +using OsuPlayer.Data.DataModels; namespace OsuPlayer.IO.DbReader; @@ -14,10 +15,10 @@ public OsuCollectionReader(Stream input) : base(input) /// Reads the collection from the collection.db /// /// the osu full path - /// a list - public static async Task?> Read(string osuPath) + /// a list + public static async Task?> Read(string osuPath) { - var collections = new List(); + var collections = new List(); var colLoc = Path.Combine(osuPath, "collection.db"); if (!File.Exists(colLoc)) return null; @@ -37,9 +38,9 @@ public OsuCollectionReader(Stream input) : base(input) return collections; } - private static Collection ReadFromStream(OsuCollectionReader r) + private static OsuCollection ReadFromStream(OsuCollectionReader r) { - var collection = new Collection + var collection = new OsuCollection { Name = r.ReadString() }; diff --git a/OsuPlayer.IO/DbReader/OsuDbReader.cs b/OsuPlayer.IO/DbReader/OsuDbReader.cs index 6d40df55..14ac1cc2 100644 --- a/OsuPlayer.IO/DbReader/OsuDbReader.cs +++ b/OsuPlayer.IO/DbReader/OsuDbReader.cs @@ -1,6 +1,7 @@ using System.Text; -using OsuPlayer.IO.DbReader.DataModels; -using OsuPlayer.IO.DbReader.Interfaces; +using OsuPlayer.Data.DataModels; +using OsuPlayer.Data.DataModels.Interfaces; +using Splat; namespace OsuPlayer.IO.DbReader; @@ -10,15 +11,14 @@ namespace OsuPlayer.IO.DbReader; public class OsuDbReader : BinaryReader, IDatabaseReader { private readonly byte[] _buf = new byte[512]; - private string _path = string.Empty; - public static int OsuDbVersion { get; private set; } + private readonly string _path; + private static int _osuDbVersion; + private readonly IDbReaderFactory _readerFactory; - public OsuDbReader(Stream input) : base(input) + public OsuDbReader(Stream input, string path, IDbReaderFactory readerFactory) : base(input) { - } + _readerFactory = readerFactory; - public OsuDbReader(Stream input, string path) : base(input) - { _path = string.Intern(path); } @@ -27,7 +27,7 @@ public OsuDbReader(Stream input, string path) : base(input) var minBeatMaps = new List(); var ver = ReadInt32(); - OsuDbVersion = ver; + _osuDbVersion = ver; var flag = ver is >= 20160408 and < 20191107; ReadInt32(); @@ -76,7 +76,7 @@ public Dictionary GetBeatmapHashes() var hashes = new Dictionary(); var ver = ReadInt32(); - OsuDbVersion = ver; + _osuDbVersion = ver; var flag = ver is >= 20160408 and < 20191107; ReadInt32(); @@ -104,22 +104,133 @@ public Dictionary GetBeatmapHashes() return hashes; } - public async Task?> GetCollections(string path) + public async Task?> GetCollections(string path) { return await OsuCollectionReader.Read(path); } - public static async Task?> Read(string path) + public IMapEntry? ReadFullEntry(string path, IMapEntryBase mapEntryBase, long? dbOffset = null, Guid? id = null) { - var dbLoc = Path.Combine(path, "osu!.db"); + if (dbOffset == null) + return null; + + var version = _osuDbVersion; + + BaseStream.Seek(dbOffset.Value, SeekOrigin.Begin); + + ReadString(true); //Artist + + var artistUnicode = "Unknown Artist"; + if (version >= 20121008) + artistUnicode = ReadString(); + + ReadString(true); //Title + + var titleUnicode = "Unknown Title"; + if (version >= 20121008) + titleUnicode = ReadString(); + + ReadString(true); //Creator + ReadString(true); //Difficulty + + var audioFileName = ReadString(); + ReadString(true); //Hash + + ReadString(true); //BeatmapFileName + ReadByte(); //RankedStatus + ReadUInt16(); //CountHitCircles + ReadUInt16(); //CountSliders + ReadUInt16(); //CountSpinners + ReadDateTime(); //LastModifiedTime + + if (version >= 20140609) + { + ReadSingle(); //ApproachRate + ReadSingle(); //CircleSize + ReadSingle(); //HPDrainRate + ReadSingle(); //OverallDifficulty + } + else + { + //Float + ReadByte(); //ApproachRate + ReadByte(); //CircleSize + ReadByte(); //HPDrainRate + ReadByte(); //OverallDifficulty + } + + ReadDouble(); //SliderVelocity + + if (version >= 20140609) + { + ReadStarRating(); + ReadStarRating(); + ReadStarRating(); + ReadStarRating(); + } + + ReadInt32(); //DrainTimeSeconds + ReadInt32(); //TotalTimeSeconds + + ReadInt32(); //AudioPreviewTime + var timingCount = ReadInt32(); + + BaseStream.Position += 17 * timingCount; + + ReadInt32(); + ReadInt32(); //beatmapSetId + + ReadInt32(); //ThreadId + ReadByte(); //GradeStandard + ReadByte(); //GradeTaiko + ReadByte(); //GradeCtB + ReadByte(); //GradeMania + ReadInt16(); //OffsetLocal + ReadSingle(); //StackLeniency + ReadByte(); //GameMode + ReadString(true); //SongSource + ReadString(true); //SongTags + ReadInt16(); //OffsetOnline + ReadString(true); //TitleFont + ReadBoolean(); //Unplayed + ReadDateTime(); //LastPlayed + ReadBoolean(); //IsOsz2 + + var folderName = ReadString(); + + ReadDateTime(); //LastCheckAgainstOsuRepo + ReadBoolean(); //IgnoreBeatmapSounds + ReadBoolean(); //IgnoreBeatmapSkin + ReadBoolean(); //DisableStoryBoard + ReadBoolean(); //DisableVideo + ReadBoolean(); // - if (!File.Exists(dbLoc)) return null; + if (version < 20140609) + ReadInt16(); //OldUnknown1 - var file = File.OpenRead(dbLoc); + ReadInt32(); //LastEditTime + ReadByte(); //ManiaScrollSpeed - var reader = new OsuDbReader(file, path); + var fullPath = Path.Combine(path, "Songs", folderName, audioFileName); + var folderPath = Path.Combine(path, "Songs", folderName); - return await reader.ReadBeatmaps(); + return new DbMapEntry + { + DbReaderFactory = _readerFactory, + DbOffset = dbOffset.Value, + OsuPath = path, + Artist = mapEntryBase.Artist, + ArtistUnicode = artistUnicode, + Title = mapEntryBase.Title, + TitleUnicode = titleUnicode, + AudioFileName = audioFileName, + BeatmapSetId = mapEntryBase.BeatmapSetId, + FolderName = folderName, + FolderPath = folderPath, + FullPath = fullPath, + Hash = mapEntryBase.Hash, + TotalTime = mapEntryBase.TotalTime + }; } /// @@ -134,7 +245,7 @@ private void ReadFromStream(out DbMapEntryBase minBeatmap) if (artist.Length == 0) artist = "Unknown Artist"; - if (OsuDbVersion >= 20121008) + if (_osuDbVersion >= 20121008) ReadString(true); var title = string.Intern(ReadString()); @@ -142,7 +253,7 @@ private void ReadFromStream(out DbMapEntryBase minBeatmap) if (title.Length == 0) title = "Unknown Title"; - if (OsuDbVersion >= 20121008) + if (_osuDbVersion >= 20121008) ReadString(true); ReadString(true); @@ -153,9 +264,9 @@ private void ReadFromStream(out DbMapEntryBase minBeatmap) ReadString(true); //BeatmapFileName - BaseStream.Seek(OsuDbVersion >= 20140609 ? 39 : 27, SeekOrigin.Current); + BaseStream.Seek(_osuDbVersion >= 20140609 ? 39 : 27, SeekOrigin.Current); - if (OsuDbVersion >= 20140609) + if (_osuDbVersion >= 20140609) { ReadStarRating(); ReadStarRating(); @@ -187,10 +298,11 @@ private void ReadFromStream(out DbMapEntryBase minBeatmap) ReadBoolean(); //IsOsz2 ReadString(true); - BaseStream.Seek(OsuDbVersion < 20140609 ? 20 : 18, SeekOrigin.Current); + BaseStream.Seek(_osuDbVersion < 20140609 ? 20 : 18, SeekOrigin.Current); minBeatmap = new DbMapEntryBase { + DbReaderFactory = _readerFactory, OsuPath = string.Intern(_path), Artist = artist, Title = title, @@ -212,10 +324,10 @@ private long CalculateMapLength(out int setId, out string hash) var initOffset = BaseStream.Position; ReadString(true); - if (OsuDbVersion >= 20121008) ReadString(true); + if (_osuDbVersion >= 20121008) ReadString(true); ReadString(true); - if (OsuDbVersion >= 20121008) ReadString(true); + if (_osuDbVersion >= 20121008) ReadString(true); ReadString(true); ReadString(true); @@ -225,10 +337,10 @@ private long CalculateMapLength(out int setId, out string hash) ReadString(true); BaseStream.Seek(15, SeekOrigin.Current); - BaseStream.Seek(OsuDbVersion >= 20140609 ? 16 : 4, SeekOrigin.Current); + BaseStream.Seek(_osuDbVersion >= 20140609 ? 16 : 4, SeekOrigin.Current); BaseStream.Seek(8, SeekOrigin.Current); - if (OsuDbVersion >= 20140609) + if (_osuDbVersion >= 20140609) { ReadStarRating(); ReadStarRating(); @@ -257,7 +369,7 @@ private long CalculateMapLength(out int setId, out string hash) ReadString(true); - BaseStream.Seek(OsuDbVersion < 20140609 ? 20 : 18, SeekOrigin.Current); + BaseStream.Seek(_osuDbVersion < 20140609 ? 20 : 18, SeekOrigin.Current); return BaseStream.Position - initOffset; } diff --git a/OsuPlayer.IO/DbReader/RealmReader.cs b/OsuPlayer.IO/DbReader/RealmReader.cs index 8c7ce07a..22899aaf 100644 --- a/OsuPlayer.IO/DbReader/RealmReader.cs +++ b/OsuPlayer.IO/DbReader/RealmReader.cs @@ -1,9 +1,11 @@ -using OsuPlayer.IO.DbReader.DataModels; -using OsuPlayer.IO.DbReader.Interfaces; -using OsuPlayer.IO.Storage.LazerModels.Beatmaps; -using OsuPlayer.IO.Storage.LazerModels.Collections; +using OsuPlayer.Data.DataModels; +using OsuPlayer.Data.DataModels.Interfaces; +using OsuPlayer.Data.LazerModels.Beatmaps; +using OsuPlayer.Data.LazerModels.Collections; +using OsuPlayer.Data.LazerModels.Files; using Realms; using Realms.Dynamic; +using Splat; namespace OsuPlayer.IO.DbReader; @@ -14,12 +16,16 @@ public class RealmReader : IDatabaseReader { private readonly string _path; private readonly Realm _realm; + private readonly IDbReaderFactory _readerFactory; - public RealmReader(string path) + public RealmReader(string path, IDbReaderFactory readerFactory) { + _readerFactory = readerFactory; + _path = string.Intern(path); var realmLoc = Path.Combine(_path, "client.realm"); + var realmConfig = new RealmConfiguration(realmLoc) { IsDynamic = true, @@ -44,20 +50,20 @@ public Dictionary GetBeatmapHashes() return hashes; } - public Task> GetCollections(string path) + public Task> GetCollections(string path) { if (File.Exists(Path.Combine(path, "collection.db"))) return OsuCollectionReader.Read(path); var dynamicRealmObjects = _realm.DynamicApi.All(nameof(BeatmapCollection)).ToList().OfType().ToList(); - var collections = new List(); + var collections = new List(); foreach (var realmObject in dynamicRealmObjects) { var name = realmObject.DynamicApi.Get(nameof(BeatmapCollection.Name)); var hashes = realmObject.DynamicApi.GetList(nameof(BeatmapCollection.BeatmapMD5Hashes)); - var col = new Collection(name, hashes.ToList()); + var col = new OsuCollection(name, hashes.ToList()); collections.Add(col); } @@ -65,6 +71,63 @@ public Task> GetCollections(string path) return Task.FromResult(collections); } + public IMapEntry? ReadFullEntry(string path, IMapEntryBase mapEntryBase, long? dbOffset = null, Guid? id = null) + { + if (string.IsNullOrWhiteSpace(path) || id == null) + return null; + + var beatmap = (DynamicRealmObject) _realm.DynamicApi.Find("BeatmapSet", id); + + if (beatmap == default) + return null; + + var beatmaps = beatmap.DynamicApi.GetList(nameof(BeatmapSetInfo.Beatmaps)); + var metadata = beatmaps.First().DynamicApi.Get(nameof(BeatmapInfo.Metadata)).DynamicApi; + + var files = (RealmList) beatmap.DynamicApi.GetList(nameof(BeatmapSetInfo.Files)); + + var audioFileName = metadata.Get(nameof(BeatmapMetadata.AudioFile)); + var backgroundFileName = metadata.Get(nameof(BeatmapMetadata.BackgroundFile)); + + var audioFile = (IRealmObjectBase) files.FirstOrDefault(x => + string.Equals(x.DynamicApi.Get(nameof(RealmNamedFileUsage.Filename)), audioFileName, StringComparison.CurrentCultureIgnoreCase)); + var backgroundFile = (IRealmObjectBase) files.FirstOrDefault(x => + string.Equals(x.DynamicApi.Get(nameof(RealmNamedFileUsage.Filename)), backgroundFileName, StringComparison.CurrentCultureIgnoreCase)); + + if (audioFile == null) + return null; + + var audioHash = audioFile.DynamicApi.Get(nameof(RealmNamedFileUsage.File)).DynamicApi.Get(nameof(RealmFile.Hash)); + var backgroundHash = backgroundFile?.DynamicApi.Get(nameof(RealmNamedFileUsage.File)).DynamicApi.Get(nameof(RealmFile.Hash)); + + var audioFolderName = Path.Combine($"{audioHash[0]}", $"{audioHash[0]}{audioHash[1]}"); + var backgroundFolderName = Path.Combine($"{backgroundHash?[0]}", $"{backgroundHash?[0]}{backgroundHash?[1]}"); + + var newMap = new RealmMapEntry + { + DbReaderFactory = _readerFactory, + Id = id.Value, + OsuPath = string.Intern(path), + Artist = string.Intern(mapEntryBase.Artist), + ArtistUnicode = metadata.Get(nameof(BeatmapMetadata.ArtistUnicode)), + Title = mapEntryBase.Title, + TitleUnicode = metadata.Get(nameof(BeatmapMetadata.TitleUnicode)), + AudioFileName = audioFileName, + BackgroundFileLocation = string.IsNullOrEmpty(backgroundFolderName) + ? string.Empty + : Path.Combine(path, "files", backgroundFolderName, backgroundHash!), + Hash = mapEntryBase.Hash, + BeatmapSetId = mapEntryBase.BeatmapSetId, + FolderName = audioFolderName, + FolderPath = Path.Combine("files", audioFolderName), + FullPath = Path.Combine(path, "files", audioFolderName, audioHash) + }; + + _realm.Dispose(); + + return newMap; + } + public Task?> ReadBeatmaps() { var minBeatMaps = new List(); @@ -86,6 +149,7 @@ public Task> GetCollections(string path) minBeatMaps.Add(new RealmMapEntryBase { + DbReaderFactory = _readerFactory, OsuPath = string.Intern(_path), Artist = string.Intern(artist), Hash = hash, @@ -116,7 +180,7 @@ public void Dispose() /// an of read from the client.realm public static async Task?> Read(string path) { - using var reader = new RealmReader(path); + using var reader = new RealmReader(path, Locator.Current.GetService()); return await reader.ReadBeatmaps(); } diff --git a/OsuPlayer.IO/Importer/ISongSourceProvider.cs b/OsuPlayer.IO/Importer/ISongSourceProvider.cs index 6e531ebc..43aa9812 100644 --- a/OsuPlayer.IO/Importer/ISongSourceProvider.cs +++ b/OsuPlayer.IO/Importer/ISongSourceProvider.cs @@ -1,7 +1,6 @@ using System.Collections.ObjectModel; using DynamicData; -using OsuPlayer.IO.DbReader.DataModels; -using OsuPlayer.IO.DbReader.Interfaces; +using OsuPlayer.Data.DataModels.Interfaces; namespace OsuPlayer.IO.Importer; diff --git a/OsuPlayer.IO/Importer/SongImporter.cs b/OsuPlayer.IO/Importer/SongImporter.cs index 5acf558f..503138df 100644 --- a/OsuPlayer.IO/Importer/SongImporter.cs +++ b/OsuPlayer.IO/Importer/SongImporter.cs @@ -1,8 +1,12 @@ -using OsuPlayer.Data.OsuPlayer.Enums; +using OsuPlayer.Data.DataModels; +using OsuPlayer.Data.DataModels.Interfaces; +using OsuPlayer.Data.Enums; +using OsuPlayer.Data.OsuPlayer.Enums; +using OsuPlayer.Interfaces.Service; using OsuPlayer.IO.DbReader; -using OsuPlayer.IO.DbReader.DataModels; -using OsuPlayer.IO.DbReader.Interfaces; using OsuPlayer.IO.Storage.Config; +using OsuPlayer.Services; +using Splat; namespace OsuPlayer.IO.Importer; @@ -54,10 +58,19 @@ public static async Task ImportSongsAsync(ISongSourceProvider songSourceProvider IEnumerable? readMaps = null; + var dbReaderFactory = Locator.Current.GetService(); + var loggingService = Locator.Current.GetService(); + if (File.Exists(Path.Combine(path, "osu!.db"))) - readMaps = await OsuDbReader.Read(path); + dbReaderFactory.Type = DbCreationType.OsuDb; else if (File.Exists(Path.Combine(path, "client.realm"))) - readMaps = await RealmReader.Read(path); + dbReaderFactory.Type = DbCreationType.Realm; + + using var reader = dbReaderFactory.CreateDatabaseReader(path); + + loggingService.Log("Starting beatmap import..."); + + readMaps = await reader.ReadBeatmaps(); if (readMaps == null) return null; @@ -65,6 +78,8 @@ public static async Task ImportSongsAsync(ISongSourceProvider songSourceProvider //.DistinctBy(x => x.Title) .Where(x => !string.IsNullOrEmpty(x.Title)).ToArray(); + loggingService.Log($"Successfully imported {maps.Length} beatmaps.", LogType.Success); + return !maps.Any() ? null : maps; } } \ No newline at end of file diff --git a/OsuPlayer.IO/OsuPlayer.IO.csproj b/OsuPlayer.IO/OsuPlayer.IO.csproj index e9638183..d722bca2 100644 --- a/OsuPlayer.IO/OsuPlayer.IO.csproj +++ b/OsuPlayer.IO/OsuPlayer.IO.csproj @@ -9,7 +9,7 @@ - + @@ -36,4 +36,4 @@ - + \ No newline at end of file diff --git a/OsuPlayer.IO/Storage/Blacklist/Blacklist.cs b/OsuPlayer.IO/Storage/Blacklist/Blacklist.cs index 4af58d13..976cd42e 100644 --- a/OsuPlayer.IO/Storage/Blacklist/Blacklist.cs +++ b/OsuPlayer.IO/Storage/Blacklist/Blacklist.cs @@ -1,7 +1,6 @@ using Newtonsoft.Json; +using OsuPlayer.Data.DataModels.Interfaces; using OsuPlayer.Data.OsuPlayer.StorageModels; -using OsuPlayer.IO.DbReader.DataModels; -using OsuPlayer.IO.DbReader.Interfaces; namespace OsuPlayer.IO.Storage.Blacklist; diff --git a/OsuPlayer.IO/Storage/Playlists/PlaylistManager.cs b/OsuPlayer.IO/Storage/Playlists/PlaylistManager.cs index 953086d2..4377f772 100644 --- a/OsuPlayer.IO/Storage/Playlists/PlaylistManager.cs +++ b/OsuPlayer.IO/Storage/Playlists/PlaylistManager.cs @@ -1,6 +1,5 @@ -using OsuPlayer.Data.OsuPlayer.StorageModels; -using OsuPlayer.IO.DbReader.DataModels; -using OsuPlayer.IO.DbReader.Interfaces; +using OsuPlayer.Data.DataModels.Interfaces; +using OsuPlayer.Data.OsuPlayer.StorageModels; namespace OsuPlayer.IO.Storage.Playlists; diff --git a/OsuPlayer.Interfaces/OsuPlayer.Interfaces.csproj b/OsuPlayer.Interfaces/OsuPlayer.Interfaces.csproj new file mode 100644 index 00000000..b2d779b3 --- /dev/null +++ b/OsuPlayer.Interfaces/OsuPlayer.Interfaces.csproj @@ -0,0 +1,25 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + + + + + + + + + diff --git a/OsuPlayer.Interfaces/Service/Endpoint/IOsuPlayerApiUserEndpoint.cs b/OsuPlayer.Interfaces/Service/Endpoint/IOsuPlayerApiUserEndpoint.cs new file mode 100644 index 00000000..b78f8008 --- /dev/null +++ b/OsuPlayer.Interfaces/Service/Endpoint/IOsuPlayerApiUserEndpoint.cs @@ -0,0 +1,22 @@ +using System.Collections; +using Avalonia.Media.Imaging; +using OsuPlayer.Api.Data.API.EntityModels; +using OsuPlayer.Api.Data.API.RequestModels.User; + +namespace OsuPlayer.Interfaces.Service.Endpoint; + +public interface IOsuPlayerApiUserEndpoint +{ + Task UpdateSongsPlayed(int amount, int beatmapSetId = -1); + Task UpdateXp(UpdateXpModel updateXpModel); + Task SetOnlineStatus(UserOnlineStatusModel data); + Task GetProfilePictureAsync(Guid currentUserUniqueId); + Task GetUserFromLoginToken(); + Task EditUser(EditUserModel editUserModel); + Task SaveProfilePicture(byte[] data); + Task GetProfileBannerAsync(string? bannerUrl); + Task DeleteUser(); + Task?> GetAllUsers(); + Task?> GetActivityOfUser(Guid selectedUserUniqueId); + Task Register(AddUserModel addUserModel); +} \ No newline at end of file diff --git a/OsuPlayer/Modules/Services/IHistoryProvider.cs b/OsuPlayer.Interfaces/Service/IHistoryProvider.cs similarity index 65% rename from OsuPlayer/Modules/Services/IHistoryProvider.cs rename to OsuPlayer.Interfaces/Service/IHistoryProvider.cs index 8669353e..eee58386 100644 --- a/OsuPlayer/Modules/Services/IHistoryProvider.cs +++ b/OsuPlayer.Interfaces/Service/IHistoryProvider.cs @@ -1,8 +1,8 @@ -using System.Collections; -using OsuPlayer.IO.DbReader.DataModels.Extensions; -using OsuPlayer.IO.DbReader.Interfaces; +using Nein.Extensions.Bindables; +using OsuPlayer.Data.DataModels.Extensions; +using OsuPlayer.Data.DataModels.Interfaces; -namespace OsuPlayer.Modules.Audio.Interfaces; +namespace OsuPlayer.Interfaces.Service; /// /// This interface provides historic capabilities diff --git a/OsuPlayer.Interfaces/Service/ILastFmApiService.cs b/OsuPlayer.Interfaces/Service/ILastFmApiService.cs new file mode 100644 index 00000000..430b3abc --- /dev/null +++ b/OsuPlayer.Interfaces/Service/ILastFmApiService.cs @@ -0,0 +1,14 @@ +namespace OsuPlayer.Interfaces.Service; + +public interface ILastFmApiService +{ + public void SetApiKeyAndSecret(string apiKey, string secret); + public Task Scrobble(string title, string artist); + public bool LoadSessionKey(); + public Task LoadSessionKeyAsync(); + public bool IsAuthorized(); + public Task GetAuthToken(); + public void AuthorizeToken(); + public Task GetSessionKey(); + public Task SaveSessionKeyAsync(); +} \ No newline at end of file diff --git a/OsuPlayer.Interfaces/Service/ILoggingService.cs b/OsuPlayer.Interfaces/Service/ILoggingService.cs new file mode 100644 index 00000000..c743ff84 --- /dev/null +++ b/OsuPlayer.Interfaces/Service/ILoggingService.cs @@ -0,0 +1,8 @@ +using OsuPlayer.Services; + +namespace OsuPlayer.Interfaces.Service; + +public interface ILoggingService +{ + public void Log(string message, LogType logType = LogType.Info, object? data = null); +} \ No newline at end of file diff --git a/OsuPlayer.Interfaces/Service/IOsuPlayerApiService.cs b/OsuPlayer.Interfaces/Service/IOsuPlayerApiService.cs new file mode 100644 index 00000000..1fc7aaa6 --- /dev/null +++ b/OsuPlayer.Interfaces/Service/IOsuPlayerApiService.cs @@ -0,0 +1,12 @@ +using OsuPlayer.Api.Data.API.EntityModels; +using OsuPlayer.Interfaces.Service.Endpoint; + +namespace OsuPlayer.Interfaces.Service; + +public interface IOsuPlayerApiService +{ + public IOsuPlayerApiUserEndpoint User { get; set; } + + public Task LoginAndSaveAuthToken(string username, string password); + public Task LoginWithTokenAndSaveNewToken(string token); +} \ No newline at end of file diff --git a/OsuPlayer.Interfaces/Service/IOsuPlayerService.cs b/OsuPlayer.Interfaces/Service/IOsuPlayerService.cs new file mode 100644 index 00000000..92b0433d --- /dev/null +++ b/OsuPlayer.Interfaces/Service/IOsuPlayerService.cs @@ -0,0 +1,7 @@ +namespace OsuPlayer.Interfaces.Service; + +public interface IOsuPlayerService +{ + protected abstract string ServiceName { get; } + protected abstract string ServiceTag(); +} \ No newline at end of file diff --git a/OsuPlayer.Interfaces/Service/IProfileManagerService.cs b/OsuPlayer.Interfaces/Service/IProfileManagerService.cs new file mode 100644 index 00000000..f93baa70 --- /dev/null +++ b/OsuPlayer.Interfaces/Service/IProfileManagerService.cs @@ -0,0 +1,11 @@ +using OsuPlayer.Data.DataModels; + +namespace OsuPlayer.Interfaces.Service; + +public interface IProfileManagerService +{ + public User? User { get; set; } + + public Task Login(string username, string password); + public Task Login(string token); +} \ No newline at end of file diff --git a/OsuPlayer/Modules/ShuffleImpl/IShuffleImpl.cs b/OsuPlayer.Interfaces/Service/IShuffleImpl.cs similarity index 96% rename from OsuPlayer/Modules/ShuffleImpl/IShuffleImpl.cs rename to OsuPlayer.Interfaces/Service/IShuffleImpl.cs index 7078d486..8067aec0 100644 --- a/OsuPlayer/Modules/ShuffleImpl/IShuffleImpl.cs +++ b/OsuPlayer.Interfaces/Service/IShuffleImpl.cs @@ -1,6 +1,6 @@ using OsuPlayer.Data.OsuPlayer.Enums; -namespace OsuPlayer.Modules.ShuffleImpl; +namespace OsuPlayer.Interfaces.Service; /// /// This interface is used as a base for all shuffle algorithm implementations. diff --git a/OsuPlayer/Modules/Services/IShuffleServiceProvider.cs b/OsuPlayer.Interfaces/Service/IShuffleServiceProvider.cs similarity index 85% rename from OsuPlayer/Modules/Services/IShuffleServiceProvider.cs rename to OsuPlayer.Interfaces/Service/IShuffleServiceProvider.cs index fe444010..68270a55 100644 --- a/OsuPlayer/Modules/Services/IShuffleServiceProvider.cs +++ b/OsuPlayer.Interfaces/Service/IShuffleServiceProvider.cs @@ -1,6 +1,4 @@ -using OsuPlayer.Modules.ShuffleImpl; - -namespace OsuPlayer.Modules.Services; +namespace OsuPlayer.Interfaces.Service; /// /// This interface provides a service provider for shuffle implementations. diff --git a/OsuPlayer/Modules/Services/ISortProvider.cs b/OsuPlayer.Interfaces/Service/ISortProvider.cs similarity index 71% rename from OsuPlayer/Modules/Services/ISortProvider.cs rename to OsuPlayer.Interfaces/Service/ISortProvider.cs index 84dff434..78ad9d33 100644 --- a/OsuPlayer/Modules/Services/ISortProvider.cs +++ b/OsuPlayer.Interfaces/Service/ISortProvider.cs @@ -1,8 +1,10 @@ using DynamicData; +using Nein.Extensions.Bindables; +using OsuPlayer.Data.DataModels; +using OsuPlayer.Data.DataModels.Interfaces; using OsuPlayer.Data.OsuPlayer.Enums; -using OsuPlayer.IO.DbReader.Interfaces; -namespace OsuPlayer.Modules.Services; +namespace OsuPlayer.Interfaces.Service; public interface ISortProvider { diff --git a/OsuPlayer/Modules/Services/IStatisticsProvider.cs b/OsuPlayer.Interfaces/Service/IStatisticsProvider.cs similarity index 95% rename from OsuPlayer/Modules/Services/IStatisticsProvider.cs rename to OsuPlayer.Interfaces/Service/IStatisticsProvider.cs index 55580ca2..5373d607 100644 --- a/OsuPlayer/Modules/Services/IStatisticsProvider.cs +++ b/OsuPlayer.Interfaces/Service/IStatisticsProvider.cs @@ -1,9 +1,9 @@ using System.ComponentModel; -using System.Threading.Tasks; using LiveChartsCore.Defaults; +using Nein.Extensions.Bindables; using OsuPlayer.Api.Data.API.Enums; -namespace OsuPlayer.Modules.Services; +namespace OsuPlayer.Interfaces.Service; public interface IStatisticsProvider { diff --git a/OsuPlayer.Network/API/Service/AbstractApiBase.cs b/OsuPlayer.Network/API/AbstractApiBase.cs similarity index 82% rename from OsuPlayer.Network/API/Service/AbstractApiBase.cs rename to OsuPlayer.Network/API/AbstractApiBase.cs index 81c450c9..0de2993b 100644 --- a/OsuPlayer.Network/API/Service/AbstractApiBase.cs +++ b/OsuPlayer.Network/API/AbstractApiBase.cs @@ -3,22 +3,37 @@ using System.Text; using Newtonsoft.Json; using OsuPlayer.Api.Data.API; -using OsuPlayer.Network.Online; +using OsuPlayer.Interfaces.Service; +using OsuPlayer.Services; +using Splat; -namespace OsuPlayer.Network.API.Service; +namespace OsuPlayer.Network.API; public abstract class AbstractApiBase { + protected internal abstract string ApiName { get; } + + protected internal readonly ILoggingService loggingService; + private readonly IProfileManagerService _profileManager; + protected static CancellationTokenSource CancellationTokenSource = new(); protected internal string Url => GetApiUrl(); protected string? UserAuthToken { get; set; } - + + protected AbstractApiBase() + { + loggingService = Locator.Current.GetService(); + _profileManager = Locator.Current.GetService(); + + loggingService.Log($"{ApiName} uses the following base URL: {Url}"); + } + private string GetApiUrl() { var url = "https://osuplayer.founntain.dev/"; - + bool.TryParse(Environment.GetEnvironmentVariable("USE_LOCAL_API"), out var useLocalApi); bool.TryParse(Environment.GetEnvironmentVariable("USE_SANDBOX_API"), out var useSandboxApi); @@ -35,12 +50,14 @@ private string GetApiUrl() return url; } - protected internal void ParseWebException(Exception ex) + protected internal void ParseWebException(Exception ex, Uri url) { if (ex.GetType() != typeof(WebException)) return; var webEx = (WebException) ex; + loggingService.Log($"Error while requesting {url}: {webEx.Message}", LogType.Error, webEx); + if (webEx.Status != WebExceptionStatus.ConnectFailure && webEx.Status != WebExceptionStatus.Timeout) return; if (Constants.OfflineMode) return; @@ -52,10 +69,10 @@ protected internal void ParseWebException(Exception ex) /// // protected void CancelCancellationToken() // { - // // TODO: Currently does nothing, because it breaks the functionality a bit. + // // TODO: Currently does nothing, because it breaks the functionality a bit. // // TODO: This needs to be re-thinked a bit! // return; - // + // // CancellationTokenSource.Cancel(); // CancellationTokenSource = new (); // } @@ -83,15 +100,17 @@ protected AuthenticationHeaderValue GetAuthorizationHeader(string username, stri if (Constants.OfflineMode) return default; + var url = new Uri($"{Url}{controller}/{action}"); + + loggingService.Log($"Requesting => {ApiName} => {url}"); + try { using var client = new HttpClient(); - var url = new Uri($"{Url}{controller}/{action}"); - var req = new HttpRequestMessage(HttpMethod.Delete, url); - req.Headers.Add("username", ProfileManager.User?.Name); + req.Headers.Add("username", _profileManager.User?.Name); req.Headers.Add("session-token", UserAuthToken); req.Content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json"); @@ -108,7 +127,7 @@ protected AuthenticationHeaderValue GetAuthorizationHeader(string username, stri } catch (Exception ex) { - ParseWebException(ex); + ParseWebException(ex, url); return default; } @@ -152,19 +171,19 @@ protected AuthenticationHeaderValue GetAuthorizationHeader(string username, stri if (Constants.OfflineMode) return default; + var url = new Uri($"{Url}{controller}/{action}"); + + loggingService.Log($"Requesting => {ApiName} => {url}"); + try { using var client = new HttpClient(); - var url = new Uri($"{Url}{controller}/{action}"); - var req = new HttpRequestMessage(HttpMethod.Get, url); - req.Headers.Add("username", ProfileManager.User?.Name); + req.Headers.Add("username", _profileManager.User?.Name); req.Headers.Add("session-token", UserAuthToken); - // CancelCancellationToken(); - var result = await client.SendAsync(req, CancellationTokenSource.Token); var response = JsonConvert.DeserializeObject>(await result.Content.ReadAsStringAsync()); @@ -175,7 +194,7 @@ protected AuthenticationHeaderValue GetAuthorizationHeader(string username, stri } catch (Exception ex) { - ParseWebException(ex); + ParseWebException(ex, url); return default; } @@ -194,19 +213,19 @@ protected AuthenticationHeaderValue GetAuthorizationHeader(string username, stri if (Constants.OfflineMode) return default; + var url = new Uri($"{Url}{controller}/{action}?{parameters}"); + + loggingService.Log($"Requesting => {ApiName} => {url}"); + try { using var client = new HttpClient(); - var url = new Uri($"{Url}{controller}/{action}?{parameters}"); - var req = new HttpRequestMessage(HttpMethod.Get, url); - req.Headers.Add("username", ProfileManager.User?.Name); + req.Headers.Add("username", _profileManager.User?.Name); req.Headers.Add("session-token", UserAuthToken); - // CancelCancellationToken(); - var result = await client.SendAsync(req, CancellationTokenSource.Token); var response = JsonConvert.DeserializeObject>(await result.Content.ReadAsStringAsync()); @@ -217,7 +236,7 @@ protected AuthenticationHeaderValue GetAuthorizationHeader(string username, stri } catch (Exception ex) { - ParseWebException(ex); + ParseWebException(ex, url); return default; } @@ -241,21 +260,21 @@ protected AuthenticationHeaderValue GetAuthorizationHeader(string username, stri if (Constants.OfflineMode) return default; + var url = new Uri($"{Url}{controller}/{action}"); + + loggingService.Log($"Requesting => {ApiName} => {url}"); + try { using var client = new HttpClient(); - var url = new Uri($"{Url}{controller}/{action}"); - var req = new HttpRequestMessage(HttpMethod.Post, url); - req.Headers.Add("username", ProfileManager.User?.Name); + req.Headers.Add("username", _profileManager.User?.Name); req.Headers.Add("session-token", UserAuthToken); req.Content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json"); - // CancelCancellationToken(); - var result = await client.SendAsync(req, CancellationTokenSource.Token); var response = JsonConvert.DeserializeObject>(await result.Content.ReadAsStringAsync()); @@ -266,7 +285,7 @@ protected AuthenticationHeaderValue GetAuthorizationHeader(string username, stri } catch (Exception ex) { - ParseWebException(ex); + ParseWebException(ex, url); return default; } @@ -286,19 +305,19 @@ protected AuthenticationHeaderValue GetAuthorizationHeader(string username, stri if (Constants.OfflineMode) return default; + var url = new Uri($"{Url}{controller}/{action}?{parameters}"); + + loggingService.Log($"Requesting => {ApiName} => {url}"); + try { using var client = new HttpClient(); - var url = new Uri($"{Url}{controller}/{action}?{parameters}"); - var req = new HttpRequestMessage(HttpMethod.Post, url); - req.Headers.Add("username", ProfileManager.User?.Name); + req.Headers.Add("username", _profileManager.User?.Name); req.Headers.Add("session-token", UserAuthToken); - // CancelCancellationToken(); - var result = await client.SendAsync(req, CancellationTokenSource.Token); var response = JsonConvert.DeserializeObject>(await result.Content.ReadAsStringAsync()); @@ -309,7 +328,7 @@ protected AuthenticationHeaderValue GetAuthorizationHeader(string username, stri } catch (Exception ex) { - ParseWebException(ex); + ParseWebException(ex, url); return default; } diff --git a/OsuPlayer.Network/API/Service/NorthFox/Endpoints/Nortfox.cs b/OsuPlayer.Network/API/NorthFox/Nortfox.cs similarity index 83% rename from OsuPlayer.Network/API/Service/NorthFox/Endpoints/Nortfox.cs rename to OsuPlayer.Network/API/NorthFox/Nortfox.cs index dcdd6b0b..a94563c8 100644 --- a/OsuPlayer.Network/API/Service/NorthFox/Endpoints/Nortfox.cs +++ b/OsuPlayer.Network/API/NorthFox/Nortfox.cs @@ -3,15 +3,19 @@ using OsuPlayer.Api.Data.API; using OsuPlayer.Api.Data.API.EntityModels; using OsuPlayer.Api.Data.API.RequestModels.User.Responses; +using OsuPlayer.Interfaces.Service; +using OsuPlayer.Interfaces.Service.Endpoint; -namespace OsuPlayer.Network.API.Service.NorthFox.Endpoints; +namespace OsuPlayer.Network.API.NorthFox; /// /// NorthFox is the wrapper for the OsuPlayer.API and provides access to all public available API Endpoints! -/// Yes I like foxes C: +/// Yes I like foxes 🦊 /// -public class NorthFox : AbstractApiBase +public class NorthFox : AbstractApiBase, IOsuPlayerApiService { + protected internal override string ApiName => "NorthFox API 🦊"; + #region API Endpoints public NorthFoxActivityEndpoint Activity { get; set; } @@ -19,7 +23,7 @@ public class NorthFox : AbstractApiBase public NorthFoxBadgeEndpoint Badge { get; set; } public NorthFoxBeatmapEndpoint Beatmap { get; set; } public NorthFoxEventEndpoint Event { get; set; } - public NorthFoxUserEndpoint User { get; set; } + public IOsuPlayerApiUserEndpoint User { get; set; } public NorthFoxUserStatisticsEndpoint UserStatistics { get; set; } #endregion @@ -34,9 +38,9 @@ public NorthFox() User = new NorthFoxUserEndpoint(this); UserStatistics = new NorthFoxUserStatisticsEndpoint(this); } - + #region Authentication - + public async Task LoginAndSaveAuthToken(string username, string password) { var response = await Login(username, password); @@ -64,17 +68,17 @@ public NorthFox() if (Constants.OfflineMode) return default; + var url = new Uri($"{Url}User/login"); + + loggingService.Log($"Requesting => {ApiName} => {url}"); + try { using var client = new HttpClient(); - var url = new Uri($"{Url}User/login"); - var req = new HttpRequestMessage(HttpMethod.Post, url); req.Headers.Authorization = GetAuthorizationHeader(username, password); - // CancelCancellationToken(); - var result = await client.SendAsync(req, CancellationTokenSource.Token); var response = JsonConvert.DeserializeObject>(await result.Content.ReadAsStringAsync()); @@ -85,7 +89,7 @@ public NorthFox() } catch (Exception ex) { - ParseWebException(ex); + ParseWebException(ex, url); return default; } @@ -96,18 +100,18 @@ public NorthFox() if (Constants.OfflineMode) return default; + var url = new Uri($"{Url}User/loginWithToken"); + + loggingService.Log($"Requesting => {ApiName} => {url}"); + try { using var client = new HttpClient(); - var url = new Uri($"{Url}User/loginWithToken"); - var req = new HttpRequestMessage(HttpMethod.Post, url); req.Headers.Add("session-token", token); - // CancelCancellationToken(); - var result = await client.SendAsync(req, CancellationTokenSource.Token); var response = JsonConvert.DeserializeObject>(await result.Content.ReadAsStringAsync()); @@ -118,7 +122,7 @@ public NorthFox() } catch (Exception ex) { - ParseWebException(ex); + ParseWebException(ex, url); return default; } diff --git a/OsuPlayer.Network/API/Service/NorthFox/Endpoints/NorthFox.Activity.cs b/OsuPlayer.Network/API/NorthFox/NorthFox.Activity.cs similarity index 89% rename from OsuPlayer.Network/API/Service/NorthFox/Endpoints/NorthFox.Activity.cs rename to OsuPlayer.Network/API/NorthFox/NorthFox.Activity.cs index 2aa89b71..bb08166d 100644 --- a/OsuPlayer.Network/API/Service/NorthFox/Endpoints/NorthFox.Activity.cs +++ b/OsuPlayer.Network/API/NorthFox/NorthFox.Activity.cs @@ -1,16 +1,16 @@ using OsuPlayer.Api.Data.API.EntityModels; -namespace OsuPlayer.Network.API.Service.NorthFox.Endpoints; +namespace OsuPlayer.Network.API.NorthFox; public class NorthFoxActivityEndpoint { private readonly AbstractApiBase _apiBase; - + public NorthFoxActivityEndpoint(AbstractApiBase apiBase) { _apiBase = apiBase; } - + #region GET Requests public async Task?> GetAllActivities() diff --git a/OsuPlayer.Network/API/Service/NorthFox/Endpoints/NorthFox.ApiStatistics.cs b/OsuPlayer.Network/API/NorthFox/NorthFox.ApiStatistics.cs similarity index 89% rename from OsuPlayer.Network/API/Service/NorthFox/Endpoints/NorthFox.ApiStatistics.cs rename to OsuPlayer.Network/API/NorthFox/NorthFox.ApiStatistics.cs index 96c7481d..7216ee97 100644 --- a/OsuPlayer.Network/API/Service/NorthFox/Endpoints/NorthFox.ApiStatistics.cs +++ b/OsuPlayer.Network/API/NorthFox/NorthFox.ApiStatistics.cs @@ -1,16 +1,16 @@ using OsuPlayer.Api.Data.API.Models; -namespace OsuPlayer.Network.API.Service.NorthFox.Endpoints; +namespace OsuPlayer.Network.API.NorthFox; public class NorthFoxApiStatisticsEndpoint { private readonly AbstractApiBase _apiBase; - + public NorthFoxApiStatisticsEndpoint(AbstractApiBase apiBase) { _apiBase = apiBase; } - + #region GET Requests public async Task GetApiStatistics() diff --git a/OsuPlayer.Network/API/Service/NorthFox/Endpoints/NorthFox.Badge.cs b/OsuPlayer.Network/API/NorthFox/NorthFox.Badge.cs similarity index 96% rename from OsuPlayer.Network/API/Service/NorthFox/Endpoints/NorthFox.Badge.cs rename to OsuPlayer.Network/API/NorthFox/NorthFox.Badge.cs index 624598c8..956a28a9 100644 --- a/OsuPlayer.Network/API/Service/NorthFox/Endpoints/NorthFox.Badge.cs +++ b/OsuPlayer.Network/API/NorthFox/NorthFox.Badge.cs @@ -1,6 +1,6 @@ using OsuPlayer.Api.Data.API.EntityModels; -namespace OsuPlayer.Network.API.Service.NorthFox.Endpoints; +namespace OsuPlayer.Network.API.NorthFox; public class NorthFoxBadgeEndpoint { diff --git a/OsuPlayer.Network/API/Service/NorthFox/Endpoints/NorthFox.Beatmap.cs b/OsuPlayer.Network/API/NorthFox/NorthFox.Beatmap.cs similarity index 95% rename from OsuPlayer.Network/API/Service/NorthFox/Endpoints/NorthFox.Beatmap.cs rename to OsuPlayer.Network/API/NorthFox/NorthFox.Beatmap.cs index bbc0db11..8a52ae99 100644 --- a/OsuPlayer.Network/API/Service/NorthFox/Endpoints/NorthFox.Beatmap.cs +++ b/OsuPlayer.Network/API/NorthFox/NorthFox.Beatmap.cs @@ -3,7 +3,7 @@ using OsuPlayer.Api.Data.API.RequestModels.Statistics; using OsuPlayer.Api.Data.API.ResponseModels; -namespace OsuPlayer.Network.API.Service.NorthFox.Endpoints; +namespace OsuPlayer.Network.API.NorthFox; public class NorthFoxBeatmapEndpoint { diff --git a/OsuPlayer.Network/API/Service/NorthFox/Endpoints/NorthFox.Event.cs b/OsuPlayer.Network/API/NorthFox/NorthFox.Event.cs similarity index 90% rename from OsuPlayer.Network/API/Service/NorthFox/Endpoints/NorthFox.Event.cs rename to OsuPlayer.Network/API/NorthFox/NorthFox.Event.cs index 2bed1e97..3a4aaed0 100644 --- a/OsuPlayer.Network/API/Service/NorthFox/Endpoints/NorthFox.Event.cs +++ b/OsuPlayer.Network/API/NorthFox/NorthFox.Event.cs @@ -1,6 +1,6 @@ using OsuPlayer.Api.Data.API.EntityModels; -namespace OsuPlayer.Network.API.Service.NorthFox.Endpoints; +namespace OsuPlayer.Network.API.NorthFox; public class NorthFoxEventEndpoint { diff --git a/OsuPlayer.Network/API/Service/NorthFox/Endpoints/NorthFox.User.cs b/OsuPlayer.Network/API/NorthFox/NorthFox.User.cs similarity index 88% rename from OsuPlayer.Network/API/Service/NorthFox/Endpoints/NorthFox.User.cs rename to OsuPlayer.Network/API/NorthFox/NorthFox.User.cs index 6a38d8f7..3da17a39 100644 --- a/OsuPlayer.Network/API/Service/NorthFox/Endpoints/NorthFox.User.cs +++ b/OsuPlayer.Network/API/NorthFox/NorthFox.User.cs @@ -1,18 +1,19 @@ using Avalonia.Media.Imaging; using OsuPlayer.Api.Data.API.EntityModels; using OsuPlayer.Api.Data.API.RequestModels.User; +using OsuPlayer.Interfaces.Service.Endpoint; -namespace OsuPlayer.Network.API.Service.NorthFox.Endpoints; +namespace OsuPlayer.Network.API.NorthFox; -public class NorthFoxUserEndpoint +public class NorthFoxUserEndpoint : IOsuPlayerApiUserEndpoint { private readonly AbstractApiBase _apiBase; - + public NorthFoxUserEndpoint(AbstractApiBase apiBase) { _apiBase = apiBase; } - + #region DELETE Requests public async Task DeleteUser() @@ -38,17 +39,21 @@ public async Task DeleteUser() if (Constants.OfflineMode) return default; + var url = new Uri($"{_apiBase.Url}User/getProfilePicture?id={uniqueId}"); + + _apiBase.loggingService.Log($"Requesting => {_apiBase.ApiName} => {url}"); + try { using var client = new HttpClient(); - var data = await client.GetAsync(new Uri($"{_apiBase.Url}User/getProfilePicture?id={uniqueId}")); + var data = await client.GetAsync(url); return new Bitmap(await data.Content.ReadAsStreamAsync()); } catch (Exception ex) { - _apiBase.ParseWebException(ex); + _apiBase.ParseWebException(ex, url); return default; } @@ -66,6 +71,8 @@ public async Task DeleteUser() using var client = new HttpClient(); + _apiBase.loggingService.Log($"Requesting => {_apiBase.ApiName} => {bannerUrl}"); + try { var data = await client.GetAsync(bannerUrl); diff --git a/OsuPlayer.Network/API/Service/NorthFox/Endpoints/NorthFox.UserStatistics.cs b/OsuPlayer.Network/API/NorthFox/NorthFox.UserStatistics.cs similarity index 93% rename from OsuPlayer.Network/API/Service/NorthFox/Endpoints/NorthFox.UserStatistics.cs rename to OsuPlayer.Network/API/NorthFox/NorthFox.UserStatistics.cs index b801cdb4..9a74c67c 100644 --- a/OsuPlayer.Network/API/Service/NorthFox/Endpoints/NorthFox.UserStatistics.cs +++ b/OsuPlayer.Network/API/NorthFox/NorthFox.UserStatistics.cs @@ -1,7 +1,7 @@ using OsuPlayer.Api.Data.API.EntityModels; using OsuPlayer.Api.Data.API.RequestModels.Statistics; -namespace OsuPlayer.Network.API.Service.NorthFox.Endpoints; +namespace OsuPlayer.Network.API.NorthFox; public class NorthFoxUserStatisticsEndpoint { diff --git a/OsuPlayer.Network/Discord/DiscordClient.cs b/OsuPlayer.Network/Discord/DiscordClient.cs index 965c17f1..11ae3d3c 100644 --- a/OsuPlayer.Network/Discord/DiscordClient.cs +++ b/OsuPlayer.Network/Discord/DiscordClient.cs @@ -1,20 +1,22 @@ using System.Diagnostics; -using System.Net; using System.Reflection; using System.Text; using DiscordRPC; -using DiscordRPC.Logging; using DiscordRPC.Message; using Nein.Extensions; -using OsuPlayer.Network.Online; +using OsuPlayer.Interfaces.Service; +using Splat; +using ConsoleLogger = DiscordRPC.Logging.ConsoleLogger; +using LogLevel = DiscordRPC.Logging.LogLevel; namespace OsuPlayer.Network.Discord; public class DiscordClient { private const string ApplicationId = "506435812397940736"; - private readonly DiscordRpcClient _client; private const string DefaultImageKey = "logo"; + private readonly DiscordRpcClient _client; + private readonly IProfileManagerService _profileManager; private readonly string _defaultOsuThumbnailUrl = "https://assets.ppy.sh/beatmaps/{0}/covers/list.jpg"; /// @@ -22,8 +24,13 @@ public class DiscordClient /// private readonly Assets _defaultAssets; - public DiscordClient() + public DiscordClient() : this(Locator.Current.GetRequiredService()) + { + } + + public DiscordClient(IProfileManagerService profileManager) { + _profileManager = profileManager; _client = new DiscordRpcClient(ApplicationId); _defaultAssets = new Assets @@ -92,7 +99,7 @@ public async Task UpdatePresence(string details, string state, int beatmapSetId } var timestamps = durationLeft == null ? null : Timestamps.FromTimeSpan(durationLeft.Value); - + _client.SetPresence(new RichPresence { Details = details, @@ -121,10 +128,10 @@ public async Task UpdatePresence(string details, string state, int beatmapSetId return null; return new() - { - LargeImageKey = url, - LargeImageText = $"osu!player v{Assembly.GetEntryAssembly().ToVersionString()}" - }; + { + LargeImageKey = url, + LargeImageText = $"osu!player v{Assembly.GetEntryAssembly().ToVersionString()}" + }; } private Button[]? GetButtons() @@ -138,12 +145,12 @@ public async Task UpdatePresence(string details, string state, int beatmapSetId } }; - if (ProfileManager.User is null || string.IsNullOrWhiteSpace(ProfileManager.User.Name)) return buttons.ToArray(); + if (_profileManager.User is null || string.IsNullOrWhiteSpace(_profileManager.User.Name)) return buttons.ToArray(); buttons.Add(new Button { - Label = $"{ProfileManager.User.Name}'s profile", - Url = $"https://stats.founntain.dev/user/{Uri.EscapeDataString(ProfileManager.User.Name)}" + Label = $"{_profileManager.User.Name}'s profile", + Url = $"https://stats.founntain.dev/user/{Uri.EscapeDataString(_profileManager.User.Name)}" } ); diff --git a/OsuPlayer.Network/LastFM/Responses/TokenResponse.cs b/OsuPlayer.Network/LastFM/Responses/TokenResponse.cs deleted file mode 100644 index 14efd063..00000000 --- a/OsuPlayer.Network/LastFM/Responses/TokenResponse.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace OsuPlayer.Network.LastFM.Responses; - -public record TokenResponse(string Token); \ No newline at end of file diff --git a/OsuPlayer.Network/LastFM/LastFmApi.cs b/OsuPlayer.Network/LastFm/LastFmApi.cs similarity index 90% rename from OsuPlayer.Network/LastFM/LastFmApi.cs rename to OsuPlayer.Network/LastFm/LastFmApi.cs index cdcb2c45..c6ded02b 100644 --- a/OsuPlayer.Network/LastFM/LastFmApi.cs +++ b/OsuPlayer.Network/LastFm/LastFmApi.cs @@ -2,14 +2,19 @@ using System.Text; using System.Xml; using Nein.Extensions; +using OsuPlayer.Data.LazerModels.Extensions; +using OsuPlayer.Interfaces.Service; using OsuPlayer.IO.Storage.Config; -using OsuPlayer.IO.Storage.LazerModels.Extensions; -using OsuPlayer.Network.LastFM.Responses; +using OsuPlayer.Network.LastFm.Responses; +using OsuPlayer.Services; +using Splat; -namespace OsuPlayer.Network.LastFM; +namespace OsuPlayer.Network.LastFm; -public class LastFmApi : WebRequestBase +public class LastFmApi : WebRequestBase, ILastFmApiService { + private readonly ILoggingService _loggingService; + /// /// The Last.FM API-Key of the user /// @@ -29,6 +34,8 @@ public class LastFmApi : WebRequestBase public LastFmApi() { + _loggingService = Locator.Current.GetRequiredService(); + BaseUrl = "http://ws.audioscrobbler.com/2.0/?format=json"; using var config = new Config(); @@ -38,15 +45,6 @@ public LastFmApi() _authToken = string.Empty; } - public LastFmApi(string apiKey, string secret) - { - _apiKey = apiKey; - _secret = secret; - _authToken = string.Empty; - - BaseUrl = "http://ws.audioscrobbler.com/2.0/?format=json"; - } - public void SetApiKeyAndSecret(string apiKey, string secret) { _apiKey = apiKey; @@ -62,7 +60,8 @@ public async Task Scrobble(string title, string artist) { if (string.IsNullOrWhiteSpace(_sessionKey)) { - Console.WriteLine("can't scrobble to the last.fm api, because there isn't a valid session key"); + _loggingService.Log("Can't scrobble to the last.fm api, because there isn't a valid session key", LogType.Warning); + return; } @@ -91,6 +90,7 @@ public async Task SaveSessionKeyAsync() config.Container.LastFmApiKey = _apiKey; config.Container.LastFmSecret = _secret; } + /// /// Loads the last.fm Session Key from the users file system /// @@ -104,7 +104,7 @@ public bool LoadSessionKey() return !string.IsNullOrWhiteSpace(_sessionKey); } - + /// /// Loads the last.fm Session Key from the users file system asynchronously /// @@ -174,20 +174,15 @@ public async Task GetSessionKey() /// a private async Task SessionRequest(string route) { + var baseUrl = $"http://ws.audioscrobbler.com/2.0/?api_key={_apiKey}"; + var url = new Uri($"{baseUrl}{route}"); + try { using var client = new HttpClient(); - var baseUrl = $"http://ws.audioscrobbler.com/2.0/?api_key={_apiKey}"; - - var url = new Uri($"{baseUrl}{route}"); - var req = new HttpRequestMessage(HttpMethod.Get, url); - - // CancelCancellationToken(); - var result = await client.SendAsync(req, CancellationTokenSource.Token); - var respString = await result.Content.ReadAsStringAsync(); var xmlDoc = new XmlDocument(); @@ -200,7 +195,7 @@ public async Task GetSessionKey() } catch (Exception ex) { - ParseWebException(ex); + ParseWebException(ex, url); return default; } diff --git a/OsuPlayer.Network/LastFM/Responses/SessionResponse.cs b/OsuPlayer.Network/LastFm/Responses/SessionResponse.cs similarity index 59% rename from OsuPlayer.Network/LastFM/Responses/SessionResponse.cs rename to OsuPlayer.Network/LastFm/Responses/SessionResponse.cs index 4a2db73d..65c8339f 100644 --- a/OsuPlayer.Network/LastFM/Responses/SessionResponse.cs +++ b/OsuPlayer.Network/LastFm/Responses/SessionResponse.cs @@ -1,3 +1,3 @@ -namespace OsuPlayer.Network.LastFM.Responses; +namespace OsuPlayer.Network.LastFm.Responses; public record SessionResponse(string? Name, string Key, int Subscriber); \ No newline at end of file diff --git a/OsuPlayer.Network/LastFm/Responses/TokenResponse.cs b/OsuPlayer.Network/LastFm/Responses/TokenResponse.cs new file mode 100644 index 00000000..fbae3c3a --- /dev/null +++ b/OsuPlayer.Network/LastFm/Responses/TokenResponse.cs @@ -0,0 +1,3 @@ +namespace OsuPlayer.Network.LastFm.Responses; + +public record TokenResponse(string Token); \ No newline at end of file diff --git a/OsuPlayer.Network/Online/ProfileManager.cs b/OsuPlayer.Network/Online/ProfileManager.cs deleted file mode 100644 index 788da41d..00000000 --- a/OsuPlayer.Network/Online/ProfileManager.cs +++ /dev/null @@ -1,41 +0,0 @@ -using OsuPlayer.Network.API.Service.NorthFox.Endpoints; -using Splat; - -namespace OsuPlayer.Network.Online; - -public class ProfileManager -{ - public static User? User = User ?? new User(); - - public static async Task Login(string username, string password) - { - var result = await Locator.Current.GetService().LoginAndSaveAuthToken(username, password); - - // If login failed, we set the user to its default value - if (result == default) - { - User = default; - - return; - } - - User = new User(result); - } - - public static async Task Login(string token) - { - if (string.IsNullOrWhiteSpace(token)) return; - - var result = await Locator.Current.GetService().LoginWithTokenAndSaveNewToken(token); - - // If login via token failed, we set the User to its default value - if (result == default) - { - User = default; - - return; - } - - User = new User(result); - } -} \ No newline at end of file diff --git a/OsuPlayer.Network/OsuPlayer.Network.csproj b/OsuPlayer.Network/OsuPlayer.Network.csproj index aebc6234..e533e183 100644 --- a/OsuPlayer.Network/OsuPlayer.Network.csproj +++ b/OsuPlayer.Network/OsuPlayer.Network.csproj @@ -17,7 +17,7 @@ - + - + \ No newline at end of file diff --git a/OsuPlayer.Network/WebRequestBase.cs b/OsuPlayer.Network/WebRequestBase.cs index f1a39eee..94625f44 100644 --- a/OsuPlayer.Network/WebRequestBase.cs +++ b/OsuPlayer.Network/WebRequestBase.cs @@ -1,59 +1,64 @@ using System.Net; using System.Text; using Newtonsoft.Json; +using OsuPlayer.Interfaces.Service; +using OsuPlayer.Services; +using Splat; namespace OsuPlayer.Network; public class WebRequestBase : IWebRequest { + private readonly ILoggingService _loggingService; protected string BaseUrl; - protected CancellationTokenSource CancellationTokenSource = new(); + protected readonly CancellationTokenSource CancellationTokenSource = new(); - public WebRequestBase() + protected WebRequestBase() :this(string.Empty) { - BaseUrl = string.Empty; } public WebRequestBase(string baseUrl) { BaseUrl = baseUrl; + + _loggingService = Locator.Current.GetService(); } - - protected void ParseWebException(Exception ex) + + protected void ParseWebException(Exception ex, Uri url) { if (ex.GetType() != typeof(WebException)) return; var webEx = (WebException) ex; - if (webEx.Status != WebExceptionStatus.ConnectFailure && webEx.Status != WebExceptionStatus.Timeout) return; + _loggingService.Log($"Error while requesting {url}: {webEx.Message}", LogType.Error, webEx); } public virtual async Task GetRequest(string route, TRequest? data = default) { + var url = new Uri($"{BaseUrl}{route}"); + + _loggingService.Log($"Request => {url}"); + try { using var client = new HttpClient(); - var url = new Uri($"{BaseUrl}{route}"); - var req = new HttpRequestMessage(HttpMethod.Get, url); if ( data != null ) req.Content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json"); - // CancelCancellationToken(); - var result = await client.SendAsync(req, CancellationTokenSource.Token); var respString = await result.Content.ReadAsStringAsync(); - + var response = JsonConvert.DeserializeObject(respString); return response; } catch (Exception ex) { - ParseWebException(ex); + ParseWebException(ex, url); return default; } @@ -61,19 +66,21 @@ public virtual async Task GetRequest(string rout public async Task GetRequestWithHttpResponseMessage(string route) { + var url = new Uri($"{BaseUrl}{route}"); + + _loggingService.Log($"Request => {url}"); + try { using var client = new HttpClient(); - var url = new Uri($"{BaseUrl}{route}"); - var req = new HttpRequestMessage(HttpMethod.Get, url); - + return await client.SendAsync(req, CancellationTokenSource.Token); } catch (Exception ex) { - ParseWebException(ex); + ParseWebException(ex, url); return new HttpResponseMessage(HttpStatusCode.BadRequest); } @@ -81,12 +88,14 @@ public async Task GetRequestWithHttpResponseMessage(string public virtual async Task PostRequest(string route, TRequest? data = default) { + var url = new Uri($"{BaseUrl}{route}"); + + _loggingService.Log($"Request => {url}"); + try { using var client = new HttpClient(); - var url = new Uri($"{BaseUrl}{route}"); - var req = new HttpRequestMessage(HttpMethod.Post, url); if(data != null) @@ -97,8 +106,6 @@ public virtual async Task PostRequest(string rou req.Content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json"); } - // CancelCancellationToken(); - var result = await client.SendAsync(req, CancellationTokenSource.Token); var response = JsonConvert.DeserializeObject(await result.Content.ReadAsStringAsync()); @@ -107,7 +114,7 @@ public virtual async Task PostRequest(string rou } catch (Exception ex) { - ParseWebException(ex); + ParseWebException(ex, url); return default; } diff --git a/OsuPlayer/Modules/Services/ApiStatisticsProvider.cs b/OsuPlayer.Services/ApiStatisticsService.cs similarity index 50% rename from OsuPlayer/Modules/Services/ApiStatisticsProvider.cs rename to OsuPlayer.Services/ApiStatisticsService.cs index f7e8636f..8ddcf3e4 100644 --- a/OsuPlayer/Modules/Services/ApiStatisticsProvider.cs +++ b/OsuPlayer.Services/ApiStatisticsService.cs @@ -1,27 +1,37 @@ using System.ComponentModel; -using System.Threading.Tasks; using Avalonia.Threading; using LiveChartsCore.Defaults; using Nein.Extensions; +using Nein.Extensions.Bindables; using OsuPlayer.Api.Data.API.EntityModels; using OsuPlayer.Api.Data.API.Enums; using OsuPlayer.Api.Data.API.RequestModels.User; -using OsuPlayer.Network.API.Service.NorthFox.Endpoints; +using OsuPlayer.Data.DataModels; +using OsuPlayer.Interfaces.Service; using Splat; -namespace OsuPlayer.Modules.Services; +namespace OsuPlayer.Services; -public class ApiStatisticsProvider : IStatisticsProvider +public class ApiStatisticsService : OsuPlayerService, IStatisticsProvider { public BindableList GraphValues { get; } = new(); + + public override string ServiceName => "API_STATISTICS_SERVICE"; public event PropertyChangedEventHandler? UserDataChanged; + private readonly IProfileManagerService _profileManager; + + public ApiStatisticsService(IProfileManagerService profileManager) + { + _profileManager = profileManager; + } + public async Task UpdateOnlineStatus(UserOnlineStatusType statusType, string? song = null, string? checksum = null) { - if (ProfileManager.User == default || ProfileManager.User.UniqueId == Guid.Empty) + if (_profileManager.User == default || _profileManager.User.UniqueId == Guid.Empty) return; - await Locator.Current.GetService().User.SetOnlineStatus(new UserOnlineStatusModel + await Locator.Current.GetRequiredService().User.SetOnlineStatus(new UserOnlineStatusModel { StatusType = statusType, Song = song, @@ -31,13 +41,13 @@ await Locator.Current.GetService().User.SetOnlineStatus(new UserOnline public async Task UpdateXp(string hash, double timeListened, double channelLength) { - if (ProfileManager.User == default) return; + if (_profileManager.User == default) return; - var currentTotalXp = ProfileManager.User.TotalXp; + var currentTotalXp = _profileManager.User.TotalXp; var time = timeListened / 1000; - var response = await Locator.Current.GetService().User.UpdateXp(new UpdateXpModel + var response = await Locator.Current.GetRequiredService().User.UpdateXp(new UpdateXpModel { SongChecksum = hash, ChannelLength = channelLength, @@ -46,25 +56,29 @@ public async Task UpdateXp(string hash, double timeListened, double channelLengt if (response == default) return; - ProfileManager.User = response.ConvertObjectToJson(); + _profileManager.User = response.ConvertObjectToJson(); var xpEarned = response.TotalXp - currentTotalXp; GraphValues.Add(new ObservableValue(xpEarned)); await Dispatcher.UIThread.InvokeAsync(() => UserDataChanged?.Invoke(this, new PropertyChangedEventArgs("Xp"))); + + LogToConsole($"Successfully updated XP for {_profileManager.User?.Name ?? string.Empty}. Earned {xpEarned} XP."); } public async Task UpdateSongsPlayed(int beatmapSetId) { - if (ProfileManager.User == default) return; + if (_profileManager.User == default) return; - var response = await Locator.Current.GetService().User.UpdateSongsPlayed(1, beatmapSetId); + var response = await Locator.Current.GetRequiredService().User.UpdateSongsPlayed(1, beatmapSetId); if (response == default) return; - ProfileManager.User = response.ConvertObjectToJson(); + _profileManager.User = response.ConvertObjectToJson(); await Dispatcher.UIThread.InvokeAsync(() => UserDataChanged?.Invoke(this, new PropertyChangedEventArgs("SongsPlayed"))); + + LogToConsole($"Successfully updated songs played for {_profileManager.User?.Name ?? string.Empty}"); } } \ No newline at end of file diff --git a/OsuPlayer.Services/DbReaderFactory.cs b/OsuPlayer.Services/DbReaderFactory.cs new file mode 100644 index 00000000..06fa94ce --- /dev/null +++ b/OsuPlayer.Services/DbReaderFactory.cs @@ -0,0 +1,32 @@ +using OsuPlayer.Data.DataModels; +using OsuPlayer.Data.DataModels.Interfaces; +using OsuPlayer.Data.Enums; +using OsuPlayer.IO.DbReader; + +namespace OsuPlayer.Services; + +public class DbReaderFactory : OsuPlayerService, IDbReaderFactory +{ + public override string ServiceName => "DBREADER_FACTORY_SERVICE"; + + public DbCreationType Type { get; set; } + + public IDatabaseReader? CreateDatabaseReader(string path) + { + switch (Type) + { + case DbCreationType.OsuDb: + var dbLoc = Path.Combine(path, "osu!.db"); + + if (!File.Exists(dbLoc)) return null; + + var file = File.OpenRead(dbLoc); + + return new OsuDbReader(file, path, this); + case DbCreationType.Realm: + return new RealmReader(path, this); + default: + throw new ArgumentOutOfRangeException(); + } + } +} \ No newline at end of file diff --git a/OsuPlayer.Services/HistoryService.cs b/OsuPlayer.Services/HistoryService.cs new file mode 100644 index 00000000..f6f2f3f5 --- /dev/null +++ b/OsuPlayer.Services/HistoryService.cs @@ -0,0 +1,37 @@ +using Nein.Extensions.Bindables; +using OsuPlayer.Data.DataModels.Extensions; +using OsuPlayer.Data.DataModels.Interfaces; +using OsuPlayer.Interfaces.Service; + +namespace OsuPlayer.Services; + +public class HistoryService : OsuPlayerService, IHistoryProvider +{ + public HistoricalMapEntryComparer Comparer { get; } = new(); + public BindableList History { get; } = new(); + + public override string ServiceName => "HISTORY_SERVICE"; + + public void AddOrUpdateMapEntry(IMapEntryBase? mapEntry) + { + if (mapEntry == default) return; + + var historicalMapEntry = new HistoricalMapEntry(mapEntry); + + var foundEntry = History.FirstOrDefault(entry => Comparer.Equals(entry, historicalMapEntry)); + + if (foundEntry != default) + History.Remove(foundEntry); + + History.Add(historicalMapEntry); + + LogToConsole($"Added or updated map entry {mapEntry.GetSongName()} ({mapEntry.Hash}) to history."); + } + + public void ClearHistory() + { + History.Clear(); + + LogToConsole($"Cleared listening history."); + } +} \ No newline at end of file diff --git a/OsuPlayer.Services/LastFmService.cs b/OsuPlayer.Services/LastFmService.cs new file mode 100644 index 00000000..4b08d988 --- /dev/null +++ b/OsuPlayer.Services/LastFmService.cs @@ -0,0 +1,25 @@ +using OsuPlayer.Interfaces.Service; + +namespace OsuPlayer.Services; + +public class LastFmService : OsuPlayerService, ILastFmApiService +{ + private readonly ILastFmApiService _lastFmApiService; + + public override string ServiceName => "LASTFM_SERVICE"; + + public LastFmService( ILastFmApiService lastFmApiService ) + { + _lastFmApiService = lastFmApiService; + } + + public void AuthorizeToken() => _lastFmApiService.AuthorizeToken(); + public void SetApiKeyAndSecret(string apiKey, string secret) => _lastFmApiService.SetApiKeyAndSecret(apiKey, secret); + public bool LoadSessionKey() => _lastFmApiService.LoadSessionKey(); + public bool IsAuthorized() => _lastFmApiService.IsAuthorized(); + public Task GetSessionKey() => _lastFmApiService.GetSessionKey(); + public Task SaveSessionKeyAsync() => _lastFmApiService.SaveSessionKeyAsync(); + public Task Scrobble(string title, string artist) => _lastFmApiService.Scrobble(title, artist); + public Task LoadSessionKeyAsync() => _lastFmApiService.LoadSessionKeyAsync(); + public Task GetAuthToken() => _lastFmApiService.GetAuthToken(); +} \ No newline at end of file diff --git a/OsuPlayer.Services/LoggingService.cs b/OsuPlayer.Services/LoggingService.cs new file mode 100644 index 00000000..65bd48d7 --- /dev/null +++ b/OsuPlayer.Services/LoggingService.cs @@ -0,0 +1,10 @@ +using OsuPlayer.Interfaces.Service; + +namespace OsuPlayer.Services; + +public class LoggingService : OsuPlayerService, ILoggingService +{ + public override string ServiceName => "LOGGING_SERVICE"; + + public void Log(string message, LogType logType = LogType.Info, object? data = null) => LogToConsole(message, logType, data); +} \ No newline at end of file diff --git a/OsuPlayer.Services/OsuPlayer.Services.csproj b/OsuPlayer.Services/OsuPlayer.Services.csproj new file mode 100644 index 00000000..5e195cac --- /dev/null +++ b/OsuPlayer.Services/OsuPlayer.Services.csproj @@ -0,0 +1,21 @@ + + + + net7.0 + enable + annotations + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OsuPlayer.Services/OsuPlayerService.cs b/OsuPlayer.Services/OsuPlayerService.cs new file mode 100644 index 00000000..74b9f4cb --- /dev/null +++ b/OsuPlayer.Services/OsuPlayerService.cs @@ -0,0 +1,87 @@ +using System.Text; +using System.Text.Json; +using OsuPlayer.Interfaces.Service; + +namespace OsuPlayer.Services; + +public abstract class OsuPlayerService : IOsuPlayerService +{ + public abstract string ServiceName { get; } + + protected OsuPlayerService() + { + try + { + Console.OutputEncoding = Encoding.UTF8; + + LogToConsole("Service initialized.", LogType.Success, includeLogTypeTag: false); + } + catch (Exception _) + { + LogToConsole($"Service initialized, but couldn't initialize with encoding {Encoding.UTF8.EncodingName}.", LogType.Warning, includeLogTypeTag: false); + } + } + + ~OsuPlayerService() => LogToConsole("Service deinitialized.", LogType.Warning, includeLogTypeTag: false); + + public string ServiceTag() => $"[{ServiceName}] "; + + protected void LogToConsole(string message, LogType logType = LogType.Info, object? data = null, bool includeLogTypeTag = true) + { + string outputMessage; + + if (data == null) + outputMessage = $"{ServiceTag()}{(includeLogTypeTag ? GetLogTypeIcon(logType) + " " : string.Empty)}{message}"; + else + outputMessage = + $"{ServiceTag()}{(includeLogTypeTag ? GetLogTypeIcon(logType) + " " : string.Empty)}{message} - {JsonSerializer.Serialize(data)}"; + + switch (logType) + { + case LogType.Info: + Console.ResetColor(); + + break; + case LogType.Success: + Console.BackgroundColor = ConsoleColor.DarkGreen; + Console.ForegroundColor = ConsoleColor.White; + + break; + case LogType.Warning: + Console.BackgroundColor = ConsoleColor.DarkYellow; + Console.ForegroundColor = ConsoleColor.White; + + break; + case LogType.Error: + Console.BackgroundColor = ConsoleColor.DarkRed; + Console.ForegroundColor = ConsoleColor.White; + + break; + case LogType.Debug: + Console.BackgroundColor = ConsoleColor.DarkMagenta; + Console.ForegroundColor = ConsoleColor.White; + + break; + default: + throw new ArgumentOutOfRangeException(nameof(logType), logType, null); + } + + Console.WriteLine(outputMessage); + + // Resetting the colors, so subsequent logs don't have the same colors. + Console.ResetColor(); + } + + private string GetLogTypeIcon(LogType logType) + { + return logType switch + { + LogType.Info => "ℹ️", + LogType.Success => "✅", + LogType.Warning => "⚠️", + LogType.Error => "❌", + LogType.Debug => "🐛", + _ => throw new ArgumentOutOfRangeException(nameof(logType), logType, null) + }; + } +} \ No newline at end of file diff --git a/OsuPlayer/Modules/Services/OsuSongSourceProvider.cs b/OsuPlayer.Services/OsuSongSourceService.cs similarity index 78% rename from OsuPlayer/Modules/Services/OsuSongSourceProvider.cs rename to OsuPlayer.Services/OsuSongSourceService.cs index 5b07ed2e..3adfbc03 100644 --- a/OsuPlayer/Modules/Services/OsuSongSourceProvider.cs +++ b/OsuPlayer.Services/OsuSongSourceService.cs @@ -1,10 +1,12 @@ -using DynamicData; -using OsuPlayer.IO.DbReader.Interfaces; +using System.Collections.ObjectModel; +using DynamicData; +using OsuPlayer.Data.DataModels.Interfaces; +using OsuPlayer.Interfaces.Service; using OsuPlayer.IO.Importer; -namespace OsuPlayer.Modules.Services; +namespace OsuPlayer.Services; -public class OsuSongSourceProvider : ISongSourceProvider +public class OsuSongSourceService : OsuPlayerService, ISongSourceProvider { private readonly ReadOnlyObservableCollection? _songSourceList; @@ -12,7 +14,9 @@ public class OsuSongSourceProvider : ISongSourceProvider public IObservable>? Songs { get; } public ReadOnlyObservableCollection? SongSourceList => _songSourceList; - public OsuSongSourceProvider(ISortProvider? sortProvider = null) + public override string ServiceName => "OSU_SONGSOURCE_SERVICE"; + + public OsuSongSourceService(ISortProvider? sortProvider = null) { if (sortProvider != null) { diff --git a/OsuPlayer.Services/ProfileManagerServiceService.cs b/OsuPlayer.Services/ProfileManagerServiceService.cs new file mode 100644 index 00000000..15b9e2a8 --- /dev/null +++ b/OsuPlayer.Services/ProfileManagerServiceService.cs @@ -0,0 +1,43 @@ +using OsuPlayer.Data.DataModels; +using OsuPlayer.Data.DataModels.Interfaces; +using OsuPlayer.Interfaces.Service; +using Splat; + +namespace OsuPlayer.Services; + +public class ProfileManagerServiceService : IProfileManagerService +{ + public User? User { get; set; } + + public async Task Login(string username, string password) + { + var result = await Locator.Current.GetService().LoginAndSaveAuthToken(username, password); + + // If login failed, we set the user to its default value + if (result == default) + { + User = default; + + return; + } + + User = new User(result); + } + + public async Task Login(string token) + { + if (string.IsNullOrWhiteSpace(token)) return; + + var result = await Locator.Current.GetService().LoginWithTokenAndSaveNewToken(token); + + // If login via token failed, we set the User to its default value + if (result == default) + { + User = default; + + return; + } + + User = new User(result); + } +} \ No newline at end of file diff --git a/OsuPlayer/Modules/ShuffleImpl/BalancedShuffler.cs b/OsuPlayer.Services/ShuffleImpl/BalancedShuffler.cs similarity index 88% rename from OsuPlayer/Modules/ShuffleImpl/BalancedShuffler.cs rename to OsuPlayer.Services/ShuffleImpl/BalancedShuffler.cs index ae43dc53..095cd147 100644 --- a/OsuPlayer/Modules/ShuffleImpl/BalancedShuffler.cs +++ b/OsuPlayer.Services/ShuffleImpl/BalancedShuffler.cs @@ -1,11 +1,12 @@ using OsuPlayer.Data.OsuPlayer.Enums; +using OsuPlayer.Interfaces.Service; -namespace OsuPlayer.Modules.ShuffleImpl; +namespace OsuPlayer.Services.ShuffleImpl; /// /// This shuffle algorithm performs a full reshuffle of the song list so there aren't any duplicates and biases. /// -public class BalancedShuffler : IShuffleImpl +public class BalancedShuffler : OsuPlayerService, IShuffleImpl { private readonly List _shuffledIndexes = new(); private int _currentIndex; @@ -15,6 +16,8 @@ public class BalancedShuffler : IShuffleImpl public string Name => "Balanced Shuffle"; public string Description => "Shuffles the entire list of songs so there aren't any duplicates and biases."; + public override string ServiceName => "BALANCED_SHUFFLE_SERVICE"; + public void Init(int maxRange) { if (_maxRange == maxRange) return; diff --git a/OsuPlayer/Modules/ShuffleImpl/RngHistoryShuffler.cs b/OsuPlayer.Services/ShuffleImpl/RngHistoryShuffler.cs similarity index 94% rename from OsuPlayer/Modules/ShuffleImpl/RngHistoryShuffler.cs rename to OsuPlayer.Services/ShuffleImpl/RngHistoryShuffler.cs index f40c0d18..40a19804 100644 --- a/OsuPlayer/Modules/ShuffleImpl/RngHistoryShuffler.cs +++ b/OsuPlayer.Services/ShuffleImpl/RngHistoryShuffler.cs @@ -1,13 +1,14 @@ using OsuPlayer.Data.OsuPlayer.Enums; +using OsuPlayer.Interfaces.Service; -namespace OsuPlayer.Modules.ShuffleImpl; +namespace OsuPlayer.Services.ShuffleImpl; /// /// This shuffle implementation is mostly the same as but adds a 10 depth history buffer so one /// can go back to the previous song.
/// This also has a security check to prevent the same song from being played twice in a row. ///
-public class RngHistoryShuffler : IShuffleImpl +public class RngHistoryShuffler : OsuPlayerService, IShuffleImpl { private readonly int?[] _shuffleHistory = new int?[10]; private int _currentIndex; @@ -18,10 +19,9 @@ public class RngHistoryShuffler : IShuffleImpl public string Name => "Random Shuffle (with history)"; public string Description => "Randomly picks a song from the list with a history buffer of 10 songs."; - public void Init(int maxRange) - { - _maxRange = maxRange; - } + public override string ServiceName => "RNG_HISTORY_SHUFFLE_SERVICE"; + + public void Init(int maxRange) => _maxRange = maxRange; public int DoShuffle(int currentIndex, ShuffleDirection direction) { diff --git a/OsuPlayer/Modules/ShuffleImpl/RngShuffler.cs b/OsuPlayer.Services/ShuffleImpl/RngShuffler.cs similarity index 56% rename from OsuPlayer/Modules/ShuffleImpl/RngShuffler.cs rename to OsuPlayer.Services/ShuffleImpl/RngShuffler.cs index ca9cbb48..9b4d2552 100644 --- a/OsuPlayer/Modules/ShuffleImpl/RngShuffler.cs +++ b/OsuPlayer.Services/ShuffleImpl/RngShuffler.cs @@ -1,31 +1,25 @@ using OsuPlayer.Data.OsuPlayer.Enums; using OsuPlayer.Extensions; +using OsuPlayer.Interfaces.Service; -namespace OsuPlayer.Modules.ShuffleImpl; +namespace OsuPlayer.Services.ShuffleImpl; /// /// This shuffle implementation will randomly select a song each time with no further logic. /// [DefaultImplAttr] -public class RngShuffler : IShuffleImpl +public class RngShuffler : OsuPlayerService, IShuffleImpl { private int _maxRange = -1; public string Name => "Random Shuffle"; public string Description => "Randomly select a song each time with no further logic."; - public void Init(int maxRange) - { - _maxRange = maxRange; - } + public override string ServiceName => "RNG_SHUFFLE_SERVICE"; - public int DoShuffle(int currentIndex, ShuffleDirection direction) - { - return Random.Shared.Next(_maxRange); - } + public void Init(int maxRange) => _maxRange = maxRange; - public override string ToString() - { - return Name; - } + public int DoShuffle(int currentIndex, ShuffleDirection direction) => Random.Shared.Next(_maxRange); + + public override string ToString() => Name; } \ No newline at end of file diff --git a/OsuPlayer/Modules/Services/ShuffleServiceProvider.cs b/OsuPlayer.Services/ShuffleService.cs similarity index 82% rename from OsuPlayer/Modules/Services/ShuffleServiceProvider.cs rename to OsuPlayer.Services/ShuffleService.cs index 6986d83a..d8e9dda7 100644 --- a/OsuPlayer/Modules/Services/ShuffleServiceProvider.cs +++ b/OsuPlayer.Services/ShuffleService.cs @@ -1,17 +1,20 @@ using OsuPlayer.Extensions; -using OsuPlayer.Modules.ShuffleImpl; +using OsuPlayer.Interfaces.Service; +using OsuPlayer.IO.Storage.Config; -namespace OsuPlayer.Modules.Services; +namespace OsuPlayer.Services; /// /// Provides a service for shuffle implementation registering the services using Splat. /// -public class ShuffleServiceProvider : IShuffleServiceProvider +public class ShuffleService : OsuPlayerService, IShuffleServiceProvider { public List ShuffleAlgorithms { get; } public IShuffleImpl? ShuffleImpl { get; private set; } - public ShuffleServiceProvider() + public override string ServiceName => "SHUFFLE_SERVICE"; + + public ShuffleService() { using var config = new Config(); diff --git a/OsuPlayer/Modules/Services/SortProvider.cs b/OsuPlayer.Services/SortService.cs similarity index 82% rename from OsuPlayer/Modules/Services/SortProvider.cs rename to OsuPlayer.Services/SortService.cs index daaab9fe..7431974c 100644 --- a/OsuPlayer/Modules/Services/SortProvider.cs +++ b/OsuPlayer.Services/SortService.cs @@ -1,16 +1,21 @@ using DynamicData; +using Nein.Extensions.Bindables; +using OsuPlayer.Data.DataModels; +using OsuPlayer.Data.DataModels.Interfaces; using OsuPlayer.Data.OsuPlayer.Enums; -using OsuPlayer.IO.DbReader.Interfaces; +using OsuPlayer.Interfaces.Service; -namespace OsuPlayer.Modules.Services; +namespace OsuPlayer.Services; -public class SortProvider : ISortProvider +public class SortService : OsuPlayerService, ISortProvider { public IObservable>? SortedSongs { get; set; } public Bindable SortingModeBindable { get; } = new(); public ObservableSorter SortingModeObservable { get; } = new(); - public SortProvider() + public override string ServiceName => "SORT_SERVICE"; + + public SortService() { SortingModeBindable.BindValueChanged(d => SortingModeObservable.UpdateComparer(new MapSorter(d.NewValue)), true, true); } diff --git a/OsuPlayer.Tests/IOTests/ConfigTests.cs b/OsuPlayer.Tests/IOTests/ConfigTests.cs index 73645ab8..1168279f 100644 --- a/OsuPlayer.Tests/IOTests/ConfigTests.cs +++ b/OsuPlayer.Tests/IOTests/ConfigTests.cs @@ -3,7 +3,7 @@ using NUnit.Framework; using OsuPlayer.IO.Storage.Config; -namespace OsuPlayer.Tests; +namespace OsuPlayer.Tests.IOTests; public class ConfigTests { diff --git a/OsuPlayer.Tests/IOTests/DirectoryManagerTests.cs b/OsuPlayer.Tests/IOTests/DirectoryManagerTests.cs index b6dac2cc..83df95b1 100644 --- a/OsuPlayer.Tests/IOTests/DirectoryManagerTests.cs +++ b/OsuPlayer.Tests/IOTests/DirectoryManagerTests.cs @@ -2,7 +2,7 @@ using NUnit.Framework; using OsuPlayer.IO; -namespace OsuPlayer.Tests; +namespace OsuPlayer.Tests.IOTests; public class DirectoryManagerTests { diff --git a/OsuPlayer.Tests/IOTests/PlaylistStorageTests.cs b/OsuPlayer.Tests/IOTests/PlaylistStorageTests.cs index dc4f8db8..a337f39e 100644 --- a/OsuPlayer.Tests/IOTests/PlaylistStorageTests.cs +++ b/OsuPlayer.Tests/IOTests/PlaylistStorageTests.cs @@ -6,7 +6,7 @@ using OsuPlayer.Data.OsuPlayer.StorageModels; using OsuPlayer.IO.Storage.Playlists; -namespace OsuPlayer.Tests; +namespace OsuPlayer.Tests.IOTests; public class PlaylistStorageTests { diff --git a/OsuPlayer.Tests/IOTests/PlaylistTests.cs b/OsuPlayer.Tests/IOTests/PlaylistTests.cs index 0526fda3..cb88e5e0 100644 --- a/OsuPlayer.Tests/IOTests/PlaylistTests.cs +++ b/OsuPlayer.Tests/IOTests/PlaylistTests.cs @@ -5,7 +5,7 @@ using OsuPlayer.Data.OsuPlayer.StorageModels; using OsuPlayer.IO.Storage.Playlists; -namespace OsuPlayer.Tests; +namespace OsuPlayer.Tests.IOTests; public class PlaylistTests { diff --git a/OsuPlayer.Tests/NetworkTests/OnlineTests.cs b/OsuPlayer.Tests/NetworkTests/OnlineTests.cs index 1b8803fb..4f97241d 100644 --- a/OsuPlayer.Tests/NetworkTests/OnlineTests.cs +++ b/OsuPlayer.Tests/NetworkTests/OnlineTests.cs @@ -1,9 +1,10 @@ using System; using NUnit.Framework; using OsuPlayer.Api.Data.API.Enums; -using OsuPlayer.Network.Online; +using OsuPlayer.Data.DataModels; +using OsuPlayer.Data.DataModels.Online; -namespace OsuPlayer.Tests; +namespace OsuPlayer.Tests.NetworkTests; public class OnlineTests { diff --git a/OsuPlayer.Tests/NetworkTests/PasswordManagerTests.cs b/OsuPlayer.Tests/NetworkTests/PasswordManagerTests.cs index 730789c1..3fda91e2 100644 --- a/OsuPlayer.Tests/NetworkTests/PasswordManagerTests.cs +++ b/OsuPlayer.Tests/NetworkTests/PasswordManagerTests.cs @@ -1,7 +1,7 @@ using NUnit.Framework; using OsuPlayer.Network.Security; -namespace OsuPlayer.Tests; +namespace OsuPlayer.Tests.NetworkTests; public class PasswordManagerTests { diff --git a/OsuPlayer.Tests/ValueConverterTests/GridFormatterTests.cs b/OsuPlayer.Tests/ValueConverterTests/GridFormatterTests.cs index eaa32f71..9b2990eb 100644 --- a/OsuPlayer.Tests/ValueConverterTests/GridFormatterTests.cs +++ b/OsuPlayer.Tests/ValueConverterTests/GridFormatterTests.cs @@ -3,7 +3,7 @@ using NUnit.Framework; using OsuPlayer.Extensions.ValueConverters; -namespace OsuPlayer.Tests; +namespace OsuPlayer.Tests.ValueConverterTests; public class GridFormatterTests { diff --git a/OsuPlayer.Tests/ValueConverterTests/PlayPauseConverterTests.cs b/OsuPlayer.Tests/ValueConverterTests/PlayPauseConverterTests.cs index 42707937..a5644909 100644 --- a/OsuPlayer.Tests/ValueConverterTests/PlayPauseConverterTests.cs +++ b/OsuPlayer.Tests/ValueConverterTests/PlayPauseConverterTests.cs @@ -4,7 +4,7 @@ using NUnit.Framework; using OsuPlayer.Extensions.ValueConverters; -namespace OsuPlayer.Tests; +namespace OsuPlayer.Tests.ValueConverterTests; public class PlayPauseConverterTests { diff --git a/OsuPlayer.Tests/ValueConverterTests/RepeatConverterTests.cs b/OsuPlayer.Tests/ValueConverterTests/RepeatConverterTests.cs index 55b5bf41..fbf0fa3f 100644 --- a/OsuPlayer.Tests/ValueConverterTests/RepeatConverterTests.cs +++ b/OsuPlayer.Tests/ValueConverterTests/RepeatConverterTests.cs @@ -5,7 +5,7 @@ using OsuPlayer.Data.OsuPlayer.Enums; using OsuPlayer.Extensions.ValueConverters; -namespace OsuPlayer.Tests; +namespace OsuPlayer.Tests.ValueConverterTests; public class RepeatConverterTests { diff --git a/OsuPlayer.Tests/ValueConverterTests/SettingsUserConverterTests.cs b/OsuPlayer.Tests/ValueConverterTests/SettingsUserConverterTests.cs index 0e07bf6c..8673115b 100644 --- a/OsuPlayer.Tests/ValueConverterTests/SettingsUserConverterTests.cs +++ b/OsuPlayer.Tests/ValueConverterTests/SettingsUserConverterTests.cs @@ -1,10 +1,10 @@ using System; using System.Globalization; using NUnit.Framework; +using OsuPlayer.Data.DataModels; using OsuPlayer.Extensions.ValueConverters; -using OsuPlayer.Network.Online; -namespace OsuPlayer.Tests; +namespace OsuPlayer.Tests.ValueConverterTests; public class SettingsUserConverterTests { @@ -47,11 +47,11 @@ public void TestOutputOnNullInput() // { // Name = name // }; - // + // // Assert.IsInstanceOf(_expectedInput, input); - // + // // var output = _userConverter.Convert(input, _expectedOutput, null, CultureInfo.InvariantCulture); - // + // // Assert.IsInstanceOf(_expectedOutput, output); // Assert.AreEqual(input.Name, output); // } diff --git a/OsuPlayer.Tests/ValueConverterTests/ShuffleConverterTests.cs b/OsuPlayer.Tests/ValueConverterTests/ShuffleConverterTests.cs index 73796d67..eb6e6e6a 100644 --- a/OsuPlayer.Tests/ValueConverterTests/ShuffleConverterTests.cs +++ b/OsuPlayer.Tests/ValueConverterTests/ShuffleConverterTests.cs @@ -4,7 +4,7 @@ using NUnit.Framework; using OsuPlayer.Extensions.ValueConverters; -namespace OsuPlayer.Tests; +namespace OsuPlayer.Tests.ValueConverterTests; public class ShuffleConverterTests { diff --git a/OsuPlayer.Tests/ValueConverterTests/SourceListValueConverterTests.cs b/OsuPlayer.Tests/ValueConverterTests/SourceListValueConverterTests.cs index 5168008c..9ef17cd1 100644 --- a/OsuPlayer.Tests/ValueConverterTests/SourceListValueConverterTests.cs +++ b/OsuPlayer.Tests/ValueConverterTests/SourceListValueConverterTests.cs @@ -6,7 +6,7 @@ using OsuPlayer.Data.OsuPlayer.StorageModels; using OsuPlayer.Extensions.ValueConverters; -namespace OsuPlayer.Tests; +namespace OsuPlayer.Tests.ValueConverterTests; public class SourceListValueConverterTests { diff --git a/OsuPlayer.Tests/ValueConverterTests/VolumeConverterTests.cs b/OsuPlayer.Tests/ValueConverterTests/VolumeConverterTests.cs index 4a2ef4e9..7f9ccbf8 100644 --- a/OsuPlayer.Tests/ValueConverterTests/VolumeConverterTests.cs +++ b/OsuPlayer.Tests/ValueConverterTests/VolumeConverterTests.cs @@ -4,7 +4,7 @@ using NUnit.Framework; using OsuPlayer.Extensions.ValueConverters; -namespace OsuPlayer.Tests; +namespace OsuPlayer.Tests.ValueConverterTests; public class VolumeConverterTests { diff --git a/OsuPlayer.sln b/OsuPlayer.sln index cefb97e3..76f1fb48 100644 --- a/OsuPlayer.sln +++ b/OsuPlayer.sln @@ -23,6 +23,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{0EC9D629 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OsuPlayer.CrashHandler", "OsuPlayer.CrashHandler\OsuPlayer.CrashHandler.csproj", "{99497585-DB09-4427-8813-88CE41BB392E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OsuPlayer.Services", "OsuPlayer.Services\OsuPlayer.Services.csproj", "{704B3DBB-458D-4C83-818D-3BE403AA433B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OsuPlayer.Interfaces", "OsuPlayer.Interfaces\OsuPlayer.Interfaces.csproj", "{959F77A7-DC5D-4C01-8291-2660683C099C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -61,6 +65,14 @@ Global {99497585-DB09-4427-8813-88CE41BB392E}.Debug|Any CPU.Build.0 = Debug|Any CPU {99497585-DB09-4427-8813-88CE41BB392E}.Release|Any CPU.ActiveCfg = Release|Any CPU {99497585-DB09-4427-8813-88CE41BB392E}.Release|Any CPU.Build.0 = Release|Any CPU + {704B3DBB-458D-4C83-818D-3BE403AA433B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {704B3DBB-458D-4C83-818D-3BE403AA433B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {704B3DBB-458D-4C83-818D-3BE403AA433B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {704B3DBB-458D-4C83-818D-3BE403AA433B}.Release|Any CPU.Build.0 = Release|Any CPU + {959F77A7-DC5D-4C01-8291-2660683C099C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {959F77A7-DC5D-4C01-8291-2660683C099C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {959F77A7-DC5D-4C01-8291-2660683C099C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {959F77A7-DC5D-4C01-8291-2660683C099C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {0034AEFA-ED66-47FA-AA37-1AB01F371AAF} = {AF8CA35D-63B3-4B8B-95B1-4717C57ED0E1} @@ -69,5 +81,7 @@ Global {5C528D33-1973-412A-8068-8397B7BE5897} = {AF8CA35D-63B3-4B8B-95B1-4717C57ED0E1} {D428D0A2-A166-4522-98CF-6365261E2C8F} = {AF8CA35D-63B3-4B8B-95B1-4717C57ED0E1} {BA344BAD-2E55-4617-B2E0-926D67D8BBA8} = {0EC9D629-BCAE-4603-9ACA-8479877FA198} + {704B3DBB-458D-4C83-818D-3BE403AA433B} = {AF8CA35D-63B3-4B8B-95B1-4717C57ED0E1} + {959F77A7-DC5D-4C01-8291-2660683C099C} = {AF8CA35D-63B3-4B8B-95B1-4717C57ED0E1} EndGlobalSection EndGlobal diff --git a/OsuPlayer/Modules/Audio/Engine/BassEngine.cs b/OsuPlayer/Modules/Audio/Engine/BassEngine.cs index 980c7cda..72ad08cc 100644 --- a/OsuPlayer/Modules/Audio/Engine/BassEngine.cs +++ b/OsuPlayer/Modules/Audio/Engine/BassEngine.cs @@ -4,16 +4,30 @@ using ManagedBass.DirectX8; using ManagedBass.Fx; using OsuPlayer.Data.OsuPlayer.Classes; +using OsuPlayer.Interfaces.Service; using OsuPlayer.IO.Storage.Equalizer; using OsuPlayer.Modules.Audio.Interfaces; +using OsuPlayer.Services; namespace OsuPlayer.Modules.Audio.Engine; /// /// Audio engine for the osu!player using /// -public sealed class BassEngine : IAudioEngine +public sealed class BassEngine : OsuPlayerService, IAudioEngine { + public override string ServiceName => "BASS_ENGINE"; + + public List AvailableAudioDevices { get; } = new(); + public IAudioEngine.ChannelReachedEndHandler? ChannelReachedEnd { private get; set; } + public BindableArray EqGains { get; } = new(10, 1); + + public Bindable ChannelLength { get; } = new(); + public Bindable ChannelPosition { get; } = new(); + public Bindable Volume { get; } = new(); + public Bindable IsPlaying { get; } = new(); + public Bindable PlaybackSpeed { get; } = new(); + private readonly SyncProcedure _endTrackSyncProc; private readonly DispatcherTimer _positionTimer = new(DispatcherPriority.ApplicationIdle); @@ -27,16 +41,6 @@ public sealed class BassEngine : IAudioEngine private double _playbackSpeed; private int _sampleFrequency = 44100; - public List AvailableAudioDevices { get; } = new(); - public IAudioEngine.ChannelReachedEndHandler? ChannelReachedEnd { private get; set; } - public BindableArray EqGains { get; } = new(10, 1); - - public Bindable ChannelLength { get; } = new(); - public Bindable ChannelPosition { get; } = new(); - public Bindable Volume { get; } = new(); - public Bindable IsPlaying { get; } = new(); - public Bindable PlaybackSpeed { get; } = new(); - public bool IsEqEnabled { get => _isEqEnabled; @@ -50,7 +54,7 @@ public bool IsEqEnabled } } - public BassEngine() + public BassEngine() : base() { _positionTimer.Interval = TimeSpan.FromMilliseconds((double) 1000 / 60); _positionTimer.Tick += PositionTimer_Tick; @@ -193,7 +197,7 @@ public void SetDevice(AudioDevice? audioDevice) using var config = new Config(); config.Container.SelectedAudioDeviceDriver = audioDevices[index].Driver; - Console.WriteLine($"[BASSENGINE] DEVICE {index} | LOADING PLAYBACK {Bass.LastError}"); + LogToConsole($"DEVICE {index} | LOADING PLAYBACK {Bass.LastError}"); } private void SetPlaybackSpeedOptions(double speed) @@ -232,7 +236,7 @@ private void InitAudioDevices() { var success = Bass.Init(counter); - Console.WriteLine($"[BASSENGINE] INIT DEVICE {deviceInfo} | SUCCESSFUL: {success} | CODE: {Bass.LastError}"); + LogToConsole($"INIT DEVICE {deviceInfo} | SUCCESSFUL: {success} | CODE: {Bass.LastError}"); if (success) AvailableAudioDevices.Add(deviceInfo); @@ -346,6 +350,7 @@ private IEnumerable GetAudioDevices() private void PositionTimer_Tick(object sender, EventArgs e) { if (!IsPlaying.Value) return; + if (_fxStream == 0) { ChannelPosition.Value = 0; diff --git a/OsuPlayer/Modules/Audio/Interfaces/IPlayer.cs b/OsuPlayer/Modules/Audio/Interfaces/IPlayer.cs index 3b468c8e..7e9d1e7a 100644 --- a/OsuPlayer/Modules/Audio/Interfaces/IPlayer.cs +++ b/OsuPlayer/Modules/Audio/Interfaces/IPlayer.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; +using OsuPlayer.Data.DataModels.Interfaces; using OsuPlayer.Data.OsuPlayer.Enums; -using OsuPlayer.IO.DbReader.Interfaces; using OsuPlayer.IO.Importer; namespace OsuPlayer.Modules.Audio.Interfaces; diff --git a/OsuPlayer/Modules/Audio/Player.cs b/OsuPlayer/Modules/Audio/Player.cs index cc813c08..5824eb0a 100644 --- a/OsuPlayer/Modules/Audio/Player.cs +++ b/OsuPlayer/Modules/Audio/Player.cs @@ -6,19 +6,19 @@ using Nein.Extensions; using Nein.Extensions.Exceptions; using OsuPlayer.Api.Data.API.Enums; +using OsuPlayer.Data.DataModels.Extensions; +using OsuPlayer.Data.DataModels.Interfaces; using OsuPlayer.Data.OsuPlayer.Classes; using OsuPlayer.Data.OsuPlayer.Enums; using OsuPlayer.Data.OsuPlayer.StorageModels; -using OsuPlayer.IO.DbReader.DataModels.Extensions; -using OsuPlayer.IO.DbReader.Interfaces; +using OsuPlayer.Interfaces.Service; using OsuPlayer.IO.Importer; using OsuPlayer.IO.Storage.Blacklist; using OsuPlayer.IO.Storage.Playlists; using OsuPlayer.Modules.Audio.Engine; using OsuPlayer.Modules.Audio.Interfaces; -using OsuPlayer.Modules.Services; using OsuPlayer.Network.Discord; -using OsuPlayer.Network.LastFM; +using OsuPlayer.Network.LastFm; namespace OsuPlayer.Modules.Audio; @@ -35,7 +35,7 @@ public class Player : IPlayer, IImportNotifications private readonly IStatisticsProvider? _statisticsProvider; private readonly IHistoryProvider? _historyProvider; private readonly WindowsMediaTransportControls? _winMediaControls; - private readonly LastFmApi? _lastFmApi; + private readonly ILastFmApiService? _lastFmApi; private bool _isMuted; private double _oldVolume; @@ -53,7 +53,7 @@ public class Player : IPlayer, IImportNotifications public Bindable RepeatMode { get; } = new(); public BindableList History { get; } = new(); - + public List AvailableAudioDevices => _audioEngine.AvailableAudioDevices; public BindableArray EqGains => _audioEngine.EqGains; public Bindable Volume => _audioEngine.Volume; @@ -70,7 +70,8 @@ public bool IsEqEnabled private List ActivePlaylistSongs { get; set; } public Player(IAudioEngine audioEngine, ISongSourceProvider songSourceProvider, IShuffleServiceProvider? shuffleProvider = null, - IStatisticsProvider? statisticsProvider = null, ISortProvider? sortProvider = null, IHistoryProvider? historyProvider = null, LastFmApi? lastFmApi = null) + IStatisticsProvider? statisticsProvider = null, ISortProvider? sortProvider = null, IHistoryProvider? historyProvider = null, + ILastFmApiService? lastFmApi = null) { _audioEngine = audioEngine; @@ -153,7 +154,7 @@ private void OnRepeatModeChanged(ValueChangedEvent repeatMode) private void OnCurrentSongChanged(ValueChangedEvent mapEntry) { _historyProvider?.AddOrUpdateMapEntry(mapEntry.NewValue); - + using var cfg = new Config(); cfg.Container.LastPlayedSong = mapEntry.NewValue?.Hash; @@ -163,7 +164,7 @@ private void OnCurrentSongChanged(ValueChangedEvent mapEntry) if (mapEntry.NewValue is null) return; var timestamp = TimeSpan.FromSeconds(_audioEngine.ChannelLength.Value * (1 - _audioEngine.PlaybackSpeed.Value)); - + _discordClient?.UpdatePresence(mapEntry.NewValue.Title, $"by {mapEntry.NewValue.Artist}", mapEntry.NewValue.BeatmapSetId, durationLeft: timestamp); } @@ -219,11 +220,11 @@ public void TriggerBlacklistChanged(PropertyChangedEventArgs e) public void SetPlaybackSpeed(double speed) { _audioEngine.SetPlaybackSpeed(speed); - - if(!_audioEngine.IsPlaying.Value) return; - + + if (!_audioEngine.IsPlaying.Value) return; + var timestamp = TimeSpan.FromSeconds(_audioEngine.ChannelLength.Value * (1 - _audioEngine.PlaybackSpeed.Value) - _audioEngine.ChannelPosition.Value); - + _discordClient?.UpdatePresence(CurrentSong.Value.Title, $"by {CurrentSong.Value.Artist}", CurrentSong.Value.BeatmapSetId, durationLeft: timestamp); } @@ -259,9 +260,9 @@ public void Play() _currentSongTimer.Start(); _winMediaControls?.UpdatePlayingStatus(true); - + var timestamp = TimeSpan.FromSeconds(_audioEngine.ChannelLength.Value * (1 - _audioEngine.PlaybackSpeed.Value) - _audioEngine.ChannelPosition.Value); - + _discordClient?.UpdatePresence(CurrentSong.Value.Title, $"by {CurrentSong.Value.Artist}", CurrentSong.Value.BeatmapSetId, durationLeft: timestamp); } @@ -271,7 +272,7 @@ public void Pause() _currentSongTimer.Stop(); _winMediaControls?.UpdatePlayingStatus(false); - + _discordClient?.UpdatePresence(CurrentSong.Value.Title, $"by {CurrentSong.Value.Artist}", CurrentSong.Value.BeatmapSetId); } @@ -326,7 +327,6 @@ public async Task TryPlaySongAsync(IMapEntryBase? song, PlayDirection playDirect { if (SongSourceProvider.SongSourceList == default || !SongSourceProvider.SongSourceList.Any()) throw new NullOrEmptyException($"{nameof(SongSourceProvider.SongSourceList)} can't be null or empty"); - if (song == default) { await TryStartSongAsync(SongSourceProvider.SongSourceList[0]); @@ -427,7 +427,7 @@ private async Task TryStartSongAsync(IMapEntryBase song) await using var config = new Config(); - var fullMapEntry = await song.ReadFullEntry(); + var fullMapEntry = song.ReadFullEntry(); if (fullMapEntry == default) throw new NullReferenceException(); @@ -485,7 +485,7 @@ private async Task TryStartSongAsync(IMapEntryBase song) { if (!config.Container.EnableScrobbling) return; - + await _lastFmApi?.Scrobble(CurrentSong.Value.Title, CurrentSong.Value.Artist)!; } catch (Exception e) diff --git a/OsuPlayer/Modules/Audio/WindowsMediaTransportControls.cs b/OsuPlayer/Modules/Audio/WindowsMediaTransportControls.cs index 449b1cb7..118b9da1 100644 --- a/OsuPlayer/Modules/Audio/WindowsMediaTransportControls.cs +++ b/OsuPlayer/Modules/Audio/WindowsMediaTransportControls.cs @@ -2,8 +2,8 @@ using Windows.Media.Playback; using Windows.Storage; using Windows.Storage.Streams; +using OsuPlayer.Data.DataModels.Interfaces; using OsuPlayer.Data.OsuPlayer.Enums; -using OsuPlayer.IO.DbReader.Interfaces; using OsuPlayer.Modules.Audio.Interfaces; namespace OsuPlayer.Modules.Audio; diff --git a/OsuPlayer/Modules/Services/HistoryProvider.cs b/OsuPlayer/Modules/Services/HistoryProvider.cs deleted file mode 100644 index 0372cd80..00000000 --- a/OsuPlayer/Modules/Services/HistoryProvider.cs +++ /dev/null @@ -1,36 +0,0 @@ -using OsuPlayer.IO.DbReader.DataModels.Extensions; -using OsuPlayer.IO.DbReader.Interfaces; -using OsuPlayer.Modules.Audio.Interfaces; - -namespace OsuPlayer.Modules.Services; - -public class HistoryProvider : IHistoryProvider -{ - public HistoricalMapEntryComparer Comparer { get; } - public BindableList History { get; } - - public HistoryProvider() - { - Comparer = new HistoricalMapEntryComparer(); - History = new BindableList(); - } - - public void AddOrUpdateMapEntry(IMapEntryBase? mapEntry) - { - if (mapEntry == default) return; - - var historicalMapEntry = new HistoricalMapEntry(mapEntry); - - var foundEntry = History.FirstOrDefault(entry => Comparer.Equals(entry, historicalMapEntry)); - - if (foundEntry != default) - History.Remove(foundEntry); - - History.Add(historicalMapEntry); - } - - public void ClearHistory() - { - History.Clear(); - } -} \ No newline at end of file diff --git a/OsuPlayer/OsuPlayer.csproj b/OsuPlayer/OsuPlayer.csproj index bc7c3f38..4693d490 100644 --- a/OsuPlayer/OsuPlayer.csproj +++ b/OsuPlayer/OsuPlayer.csproj @@ -57,8 +57,10 @@ + + @@ -146,16 +148,16 @@ BeatmapsView.axaml - ExportSongsView.axaml + ExportSongsView.axaml - ExportSongsProcessWindow.axaml + ExportSongsProcessWindow.axaml - PlayHistoryView.axaml + PlayHistoryView.axaml - PlayHistoryView.axaml + PlayHistoryView.axaml diff --git a/OsuPlayer/Program.cs b/OsuPlayer/Program.cs index e55588b2..640f6434 100644 --- a/OsuPlayer/Program.cs +++ b/OsuPlayer/Program.cs @@ -3,13 +3,15 @@ using Avalonia.Platform; using Avalonia.ReactiveUI; using Nein.Extensions; +using OsuPlayer.Data.DataModels; using OsuPlayer.Extensions; +using OsuPlayer.Interfaces.Service; using OsuPlayer.IO.Importer; using OsuPlayer.Modules.Audio.Engine; using OsuPlayer.Modules.Audio.Interfaces; -using OsuPlayer.Modules.Services; -using OsuPlayer.Network.API.Service.NorthFox.Endpoints; -using OsuPlayer.Network.LastFM; +using OsuPlayer.Network.API.NorthFox; +using OsuPlayer.Network.LastFm; +using OsuPlayer.Services; using OsuPlayer.Windows; using Splat; @@ -71,16 +73,21 @@ private static AppBuilder BuildAvaloniaApp() private static void Register(IMutableDependencyResolver services, IReadonlyDependencyResolver resolver, IRuntimePlatform platform) { + services.RegisterLazySingleton(() => new LoggingService()); + services.RegisterLazySingleton(() => new BassEngine()); - services.RegisterLazySingleton(() => new ShuffleServiceProvider()); - services.RegisterLazySingleton(() => new ApiStatisticsProvider()); - services.RegisterLazySingleton(() => new SortProvider()); - services.RegisterLazySingleton(() => new OsuSongSourceProvider(resolver.GetService())); - services.RegisterLazySingleton(() => new HistoryProvider()); - services.RegisterLazySingleton(() => new LastFmApi()); + services.RegisterLazySingleton(() => new DbReaderFactory()); + + services.RegisterLazySingleton(() => new ProfileManagerServiceService()); + services.RegisterLazySingleton(() => new ShuffleService()); + services.RegisterLazySingleton(() => new ApiStatisticsService(resolver.GetService())); + services.RegisterLazySingleton(() => new SortService()); + services.RegisterLazySingleton(() => new OsuSongSourceService(resolver.GetService())); + services.RegisterLazySingleton(() => new HistoryService()); + services.RegisterLazySingleton(() => new LastFmService(new LastFmApi())); - services.RegisterLazySingleton(() => new NorthFox()); + services.RegisterLazySingleton(() => new NorthFox()); services.RegisterLazySingleton(() => new Player( audioEngine: resolver.GetRequiredService(), @@ -89,18 +96,20 @@ private static void Register(IMutableDependencyResolver services, IReadonlyDepen statisticsProvider: resolver.GetRequiredService(), sortProvider: resolver.GetRequiredService(), historyProvider: resolver.GetRequiredService(), - lastFmApi: resolver.GetRequiredService() + lastFmApi: resolver.GetRequiredService() )); services.Register(() => new MainWindowViewModel( resolver.GetRequiredService(), resolver.GetRequiredService(), + resolver.GetRequiredService(), resolver.GetService(), resolver.GetService(), resolver.GetService(), resolver.GetService())); services.RegisterLazySingleton(() => new MainWindow( - resolver.GetRequiredService())); + resolver.GetRequiredService(), + resolver.GetRequiredService())); } } \ No newline at end of file diff --git a/OsuPlayer/Usings.cs b/OsuPlayer/Usings.cs index 645750a8..ee62d37c 100644 --- a/OsuPlayer/Usings.cs +++ b/OsuPlayer/Usings.cs @@ -4,10 +4,8 @@ global using System.Collections.Generic; global using System.Collections.ObjectModel; global using Nein.Extensions.Bindables; -global using OsuPlayer.IO.DbReader.DataModels; global using OsuPlayer.IO.Storage.Config; global using OsuPlayer.Modules.Audio; -global using OsuPlayer.Network.Online; using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("OsuPlayer.Tests")] \ No newline at end of file diff --git a/OsuPlayer/Views/BeatmapsViewModel.cs b/OsuPlayer/Views/BeatmapsViewModel.cs index 409c5c78..80f133f4 100644 --- a/OsuPlayer/Views/BeatmapsViewModel.cs +++ b/OsuPlayer/Views/BeatmapsViewModel.cs @@ -6,8 +6,9 @@ using OsuPlayer.Api.Data.API.Enums; using OsuPlayer.Api.Data.API.RequestModels.Beatmap; using OsuPlayer.Api.Data.API.ResponseModels; +using OsuPlayer.Interfaces.Service; using OsuPlayer.Modules.Audio.Interfaces; -using OsuPlayer.Network.API.Service.NorthFox.Endpoints; +using OsuPlayer.Network.API.NorthFox; using ReactiveUI; using Splat; @@ -133,14 +134,14 @@ private async void Block(CompositeDisposable disposables) // If we have beatmaps already loaded and reload the view we don't want to load them again if (Beatmaps?.Count > 0) return; - var api = Locator.Current.GetService(); - await SearchBeatmaps(); } public async Task SearchBeatmaps(int newPage = 1, int pageSize = 64) { - var api = Locator.Current.GetService(); + var api = Locator.Current.GetService() as NorthFox; + + if (api == default) return new (); SearchingBeatmaps = true; diff --git a/OsuPlayer/Views/BlacklistEditorView.axaml b/OsuPlayer/Views/BlacklistEditorView.axaml index 445530eb..8ec2e53b 100644 --- a/OsuPlayer/Views/BlacklistEditorView.axaml +++ b/OsuPlayer/Views/BlacklistEditorView.axaml @@ -5,8 +5,7 @@ xmlns:valueConverters="clr-namespace:OsuPlayer.ValueConverters" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:views="clr-namespace:OsuPlayer.Views" - xmlns:dataModels="clr-namespace:OsuPlayer.IO.DbReader.DataModels;assembly=OsuPlayer.IO" - xmlns:interfaces="clr-namespace:OsuPlayer.IO.DbReader.Interfaces;assembly=OsuPlayer.IO" + xmlns:interfaces="clr-namespace:OsuPlayer.Data.DataModels.Interfaces;assembly=OsuPlayer.Data" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="OsuPlayer.Views.BlacklistEditorView" FontWeight="{DynamicResource DefaultFontWeight}"> diff --git a/OsuPlayer/Views/BlacklistEditorView.axaml.cs b/OsuPlayer/Views/BlacklistEditorView.axaml.cs index fb1ffb05..a85fae6c 100644 --- a/OsuPlayer/Views/BlacklistEditorView.axaml.cs +++ b/OsuPlayer/Views/BlacklistEditorView.axaml.cs @@ -4,7 +4,7 @@ using Avalonia.Markup.Xaml; using Avalonia.VisualTree; using Nein.Base; -using OsuPlayer.IO.DbReader.Interfaces; +using OsuPlayer.Data.DataModels.Interfaces; using OsuPlayer.IO.Storage.Blacklist; using OsuPlayer.Windows; using ReactiveUI; diff --git a/OsuPlayer/Views/BlacklistEditorViewModel.cs b/OsuPlayer/Views/BlacklistEditorViewModel.cs index 13437e6a..d7ece5e2 100644 --- a/OsuPlayer/Views/BlacklistEditorViewModel.cs +++ b/OsuPlayer/Views/BlacklistEditorViewModel.cs @@ -3,8 +3,8 @@ using Avalonia.Threading; using DynamicData; using Nein.Base; +using OsuPlayer.Data.DataModels.Interfaces; using OsuPlayer.Data.OsuPlayer.StorageModels; -using OsuPlayer.IO.DbReader.Interfaces; using OsuPlayer.IO.Storage.Blacklist; using OsuPlayer.Modules.Audio.Interfaces; using ReactiveUI; diff --git a/OsuPlayer/Views/EditUserView.axaml.cs b/OsuPlayer/Views/EditUserView.axaml.cs index 6898b41f..3785cdde 100644 --- a/OsuPlayer/Views/EditUserView.axaml.cs +++ b/OsuPlayer/Views/EditUserView.axaml.cs @@ -8,7 +8,10 @@ using Avalonia.VisualTree; using Nein.Extensions; using OsuPlayer.Api.Data.API.RequestModels.User; -using OsuPlayer.Network.API.Service.NorthFox.Endpoints; +using OsuPlayer.Data.DataModels; +using OsuPlayer.Interfaces.Service; +using OsuPlayer.Network.API.NorthFox; +using OsuPlayer.Services; using OsuPlayer.UI_Extensions; using OsuPlayer.Windows; using ReactiveUI; @@ -19,9 +22,16 @@ namespace OsuPlayer.Views; public partial class EditUserView : ReactiveUserControl { private MainWindow? _mainWindow; + private readonly IProfileManagerService _profileManager; - public EditUserView() + public EditUserView() : this(Locator.Current.GetService()) { + } + + public EditUserView(IProfileManagerService profileManager) + { + _profileManager = profileManager; + InitializeComponent(); } @@ -135,7 +145,7 @@ private async void SaveChanges_OnClick(object? sender, RoutedEventArgs e) { if (_mainWindow == default || ViewModel?.CurrentUser == default || string.IsNullOrWhiteSpace(ViewModel?.CurrentUser.Name)) return; - var api = Locator.Current.GetService(); + if (Locator.Current.GetService() is not NorthFox api) return; var tempUser = await api.User.GetUserFromLoginToken(); @@ -192,7 +202,7 @@ await MessageBox.ShowDialogAsync(_mainWindow, if (ViewModel == null) { if (!string.IsNullOrWhiteSpace(editUserModel.User.Name)) - ProfileManager.User = (await api.User.GetUserFromLoginToken())?.ConvertObjectToJson(); + _profileManager.User = (await api.User.GetUserFromLoginToken())?.ConvertObjectToJson(); if (changedProfilePicture) await MessageBox.ShowDialogAsync(_mainWindow, @@ -203,7 +213,7 @@ await MessageBox.ShowDialogAsync(_mainWindow, ViewModel.NewPassword = string.Empty; ViewModel.Password = string.Empty; - ProfileManager.User = ViewModel.CurrentUser.ConvertObjectToJson(); + _profileManager.User = ViewModel.CurrentUser.ConvertObjectToJson(); var successMessage = "Profile updated successfully!"; @@ -223,7 +233,7 @@ private async Task UpdateProfilePicture() { if (_mainWindow == default || ViewModel?.CurrentUser == default || ViewModel?.CurrentProfilePicture == default) return; - var api = Locator.Current.GetService(); + if (Locator.Current.GetService() is not NorthFox api) return; await using var stream = new MemoryStream(); @@ -265,7 +275,7 @@ private async void PreviewBanner_OnClick(object? sender, RoutedEventArgs e) { if (ViewModel?.CurrentUser == default) return; - var banner = await Locator.Current.GetService().User.GetProfileBannerAsync(ViewModel.CurrentUser.CustomBannerUrl); + var banner = await Locator.Current.GetService().User.GetProfileBannerAsync(ViewModel.CurrentUser.CustomBannerUrl); if (banner == default) return; @@ -276,7 +286,7 @@ private async void ResetBanner_OnClick(object? sender, RoutedEventArgs e) { if (ViewModel?.CurrentUser == default || string.IsNullOrWhiteSpace(ViewModel.CurrentUser.Name)) return; - var api = Locator.Current.GetService(); + var api = Locator.Current.GetService(); var user = await api.User.GetUserFromLoginToken(); @@ -303,7 +313,7 @@ private async void ResetBanner_OnClick(object? sender, RoutedEventArgs e) private async void ConfirmDeleteProfile_OnClick(object? sender, RoutedEventArgs e) { - if (_mainWindow?.ViewModel == default || ProfileManager.User == default || ViewModel == default) return; + if (_mainWindow?.ViewModel == default || _profileManager.User == default || ViewModel == default) return; if (string.IsNullOrWhiteSpace(ViewModel.ConfirmDeletionPassword)) { @@ -312,7 +322,7 @@ private async void ConfirmDeleteProfile_OnClick(object? sender, RoutedEventArgs return; } - var response = await Locator.Current.GetService().User.DeleteUser(); + var response = await Locator.Current.GetService().User.DeleteUser(); if (!response) { @@ -321,7 +331,7 @@ private async void ConfirmDeleteProfile_OnClick(object? sender, RoutedEventArgs return; } - ProfileManager.User = default; + _profileManager.User = default; await MessageBox.ShowDialogAsync(_mainWindow, "Profile deleted!\n\nSee you next time!"); diff --git a/OsuPlayer/Views/EditUserViewModel.cs b/OsuPlayer/Views/EditUserViewModel.cs index 8f024206..c05dc359 100644 --- a/OsuPlayer/Views/EditUserViewModel.cs +++ b/OsuPlayer/Views/EditUserViewModel.cs @@ -7,7 +7,9 @@ using Nein.Extensions; using OsuPlayer.Api.Data.API.EntityModels; using OsuPlayer.Api.Data.API.RequestModels.Statistics; -using OsuPlayer.Network.API.Service.NorthFox.Endpoints; +using OsuPlayer.Interfaces.Service; +using OsuPlayer.Network.API.NorthFox; +using OsuPlayer.Services; using ReactiveUI; using Splat; @@ -15,6 +17,8 @@ namespace OsuPlayer.Views; public class EditUserViewModel : BaseViewModel { + private readonly IProfileManagerService _profileManager; + private CancellationTokenSource? _bannerCancellationTokenSource; private string _confirmDeletionPassword = string.Empty; private Bitmap? _currentProfileBanner; @@ -24,8 +28,8 @@ public class EditUserViewModel : BaseViewModel private bool _isDeleteProfilePopupOpen; private bool _isNewBannerSelected; private bool _isNewProfilePictureSelected; - private string _newPassword = string.Empty; - private string _newUsername = ProfileManager.User?.Name ?? string.Empty; + private string? _newPassword; + private string? _newUsername; private string _password = string.Empty; private CancellationTokenSource? _profilePictureCancellationTokenSource; private CancellationTokenSource? _topSongsCancellationTokenSource; @@ -106,18 +110,20 @@ public string NewPassword public string NewUsername { - get => _newUsername; + get => _newUsername ??= _profileManager.User?.Name ?? string.Empty; set => this.RaiseAndSetIfChanged(ref _newUsername, value); } - public EditUserViewModel() + public EditUserViewModel(IProfileManagerService profileManager) { + _profileManager = profileManager; + Activator = new ViewModelActivator(); this.WhenActivated(disposables => { Disposable.Create(() => { }).DisposeWith(disposables); - CurrentUser = ProfileManager.User; + CurrentUser = _profileManager.User; }); } @@ -139,7 +145,11 @@ private async void LoadTopSongs() if (cancellationToken.IsCancellationRequested) cancellationToken.ThrowIfCancellationRequested(); - var stats = await Locator.Current.GetService().Beatmap.GetBeatmapsPlayedByUser(CurrentUser.UniqueId); + var api = Locator.Current.GetService() as NorthFox; + + if (api == default) return; + + var stats = await api.Beatmap.GetBeatmapsPlayedByUser(CurrentUser.UniqueId); if (cancellationToken.IsCancellationRequested) cancellationToken.ThrowIfCancellationRequested(); @@ -171,7 +181,7 @@ public async void LoadProfilePicture() if (cancellationToken.IsCancellationRequested) cancellationToken.ThrowIfCancellationRequested(); - var profilePicture = await Locator.Current.GetService().User.GetProfilePictureAsync(CurrentUser.UniqueId); + var profilePicture = await Locator.Current.GetService().User.GetProfilePictureAsync(CurrentUser.UniqueId); if (cancellationToken.IsCancellationRequested) cancellationToken.ThrowIfCancellationRequested(); @@ -203,7 +213,7 @@ public async void LoadProfileBanner() if (cancellationToken.IsCancellationRequested) cancellationToken.ThrowIfCancellationRequested(); - var banner = await Locator.Current.GetService().User.GetProfileBannerAsync(CurrentUser.CustomBannerUrl); + var banner = await Locator.Current.GetService().User.GetProfileBannerAsync(CurrentUser.CustomBannerUrl); if (cancellationToken.IsCancellationRequested) cancellationToken.ThrowIfCancellationRequested(); diff --git a/OsuPlayer/Views/ExportSongsView.axaml b/OsuPlayer/Views/ExportSongsView.axaml index b820dff8..98e5716b 100644 --- a/OsuPlayer/Views/ExportSongsView.axaml +++ b/OsuPlayer/Views/ExportSongsView.axaml @@ -3,8 +3,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:views="clr-namespace:OsuPlayer.Views" - xmlns:dataModels="clr-namespace:OsuPlayer.IO.DbReader.DataModels;assembly=OsuPlayer.IO" - xmlns:interfaces="clr-namespace:OsuPlayer.IO.DbReader.Interfaces;assembly=OsuPlayer.IO" + xmlns:interfaces="clr-namespace:OsuPlayer.Data.DataModels.Interfaces;assembly=OsuPlayer.Data" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="OsuPlayer.Views.ExportSongsView"> diff --git a/OsuPlayer/Views/ExportSongsView.axaml.cs b/OsuPlayer/Views/ExportSongsView.axaml.cs index bbcb7e55..d0051908 100644 --- a/OsuPlayer/Views/ExportSongsView.axaml.cs +++ b/OsuPlayer/Views/ExportSongsView.axaml.cs @@ -1,20 +1,13 @@ -using System.Text; -using System.Text.Json.Serialization; -using System.Threading.Tasks; +using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Markup.Xaml; using Avalonia.ReactiveUI; -using Avalonia.Threading; using Avalonia.VisualTree; -using Nein.Extensions; -using OsuPlayer.IO.DbReader.Interfaces; -using OsuPlayer.Network; +using OsuPlayer.Data.DataModels.Interfaces; using OsuPlayer.UI_Extensions; using OsuPlayer.Windows; using ReactiveUI; -using TagLib; -using File = System.IO.File; namespace OsuPlayer.Views; diff --git a/OsuPlayer/Views/ExportSongsViewModel.cs b/OsuPlayer/Views/ExportSongsViewModel.cs index d2e1ad3d..01ddcedb 100644 --- a/OsuPlayer/Views/ExportSongsViewModel.cs +++ b/OsuPlayer/Views/ExportSongsViewModel.cs @@ -1,8 +1,8 @@ using System.Reactive.Disposables; using Nein.Base; using Nein.Extensions; +using OsuPlayer.Data.DataModels.Interfaces; using OsuPlayer.Data.OsuPlayer.StorageModels; -using OsuPlayer.IO.DbReader.Interfaces; using OsuPlayer.IO.Importer; using OsuPlayer.IO.Storage.Playlists; using ReactiveUI; diff --git a/OsuPlayer/Views/HomeSubViews/HomeUserPanelViewModel.cs b/OsuPlayer/Views/HomeSubViews/HomeUserPanelViewModel.cs index 5af8b21a..4a8eef6b 100644 --- a/OsuPlayer/Views/HomeSubViews/HomeUserPanelViewModel.cs +++ b/OsuPlayer/Views/HomeSubViews/HomeUserPanelViewModel.cs @@ -6,8 +6,11 @@ using LiveChartsCore.SkiaSharpView; using LiveChartsCore.SkiaSharpView.Painting; using Nein.Base; -using OsuPlayer.Modules.Services; -using OsuPlayer.Network.API.Service.NorthFox.Endpoints; +using OsuPlayer.Data.DataModels; +using OsuPlayer.Data.DataModels.Interfaces; +using OsuPlayer.Interfaces.Service; +using OsuPlayer.Network.API.NorthFox; +using OsuPlayer.Services; using ReactiveUI; using SkiaSharp; using Splat; @@ -16,12 +19,13 @@ namespace OsuPlayer.Views.HomeSubViews; public class HomeUserPanelViewModel : BaseViewModel { + private readonly IProfileManagerService _profileManager; private readonly BindableList _graphValues = new(); - + public bool IsUserNotLoggedIn => CurrentUser == default || CurrentUser?.UniqueId == Guid.Empty; public bool IsUserLoggedIn => CurrentUser != default && CurrentUser?.UniqueId != Guid.Empty; - - public User? CurrentUser => ProfileManager.User; + + public IUser? CurrentUser => _profileManager.User; public Axis[] Axes { get; set; } = { @@ -31,7 +35,7 @@ public class HomeUserPanelViewModel : BaseViewModel Labels = null } }; - + private Bitmap? _profilePicture; public Bitmap? ProfilePicture @@ -39,13 +43,15 @@ public Bitmap? ProfilePicture get => _profilePicture; set => this.RaiseAndSetIfChanged(ref _profilePicture, value); } - + public ObservableCollection Series { get; set; } - public HomeUserPanelViewModel(IStatisticsProvider? statisticsProvider) + public HomeUserPanelViewModel(IStatisticsProvider? statisticsProvider, IProfileManagerService profileManager) { + _profileManager = profileManager; + var statsProvider = statisticsProvider; - + if (statsProvider != null) { _graphValues.BindTo(statsProvider.GraphValues); @@ -58,7 +64,7 @@ public HomeUserPanelViewModel(IStatisticsProvider? statisticsProvider) statsProvider.UserDataChanged += (_, _) => this.RaisePropertyChanged(nameof(CurrentUser)); } - + Activator = new ViewModelActivator(); this.WhenActivated(Block); @@ -67,7 +73,7 @@ public HomeUserPanelViewModel(IStatisticsProvider? statisticsProvider) private async void Block(CompositeDisposable obj) { ProfilePicture = await LoadProfilePictureAsync(); - + Series = new ObservableCollection { new LineSeries @@ -105,12 +111,12 @@ public async Task LoadUserProfileAsync() var sessionToken = await File.ReadAllTextAsync("data/session.op"); - await ProfileManager.Login(sessionToken); + await _profileManager.Login(sessionToken); this.RaisePropertyChanged(nameof(IsUserLoggedIn)); this.RaisePropertyChanged(nameof(IsUserNotLoggedIn)); this.RaisePropertyChanged(nameof(CurrentUser)); - + ProfilePicture = await LoadProfilePictureAsync(); } @@ -118,6 +124,6 @@ public async Task LoadUserProfileAsync() { if (CurrentUser == default || CurrentUser.UniqueId == Guid.Empty) return default; - return await Locator.Current.GetService().User.GetProfilePictureAsync(CurrentUser.UniqueId); + return await Locator.Current.GetService().User.GetProfilePictureAsync(CurrentUser.UniqueId); } } \ No newline at end of file diff --git a/OsuPlayer/Views/HomeView.axaml b/OsuPlayer/Views/HomeView.axaml index bd1da925..2130a332 100644 --- a/OsuPlayer/Views/HomeView.axaml +++ b/OsuPlayer/Views/HomeView.axaml @@ -5,9 +5,8 @@ xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:views="clr-namespace:OsuPlayer.Views" xmlns:lvc="using:LiveChartsCore.SkiaSharpView.Avalonia" - xmlns:dataModels="clr-namespace:OsuPlayer.IO.DbReader.DataModels;assembly=OsuPlayer.IO" xmlns:valueConverters="clr-namespace:OsuPlayer.Extensions.ValueConverters;assembly=OsuPlayer.Extensions" - xmlns:interfaces="clr-namespace:OsuPlayer.IO.DbReader.Interfaces;assembly=OsuPlayer.IO" + xmlns:interfaces="clr-namespace:OsuPlayer.Data.DataModels.Interfaces;assembly=OsuPlayer.Data" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="OsuPlayer.Views.HomeView" FontWeight="{DynamicResource DefaultFontWeight}"> @@ -18,10 +17,10 @@ - - + + - + _songsLoading = new(); private readonly ReadOnlyObservableCollection? _sortedSongEntries; + private readonly IProfileManagerService _profileManager; public readonly IPlayer Player; + private List? _playlistContextMenuEntries; private List? _playlists; private Bitmap? _profilePicture; @@ -31,7 +35,7 @@ public class HomeViewModel : BaseViewModel public ReadOnlyObservableCollection? SortedSongEntries => _sortedSongEntries; public HomeUserPanelViewModel HomeUserPanelView { get; } - + public IMapEntryBase? SelectedSong { get => _selectedSong; @@ -43,7 +47,7 @@ public IMapEntryBase? SelectedSong public bool SongsLoading => new Config().Container.OsuPath != null && _songsLoading.Value; - public User? CurrentUser => ProfileManager.User; + public User? CurrentUser => _profileManager.User; public Bitmap? ProfilePicture { @@ -58,22 +62,24 @@ public bool DisplayUserStats get => _displayUserStats; set => this.RaiseAndSetIfChanged(ref _displayUserStats, value); } - + public List? PlaylistContextMenuEntries { get => _playlistContextMenuEntries; set => this.RaiseAndSetIfChanged(ref _playlistContextMenuEntries, value); } - public HomeViewModel(IPlayer player, IStatisticsProvider? statisticsProvider) + public HomeViewModel(IPlayer player, IStatisticsProvider? statisticsProvider, IProfileManagerService profileManager) { - HomeUserPanelView = new HomeUserPanelViewModel(statisticsProvider); - + _profileManager = profileManager; + + HomeUserPanelView = new HomeUserPanelViewModel(statisticsProvider, _profileManager); + Player = player; _songsLoading.BindTo(((IImportNotifications) Player).SongsLoading); _songsLoading.BindValueChanged(_ => this.RaisePropertyChanged(nameof(SongsLoading))); - + player.SongSourceProvider.Songs?.ObserveOn(AvaloniaScheduler.Instance).Bind(out _sortedSongEntries).Subscribe(); this.RaisePropertyChanged(nameof(SortedSongEntries)); diff --git a/OsuPlayer/Views/PlayHistoryView.axaml b/OsuPlayer/Views/PlayHistoryView.axaml index 0eb30950..9516ae1f 100644 --- a/OsuPlayer/Views/PlayHistoryView.axaml +++ b/OsuPlayer/Views/PlayHistoryView.axaml @@ -3,7 +3,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:views="clr-namespace:OsuPlayer.Views" - xmlns:extensions="clr-namespace:OsuPlayer.IO.DbReader.DataModels.Extensions;assembly=OsuPlayer.IO" + xmlns:extensions="clr-namespace:OsuPlayer.Data.DataModels.Extensions;assembly=OsuPlayer.Data" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="OsuPlayer.Views.PlayHistoryView"> diff --git a/OsuPlayer/Views/PlayHistoryViewModel.cs b/OsuPlayer/Views/PlayHistoryViewModel.cs index 04a4883a..ce10df22 100644 --- a/OsuPlayer/Views/PlayHistoryViewModel.cs +++ b/OsuPlayer/Views/PlayHistoryViewModel.cs @@ -1,6 +1,7 @@ using Nein.Base; using Nein.Extensions; -using OsuPlayer.IO.DbReader.DataModels.Extensions; +using OsuPlayer.Data.DataModels.Extensions; +using OsuPlayer.Interfaces.Service; using OsuPlayer.IO.Importer; using OsuPlayer.Modules.Audio.Interfaces; using ReactiveUI; diff --git a/OsuPlayer/Views/PlayerControlViewModel.cs b/OsuPlayer/Views/PlayerControlViewModel.cs index 1f7afb7a..aaffa8e9 100644 --- a/OsuPlayer/Views/PlayerControlViewModel.cs +++ b/OsuPlayer/Views/PlayerControlViewModel.cs @@ -3,10 +3,10 @@ using Avalonia.Threading; using Nein.Base; using Nein.Extensions; +using OsuPlayer.Data.DataModels.Interfaces; using OsuPlayer.Data.OsuPlayer.Enums; using OsuPlayer.Data.OsuPlayer.StorageModels; using OsuPlayer.Extensions.EnumExtensions; -using OsuPlayer.IO.DbReader.Interfaces; using OsuPlayer.IO.Storage.Blacklist; using OsuPlayer.IO.Storage.Playlists; using OsuPlayer.Modules.Audio.Interfaces; diff --git a/OsuPlayer/Views/PlaylistEditorView.axaml b/OsuPlayer/Views/PlaylistEditorView.axaml index 32aa3628..77a92efb 100644 --- a/OsuPlayer/Views/PlaylistEditorView.axaml +++ b/OsuPlayer/Views/PlaylistEditorView.axaml @@ -6,8 +6,7 @@ xmlns:valueConverters="clr-namespace:OsuPlayer.ValueConverters" xmlns:extensions="clr-namespace:OsuPlayer.Extensions.ValueConverters;assembly=OsuPlayer.Extensions" xmlns:views="clr-namespace:OsuPlayer.Views" - xmlns:dataModels="clr-namespace:OsuPlayer.IO.DbReader.DataModels;assembly=OsuPlayer.IO" - xmlns:interfaces="clr-namespace:OsuPlayer.IO.DbReader.Interfaces;assembly=OsuPlayer.IO" + xmlns:interfaces="clr-namespace:OsuPlayer.Data.DataModels.Interfaces;assembly=OsuPlayer.Data" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="OsuPlayer.Views.PlaylistEditorView" FontWeight="{DynamicResource DefaultFontWeight}"> diff --git a/OsuPlayer/Views/PlaylistEditorView.axaml.cs b/OsuPlayer/Views/PlaylistEditorView.axaml.cs index ec181d40..71a1684d 100644 --- a/OsuPlayer/Views/PlaylistEditorView.axaml.cs +++ b/OsuPlayer/Views/PlaylistEditorView.axaml.cs @@ -5,8 +5,8 @@ using Avalonia.VisualTree; using Nein.Base; using Nein.Extensions; +using OsuPlayer.Data.DataModels.Interfaces; using OsuPlayer.Data.OsuPlayer.StorageModels; -using OsuPlayer.IO.DbReader.Interfaces; using OsuPlayer.IO.Storage.Playlists; using OsuPlayer.UI_Extensions; using OsuPlayer.Windows; diff --git a/OsuPlayer/Views/PlaylistEditorViewModel.cs b/OsuPlayer/Views/PlaylistEditorViewModel.cs index db6de530..580e01a3 100644 --- a/OsuPlayer/Views/PlaylistEditorViewModel.cs +++ b/OsuPlayer/Views/PlaylistEditorViewModel.cs @@ -4,8 +4,8 @@ using DynamicData; using Nein.Base; using Nein.Extensions; +using OsuPlayer.Data.DataModels.Interfaces; using OsuPlayer.Data.OsuPlayer.StorageModels; -using OsuPlayer.IO.DbReader.Interfaces; using OsuPlayer.IO.Storage.Playlists; using OsuPlayer.Modules.Audio.Interfaces; using ReactiveUI; diff --git a/OsuPlayer/Views/PlaylistView.axaml b/OsuPlayer/Views/PlaylistView.axaml index 8984f828..00f09ed8 100644 --- a/OsuPlayer/Views/PlaylistView.axaml +++ b/OsuPlayer/Views/PlaylistView.axaml @@ -3,10 +3,9 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:views="clr-namespace:OsuPlayer.Views" - xmlns:dataModels="clr-namespace:OsuPlayer.IO.DbReader.DataModels;assembly=OsuPlayer.IO" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:storageModels="clr-namespace:OsuPlayer.Data.OsuPlayer.StorageModels;assembly=OsuPlayer.Data" - xmlns:interfaces="clr-namespace:OsuPlayer.IO.DbReader.Interfaces;assembly=OsuPlayer.IO" + xmlns:interfaces="clr-namespace:OsuPlayer.Data.DataModels.Interfaces;assembly=OsuPlayer.Data" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="OsuPlayer.Views.PlaylistView" FontWeight="{DynamicResource DefaultFontWeight}"> diff --git a/OsuPlayer/Views/PlaylistView.axaml.cs b/OsuPlayer/Views/PlaylistView.axaml.cs index 737ec1b4..be5df20d 100644 --- a/OsuPlayer/Views/PlaylistView.axaml.cs +++ b/OsuPlayer/Views/PlaylistView.axaml.cs @@ -6,9 +6,9 @@ using Avalonia.VisualTree; using Material.Icons.Avalonia; using Nein.Base; +using OsuPlayer.Data.DataModels.Interfaces; using OsuPlayer.Data.OsuPlayer.Enums; using OsuPlayer.Data.OsuPlayer.StorageModels; -using OsuPlayer.IO.DbReader.Interfaces; using OsuPlayer.IO.Storage.Playlists; using OsuPlayer.UI_Extensions; using OsuPlayer.Windows; diff --git a/OsuPlayer/Views/PlaylistViewModel.cs b/OsuPlayer/Views/PlaylistViewModel.cs index b6caf62a..d46cf2c1 100644 --- a/OsuPlayer/Views/PlaylistViewModel.cs +++ b/OsuPlayer/Views/PlaylistViewModel.cs @@ -5,9 +5,9 @@ using Material.Icons.Avalonia; using Nein.Base; using Nein.Extensions; +using OsuPlayer.Data.DataModels.Interfaces; using OsuPlayer.Data.OsuPlayer.Enums; using OsuPlayer.Data.OsuPlayer.StorageModels; -using OsuPlayer.IO.DbReader.Interfaces; using OsuPlayer.IO.Storage.Playlists; using OsuPlayer.Modules.Audio.Interfaces; using ReactiveUI; diff --git a/OsuPlayer/Views/SearchView.axaml b/OsuPlayer/Views/SearchView.axaml index d224806b..df090c05 100644 --- a/OsuPlayer/Views/SearchView.axaml +++ b/OsuPlayer/Views/SearchView.axaml @@ -3,8 +3,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:views="clr-namespace:OsuPlayer.Views" - xmlns:dataModels="clr-namespace:OsuPlayer.IO.DbReader.DataModels;assembly=OsuPlayer.IO" - xmlns:interfaces="clr-namespace:OsuPlayer.IO.DbReader.Interfaces;assembly=OsuPlayer.IO" + xmlns:interfaces="clr-namespace:OsuPlayer.Data.DataModels.Interfaces;assembly=OsuPlayer.Data" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="OsuPlayer.Views.SearchView" FontWeight="{DynamicResource DefaultFontWeight}"> @@ -18,7 +17,8 @@ - diff --git a/OsuPlayer/Views/SearchView.axaml.cs b/OsuPlayer/Views/SearchView.axaml.cs index 8d9bd7e4..817d4583 100644 --- a/OsuPlayer/Views/SearchView.axaml.cs +++ b/OsuPlayer/Views/SearchView.axaml.cs @@ -2,7 +2,7 @@ using Avalonia.Interactivity; using Avalonia.Markup.Xaml; using Nein.Base; -using OsuPlayer.IO.DbReader.Interfaces; +using OsuPlayer.Data.DataModels.Interfaces; using OsuPlayer.IO.Storage.Blacklist; using ReactiveUI; diff --git a/OsuPlayer/Views/SearchViewModel.cs b/OsuPlayer/Views/SearchViewModel.cs index 39fc2db9..d5e38f90 100644 --- a/OsuPlayer/Views/SearchViewModel.cs +++ b/OsuPlayer/Views/SearchViewModel.cs @@ -4,9 +4,9 @@ using Avalonia.Threading; using DynamicData; using Nein.Base; +using OsuPlayer.Data.DataModels.Interfaces; using OsuPlayer.Data.OsuPlayer.Classes; using OsuPlayer.Data.OsuPlayer.StorageModels; -using OsuPlayer.IO.DbReader.Interfaces; using OsuPlayer.IO.Storage.Playlists; using OsuPlayer.Modules.Audio.Interfaces; using ReactiveUI; diff --git a/OsuPlayer/Views/SettingsView.axaml b/OsuPlayer/Views/SettingsView.axaml index 5cede5d3..45f3545c 100644 --- a/OsuPlayer/Views/SettingsView.axaml +++ b/OsuPlayer/Views/SettingsView.axaml @@ -321,7 +321,7 @@ - + @@ -400,6 +400,17 @@ + + + +