/// <summary>
        /// Opens the archive stream for reading.
        /// </summary>
        /// <param name="archiveNumber">The zero-based index of the archive to
        /// open.</param>
        /// <param name="archiveName">The name of the archive being opened.</param>
        /// <param name="compressionEngine">Instance of the compression engine
        /// doing the operations.</param>
        /// <returns>A stream from which archive bytes are read, or null to cancel
        /// extraction of the archive.</returns>
        /// <remarks>
        /// This method opens the file from the <see cref="ArchiveFiles"/> list with
        /// the specified index. If the archive number is outside the bounds of the
        /// list, this method returns null.
        /// <para>If the <see cref="EnableOffsetOpen"/> flag is set, this method will
        /// seek to the start of any existing archive in the file, or to the end of
        /// the file if the existing file is not an archive.</para>
        /// </remarks>
        public virtual Stream OpenArchiveReadStream(
            int archiveNumber, string archiveName, CompressionEngine compressionEngine)
        {
            if (archiveNumber >= this.archiveFiles.Count)
            {
                return null;
            }

            string archiveFile = this.archiveFiles[archiveNumber];
            Stream stream = File.Open(
                archiveFile, FileMode.Open, FileAccess.Read, FileShare.Read);

            if (this.enableOffsetOpen)
            {
                if (compressionEngine == null) {
                    throw new ArgumentNullException("compressionEngine");
                }
                long offset = compressionEngine.FindArchiveOffset(
                    new DuplicateStream(stream));
                if (offset > 0)
                {
                    stream = new OffsetStream(stream, offset);
                }
                else
                {
                    stream.Seek(0, SeekOrigin.Begin);
                }
            }

            return stream;
        }
 /// <summary>
 /// Opens the archive stream for reading. Returns a DuplicateStream instance,
 /// so the stream may be virtually opened multiple times.
 /// </summary>
 /// <param name="archiveNumber">The archive number to open (ignored; 0 is assumed).</param>
 /// <param name="archiveName">The name of the archive being opened.</param>
 /// <param name="compressionEngine">Instance of the compression engine doing the operations.</param>
 /// <returns>A stream from which archive bytes are read.</returns>
 public Stream OpenArchiveReadStream(int archiveNumber, string archiveName, CompressionEngine compressionEngine)
 {
     return new DuplicateStream(this.archiveStream);
 }
        /// <summary>
        /// Opens a stream for writing an archive.
        /// </summary>
        /// <param name="archiveNumber">The 0-based index of the archive within
        /// the chain.</param>
        /// <param name="archiveName">The name of the archive that was returned
        /// by <see cref="GetArchiveName"/>.</param>
        /// <param name="truncate">True if the stream should be truncated when
        /// opened (if it already exists); false if an existing stream is being
        /// re-opened for writing additional data.</param>
        /// <param name="compressionEngine">Instance of the compression engine
        /// doing the operations.</param>
        /// <returns>A writable Stream where the compressed archive bytes will be
        /// written, or null to cancel the archive creation.</returns>
        /// <remarks>
        /// This method opens the file from the <see cref="ArchiveFiles"/> list
        /// with the specified index. If the archive number is outside the bounds
        /// of the list, this method returns null.
        /// <para>If the <see cref="EnableOffsetOpen"/> flag is set, this method
        /// will seek to the start of any existing archive in the file, or to the
        /// end of the file if the existing file is not an archive.</para>
        /// </remarks>
        public virtual Stream OpenArchiveWriteStream(
            int archiveNumber,
            string archiveName,
            bool truncate,
            CompressionEngine compressionEngine)
        {
            if (archiveNumber >= this.archiveFiles.Count)
            {
                return null;
            }

            if (string.IsNullOrWhiteSpace(archiveName))
            {
                throw new ArgumentNullException("archiveName");
            }

            // All archives must be in the same directory,
            // so always use the directory from the first archive.
            string archiveFile = Path.Combine(
                Path.GetDirectoryName(this.archiveFiles[0]), archiveName);
            Stream stream = File.Open(
                archiveFile,
                (truncate ? FileMode.OpenOrCreate : FileMode.Open),
                FileAccess.ReadWrite);

            if (this.enableOffsetOpen)
            {
                if (compressionEngine == null) {
                    throw new ArgumentNullException("compressionEngine");
                }

                long offset = compressionEngine.FindArchiveOffset(
                    new DuplicateStream(stream));

                // If this is not an archive file, append the archive to it.
                if (offset < 0)
                {
                    offset = stream.Length;
                }

                if (offset > 0)
                {
                    stream = new OffsetStream(stream, offset);
                }

                stream.Seek(0, SeekOrigin.Begin);
            }

            if (truncate)
            {
                // Truncate the stream, in case a larger old archive starts here.
                stream.SetLength(0);
            }

            return stream;
        }