]> git.donarmstrong.com Git - bamtools.git/blobdiff - src/api/internal/io/RollingBuffer_p.cpp
Added FTP support (text-tested, not BAM)
[bamtools.git] / src / api / internal / io / RollingBuffer_p.cpp
diff --git a/src/api/internal/io/RollingBuffer_p.cpp b/src/api/internal/io/RollingBuffer_p.cpp
new file mode 100644 (file)
index 0000000..ab29253
--- /dev/null
@@ -0,0 +1,335 @@
+#include "api/internal/io/RollingBuffer_p.h"
+using namespace BamTools;
+using namespace BamTools::Internal;
+
+#include <iostream> // for debug
+
+#include <climits>
+#include <cstring>
+#include <algorithm>
+#include <string>
+using namespace std;
+
+// ------------------------------
+// RollingBuffer implementation
+// ------------------------------
+
+RollingBuffer::RollingBuffer(size_t growth)
+    : m_bufferGrowth(growth)
+{
+    // buffer always contains at least 1 (maybe empty) byte array
+    m_data.push_back( ByteArray() );
+
+    // set cleared state
+    Clear();
+}
+
+RollingBuffer::~RollingBuffer(void) { }
+
+size_t RollingBuffer::BlockSize(void) const {
+
+    // if only one byte array in buffer <- needed?
+    if ( m_tailBufferIndex == 0 )
+        return m_tail - m_head;
+
+    // otherwise return remaining num bytes in first array
+    const ByteArray& first = m_data.front();
+    return first.Size() - m_head;
+}
+
+bool RollingBuffer::CanReadLine(void) const {
+    return IndexOf('\n') != string::npos;
+}
+
+void RollingBuffer::Chop(size_t n) {
+
+    // update buffer size
+    if ( n > m_totalBufferSize )
+        m_totalBufferSize = 0;
+    else
+        m_totalBufferSize -= n;
+
+    // loop until target case hit
+    for ( ; ; ) {
+
+        // if only one array, decrement tail
+        if ( m_tailBufferIndex == 0 ) {
+            m_tail -= n;
+
+            // if all data chopped
+            if ( m_tail <= m_head ) {
+                m_head = 0;
+                m_tail = 0;
+            }
+            return;
+        }
+
+        // if there's room in last byte array to 'chop', just decrement tail
+        if ( n <= m_tail ) {
+            m_tail -= n;
+            return;
+        }
+
+        // otherwise we're going to overlap our internal byte arrays
+        // reduce our chop amount by the amount of data in the last byte array
+        n -= m_tail;
+
+        // remove last byte array & set tail to it's end
+        m_data.pop_back();
+        --m_tailBufferIndex;
+        m_tail = m_data.at(m_tailBufferIndex).Size();
+    }
+
+    // if buffer is now empty, reset state & clear up memory
+    if ( IsEmpty() )
+        Clear();
+}
+
+void RollingBuffer::Clear(void) {
+
+    // remove all byte arrays (except first)
+    m_data.erase( m_data.begin()+1, m_data.end() );
+
+    // clear out first byte array
+    m_data[0].Resize(0);
+    m_data[0].Squeeze();
+
+    // reset index & size markers
+    m_head = 0;
+    m_tail = 0;
+    m_tailBufferIndex = 0;
+    m_totalBufferSize = 0;
+}
+
+void RollingBuffer::Free(size_t n) {
+
+    // update buffer size
+    if ( n > m_totalBufferSize )
+        m_totalBufferSize = 0;
+    else
+        m_totalBufferSize -= n;
+
+    // loop until target case hit
+    for ( ; ; ) {
+
+        const size_t blockSize = BlockSize();
+
+        // if there's room in current array
+        if ( n < blockSize ) {
+
+            // shift 'head' over @n bytes
+            m_head += n;
+
+            // check for emptied, single byte array
+            if ( m_head == m_tail && m_tailBufferIndex == 0 ) {
+                m_head = 0;
+                m_tail = 0;
+            }
+
+            break;
+        }
+
+        // otherwise we need to check next byte array
+        // first update amount to remove
+        n -= blockSize;
+
+        // special case - there was only 1 array
+        if ( m_data.size() == 1 ) {
+            if ( m_data.at(0).Size() != m_bufferGrowth )
+                m_data[0].Resize(m_bufferGrowth);
+            m_head = 0;
+            m_tail = 0;
+            m_tailBufferIndex = 0;
+            break;
+        }
+
+        // otherwise, remove first array and move to next iteration
+        m_data.pop_front();
+        --m_tailBufferIndex;
+        m_head = 0;
+    }
+
+    // if buffer is now empty, reset state & clear up memory
+    if ( IsEmpty() )
+        Clear();
+}
+
+size_t RollingBuffer::IndexOf(char c) const {
+
+    size_t index(0);
+
+    // iterate over byte arrays
+    const size_t numBuffers = m_data.size();
+    for ( size_t i = 0; i < numBuffers; ++i ) {
+        const ByteArray& current = m_data.at(i);
+
+        // if on first array, use head; else 0
+        const size_t start = ( (i==0) ? m_head : 0 );
+
+        // if on last array, set end; else use current byte array size
+        const size_t end   = ( (i==m_tailBufferIndex) ? m_tail : current.Size());
+
+        // look through this iteration's byte array for @c
+        const char* p = current.ConstData()+start;
+        for ( size_t j = start; j < end; ++j ) {
+            if ( *p++ == c )
+                return index;
+            ++index;
+        }
+    }
+
+    // no match found
+    return string::npos;
+}
+
+bool RollingBuffer::IsEmpty(void) const {
+    return (m_tailBufferIndex == 0) && (m_tail == 0);
+}
+
+size_t RollingBuffer::Read(char* dest, size_t max) {
+
+    size_t bytesToRead    = std::min(Size(), max);
+    size_t bytesReadSoFar = 0;
+
+    while ( bytesReadSoFar < bytesToRead ) {
+        const char* readPtr = ReadPointer();
+        size_t blockBytes = std::min( (bytesToRead - bytesReadSoFar), BlockSize() );
+        if ( dest )
+            memcpy(dest+bytesReadSoFar, readPtr, blockBytes);
+        bytesReadSoFar += blockBytes;
+        Free(blockBytes);
+    }
+
+    return bytesReadSoFar;
+}
+
+size_t RollingBuffer::ReadLine(char* dest, size_t max) {
+
+    // if we can't read line or if max is 0
+    if ( !CanReadLine() || max == 0 )
+        return 0;
+
+    // otherwise, read until we hit newline
+    size_t bytesReadSoFar = 0;
+    bool finished = false;
+    while ( !finished ) {
+
+        const size_t index = IndexOf('\n');
+        const char* readPtr = ReadPointer();
+        size_t bytesToRead = std::min( (index+1)-bytesReadSoFar, BlockSize() );
+        bytesToRead = std::min( bytesToRead, (max-1)-bytesReadSoFar );
+        memcpy(dest+bytesReadSoFar, readPtr, bytesToRead);
+        bytesReadSoFar += bytesToRead;
+        Free(bytesToRead);
+
+        if ( !((bytesReadSoFar < index+1)&&(bytesReadSoFar < max-1)) )
+            finished = true;
+    }
+
+    // null terminate 'dest' & return numBytesRead
+    dest[bytesReadSoFar] = '\0';
+    return bytesReadSoFar;
+}
+
+string RollingBuffer::ReadLine(size_t max) {
+
+    ByteArray result;
+    result.Resize(max);
+
+    size_t numBytesRead = 0;
+
+    // if max not provided, we need to read incrementally
+    if ( max == 0 ) {
+        max = UINT_MAX;
+
+        // make sure we leave room for null terminator
+        result.Resize(1);
+
+        size_t readResult;
+        do {
+            result.Resize(std::min(max, result.Size()+m_bufferGrowth));
+            readResult = ReadLine(result.Data() + numBytesRead, result.Size() - numBytesRead);
+            if ( readResult > 0 || numBytesRead == 0 )
+                numBytesRead += readResult;
+        } while ( readResult == m_bufferGrowth && result[numBytesRead-1] != '\n');
+    }
+
+    // otherwise read line with provided max
+    else numBytesRead = ReadLine(result.Data(), result.Size());
+
+    // adjust byte array depending on numBytesRead
+    if ( numBytesRead == 0 )
+        result.Clear();
+    else
+        result.Resize(numBytesRead);
+
+    // return string from byte array
+    return string(result.ConstData(), result.Size());
+}
+
+const char* RollingBuffer::ReadPointer(void) const {
+
+    // return null if empty buffer
+    if ( m_data.empty() )
+        return 0;
+
+    // otherwise return pointer to current position
+    const ByteArray& first = m_data.front();
+    return first.ConstData() + m_head;
+}
+
+char* RollingBuffer::Reserve(size_t n) {
+
+    // if empty buffer
+    if ( m_totalBufferSize == 0 ) {
+        m_data[0].Resize( std::max(m_bufferGrowth, n) );
+        m_totalBufferSize += n;
+        m_tail = n;
+        return m_data[m_tailBufferIndex].Data();
+    }
+
+    // increment buffer's byte count
+    m_totalBufferSize += n;
+
+    // if buffer already contains enough space to fit @n more bytes
+    if ( (m_tail + n) <= m_data.at(m_tailBufferIndex).Size() ) {
+
+        // fetch write pointer at current 'tail', increment tail by @n & return
+        char* ptr = m_data[m_tailBufferIndex].Data() + m_tail;
+        m_tail += n;
+        return ptr;
+    }
+
+    // if last byte array isn't half full
+    if ( m_tail < m_data.at(m_tailBufferIndex).Size()/2 ) {
+
+        // we'll allow simple resize
+        m_data[m_tailBufferIndex].Resize(m_tail + n);
+
+        // fetch write pointer at current 'tail', increment tail by @n & return
+        char* ptr = m_data[m_tailBufferIndex].Data() + m_tail;
+        m_tail += n;
+        return ptr;
+    }
+
+    // otherwise, shrink last byte array to current used size
+    m_data[m_tailBufferIndex].Resize(m_tail);
+
+    // then append new byte array
+    m_data.push_back( ByteArray() );
+    ++m_tailBufferIndex;
+    m_data[m_tailBufferIndex].Resize( std::max(m_bufferGrowth, n) );
+    m_tail = n;
+
+    // return write-able pointer on new array
+    return m_data[m_tailBufferIndex].Data();
+}
+
+size_t RollingBuffer::Size(void) const {
+    return m_totalBufferSize;
+}
+
+void RollingBuffer::Write(const char* src, size_t n) {
+    char* writePtr = Reserve(n);
+    memcpy(writePtr, src, n);
+}