// ========================================================= Constructor internal SenseNetSqlFileStream(long size, int fileId, SqlFileStreamData fileStreamData = null) { Length = size; _fileStreamData = fileStreamData?.TransactionContext != null ? fileStreamData : null; FileId = fileId; }
private static BlobStorageContext GetBlobStorageContextPrivate(SqlDataReader reader, int fileId, int versionId, int propertyTypeId) { // this is a helper method to aid both the sync and // async version of the GetBlobContext operation var length = reader.GetSafeInt64(0); var providerName = reader.GetSafeString(1); var providerData = reader.GetSafeString(2); var fsData = new SqlFileStreamData { Path = reader.GetSafeString(3), TransactionContext = reader.GetSqlBytes(4).Buffer }; var useFileStream = fsData.Path != null; var provider = BlobStorageBase.GetProvider(providerName); object blobProviderData; if (IsBuiltInOrSqlFileStreamProvider(provider)) { if (useFileStream) // based on db column { blobProviderData = new SqlFileStreamBlobProviderData { FileStreamData = fsData }; // Name of the SqlFS and BuiltIn are the same: null // so currently need to change to the SqlFS provider. provider = new SqlFileStreamBlobProvider(); } else { blobProviderData = new BuiltinBlobProviderData(); } } else { blobProviderData = provider.ParseData(providerData); } return(new BlobStorageContext(provider, providerData) { VersionId = versionId, PropertyTypeId = propertyTypeId, FileId = fileId, Length = length, BlobProviderData = blobProviderData }); }
internal static void AddStream(BlobStorageContext context, Stream stream) { SqlFileStreamData fileStreamData = null; if (context.BlobProviderData is SqlFileStreamBlobProviderData providerData) { fileStreamData = providerData.FileStreamData; } if (!UseFileStream(context.Provider, stream.Length)) { throw new NotSupportedException("Assertion failed."); } WriteSqlFileStream(stream, context.FileId, fileStreamData); }
private static void WriteSqlFileStream(Stream stream, int fileId, SqlFileStreamData fileStreamData = null) { SqlProcedure cmd = null; try { // if we did not receive a path and transaction context, retrieve it now from the database if (fileStreamData == null) { cmd = new SqlProcedure { CommandText = UpdateBinaryPropertyFileStreamScript, CommandType = CommandType.Text }; cmd.Parameters.Add("@Id", SqlDbType.Int).Value = fileId; string path; byte[] transactionContext; // Set Stream column to NULL and retrieve file path and // transaction context for the Filestream column. using (var reader = cmd.ExecuteReader()) { reader.Read(); path = reader.GetString(0); transactionContext = reader.GetSqlBytes(1).Buffer; } fileStreamData = new SqlFileStreamData { Path = path, TransactionContext = transactionContext }; } stream.Seek(0, SeekOrigin.Begin); using (var fs = new SqlFileStream(fileStreamData.Path, fileStreamData.TransactionContext, FileAccess.Write)) { // default buffer size is 4096 stream.CopyTo(fs); } } finally { cmd?.Dispose(); } }
/// <summary> /// Loads a cache item into memory that either contains the raw binary (if its size fits into the limit) or /// just the blob metadata pointing to the blob storage. /// </summary> /// <param name="versionId">Content version id.</param> /// <param name="propertyTypeId">Binary property type id.</param> public BinaryCacheEntity LoadBinaryCacheEntity(int versionId, int propertyTypeId) { var commandText = string.Format(LoadBinaryCacheentityFormatScript, BlobStorage.BinaryCacheSize); using (var cmd = new SqlProcedure { CommandText = commandText }) { cmd.Parameters.Add("@VersionId", SqlDbType.Int).Value = versionId; cmd.Parameters.Add("@PropertyTypeId", SqlDbType.Int).Value = propertyTypeId; cmd.CommandType = CommandType.Text; using (var reader = cmd.ExecuteReader(CommandBehavior.SingleRow | CommandBehavior.SingleResult)) { if (!reader.HasRows || !reader.Read()) { return(null); } var length = reader.GetInt64(0); var binaryPropertyId = reader.GetInt32(1); var fileId = reader.GetInt32(2); var providerName = reader.GetSafeString(3); var providerTextData = reader.GetSafeString(4); byte[] rawData = null; SqlFileStreamData fileStreamData = null; var useFileStream = providerName == null && reader.GetInt32(6) == 1; if (useFileStream) { // fill Filestream info if we really need it fileStreamData = new SqlFileStreamData { Path = reader.GetSafeString(7), TransactionContext = reader.GetSqlBytes(8).Buffer }; } var provider = useFileStream ? new SqlFileStreamBlobProvider() : BlobStorageBase.GetProvider(providerName); var context = new BlobStorageContext(provider, providerTextData) { VersionId = versionId, PropertyTypeId = propertyTypeId, FileId = fileId, Length = length }; if (provider == BlobStorageBase.BuiltInProvider) { context.BlobProviderData = new BuiltinBlobProviderData(); } else if (provider is SqlFileStreamBlobProvider) { context.BlobProviderData = new SqlFileStreamBlobProviderData { FileStreamData = fileStreamData } } ; if (IsBuiltInOrSqlFileStreamProvider(provider)) { if (!reader.IsDBNull(5)) { rawData = (byte[])reader.GetValue(5); } } return(new BinaryCacheEntity { Length = length, RawData = rawData, BinaryPropertyId = binaryPropertyId, FileId = fileId, Context = context }); } } }
/// <summary> /// Updates an existing binary property value in the database and the blob storage. /// </summary> /// <param name="blobProvider">Blob storage provider.</param> /// <param name="value">Binary data to update.</param> public void UpdateBinaryProperty(IBlobProvider blobProvider, BinaryDataValue value) { var streamLength = value.Stream?.Length ?? 0; var isExternal = false; if (!IsBuiltInOrSqlFileStreamProvider(blobProvider)) { var ctx = new BlobStorageContext(blobProvider, value.BlobProviderData) { VersionId = 0, PropertyTypeId = 0, FileId = value.FileId, Length = streamLength, }; blobProvider.Allocate(ctx); isExternal = true; value.BlobProviderName = ctx.Provider.GetType().FullName; value.BlobProviderData = BlobStorageContext.SerializeBlobProviderData(ctx.BlobProviderData); } else { value.BlobProviderName = null; value.BlobProviderData = null; } var isRepositoryStream = value.Stream is RepositoryStream || value.Stream is SenseNetSqlFileStream; var hasStream = isRepositoryStream || value.Stream is MemoryStream; if (!isExternal && !hasStream) { // do not do any database operation if the stream is not modified return; } SqlFileStreamData fileStreamData = null; SqlProcedure cmd = null; try { string sql; CommandType commandType; if (IsBuiltInOrSqlFileStreamProvider(blobProvider)) { commandType = CommandType.StoredProcedure; sql = "proc_BinaryProperty_Update"; } else { commandType = CommandType.Text; sql = UpdateBinarypropertyNewFilerowFilestreamScript; } cmd = new SqlProcedure { CommandText = sql, CommandType = commandType }; cmd.Parameters.Add("@BinaryPropertyId", SqlDbType.Int).Value = value.Id; cmd.Parameters.Add("@ContentType", SqlDbType.NVarChar, 450).Value = value.ContentType; cmd.Parameters.Add("@FileNameWithoutExtension", SqlDbType.NVarChar, 450).Value = value.FileName.FileNameWithoutExtension == null ? DBNull.Value : (object)value.FileName.FileNameWithoutExtension; cmd.Parameters.Add("@Extension", SqlDbType.NVarChar, 50).Value = ValidateExtension(value.FileName.Extension); cmd.Parameters.Add("@Size", SqlDbType.BigInt).Value = value.Size; cmd.Parameters.Add("@Checksum", SqlDbType.VarChar, 200).Value = value.Checksum != null ? (object)value.Checksum : DBNull.Value; cmd.Parameters.Add("@BlobProvider", SqlDbType.NVarChar, 450).Value = value.BlobProviderName != null ? (object)value.BlobProviderName : DBNull.Value; cmd.Parameters.Add("@BlobProviderData", SqlDbType.NVarChar, int.MaxValue).Value = value.BlobProviderData != null ? (object)value.BlobProviderData : DBNull.Value; int fileId; string path; byte[] transactionContext; // Update row and retrieve file path and // transaction context for the Filestream column using (var reader = cmd.ExecuteReader()) { reader.Read(); fileId = reader.GetInt32(0); path = reader.GetSafeString(1); transactionContext = reader.IsDBNull(2) ? null : reader.GetSqlBytes(2).Buffer; } if (!string.IsNullOrEmpty(path)) { fileStreamData = new SqlFileStreamData { Path = path, TransactionContext = transactionContext } } ; if (fileId > 0 && fileId != value.FileId) { value.FileId = fileId; } } finally { cmd?.Dispose(); } if (blobProvider == BlobStorageBase.BuiltInProvider) { var ctx = new BlobStorageContext(blobProvider, value.BlobProviderData) { VersionId = 0, PropertyTypeId = 0, FileId = value.FileId, Length = streamLength, BlobProviderData = new SqlFileStreamBlobProviderData { FileStreamData = fileStreamData } }; BuiltInBlobProvider.UpdateStream(ctx, value.Stream); } else if (blobProvider is SqlFileStreamBlobProvider) { var ctx = new BlobStorageContext(blobProvider, value.BlobProviderData) { VersionId = 0, PropertyTypeId = 0, FileId = value.FileId, Length = streamLength, BlobProviderData = new SqlFileStreamBlobProviderData { FileStreamData = fileStreamData } }; SqlFileStreamBlobProvider.UpdateStream(ctx, value.Stream); } else { var ctx = new BlobStorageContext(blobProvider, value.BlobProviderData) { VersionId = 0, PropertyTypeId = 0, FileId = value.FileId, Length = streamLength, }; using (var stream = blobProvider.GetStreamForWrite(ctx)) value.Stream?.CopyTo(stream); } }
/// <summary> /// Inserts a new binary property value into the metadata database and the blob storage, /// removing the previous one if the content is not new. /// </summary> /// <param name="blobProvider">Blob storage provider.</param> /// <param name="value">Binary data to insert.</param> /// <param name="versionId">Content version id.</param> /// <param name="propertyTypeId">Binary property type id.</param> /// <param name="isNewNode">Whether this value belongs to a new or an existing node.</param> public void InsertBinaryProperty(IBlobProvider blobProvider, BinaryDataValue value, int versionId, int propertyTypeId, bool isNewNode) { var streamLength = value.Stream?.Length ?? 0; var useFileStream = SqlFileStreamBlobProvider.UseFileStream(blobProvider, streamLength); var ctx = new BlobStorageContext(blobProvider) { VersionId = versionId, PropertyTypeId = propertyTypeId, FileId = 0, Length = streamLength }; // In case of an external provider allocate the place for bytes and // write the stream beforehand and get the generated provider data. // Note that the external provider does not need an existing record // in the Files table to work, it just stores the bytes. if (!IsBuiltInOrSqlFileStreamProvider(blobProvider)) { blobProvider.Allocate(ctx); using (var stream = blobProvider.GetStreamForWrite(ctx)) value.Stream?.CopyTo(stream); value.BlobProviderName = ctx.Provider.GetType().FullName; value.BlobProviderData = BlobStorageContext.SerializeBlobProviderData(ctx.BlobProviderData); } SqlProcedure cmd = null; SqlFileStreamData fileStreamData = null; try { cmd = useFileStream ? new SqlProcedure { CommandText = isNewNode ? InsertBinaryPropertyFilestreamScript : DeleteAndInsertBinaryPropertyFilestream, CommandType = CommandType.Text } : new SqlProcedure { CommandText = isNewNode ? InsertBinaryPropertyScript : DeleteAndInsertBinaryProperty, CommandType = CommandType.Text }; cmd.Parameters.Add("@VersionId", SqlDbType.Int).Value = versionId != 0 ? (object)versionId : DBNull.Value; cmd.Parameters.Add("@PropertyTypeId", SqlDbType.Int).Value = propertyTypeId != 0 ? (object)propertyTypeId : DBNull.Value; cmd.Parameters.Add("@ContentType", SqlDbType.NVarChar, 450).Value = value.ContentType; cmd.Parameters.Add("@FileNameWithoutExtension", SqlDbType.NVarChar, 450).Value = value.FileName.FileNameWithoutExtension == null ? DBNull.Value : (object)value.FileName.FileNameWithoutExtension; cmd.Parameters.Add("@Extension", SqlDbType.NVarChar, 50).Value = ValidateExtension(value.FileName.Extension); cmd.Parameters.Add("@Size", SqlDbType.BigInt).Value = Math.Max(0, value.Size); cmd.Parameters.Add("@BlobProvider", SqlDbType.NVarChar, 450).Value = value.BlobProviderName != null ? (object)value.BlobProviderName : DBNull.Value; cmd.Parameters.Add("@BlobProviderData", SqlDbType.NVarChar, int.MaxValue).Value = value.BlobProviderData != null ? (object)value.BlobProviderData : DBNull.Value; cmd.Parameters.Add("@Checksum", SqlDbType.VarChar, 200).Value = value.Checksum != null ? (object)value.Checksum : DBNull.Value; // insert binary and file rows and retrieve file path and transaction context for the Filestream column using (var reader = cmd.ExecuteReader()) { reader.Read(); value.Id = Convert.ToInt32(reader[0]); value.FileId = Convert.ToInt32(reader[1]); value.Timestamp = Utility.Convert.BytesToLong((byte[])reader.GetValue(2)); if (useFileStream) { fileStreamData = new SqlFileStreamData { Path = reader.GetString(3), TransactionContext = reader.GetSqlBytes(4).Buffer }; } } } finally { cmd.Dispose(); } // The BuiltIn blob provider saves the stream after the record // was saved into the Files table, because simple varbinary // and sql filestream columns must exist before we can write a // stream into the record. // ReSharper disable once InvertIf if (blobProvider == BlobStorageBase.BuiltInProvider && value.Stream != null) { ctx.FileId = value.FileId; ctx.BlobProviderData = new SqlFileStreamBlobProviderData { FileStreamData = fileStreamData }; BuiltInBlobProvider.AddStream(ctx, value.Stream); } else if (blobProvider is SqlFileStreamBlobProvider && value.Stream != null) { ctx.FileId = value.FileId; ctx.BlobProviderData = new SqlFileStreamBlobProviderData { FileStreamData = fileStreamData }; SqlFileStreamBlobProvider.AddStream(ctx, value.Stream); } }
public override int Read(byte[] buffer, int offset, int count) { if (buffer == null) { throw new ArgumentNullException(nameof(buffer)); } if (offset + count > buffer.Length) { throw new ArgumentException("Offset + count must not be greater than the buffer length."); } if (offset < 0) { throw new ArgumentOutOfRangeException(nameof(offset), "The offset must be greater than zero."); } if (count < 0) { throw new ArgumentOutOfRangeException(nameof(count), "The count must be greater than zero."); } // Calculate the maximum count of the bytes that can be read. // Return immediately if nothing to read. var maximumReadableByteCount = Length - Position; if (maximumReadableByteCount < 1) { return(0); } var isLocalTransaction = false; var realCount = (int)Math.Min(count, maximumReadableByteCount); if (CanInnerBufferHandleReadRequest(realCount)) { Array.Copy(_innerBuffer, Position - _innerBufferFirstPostion, buffer, offset, realCount); } else { if (!TransactionScope.IsActive) { // make sure we do not use an obsolete value _fileStreamData = null; // Start a new transaction here to serve the needs of the SqlFileStream type. TransactionScope.Begin(); isLocalTransaction = true; } try { // Load transaction data for SqlFilestream. If this is not a local transaction, // than we will be able to use this data in the future if the client calls // the Read method multiple times and will not have to execute SQL queries // every time. var ctx = BlobStorageBase.GetBlobStorageContext(FileId); if (ctx != null) { if (SqlFileStreamBlobMetaDataProvider.IsBuiltInOrSqlFileStreamProvider(ctx.Provider)) { _fileStreamData = ((SqlFileStreamBlobProviderData)ctx.BlobProviderData).FileStreamData; } } if (_fileStreamData == null) { throw new InvalidOperationException($"Transaction data and file path could not be retrieved for SqlFilestream. FileId: {FileId}. Provider: {ctx?.Provider?.GetType().FullName}"); } using (var fs = new SqlFileStream(_fileStreamData.Path, _fileStreamData.TransactionContext, FileAccess.Read, FileOptions.SequentialScan, 0)) { fs.Seek(Position, SeekOrigin.Begin); _innerBuffer = null; var bytesRead = 0; var bytesStoredInInnerBuffer = 0; while (bytesRead < realCount) { var bytesToReadInThisIteration = (int)Math.Min(Length - Position - bytesRead, BlobStorage.BinaryChunkSize); var bytesToStoreInThisIteration = Math.Min(bytesToReadInThisIteration, realCount - bytesRead); var tempBuffer = new byte[bytesToReadInThisIteration]; // copy the bytes from the file stream to the temp buffer // (it is possible that we loaded a lot more bytes than the client requested) fs.Read(tempBuffer, 0, bytesToReadInThisIteration); // first iteration: create inner buffer for caching a part of the stream in memory if (_innerBuffer == null) { _innerBuffer = new byte[GetInnerBufferSize(realCount)]; _innerBufferFirstPostion = Position; } // store a fragment of the data in the inner buffer if possible if (bytesStoredInInnerBuffer < _innerBuffer.Length) { var bytesToStoreInInnerBuffer = Math.Min(bytesToReadInThisIteration, _innerBuffer.Length - bytesStoredInInnerBuffer); Array.Copy(tempBuffer, 0, _innerBuffer, bytesStoredInInnerBuffer, bytesToStoreInInnerBuffer); bytesStoredInInnerBuffer += bytesToStoreInInnerBuffer; } // copy the chunk from the temp buffer to the buffer of the caller Array.Copy(tempBuffer, 0, buffer, bytesRead, bytesToStoreInThisIteration); bytesRead += bytesToReadInThisIteration; } } } catch { // ReSharper disable once InvertIf if (isLocalTransaction && TransactionScope.IsActive) { TransactionScope.Rollback(); // cleanup isLocalTransaction = false; _fileStreamData = null; } throw; } finally { if (isLocalTransaction && TransactionScope.IsActive) { TransactionScope.Commit(); // Set filestream data to null as this was a local transaction and we cannot use it anymore _fileStreamData = null; } } } Position += realCount; return(realCount); }