From fea33aa02fc08abb6ab6ca3ffe2aafd70bfe784a Mon Sep 17 00:00:00 2001 From: Zaggy1024 Date: Fri, 15 May 2026 18:35:51 -0500 Subject: [PATCH] LibMedia+Tests: Add a method to remove data from incremental streams This will later be used to test updating of buffered time ranges when data is removed. It will also be needed when data eviction is implemented. --- .../LibMedia/IncrementallyPopulatedStream.cpp | 29 +++++++ .../LibMedia/IncrementallyPopulatedStream.h | 1 + .../TestIncrementallyPopulatedStream.cpp | 82 +++++++++++++++++++ 3 files changed, 112 insertions(+) diff --git a/Libraries/LibMedia/IncrementallyPopulatedStream.cpp b/Libraries/LibMedia/IncrementallyPopulatedStream.cpp index 1530bb9446..5188541b65 100644 --- a/Libraries/LibMedia/IncrementallyPopulatedStream.cpp +++ b/Libraries/LibMedia/IncrementallyPopulatedStream.cpp @@ -95,6 +95,35 @@ void IncrementallyPopulatedStream::add_chunk_at(u64 offset, ReadonlyBytes data) m_state_changed.broadcast(); } +void IncrementallyPopulatedStream::remove_byte_range(u64 start, u64 end) +{ + VERIFY(start < end); + + Sync::MutexLocker locker { m_mutex }; + + auto chunk_iterator = m_chunks.find_largest_not_above_iterator(start); + if (chunk_iterator.is_end() || chunk_iterator->end() <= start) + chunk_iterator = m_chunks.find_smallest_not_below_iterator(start); + + while (!chunk_iterator.is_end() && chunk_iterator->offset() < end) { + auto chunk = m_chunks.remove_and_advance(chunk_iterator); + auto chunk_start = chunk.offset(); + auto chunk_end = chunk.end(); + + if (chunk_start < start) { + auto left_size = start - chunk_start; + m_chunks.insert(chunk_start, DataChunk { chunk_start, MUST(ByteBuffer::copy(chunk.data().bytes().trim(left_size))) }); + } + + if (chunk_end > end) { + auto right_offset_in_chunk = end - chunk_start; + m_chunks.insert(end, DataChunk { end, MUST(ByteBuffer::copy(chunk.data().bytes().slice(right_offset_in_chunk))) }); + } + } + + m_state_changed.broadcast(); +} + Vector IncrementallyPopulatedStream::available_byte_ranges() const { Sync::MutexLocker locker { m_mutex }; diff --git a/Libraries/LibMedia/IncrementallyPopulatedStream.h b/Libraries/LibMedia/IncrementallyPopulatedStream.h index 6ab1c2850e..1b09703bed 100644 --- a/Libraries/LibMedia/IncrementallyPopulatedStream.h +++ b/Libraries/LibMedia/IncrementallyPopulatedStream.h @@ -39,6 +39,7 @@ public: void set_data_request_callback(DataRequestCallback); void add_chunk_at(u64 offset, ReadonlyBytes); + void remove_byte_range(u64 start, u64 end); u64 next_chunk_start() const { return m_last_chunk_end; } void close(); diff --git a/Tests/LibMedia/TestIncrementallyPopulatedStream.cpp b/Tests/LibMedia/TestIncrementallyPopulatedStream.cpp index 4eb3c02880..ef386478cb 100644 --- a/Tests/LibMedia/TestIncrementallyPopulatedStream.cpp +++ b/Tests/LibMedia/TestIncrementallyPopulatedStream.cpp @@ -273,6 +273,88 @@ TEST_CASE(redundant_chunk_within_existing_chunk_at_nonzero_offset) EXPECT_EQ(buffer[i], static_cast(100 + i)); } +TEST_CASE(remove_byte_range_splits_chunk) +{ + auto data = make_test_data(100); + auto stream = Media::IncrementallyPopulatedStream::create_from_data(data.bytes()); + + stream->remove_byte_range(30, 70); + + auto ranges = stream->available_byte_ranges(); + EXPECT_EQ(ranges.size(), 2u); + EXPECT_EQ(ranges[0].start, 0u); + EXPECT_EQ(ranges[0].end, 30u); + EXPECT_EQ(ranges[1].start, 70u); + EXPECT_EQ(ranges[1].end, 100u); + + auto cursor = stream->create_cursor(); + Array buffer; + auto bytes_read = MUST(cursor->read_into(buffer)); + EXPECT_EQ(bytes_read, 30u); + for (size_t i = 0; i < 30; i++) + EXPECT_EQ(buffer[i], static_cast(i)); + + MUST(cursor->seek(70, SeekMode::SetPosition)); + bytes_read = MUST(cursor->read_into(buffer)); + EXPECT_EQ(bytes_read, 30u); + for (size_t i = 0; i < 30; i++) + EXPECT_EQ(buffer[i], static_cast(70 + i)); +} + +TEST_CASE(remove_byte_range_trims_chunk) +{ + auto data = make_test_data(100); + auto stream = Media::IncrementallyPopulatedStream::create_from_data(data.bytes()); + + stream->remove_byte_range(0, 20); + stream->remove_byte_range(80, 100); + + auto ranges = stream->available_byte_ranges(); + EXPECT_EQ(ranges.size(), 1u); + EXPECT_EQ(ranges[0].start, 20u); + EXPECT_EQ(ranges[0].end, 80u); + + auto cursor = stream->create_cursor(); + MUST(cursor->seek(20, SeekMode::SetPosition)); + + Array buffer; + auto bytes_read = MUST(cursor->read_into(buffer)); + EXPECT_EQ(bytes_read, 60u); + for (size_t i = 0; i < 60; i++) + EXPECT_EQ(buffer[i], static_cast(20 + i)); +} + +TEST_CASE(remove_byte_range_across_disjoint_chunks) +{ + auto stream = Media::IncrementallyPopulatedStream::create_empty(); + auto data = make_test_data(100); + + stream->add_chunk_at(0, data.bytes().trim(20)); + stream->add_chunk_at(40, data.bytes().slice(40, 20)); + stream->add_chunk_at(80, data.bytes().slice(80)); + stream->remove_byte_range(10, 90); + + auto ranges = stream->available_byte_ranges(); + EXPECT_EQ(ranges.size(), 2u); + EXPECT_EQ(ranges[0].start, 0u); + EXPECT_EQ(ranges[0].end, 10u); + EXPECT_EQ(ranges[1].start, 90u); + EXPECT_EQ(ranges[1].end, 100u); + + auto cursor = stream->create_cursor(); + Array buffer; + auto bytes_read = MUST(cursor->read_into(buffer)); + EXPECT_EQ(bytes_read, 10u); + for (size_t i = 0; i < 10; i++) + EXPECT_EQ(buffer[i], static_cast(i)); + + MUST(cursor->seek(90, SeekMode::SetPosition)); + bytes_read = MUST(cursor->read_into(buffer)); + EXPECT_EQ(bytes_read, 10u); + for (size_t i = 0; i < 10; i++) + EXPECT_EQ(buffer[i], static_cast(90 + i)); +} + TEST_CASE(add_touching_chunks_forward) { auto stream = Media::IncrementallyPopulatedStream::create_empty();