pw_stream: Implement StdFileReader::ConservativeLimit

The ConservativeLimit method returns an estimate of how much data is
available to be read or written in a pw::stream. The default
implementation returns a very large value ("unlimited").

This commit implements a file-specific implementation for StdFileReader.
It determines the size of the file (and thus how much data is left to
read) by seeking to the end and getting the current file position. This
makes the ConservativeReadLimit method much more useful for users of the
class.

Change-Id: I55b2518d3dc17ca8c4f98847dc7c3557eb9fda74
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/106130
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
Reviewed-by: Wyatt Hepler <hepler@google.com>
Pigweed-Auto-Submit: Eli Lipsitz <elipsitz@google.com>
diff --git a/pw_stream/public/pw_stream/std_file_stream.h b/pw_stream/public/pw_stream/std_file_stream.h
index 51268ef..fdf7892 100644
--- a/pw_stream/public/pw_stream/std_file_stream.h
+++ b/pw_stream/public/pw_stream/std_file_stream.h
@@ -30,6 +30,7 @@
   StatusWithSize DoRead(ByteSpan dest) override;
   Status DoSeek(ptrdiff_t offset, Whence origin) override;
   size_t DoTell() override;
+  size_t ConservativeLimit(LimitType limit) const override;
 
   std::ifstream stream_;
 };
diff --git a/pw_stream/std_file_stream.cc b/pw_stream/std_file_stream.cc
index 458f9f0..9542721 100644
--- a/pw_stream/std_file_stream.cc
+++ b/pw_stream/std_file_stream.cc
@@ -63,6 +63,30 @@
   return pos < 0 ? kUnknownPosition : pos;
 }
 
+size_t StdFileReader::ConservativeLimit(LimitType limit) const {
+  if (limit == LimitType::kWrite) {
+    return 0;
+  }
+
+  // Attempt to determine the number of bytes left in the file by seeking
+  // to the end and checking where we end up.
+  if (stream_.eof()) {
+    return 0;
+  }
+  auto stream = const_cast<std::ifstream*>(&this->stream_);
+  auto start = stream->tellg();
+  if (start == -1) {
+    return 0;
+  }
+  stream->seekg(0, std::ios::end);
+  auto end = stream->tellg();
+  if (end == -1) {
+    return 0;
+  }
+  stream->seekg(start, std::ios::beg);
+  return end - start;
+}
+
 Status StdFileWriter::DoWrite(ConstByteSpan data) {
   if (stream_.eof()) {
     return Status::OutOfRange();
diff --git a/pw_stream/std_file_stream_test.cc b/pw_stream/std_file_stream_test.cc
index 5c5abad..77882f6 100644
--- a/pw_stream/std_file_stream_test.cc
+++ b/pw_stream/std_file_stream_test.cc
@@ -143,6 +143,8 @@
   writer.Close();
 
   StdFileReader reader(TempFilename());
+  ASSERT_EQ(reader.ConservativeReadLimit(), kTestData.size());
+
   std::array<char, 3> read_buffer;
   size_t read_offset = 0;
   while (read_offset < kTestData.size()) {
@@ -156,17 +158,20 @@
         as_bytes(span(kTestData)).subspan(read_offset, result.value().size());
     EXPECT_TRUE(pw::containers::Equal(result.value(), expect_window));
     read_offset += result.value().size();
+    ASSERT_EQ(reader.ConservativeReadLimit(), kTestData.size() - read_offset);
   }
   // After data has been read, do a final read to trigger EOF.
   Result<ConstByteSpan> result =
       reader.Read(as_writable_bytes(span(read_buffer)));
   EXPECT_EQ(result.status(), Status::OutOfRange());
+  ASSERT_EQ(reader.ConservativeReadLimit(), 0u);
 
   EXPECT_EQ(read_offset, kTestData.size());
 
   // Seek backwards and read again to ensure seek at EOF works.
   ASSERT_EQ(reader.Seek(-1 * read_buffer.size(), Stream::Whence::kEnd),
             OkStatus());
+  ASSERT_EQ(reader.ConservativeReadLimit(), read_buffer.size());
   result = reader.Read(as_writable_bytes(span(read_buffer)));
   EXPECT_EQ(result.status(), OkStatus());