#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]; } } }