From 4eecf3792d4b9957a9fffe1be8c3fa70ab83ce1c Mon Sep 17 00:00:00 2001 From: Danil Iarosh Date: Tue, 13 Jan 2026 10:58:55 +0300 Subject: [PATCH] Enhance image caching reliability and URL handling --- CORP-MAUI/Models/Post.cs | 7 +++++- CORP-MAUI/Pages/PostsPage.xaml.cs | 34 ++++++++++++++++++++++----- CORP-MAUI/ViewModels/PostViewModel.cs | 12 ++++++++-- 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/CORP-MAUI/Models/Post.cs b/CORP-MAUI/Models/Post.cs index 19e362a..0ce3c1a 100644 --- a/CORP-MAUI/Models/Post.cs +++ b/CORP-MAUI/Models/Post.cs @@ -37,11 +37,16 @@ public class Post string? url = !string.IsNullOrEmpty(ImageUrl) ? ImageUrl : Image; if (string.IsNullOrEmpty(url)) return null; + // Handle protocol-relative URLs (e.g. //cdn.com/img.jpg) + if (url.StartsWith("//")) + { + return $"https:{url}"; + } + // Если ссылка уже абсолютная - возвращаем как есть if (url.StartsWith("http", StringComparison.OrdinalIgnoreCase)) return url; // Если ссылка относительная - добавляем домен - // Обрабатываем случай, когда url начинается со / или без string cleanUrl = url.TrimStart('/'); return $"https://corp.robotail.pro/{cleanUrl}"; } diff --git a/CORP-MAUI/Pages/PostsPage.xaml.cs b/CORP-MAUI/Pages/PostsPage.xaml.cs index 81e6aef..1a60f1d 100644 --- a/CORP-MAUI/Pages/PostsPage.xaml.cs +++ b/CORP-MAUI/Pages/PostsPage.xaml.cs @@ -62,11 +62,14 @@ public partial class PostsPage try { Uri uri = new Uri(post.DisplayImageUrl); - // Sanitize filename - string fileName = Path.GetFileName(uri.LocalPath); + // Sanitize filename & use extension from URL if available, else .jpg + string extension = Path.GetExtension(uri.LocalPath); + if (string.IsNullOrEmpty(extension)) extension = ".jpg"; + + string fileName = Path.GetFileNameWithoutExtension(uri.LocalPath); fileName = Path.GetInvalidFileNameChars().Aggregate(fileName, (current, invalidChar) => current.Replace(invalidChar, '_')); - string cacheFileName = $"post_img_{post.Id}_{fileName}"; + string cacheFileName = $"post_img_{post.Id}_{fileName}{extension}"; string cacheFilePath = Path.Combine(FileSystem.CacheDirectory, cacheFileName); bool isCached = false; @@ -90,9 +93,28 @@ public partial class PostsPage byte[]? bytes = await ServiceProvider.ApiService.GetImageBytesAsync(post.DisplayImageUrl); if (bytes is { Length: > 0 }) { - await File.WriteAllBytesAsync(cacheFilePath, bytes); - Trace.WriteLine($"[PostsPage] Сохранено в кэш: {cacheFilePath}"); - vm.LocalImagePath = cacheFilePath; + // Basic signature check + bool isLikelyImage = false; + if (bytes.Length > 2) + { + if (bytes[0] == 0xFF && bytes[1] == 0xD8) isLikelyImage = true; // JPEG + else if (bytes[0] == 0x89 && bytes[1] == 0x50) isLikelyImage = true; // PNG + else isLikelyImage = true; // Others + + // Check for HTML + if (bytes.Length > 5 && bytes[0] == '<') isLikelyImage = false; + } + + if (isLikelyImage) + { + await File.WriteAllBytesAsync(cacheFilePath, bytes); + Trace.WriteLine($"[PostsPage] Сохранено в кэш: {cacheFilePath}"); + vm.LocalImagePath = cacheFilePath; + } + else + { + Trace.WriteLine($"[PostsPage] ⚠️ Received bytes are likely NOT an image (starts with {bytes[0]:X}), skipping cache."); + } } } } diff --git a/CORP-MAUI/ViewModels/PostViewModel.cs b/CORP-MAUI/ViewModels/PostViewModel.cs index aad9013..a1d8274 100644 --- a/CORP-MAUI/ViewModels/PostViewModel.cs +++ b/CORP-MAUI/ViewModels/PostViewModel.cs @@ -64,6 +64,13 @@ public class PostViewModel : INotifyPropertyChanged if (!string.IsNullOrEmpty(_localImagePath) && File.Exists(_localImagePath)) { + // Basic check for empty files + try + { + if (new FileInfo(_localImagePath).Length == 0) return null; + } + catch { return null; } + // Используем FromStream для надежности, чтобы не блокировать файл // и гарантировать чтение даже из системных папок return ImageSource.FromStream(() => @@ -72,9 +79,10 @@ public class PostViewModel : INotifyPropertyChanged { return new FileStream(_localImagePath, FileMode.Open, FileAccess.Read, FileShare.Read); } - catch + catch (Exception ex) { - return Stream.Null; + Trace.WriteLine($"[PostViewModel] Error reading image {_localImagePath}: {ex.Message}"); + return null; } }); }