Skip to content

Improve time zones handling#41

Merged
meesoft merged 8 commits intomainfrom
features/ImproveTimeZones
Jun 16, 2025
Merged

Improve time zones handling#41
meesoft merged 8 commits intomainfrom
features/ImproveTimeZones

Conversation

@meesoft
Copy link
Owner

@meesoft meesoft commented Jun 15, 2025

Summary by CodeRabbit

  • New Features

    • Added option to always use ExifTool for loading photo metadata.
    • Improved metadata extraction to support Canon-specific timezone offsets and more detailed EXIF information.
    • Enhanced settings window with reorganization and new controls for easier configuration.
  • Bug Fixes

    • Improved compatibility between video processing options and output formats to prevent invalid combinations.
  • Performance

    • Added performance logging for thumbnail and metadata loading times.
  • Refactor

    • Streamlined internal handling of image metadata and file formats for greater accuracy and maintainability.
    • Replaced synchronous metadata loading with asynchronous calls and conditional logic based on settings.
    • Introduced a new decoder for image file directory (IFD) tags to improve metadata parsing.
  • Tests

    • Updated and expanded tests to cover new metadata extraction features and ensure consistent behavior.

@meesoft meesoft marked this pull request as ready for review June 15, 2025 20:46
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jun 15, 2025

Walkthrough

This update introduces a new setting, "ForceUseExifTool," which allows users to always use ExifTool for metadata extraction. Metadata handling methods are refactored to accept image streams, enabling more accurate timestamp and timezone extraction, including Canon-specific offsets. The IFD (Image File Directory) parsing logic is modularized with a new decoder class, and the settings UI is reorganized.

Changes

File(s) Change Summary
PhotoLocator/Settings/ISettings.cs
PhotoLocator/Settings/ObservableSettings.cs
PhotoLocator/Settings/RegistrySettings.cs
Added ForceUseExifTool property to settings interfaces and implementations.
PhotoLocator/Settings/SettingsExtensions.cs Updated AssignSettings to copy the new ForceUseExifTool property.
PhotoLocator/Settings/SettingsWindow.xaml Reorganized UI: grouped related settings, added crop ratio controls, and moved/adjusted margins for clarity.
PhotoLocator/MainViewModel.cs
PhotoLocator/PictureItemViewModel.cs
Conditional metadata loading via ExifTool based on new setting; updated refresh logic; added performance logging.
PhotoLocator/Metadata/ExifHandler.cs Refactored all major metadata methods to accept streams; improved timestamp decoding (Canon offsets, ExifTool);
enhanced metadata enumeration for maker notes.
PhotoLocator/PictureFileFormats/CR2FileFormatHandler.cs Refactored CR2 loading to use new IFD decoder for tag parsing.
PhotoLocator/PictureFileFormats/IfdDecoder.cs Introduced new IfdDecoder class for modular IFD tag parsing from streams.
PhotoLocator/VideoTransformCommands.cs Refined property setters, preview logic, and validation for video processing and format compatibility.
PhotoLocatorTest/Metadata/ExifHandlerTest.cs Updated tests to pass streams to metadata methods; added Canon offset and ExifTool timestamp tests.
PhotoLocatorTest/PhotoLocatorTest.csproj Changed test data files from embedded to output-copied; added new Canon test image.
PhotoLocator/PictureFileFormats/JpegliEncoder.cs Added check for incomplete reads from source stream with IOException.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant MainViewModel
    participant Settings
    participant PictureItemViewModel
    participant ExifHandler
    participant ExifTool

    User->>MainViewModel: Open Settings Dialog
    MainViewModel->>Settings: Read/Write ForceUseExifTool
    User->>MainViewModel: Request Metadata for Image
    MainViewModel->>PictureItemViewModel: LoadMetadataAsync
    alt ForceUseExifTool enabled and ExifToolPath set
        PictureItemViewModel->>ExifHandler: DecodeMetadataUsingExifTool
        ExifHandler->>ExifTool: Extract Metadata
        ExifTool-->>ExifHandler: Metadata Output
    else
        PictureItemViewModel->>ExifHandler: DecodeMetadataAsync (with stream)
        ExifHandler->>ExifHandler: Parse Metadata, Decode Timestamps, etc.
    end
    ExifHandler-->>PictureItemViewModel: Metadata, Timestamp, Location
    PictureItemViewModel-->>MainViewModel: Display Metadata
Loading

Possibly related PRs

  • meesoft/PhotoLocator#28: The main PR and the retrieved PR both modify PhotoLocator/MainViewModel.cs and the metadata handling logic in ExifHandler.cs to support conditional use of ExifTool for metadata enumeration and decoding, indicating a direct relation in implementing ExifTool integration and metadata refresh behavior.
  • meesoft/PhotoLocator#33: The main PR and the retrieved PR both modify the ExifHandler class and related metadata decoding logic to improve timestamp handling with timezone offset awareness, including changes to method signatures and metadata enumeration; thus, their changes are directly related at the code level.

Poem

🐇
A hop, a skip, ExifTool in tow,
Now streams and tags together flow.
Canon’s secrets, offsets found,
In maker notes and bytes abound.
With settings grouped and tests anew,
This bunny celebrates what code can do!
🥕

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate Unit Tests
  • Create PR with Unit Tests
  • Commit Unit Tests in branch features/ImproveTimeZones
  • Post Copyable Unit Tests in Comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai auto-generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (7)
PhotoLocator/Settings/RegistrySettings.cs (1)

83-87: Verify default behaviour & missing-tool fallback

The getter defaults to true when the key is missing (?? 1).
That flips the application into “always‐use ExifTool” mode on the very first launch even if ExifToolPath is empty or ExifTool is not on the PATH.
Down-stream calls such as ExifHandler.DecodeMetadataUsingExifTool will execute and can fail loudly if the tool is unavailable.

Please double-check that:

  1. A graceful fallback / error message exists when ExifTool cannot be located.
  2. Opt-in behaviour is intentional; if you prefer to preserve current behaviour for existing users, consider defaulting to 0.
PhotoLocator/Settings/ISettings.cs (1)

31-32: Drop redundant public for consistency

Interface members are implicitly public; none of the other properties declare the modifier.
Keeping the file stylistically uniform improves readability.

-        public bool ForceUseExifTool { get; set; }
+        bool ForceUseExifTool { get; set; }
PhotoLocator/Settings/ObservableSettings.cs (1)

78-84: Initial value may briefly disagree with registry default

_forceUseExifTool is implicitly false, whereas RegistrySettings’ default is true.
If the UI binds to ObservableSettings before AssignSettings runs, the checkbox will flash unchecked and a PropertyChanged event will fire once the registry value is copied.

Either initialise the backing field to true, or ensure AssignSettings happens before the view models subscribe/bind.

-bool _forceUseExifTool;
+bool _forceUseExifTool = true; // align with RegistrySettings default
PhotoLocator/Settings/SettingsWindow.xaml (1)

67-71: Consider text-wrapping or tooltip to avoid truncated checkbox label

The new “Always use ExifTool …” label is fairly long; on medium-dpi displays it may not fit the 500 px window without being cut off.
Options:

  1. Set TextWrapping="Wrap" and maybe Width="450" on the CheckBox, or
  2. Provide a concise label and move the explanation into a tooltip.
PhotoLocator/VideoTransformCommands.cs (2)

673-676: IsAnyProcessingSelected omits RollingAverageMode / local-contrast check

The helper currently ignores rolling-average / combine-frames operations, which are also “processing”. Add them to avoid falsely permitting “Copy”.


758-760: Runtime validation is correct but throws generic exception

Throwing UserMessageException is fine; consider validating earlier (e.g., disabling OK button) to provide UX feedback before heavy processing dialog opens.

PhotoLocator/Metadata/ExifHandler.cs (1)

750-781: Robust timestamp parsing implementation!

The enhanced logic properly handles:

  • Multiple timestamp field priorities
  • Fractional seconds extraction
  • Various offset field formats
  • Proper culture-specific parsing

Consider documenting the priority order of timestamp fields for future maintainability.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2021c64 and a77aa40.

⛔ Files ignored due to path filters (1)
  • PhotoLocatorTest/TestData/CanonR10-2025-05-11 14.26.42+9.jpg is excluded by !**/*.jpg
📒 Files selected for processing (12)
  • PhotoLocator/MainViewModel.cs (4 hunks)
  • PhotoLocator/Metadata/ExifHandler.cs (17 hunks)
  • PhotoLocator/Metadata/IfdDecoder.cs (1 hunks)
  • PhotoLocator/PictureItemViewModel.cs (2 hunks)
  • PhotoLocator/Settings/ISettings.cs (1 hunks)
  • PhotoLocator/Settings/ObservableSettings.cs (1 hunks)
  • PhotoLocator/Settings/RegistrySettings.cs (1 hunks)
  • PhotoLocator/Settings/SettingsExtensions.cs (1 hunks)
  • PhotoLocator/Settings/SettingsWindow.xaml (1 hunks)
  • PhotoLocator/VideoTransformCommands.cs (5 hunks)
  • PhotoLocatorTest/Metadata/ExifHandlerTest.cs (12 hunks)
  • PhotoLocatorTest/PhotoLocatorTest.csproj (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
PhotoLocator/Settings/ObservableSettings.cs (8)
PhotoLocator/PictureItemViewModel.cs (1)
  • SetProperty (60-67)
PhotoLocator/MainViewModel.cs (1)
  • SetProperty (54-61)
PhotoLocator/AutoTagViewModel.cs (1)
  • SetProperty (62-69)
PhotoLocator/VideoTransformCommands.cs (1)
  • SetProperty (65-72)
PhotoLocator/RenameWindow.xaml.cs (1)
  • SetProperty (76-83)
PhotoLocator/Helpers/CropControl.xaml.cs (1)
  • SetProperty (65-73)
PhotoLocator/LocalContrastViewModel.cs (1)
  • SetProperty (46-53)
PhotoLocator/SlideShowWindow.xaml.cs (1)
  • SetProperty (54-61)
PhotoLocator/Settings/RegistrySettings.cs (1)
PhotoLocator/Settings/IRegistrySettings.cs (2)
  • GetValue (12-12)
  • SetValue (13-13)
PhotoLocator/VideoTransformCommands.cs (2)
PhotoLocator/Settings/ObservableSettings.cs (1)
  • SetProperty (13-20)
PhotoLocator/Helpers/TaskExtensions.cs (1)
  • WithExceptionLogging (14-18)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: build
  • GitHub Check: Analyze (csharp)
  • GitHub Check: build
🔇 Additional comments (12)
PhotoLocator/Settings/SettingsExtensions.cs (1)

17-17: Property correctly propagated

Nice addition—copying ForceUseExifTool here keeps the settings transfer logic complete.

PhotoLocatorTest/PhotoLocatorTest.csproj (1)

47-55: Resource handling change looks good

Switching the JPGs from embedded resources to None with CopyToOutputDirectory=PreserveNewest should reduce assembly size and speeds up test start-up while still making the files available at runtime.

PhotoLocator/VideoTransformCommands.cs (1)

659-661: Copy format automatically overridden only when no processing selected

Good safeguard—auto-selecting copy only when IsAnyProcessingSelected()==false prevents invalid ffmpeg invocations.
LGTM.

PhotoLocatorTest/Metadata/ExifHandlerTest.cs (4)

23-35: LGTM!

The test correctly uses the updated method signatures with stream parameters and properly disposes resources.


174-183: LGTM!

Good test coverage for the new timestamp offset decoding functionality.


185-195: LGTM!

Excellent test coverage for the Canon maker notes timezone offset extraction feature.


350-350: LGTM!

Correct fix for the milliseconds value in the test data.

PhotoLocator/Metadata/ExifHandler.cs (5)

75-76: LGTM!

Proper constant definition for accessing EXIF maker notes.


451-500: LGTM!

Well-implemented Canon maker notes timezone extraction with proper resource management and manufacturer-specific checks.


541-607: LGTM!

Comprehensive implementation for enumerating and decoding maker note metadata with proper handling of different field types.


620-667: LGTM!

Good refactoring that separates timestamp decoding from formatting logic.


674-701: LGTM!

Good API improvement by standardizing to non-nullable string return type, reducing potential null reference issues.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🔭 Outside diff range comments (1)
PhotoLocator/Metadata/ExifHandler.cs (1)

451-493: Canon time-zone extraction – robustness & efficiency improvements

  1. metadata.CameraManufacturer == "Canon" is case-sensitive and fails on typical “Canon Inc.” strings.
  • if (imageStream is not null && metadata.CameraManufacturer == "Canon")
  • if (imageStream is not null &&
  • metadata.CameraManufacturer?.StartsWith("Canon", StringComparison.OrdinalIgnoreCase) == true)
    
  1. A fresh IfdDecoder(imageStream, 12) is created inside the loop for every tag; instantiate once outside to avoid O(N²) seeks.

  2. The TimeSpan.FromMinutes((Int16)(timeZone[1])) cast relies on uint → Int16 truncation for negative offsets.
    Prefer an explicit unchecked cast for readability and to avoid compiler warnings:

    var minutes = unchecked((short)timeZone[1]);
  3. If the Canon tag exists but has unexpected length/value, the method silently falls through and returns a local DateTime, potentially mis-dating photos. Consider logging a debug message or returning null to signal uncertainty.

🧹 Nitpick comments (5)
PhotoLocator/Metadata/ExifHandler.cs (5)

75-76: Consider adding corresponding TIFF path for maker notes

Only the JPEG-style query (ExifMakerNoteQuery1) is defined. Cameras that store maker notes in TIFF/RAW containers will require a /ifd/... variant. Declaring it now avoids a later conditional‐path explosion.


195-201: PNG branch now discards potential time-zone data

DecodeTimeStamp(source, null) deliberately passes null for imageStream, which means the Canon maker-note fallback added below is disabled for PNG files.
If PNGs created by Canon cameras should also benefit from the new logic, forward the same stream you already have instead of null.

- return EncodePngMetadata(exposureTime, location, DecodeTimeStamp(source, null));
+ return EncodePngMetadata(exposureTime, location, DecodeTimeStamp(source, imageStream: null /* <- supply stream if available */));

541-603: Minor performance micro-tuning in recursive enumeration

Great addition decoding maker-note IFDs! Two low-hanging tweaks:

  1. Re-use a single StringBuilder when concatenating short arrays (lines 570-573) to reduce GC pressure in deep metadata trees.

  2. For maker-note parsing the inner IfdDecoder (tagDecoder) can be instantiated once per blob instead of for every tag (see comment above).

These won’t block the PR but are cheap wins.


620-626: Method naming now slightly misleading

GetMetadataString(BitmapMetadata, Stream?) delegates to GetMetadataString(BitmapMetadata, DateTimeOffset?).
Consider renaming one overload (e.g., BuildMetadataString) or adding XML docs so that IntelliSense reflects the side-effect of decoding the timestamp.


752-777: Fractional-second stripping – guard against malformed input

The current logic assumes the fraction separator is .. Some cameras emit , or more than one fraction block. A regex is safer and avoids multiple index searches:

timeStampStr = Regex.Replace(timeStampStr, @"\.\d+(?=[\+-]|$)", string.Empty);

(Add using System.Text.RegularExpressions;)

Optional, but improves resilience.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a77aa40 and b1b77e8.

📒 Files selected for processing (6)
  • PhotoLocator/Metadata/ExifHandler.cs (17 hunks)
  • PhotoLocator/Metadata/IfdDecoder.cs (1 hunks)
  • PhotoLocator/PictureItemViewModel.cs (2 hunks)
  • PhotoLocator/Settings/RegistrySettings.cs (1 hunks)
  • PhotoLocator/Settings/SettingsWindow.xaml (2 hunks)
  • PhotoLocatorTest/Metadata/ExifHandlerTest.cs (12 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • PhotoLocator/Settings/RegistrySettings.cs
  • PhotoLocator/Settings/SettingsWindow.xaml
  • PhotoLocator/PictureItemViewModel.cs
  • PhotoLocator/Metadata/IfdDecoder.cs
  • PhotoLocatorTest/Metadata/ExifHandlerTest.cs
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: build
  • GitHub Check: Analyze (csharp)
  • GitHub Check: build
🔇 Additional comments (3)
PhotoLocator/Metadata/ExifHandler.cs (3)

528-532: 👍 Stream-aware enumeration call looks good

Passing the opened file stream into the new overload ensures maker-note blobs can be dereferenced. No further issues spotted.


663-665: Good UX – timestamp now always shown when available

The extra FormatTimestampForDisplay injection keeps UI behaviour consistent with ExifTool code path. Nice catch.


674-694: File stream lifetime is safe, but verify for asynchronous callers

DecodeMetadataAsync passes file to DecodeTimeStamp, which does not dispose the stream until using completes – good.
Ensure no future awaits are inserted between DecodeTimeStamp and disposal; otherwise the stream could be used after disposal.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🔭 Outside diff range comments (1)
PhotoLocator/PictureFileFormats/CR2FileFormatHandler.cs (1)

57-64: Guarantee the entire preview image is read & prevent huge allocations

  1. new byte[imageSize] silently truncates when imageSize > int.MaxValueOverflowException.
  2. stream.Read(buf, 0, (int)imageSize) does not guarantee that the full buffer is filled.
-                            var buf = new byte[imageSize];
-                            stream.Read(buf, 0, (int)imageSize);
+                            if (imageSize > int.MaxValue)
+                                throw new FileFormatException("Embedded JPEG preview exceeds .NET array size limit.");
+
+                            var buf = new byte[(int)imageSize];
+                            int read = 0;
+                            while (read < buf.Length)
+                            {
+                                int n = stream.Read(buf, read, buf.Length - read);
+                                if (n == 0)
+                                    throw new EndOfStreamException("Unexpected EOF in embedded JPEG.");
+                                read += n;
+                            }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b1b77e8 and 037bb3c.

📒 Files selected for processing (3)
  • PhotoLocator/Metadata/ExifHandler.cs (17 hunks)
  • PhotoLocator/PictureFileFormats/CR2FileFormatHandler.cs (3 hunks)
  • PhotoLocator/PictureFileFormats/IfdDecoder.cs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • PhotoLocator/Metadata/ExifHandler.cs
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: build
  • GitHub Check: Analyze (csharp)
  • GitHub Check: build

@sonarqubecloud
Copy link

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 303c7df and 40d8849.

📒 Files selected for processing (2)
  • PhotoLocator/PictureFileFormats/CR2FileFormatHandler.cs (3 hunks)
  • PhotoLocator/PictureFileFormats/JpegliEncoder.cs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • PhotoLocator/PictureFileFormats/CR2FileFormatHandler.cs
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Analyze (csharp)
  • GitHub Check: build
  • GitHub Check: build

Comment on lines 55 to 58
var srcBytes = new byte[srcStream.Length];
srcStream.Read(srcBytes, 0, srcBytes.Length);
if (srcStream.Read(srcBytes, 0, srcBytes.Length) < srcBytes.Length)
throw new IOException("Failed to read from source stream");
sourceWriter.Write(srcBytes.Length);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

❓ Verification inconclusive

Loop/ReadExactly instead of single-shot Read to prevent false positives

Stream.Read is allowed to return fewer bytes than requested even when the stream hasn’t ended.
Failing immediately on the first short read can break callers that pass a non-MemoryStream (e.g. NetworkStream, some FileStream implementations) – they’ll now see an IOException where none is warranted.

Use a read-loop or the .NET 7+ helper ReadExactly to fill the buffer deterministically:

-var srcBytes = new byte[srcStream.Length];
-if (srcStream.Read(srcBytes, 0, srcBytes.Length) < srcBytes.Length)
-    throw new IOException("Failed to read from source stream");
+var srcBytes = new byte[srcStream.Length];
+// .NET <7           – manual loop
+int read = 0;
+while (read < srcBytes.Length)
+{
+    var n = srcStream.Read(srcBytes, read, srcBytes.Length - read);
+    if (n == 0)
+        throw new EndOfStreamException();
+    read += n;
+}
+// .NET ≥7 – one-liner
+// srcStream.ReadExactly(srcBytes);

Bonus: consider renting the buffer via ArrayPool<byte>.Shared to avoid large heap allocations for high-resolution images.


Loop/ReadExactly instead of single-shot Read to prevent false positives

Stream.Read is allowed to return fewer bytes than requested even when the stream hasn’t ended.
Failing immediately on the first short read can break callers that pass a non-MemoryStream (e.g. NetworkStream, some FileStream implementations) – they’ll now see an IOException where none is warranted.

Use a read-loop or the .NET 7+ helper ReadExactly to fill the buffer deterministically:

-var srcBytes = new byte[srcStream.Length];
-if (srcStream.Read(srcBytes, 0, srcBytes.Length) < srcBytes.Length)
-    throw new IOException("Failed to read from source stream");
+var srcBytes = new byte[srcStream.Length];
+// .NET <7           – manual loop
+int read = 0;
+while (read < srcBytes.Length)
+{
+    var n = srcStream.Read(srcBytes, read, srcBytes.Length - read);
+    if (n == 0)
+        throw new EndOfStreamException();
+    read += n;
+}
+// .NET ≥7 – one-liner
+// srcStream.ReadExactly(srcBytes);

Bonus: consider renting the buffer via ArrayPool<byte>.Shared to avoid large heap allocations for high-resolution images.

🤖 Prompt for AI Agents
In PhotoLocator/PictureFileFormats/JpegliEncoder.cs around lines 55 to 58, the
code uses a single Read call on the stream which may return fewer bytes than
requested without indicating end-of-stream, causing false IOException. Replace
the single Read call with a loop that continues reading until the entire buffer
is filled or use the .NET 7+ ReadExactly method to ensure the buffer is fully
populated. Additionally, consider renting the byte array buffer from
ArrayPool<byte>.Shared to reduce large heap allocations for high-resolution
images.

@meesoft meesoft merged commit 48ad05e into main Jun 16, 2025
6 checks passed
@meesoft meesoft deleted the features/ImproveTimeZones branch June 16, 2025 20:46
@coderabbitai coderabbitai bot mentioned this pull request Jan 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant