Newer
Older
Qwilight / Qwilight / System / LevelSystem.cs
@Taehui Taehui on 21 Aug 11 KB v1.16.40
using HtmlAgilityPack;
using Qwilight.NoteFile;
using Qwilight.UIComponent;
using Qwilight.Utilities;
using Qwilight.ViewModel;
using SharpCompress.Common;
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> LevelIDCollection { 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) => LevelIDCollection.IndexOf(x).CompareTo(LevelIDCollection.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 void Load(bool isNotify)
        {
            LevelID128s.Clear();
            LevelID256s.Clear();
            LevelID128NoteFiles.Clear();
            LevelID256NoteFiles.Clear();
            LevelIDCollection.Clear();
            var wantLevelName = Configure.Instance.LastWantLevelName;
            if (!string.IsNullOrEmpty(wantLevelName))
            {
                try
                {
                    var levelFilePath = Path.Combine(QwilightComponent.QwilightEntryPath, "Level", $"{wantLevelName}.json");
                    if (File.Exists(levelFilePath))
                    {
                        var levelTableFilePath = Path.Combine(QwilightComponent.QwilightEntryPath, "Level", $"#{wantLevelName}.json");
                        if (File.Exists(levelTableFilePath))
                        {
                            var levelTable = Utility.GetJSON<JSON.BMSTable?>(File.ReadAllText(levelTableFilePath));
                            if (levelTable.HasValue)
                            {
                                var levelTableValue = levelTable.Value;
                                var levelTexts = new List<object>();
                                var levelTitle = levelTableValue.symbol;
                                foreach (var levelTableData in Utility.GetJSON<JSON.BMSTableData[]>(File.ReadAllText(levelFilePath)))
                                {
                                    var level = levelTableData.level;
                                    var noteID128 = levelTableData.md5.ToLowerInvariant();
                                    var www = levelTableData.url;
                                    if (!string.IsNullOrEmpty(noteID128))
                                    {
                                        LevelID128s[noteID128] = levelTitle + level;
                                        LevelID128NoteFiles[noteID128] = new LevelNoteFile(levelTableData, levelTitle + level);
                                    }
                                    var noteID256 = levelTableData.sha256.ToLowerInvariant();
                                    if (!string.IsNullOrEmpty(noteID256))
                                    {
                                        LevelID256s[noteID256] = levelTitle + level;
                                        LevelID256NoteFiles[noteID256] = new LevelNoteFile(levelTableData, 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)
                                {
                                    LevelIDCollection.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<bool> GetWww(string www)
        {
            return await GetLevelNotifyItem(async levelNotifyItem =>
            {
                using var s = await TwilightSystem.Instance.GetWwwParallel(www);
                using var sr = new StreamReader(s);
                return await (www.IsTailCaselsss(".json") ? GetJSON(levelNotifyItem, s, www) : GetHTML(levelNotifyItem, await sr.ReadToEndAsync(), www));
            });
        }

        public async ValueTask<bool> GetLevelNotifyItem(Func<NotifyItem, ValueTask<bool>> onNotifyItem)
        {
            var levelNotifyItem = new NotifyItem
            {
                Text = LanguageSystem.Instance.SavingLevelContents,
                Variety = NotifySystem.NotifyVariety.Handling,
                OnStop = wipeTotal => false
            };
            try
            {
                UIHandler.Instance.HandleParallel(() => ViewModels.Instance.NotifyValue.HandlingNotifyItemCollection.Insert(0, levelNotifyItem));
                NotifySystem.Instance.Notify(NotifySystem.NotifyVariety.Info, NotifySystem.NotifyConfigure.NotSave, levelNotifyItem.Text, false, null, null, NotifySystem.SaveLevelID);
                return await onNotifyItem(levelNotifyItem);
            }
            catch (OperationCanceledException)
            {
            }
            catch (Exception e)
            {
                levelNotifyItem.Variety = NotifySystem.NotifyVariety.Fault;
                levelNotifyItem.Text += $" ({e.Message})";
                NotifySystem.Instance.Notify(levelNotifyItem.Variety, NotifySystem.NotifyConfigure.Default, levelNotifyItem.Text, false, null, null, NotifySystem.SaveLevelID);
            }
            return false;
        }

        public async ValueTask<bool> GetHTML(NotifyItem levelNotifyItem, string text, string www)
        {
            var levelCompiler = new HtmlDocument();
            levelCompiler.LoadHtml(text);
            using var s = await TwilightSystem.Instance.GetWwwParallel(WebUtility.HtmlDecode(ModifyDataValue(levelCompiler.CreateNavigator().SelectSingleNode("/html/head/meta[@name='bmstable']/@content")?.ToString()
                ?? levelCompiler.CreateNavigator().SelectSingleNode("/html/body/meta[@name='bmstable']/@content")?.ToString()
                ?? levelCompiler.CreateNavigator().SelectSingleNode("/html/head/body/meta[@name='bmstable']/@content")?.ToString(), www)));
            return await GetJSON(levelNotifyItem, s, www);
        }

        async ValueTask<bool> GetJSON(NotifyItem levelNotifyItem, Stream s, string www)
        {
            var levelTable = await Utility.GetJSON<JSON.BMSTable?>(s);
            if (levelTable.HasValue)
            {
                var levelTableValue = levelTable.Value;
                var target = ModifyDataValue(levelTableValue.data_url, www);
                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 hrm = await wwwClient.GetAsync(target, HttpCompletionOption.ResponseHeadersRead))
                    {
                        levelNotifyItem.MaxStatus = hrm.Content.Headers.ContentLength ?? 0L;
                    }
                    using var fs = File.Open(Path.Combine(EntryPath, $"{levelTableFileName}.json"), FileMode.Create);
                    using var ws = await wwwClient.GetStreamAsync(target);
                    var data = ArrayPool<byte>.Shared.Rent(QwilightComponent.SendUnit);
                    try
                    {
                        var length = 0;
                        while ((length = await ws.ReadAsync(data.AsMemory(0, data.Length))) > 0)
                        {
                            if (levelNotifyItem.IsStopped)
                            {
                                throw new OperationCanceledException();
                            }
                            await fs.WriteAsync(data.AsMemory(0, length));
                            levelNotifyItem.Status += length;
                        }
                    }
                    finally
                    {
                        ArrayPool<byte>.Shared.Return(data);
                    }
                }
                using (var fs = File.Open(Path.Combine(EntryPath, $"#{levelTableFileName}.json"), FileMode.Create))
                {
                    s.Position = 0;
                    await s.CopyToAsync(fs);
                }
                levelNotifyItem.Variety = NotifySystem.NotifyVariety.Closed;
                levelNotifyItem.Text = LanguageSystem.Instance.SavedLevelContents;
                levelNotifyItem.OnStop = wipeTotal => true;
                NotifySystem.Instance.Notify(NotifySystem.NotifyVariety.Info, NotifySystem.NotifyConfigure.NotSave, LanguageSystem.Instance.SavedLevelContents, false, null, null, NotifySystem.SaveLevelID);
                Configure.Instance.LevelTargetMap[levelTableFileName] = www;
                LoadLevelFiles();
                if (Configure.Instance.LastWantLevelName != levelTableFileName)
                {
                    Configure.Instance.LastWantLevelName = levelTableFileName;
                }
                else
                {
                    return true;
                }
            }
            return false;
        }

        static string ModifyDataValue(string dataValue, string www)
        {
            if (!Uri.IsWellFormedUriString(dataValue, UriKind.Absolute))
            {
                if (www.Substring(www.LastIndexOf('/')).Contains('.') || www.EndsWith('/'))
                {
                    return $"{www}/../{dataValue}";
                }
                else
                {
                    return $"{www}/{dataValue}";
                }
            }
            else
            {
                return dataValue;
            }
        }
    }
}