#region Disclaimer / License
// Copyright (C) 2008, Kenneth Skovhede
// http://www.hexad.dk, opensource@hexad.dk
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
#endregion
using System;
using System.Collections.Generic;
using System.Text;
namespace Duplicati.Library.SharpRSync
{
///
/// This implements a rolling buffer, used for non-seekable streams
///
public class RollingBuffer
{
private const int BUFFER_SIZE = 1024;
private int m_headIndex;
private int m_tailIndex;
List m_buffers;
private System.IO.Stream m_stream;
public RollingBuffer(System.IO.Stream stream)
{
m_stream = stream;
m_buffers = new List();
m_buffers.Add(new byte[BUFFER_SIZE]);
m_headIndex = 0;
m_tailIndex = 0;
}
public long Count { get { return m_buffers.Count * BUFFER_SIZE - m_tailIndex - (BUFFER_SIZE - m_headIndex); } }
///
/// Reads the supplied amount of bytes
///
/// The number of bytes to read
/// The number of bytes actually read
public int Advance(int count)
{
int totalRead = 0;
while (count > 0)
{
if (m_headIndex == BUFFER_SIZE)
{
m_buffers.Add(new byte[BUFFER_SIZE]);
m_headIndex = 0;
}
int read = m_stream.Read(m_buffers[m_buffers.Count - 1], m_headIndex, Math.Min(BUFFER_SIZE - m_headIndex, count));
count -= read;
m_headIndex += read;
totalRead += read;
if (read == 0)
break;
}
return totalRead;
}
///
/// Returns the last read bytes
///
/// The number of bytes to return
/// The bytes requested
public byte[] GetHead(long count)
{
byte[] tmp = new byte[count];
GetHead(tmp, 0, count);
return tmp;
}
///
/// Gets the number of avalible bytes in the given buffer
///
/// The index for the buffer
/// The number of avalible bytes in the buffer
private int GetAvalible(int ix)
{
if (ix == 0)
return BUFFER_SIZE - m_tailIndex;
else if (m_buffers.Count == 0)
return m_headIndex - m_tailIndex;
else if (ix == m_buffers.Count - 1)
return m_headIndex;
else
return BUFFER_SIZE;
}
///
/// Returns the last read bytes
///
/// The number of bytes to return
/// The buffer to write the bytes into
/// The offset into the buffer
/// The bytes requested
public void GetHead(byte[] buf, long offset, long count)
{
int ix = 0;
long total = this.Count;
if (total < count)
throw new Exception("Buffer has too few bytes");
int b = m_tailIndex;
while (total > count)
{
int availible = GetAvalible(ix);
int diff = (int)Math.Min(availible, total - count);
total -= diff;
b += diff;
if (b >= BUFFER_SIZE)
{
ix++;
b = 0;
}
}
if (ix == 0)
b -= m_tailIndex;
while (count > 0)
{
int avalible = (int)Math.Min(GetAvalible(ix) - b, count);
if (ix == 0)
b += m_tailIndex;
Array.Copy(m_buffers[ix], b, buf, offset, avalible);
count -= avalible;
offset += avalible;
ix++;
b = 0;
}
}
///
/// Returns the leading bytes
///
/// The number of bytes to return
/// The bytes requested
public byte[] GetTail(long count)
{
byte[] tmp = new byte[count];
GetTail(tmp, 0, count);
return tmp;
}
///
/// Returns the leading bytes
///
/// The number of bytes to return
/// The buffer to write the bytes into
/// The offset into the buffer
public void GetTail(byte[] buf, long offset, long count)
{
int ix = 0;
if (this.Count < count)
throw new Exception("Buffer has too few bytes");
int b = m_tailIndex;
while (count > 0)
{
int avalible = (int)Math.Min(GetAvalible(ix), count);
Array.Copy(m_buffers[ix], b, buf, offset, avalible);
count -= avalible;
offset += avalible;
ix++;
b = 0;
}
}
///
/// Removes the bytes from memory
///
/// The number of bytes to drop
public void DropTail(long count)
{
if (count == this.Count)
{
m_buffers = new List();
m_buffers.Add(new byte[BUFFER_SIZE]);
m_headIndex = 0;
m_tailIndex = 0;
return;
}
if (count < (BUFFER_SIZE - m_tailIndex))
{
m_tailIndex += (int)count;
return;
}
m_buffers.RemoveAt(0);
count -= (BUFFER_SIZE - m_tailIndex);
m_tailIndex = 0;
while (count >= BUFFER_SIZE)
{
m_buffers.RemoveAt(0);
count -= BUFFER_SIZE;
}
m_tailIndex += (int)count;
if (m_buffers.Count == 0)
{
m_headIndex = 0;
m_tailIndex = 0;
m_buffers.Add(new byte[BUFFER_SIZE]);
}
if (m_buffers.Count <= 1 && m_tailIndex > m_headIndex)
throw new Exception("This is one sad rolling buffer :(");
}
///
/// Returns the byte at the given position.
///
/// The index of the byte
/// The byte value
public byte GetByteAt(long index)
{
int ix = 0;
if (index > this.Count - 1)
throw new Exception("Buffer has too few bytes");
if (index < 0)
throw new Exception("Index cannot be negative");
while (GetAvalible(ix) <= index)
{
index -= GetAvalible(ix);
ix++;
}
long offset = ix == 0 ? m_tailIndex : 0;
offset += index;
return m_buffers[ix][offset];
}
}
}