diff --git a/Apewer.Run/Apewer.Run.csproj b/Apewer.Run/Apewer.Run.csproj index bcd48ff..2ee913f 100644 --- a/Apewer.Run/Apewer.Run.csproj +++ b/Apewer.Run/Apewer.Run.csproj @@ -1,15 +1,38 @@ - - Exe - - - - net461 - - - - - + + Exe + + + + net461 + + + + + $(AssemblyName) - .NET Framework 4.6.1 + NETFX;NET461;NET45;HAVE_ASYNC;HAVE_BIG_INTEGER;$(DefineConstants);$(AdditionalConstants) + + + + + + + + + + + + + + + + + + + + + + diff --git a/Apewer/Apewer.csproj b/Apewer/Apewer.csproj index ed44a85..dea55f6 100644 --- a/Apewer/Apewer.csproj +++ b/Apewer/Apewer.csproj @@ -9,7 +9,7 @@ latest CS0108,CS0162,CS0414,CS0612,CS0618,CS0649,CS1589,CS1570,CS1572,CS1573,CS3019,CS3021 Library - netcoreapp3.1;netstandard2.1;net461;net40;net20 + netcoreapp3.1;netstandard2.1;netstandard2.0;net461;net40;net20 @@ -20,7 +20,7 @@ Apewer Apewer Libraries - 6.3.2 + 6.3.3 @@ -43,6 +43,12 @@ NETSTD;HAVE_ASYNC;HAVE_BIG_INTEGER;$(DefineConstants);$(AdditionalConstants) + + + $(AssemblyName) - .NET Standard 2.0 + NETSTD;HAVE_ASYNC;HAVE_BIG_INTEGER;$(DefineConstants);$(AdditionalConstants) + + $(AssemblyName) - .NET Framework 2.0 diff --git a/Apewer/BinaryUtility.cs b/Apewer/BinaryUtility.cs index eaf7342..be97f67 100644 --- a/Apewer/BinaryUtility.cs +++ b/Apewer/BinaryUtility.cs @@ -153,6 +153,9 @@ namespace Apewer return wiped; } + /// 生成新的 GUID 数据。 + public static byte[] NewGuid() => Guid.NewGuid().ToByteArray(); + #endregion #region Text diff --git a/Apewer/ChinaUtility.cs b/Apewer/ChinaUtility.cs new file mode 100644 index 0000000..91e039a --- /dev/null +++ b/Apewer/ChinaUtility.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Apewer +{ + + /// + public class ChinaUtility + { + + private static bool CheckIdCode18(string idCode18) + { + // GB11643-1999 + if (string.IsNullOrEmpty(idCode18) || idCode18.Length != 18) return false; + var code = idCode18; + + // 数字。 + var n = 0L; + if (long.TryParse(code.Remove(17), out n) == false) return false; + if (n < Math.Pow(10, 16)) return false; + if (long.TryParse(code.Replace('x', '0').Replace('X', '0'), out n) == false) return false; + + // 省份。 + var address = "11x22x35x44x53x12x23x36x45x54x13x31x37x46x61x14x32x41x50x62x15x33x42x51x63x21x34x43x52x64x65x71x81x82x91"; + if (address.IndexOf(code.Remove(2)) < 0) return false; + + // 生日。 + var birth = code.Substring(6, 8).Insert(6, "-").Insert(4, "-"); + var time = new DateTime(); + if (DateTime.TryParse(birth, out time) == false) return false; + + // 校验码。 + var cc = "10x98765432".ToCharArray(); + var wi = "7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2".Split(','); + var ai = code.Remove(17).ToCharArray(); + var sum = 0; + for (int i = 0; i < 17; i++) + { + sum += int.Parse(wi[i]) * int.Parse(ai[i].ToString()); + } + var y = sum % 11; + if (cc[y] != code[17]) return false; + + return true; + } + + private static bool CheckIdCode15(string idCode15) + { + if (string.IsNullOrEmpty(idCode15) || idCode15.Length != 15) return false; + var code = idCode15; + + // 数字。 + var n = 0L; + if (long.TryParse(code, out n) == false || n < Math.Pow(10, 14)) return false; + + // 省份。 + var address = "11x22x35x44x53x12x23x36x45x54x13x31x37x46x61x14x32x41x50x62x15x33x42x51x63x21x34x43x52x64x65x71x81x82x91"; + if (address.IndexOf(code.Remove(2)) == -1) return false; + + // 生日。 + var birth = code.Substring(6, 6).Insert(4, "-").Insert(2, "-"); + var time = new DateTime(); + if (DateTime.TryParse(birth, out time) == false) return false; + + return true; + + } + + /// + public static bool CheckIdCode(string idCode) + { + if (string.IsNullOrEmpty(idCode)) return false; + if (idCode.Length == 18) return CheckIdCode18(idCode); + if (idCode.Length == 15) return CheckIdCode15(idCode); + return false; + } + + } + +} diff --git a/Apewer/Externals/Newtonsoft.Json-11.0.1/Serialization/JsonPropertyCollection.cs b/Apewer/Externals/Newtonsoft.Json-11.0.1/Serialization/JsonPropertyCollection.cs index 08ed321..cd58f66 100644 --- a/Apewer/Externals/Newtonsoft.Json-11.0.1/Serialization/JsonPropertyCollection.cs +++ b/Apewer/Externals/Newtonsoft.Json-11.0.1/Serialization/JsonPropertyCollection.cs @@ -140,7 +140,7 @@ namespace Newtonsoft.Json.Serialization return property; } -#if NETFX +#if NETFX || NETSTANDARD2_0 private bool TryGetValue(string key, out JsonProperty item) #else private new bool TryGetValue(string key, out JsonProperty item) diff --git a/Apewer/Internals/Interop/AdvApi32.cs b/Apewer/Internals/Interop/AdvApi32.cs index 8caac06..0a74784 100644 --- a/Apewer/Internals/Interop/AdvApi32.cs +++ b/Apewer/Internals/Interop/AdvApi32.cs @@ -9,15 +9,16 @@ namespace Apewer.Internals.Interop { [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)] - internal static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok); + internal static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall, ref TokenPrivilege newst, int len, IntPtr prev, IntPtr relen); + + [DllImport("advapi32.dll", SetLastError = true)] + internal static extern bool LookupPrivilegeValue(string host, string name, ref long pluid); [DllImport("advapi32.dll", SetLastError = true)] - internal static extern bool LookupPrivilegeValueA - (string host, string name, ref long pluid); + internal static extern bool LookupPrivilegeValueA(string host, string name, ref long pluid); [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)] - internal static extern bool - AdjustTokenPrivileges(IntPtr htok, bool disall, ref TokenPrivilege newst, int len, IntPtr prev, IntPtr relen); + internal static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok); } diff --git a/Apewer/Internals/Interop/Constant.cs b/Apewer/Internals/Interop/Constant.cs index 934a7c9..476298e 100644 --- a/Apewer/Internals/Interop/Constant.cs +++ b/Apewer/Internals/Interop/Constant.cs @@ -218,6 +218,8 @@ namespace Apewer.Internals.Interop public const int DMDO_180 = 2; public const int DMDO_270 = 3; + #region 电源。 + public const int SE_PRIVILEGE_ENABLED = 0x00000002; public const int TOKEN_QUERY = 0x00000008; public const int TOKEN_ADJUST_PRIVILEGES = 0x00000020; @@ -230,6 +232,8 @@ namespace Apewer.Internals.Interop public const int EWX_POWEROFF = 0x00000008; public const int EWX_FORCEIFHUNG = 0x00000010; + #endregion + #region Windows Hook /// @@ -327,6 +331,109 @@ namespace Apewer.Internals.Interop #endregion + #region vbKey 按键事件 + + public const byte vbKeyLButton = 0x1; // 鼠标左键 + public const byte vbKeyRButton = 0x2; // 鼠标右键 + public const byte vbKeyCancel = 0x3; // CANCEL 键 + public const byte vbKeyMButton = 0x4; // 鼠标中键 + public const byte vbKeyBack = 0x8; // BACKSPACE 键 + public const byte vbKeyTab = 0x9; // TAB 键 + public const byte vbKeyClear = 0xC; // CLEAR 键 + public const byte vbKeyReturn = 0xD; // ENTER 键 + public const byte vbKeyShift = 0x10; // SHIFT 键 + public const byte vbKeyControl = 0x11; // CTRL 键 + public const byte vbKeyAlt = 18; // Alt 键 (键码18) + public const byte vbKeyMenu = 0x12; // MENU 键 + public const byte vbKeyPause = 0x13; // PAUSE 键 + public const byte vbKeyCapital = 0x14; // CAPS LOCK 键 + public const byte vbKeyEscape = 0x1B; // ESC 键 + public const byte vbKeySpace = 0x20; // SPACEBAR 键 + public const byte vbKeyPageUp = 0x21; // PAGE UP 键 + public const byte vbKeyEnd = 0x23; // End 键 + public const byte vbKeyHome = 0x24; // HOME 键 + public const byte vbKeyLeft = 0x25; // LEFT ARROW 键 + public const byte vbKeyUp = 0x26; // UP ARROW 键 + public const byte vbKeyRight = 0x27; // RIGHT ARROW 键 + public const byte vbKeyDown = 0x28; // DOWN ARROW 键 + public const byte vbKeySelect = 0x29; // Select 键 + public const byte vbKeyPrint = 0x2A; // PRINT SCREEN 键 + public const byte vbKeyExecute = 0x2B; // EXECUTE 键 + public const byte vbKeySnapshot = 0x2C; // SNAPSHOT 键 + public const byte vbKeyDelete = 0x2E; // Delete 键 + public const byte vbKeyHelp = 0x2F; // HELP 键 + public const byte vbKeyNumlock = 0x90; // NUM LOCK 键 + + public const byte vbKeyA = 65; + public const byte vbKeyB = 66; + public const byte vbKeyC = 67; + public const byte vbKeyD = 68; + public const byte vbKeyE = 69; + public const byte vbKeyF = 70; + public const byte vbKeyG = 71; + public const byte vbKeyH = 72; + public const byte vbKeyI = 73; + public const byte vbKeyJ = 74; + public const byte vbKeyK = 75; + public const byte vbKeyL = 76; + public const byte vbKeyM = 77; + public const byte vbKeyN = 78; + public const byte vbKeyO = 79; + public const byte vbKeyP = 80; + public const byte vbKeyQ = 81; + public const byte vbKeyR = 82; + public const byte vbKeyS = 83; + public const byte vbKeyT = 84; + public const byte vbKeyU = 85; + public const byte vbKeyV = 86; + public const byte vbKeyW = 87; + public const byte vbKeyX = 88; + public const byte vbKeyY = 89; + public const byte vbKeyZ = 90; + + public const byte vbKey0 = 48; + public const byte vbKey1 = 49; + public const byte vbKey2 = 50; + public const byte vbKey3 = 51; + public const byte vbKey4 = 52; + public const byte vbKey5 = 53; + public const byte vbKey6 = 54; + public const byte vbKey7 = 55; + public const byte vbKey8 = 56; + public const byte vbKey9 = 57; + + public const byte vbKeyNumpad0 = 0x60; + public const byte vbKeyNumpad1 = 0x61; + public const byte vbKeyNumpad2 = 0x62; + public const byte vbKeyNumpad3 = 0x63; + public const byte vbKeyNumpad4 = 0x64; + public const byte vbKeyNumpad5 = 0x65; + public const byte vbKeyNumpad6 = 0x66; + public const byte vbKeyNumpad7 = 0x67; + public const byte vbKeyNumpad8 = 0x68; + public const byte vbKeyNumpad9 = 0x69; + public const byte vbKeyMultiply = 0x6A; // MULTIPLICATIONSIGN (*) + public const byte vbKeyAdd = 0x6B; // PLUS SIGN (+) + public const byte vbKeySeparator = 0x6C; // ENTER + public const byte vbKeySubtract = 0x6D; // MINUS SIGN (-) + public const byte vbKeyDecimal = 0x6E; // DECIMAL POINT (.) + public const byte vbKeyDivide = 0x6F; // DIVISION SIGN (/) + + public const byte vbKeyF1 = 0x70; + public const byte vbKeyF2 = 0x71; + public const byte vbKeyF3 = 0x72; + public const byte vbKeyF4 = 0x73; + public const byte vbKeyF5 = 0x74; + public const byte vbKeyF6 = 0x75; + public const byte vbKeyF7 = 0x76; + public const byte vbKeyF8 = 0x77; + public const byte vbKeyF9 = 0x78; + public const byte vbKeyF10 = 0x79; + public const byte vbKeyF11 = 0x7A; + public const byte vbKeyF12 = 0x7B; + + #endregion + } } diff --git a/Apewer/Internals/Interop/DWM_BLURBEHIND.cs b/Apewer/Internals/Interop/DWM_BLURBEHIND.cs new file mode 100644 index 0000000..e452b7d --- /dev/null +++ b/Apewer/Internals/Interop/DWM_BLURBEHIND.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace Apewer.Internals.Interop +{ + + [StructLayout(LayoutKind.Sequential)] + internal class DWM_BLURBEHIND + { + public uint dwFlags; + [MarshalAs(UnmanagedType.Bool)] + public bool fEnable; + public IntPtr hRegionBlur; + [MarshalAs(UnmanagedType.Bool)] + public bool fTransitionOnMaximized; + + public const uint DWM_BB_ENABLE = 0x00000001; + public const uint DWM_BB_BLURREGION = 0x00000002; + public const uint DWM_BB_TRANSITIONONMAXIMIZED = 0x00000004; + } + +} diff --git a/Apewer/Internals/Interop/DWM_THUMBNAIL_PROPERTIES.cs b/Apewer/Internals/Interop/DWM_THUMBNAIL_PROPERTIES.cs new file mode 100644 index 0000000..2ec5cae --- /dev/null +++ b/Apewer/Internals/Interop/DWM_THUMBNAIL_PROPERTIES.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace Apewer.Internals.Interop +{ + + [StructLayout(LayoutKind.Sequential)] + internal class DWM_THUMBNAIL_PROPERTIES + { + public uint dwFlags; + public RECT rcDestination; + public RECT rcSource; + public byte opacity; + [MarshalAs(UnmanagedType.Bool)] + public bool fVisible; + [MarshalAs(UnmanagedType.Bool)] + public bool fSourceClientAreaOnly; + public const uint DWM_TNP_RECTDESTINATION = 0x00000001; + public const uint DWM_TNP_RECTSOURCE = 0x00000002; + public const uint DWM_TNP_OPACITY = 0x00000004; + public const uint DWM_TNP_VISIBLE = 0x00000008; + public const uint DWM_TNP_SOURCECLIENTAREAONLY = 0x00000010; + } + +} diff --git a/Apewer/Internals/Interop/DwmApi.cs b/Apewer/Internals/Interop/DwmApi.cs new file mode 100644 index 0000000..f369047 --- /dev/null +++ b/Apewer/Internals/Interop/DwmApi.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace Apewer.Internals.Interop +{ + + class DwmApi + { + + [DllImport("dwmapi.dll", PreserveSig = false)] + public static extern void DwmEnableBlurBehindWindow(IntPtr hWnd, DWM_BLURBEHIND pBlurBehind); + + [DllImport("dwmapi.dll", PreserveSig = false)] + public static extern void DwmEnableComposition(bool bEnable); + + [DllImport("dwmapi.dll", PreserveSig = false)] + public static extern void DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS margins); + + [DllImport("dwmapi.dll", PreserveSig = false)] + public static extern void DwmExtendFrameIntoClientArea(IntPtr hWnd, MARGINS pMargins); + + [DllImport("dwmapi.dll", PreserveSig = false)] + public static extern void DwmGetColorizationColor(out int pcrColorization, [MarshalAs(UnmanagedType.Bool)] out bool pfOpaqueBlend); + + [DllImport("dwmapi.dll", PreserveSig = false)] + public static extern bool DwmIsCompositionEnabled(); + + [DllImport("dwmapi.dll", PreserveSig = false)] + public static extern void DwmQueryThumbnailSourceSize(IntPtr hThumbnail, out Size size); + + [DllImport("dwmapi.dll", PreserveSig = false)] + public static extern IntPtr DwmRegisterThumbnail(IntPtr dest, IntPtr source); + + [DllImport("dwmapi.dll", PreserveSig = false)] + public static extern void DwmUnregisterThumbnail(IntPtr hThumbnail); + + [DllImport("dwmapi.dll", PreserveSig = false)] + public static extern void DwmUpdateThumbnailProperties(IntPtr hThumbnail, DWM_THUMBNAIL_PROPERTIES props); + + } + +} diff --git a/Apewer/Internals/Interop/MARGINS.cs b/Apewer/Internals/Interop/MARGINS.cs new file mode 100644 index 0000000..4fb9500 --- /dev/null +++ b/Apewer/Internals/Interop/MARGINS.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace Apewer.Internals.Interop +{ + + [StructLayout(LayoutKind.Sequential)] + internal struct MARGINS + { + public int Left; + public int Right; + public int Top; + public int Bottom; + } + +} diff --git a/Apewer/Internals/Interop/RECT.cs b/Apewer/Internals/Interop/RECT.cs new file mode 100644 index 0000000..2e214d5 --- /dev/null +++ b/Apewer/Internals/Interop/RECT.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace Apewer.Internals.Interop +{ + + [StructLayout(LayoutKind.Sequential)] + internal struct RECT + { + public int Left; + public int Top; + public int Right; + public int Bottom; + + public RECT(int left, int top, int right, int bottom) + { + Left = left; + Top = top; + Right = right; + Bottom = bottom; + } + } + +} diff --git a/Apewer/Internals/Interop/SHCore.cs b/Apewer/Internals/Interop/SHCore.cs new file mode 100644 index 0000000..18c7b0d --- /dev/null +++ b/Apewer/Internals/Interop/SHCore.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace Apewer.Internals.Interop +{ + + internal class SHCore + { + + [DllImport("SHCore.dll", SetLastError = true)] + public static extern bool SetProcessDpiAwareness(PROCESS_DPI_AWARENESS awareness); + + [DllImport("SHCore.dll", SetLastError = true)] + public static extern void GetProcessDpiAwareness(IntPtr hprocess, out PROCESS_DPI_AWARENESS awareness); + + public enum PROCESS_DPI_AWARENESS + { + Process_DPI_Unaware = 0, + Process_System_DPI_Aware = 1, + Process_Per_Monitor_DPI_Aware = 2 + } + + } + +} diff --git a/Apewer/Internals/Interop/User32.cs b/Apewer/Internals/Interop/User32.cs index ee4613a..ab15a30 100644 --- a/Apewer/Internals/Interop/User32.cs +++ b/Apewer/Internals/Interop/User32.cs @@ -131,6 +131,14 @@ namespace Apewer.Internals.Interop [DllImport("user32.dll")] public static extern void keybd_Callback(byte bVk, byte bScan, uint dwFlags, uint dwExtraInfo); + /// 发送键盘事件。 + /// 按键的虚拟键值 + /// 扫描码,一般不用设置,用 0 代替。 + /// 选项标志:0 按下,2 释放。 + /// 扩展信息,默认为 0。 + [DllImport("user32.dll")] + public static extern void keybd_event(byte bVk, byte bScan, int dwFlags, int dwExtraInfo); + /// /// /// diff --git a/Apewer/Logger.cs b/Apewer/Logger.cs index 56f2469..cf55562 100644 --- a/Apewer/Logger.cs +++ b/Apewer/Logger.cs @@ -16,7 +16,6 @@ namespace Apewer public class Logger { - private string _key = Guid.NewGuid().ToString("n"); private string _target = ""; private bool _enabled = true; private Func _preoutput = null; @@ -25,15 +24,15 @@ namespace Apewer private bool _usefile = false; private bool _uselock = false; + /// 当前日志记录器的名称。 + public virtual string Name { get; set; } + /// 已启用。 public virtual bool Enabled { get; set; } /// 在后台线程处理日志。默认值:FALSE。 internal virtual bool Background { get; set; } = false; - /// 捕获并丢弃事件中的异常。默认值:FALSE。 - public virtual bool Catch { get; set; } = false; - /// 使用控制台输出。默认值:TRUE。 public virtual bool UseConsole { @@ -70,15 +69,17 @@ namespace Apewer { if (Background) { - RuntimeUtility.InBackground(action); + RuntimeUtility.InBackground(action, true); return; } - action.Invoke(); + try { action.Invoke(); } catch { } } /// 记录异常。 public virtual void Exception(object sender, Exception exception) => Invoke(() => { + if (!Enabled) return; + if (OnException != null) { OnException(sender, exception); @@ -109,11 +110,13 @@ namespace Apewer } if (UseConsole) ToConsole(item); - if (UseFile) ToFile(item); + if (UseFile) ToFile(item, this); }); private void Colorful(object sender, string tag, Nullable color, object[] content, Event defined) => Invoke(() => { + if (!Enabled) return; + if (defined != null) { defined.Invoke(sender, MergeContent(content)); @@ -127,7 +130,7 @@ namespace Apewer item.Content = MergeContent(content); if (UseConsole) ToConsole(item); - if (UseFile) ToFile(item); + if (UseFile) ToFile(item, this); }); /// 记录错误。多个 Content 参数将以“ | ”分隔。 @@ -149,8 +152,9 @@ namespace Apewer /// 创建新实例。 public Logger() { } - internal Logger(bool useConsole, bool useFile, bool enabled) + private Logger(string name, bool useConsole, bool useFile, bool enabled) { + Name = name; _useconsole = useConsole; _usefile = useFile; _uselock = true; @@ -162,6 +166,9 @@ namespace Apewer internal static object FileLocker = new object(); internal static object ConsoleLocker = new object(); + /// 获取用于保存日志文件的路径。 + public static Func FilePathGetter { get; set; } + private static string MergeContent(params object[] content) => TextUtility.Join(" | ", content); private static string FormatSender(object sender) @@ -254,29 +261,30 @@ namespace Apewer } // 向日志文件输出文本,文件名按日期自动生成。 - private static string ToFile(string plain) + private static string ToFile(string plain, Logger logger) { var text2 = TextUtility.Trim(plain) + "\r\n"; lock (FileLocker) { - var path = GetFileDir(); + var path = GetFilePath(logger); if (string.IsNullOrEmpty(path)) return "写入日志文件失败:无法获取日志文件路径。"; var bytes = TextUtility.ToBinary(TextUtility.Merge(plain, "\r\n")); if (!StorageUtility.AppendFile(path, bytes)) return "写入日志文件失败。"; } return null; - } // 向日志文件输出文本,文件名按日期自动生成。 - private static string ToFile(LogItem item) => ToFile(ToText(item)); + private static string ToFile(LogItem item, Logger logger) => ToFile(ToText(item), logger); /// 获取日志文件路径发生错误时返回 NULL 值。 - /// d:\app\log\2020-02-02.log - /// d:\www\app_data\log\2020-02-02.log - public static string GetFileDir() + /// 默认例:
d:\app\log\1970-01-01.log
d:\www\app_data\log\1970-01-01.log
+ public static string GetFilePath(Logger logger = null) { + var getter = FilePathGetter; + if (getter != null) try { return getter.Invoke(); } catch { } + // 找到 App_Data 目录。 var appDir = RuntimeUtility.ApplicationPath; var dataDir = Path.Combine(appDir, "app_data"); @@ -303,18 +311,18 @@ namespace Apewer #region 默认实列。 - private static Logger _default = new Logger(true, false, true); - private static Logger _console = new Logger(true, false, true); - private static Logger _web = new Logger(true, false, true); + private static Logger _default = new Logger("Apewer.Logger.Default", true, false, true); + private static Logger _console = new Logger("Apewer.Logger.Console", true, false, true); + private static Logger _web = new Logger("Apewer.Logger.Web", true, false, true); #if DEBUG - internal static Logger _internals = new Logger(true, true, true); + internal static Logger _internals = new Logger("Apewer.Logger.Internals", true, true, true); #else - internal static Logger _internals = new Logger(true, false, false); + internal static Logger _internals = new Logger("Apewer.Logger.Internals", true, false, false); #endif /// 内部的日志记录程序。 - public static Logger Internals { get => _internals; } + internal static Logger Internals { get => _internals; } /// 默认的日志记录程序。 public static Logger Default { get => _default; } @@ -325,9 +333,9 @@ namespace Apewer /// 用于 Web 的日志记录程序。 public static Logger Web { get => _web; } -#endregion + #endregion -#region 静态调用。 + #region 静态调用。 /// 使用 Logger.Default 写入日志,自动添加时间和日期,多个 Content 参数将以“ | ”分隔。 public static void Write(params object[] content) => Default.Invoke(() => @@ -340,7 +348,7 @@ namespace Apewer item.Content = MergeContent(content); if (console) ToConsole(item); - if (file) ToFile(item); + if (file) ToFile(item, Default); } }); @@ -356,11 +364,11 @@ namespace Apewer item.Content = MergeContent(content); if (console) ToConsole(item); - if (file) ToFile(item); + if (file) ToFile(item, Default); } }); -#endregion + #endregion } diff --git a/Apewer/NetworkUtility.cs b/Apewer/NetworkUtility.cs index ac3caba..a401f74 100644 --- a/Apewer/NetworkUtility.cs +++ b/Apewer/NetworkUtility.cs @@ -69,9 +69,7 @@ namespace Apewer return GetUtcFromNtp(endpoint, timeout); } } - catch (Exception ex) - { - } + catch { } return ClockUtility.Zero; } @@ -103,9 +101,7 @@ namespace Apewer var utc = (new DateTime(1900, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).AddMilliseconds(milliseconds); return utc; } - catch (Exception ex) - { - } + catch { } return ClockUtility.Zero; } @@ -122,10 +118,9 @@ namespace Apewer #region ARP -#if NET40 || NET461 - /// 发送 ARP 请求,获取目标 IP 地址对应设备的 MAC 地址,MAC 格式为 01-23-45-67-89-AB。 /// 目标 IP 地址。 + /// 注:
仅 Windows 可用。
/// MAC 地址。 public static string SendARP(string ip) { @@ -146,8 +141,6 @@ namespace Apewer return ""; } -#endif - #endregion #region Puny Code diff --git a/Apewer/ObjectSet.cs b/Apewer/ObjectSet.cs index 9f7a46a..3086a5c 100644 --- a/Apewer/ObjectSet.cs +++ b/Apewer/ObjectSet.cs @@ -14,9 +14,9 @@ namespace Apewer { /// - public ObjectSet(bool autotrim) + public ObjectSet(bool autoTrim) { - AutoTrim = autotrim; + AutoTrim = autoTrim; } /// @@ -62,9 +62,9 @@ namespace Apewer } /// 构造函数。 - public ObjectSet(bool autotrim) + public ObjectSet(bool autoTrim) { - _autotrim = autotrim; + _autotrim = autoTrim; _origin = new Dictionary(); } diff --git a/Apewer/RuntimeUtility.cs b/Apewer/RuntimeUtility.cs index 5116277..b57b642 100644 --- a/Apewer/RuntimeUtility.cs +++ b/Apewer/RuntimeUtility.cs @@ -707,40 +707,35 @@ namespace Apewer if (milliseconds > 0) Thread.Sleep(milliseconds); } - /// 在后台线程中执行,指定 Try 将忽略异常。 - [MethodImpl(MethodImplOptions.NoInlining)] - public static Thread InBackground(Action action, bool @try = false) + private static void Invoke(Action action, bool @try = false) { - if (action == null) return null; - Thread thread; + if (action == null) return; if (@try) { - thread = new Thread(delegate (object v) - { - try - { - ((Action)v)(); - } - catch { } - }); + try { action.Invoke(); } catch { } } - else + else { action.Invoke(); } + } + + /// 在后台线程中执行,指定 Try 将忽略 Action 抛出的异常。 + [MethodImpl(MethodImplOptions.NoInlining)] + public static Thread InBackground(Action action, bool @try = false) + { + if (action == null) return null; + var thread = new Thread(delegate (object v) { - thread = new Thread(delegate (object v) - { - ((Action)v)(); - }); - } + Invoke(() => ((Action)v)(), @try); + }); thread.IsBackground = true; thread.Start(action); return thread; } - /// 启动线程。 - public static Thread StartThread(Action action, bool background = true) + /// 启动线程,指定 Try 将忽略 Action 抛出的异常。 + public static Thread StartThread(Action action, bool background = true, bool @try = false) { if (action == null) return null; - var thread = new Thread(new ThreadStart(action)); + var thread = new Thread(new ThreadStart(() => Invoke(action, @try))); thread.IsBackground = background; thread.Start(); return thread; diff --git a/Apewer/StorageUtility.cs b/Apewer/StorageUtility.cs index 6862697..ce1457d 100644 --- a/Apewer/StorageUtility.cs +++ b/Apewer/StorageUtility.cs @@ -172,43 +172,42 @@ namespace Apewer } /// 获取指定目录的子目录,可指定递归子目录。 - public static List GetSubDirectories(string directory, bool recursive = false, bool recurPrecedence = true) => GetSubDirectories(directory, recursive ? -1 : 0, recurPrecedence); + public static List GetSubDirectories(string dirPath, bool recursive = false, bool recurPrecedence = true) => GetSubDirectories(dirPath, recursive ? -1 : 0, recurPrecedence); /// 获取指定目录下子目录的路径。 - /// 顶级目录。 + /// 顶级目录。 /// 子目录递归深度,指定为 0 时不递归,指定为 -1 时无限递归。 /// 优先排列递归项。 - public static List GetSubDirectories(string directory, int recurDepth, bool recurPrecedence = true) + public static List GetSubDirectories(string dirPath, int recurDepth, bool recurPrecedence = true) { var list = new List(); - if (string.IsNullOrEmpty(directory)) return list; + if (string.IsNullOrEmpty(dirPath)) return list; var directories = new string[0]; - try { - directories = Directory.GetDirectories(directory); + directories = Directory.GetDirectories(dirPath); } catch { } - foreach (var d in directories) + foreach (var dir in directories) { var recurlist = new List(); if (recurDepth != 0) { var depth = (recurDepth > 0) ? recurDepth - 1 : recurDepth; - var subrecur = GetSubDirectories(d, depth, recurPrecedence); + var subrecur = GetSubDirectories(dir, depth, recurPrecedence); recurlist.AddRange(subrecur); } if (recurPrecedence) { list.AddRange(recurlist); - list.Add(d); + list.Add(dir); } else { - list.Add(d); + list.Add(dir); list.AddRange(recurlist); } } @@ -217,33 +216,33 @@ namespace Apewer } /// 获取指定目录的子文件,可指定递归子目录。 - public static List GetSubFiles(string path, bool recursive = false, bool recurPrecedence = true) => GetSubFiles(path, recursive ? -1 : 0, recurPrecedence); + public static List GetSubFiles(string dirPath, bool recursive = false, bool recurPrecedence = true) => GetSubFiles(dirPath, recursive ? -1 : 0, recurPrecedence); /// 获取指定目录下子文件的路径。 - /// 顶级目录。 + /// 顶级目录。 /// 子目录递归深度,指定为 0 时不递归,指定为 -1 时无限递归。 /// 优先排列递归项。 - public static List GetSubFiles(string directory, int recurDepth, bool recurPrecedence = true) + public static List GetSubFiles(string dirPath, int recurDepth, bool recurPrecedence = true) { var list = new List(); - if (string.IsNullOrEmpty(directory)) return list; + if (string.IsNullOrEmpty(dirPath)) return list; var dirs = new List(); if (recurDepth == 0) { - dirs.Add(directory); + dirs.Add(dirPath); } else { - var recurdicrotylist = GetSubDirectories(directory, recurDepth, recurPrecedence); + var recurdicrotylist = GetSubDirectories(dirPath, recurDepth, recurPrecedence); if (recurPrecedence) { dirs.AddRange(recurdicrotylist); - dirs.Add(directory); + dirs.Add(dirPath); } else { - dirs.Add(directory); + dirs.Add(dirPath); dirs.AddRange(recurdicrotylist); } } @@ -459,16 +458,61 @@ namespace Apewer } /// 读取文件,获取文件内容。当文件为 UTF-8 文本文件时,可去除 BOM 头。 + /// 注:
字节数组最大为 2GB;
此方法不抛出异常,读取失时返回空字节数组。
public static byte[] ReadFile(string path, bool wipeBom = false) { + const int Buffer = 1048576; + if (!FileExists(path)) return Constant.EmptyBytes; lock (Locker) { try { - var bytes = File.ReadAllBytes(path); - if (wipeBom) bytes = BinaryUtility.WipeTextBom(bytes); - return bytes; + var result = new byte[0]; + using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + if (!stream.CanRead) return Constant.EmptyBytes; + stream.Position = 0; + + var length = stream.Length; + if (length < 1) return Constant.EmptyBytes; + if (length > int.MaxValue) return Constant.EmptyBytes; + + var offset = 0L; + if (wipeBom && length >= 3) + { + var head = new byte[3]; + stream.Read(head, 0, 3); + if (BinaryUtility.ContainsBOM(head)) + { + var capacity = length - 3; + result = new byte[capacity]; + } + else + { + result = new byte[length]; + result[0] = head[0]; + result[1] = head[1]; + result[2] = head[2]; + offset = 3; + } + } + else + { + result = new byte[length]; + } + + var block = new byte[Buffer]; + while (true) + { + var read = stream.Read(block, 0, Buffer); + if (read < 1) break; + + Array.Copy(block, 0, result, offset, read); + offset += read; + } + } + return result; } catch { } } diff --git a/Apewer/Surface/FormsUtility.cs b/Apewer/Surface/FormsUtility.cs index a701b47..9deaa94 100644 --- a/Apewer/Surface/FormsUtility.cs +++ b/Apewer/Surface/FormsUtility.cs @@ -6,6 +6,7 @@ using System.IO; using System.Runtime.InteropServices; using System.Security; using System.Reflection; +using System.Diagnostics; #if NETFX || NETCORE using System.Drawing.Drawing2D; @@ -14,6 +15,8 @@ using System.Drawing.Text; using System.Windows.Forms; #endif +using static Apewer.Internals.Interop.SHCore; + namespace Apewer.Surface { @@ -25,15 +28,40 @@ namespace Apewer.Surface /// 线程锁。 public static object ThreadLocker = new object(); + /// 已使用系统 DPI 设置。 + public static bool UsedSystemDPI { get; private set; } + + internal static Nullable DpiScale { get; set; } + #if NETFX || NETCORE /// 窗体启动初始化。 [STAThread] - public static void StartInitialization() + public static void StartInitialization(bool useSystemDPI = false) { Control.CheckForIllegalCrossThreadCalls = false; Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); + // Application.SetCompatibleTextRenderingDefault(true); + + if (useSystemDPI) UseSystemDPI(); + } + + /// 使用系统的 DPI 设置。 + public static void UseSystemDPI() + { + if (UsedSystemDPI) return; + UsedSystemDPI = true; + +#if NETCORE + Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); +#else + SetProcessDpiAwareness(PROCESS_DPI_AWARENESS.Process_System_DPI_Aware); + Marshal.GetLastWin32Error(); + PROCESS_DPI_AWARENESS awareness; + GetProcessDpiAwareness(Process.GetCurrentProcess().Handle, out awareness); + Marshal.GetLastWin32Error(); +#endif } /// 禁用跨线程调用检查。 @@ -376,6 +404,16 @@ namespace Apewer.Surface #endregion + #region Aero + + /// 获取 DWM 的 Composition 启用状态。 + public static bool DwmIsCompositionEnabled + { + get => DwmApi.DwmIsCompositionEnabled(); + } + + #endregion + #region 窗体。 #if NETFX || NETCORE diff --git a/Apewer/Surface/NormalForm.cs b/Apewer/Surface/NormalForm.cs index 0ed8064..221110d 100644 --- a/Apewer/Surface/NormalForm.cs +++ b/Apewer/Surface/NormalForm.cs @@ -1,8 +1,11 @@ #if NETFX || NETCORE +using Apewer.Internals.Interop; using System; using System.Collections.Generic; +using System.Drawing; using System.Text; +using System.Windows.Forms; namespace Apewer.Surface { @@ -47,6 +50,69 @@ namespace Apewer.Surface InitializeComponent(); } + /// 获取 DPI 缩放比例。 + public double DpiScale + { + get + { + if (FormsUtility.DpiScale == null) + { + using (var g = CreateGraphics()) + { + FormsUtility.DpiScale = g.DpiX / 96F; + } + } + return FormsUtility.DpiScale.Value; + } + } + + /// + /// + /// + /// + protected override void WndProc(ref Message m) + { + // WM_DPICHANGED = 0x02E0 + base.WndProc(ref m); + } + + #region Aero + + private bool AeroEnabled = false; + + /// 启用 Aero 透明效果。 + private void EnableAero() + { + AeroEnabled = true; + Load += (s, e) => + { + if (!FormsUtility.DwmIsCompositionEnabled) return; + + var width = Width; + var height = Height; + + var margins = new MARGINS(); + // margins.Right = -1; // 全窗体透明。 + margins.Right = margins.Left = margins.Top = margins.Bottom = width + height; + DwmApi.DwmExtendFrameIntoClientArea(Handle, ref margins); + }; + } + + /// + protected override void OnPaintBackground(PaintEventArgs e) + { + base.OnPaintBackground(e); + if (AeroEnabled) + { + if (FormsUtility.DwmIsCompositionEnabled) + { + e.Graphics.Clear(Color.Black); + } + } + } + + #endregion + } } diff --git a/Apewer/SystemUtility.cs b/Apewer/SystemUtility.cs index 5b461d1..14fc07b 100644 --- a/Apewer/SystemUtility.cs +++ b/Apewer/SystemUtility.cs @@ -18,6 +18,11 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading; +using static Apewer.Internals.Interop.Constant; +using static Apewer.Internals.Interop.AdvApi32; +using static Apewer.Internals.Interop.Kernel32; +using static Apewer.Internals.Interop.User32; + namespace Apewer { @@ -48,6 +53,36 @@ namespace Apewer /// 当前操作系统是 Linux。 public static bool IsLinux { get => CheckOsType() == 3; } +#if NETFX + + /// 获取内存容量。 + public static long RamTotal + { + get + { + try + { + var mos = new ManagementObjectSearcher(); + mos.Query = new SelectQuery("win32_physicalmemory", "", new string[] { "capacity" }); + var moc = mos.Get(); + var total = 0L; + foreach (var mbo in moc) + { + var value = mbo.Properties["capacity"].Value; + if (value != null) + { + total += long.Parse(value.ToString()); + } + } + return total; + } + catch { } + return 0; + } + } + +#endif + #endregion #region 进程。 @@ -323,40 +358,30 @@ namespace Apewer #endregion - #region 硬件。 + #region 硬件 & 电源。 private static void ExitWindows(int flag) { - bool ok; TokenPrivilege tp; - IntPtr hproc = Kernel32.GetCurrentProcess(); + IntPtr hproc = GetCurrentProcess(); IntPtr htok = IntPtr.Zero; - ok = AdvApi32.OpenProcessToken(hproc, Constant.TOKEN_ADJUST_PRIVILEGES | Constant.TOKEN_QUERY, ref htok); + OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok); tp.Count = 1; tp.Luid = 0; - tp.Attr = Constant.SE_PRIVILEGE_ENABLED; - ok = AdvApi32.LookupPrivilegeValueA(null, Constant.SE_SHUTDOWN_NAME, ref tp.Luid); - ok = AdvApi32.AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero); - ok = User32.ExitWindowsEx(flag, 0); + tp.Attr = SE_PRIVILEGE_ENABLED; + LookupPrivilegeValue(null, SE_SHUTDOWN_NAME, ref tp.Luid); + AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero); + ExitWindowsEx(flag, 0); } - /// 强制关机。 - public static void Shutdown() - { - ExitWindows(Constant.EWX_FORCE | Constant.EWX_POWEROFF); - } + /// 关机。 + public static void Shutdown() => ExitWindows(EWX_FORCE | EWX_POWEROFF); - /// 强制重启。 - public static void Reboot() - { - ExitWindows(Constant.EWX_FORCE | Constant.EWX_REBOOT); - } + /// 重启。 + public static void Reboot() => ExitWindows(EWX_FORCE | EWX_REBOOT); - /// 强制注销。 - public static void LogOff() - { - ExitWindows(Constant.EWX_FORCE | Constant.EWX_LOGOFF); - } + /// 注销。 + public static void LogOff() => ExitWindows(EWX_FORCE | EWX_LOGOFF); #if NETFX @@ -561,12 +586,22 @@ namespace Apewer User32.mouse_Callback(MouseCallbackFlag.RightUp, x, y, 0, UIntPtr.Zero); } + private static IntPtr GetHINSTANCE(Module m) + { + if ((object)m == null) + { + throw new ArgumentNullException("m"); + } + return (IntPtr)(-1); + } + /// 钩住鼠标。当前线程必须具有消息循环。 private static int HookMouse(Func func) { if (func == null) return 0; var proc = new HookProc(LowLevelMouseProc); - var hInstance = Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]); + // var hInstance = Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]); + var hInstance = GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]); var hook = 0; hook = User32.SetWindowsHookEx(Constant.WH_MOUSE_LL, (int nCode, IntPtr wParam, IntPtr lParam) => { diff --git a/Apewer/Web/ApiProgram.cs b/Apewer/Web/ApiProgram.cs index a0d2059..73355ad 100644 --- a/Apewer/Web/ApiProgram.cs +++ b/Apewer/Web/ApiProgram.cs @@ -23,7 +23,7 @@ namespace Apewer.Web #if NETFX || NETCORE /// 在当前线程运行标准应用程序消息循环。 - public static void RunMessageLoop() => Application.Run(); + protected static void RunMessageLoop() => Application.Run(); #endif @@ -106,7 +106,6 @@ namespace Apewer.Web public partial class ApiProgram : IHttpHandler, IHttpModule { - private bool _initialized = false; private Action _before = null; /// 仅运行在 IIS 中时会对此类型创建实例。 @@ -116,7 +115,25 @@ namespace Apewer.Web _before = before; } - #region IHttpHandler + #region Initialize + + private static object InitLocker = new object(); + private bool _initialized = false; + + private void Initialize(Action action) + { + if (_initialized) return; + lock (InitLocker) + { + if (_initialized) return; + action?.Invoke(); + _initialized = true; + } + } + + #endregion + + #region IHttpHandler /// public bool IsReusable { get { return false; } } @@ -138,18 +155,13 @@ namespace Apewer.Web // return; //} - if (!_initialized) - { - _initialized = true; - var before = _before; - before?.Invoke(); - } + Initialize(_before); ApiInvoker.IIS(context); } - #endregion + #endregion - #region IHttpModule + #region IHttpModule /// public void Dispose() { } @@ -166,7 +178,7 @@ namespace Apewer.Web }; } - #endregion + #endregion } diff --git a/Apewer/Web/CronLog.cs b/Apewer/Web/CronLog.cs index 68f5447..547b31b 100644 --- a/Apewer/Web/CronLog.cs +++ b/Apewer/Web/CronLog.cs @@ -44,7 +44,7 @@ namespace Apewer.Web } /// 向控制台写入文本的 Log 处理程序。 - public static void Console(string text) => Logger.Console.Text(nameof (CronLog), text); + public static void Console(string text) => Logger.Web.Text("Cron", text); } diff --git a/Apewer/Web/StaticController.cs b/Apewer/Web/StaticController.cs index e4efc92..71301d8 100644 --- a/Apewer/Web/StaticController.cs +++ b/Apewer/Web/StaticController.cs @@ -14,11 +14,28 @@ namespace Apewer.Web public StaticController() { AllowFunction = false; - AfterInitialized = Execute; + AfterInitialized = () => + { + if (Request == null || Request.Url == null) return; + var path = MapPath(Request.Url.AbsolutePath); + if (StorageUtility.DirectoryExists(path)) + { + ExecuteDirectory(path); + return; + } + if (StorageUtility.FileExists(path)) + { + ExecuteFile(path); + return; + } + Respond404(path); + }; } + #region property + /// 获取此静态站点的目录。 - public virtual string GetSiteDirectory() + protected virtual string GetSiteDirectory() { var app = WebUtility.AppDirectory; var www = StorageUtility.CombinePath(app, "www"); @@ -26,6 +43,34 @@ namespace Apewer.Web return www; } + /// 响应 403 状态。 + protected virtual void Respond403(string path) => Response.Error("403"); + + /// 响应 404 状态。 + protected virtual void Respond404(string path) => Response.Error("404"); + + /// 从扩展名获取内容类型。 + /// 例:
GetContentType("txt") = "text/plain"
+ protected virtual string GetContentType(string extension) => Mime(extension); + + /// 按扩展名获取过期时间,单位为秒。默认值为 0(不缓存)。 + /// 例:
GetExpires("txt") = 0
+ protected virtual int GetExpires(string extension) => 0; + + /// 访问目录时,允许列出子项。默认值:false,不允许。 + protected virtual bool AllowListChildren() => false; + + string PrivateGetContentType(string extension) + { + var result = GetContentType(extension); + if (string.IsNullOrEmpty(result)) result = Mime(extension); + return result; + } + + #endregion + + #region common + /// 解析 URL 的路径,获取本地路径。 protected string MapPath(string urlPath) { @@ -43,24 +88,6 @@ namespace Apewer.Web return path; } - /// 处理请求。 - public virtual void Execute() - { - if (Request == null || Request.Url == null) return; - var path = MapPath(Request.Url.AbsolutePath); - if (StorageUtility.DirectoryExists(path)) - { - ExecuteDirectory(path); - return; - } - if (StorageUtility.FileExists(path)) - { - ExecuteFile(path); - return; - } - Respond404(path); - } - void ExecuteFile(string path) { if (!File.Exists(path)) @@ -78,8 +105,7 @@ namespace Apewer.Web if (expires > 0) Response.Expires = expires; // 按扩展名获取 Content-Type。 - var type = GetContentType(ext); - // var name = Path.GetFileName(path); + var type = PrivateGetContentType(ext); // Server Side Includes if (ext == "html" || ext == "htm" || ext == "shtml") @@ -101,13 +127,89 @@ namespace Apewer.Web Respond404(path); return; } + var @default = GetDefaultFile(path); - if (string.IsNullOrEmpty(@default)) Respond403(path); - else ExecuteFile(@default); + if (!string.IsNullOrEmpty(@default)) + { + ExecuteFile(@default); + return; + } + + if (AllowListChildren()) + { + Response.Data = ListChildren(path); + } + Respond403(path); } + // Server Side Includes + string ReadWithSSI(string path, int recursive = 0) + { + if (recursive > 10) return ""; + + var input = StorageUtility.ReadFile(path, true); + if (input == null || input.LongLength < 1) return ""; + + // 尝试以 UTF-8 解码。 + var html = TextUtility.FromBinary(input); + if (string.IsNullOrEmpty(html)) return ""; + + // 按首尾截取。 + const string left = ""; + const string head = "#include virtual="; + var sb = new StringBuilder(); + var text = html; + while (true) + { + var offset = text.IndexOf(left); + if (offset < 0) + { + sb.Append(text); + break; + } + if (offset > 0) + { + sb.Append(text.Substring(0, offset)); + text = text.Substring(offset + left.Length); + } + else text = text.Substring(left.Length); + var length = text.IndexOf(right); + if (length < 1) + { + sb.Append(left); + sb.Append(text); + break; + } + var inner = text.Substring(0, length); + var temp = inner.SafeTrim(); + if (temp.StartsWith(head)) + { + temp = temp.Substring(head.Length); + temp = temp.Replace("\"", ""); + var subPath = MapPath(temp); + var subText = ReadWithSSI(subPath, recursive + 1); + if (subText != null && subText.Length > 0) sb.Append(subText); + } + else + { + sb.Append(left); + sb.Append(inner); + sb.Append(right); + } + text = text.Substring(length + right.Length); + } + + var output = sb.ToString(); + return output; + } + + #endregion + + #region static + // 获取目录中的默认文件,返回文件的完整路径。 - string GetDefaultFile(string directory) + static string GetDefaultFile(string directory) { var subs = StorageUtility.GetSubFiles(directory); if (subs.Count < 0) return null; @@ -130,18 +232,16 @@ namespace Apewer.Web } /// 列出子项。可指定筛选器,传入完整路径。 - public void ListChildren(string directory) + static Json ListChildren(string directory) { - if (!Directory.Exists(directory)) return; - + if (!Directory.Exists(directory)) return null; var json = Json.NewObject(); json.SetProperty("directories", ListDirectories(directory)); json.SetProperty("files", ListFiles(directory)); - - Response.Data = json; + return json; } - Json ListDirectories(string directory) + static Json ListDirectories(string directory) { var array = Json.NewArray(); var subs = StorageUtility.GetSubDirectories(directory); @@ -171,7 +271,7 @@ namespace Apewer.Web return array; } - Json ListFiles(string directory) + static Json ListFiles(string directory) { var array = Json.NewArray(); var subs = StorageUtility.GetSubFiles(directory); @@ -199,20 +299,7 @@ namespace Apewer.Web return array; } - /// 响应 403 状态。 - public virtual void Respond403(string path) - { - Response.Error("403"); - } - - /// 响应 404 状态。 - public virtual void Respond404(string path) - { - Response.Error("404"); - } - - /// 按扩展名获取 Content-Type。 - public virtual string GetContentType(string extension) + static string Mime(string extension) { if (!string.IsNullOrEmpty(extension)) { @@ -335,70 +422,7 @@ namespace Apewer.Web return "application/octet-stream"; } - /// 按扩展名获取过期时间,单位为秒,默认值为 0(不缓存)。 - public virtual int GetExpires(string extension) => 0; - - // Server Side Includes - string ReadWithSSI(string path, int recursive = 0) - { - if (recursive > 10) return ""; - - var input = StorageUtility.ReadFile(path, true); - if (input == null || input.LongLength < 1) return ""; - - // 尝试以 UTF-8 解码。 - var html = TextUtility.FromBinary(input); - if (string.IsNullOrEmpty(html)) return ""; - - // 按首尾截取。 - const string left = ""; - const string head = "#include virtual="; - var sb = new StringBuilder(); - var text = html; - while (true) - { - var offset = text.IndexOf(left); - if (offset < 0) - { - sb.Append(text); - break; - } - if (offset > 0) - { - sb.Append(text.Substring(0, offset)); - text = text.Substring(offset + left.Length); - } - else text = text.Substring(left.Length); - var length = text.IndexOf(right); - if (length < 1) - { - sb.Append(left); - sb.Append(text); - break; - } - var inner = text.Substring(0, length); - var temp = inner.SafeTrim(); - if (temp.StartsWith(head)) - { - temp = temp.Substring(head.Length); - temp = temp.Replace("\"", ""); - var subPath = MapPath(temp); - var subText = ReadWithSSI(subPath, recursive + 1); - if (subText != null && subText.Length > 0) sb.Append(subText); - } - else - { - sb.Append(left); - sb.Append(inner); - sb.Append(right); - } - text = text.Substring(length + right.Length); - } - - var output = sb.ToString(); - return output; - } + #endregion } diff --git a/Apewer/_ChangeLog.md b/Apewer/_ChangeLog.md index 183a601..fd15662 100644 --- a/Apewer/_ChangeLog.md +++ b/Apewer/_ChangeLog.md @@ -1,6 +1,12 @@  ### 最新提交 +### 6.3.3 +- 全局:恢复 .NET Standard 2.0 的输出,用于支持 .NET Framework 4.6.1; +- StorageUtility:优化了 ReadFile 的内存占用; +- Web:优化 ApiProgram 的初始化,减少并发时的问题; +- Web:Static 控制器支持列出子项。 + ### 6.3.2 - RuntimeUtility:调整了 Assembly 的方法名称; - Surface:迁移获取字形的方法到 FormsUtility,不再提供 FontInstance 类;