diff --git a/Convention/Runtime/Config.cs b/Convention/Runtime/Config.cs index 794c2c2..89a6ed3 100644 --- a/Convention/Runtime/Config.cs +++ b/Convention/Runtime/Config.cs @@ -32,9 +32,7 @@ namespace Convention return MainThreadID == Thread.CurrentThread.ManagedThreadId; } - //var filePath = Environment.ExpandEnvironmentVariables(@"%USERPROFILE%\AppData\Local\Temp"); - - public static string CompanyName = "DefaultCom"; + public static string CompanyName = "DefaultCompany"; public static string ProductName = "DefaultProject"; diff --git a/Convention/Runtime/File.cs b/Convention/Runtime/File.cs new file mode 100644 index 0000000..82e4102 --- /dev/null +++ b/Convention/Runtime/File.cs @@ -0,0 +1,416 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json; + +namespace Convention +{ + [Serializable] + public sealed class ToolFile + { + private string FullPath; + private FileSystemInfo OriginInfo; + private FileStream OriginControlStream; + public ToolFile(string path) + { + FullPath = path; + Refresh(); + } + public override string ToString() + { + return this.FullPath; + } + + #region Path + + public static implicit operator string(ToolFile data) => data.FullPath; + public string GetFullPath() + { + return this.FullPath; + } + public string GetName(bool is_ignore_extension = false) + { + return this.FullPath[..( + (this.FullPath.Contains('.') && is_ignore_extension) + ? this.FullPath.LastIndexOf('.') + : ^0 + )] + [..( + (this.FullPath[^1] == '/' || this.FullPath[^1] == '\\') + ? ^1 + : ^0 + )]; + } + public string GetExtension() + { + if (IsDir()) + return ""; + return this.FullPath[( + (this.FullPath.Contains('.')) + ? this.FullPath.LastIndexOf('.') + : ^0 + )..]; + } + + #endregion + + #region Exists + + public bool Exists() => File.Exists(FullPath) || Directory.Exists(FullPath); + + public static implicit operator bool(ToolFile file) => file.Exists(); + + #endregion + + public ToolFile Refresh() + { + if (Exists() == false) + OriginInfo = null; + else if (IsDir()) + OriginInfo = new DirectoryInfo(FullPath); + else + OriginInfo = new FileInfo(FullPath); + return this; + } + + #region Load + + public T LoadAsRawJson() + { + return JsonSerializer.Deserialize(LoadAsText()); + } + public T LoadAsJson(string key = "data") + { + return EasySave.EasySave.Load(key, FullPath); + } + public string LoadAsText() + { + if (IsFile() == false) + throw new InvalidOperationException("Target is not a file"); + string result = ""; + using (var fs = (this.OriginInfo as FileInfo).OpenText()) + { + result = fs.ReadToEnd(); + } + return result; + } + public byte[] LoadAsBinary() + { + if (IsFile() == false) + throw new InvalidOperationException("Target is not a file"); + var file = this.OriginInfo as FileInfo; + const int BlockSize = 1024; + long FileSize = file.Length; + byte[] result = new byte[FileSize]; + long offset = 0; + using (var fs = file.OpenRead()) + { + fs.ReadAsync(result[(int)(offset)..(int)(offset + BlockSize)], 0, (int)(offset + BlockSize) - (int)(offset)); + offset += BlockSize; + offset = System.Math.Min(offset, FileSize); + } + return result; + } + + #endregion + + #region Save + + public void SaveAsRawJson(T data) + { + SaveAsText(JsonSerializer.Serialize(data)); + } + public void SaveAsJson(T data, string key) + { + EasySave.EasySave.Save(key, data,FullPath); + } + private void SaveAsText(string data) + { + if (OriginControlStream != null && OriginControlStream.CanWrite) + { + using var sw = new StreamWriter(OriginControlStream); + sw.Write(data); + sw.Flush(); + } + else + { + using var fs = new FileStream(FullPath, FileMode.CreateNew, FileAccess.Write); + using var sw = new StreamWriter(fs); + sw.Write(data); + sw.Flush(); + } + } + public static void SaveDataAsBinary(string path, byte[] outdata, FileStream Stream = null) + { + if (Stream != null && Stream.CanWrite) + { + Stream.Write(outdata, 0, outdata.Length); + Stream.Flush(); + } + else + { + using var fs = new FileStream(path, FileMode.CreateNew, FileAccess.Write); + fs.Write(outdata, 0, outdata.Length); + fs.Flush(); + } + } + + public void SaveAsBinary(byte[] data) + { + SaveDataAsBinary(FullPath, data, OriginControlStream); + } + + #endregion + + #region IsFileType + + public bool IsDir() + { + if (Exists()) + { + return Directory.Exists(this.FullPath); + } + return this.FullPath[^1] == '\\' || this.FullPath[^1] == '/'; + } + + public bool IsFile() + { + return !IsDir(); + } + + public bool IsFileEmpty() + { + if (IsFile()) + return (this.OriginInfo as FileInfo).Length == 0; + throw new InvalidOperationException(); + } + + #endregion + + #region Operator + + public static ToolFile operator |(ToolFile left, string rightPath) + { + string lp = left.GetFullPath(); + return new ToolFile(Path.Combine(lp, rightPath)); + } + public ToolFile Open(string path) + { + this.FullPath = path; + Refresh(); + return this; + } + public ToolFile Open(FileMode mode) + { + this.Close(); + OriginControlStream = new FileStream(this.FullPath, mode); + return this; + } + public ToolFile Close() + { + OriginControlStream?.Close(); + return this; + } + public ToolFile Create() + { + if (Exists() == false) + { + if (IsDir()) + Directory.CreateDirectory(this.FullPath); + else + File.Create(this.FullPath); + } + return this; + } + public ToolFile Rename(string newPath) + { + if(IsDir()) + { + var dir = OriginInfo as DirectoryInfo; + dir.MoveTo(newPath); + } + else + { + var file = OriginInfo as FileInfo; + file.MoveTo(newPath); + } + FullPath = newPath; + return this; + } + public ToolFile Move(string path) + { + Rename(path); + return this; + } + public ToolFile Copy(string path,out ToolFile copyTo) + { + if (IsDir()) + { + throw new InvalidOperationException(); + } + else + { + var file = OriginInfo as FileInfo; + file.CopyTo(path); + copyTo = new(path); + } + return this; + } + public ToolFile Delete() + { + if (IsDir()) + Directory.Delete(FullPath); + else + File.Delete(FullPath); + return this; + } + public ToolFile Remove() => Delete(); + public ToolFile MustExistsPath() + { + this.Close(); + this.TryCreateParentPath(); + this.Create(); + return this; + } + #endregion + + #region Dir + + public ToolFile TryCreateParentPath() + { + if (this.GetName().Contains('/') || this.GetName().Contains('\\')) + { + var parent = new ToolFile(this.GetParentDir()); + if (parent.Exists()) + { + parent.Create(); + } + } + return this; + } + + public List DirIter() + { + if (this.IsDir()) + { + var dir = new DirectoryInfo(FullPath); + var result = dir.GetDirectories().ToList().ConvertAll(x => x.FullName); + result.AddRange(dir.GetFiles().ToList().ConvertAll(x => x.FullName)); + return result; + } + return null; + } + + public List DirToolFileIter() + { + if (this.IsDir()) + { + return DirIter().ConvertAll(x => new ToolFile(x)); + } + throw new DirectoryNotFoundException(FullPath); + } + + public ToolFile BackToParentDir() + { + var file = new ToolFile(this.GetParentDir()); + this.Close(); + this.FullPath = file.FullPath; + this.OriginInfo = file.OriginInfo; + return this; + } + + public ToolFile GetParentDir() + { + if (IsDir()) + return new ToolFile((this.OriginInfo as DirectoryInfo).Parent.FullName); + else + return new ToolFile((this.OriginInfo as FileInfo).DirectoryName); + } + + public int DirCount() + { + if (IsDir()) + return Directory.EnumerateFiles(FullPath).Count(); + return -1; + } + + public ToolFile DirClear() + { + if (IsDir()) + { + foreach (var file in DirIter()) + { + File.Delete(file); + } + } + throw new DirectoryNotFoundException(); + } + + public ToolFile MakeFileInside(string source, bool isDeleteSource = false) + { + if (this.IsDir() == false) + throw new DirectoryNotFoundException(FullPath); + string target = this | source; + if (isDeleteSource) + File.Move(target, source); + else + File.Copy(target, source); + return this; + } + + #endregion + + public static string[] SelectMultipleFiles(string filter = "所有文件|*.*", string title = "选择文件") + { + if (PlatformIndicator.IsPlatformWindows) + return WindowsKit.SelectMultipleFiles(filter, title); + throw new NotImplementedException(); + } + + public static string SelectFile(string filter = "所有文件|*.*", string title = "选择文件") + { + if (PlatformIndicator.IsPlatformWindows) + { + var results = WindowsKit.SelectMultipleFiles(filter, title); + if (results != null && results.Length > 0) + return results[0]; + } + throw new NotImplementedException(); + } + + public static string SaveFile(string filter = "所有文件|*.*", string title = "保存文件") + { + if (PlatformIndicator.IsPlatformWindows) + return WindowsKit.SaveFile(filter, title); + throw new NotImplementedException(); + } + + public static string SelectFolder(string description = "请选择文件夹") + { + if (PlatformIndicator.IsPlatformWindows) + return WindowsKit.SelectFolder(description); + throw new NotImplementedException(); + } + + public DateTime GetTimestamp() + { + return File.GetLastWriteTime(FullPath).ToUniversalTime(); + } + + public static string BrowseFile(params string[] extensions) + { + string filter = ""; + foreach (var ext in extensions) + { + filter += "*." + ext + ";"; + } + string result = SelectFile("所有文件|" + filter); + return result; + } + public static ToolFile BrowseToolFile(params string[] extensions) + { + return new ToolFile(BrowseFile(extensions)); + } + } +} diff --git a/Convention/Runtime/GlobalConfig.cs b/Convention/Runtime/GlobalConfig.cs new file mode 100644 index 0000000..d7717fd --- /dev/null +++ b/Convention/Runtime/GlobalConfig.cs @@ -0,0 +1,248 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Convention +{ + public class GlobalConfig : IEnumerable> + { + public static string ConstConfigFile = "config.json"; + + public static void InitExtensionEnv() + { + ConstConfigFile = "config.json"; + ProjectConfig.InitExtensionEnv(); + } + + public static void GenerateEmptyConfigJson(ToolFile file) + { + file.Open(System.IO.FileMode.CreateNew); + file.SaveAsRawJson>(new() + { + { "properties",new Dictionary() } + }); + } + + private int configLogging_tspace = "Property not found".Length; + + private ToolFile DataDir; + private Dictionary data_pair = new(); + + public GlobalConfig(string dataDir, bool isTryCreateDataDir = false, bool isLoad = true) + : this(new ToolFile(dataDir), isTryCreateDataDir, isLoad) { } + public GlobalConfig(ToolFile dataDir, bool isTryCreateDataDir = false, bool isLoad = true) + { + // build up data folder + dataDir ??= new ToolFile("./"); + this.DataDir = dataDir; + if (this.DataDir.IsDir() == false) + this.DataDir.BackToParentDir(); + if (this.DataDir.Exists() == false) + { + if (isTryCreateDataDir) + this.DataDir.MustExistsPath(); + else + throw new Exception($"Data dir not found: {this.DataDir}"); + } + // build up init data file + var configFile = this.ConfigFile; + if (configFile.Exists() == false) + GenerateEmptyConfigJson(configFile); + else if (isLoad) + this.LoadProperties(); + } + ~GlobalConfig() + { + + } + + public ToolFile GetConfigFile() => DataDir | ConstConfigFile; + public ToolFile ConfigFile => GetConfigFile(); + + public ToolFile GetFile(string path, bool isMustExist = false) + { + var file = DataDir | path; + if (isMustExist) + file.MustExistsPath(); + return file; + } + public bool EraseFile(string path) + { + var file = DataDir | path; + if (file.Exists()) + { + try + { + file.Open(System.IO.FileMode.Create); + return true; + } + catch (Exception) { } + } + return false; + } + public bool RemoveFile(string path) + { + var file = DataDir | path; + if (file.Exists()) + { + try + { + file.Delete(); + return true; + } + catch (Exception) { } + } + return false; + } + public bool CreateFile(string path) + { + var file = DataDir | path; + if (file.Exists()) + return false; + if (file.GetParentDir().Exists() == false) + return false; + file.Create(); + return true; + } + + public object this[string key] + { + get + { + return data_pair[key]; + } + set + { + data_pair[key] = value; + } + } + public bool Contains(string key) => data_pair.ContainsKey(key); + public bool Remove(string key) + { + if (data_pair.ContainsKey(key)) + { + data_pair.Remove(key); + return true; + } + return false; + } + public IEnumerator> GetEnumerator() + { + return ((IEnumerable>)this.data_pair).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)this.data_pair).GetEnumerator(); + } + public int DataSize() => data_pair.Count; + + public GlobalConfig SaveProperties() + { + var configFile = this.ConfigFile; + configFile.SaveAsRawJson>>(new() + { + { "properties", data_pair } + }); + return this; + } + public GlobalConfig LoadProperties() + { + var configFile = this.ConfigFile; + if (configFile.Exists() == false) + { + data_pair = new(); + } + else + { + var data = configFile.LoadAsRawJson>>(); + if (data.TryGetValue("properties", out data_pair) == false) + { + throw new Exception($"Can't find properties not found in config file"); + } + } + return this; + } + + public ToolFile GetLogFile() + { + return this.GetFile(ConfigFile.GetName(true) + "_log.txt", true); + } + public ToolFile LogFile => GetLogFile(); + + private Action MyDefaultLogger; + public Action DefaultLogger + { + get + { + return MyDefaultLogger ?? Console.WriteLine; + } + set + { + MyDefaultLogger = value; + } + } + + public virtual void Log(string messageType, string message, Action logger) + { + configLogging_tspace = Math.Max(configLogging_tspace, messageType.Length); + (logger ?? DefaultLogger)($"[{Utility.NowFormat()}]{new string(' ', configLogging_tspace / 2)}{messageType}{new string(' ', configLogging_tspace - configLogging_tspace / 2)}: {message}"); + } + public void Log(string messageType, string message) => Log(messageType, message, null); + public void LogPropertyNotFound(string message, Action logger, object @default = null) + { + if (@default != null) + { + message = $"{message} (default: {@default})"; + } + Log("Property not found", message); + } + public void LogPropertyNotFound(string message, object @default = null) + { + if (@default != null) + { + message = $"{message} (default: {@default})"; + } + Log("Property not found", message); + } + public void LogMessageOfPleaseCompleteConfiguration() + { + var message = "Please complete configuration"; + Log("Error", message); + } + + public object FindItem(string key, object @default = null) + { + if (Contains(key)) + { + return this[key]; + } + else + { + LogPropertyNotFound(key, @default); + return @default; + } + } + } + + public class ProjectConfig : GlobalConfig + { + private static string ProjectConfigFileFocus = "Assets/"; + + public static new void InitExtensionEnv() + { + ProjectConfigFileFocus = "Assets/"; + } + + public ProjectConfig(bool isLoad = true) : base(ProjectConfigFileFocus, true, isLoad) { } + + public static void SetProjectConfigFileFocus(string path) + { + ProjectConfigFileFocus = path; + } + public static string GetProjectConfigFileFocus() + { + return ProjectConfigFileFocus; + } + } +} diff --git a/Convention/Runtime/Math.cs b/Convention/Runtime/Math.cs new file mode 100644 index 0000000..fb57512 --- /dev/null +++ b/Convention/Runtime/Math.cs @@ -0,0 +1,6 @@ +using System; + +namespace Convention +{ + +} diff --git a/Convention/Runtime/Plugins/Plugin.cs b/Convention/Runtime/Plugins/Plugin.cs new file mode 100644 index 0000000..a297b65 --- /dev/null +++ b/Convention/Runtime/Plugins/Plugin.cs @@ -0,0 +1,200 @@ +using System; +using System.Collections.Generic; + +namespace Convention +{ + public class PriorityQueue where T : IComparable + { + private int _size; + private int _capacity; + private T[] _elements; + public readonly IComparer _comparer = null; + public readonly Func _comparer_func = null; + public readonly Comparator _comparator = Comparator.less; + + public int Size => _size; + public int Capacity => _capacity; + public int Count => _size; + public bool IsEmpty => _size == 0; + public T Top => _elements[0]; + + public void Clear() + { + Array.Clear(_elements, 0, _size); + _size = 0; + } + + public PriorityQueue(Comparator comparator = Comparator.less, int capacity = 1) + { + _size = 0; + _capacity = Math.Max(1, capacity); + _comparator = comparator; + _elements = new T[_capacity]; + } + public PriorityQueue(IComparer comparer, int capacity = 1) + { + _size = 0; + _capacity = Math.Max(1, capacity); + _comparer = comparer; + _elements = new T[_capacity]; + } + public PriorityQueue(Func comparer, int capacity = 1) + { + _size = 0; + _capacity = Math.Max(1, capacity); + _comparer_func = comparer; + _elements = new T[_capacity]; + } + + private int Compare(T x, T y) + { + if (_comparer != null) + { + return _comparer.Compare(x, y) * (int)_comparator; + } + else if (_comparer_func != null) + { + return _comparer_func(x, y); + } + else + { + return x.CompareTo(y) * (int)_comparator; + } + } + + private void ShiftDown() + { + int cur = 0; + int child = 1; + while (child < _size) + { + if (child + 1 < _size && Compare(_elements[child + 1], _elements[child]) < 0) + child++; + if (Compare(_elements[child], _elements[cur]) < 0) + { + Swap(ref _elements[child], ref _elements[cur]); + cur = child; + child = 2 * cur + 1; + } + else break; + } + } + + private void ShiftUp() + { + int cur = _size - 1; + int parent = (cur - 1) / 2; + while (cur > 0) + { + if (Compare(_elements[cur], _elements[parent]) < 0) + { + Swap(ref _elements[cur], ref _elements[parent]); + cur = parent; + parent = (cur - 1) / 2; + } + else break; + } + } + + private void ExpandCapacity() + { + int newCapacity = Math.Max(_capacity * 2, 4); + T[] temp = new T[newCapacity]; + Array.Copy(_elements, temp, _size); + _elements = temp; + _capacity = newCapacity; + } + + public void EnsureCapacity(int minCapacity) + { + if (_capacity < minCapacity) + { + int newCapacity = Math.Max(_capacity * 2, minCapacity); + T[] temp = new T[newCapacity]; + Array.Copy(_elements, temp, _size); + _elements = temp; + _capacity = newCapacity; + } + } + + public T Peek() + { + if (_size == 0) + throw new InvalidOperationException("Queue is empty"); + return _elements[0]; + } + + public T Dequeue() + { + if (_size == 0) + throw new InvalidOperationException("Queue is empty"); + + T result = _elements[0]; + Swap(ref _elements[0], ref _elements[_size - 1]); + _size--; + ShiftDown(); + return result; + } + + public bool TryDequeue(out T result) + { + if (_size == 0) + { + result = default; + return false; + } + result = Dequeue(); + return true; + } + + public void Enqueue(T value) + { + if (_size == _capacity) + ExpandCapacity(); + _elements[_size++] = value; + ShiftUp(); + } + + public bool Contains(T item) + { + for (int i = 0; i < _size; i++) + { + if (EqualityComparer.Default.Equals(_elements[i], item)) + return true; + } + return false; + } + + public T[] ToArray() + { + T[] result = new T[_size]; + Array.Copy(_elements, result, _size); + return result; + } + + public void TrimExcess() + { + if (_size < _capacity * 0.9) + { + T[] temp = new T[_size]; + Array.Copy(_elements, temp, _size); + _elements = temp; + _capacity = _size; + } + } + + private void Swap(ref T a, ref T b) + { + T temp = a; + a = b; + b = temp; + } + + public enum Comparator + { + less = -1, + equal = 0, + greater = 1 + } + } +} diff --git a/Convention/Runtime/Plugins/Windows/WindowsKit.cs b/Convention/Runtime/Plugins/Windows/WindowsKit.cs new file mode 100644 index 0000000..cdd8e17 --- /dev/null +++ b/Convention/Runtime/Plugins/Windows/WindowsKit.cs @@ -0,0 +1,129 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace Convention +{ + public static class WindowsKit + { + public static string current_initialDir = ""; + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public class OpenFileName + { + public int structSize = 0; + public IntPtr dlgOwner = IntPtr.Zero; + public IntPtr instance = IntPtr.Zero; + public string filter = null; + public string customFilter = null; + public int maxCustFilter = 0; + public int filterIndex = 0; + public string file = null; + public int maxFile = 0; + public string fileTitle = null; + public int maxFileTitle = 0; + public string initialDir = null; + public string title = null; + public int flags = 0; + public short fileOffset = 0; + public short fileExtension = 0; + public string defExt = null; + public IntPtr custData = IntPtr.Zero; + public IntPtr hook = IntPtr.Zero; + public string templateName = null; + public IntPtr reservedPtr = IntPtr.Zero; + public int reservedInt = 0; + public int flagsEx = 0; + } + + [DllImport("Comdlg32.dll", SetLastError = true, ThrowOnUnmappableChar = true, CharSet = CharSet.Auto)] + public static extern bool GetOpenFileName([In, Out] OpenFileName ofn); + + [DllImport("Comdlg32.dll", SetLastError = true, ThrowOnUnmappableChar = true, CharSet = CharSet.Auto)] + public static extern bool GetSaveFileName([In, Out] OpenFileName ofn); + + [DllImport("shell32.dll", CharSet = CharSet.Auto)] + public static extern IntPtr SHBrowseForFolder(ref BROWSEINFO lpbi); + + [DllImport("shell32.dll", CharSet = CharSet.Auto)] + public static extern bool SHGetPathFromIDList(IntPtr pidl, IntPtr pszPath); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct BROWSEINFO + { + public IntPtr hwndOwner; + public IntPtr pidlRoot; + public string pszDisplayName; + public string lpszTitle; + public uint ulFlags; + public IntPtr lpfn; + public IntPtr lParam; + public int iImage; + } + + public static string SelectFolder(string description = "请选择文件夹") + { + BROWSEINFO bi = new BROWSEINFO(); + bi.lpszTitle = description; + bi.ulFlags = 0x00000040; // BIF_NEWDIALOGSTYLE + bi.hwndOwner = IntPtr.Zero; + + IntPtr pidl = SHBrowseForFolder(ref bi); + if (pidl != IntPtr.Zero) + { + IntPtr pathPtr = Marshal.AllocHGlobal(260); + if (SHGetPathFromIDList(pidl, pathPtr)) + { + string path = Marshal.PtrToStringAuto(pathPtr); + Marshal.FreeHGlobal(pathPtr); + current_initialDir = path; + return path; + } + Marshal.FreeHGlobal(pathPtr); + } + return null; + } + + public static string[] SelectMultipleFiles(string filter = "所有文件|*.*", string title = "选择文件") + { + OpenFileName ofn = new OpenFileName(); + ofn.structSize = Marshal.SizeOf(ofn); + ofn.filter = filter.Replace("|", "\0") + "\0"; + ofn.file = new string(new char[256]); + ofn.maxFile = ofn.file.Length; + ofn.fileTitle = new string(new char[64]); + ofn.maxFileTitle = ofn.fileTitle.Length; + ofn.initialDir = current_initialDir; + ofn.title = title; + ofn.flags = 0x00080000 | 0x00001000 | 0x00000800 | 0x00000008 | 0x00000200; // OFN_ALLOWMULTISELECT + + if (GetOpenFileName(ofn)) + { + current_initialDir = Path.GetDirectoryName(ofn.file); + return ofn.file.Split('\0'); + } + return null; + } + + public static string SaveFile(string filter = "所有文件|*.*", string title = "保存文件") + { + OpenFileName ofn = new OpenFileName(); + ofn.structSize = Marshal.SizeOf(ofn); + ofn.filter = filter.Replace("|", "\0") + "\0"; + ofn.file = new string(new char[256]); + ofn.maxFile = ofn.file.Length; + ofn.fileTitle = new string(new char[64]); + ofn.maxFileTitle = ofn.fileTitle.Length; + ofn.initialDir = current_initialDir; + ofn.title = title; + ofn.flags = 0x00080000 | 0x00001000 | 0x00000800 | 0x00000008 | 0x00000002; // OFN_OVERWRITEPROMPT + + if (GetSaveFileName(ofn)) + { + current_initialDir = Path.GetDirectoryName(ofn.file); + return ofn.file; + } + return null; + } + } +} diff --git a/Convention/Runtime/Web.cs b/Convention/Runtime/Web.cs new file mode 100644 index 0000000..a7a612d --- /dev/null +++ b/Convention/Runtime/Web.cs @@ -0,0 +1,734 @@ +using UnityEngine; +using UnityEngine.Networking; +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Text.RegularExpressions; +using Newtonsoft; +using Newtonsoft.Json; + +namespace Convention +{ + [Serializable] + public class ToolURL : LeftValueReference +#if UNITY_EDITOR + , ISerializationCallbackReceiver +#endif + { + [System.AttributeUsage(AttributeTargets.All, Inherited = true, AllowMultiple = false)] + public class URLAttribute : Attribute + { + public string[] urlTypes; + public URLAttribute(params string[] types) + { + urlTypes = types; + } + public URLAttribute(bool IsAnyURL) + { + if (IsAnyURL) + urlTypes = new string[] { "*" }; + else + urlTypes = new string[] { }; + } + } + + public static string[] ImageURLTypes = new string[] { "jpg", "jpeg", "png", "gif", "bmp", "webp" }; + public static string[] AudioURLTypes = new string[] { "mp3", "wav", "ogg", "aac", "m4a" }; + public static string[] VideoURLTypes = new string[] { "mp4", "webm", "mov", "avi", "mkv" }; + public static string[] DocumentURLTypes = new string[] { "pdf", "doc", "docx", "txt", "rtf" }; + public static string[] JsonURLTypes = new string[] { "json" }; + + [Content, SerializeField] private string url { get => ref_value; set => ref_value = value; } + [Ignore][HideInInspector] public UnityWebRequest WebRequest { get; protected set; } + [Content] public object data; + + public ToolURL([In] string url) : base(url) { } + + ~ToolURL() + { + this.Close(); + } + + public override string SymbolName() + { + return + $"url<" + + $"{(IsValid ? "v" : "-")}" + + $">"; + } + + public override string ToString() + { + return this.url; + } + + public delegate void GetCallback([In] UnityWebRequest request); + public bool Get([In] GetCallback callback) + { + if (!IsValid) + return false; + + WebRequest = UnityWebRequest.Get(this.url); + WebRequest.SendWebRequest(); + + while (!WebRequest.isDone) ; + + callback(WebRequest); + return WebRequest.result == UnityWebRequest.Result.Success; + } + + public delegate void GetAsyncCallback([In, Opt] UnityWebRequest request); + public IEnumerator GetAsync([In] GetAsyncCallback callback) + { + if (!IsValid) + { + callback(null); + yield break; + } + + WebRequest = UnityWebRequest.Get(this.url); + WebRequest.SendWebRequest(); + + while (!WebRequest.isDone) + yield return null; + + callback(WebRequest); + } + + public delegate void PostCallback([In] UnityWebRequest request); + public delegate UnityWebRequest PostIniter([In]UnityWebRequest request); + public bool Post([In] PostCallback callback, [In]WWWForm form) + { + if (!IsValid) + return false; + + WebRequest = UnityWebRequest.Post(this.url, form); + WebRequest.SendWebRequest(); + + while (!WebRequest.isDone) ; + + callback(WebRequest); + return WebRequest.result == UnityWebRequest.Result.Success; + } + public bool Post([In]PostCallback callback, [In]PostIniter initer, [In]WWWForm form) + { + if (!IsValid) + return false; + + WebRequest = initer(UnityWebRequest.Post(this.url, form)); + WebRequest.SendWebRequest(); + + while (!WebRequest.isDone) ; + + callback(WebRequest); + return WebRequest.result == UnityWebRequest.Result.Success; + } + + public delegate void PostAsyncCallback([In, Opt] UnityWebRequest request); + public IEnumerator PostAsync([In] PostAsyncCallback callback, [In] WWWForm form) + { + if (!IsValid) + { + callback(null); + yield break; + } + + WebRequest = UnityWebRequest.Post(this.url, form); + WebRequest.SendWebRequest(); + + while (!WebRequest.isDone) + yield return null; + + callback(WebRequest); + } + + #region URL Properties + public string FullURL => this.url; + public static implicit operator string(ToolURL data) => data.FullURL; + public string GetFullURL() + { + return this.url; + } + + public string GetFilename() + { + if (string.IsNullOrEmpty(this.url)) + return ""; + + Uri uri = new Uri(this.url); + string path = uri.AbsolutePath; + return Path.GetFileName(path); + } + + public string GetExtension() + { + string filename = GetFilename(); + if (string.IsNullOrEmpty(filename)) + return ""; + + return Path.GetExtension(filename); + } + + public bool ExtensionIs(params string[] extensions) + { + string el = GetExtension().ToLower(); + string eln = el.Length > 1 ? el[1..] : null; + foreach (string extension in extensions) + if (el == extension || eln == extension) + return true; + return false; + } + #endregion + + #region Validation + public bool IsValid => ValidateURL(); + public bool ValidateURL() + { + if (string.IsNullOrEmpty(this.url)) + return false; + + return Uri.TryCreate(this.url, UriKind.Absolute, out Uri uriResult) + && (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps); + } + public static implicit operator bool(ToolURL url) => url.IsValid; + #endregion + + public ToolURL Refresh() + { + this.Close(); + return this; + } + + public static object LoadObjectFromBinary([In] byte[] data) + { + using MemoryStream fs = new(data, false); + return new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter().Deserialize(fs); + } + + public ToolURL Data2Object() + { + this.data = LoadObjectFromBinary((byte[])this.data); + return this; + } + + public ToolURL Data2Bytes() + { + using MemoryStream ms = new(); + new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter().Serialize(ms, this.data); + var outdata = ms.GetBuffer(); + this.data = outdata; + return this; + } + + public bool IsDataByteArray() + { + return this.data != null && this.data.GetType() == typeof(byte[]); + } + + #region Load + public object Load() + { + if (IsText) + return this.LoadAsText(); + else if (IsJson) + return LoadAsJson(); + else if (IsImage) + return LoadAsImage(); + else if (IsAudio) + return LoadAsAudio(); + else if (IsDocument) + return LoadAsDocument(); + else + return LoadAsText(); + } + + public static T LoadFromText([In] string requestText) + { + return JsonConvert.DeserializeObject(requestText); + } + [return: ReturnMayNull] + public static T LoadFromRequest([In] UnityWebRequest request) + { + while (!request.isDone) ; + + return request.result == UnityWebRequest.Result.Success + ? LoadFromText(request.downloadHandler.text) + : default; + } + [return: ReturnNotNull] + public static T LoadFromRequestNotNull([In] UnityWebRequest request) where T : class, new() + { + while (!request.isDone) ; + + return request.result == UnityWebRequest.Result.Success + ? LoadFromText(request.downloadHandler.text) + : new(); + } + [return: ReturnMayNull] + public static IEnumerator LoadFromRequestAsync([In] UnityWebRequest request,[In]Action callback) + { + while (!request.isDone) + { + yield return null; + } + + callback(request.result == UnityWebRequest.Result.Success + ? LoadFromText(request.downloadHandler.text) + : default); + } + [return: ReturnNotNull] + public static IEnumerator LoadFromRequestNotNullAsync([In] UnityWebRequest request, [In] Action callback) where T : class, new() + { + while (!request.isDone) + { + yield return null; + } + + callback(request.result == UnityWebRequest.Result.Success + ? LoadFromText(request.downloadHandler.text) + : new()); + } + + public object LoadAsJson() + { + return LoadAsJsonWithType(); + } + + public T LoadAsJsonWithType() + { + string jsonText = LoadAsText(); + try + { + T result = JsonConvert.DeserializeObject(jsonText); + this.data = result; + return result; + } + catch (Exception) + { + Debug.Log(jsonText); + return default; + } + } + + public T LoadAsJsonWithType(T whenThrow) + { + string jsonText = LoadAsText(); + try + { + T result = JsonConvert.DeserializeObject(jsonText); + this.data = result; + return result; + } + catch (Exception) + { + Debug.Log(jsonText); + return whenThrow; + } + } + + public string LoadAsText() + { + if (!IsValid) + return null; + + WebRequest = UnityWebRequest.Get(this.url); + WebRequest.SendWebRequest(); + + while (!WebRequest.isDone) + { + // 等待请求完成 + } + + if (WebRequest.result == UnityWebRequest.Result.Success) + { + this.data = WebRequest.downloadHandler.text; + return (string)this.data; + } + + return null; + } + + public IEnumerator LoadAsTextAsync([In] Action callback) + { + if (!IsValid) + { + callback(null); + yield break; + } + + WebRequest = UnityWebRequest.Get(this.url); + yield return WebRequest.SendWebRequest(); + + if (WebRequest.result == UnityWebRequest.Result.Success) + { + this.data = WebRequest.downloadHandler.text; + callback((string)this.data); + } + else + { + callback(null); + } + } + + public byte[] LoadAsBinary() + { + if (!IsValid) + return null; + + WebRequest = UnityWebRequest.Get(this.url); + WebRequest.SendWebRequest(); + + while (!WebRequest.isDone) + { + // 等待请求完成 + } + + if (WebRequest.result == UnityWebRequest.Result.Success) + { + this.data = WebRequest.downloadHandler.data; + return (byte[])this.data; + } + + return null; + } + + public IEnumerator LoadAsBinaryAsync([In] Action callback) + { + if (!IsValid) + { + callback(null); + yield break; + } + + WebRequest = UnityWebRequest.Get(this.url); + yield return WebRequest.SendWebRequest(); + + if (WebRequest.result == UnityWebRequest.Result.Success) + { + this.data = WebRequest.downloadHandler.data; + callback((byte[])this.data); + } + else + { + callback(null); + } + } + + public Texture2D LoadAsImage() + { + if (!IsValid) + return null; + + WebRequest = UnityWebRequestTexture.GetTexture(this.url); + WebRequest.SendWebRequest(); + + while (!WebRequest.isDone) + { + // 等待请求完成 + } + + if (WebRequest.result == UnityWebRequest.Result.Success) + { + this.data = DownloadHandlerTexture.GetContent(WebRequest); + return (Texture2D)this.data; + } + + return null; + } + + public IEnumerator LoadAsImageAsync([In] Action callback) + { + if (!IsValid) + { + callback(null); + yield break; + } + + WebRequest = UnityWebRequestTexture.GetTexture(this.url); + yield return WebRequest.SendWebRequest(); + + if (WebRequest.result == UnityWebRequest.Result.Success) + { + this.data = DownloadHandlerTexture.GetContent(WebRequest); + callback((Texture2D)this.data); + } + else + { + callback(null); + } + } + + public AudioClip LoadAsAudio() + { + if (!IsValid) + return null; + + WebRequest = UnityWebRequestMultimedia.GetAudioClip(this.url, GetAudioType()); + WebRequest.SendWebRequest(); + + while (!WebRequest.isDone) + { + // 等待请求完成 + } + + if (WebRequest.result == UnityWebRequest.Result.Success) + { + this.data = DownloadHandlerAudioClip.GetContent(WebRequest); + return (AudioClip)this.data; + } + + return null; + } + + public IEnumerator LoadAsAudioAsync([In] Action callback) + { + if (!IsValid) + { + callback(null); + yield break; + } + + WebRequest = UnityWebRequestMultimedia.GetAudioClip(this.url, GetAudioType()); + yield return WebRequest.SendWebRequest(); + + if (WebRequest.result == UnityWebRequest.Result.Success) + { + this.data = DownloadHandlerAudioClip.GetContent(WebRequest); + callback((AudioClip)this.data); + } + else + { + callback(null); + } + } + + public byte[] LoadAsDocument() + { + return LoadAsBinary(); + } + + public IEnumerator LoadAsDocumentAsync([In] Action callback) + { + yield return LoadAsBinaryAsync(callback); + } + + public object LoadAsUnknown() + { + this.data = this.LoadAsBinary(); + return this; + } + #endregion + + #region Save + public void Save([In][Opt] string localPath = null) + { + if (IsText) + SaveAsText(localPath); + else if (IsJson) + SaveAsJson(localPath); + else if (IsImage) + SaveAsImage(localPath); + else if (IsAudio) + SaveAsAudio(localPath); + else if (IsVideo) + SaveAsVideo(localPath); + else if (IsDocument) + SaveAsDocument(localPath); + else + SaveAsBinary(localPath); + } + + public void SaveAsJson([In][Opt] string localPath = null) + { + if (localPath == null) + { + localPath = Path.Combine(Application.temporaryCachePath, GetFilename()); + } + + string jsonText = JsonConvert.SerializeObject(this.data); + File.WriteAllText(localPath, jsonText); + } + + public void SaveAsText([In][Opt] string localPath = null) + { + if (localPath == null) + { + localPath = Path.Combine(Application.temporaryCachePath, GetFilename()); + } + + File.WriteAllText(localPath, (string)this.data); + } + + public void SaveAsBinary([In][Opt] string localPath = null) + { + if (localPath == null) + { + localPath = Path.Combine(Application.temporaryCachePath, GetFilename()); + } + + File.WriteAllBytes(localPath, (byte[])this.data); + } + + public void SaveAsImage([In][Opt] string localPath = null) + { + if (localPath == null) + { + localPath = Path.Combine(Application.temporaryCachePath, GetFilename()); + } + + Texture2D texture = (Texture2D)this.data; + byte[] bytes = texture.EncodeToPNG(); + File.WriteAllBytes(localPath, bytes); + } + + public void SaveAsAudio([In][Opt] string localPath = null) + { + // 注意:Unity不直接支持将AudioClip保存为文件 + // 这里只是一个示例,实际应用中可能需要其他方法 + Debug.LogWarning("Direct saving of AudioClip to file is not supported in Unity"); + } + + public void SaveAsVideo([In][Opt] string localPath = null) + { + // 注意:Unity不直接支持将VideoClip保存为文件 + // 这里只是一个示例,实际应用中可能需要其他方法 + Debug.LogWarning("Direct saving of VideoClip to file is not supported in Unity"); + } + + public void SaveAsDocument([In][Opt] string localPath = null) + { + SaveAsBinary(localPath); + } + #endregion + + public static AudioType GetAudioType(string url) + { + return Path.GetExtension(url) switch + { + "ogg" => AudioType.OGGVORBIS, + "mp2" => AudioType.MPEG, + "mp3" => AudioType.MPEG, + "mod" => AudioType.MOD, + "wav" => AudioType.WAV, + "aif" => AudioType.IT, + _ => AudioType.UNKNOWN + }; + } + + public AudioType GetAudioType() + { + return GetAudioType(this.url); + } + + #region URL Types + public bool IsText => ExtensionIs("txt", "html", "htm", "css", "js", "xml", "csv"); + public bool IsJson => ExtensionIs(JsonURLTypes); + public bool IsImage => ExtensionIs(ImageURLTypes); + public bool IsAudio => ExtensionIs(AudioURLTypes); + public bool IsVideo => ExtensionIs(VideoURLTypes); + public bool IsDocument => ExtensionIs(DocumentURLTypes); + #endregion + + #region Operators + [return: ReturnNotNull, ReturnNotSelf] + public static ToolURL operator |([In] ToolURL left, [In] string rightPath) + { + string baseUrl = left.GetFullURL(); + if (baseUrl.EndsWith("/")) + { + return new ToolURL(baseUrl + rightPath); + } + else + { + return new ToolURL(baseUrl + "/" + rightPath); + } + } + + public ToolURL Open([In] string url) + { + this.url = url; + return this; + } + + public ToolURL Close() + { + if (WebRequest != null) + { + WebRequest.Dispose(); + WebRequest = null; + } + return this; + } + + public ToolURL Download([In][Opt] string localPath = null) + { + if (!IsValid) + return this; + + if (localPath == null) + { + localPath = Path.Combine(Application.temporaryCachePath, GetFilename()); + } + + WebRequest = UnityWebRequest.Get(this.url); + WebRequest.SendWebRequest(); + + while (!WebRequest.isDone) + { + // 等待请求完成 + } + + if (WebRequest.result == UnityWebRequest.Result.Success) + { + File.WriteAllBytes(localPath, WebRequest.downloadHandler.data); + } + + return this; + } + + public IEnumerator DownloadAsync([In] Action callback, [In][Opt] string localPath = null) + { + if (!IsValid) + { + callback(false); + yield break; + } + + if (localPath == null) + { + localPath = Path.Combine(Application.temporaryCachePath, GetFilename()); + } + + WebRequest = UnityWebRequest.Get(this.url); + yield return WebRequest.SendWebRequest(); + + if (WebRequest.result == UnityWebRequest.Result.Success) + { + File.WriteAllBytes(localPath, WebRequest.downloadHandler.data); + callback(true); + } + else + { + callback(false); + } + } + #endregion + +#if UNITY_EDITOR + [SerializeField, ToolURL.URL] private string m_URL; + public void OnBeforeSerialize() + { + m_URL = this.FullURL; + } + + public void OnAfterDeserialize() + { + if (m_URL != this.FullURL) + { + this.url = m_URL; + } + } +#endif + } +} diff --git a/[Test]/Program.cs b/[Test]/Program.cs index 26823a3..2e64fd5 100644 --- a/[Test]/Program.cs +++ b/[Test]/Program.cs @@ -30,6 +30,6 @@ public class Program x = 30, y = 40 } - }, "test.json"); + }, "./test.json"); } } \ No newline at end of file