// ========================================================= 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
            });
        }
示例#3
0
        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);
        }
示例#4
0
        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);
        }