Enhance image caching reliability and URL handling

This commit is contained in:
Данил Ярош 2026-01-13 10:58:55 +03:00
parent 272ecdac64
commit 4eecf3792d
3 changed files with 44 additions and 9 deletions

View File

@ -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}";
}

View File

@ -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.");
}
}
}
}

View File

@ -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;
}
});
}