Newer
Older
Qwilight / Qwilight / System / LevelSystem.cs
@Taehui Taehui on 15 Nov 10 KB 2023-11-15 오후 11:18
using HtmlAgilityPack;
using Qwilight.NoteFile;
using Qwilight.UIComponent;
using Qwilight.Utilities;
using Qwilight.ViewModel;
using System.Buffers;
using System.Collections.ObjectModel;
using System.IO;
using System.Net;
using System.Net.Http;

namespace Qwilight
{
    public sealed class LevelSystem
    {
        public static readonly string EntryPath = Path.Combine(QwilightComponent.QwilightEntryPath, "Level");

        public static readonly LevelSystem Instance = QwilightComponent.GetBuiltInData<LevelSystem>(nameof(LevelSystem));

        public Comparer<string> WantLevelIDEquality { get; }

        public ObservableCollection<string> LevelFileNames { get; } = new();

        public ObservableCollection<string> LevelCollection { get; } = new();

        public Dictionary<string, string> LevelID128s { get; } = new();

        public Dictionary<string, string> LevelID256s { get; } = new();

        public Dictionary<string, LevelNoteFile> LevelID128NoteFiles { get; } = new();

        public Dictionary<string, LevelNoteFile> LevelID256NoteFiles { get; } = new();

        public LevelSystem()
        {
            WantLevelIDEquality = Comparer<string>.Create((x, y) => LevelCollection.IndexOf(x).CompareTo(LevelCollection.IndexOf(y)));
        }

        public void LoadLevelFiles() => Utility.SetUICollection(LevelFileNames, Utility.GetFiles(EntryPath).Where(levelFile => !Path.GetFileName(levelFile).StartsWith('#') && levelFile.IsTailCaselsss(".json")).Select(levelFile => Path.GetFileNameWithoutExtension(levelFile)).ToArray());

        public async ValueTask LoadJSON(bool isNotify)
        {
            LevelID128s.Clear();
            LevelID256s.Clear();
            LevelID128NoteFiles.Clear();
            LevelID256NoteFiles.Clear();
            LevelCollection.Clear();
            var levelName = Configure.Instance.WantLevelName;
            if (!string.IsNullOrEmpty(levelName))
            {
                try
                {
                    var levelFilePath = Path.Combine(QwilightComponent.QwilightEntryPath, "Level", $"{levelName}.json");
                    if (File.Exists(levelFilePath))
                    {
                        var levelTableFilePath = Path.Combine(QwilightComponent.QwilightEntryPath, "Level", $"#{levelName}.json");
                        if (File.Exists(levelTableFilePath))
                        {
                            using var tfs = File.OpenRead(levelTableFilePath);
                            var levelTable = await Utility.GetJSON<JSON.BMSTable?>(tfs);
                            if (levelTable.HasValue)
                            {
                                var levelTableValue = levelTable.Value;
                                var levelTexts = new List<object>();
                                var levelTitle = levelTableValue.symbol;
                                using var fs = File.OpenRead(levelFilePath);
                                foreach (var levelData in await Utility.GetJSON<JSON.BMSTableData[]>(fs))
                                {
                                    var level = levelData.level;
                                    var noteID128 = levelData.md5;
                                    var www = levelData.url;
                                    if (!string.IsNullOrEmpty(noteID128))
                                    {
                                        LevelID128s[$"{noteID128}:0"] = levelTitle + level;
                                        LevelID128NoteFiles[$"{noteID128}:0"] = new LevelNoteFile(levelData, levelTitle + level);
                                    }
                                    var noteID256 = levelData.sha256;
                                    if (!string.IsNullOrEmpty(noteID256))
                                    {
                                        LevelID256s[$"{noteID256}:0"] = levelTitle + level;
                                        LevelID256NoteFiles[$"{noteID256}:0"] = new LevelNoteFile(levelData, levelTitle + level);
                                    }
                                    var levelText = level.ToString();
                                    if (!levelTexts.Contains(levelText))
                                    {
                                        levelTexts.Add(levelText);
                                    }
                                }
                                var levels = levelTableValue.level_order.Select(level => level.ToString()).ToArray();
                                levelTexts.Sort((x, y) => Array.IndexOf(levels, x).CompareTo(Array.IndexOf(levels, y)));
                                foreach (var levelText in levelTexts)
                                {
                                    LevelCollection.Add(levelTitle + levelText);
                                }
                            }
                        }
                    }
                }
                catch (Exception e)
                {
                    if (isNotify)
                    {
                        NotifySystem.Instance.Notify(NotifySystem.NotifyVariety.Warning, NotifySystem.NotifyConfigure.Default, string.Format(LanguageSystem.Instance.LoadLevelFault, e.Message));
                    }
                }
            }
        }

        public async ValueTask LoadWww(string www)
        {
            var data = ArrayPool<byte>.Shared.Rent(QwilightComponent.SendUnit);
            try
            {
                var o = new HtmlDocument();
                using var os = await TwilightSystem.Instance.GetWwwParallel(www).ConfigureAwait(false);
                o.Load(os);
                using var s = await TwilightSystem.Instance.GetWwwParallel(WebUtility.HtmlDecode(ModifyDataValue(o.CreateNavigator().SelectSingleNode("/html/head/meta[@name='bmstable']/@content")?.ToString()
                    ?? o.CreateNavigator().SelectSingleNode("/html/body/meta[@name='bmstable']/@content")?.ToString()
                    ?? o.CreateNavigator().SelectSingleNode("/html/head/body/meta[@name='bmstable']/@content")?.ToString()))).ConfigureAwait(false);
                var levelTable = await Utility.GetJSON<JSON.BMSTable?>(s);
                s.Position = 0;
                if (levelTable.HasValue)
                {
                    var levelTableValue = levelTable.Value;
                    var savingBundleItem = new NotifyItem
                    {
                        Text = LanguageSystem.Instance.SavingLevelContents,
                        Variety = NotifySystem.NotifyVariety.Levying,
                        OnStop = isTotal => false
                    };
                    HandlingUISystem.Instance.HandleParallel(() => ViewModels.Instance.NotifyValue.NotifyItemCollection.Insert(0, savingBundleItem));
                    NotifySystem.Instance.Notify(NotifySystem.NotifyVariety.Info, NotifySystem.NotifyConfigure.NotSave, savingBundleItem.Text);
                    var target = ModifyDataValue(levelTableValue.data_url);
                    var levelTableFileName = levelTableValue.name;
                    foreach (var targetFileName in Path.GetInvalidFileNameChars())
                    {
                        levelTableFileName = levelTableFileName.Replace(targetFileName.ToString(), string.Empty);
                    }
                    using (var wwwClient = new HttpClient())
                    {
                        wwwClient.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0");
                        using (var fs = File.Open(Path.Combine(EntryPath, $"{levelTableFileName}.json"), FileMode.Create))
                        using (var ts = await wwwClient.GetAsync(target).ConfigureAwait(false))
                        {
                            savingBundleItem.QuitStatus = ts.Content.Headers.ContentLength ?? 0L;
                            var length = 0;
                            while ((length = await (await ts.Content.ReadAsStreamAsync().ConfigureAwait(false)).ReadAsync(data.AsMemory(0, data.Length)).ConfigureAwait(false)) > 0)
                            {
                                await fs.WriteAsync(data.AsMemory(0, length)).ConfigureAwait(false);
                                savingBundleItem.LevyingStatus += length;
                                savingBundleItem.NotifyBundleStatus();
                            }
                        }
                    }
                    using (var fs = File.Open(Path.Combine(EntryPath, $"#{levelTableFileName}.json"), FileMode.Create))
                    {
                        await s.CopyToAsync(fs).ConfigureAwait(false);
                    }
                    savingBundleItem.Variety = NotifySystem.NotifyVariety.Quit;
                    savingBundleItem.Text = LanguageSystem.Instance.SavedLevelContents;
                    savingBundleItem.OnStop = isTotal => true;
                    NotifySystem.Instance.Notify(NotifySystem.NotifyVariety.Info, NotifySystem.NotifyConfigure.NotSave, LanguageSystem.Instance.SavedLevelContents);
                    Configure.Instance.LevelTargetMap[levelTableFileName] = www;
                    LoadLevelFiles();
                    Configure.Instance.WantLevelName = levelTableFileName;
                }

                string ModifyDataValue(string dataValue)
                {
                    if (!Uri.IsWellFormedUriString(dataValue, UriKind.Absolute))
                    {
                        if (www.Substring(www.LastIndexOf('/')).Contains('.') || www.EndsWith('/'))
                        {
                            return $"{www}/../{dataValue}";
                        }
                        else
                        {
                            return $"{www}/{dataValue}";
                        }
                    }
                    else
                    {
                        return dataValue;
                    }
                }
            }
            catch (Exception e)
            {
                NotifySystem.Instance.Notify(NotifySystem.NotifyVariety.Warning, NotifySystem.NotifyConfigure.Default, string.Format(LanguageSystem.Instance.NotValidNetLevelContents, e.Message));
            }
            finally
            {
                ArrayPool<byte>.Shared.Return(data);
            }
        }
    }
}