using System; using System.IO; using Best.HTTP.Shared; using Best.HTTP.Shared.Logger; using Best.HTTP.Shared.PlatformSupport.Memory; using UnityEngine; namespace Best.HTTP.Caching { /// /// Represents a writer for caching HTTP response content. /// public class HTTPCacheContentWriter { /// /// Gets the parent HTTPCache instance associated with this content writer. /// public HTTPCache Cache { get; private set; } /// /// Hash identifying the resource. If fails, it becomes an invalid one. /// public Hash128 Hash { get; private set; } /// /// Expected length of the content. Has a non-zero value only when the server is sending a "content-length" header. /// public ulong ExpectedLength { get; private set; } /// /// Number of bytes written to the cache. /// public ulong ProcessedLength { get; private set; } /// /// Context of this cache writer used for logging. /// public LoggingContext Context { get; private set; } /// /// Underlying stream the download bytes are written into. /// private Stream _contentStream; internal HTTPCacheContentWriter(HTTPCache cache, Hash128 hash, Stream contentStream, ulong expectedLength, LoggingContext loggingContext) { this.Cache = cache; this.Hash = hash; this._contentStream = contentStream; this.ExpectedLength = expectedLength; this.Context = loggingContext; } /// /// Writes content to the underlying stream. /// /// holding a reference to the data and containing information about the offset and count of the valid range of data. public void Write(BufferSegment segment) { if (!this.Hash.isValid) return; try { this._contentStream?.Write(segment.Data, segment.Offset, segment.Count); this.ProcessedLength += (ulong)segment.Count; if (this.ProcessedLength > this.ExpectedLength) { if (!this.Cache.IsThereEnoughSpaceAfterMaintain(this.ProcessedLength, this.Context)) { HTTPManager.Logger.Information(nameof(HTTPCacheContentWriter), $"Not enough space({this.ProcessedLength:N0}) in cache({this.Cache.CacheSize:N0}), even after Maintain!", this.Context); this.Cache?.EndCache(this, false, this.Context); } } } catch (Exception ex) { HTTPManager.Logger.Warning(nameof(HTTPCacheContentWriter), $"{nameof(Write)}({segment}): {ex}", this.Context); // EndCache will call Close, we don't have to in this catch block this.Cache?.EndCache(this, false, this.Context); } } /// /// Close the underlying stream and invalidate the hash. /// internal void Close() { this._contentStream?.Close(); this._contentStream = null; // this will set an invalid cache, further HTTPCaching.EndCache calls will return early. this.Hash = new Hash128(); } public override string ToString() => $"[{nameof(HTTPCacheContentWriter)} {Hash} {ProcessedLength:N0}]"; } }