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();