/******************************************************** * 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 /// /// Creates a object. This will not work /// for tables that were created WITHOUT ROWID -OR- if the query /// does not include the "rowid" column or one of its aliases -OR- /// if the was not created with the /// flag. /// /// /// The instance with a result set /// containing the desired blob column. /// /// /// The index of the blob column. /// /// /// Non-zero to open the blob object for read-only access. /// /// /// The newly created instance -OR- null /// if an error occurs. /// 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( handle, 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 /// /// The destructor. /// ~SQLiteBlob() { Dispose(false); } #endregion } }