diff --git a/listenarr.application/Audiobooks/Files/AudiobookFileService.PhysicalGeneration.cs b/listenarr.application/Audiobooks/Files/AudiobookFileService.PhysicalGeneration.cs index 3dad98b99..964c765b3 100644 --- a/listenarr.application/Audiobooks/Files/AudiobookFileService.PhysicalGeneration.cs +++ b/listenarr.application/Audiobooks/Files/AudiobookFileService.PhysicalGeneration.cs @@ -274,10 +274,10 @@ private static AudiobookFile CreatePhysicalGenerationSnapshot( string? source, bool replaceMetadata) { - var fileInfo = new FileInfo(registrationLease.MetadataPath); var replacement = AudiobookFile.CreateUnresolved(currentFile.Path); replacement.AudiobookId = currentFile.AudiobookId; - replacement.Size = fileInfo.Exists ? fileInfo.Length : currentFile.Size; + replacement.Size = TryGetRegisteredFileLength(registrationLease.MetadataPath) + ?? currentFile.Size; replacement.DurationSeconds = replaceMetadata ? metadata?.Duration.TotalSeconds : Math.Abs(metadata?.Duration.TotalSeconds ?? 0) > double.Epsilon diff --git a/listenarr.application/Audiobooks/Files/AudiobookFileService.cs b/listenarr.application/Audiobooks/Files/AudiobookFileService.cs index 823fe2f2c..0bdcae55d 100644 --- a/listenarr.application/Audiobooks/Files/AudiobookFileService.cs +++ b/listenarr.application/Audiobooks/Files/AudiobookFileService.cs @@ -327,10 +327,9 @@ private async Task EnsureAudiobookFileCoreAsync( cacheIdentity, filePath); - var fi = new FileInfo(metadataPath); var fileRecord = AudiobookFile.CreateUnresolved(filePath); fileRecord.AudiobookId = audiobook.Id; - fileRecord.Size = fi.Exists ? fi.Length : null; + fileRecord.Size = TryGetRegisteredFileLength(metadataPath); fileRecord.Source = source; fileRecord.CreatedAt = DateTime.UtcNow; fileRecord.DurationSeconds = meta?.Duration.TotalSeconds; @@ -442,6 +441,32 @@ private static string ResolveAbsolutePath(string? path) => ? string.Empty : FileSystemPathIdentity.ResolveNativeAbsolutePath(path); + /// + /// Read the byte length of the file behind a registration metadata path. + /// The pinned lease's metadata path is a /proc/self/fd magic link on + /// Linux, and FileSystemInfo reads the link inode itself, whose reported + /// size is a constant 64 — not the file's length. Opening the path + /// follows the link to the pinned file, so the stream length is the real + /// size of the exact object the lease holds open. + /// + private static long? TryGetRegisteredFileLength(string metadataPath) + { + try + { + using var stream = new FileStream( + metadataPath, + FileMode.Open, + FileAccess.Read, + FileShare.ReadWrite | FileShare.Delete); + return stream.Length; + } + catch (Exception exception) when (exception is IOException + or UnauthorizedAccessException or NotSupportedException) + { + return null; + } + } + private void LogClaimRejection( int audiobookId, string path,