root/src/kits/media/ChunkCache.cpp
/*
 * Copyright 2009, Axel Dörfler, axeld@pinc-software.de.
 * Distributed under the terms of the MIT License.
 */


#include "ChunkCache.h"

#include <new>
#include <stdlib.h>
#include <string.h>

#include "MediaDebug.h"

// #pragma mark -


ChunkCache::ChunkCache(sem_id waitSem, size_t maxBytes)
        :
        BLocker("media chunk cache"),
        fWaitSem(waitSem)
{
        rtm_create_pool(&fRealTimePool, maxBytes, "media chunk cache");
        fMaxBytes = rtm_available(fRealTimePool);
}


ChunkCache::~ChunkCache()
{
        rtm_delete_pool(fRealTimePool);
}


status_t
ChunkCache::InitCheck() const
{
        if (fRealTimePool == NULL)
                return B_NO_MEMORY;

        return B_OK;
}


void
ChunkCache::MakeEmpty()
{
        ASSERT(IsLocked());

        while (!fChunkCache.empty()) {
                RecycleChunk(fChunkCache.front());
                fChunkCache.pop();
        }
        
        release_sem(fWaitSem);
}


bool
ChunkCache::SpaceLeft() const
{
        ASSERT(IsLocked());

        if (fChunkCache.size() >= CACHE_MAX_ENTRIES) {
                return false;
        }

        // If there is no more memory we are likely to fail soon after
        return sizeof(chunk_buffer) + 2048 < rtm_available(fRealTimePool);
}


chunk_buffer*
ChunkCache::NextChunk(Reader* reader, void* cookie)
{
        ASSERT(IsLocked());

        chunk_buffer* chunk = NULL;

        if (fChunkCache.empty()) {
                TRACE("ChunkCache is empty, going direct to reader\n");
                if (ReadNextChunk(reader, cookie)) {
                        return NextChunk(reader, cookie);
                }
        } else {
                chunk = fChunkCache.front();
                fChunkCache.pop();
                
                release_sem(fWaitSem);
        }

        return chunk;
}


/*      Moves the specified chunk to the unused list.
        This means the chunk data can be overwritten again.
*/
void
ChunkCache::RecycleChunk(chunk_buffer* chunk)
{
        ASSERT(IsLocked());

        rtm_free(chunk->buffer);
        chunk->capacity = 0;
        chunk->size = 0;
        chunk->buffer = NULL;
        fUnusedChunks.push_back(chunk);
}


bool
ChunkCache::ReadNextChunk(Reader* reader, void* cookie)
{
        ASSERT(IsLocked());

        // retrieve chunk buffer
        chunk_buffer* chunk = NULL;
        if (fUnusedChunks.empty()) {
                // allocate a new one
                chunk = (chunk_buffer*)rtm_alloc(fRealTimePool, sizeof(chunk_buffer));
                if (chunk == NULL) {
                        ERROR("RTM Pool empty allocating chunk buffer structure");
                        return false;
                }
                
                chunk->size = 0;
                chunk->capacity = 0;
                chunk->buffer = NULL;

        } else {
                chunk = fUnusedChunks.front();
                fUnusedChunks.pop_front();
        }

        const void* buffer;
        size_t bufferSize;
        chunk->status = reader->GetNextChunk(cookie, &buffer, &bufferSize,
                &chunk->header);
        if (chunk->status == B_OK) {
                if (chunk->capacity < bufferSize) {
                        // adapt buffer size
                        rtm_free(chunk->buffer);
                        chunk->capacity = (bufferSize + 2047) & ~2047;
                        chunk->buffer = rtm_alloc(fRealTimePool, chunk->capacity);
                        if (chunk->buffer == NULL) {
                                rtm_free(chunk);
                                ERROR("RTM Pool empty allocating chunk buffer\n");
                                return false;
                        }
                }

                memcpy(chunk->buffer, buffer, bufferSize);
                chunk->size = bufferSize;
        }

        fChunkCache.push(chunk);
        return chunk->status == B_OK;
}