source: trunk/CrypPluginBase/IO/CStreamWriter.cs

Last change on this file was 8983, checked in by kopal, 3 months ago

Complete CrypTool 2 project

  • renamed "Cryptool" namespace to "CrypTool" namespace
File size: 12.3 KB
Line 
1/*
2   Copyright 2009-2010 Matthäus Wander, University of Duisburg-Essen
3
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7
8       http://www.apache.org/licenses/LICENSE-2.0
9
10   Unless required by applicable law or agreed to in writing, software
11   distributed under the License is distributed on an "AS IS" BASIS,
12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   See the License for the specific language governing permissions and
14   limitations under the License.
15*/
16
17using System;
18using System.IO;
19using System.Threading;
20using CrypTool.PluginBase.Miscellaneous;
21
22namespace CrypTool.PluginBase.IO
23{
24    /// <summary>
25    /// Create a stream to pass data with arbitrary size to another CT2 plugin.
26    /// The stream is internally backed by a non-cyclic memory buffer and switches automagically to a
27    /// temporary file if the membuff exceeds a certain size. Please note that the buffer does not
28    /// forget old data, therefore you can derive an arbitary number of stream readers at any time.
29    ///
30    /// <para>You SHOULD Flush() the stream when you're writing large data amounts and expect the readers
31    /// to perform intermediate processing before writing has been finished.
32    /// You MUST Close() the stream when you're finished with writing, otherwise the reader will block
33    /// and wait for more data infinitely.</para>
34    /// </summary>
35    public class CStreamWriter : Stream, ICrypToolStream
36    {
37        #region Fields and constructors
38
39        public static readonly CStreamWriter Empty;
40        static CStreamWriter()
41        {
42            // This is a somewhat ugly way to create an empty CStream -- using the Null Object
43            // pattern (see GOF) would look better. However it is the easiest workaround.
44            CStreamWriter.Empty = new CStreamWriter(0);
45            CStreamWriter.Empty.Close();
46        }
47
48        internal const int FileBufferSize = 8192;
49
50        private readonly object _monitor;
51        private bool _closed;
52
53        // membuff
54        private byte[] _buffer;
55        private int _bufPtr;
56
57        // swapfile
58        private FileStream _writeStream;
59        private string _filePath;
60
61        /// <summary>
62        /// Init CStreamWriter with 64 KB memory buffer
63        /// </summary>
64        public CStreamWriter()
65            : this(65536)
66        {
67        }
68
69        /// <summary>
70        /// Init CStreamWriter with custom size memory buffer (in bytes)
71        /// </summary>
72        public CStreamWriter(int bufSize)
73        {
74            _buffer = new byte[bufSize];
75            _monitor = new object();
76        }
77
78        /// <summary>
79        /// Init CStreamWriter and copy some data to memory buffer.
80        /// Data is *copied* from passed buffer to internal memory buffer.
81        /// Stream is auto-closed after initialization (can't write any more data).
82        /// </summary>
83        /// <param name="buf">Pre-initialized byte array which is copied into internal membuff</param>
84        public CStreamWriter(byte[] buf)
85            : this((buf == null) ? 0 : buf.Length)
86        {
87            if (buf != null)
88            {
89                Array.Copy(buf, _buffer, buf.Length);
90                _bufPtr = buf.Length;
91            }
92            Close();
93        }
94
95        /// <summary>
96        /// Init CStreamWriter with an existing file.
97        /// We acquire a read lock to ensure the file is not modified while we're accessing it.
98        /// Will throw exception if IO goes wrong.
99        /// Stream is auto-closed after initialization (can't write any data).
100        /// </summary>
101        /// <param name="filePath">Path to existing file, already filled with data</param>
102        public CStreamWriter(string filePath, bool allowAltering = false)
103            : this(0)
104        {
105            if (!File.Exists(filePath))
106            {
107                throw new FileNotFoundException();
108            }
109
110            // attempt to get shared read lock
111            // (this was a write lock previously but was changed due to #283)
112            _filePath = filePath;
113            var share = FileShare.Read;
114            if (allowAltering)
115            {
116                share = FileShare.ReadWrite;
117            }
118            _writeStream = new FileStream(_filePath, FileMode.Open, FileAccess.Read, share);
119            _writeStream.Seek(_writeStream.Length, SeekOrigin.Begin);
120            Close();
121
122            if (SwapEvent != null)
123                SwapEvent();
124        }
125
126        #endregion
127
128        #region Public properties
129
130        public override bool CanRead
131        {
132            get { return false; }
133        }
134
135        public override bool CanSeek
136        {
137            get { return false; }
138        }
139
140        public override bool CanWrite
141        {
142            get { return !_closed; }
143        }
144
145        /// <summary>
146        /// File path of swapfile (if any).
147        /// </summary>
148        public string FilePath
149        {
150            get { return _filePath; }
151        }
152
153        /// <summary>
154        /// Has the writer stream been marked as closed?
155        ///
156        /// Please note: closing the write stream is not equivalent with disposing it.
157        /// Readers can still read from a closed stream.
158        /// </summary>
159        public bool IsClosed
160        {
161            get
162            {
163                return _closed;
164            }
165            set
166            {
167                if (value)
168                    Close();
169            }
170        }
171
172        /// <summary>
173        /// Returns whether the underlying buffer is swapped out to filesystem or not.
174        /// </summary>
175        public bool IsSwapped
176        {
177            get
178            {
179                return _writeStream != null;
180            }
181        }
182
183        public override long Length
184        {
185            get
186            {
187                if (IsSwapped)
188                {
189                    // TODO: cache FileStream property
190                    return _writeStream.Length;
191                }
192                else
193                {
194                    return _bufPtr;
195                }
196            }
197        }
198
199        public override long Position
200        {
201            get
202            {
203                if (IsSwapped)
204                {
205                    // TODO: cache FileStream property
206                    return _writeStream.Position;
207                }
208                else
209                {
210                    return _bufPtr;
211                }
212            }
213
214            // writer cannot seek
215            set { throw new NotSupportedException(); }
216        }
217
218        #endregion
219
220        #region Public methods
221
222        /// <summary>
223        /// You MUST call Close() when you're done writing or the readers will be stuck in an infinite loop.
224        ///
225        /// Please note: Contrary to the API description of Stream.Close() this method DOES NOT release all
226        /// resources associated to this CStream.
227        /// </summary>
228        public override void Close()
229        {
230            /*
231             * Note 1: We're NOT following the pattern of the Stream class to implement cleanup code in
232             * Dispose() without touching Close(), because we explicitly want to mark a stream as closed
233             * without disposing the underlying mem/file buffer. That's why we also don't call base.Close().
234             *
235             * Note 2: Closing the CStream does not automatically close the underlying file handle, as
236             * the file is marked as DeleteOnClose and may be removed too early. File is closed when the
237             * CStreamWriter is garbage collected.
238             */
239
240            // do nothing if already closed
241            if (_closed)
242                return;
243
244            _closed = true;
245
246            Flush();
247        }
248
249        /// <summary>
250        /// Flush any caches, announce buffer freshness to readers.
251        /// </summary>
252        public override void Flush()
253        {
254            lock (_monitor)
255            {
256                if (IsSwapped)
257                {
258                    _writeStream.Flush();
259                }
260
261                // wakeup readers
262                Monitor.PulseAll(_monitor);
263            }
264        }
265
266        /// <summary>
267        /// Can not read, use CStream instead.
268        /// </summary>
269        public override int Read(byte[] buffer, int offset, int count)
270        {
271            throw new NotSupportedException();
272        }
273
274        /// <summary>
275        /// Writer cannot seek.
276        /// </summary>
277        /// <param name="offset"></param>
278        /// <param name="origin"></param>
279        /// <returns></returns>
280        public override long Seek(long offset, SeekOrigin origin)
281        {
282            throw new NotSupportedException();
283        }
284
285        /// <summary>
286        /// Writer cannot seek.
287        /// </summary>
288        /// <param name="value"></param>
289        public override void SetLength(long value)
290        {
291            throw new NotSupportedException();
292        }
293
294        /// <summary>
295        /// Convenience method for Write(byte[] buf, 0, buf.Length)
296        /// </summary>
297        /// <param name="buf"></param>
298        public void Write(byte[] buf)
299        {
300            Write(buf, 0, buf.Length);
301        }
302
303        /// <summary>
304        /// Write to mem/file buffer (switches transparently if required)
305        /// </summary>
306        public override void Write(byte[] buf, int offset, int count)
307        {
308            if (_closed)
309                throw new InvalidOperationException("Can't write, CStream already closed");
310
311            lock (_monitor)
312            {
313                if (!IsSwapped && hasMemorySpace(count))
314                {
315                    Array.Copy(buf, offset, _buffer, _bufPtr, count);
316                    _bufPtr += count;
317                }
318                else
319                {
320                    if (!IsSwapped)
321                    {
322                        createSwapFile();
323                        _writeStream.Write(_buffer, 0, _bufPtr);
324                        _writeStream.Flush(); // ensure reader can seek before announcing swap event
325                        _buffer = null;
326
327                        if (SwapEvent != null)
328                            SwapEvent();
329                    }
330
331                    _writeStream.Write(buf, offset, count);
332                }
333
334                // don't pulse monitor, wait for flush
335            }
336        }
337
338        /// <summary>
339        /// Create a new instance to read from this CStream.
340        /// </summary>
341        public CStreamReader CreateReader()
342        {
343            return new CStreamReader(this);
344        }
345
346        /// <summary>
347        /// Shows up to 4096 bytes of CStream as hex-string (00-AA-01-BF...)
348        /// </summary>
349        public override string ToString()
350        {
351            using (CStreamReader reader = CreateReader())
352            {
353                byte[] buf = new byte[4096];
354                int read = reader.Read(buf);
355
356                string hexString = Hex.HexToString(buf, 0, read);
357                if (reader.Length > 4096)
358                    hexString += "...";
359
360                return hexString;
361            }
362        }
363
364        #endregion
365
366        #region Private/protected methods
367
368        private bool hasMemorySpace(int count)
369        {
370            return _bufPtr + count <= _buffer.Length;
371        }
372
373        private void createSwapFile()
374        {
375            _filePath = DirectoryHelper.GetNewTempFilePath();
376            _writeStream = new FileStream(_filePath, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.Read, FileBufferSize, FileOptions.DeleteOnClose);
377        }
378
379        #endregion
380
381        #region Internal members for stream readers
382
383        internal object InternalMonitor
384        {
385            get { return _monitor; }
386        }
387
388        internal delegate void ReaderCallback();
389
390        internal event ReaderCallback SwapEvent;
391
392        internal byte[] MemBuff
393        {
394            get { return _buffer; }
395        }
396
397        #endregion
398
399    }
400}
Note: See TracBrowser for help on using the repository browser.