2015-01-20 21:44:52 +01:00
// Copyright (C) 2015, The Duplicati Team
2013-05-22 21:31:46 +02:00
2015-01-20 21:44:52 +01:00
// http://www.duplicati.com, info@duplicati.com
2013-05-22 21:31:46 +02:00
//
// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System ;
using System.Collections.Generic ;
using System.IO ;
namespace Duplicati.Library.Utility
{
2016-09-15 11:39:27 +02:00
/// <summary>
/// Represents an enumerable list that can be appended to.
/// If the use of the list exceeds the threshold, the list will
/// switch from memory based to storage to file based storage.
/// Typical usage of this list is for storing log messages,
/// that occasionally grows and produces out-of-memory errors
/// </summary>
public abstract class FileBackedList < T > : IEnumerable < T >, IDisposable
{
2013-05-22 21:31:46 +02:00
private class StreamEnumerator : IEnumerator < T >, System . Collections . IEnumerator
{
private Stream m_stream ;
2018-05-23 21:18:01 -07:00
private readonly Func < Stream , long , T > m_deserialize ;
2013-05-22 21:31:46 +02:00
private long m_position ;
private readonly byte [] m_sizebuffer ;
2018-05-23 21:18:01 -07:00
private readonly long m_expectedCount ;
private readonly FileBackedList < T > m_parent ;
2013-07-09 13:55:31 +02:00
private T m_current ;
2013-05-22 21:31:46 +02:00
public StreamEnumerator ( Stream stream , Func < Stream , long , T > deserialize , FileBackedList < T > parent )
{
m_stream = stream ;
m_deserialize = deserialize ;
m_sizebuffer = new byte [ 8 ];
m_parent = parent ;
m_expectedCount = m_parent . Count ;
this . Reset ();
}
public T Current
2016-09-15 11:39:27 +02:00
{
get
{
if ( m_expectedCount != m_parent . Count )
throw new Exception ( "Collection modified" );
2013-07-09 13:55:31 +02:00
return m_current ;
2016-09-15 11:39:27 +02:00
}
2013-05-22 21:31:46 +02:00
}
public void Dispose ()
{
m_stream = null ;
}
object System . Collections . IEnumerator . Current
{
get { return this . Current ; }
}
public bool MoveNext ()
2013-07-09 13:55:31 +02:00
{
if ( m_position >= m_stream . Length )
{
m_current = default ( T );
return false ;
}
2016-09-15 11:39:27 +02:00
if ( m_expectedCount != m_parent . Count )
throw new Exception ( "Collection modified" );
m_stream . Position = m_position ;
2018-05-30 17:42:59 -07:00
if ( Utility . ForceStreamRead ( m_stream , m_sizebuffer , m_sizebuffer . Length ) != m_sizebuffer . Length )
2013-07-10 14:33:02 +02:00
throw new IOException ( "Unexpected EOS" );
2013-07-09 13:55:31 +02:00
var len = BitConverter . ToInt64 ( m_sizebuffer , 0 );
m_current = m_deserialize ( m_stream , len );
2016-09-15 11:39:27 +02:00
m_position += m_sizebuffer . Length + len ;
return true ;
2013-05-22 21:31:46 +02:00
}
public void Reset ()
{
2013-07-09 13:55:31 +02:00
m_position = 0 ;
m_current = default ( T );
2013-05-22 21:31:46 +02:00
}
}
private Library . Utility . TempFile m_file ;
private Stream m_stream ;
private long m_count ;
2013-08-17 13:38:10 +02:00
2016-09-15 11:39:27 +02:00
public bool IsFileBacked { get { return !( m_stream is MemoryStream ); } }
public long SwitchToFileLimit { get ; set ; }
2018-04-07 17:58:46 -07:00
protected FileBackedList ()
2016-09-15 11:39:27 +02:00
{
2013-05-22 21:31:46 +02:00
m_file = null ;
m_stream = new MemoryStream ();
m_count = 0 ;
2013-08-17 13:38:10 +02:00
2014-11-10 18:39:10 +01:00
this . SwitchToFileLimit = 2 * 1024 * 1024 ;
2016-09-15 11:39:27 +02:00
}
2013-05-22 21:31:46 +02:00
public void Add ( T value )
{
2013-08-17 13:38:10 +02:00
long size = GetSize ( value );
2013-05-22 21:31:46 +02:00
if ( m_stream is MemoryStream && ( m_stream . Length + size ) > this . SwitchToFileLimit )
{
m_file = new Library . Utility . TempFile ();
2013-07-09 13:55:31 +02:00
using ( var oldstream = m_stream )
{
m_stream = System . IO . File . Open ( m_file , FileMode . Create , FileAccess . ReadWrite , FileShare . None );
oldstream . Position = 0 ;
oldstream . CopyTo ( m_stream );
}
2013-05-22 21:31:46 +02:00
}
2017-01-11 15:07:37 +01:00
m_stream . Position = m_stream . Length ;
2016-09-15 11:39:27 +02:00
m_stream . Write ( BitConverter . GetBytes ( size ), 0 , 8 );
2017-01-11 15:07:37 +01:00
var pos = m_stream . Position ;
2016-09-15 11:39:27 +02:00
Serialize ( value , m_stream );
2017-01-11 15:07:37 +01:00
if ( m_stream . Position - pos != size )
throw new Exception ( string . Format ( "Stream serializer wrote a different set of bytes than it was supposed to. Expected {0} bytes, but wrote {1} bytes" , m_stream . Position - pos , size ));
2013-05-22 21:31:46 +02:00
m_count ++;
}
public long Count { get { return m_count ; } }
public void Dispose ()
{
2014-11-10 18:39:10 +01:00
Dispose ( true );
}
~ FileBackedList ()
{
Dispose ( false );
}
protected void Dispose ( bool disposing )
{
if ( disposing )
GC . SuppressFinalize ( this );
2013-05-22 21:31:46 +02:00
if ( m_stream != null )
{
m_stream . Dispose ();
m_stream = null ;
}
if ( m_file != null )
{
m_file . Dispose ();
m_file = null ;
}
2013-08-17 13:38:10 +02:00
}
protected abstract long GetSize ( T value );
protected abstract void Serialize ( T value , Stream stream );
protected abstract T Deserialize ( Stream stream , long length );
2013-05-22 21:31:46 +02:00
#region IEnumerable implementation
2016-09-15 11:39:27 +02:00
public IEnumerator < T > GetEnumerator ()
{
2013-08-17 13:38:10 +02:00
return new StreamEnumerator ( m_stream , this . Deserialize , this );
2016-09-15 11:39:27 +02:00
}
#endregion
#region IEnumerable implementation
System . Collections . IEnumerator System . Collections . IEnumerable . GetEnumerator ()
{
return this . GetEnumerator ();
}
#endregion
}
/// <summary>
/// Represents an enumerable list that can be appended to.
/// If the use of the list exceeds the threshold, the list will
/// switch from memory based to storage to file based storage.
/// Typical usage of this list is for storing log messages,
/// that occasionally grows and produces out-of-memory errors
/// </summary>
public class FileBackedStringList : FileBackedList < string >
{
2013-07-09 13:55:31 +02:00
private byte [] m_buf ;
2014-11-10 18:39:10 +01:00
public readonly System . Text . Encoding m_encoding ;
private const int MIN_BUF_SIZE = 1024 ;
private const int BUFFER_OVERSHOOT = 32 ;
2013-08-17 13:38:10 +02:00
2016-09-15 11:39:27 +02:00
/// <summary>
/// Initializes a new instance of the <see cref="Duplicati.Library.Utility.FileBackedStringList"/> class.
/// </summary>
/// <param name="encoding">The text encoding to use, defaults to UTF8</param>
public FileBackedStringList ( System . Text . Encoding encoding = null )
{
2013-08-17 13:38:10 +02:00
m_encoding = encoding ?? System . Text . Encoding . UTF8 ;
2016-09-15 11:39:27 +02:00
}
2013-08-17 13:38:10 +02:00
protected override long GetSize ( string value )
{
2014-11-10 18:39:10 +01:00
var size = m_encoding . GetByteCount ( value );
if ( m_buf == null || m_buf . Length < size )
m_buf = new byte [ Math . Max ( MIN_BUF_SIZE , size + BUFFER_OVERSHOOT )];
return size ;
2013-08-17 13:38:10 +02:00
}
protected override void Serialize ( string value , Stream stream )
{
2014-11-10 18:39:10 +01:00
var size = m_encoding . GetBytes ( value , 0 , value . Length , m_buf , 0 );
stream . Write ( m_buf , 0 , size );
2013-08-17 13:38:10 +02:00
}
protected override string Deserialize ( Stream stream , long length )
{
if ( m_buf == null || m_buf . Length < length )
2014-11-10 18:39:10 +01:00
m_buf = new byte [ Math . Max ( MIN_BUF_SIZE , length + BUFFER_OVERSHOOT )];
2013-08-17 13:38:10 +02:00
Utility . ForceStreamRead ( stream , m_buf , ( int ) length );
return ( m_encoding ?? System . Text . Encoding . UTF8 ). GetString ( m_buf , 0 , ( int ) length );
}
2016-09-15 11:39:27 +02:00
}
2013-05-22 21:31:46 +02:00
}