mperdeck / LINQtoCSV

Popular, easy to use library to read and write CSV files.
197 stars 112 forks source link

Support for streams that cannot Seek (like GZipStream) #22

Open hamishmack opened 10 years ago

hamishmack commented 10 years ago

The use of Seek makes it difficult to use this library with some streams because they do not support seeking (like GZipStream).

Also it seems that if you try to enumerate the result a second time before finishing with the first then both will wind up using the same stream object. I can't see how that could work.

It would be nice if the functions took a sort of StreamReader factory instead of a StreamReader.

    public IEnumerable<T> Read<T>(Func<StreamReader> streamFactory)...
    public IEnumerable<T> Read<T>(Func<StreamReader> streamFactory, CsvFileDescription fileDescription)...

Then instead of calling Seek the library would just call the factory to get a new StreamReader. This would be much more in keeping with the way the library works when reading files.

For anyone who needs a work around, here is the one I came up with for now. It is kind of horrible and you have to be careful to only use the IEnumerable once.

    /// <summary>
    /// Workaround for the fact that LINQToCSV tries to rewind the zip stream.
    /// This stream has CanSeek == true, but in fact you can only call Seek
    /// if to go to the beginning of the file and only when you are already there
    /// (have not read any data)
    /// </summary>
    class LazyRewindGZipStream : GZipStream {
        // Keep track of weather we are at the start of the stream
        private bool _atStart = true;

        public LazyRewindGZipStream(Stream stream, CompressionMode mode)
            : base(stream, mode) {}

        public override int Read(byte[] array, int offset, int count) {
            _atStart = false; // We are not at the start of the stream any more
            return base.Read(array, offset, count);
        }
        public override IAsyncResult BeginRead(byte[] array, int offset, int count, AsyncCallback asyncCallback, object asyncState) {
            _atStart = false; // We are not at the start of the stream any more
            return base.BeginRead(array, offset, count, asyncCallback, asyncState);
        }
        public override bool CanSeek {
            get {
                return true; // Permit seek (even though most seeks will be unsupported)
            }
        }
        public override long Seek(long offset, SeekOrigin origin) {
            // Only case where we want to allow this
            if (offset == 0 && origin == SeekOrigin.Begin && _atStart)
                return 0;

            // Other wise this is still not supported
            return base.Seek(offset, origin);
        }
    }