/********************************************************
* ADO.NET 2.0 Data Provider for SQLite Version 3.X
* Written by Joe Mistachkin (joe@mistachkin.com)
*
* Released to the public domain, use at your own risk!
********************************************************/
namespace System.Data.SQLite
{
using System;
///
/// Represents a single SQL blob in SQLite.
///
public sealed class SQLiteBlob : IDisposable
{
#region Private Data
///
/// The underlying SQLite object this blob is bound to.
///
internal SQLiteBase _sql;
///
/// The actual blob handle.
///
internal SQLiteBlobHandle _sqlite_blob;
#endregion
///////////////////////////////////////////////////////////////////////////////////////////////
#region Private Constructors
///
/// Initializes the blob.
///
/// The base SQLite object.
/// The blob handle.
private SQLiteBlob(
SQLiteBase sqlbase,
SQLiteBlobHandle blob
)
{
_sql = sqlbase;
_sqlite_blob = blob;
}
#endregion
///////////////////////////////////////////////////////////////////////////////////////////////
#region Static "Factory" Methods
public static SQLiteBlob Create(
SQLiteDataReader dataReader,
int i,
bool readOnly
)
{
SQLiteConnection connection = SQLiteDataReader.GetConnection(
dataReader);
if (connection == null)
throw new InvalidOperationException("Connection not available");
SQLite3 sqlite3 = connection._sql as SQLite3;
if (sqlite3 == null)
throw new InvalidOperationException("Connection has no wrapper");
SQLiteConnectionHandle handle = sqlite3._sql;
if (handle == null)
throw new InvalidOperationException("Connection has an invalid handle.");
long? rowId = dataReader.GetRowId(i);
if (rowId == null)
throw new InvalidOperationException("No RowId is available");
SQLiteBlobHandle blob = null;
try
{
// do nothing.
}
finally /* NOTE: Thread.Abort() protection. */
{
IntPtr ptrBlob = IntPtr.Zero;
SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3_blob_open(
sqlite3._sql, SQLiteConvert.ToUTF8(
dataReader.GetDatabaseName(i)), SQLiteConvert.ToUTF8(
dataReader.GetTableName(i)), SQLiteConvert.ToUTF8(
dataReader.GetName(i)), (long)rowId, readOnly ? 0 : 1,
ref ptrBlob);
if (rc != SQLiteErrorCode.Ok)
throw new SQLiteException(rc, null);
blob = new SQLiteBlobHandle(handle, ptrBlob);
}
SQLiteConnection.OnChanged(null, new ConnectionEventArgs(
SQLiteConnectionEventType.NewCriticalHandle, null,
null, null, dataReader, blob, null, new object[] {
typeof(SQLiteBlob), dataReader, i, readOnly }));
return new SQLiteBlob(sqlite3, blob);
}
#endregion
///////////////////////////////////////////////////////////////////////////////////////////////
#region Private Methods
///
/// Throws an exception if the blob object does not appear to be open.
///
private void CheckOpen()
{
if (_sqlite_blob == IntPtr.Zero)
throw new InvalidOperationException("Blob is not open");
}
///////////////////////////////////////////////////////////////////////////////////////////////
///
/// Throws an exception if an invalid read/write parameter is detected.
///
///
/// When reading, this array will be populated with the bytes read from
/// the underlying database blob. When writing, this array contains new
/// values for the specified portion of the underlying database blob.
///
///
/// The number of bytes to read or write.
///
///
/// The byte offset, relative to the start of the underlying database
/// blob, where the read or write operation will begin.
///
private void VerifyParameters(
byte[] buffer,
int count,
int offset
)
{
if (buffer == null)
throw new ArgumentNullException("buffer");
if (offset < 0)
throw new ArgumentException("Negative offset not allowed.");
if (count < 0)
throw new ArgumentException("Negative count not allowed.");
if (count > buffer.Length)
throw new ArgumentException("Buffer is too small.");
}
#endregion
///////////////////////////////////////////////////////////////////////////////////////////////
#region Public Methods
///
/// Retargets this object to an underlying database blob for a
/// different row; the database, table, and column remain exactly
/// the same. If this operation fails for any reason, this blob
/// object is automatically disposed.
///
///
/// The integer identifier for the new row.
///
public void Reopen(
long rowId
)
{
CheckDisposed();
CheckOpen();
SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3_blob_reopen(
_sqlite_blob, rowId);
if (rc != SQLiteErrorCode.Ok)
{
Dispose();
throw new SQLiteException(rc, null);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////
///
/// Queries the total number of bytes for the underlying database blob.
///
///
/// The total number of bytes for the underlying database blob.
///
public int GetCount()
{
CheckDisposed();
CheckOpen();
return UnsafeNativeMethods.sqlite3_blob_bytes(_sqlite_blob);
}
///////////////////////////////////////////////////////////////////////////////////////////////
///
/// Reads data from the underlying database blob.
///
///
/// This array will be populated with the bytes read from the
/// underlying database blob.
///
///
/// The number of bytes to read.
///
///
/// The byte offset, relative to the start of the underlying
/// database blob, where the read operation will begin.
///
public void Read(
byte[] buffer,
int count,
int offset
)
{
CheckDisposed();
CheckOpen();
VerifyParameters(buffer, count, offset);
SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3_blob_read(
_sqlite_blob, buffer, count, offset);
if (rc != SQLiteErrorCode.Ok)
throw new SQLiteException(rc, null);
}
///////////////////////////////////////////////////////////////////////////////////////////////
///
/// Writes data into the underlying database blob.
///
///
/// This array contains the new values for the specified portion of
/// the underlying database blob.
///
///
/// The number of bytes to write.
///
///
/// The byte offset, relative to the start of the underlying
/// database blob, where the write operation will begin.
///
public void Write(
byte[] buffer,
int count,
int offset
)
{
CheckDisposed();
CheckOpen();
VerifyParameters(buffer, count, offset);
SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3_blob_write(
_sqlite_blob, buffer, count, offset);
if (rc != SQLiteErrorCode.Ok)
throw new SQLiteException(rc, null);
}
///////////////////////////////////////////////////////////////////////////////////////////////
///
/// Closes the blob, freeing the associated resources.
///
public void Close()
{
Dispose();
}
#endregion
///////////////////////////////////////////////////////////////////////////////////////////////
#region IDisposable Members
///
/// Disposes and finalizes the blob.
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
///////////////////////////////////////////////////////////////////////////////////////////////
#region IDisposable "Pattern" Members
private bool disposed;
private void CheckDisposed() /* throw */
{
#if THROW_ON_DISPOSED
if (disposed)
throw new ObjectDisposedException(typeof(SQLiteBlob).Name);
#endif
}
///////////////////////////////////////////////////////////////////////////////////////////////
private void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
////////////////////////////////////
// dispose managed resources here...
////////////////////////////////////
if (_sqlite_blob != null)
{
_sqlite_blob.Dispose();
_sqlite_blob = null;
}
_sql = null;
}
//////////////////////////////////////
// release unmanaged resources here...
//////////////////////////////////////
disposed = true;
}
}
#endregion
///////////////////////////////////////////////////////////////////////////////////////////////
#region Destructor
~SQLiteBlob()
{
Dispose(false);
}
#endregion
}
}