@@ -26,7 +26,6 @@ public partial class FaviconService : IDisposable
26
26
private readonly string _faviconCacheDir ;
27
27
private readonly HttpClient _httpClient ;
28
28
private readonly LocalFaviconExtractor _localExtractor ;
29
-
30
29
private readonly CancellationTokenSource _cts = new ( ) ;
31
30
private readonly ConcurrentDictionary < string , Task < string ? > > _ongoingFetches = new ( StringComparer . OrdinalIgnoreCase ) ;
32
31
@@ -53,7 +52,7 @@ public FaviconService(PluginInitContext context, Settings settings, string tempP
53
52
54
53
_faviconCacheDir = Path . Combine ( context . CurrentPluginMetadata . PluginCacheDirectoryPath , "FaviconCache" ) ;
55
54
Directory . CreateDirectory ( _faviconCacheDir ) ;
56
-
55
+
57
56
_localExtractor = new LocalFaviconExtractor ( context , tempPath ) ;
58
57
59
58
var handler = new HttpClientHandler
@@ -269,6 +268,11 @@ private async Task<FetchResult> FetchAndProcessUrlAsync(Uri faviconUri, Cancella
269
268
return new FetchResult ( tempPath , size ) ;
270
269
}
271
270
}
271
+ catch ( HttpRequestException ex ) when ( ex . InnerException is System . Net . Sockets . SocketException { SocketErrorCode : System . Net . Sockets . SocketError . HostNotFound } )
272
+ {
273
+ // Host not found is a common, expected error for misconfigured favicons. Don't log as a full error.
274
+ _context . API . LogDebug ( nameof ( FaviconService ) , $ "Favicon host not found for URI: { faviconUri } ") ;
275
+ }
272
276
catch ( TaskCanceledException ) when ( ! token . IsCancellationRequested )
273
277
{
274
278
_context . API . LogWarn ( nameof ( FaviconService ) , $ "HttpClient timed out for { faviconUri } .") ;
@@ -334,7 +338,7 @@ private async Task<List<FaviconCandidate>> GetCandidatesFromHtmlAsync(Uri pageUr
334
338
return new List < FaviconCandidate > ( ) ;
335
339
}
336
340
337
- private static List < FaviconCandidate > ParseLinkTags ( string htmlContent , Uri originalBaseUri )
341
+ private List < FaviconCandidate > ParseLinkTags ( string htmlContent , Uri originalBaseUri )
338
342
{
339
343
var candidates = new List < FaviconCandidate > ( ) ;
340
344
var effectiveBaseUri = originalBaseUri ;
@@ -361,12 +365,18 @@ private static List<FaviconCandidate> ParseLinkTags(string htmlContent, Uri orig
361
365
var href = hrefMatch . Groups [ "v" ] . Value ;
362
366
if ( string . IsNullOrWhiteSpace ( href ) ) continue ;
363
367
368
+ _context . API . LogDebug ( nameof ( ParseLinkTags ) , $ "Found potential favicon link. Raw tag: '{ linkTag } ', Extracted href: '{ href } ', Base URI: '{ effectiveBaseUri } '") ;
369
+
364
370
if ( href . StartsWith ( "//" ) )
365
371
{
366
372
href = effectiveBaseUri . Scheme + ":" + href ;
367
373
}
368
374
369
- if ( ! Uri . TryCreate ( effectiveBaseUri , href , out var fullUrl ) ) continue ;
375
+ if ( ! Uri . TryCreate ( effectiveBaseUri , href , out var fullUrl ) )
376
+ {
377
+ _context . API . LogWarn ( nameof ( ParseLinkTags ) , $ "Failed to create a valid URI from href: '{ href } ' and base URI: '{ effectiveBaseUri } '") ;
378
+ continue ;
379
+ }
370
380
371
381
candidates . Add ( new FaviconCandidate ( fullUrl . ToString ( ) , CalculateFaviconScore ( linkTag , fullUrl . ToString ( ) ) ) ) ;
372
382
}
@@ -405,6 +415,9 @@ private static int CalculateFaviconScore(string linkTag, string fullUrl)
405
415
await stream . CopyToAsync ( ms , token ) ;
406
416
ms . Position = 0 ;
407
417
418
+ // The returned 'Size' is the original width of the icon, used for scoring the best favicon.
419
+ // It does not reflect the final dimensions of the resized PNG.
420
+
408
421
// 1. Try to decode as SVG.
409
422
var ( pngData , size ) = TryConvertSvgToPng ( ms ) ;
410
423
if ( pngData is not null ) return ( pngData , size ) ;
@@ -413,7 +426,7 @@ private static int CalculateFaviconScore(string linkTag, string fullUrl)
413
426
// 2. Try to decode as an ICO file to correctly handle multiple frames.
414
427
( pngData , size ) = TryConvertIcoToPng ( ms ) ;
415
428
if ( pngData is not null ) return ( pngData , size ) ;
416
-
429
+
417
430
ms . Position = 0 ;
418
431
// 3. Fallback for simple bitmaps or ICOs that IconBitmapDecoder failed on.
419
432
( pngData , size ) = TryConvertBitmapToPng ( ms ) ;
@@ -477,7 +490,7 @@ private static (byte[]? PngData, int Size) TryConvertIcoToPng(Stream stream)
477
490
478
491
return ( null , 0 ) ;
479
492
}
480
-
493
+
481
494
private ( byte [ ] ? PngData , int Size ) TryConvertBitmapToPng ( Stream stream )
482
495
{
483
496
try
0 commit comments