File: net\System\Net\Cache\_SingleItemRequestCache.cs
Project: ndp\fx\src\System.csproj (System)
/*++
Copyright (c) Microsoft Corporation
 
Module Name:
 
    _SingleItemRequestCache.cs
 
Abstract:
    Request Caching subsystem capable of caching one file at a time.
    Used by, for example, auto-proxy script downloading.
 
Author:
    Justin Brown - Aug 2, 2004
 
Revision History:
 
--*/
 
namespace System.Net.Cache
{
    using System;
    using System.Net;
    using System.Diagnostics;
    using System.Text;
    using System.IO;
    using System.Collections.Specialized;
    using System.Threading;
    using System.Collections;
 
    internal class SingleItemRequestCache :
#if !FEATURE_PAL
        Microsoft.Win32.WinInetCache
#else
        RequestCache
#endif
    {
        bool _UseWinInet;
        FrozenCacheEntry _Entry;
 
        private sealed class FrozenCacheEntry: RequestCacheEntry {
            byte[] _StreamBytes;
            string _Key;
            
            public FrozenCacheEntry(string key, RequestCacheEntry entry, Stream stream): this(key, entry, GetBytes(stream))
            {
            }
            public FrozenCacheEntry(string key, RequestCacheEntry entry, byte[] streamBytes): base()
            {
                _Key = key;
                _StreamBytes = streamBytes;
                 IsPrivateEntry = entry.IsPrivateEntry;
                 StreamSize = entry.StreamSize;
                 ExpiresUtc = entry.ExpiresUtc;
                 HitCount = entry.HitCount;
                 LastAccessedUtc = entry.LastAccessedUtc;
                 entry.LastModifiedUtc = entry.LastModifiedUtc;
                 LastSynchronizedUtc = entry.LastSynchronizedUtc;
                 MaxStale = entry.MaxStale;
                 UsageCount = entry.UsageCount;
                 IsPartialEntry = entry.IsPartialEntry;
                 EntryMetadata = entry.EntryMetadata;
                 SystemMetadata  = entry.SystemMetadata;
            }
 
            static byte[] GetBytes(Stream stream)
            {
                byte[] bytes;
                bool   resize = false;
                if (stream.CanSeek)
                    bytes = new byte[stream.Length];
                else
                {
                    resize = true;
                    bytes = new byte[4096*2];
                }
                
                int offset = 0;
                while (true)
                {   int read = stream.Read(bytes, offset, bytes.Length-offset);
                    if (read == 0)
                        break;
                    if ((offset+=read) == bytes.Length && resize)
                    {
                        byte[] newBytes = new byte[bytes.Length+4096*2];
                        Buffer.BlockCopy(bytes, 0, newBytes, 0, offset);
                        bytes = newBytes;
                    }
                }
                if (resize)
                {
                    byte[] newBytes = new byte[offset];
                    Buffer.BlockCopy(bytes, 0, newBytes, 0, offset);
                    bytes = newBytes;
                }
                return bytes;
            }
 
            public static FrozenCacheEntry Create(FrozenCacheEntry clonedObject)
            {
                return (object)clonedObject == (object)null? null: (FrozenCacheEntry) clonedObject.MemberwiseClone();
            }
 
            public byte[] StreamBytes { get {return _StreamBytes;}}
            public string Key         { get  {return _Key;}}
        }
 
 
        internal SingleItemRequestCache(bool useWinInet) :
#if !FEATURE_PAL
            base(true, true, false)
#else
            base(true, true)
#endif
        {
            _UseWinInet = useWinInet;
        }
 
        //  Returns a read data stream and metadata associated with a cached entry.
        //  Returns Stream.Null if there is no entry found.
        // <remarks> An opened cache entry be preserved until the stream is closed. </remarks>
        //
        internal override Stream Retrieve(string key, out RequestCacheEntry cacheEntry)
        {
            Stream result;
            if (!TryRetrieve(key, out cacheEntry, out result))
            {
                FileNotFoundException fileNotFoundException = new FileNotFoundException(null, key);
                throw new IOException(SR.GetString(SR.net_cache_retrieve_failure, fileNotFoundException.Message), fileNotFoundException);
            }
 
            return result;
        }
 
        // Returns a write cache stream associated with the string Key.
        // Passed parameters allow cache to update an entry metadata accordingly.
        // <remarks>  The commit operation should happen on the stream closure. </remarks>
        //
        internal override Stream Store(string key, long contentLength, DateTime expiresUtc, DateTime lastModifiedUtc, TimeSpan maxStale, StringCollection entryMetadata, StringCollection systemMetadata)
        {
            Stream result;
            if (!TryStore(key, contentLength, expiresUtc, lastModifiedUtc, maxStale, entryMetadata, systemMetadata, out result))
            {
                FileNotFoundException fileNotFoundException = new FileNotFoundException(null, key);
                throw new IOException(SR.GetString(SR.net_cache_retrieve_failure, fileNotFoundException.Message), fileNotFoundException);
            }
 
            return result;
        }
 
        //
        // Removes an entry from the cache.
        //
        internal override void Remove(string key)
        {
            if (!TryRemove(key))
            {
                FileNotFoundException fileNotFoundException = new FileNotFoundException(null, key);
                throw new IOException(SR.GetString(SR.net_cache_retrieve_failure, fileNotFoundException.Message), fileNotFoundException);
            }
        }
 
        //
        // Updates only metadata associated with a cached entry.
        //
        internal override void Update(string key, DateTime expiresUtc, DateTime lastModifiedUtc, DateTime lastSynchronizedUtc, TimeSpan maxStale, StringCollection entryMetadata, StringCollection systemMetadata)
        {
            if (!TryUpdate(key, expiresUtc, lastModifiedUtc, lastSynchronizedUtc, maxStale, entryMetadata, systemMetadata))
            {
                FileNotFoundException fileNotFoundException = new FileNotFoundException(null, key);
                throw new IOException(SR.GetString(SR.net_cache_retrieve_failure, fileNotFoundException.Message), fileNotFoundException);
            }
        }
 
        internal override bool TryRetrieve(string key, out RequestCacheEntry cacheEntry, out Stream readStream)
        {
            if (key == null)
                throw new ArgumentNullException("key");
 
            FrozenCacheEntry chkEntry = _Entry;
            cacheEntry = null;
            readStream = null;
 
            if (chkEntry == null || chkEntry.Key != key)
            {
#if !FEATURE_PAL
                Stream realCacheStream;
                RequestCacheEntry realCacheEntry;
                if (!_UseWinInet || !base.TryRetrieve(key, out realCacheEntry, out realCacheStream))
                    return false;
                
                chkEntry = new FrozenCacheEntry(key, realCacheEntry, realCacheStream);
                // Relasing the WinInet entry earlier because we don't forward metadata-only updates ot it.
                realCacheStream.Close();
                _Entry = chkEntry;
#else
                return false;
#endif
            }
            cacheEntry = FrozenCacheEntry.Create(chkEntry);
            readStream = new ReadOnlyStream(chkEntry.StreamBytes);
            return true;
        }
 
        internal override bool TryStore(string key, long contentLength, DateTime expiresUtc, DateTime lastModifiedUtc, TimeSpan maxStale, StringCollection entryMetadata, StringCollection systemMetadata, out Stream writeStream)
        {
            if (key == null)
                throw new ArgumentNullException("key");
 
            RequestCacheEntry requestCacheEntry = new RequestCacheEntry();
            requestCacheEntry.IsPrivateEntry = this.IsPrivateCache;
            requestCacheEntry.StreamSize = contentLength;
            requestCacheEntry.ExpiresUtc = expiresUtc;
            requestCacheEntry.LastModifiedUtc = lastModifiedUtc;
            requestCacheEntry.LastAccessedUtc = DateTime.UtcNow;
            requestCacheEntry.LastSynchronizedUtc = DateTime.UtcNow;
            requestCacheEntry.MaxStale = maxStale;
            requestCacheEntry.HitCount = 0;
            requestCacheEntry.UsageCount = 0;
            requestCacheEntry.IsPartialEntry = false;
            requestCacheEntry.EntryMetadata = entryMetadata;
            requestCacheEntry.SystemMetadata = systemMetadata;
 
            writeStream = null;
            Stream realWriteStream = null;
 
#if !FEATURE_PAL
            if (_UseWinInet)
            {
                base.TryStore(key, contentLength, expiresUtc, lastModifiedUtc, maxStale, entryMetadata, systemMetadata, out realWriteStream);
            }
#endif
            
            writeStream = new WriteOnlyStream(key, this, requestCacheEntry, realWriteStream);
            return true;
        }
 
        private void Commit(string key, RequestCacheEntry tempEntry, byte[] allBytes)
        {
            FrozenCacheEntry chkEntry = new FrozenCacheEntry(key, tempEntry, allBytes);
            _Entry = chkEntry;
        }
 
        internal override bool TryRemove(string key)
        {
            if (key == null)
                throw new ArgumentNullException("key");
 
#if !FEATURE_PAL
            if (_UseWinInet)
            {
                base.TryRemove(key);
            }
#endif
 
            FrozenCacheEntry chkEntry = _Entry;
            
            if (chkEntry != null && chkEntry.Key == key)
                _Entry = null;
 
            return true;
        }
 
        internal override bool TryUpdate(string key, DateTime expiresUtc, DateTime lastModifiedUtc, DateTime lastSynchronizedUtc, TimeSpan maxStale, StringCollection entryMetadata, StringCollection systemMetadata)
        {
            if (key == null)
                throw new ArgumentNullException("key");
 
            FrozenCacheEntry chkEntry = FrozenCacheEntry.Create(_Entry);
 
            //
            // This class does not forward metadata updates to WinInet to simplify the design and avoid interlocked ops
            //
 
            if (chkEntry == null || chkEntry.Key != key)
                return true;
 
            chkEntry.ExpiresUtc = expiresUtc;
            chkEntry.LastModifiedUtc = lastModifiedUtc;
            chkEntry.LastSynchronizedUtc = lastSynchronizedUtc;
            chkEntry.MaxStale = maxStale;
            chkEntry.EntryMetadata = entryMetadata;
            chkEntry.SystemMetadata = systemMetadata;
            _Entry = chkEntry;
            return true;
        }
        //
        // We've chosen to no forward to WinInet metadata-only updates
        // Hence our entries are never locked and this method does nothing
        //
        internal override void UnlockEntry(Stream stream)
        {
        }
 
        //
        //
        //
        internal class ReadOnlyStream : Stream, IRequestLifetimeTracker {
            private byte[] _Bytes;
            private int    _Offset;
            private bool   _Disposed;
            private int    _ReadTimeout;
            private int    _WriteTimeout;
            private RequestLifetimeSetter m_RequestLifetimeSetter;
 
            internal ReadOnlyStream(byte[] bytes): base()
            {
                _Bytes  = bytes;
                _Offset = 0;
                _Disposed = false;
                _ReadTimeout = _WriteTimeout = -1;
            }
 
            public override bool CanRead {get {return true;}}
            public override bool CanSeek {get {return true;}}
            public override bool CanTimeout {get {return true;}}
            public override bool CanWrite  {get {return false;}}
            public override long Length {get {return _Bytes.Length;}}
            public override long Position {
                get {return _Offset;}
                set {
                    if (value < 0 || value > (long)_Bytes.Length)
                        throw new ArgumentOutOfRangeException("value");
                    _Offset = (int)value;
                }
            }
 
            public override int ReadTimeout {
                get {return _ReadTimeout;}
                set {
                    if (value<=0 && value!=System.Threading.Timeout.Infinite) 
                        throw new ArgumentOutOfRangeException("value", SR.GetString(SR.net_io_timeout_use_gt_zero));
                    _ReadTimeout = value;
                }
            }
            public override int WriteTimeout {
                get {return _WriteTimeout;}
                set {
                    if (value<=0 && value!=System.Threading.Timeout.Infinite)
                        throw new ArgumentOutOfRangeException("value", SR.GetString(SR.net_io_timeout_use_gt_zero));
                    _WriteTimeout = value;
                }
            }
 
            public override void Flush() {}
 
            public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, Object state)
            {
                int result = Read(buffer, offset, count);
 
                LazyAsyncResult ar = new LazyAsyncResult(null, state, callback);
                ar.InvokeCallback(result);
                return ar;
            }
            public override int EndRead(IAsyncResult asyncResult)
            {
                if (asyncResult == null)
                    throw new ArgumentNullException("asyncResult");
                LazyAsyncResult ar = (LazyAsyncResult) asyncResult;
                if (ar.EndCalled) throw new InvalidOperationException(SR.GetString(SR.net_io_invalidendcall, "EndRead"));
                ar.EndCalled = true;
                return (int)ar.InternalWaitForCompletion();
            }
 
            public override int Read(byte[] buffer, int offset, int count)
            {
                if (_Disposed) throw new ObjectDisposedException(GetType().Name);
                if (buffer==null) throw new ArgumentNullException("buffer");
                if (offset<0 || offset>buffer.Length) throw new ArgumentOutOfRangeException("offset");
                if (count<0 || count>buffer.Length-offset) throw new ArgumentOutOfRangeException("count");
                if (_Offset == _Bytes.Length) return 0;
                
                int chkOffset = (int)_Offset;
                count = Math.Min(count, _Bytes.Length - chkOffset);
                System.Buffer.BlockCopy(_Bytes, chkOffset, buffer, offset, count);
                chkOffset += count;
                _Offset = chkOffset;
                return count;
            }
            public override IAsyncResult BeginWrite(byte[] buffer, int offset, int size, AsyncCallback callback, Object state)
            {
                throw new NotSupportedException(SR.GetString(SR.net_readonlystream));
            }
            public override void EndWrite(IAsyncResult asyncResult)
            {
                throw new NotSupportedException(SR.GetString(SR.net_readonlystream));
            }
            public override void Write(byte[] buffer, int offset, int count)
            {
                throw new NotSupportedException(SR.GetString(SR.net_readonlystream));
            }
 
 
            public override long Seek(long offset, SeekOrigin origin)
            {
                switch (origin)
                {
                case SeekOrigin.Begin:
                        return Position = offset;
                case SeekOrigin.Current:
                        return Position += offset;
                    /// <include file='doc\SeekOrigin.uex' path='docs/doc[@for="SeekOrigin.End"]/*' />
                case SeekOrigin.End:  return Position = _Bytes.Length-offset;
                default:
                    throw new ArgumentException(SR.GetString(SR.net_invalid_enum, "SeekOrigin"), "origin");
                }
            }
 
            public override void SetLength(long length)
            {
                throw new NotSupportedException(SR.GetString(SR.net_readonlystream));
            }
 
 
            protected override void Dispose(bool disposing)
            {
                try {
                    if (!_Disposed) {
                        _Disposed = true;
                       
                        if (disposing) {
                            RequestLifetimeSetter.Report(m_RequestLifetimeSetter);
                        }
                    }
                }
                finally {
                    base.Dispose(disposing);
                }
            }
 
            internal byte[] Buffer
            {
                get
                {
                    return _Bytes;
                }
            }
 
            void IRequestLifetimeTracker.TrackRequestLifetime(long requestStartTimestamp)
            {
                Debug.Assert(m_RequestLifetimeSetter == null, "TrackRequestLifetime called more than once.");
                m_RequestLifetimeSetter = new RequestLifetimeSetter(requestStartTimestamp);
            }
        }
 
        //
        //
        //
        private class WriteOnlyStream: Stream {
            private string                 _Key;
            private SingleItemRequestCache _Cache;
            private RequestCacheEntry      _TempEntry;
            private Stream                 _RealStream;
            private long                   _TotalSize;
            private ArrayList              _Buffers;
 
            private bool   _Disposed;
            private int    _ReadTimeout;
            private int    _WriteTimeout;
 
            public WriteOnlyStream(string key, SingleItemRequestCache cache, RequestCacheEntry cacheEntry, Stream realWriteStream)
            {
                _Key = key;
                _Cache = cache;
                _TempEntry = cacheEntry;
                _RealStream = realWriteStream;
                _Buffers = new ArrayList();
            }
 
            public override bool CanRead {get {return false;}}
            public override bool CanSeek {get {return false;}}
 
            public override bool CanTimeout {get {return true;}}
            public override bool CanWrite  {get {return true;}}
 
            public override long Length {get {throw new NotSupportedException(SR.GetString(SR.net_writeonlystream));}}
            
            public override long Position {
                get {throw new NotSupportedException(SR.GetString(SR.net_writeonlystream));}
                set {throw new NotSupportedException(SR.GetString(SR.net_writeonlystream));}
            }
 
            public override int ReadTimeout {
                get {return _ReadTimeout;}
                set {
                    if (value<=0 && value!=System.Threading.Timeout.Infinite)
                        throw new ArgumentOutOfRangeException("value", SR.GetString(SR.net_io_timeout_use_gt_zero));
                    _ReadTimeout = value;
                }
            }
            public override int WriteTimeout {
                get {return _WriteTimeout;}
                set {
                    if (value<=0 && value!=System.Threading.Timeout.Infinite)
                        throw new ArgumentOutOfRangeException("value", SR.GetString(SR.net_io_timeout_use_gt_zero));
                    _WriteTimeout = value;
                }
            }
 
            public override void Flush() {}
 
            public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, Object state)
            {throw new NotSupportedException(SR.GetString(SR.net_writeonlystream));}
 
            public override int EndRead(IAsyncResult asyncResult)
            {throw new NotSupportedException(SR.GetString(SR.net_writeonlystream));}
 
            public override int Read(byte[] buffer, int offset, int count)
            {throw new NotSupportedException(SR.GetString(SR.net_writeonlystream));}
 
            public override long Seek(long offset, SeekOrigin origin)
            {throw new NotSupportedException(SR.GetString(SR.net_writeonlystream));}
            public override void SetLength(long length)
            {throw new NotSupportedException(SR.GetString(SR.net_writeonlystream));}
 
 
            public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, Object state)
            {
                Write(buffer, offset, count);
                LazyAsyncResult ar = new LazyAsyncResult(null, state, callback);
                ar.InvokeCallback(null);
                return ar;
            }
            public override void EndWrite(IAsyncResult asyncResult)
            {
                if (asyncResult == null)
                    throw new ArgumentNullException("asyncResult");
                LazyAsyncResult ar = (LazyAsyncResult) asyncResult;
                if (ar.EndCalled) throw new InvalidOperationException(SR.GetString(SR.net_io_invalidendcall, "EndWrite"));
                ar.EndCalled = true;
                ar.InternalWaitForCompletion();
            }
            public override void Write(byte[] buffer, int offset, int count)
            {
                if (_Disposed) throw new ObjectDisposedException(GetType().Name);
                if (buffer==null) throw new ArgumentNullException("buffer");
                if (offset<0 || offset>buffer.Length) throw new ArgumentOutOfRangeException("offset");
                if (count<0  || count>buffer.Length-offset) throw new ArgumentOutOfRangeException("count");
                
                if (_RealStream != null)
                    try {
                        _RealStream.Write(buffer, offset, count);
                    }
                    catch {
                        _RealStream.Close();
                        _RealStream = null;
                    }
 
                byte[] chunk = new byte[count];
                System.Buffer.BlockCopy(buffer, offset, chunk, 0, count);
                _Buffers.Add(chunk);
                _TotalSize += count;
            }
 
            protected override void Dispose(bool disposing)
            {
                _Disposed = true;
                base.Dispose(disposing);  // Do we mean to do this here????
                if (disposing) {
                    if (_RealStream != null)
                        try {
                            _RealStream.Close();
                        }
                        catch {
                        }
 
                    byte[] allBytes = new byte[_TotalSize];
                    int offset = 0;
                    for (int i = 0; i < _Buffers.Count; ++i)
                    {
                        byte[] buffer = (byte[])_Buffers[i];
                        Buffer.BlockCopy(buffer, 0, allBytes, offset, buffer.Length);
                        offset += buffer.Length;
                    }
 
                    _Cache.Commit(_Key, _TempEntry, allBytes);
                }
            }
 
        }
    }
}