using HeBianGu.Base.WpfBase; using HeBianGu.Control.Explorer; using Hopetry.Services; using Minio.DataModel.Args; using Minio; using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using Microsoft.Extensions.Configuration; using Minio.ApiEndpoints; using System.Windows.Threading; using System.Drawing; using System.Windows.Media.Imaging; using System.Windows.Media; namespace Hopetry.Provider.Behaviors { public class ExplorerMinIOBehavior : Behavior { #region 依赖属性 public bool UseMinIO { get { return (bool)GetValue(UseMinIOProperty); } set { SetValue(UseMinIOProperty, value); } } public static readonly DependencyProperty UseMinIOProperty = DependencyProperty.Register("UseMinIO", typeof(bool), typeof(ExplorerMinIOBehavior), new PropertyMetadata(false, OnUseMinIOChanged)); #endregion #region 字段和初始化 private IMinioClient _minioClient; private bool _isLocalHistoryRefresh; protected override void OnAttached() { base.OnAttached(); InitializeMinIOClient(); // 监听路径变化 var pathDescriptor = DependencyPropertyDescriptor.FromProperty( Explorer.CurrentPathProperty, typeof(Explorer)); pathDescriptor.AddValueChanged(AssociatedObject, OnCurrentPathChanged); // 监听历史记录变化 var historyDescriptor = DependencyPropertyDescriptor.FromProperty( Explorer.HistoryProperty, typeof(Explorer)); historyDescriptor.AddValueChanged(AssociatedObject, OnHistoryChanged); // 初始加载MinIO内容 if (UseMinIO) { Dispatcher.BeginInvoke((Action)(() => { var initialPath = string.IsNullOrEmpty(AssociatedObject.CurrentPath) ? "" : AssociatedObject.CurrentPath; RefreshMinIOPath(initialPath); // 更新CurrentPath如果为空 if (string.IsNullOrEmpty(AssociatedObject.CurrentPath)) { AssociatedObject.CurrentPath = initialPath; } }), DispatcherPriority.Loaded); } } protected override void OnDetaching() { // Clean up event handlers var pathDescriptor = DependencyPropertyDescriptor.FromProperty( Explorer.CurrentPathProperty, typeof(Explorer)); pathDescriptor.RemoveValueChanged(AssociatedObject, OnCurrentPathChanged); var historyDescriptor = DependencyPropertyDescriptor.FromProperty( Explorer.HistoryProperty, typeof(Explorer)); historyDescriptor.RemoveValueChanged(AssociatedObject, OnHistoryChanged); base.OnDetaching(); } #endregion #region 事件处理 private static void OnUseMinIOChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var behavior = (ExplorerMinIOBehavior)d; if ((bool)e.NewValue && behavior.AssociatedObject != null) { behavior.Dispatcher.BeginInvoke((Action)(() => { behavior.RefreshMinIOPath(behavior.AssociatedObject.CurrentPath); }), DispatcherPriority.ContextIdle); } } private void OnCurrentPathChanged(object sender, EventArgs e) { if (UseMinIO) { RefreshMinIOPath(AssociatedObject.CurrentPath); } } private void OnHistoryChanged(object sender, EventArgs e) { // Track when history is changed externally _isLocalHistoryRefresh = true; } #endregion #region MinIO 操作 private void InitializeMinIOClient() { var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("global.json", optional: false, reloadOnChange: true); // 构建配置 var config = builder.Build(); _minioClient = new MinioClient() .WithEndpoint(config["Minio:Endpoint"]) .WithCredentials(config["Minio:AccessKey"], config["Minio:SecretKey"]) .Build(); } private async void RefreshMinIOPath(string path) { if (AssociatedObject == null || _minioClient == null) return; try { AssociatedObject.IsEnabled = false; AssociatedObject.Cursor = System.Windows.Input.Cursors.Wait; var items = await GetMinIOItemsAsync(path); AssociatedObject.ItemsSource = items.ToObservable(); // Update history if this wasn't triggered by a history navigation if (!_isLocalHistoryRefresh && !string.IsNullOrEmpty(path)) { UpdateHistory(path); } _isLocalHistoryRefresh = false; } catch (Exception ex) { MessageBox.Show($"Error accessing MinIO: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); } finally { AssociatedObject.IsEnabled = true; AssociatedObject.Cursor = null; } } private async Task> GetMinIOItemsAsync(string path) { var items = new List(); if (string.IsNullOrEmpty(path)) { // List all buckets var buckets = await _minioClient.ListBucketsAsync(); items.AddRange(buckets.Buckets.Select(b => new MinIODirectoryModel(new MinIODirectoryInfo(b.Name,null,true, b.CreationDateDateTime)))); } else { // List objects in a bucket/path var parts = path.Split(new[] { '/' }, 2); var bucketName = parts[0]; var prefix = parts.Length > 1 ? parts[1] : null; var args = new ListObjectsArgs() .WithBucket(bucketName) .WithPrefix(prefix ?? "") .WithRecursive(false); //var ss= new ListObjectsArgs() // .WithBucket("demo") // .WithPrefix("women") // .WithRecursive(false); var observable = _minioClient.ListObjectsEnumAsync(args); //var observable1 = _minioClient.ListObjectsEnumAsync(ss); await foreach (var item in observable) { if (item.IsDir) { items.Add(new MinIODirectoryModel( new MinIODirectoryInfo(bucketName, item.Key, false, DateTime.Now))); } else { string size = item.Size < 1024 * 1024 ? $"{Math.Ceiling((decimal)item.Size / 1024)}KB" : $"{Math.Ceiling((decimal)item.Size / (1024 * 1024))}MB"; items.Add(new MinIOFileModel( new MinIOFileInfo(bucketName, item.Key, size, item.LastModifiedDateTime))); } } } return items; } private void UpdateHistory(string path) { var parts = path.Split(new[] { '/' }, 2); var bucketName = parts[0]; var prefix = parts.Length > 1 ? parts[1] : null; var dirInfo = new MinIODirectoryInfo(bucketName, prefix,false, DateTime.Now); var historyItem = new DirectoryModel(dirInfo); // Avoid duplicates if (AssociatedObject.History.FirstOrDefault()?.Model?.FullName != path) { AssociatedObject.History.Insert(0, historyItem); AssociatedObject.History = AssociatedObject.History .Take(ExplorerSetting.Instance.HistCapacity) .ToObservable(); } AssociatedObject.HistorySelectedItem = AssociatedObject.History.FirstOrDefault(); } #endregion #region 图标处理 private static readonly Dictionary _iconCache = new Dictionary(); private const string DefaultFontFamily = "Segoe MDL2 Assets"; private const double DefaultIconSize = 16; public static System.Drawing.Icon GetIconForMinIOItem(string name, bool isDirectory, bool isBucket = false) { var cacheKey = $"{(isBucket ? "bucket" : (isDirectory ? "folder" : Path.GetExtension(name).ToLower()))}"; if (!_iconCache.TryGetValue(cacheKey, out var icon)) { icon = LoadMinIOIcon(name, isDirectory, isBucket); _iconCache[cacheKey] = icon; } return icon; } private static System.Drawing.Icon LoadMinIOIcon(string name, bool isDirectory, bool isBucket) { // 获取字体图标字符 string iconChar = GetFontIconChar(name, isDirectory, isBucket); // 创建字体图标ImageSource var imageSource = CreateFontIconImageSource(iconChar); // 转换为Icon return ConvertRenderTargetBitmapToIcon(imageSource as RenderTargetBitmap); } private static System.Drawing.Icon ConvertRenderTargetBitmapToIcon(RenderTargetBitmap renderTargetBitmap) { // 将 RenderTargetBitmap 转为 Bitmap var bitmap = new System.Drawing.Bitmap( renderTargetBitmap.PixelWidth, renderTargetBitmap.PixelHeight, System.Drawing.Imaging.PixelFormat.Format32bppPArgb); var data = bitmap.LockBits( new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppPArgb); renderTargetBitmap.CopyPixels( System.Windows.Int32Rect.Empty, data.Scan0, data.Height * data.Stride, data.Stride); bitmap.UnlockBits(data); // 从 Bitmap 创建 Icon var iconHandle = bitmap.GetHicon(); return System.Drawing.Icon.FromHandle(iconHandle); } private static string GetFontIconChar(string name, bool isDirectory, bool isBucket) { // 使用Segoe MDL2 Assets字体中的字符代码 if (isBucket) { return "\ue7c3"; // Cloud icon } if (isDirectory) { return "\ue8b7"; // Folder icon } // 按文件类型返回不同图标 string extension = Path.GetExtension(name)?.ToLower(); switch (extension) { case ".pdf": return "\uea90"; case ".jpg": case ".png": case ".jpeg": case ".gif": case ".bmp": case ".svg": return "\ueb9f"; // Picture icon case ".doc": case ".docx": return "\ue8a5"; // Word document icon case ".xls": case ".xlsx": return "\ue8a6"; // Excel document icon case ".ppt": case ".pptx": return "\ue8a7"; // PowerPoint icon case ".zip": case ".rar": case ".7z": return "\ue7b8"; // Zip folder icon case ".txt": case ".log": return "\ue8ab"; // Text document icon case ".mp3": case ".wav": case ".flac": return "\ue8d6"; // Audio icon case ".mp4": case ".avi": case ".mov": case ".wmv": return "\ue8b2"; // Video icon case ".exe": case ".msi": return "\ue756"; // Application icon case ".html": case ".htm": return "\ue842"; // HTML icon case ".cs": case ".cpp": case ".js": case ".java": return "\ue9a2"; // Code icon default: return "\ue7c4"; // Default document icon } } private static ImageSource CreateFontIconImageSource( string iconChar, string fontFamily = DefaultFontFamily, double fontSize = DefaultIconSize, System.Windows.Media.Brush foreground = null) { try { var textBlock = new System.Windows.Controls.TextBlock { Text = iconChar, FontFamily = new System.Windows.Media.FontFamily(fontFamily), FontSize = fontSize, Foreground = foreground ?? System.Windows.Media.Brushes.Black, HorizontalAlignment = System.Windows.HorizontalAlignment.Center, VerticalAlignment = System.Windows.VerticalAlignment.Center }; // 计算实际需要的尺寸 textBlock.Measure(new System.Windows.Size(double.PositiveInfinity, double.PositiveInfinity)); textBlock.Arrange(new System.Windows.Rect(textBlock.DesiredSize)); var renderTargetBitmap = new RenderTargetBitmap( (int)Math.Ceiling(textBlock.ActualWidth), (int)Math.Ceiling(textBlock.ActualHeight), 96, 96, PixelFormats.Pbgra32); renderTargetBitmap.Render(textBlock); renderTargetBitmap.Freeze(); return renderTargetBitmap; } catch { // 回退到默认图标 return CreateFontIconImageSource("\ue7c4", fontFamily, fontSize, foreground); } } #endregion } #region MinIO 模型类 public interface IMinIOFileInfo : ISystemFileInfo { string BucketName { get; } bool IsDirectory { get; } bool IsBucket { get; } } public class MinIOFileInfo : IMinIOFileInfo, IFileInfo { public MinIOFileInfo(string bucketName, string key, string size, DateTime? lastModified) { BucketName = bucketName; FullName = $"{bucketName}/{key}"; Name = Path.GetFileName(key); Size = size; LastModified = lastModified; } public string BucketName { get; } public bool IsDirectory => false; public bool IsBucket => false; public string Name { get; } public string FullName { get; } public string Size { get; } public DateTime? LastModified { get; } public DateTime? LastWriteTime => LastModified; public FileAttributes Attributes => FileAttributes.Normal; } public class MinIODirectoryInfo : IMinIOFileInfo, IDirectoryInfo { public MinIODirectoryInfo(string bucketName, string key, bool isBucket, DateTime creationDate) { BucketName = bucketName; FullName = string.IsNullOrEmpty(key) ? bucketName : $"{bucketName}/{key}"; Name = string.IsNullOrEmpty(key) ? bucketName : key.TrimEnd('/').Split('/').Last(); IsBucket = isBucket; LastWriteTime = creationDate; } public string BucketName { get; } public bool IsDirectory => true; public bool IsBucket { get; } public string Name { get; } public string FullName { get; } public DateTime LastWriteTime { get; } public FileAttributes Attributes => FileAttributes.Directory; } public class MinIOFileModel : FileModel { public MinIOFileModel(IFileInfo model) : base(model) { this.Icon = "\ue7c4"; // 默认文件图标 } public new Icon Logo => ExplorerMinIOBehavior.GetIconForMinIOItem( this.Model.Name, isDirectory: false); } public class MinIODirectoryModel : DirectoryModel { public MinIODirectoryModel(IDirectoryInfo model) : base(model) { this.Icon = "\ue8b7"; // 默认文件夹图标 } public new Icon Logo => ExplorerMinIOBehavior.GetIconForMinIOItem( this.Model.Name, isDirectory: true, isBucket: (this.Model as IMinIOFileInfo)?.IsBucket ?? false); } #endregion }