3 * Copyright (C)2007-2008 Versabanq Innovations Inc. and contributors.
4 * See the included file named LICENSE for license information.
12 public interface IWvStream
: IDisposable
15 Exception err { get; }
16 EndPoint localaddr { get; }
17 EndPoint remoteaddr { get; }
21 bool flush(int msec_timeout
);
23 event Action onreadable
;
24 event Action onwritable
;
32 public class WvStream
: IWvStream
34 public static IWvEventer ev
= new WvEventer();
36 public static void runonce()
41 public static void runonce(int msec_timeout
)
43 ev
.runonce(msec_timeout
);
55 bool is_readable
= false, is_writable
= false;
56 event Action _onreadable
, _onwritable
, _onclose
;
58 protected bool can_onreadable { get { return _onreadable != null; }
}
59 protected bool can_onwritable { get { return _onwritable != null; }
}
61 protected void do_readable()
70 protected void do_writable()
79 protected void do_close()
85 object pr_obj
= new object();
86 protected void post_readable()
89 ev
.addpending(pr_obj
, do_readable
);
92 object pw_obj
= new object();
93 protected void post_writable()
96 ev
.addpending(pw_obj
, do_writable
);
99 public virtual event Action onreadable
{
100 add { _onreadable += value; if (is_readable) post_readable(); }
101 remove { _onreadable -= value; }
103 public virtual event Action onwritable
{
104 add { _onwritable += value; if (is_writable) post_writable(); }
105 remove { _onwritable -= value; }
107 public virtual event Action onclose
{
108 add { _onclose += value; }
109 remove { _onclose -= value; }
113 public virtual bool ok { get { return isopen && err == null; }
}
116 public virtual Exception err
{
121 if (_err
== null) // remember the *first* error
129 public virtual void close()
131 ev
.delpending(pr_obj
);
132 ev
.delpending(pw_obj
);
139 if (_onclose
!= null) _onclose();
141 GC
.SuppressFinalize(this);
144 public virtual EndPoint localaddr
{
148 public virtual EndPoint remoteaddr
{
152 public virtual int read(WvBytes b
)
157 // for convenience. Note: always returns non-null, but the returned
158 // array size might be zero.
159 public WvBytes
read(int len
)
161 WvBytes bytes
= new byte[len
];
162 int got
= read(bytes
);
163 return bytes
.sub(0, got
);
166 public void read(WvBuf b
, int max
)
168 int got
= read(b
.alloc(max
));
172 public virtual int write(WvBytes b
)
174 return b
.len
; // lie: we "wrote" all the bytes to nowhere
178 * Wait up to msec_timeout milliseconds for the stream to become
179 * readable or writable, respectively.
181 * Our default implementation always returns immediately, consistent
182 * with the Unix select() behaviour of returning immediately on
183 * non-select()able file handles.
185 * Returns true if the stream is readable or writable before the
186 * timeout, false otherwise.
188 * Waiting synchronously is usually a bad idea in WvStreams
189 * programs. Use onreadable/onwritable/onclose instead whenever
192 public virtual bool wait(int msec_timeout
,
193 bool readable
, bool writable
)
195 if (readable
&& is_readable
)
197 if (writable
&& is_writable
)
199 if (!ok
|| (readable
&& !canread
) || (writable
&& !canwrite
))
204 // Don't make these anything but private! They're tempting, but
205 // they're not *really* what you want to know. Use these instead:
206 // ok -> whether or not your stream is useful at all
207 // wait(0, true, false) -> whether your stream has bytes *right now*.
208 // If canread goes false, wait(-1, true, false) will return false.
209 bool canread
= true, canwrite
= true;
211 public virtual void noread()
217 public virtual void nowrite()
223 public void maybe_autoclose()
225 if (!canread
&& !canwrite
)
229 public virtual bool flush(int msec_timeout
)
231 return true; // no buffer
234 // WARNING: assumes the write() will succeed! Use only on WvStreams
235 // with a write buffer.
236 public void print(string fmt
, params object[] args
)
238 print((object)wv
.fmt(fmt
, args
));
241 // WARNING: assumes the write() will succeed! Use only on WvStreams
242 // with a write buffer.
243 public virtual void print(object o
)
245 byte[] b
= o
.ToUTF8();
247 wv
.assert(n
== b
.Length
,
248 "Don't use print() on an unbuffered WvStream!");
252 // Wraps a WvStream in another WvStream, allowing us to override some
253 // behaviour. By default, a WvStreamClone just passes everything through
254 // to the inner stream.
255 public class WvStreamClone
: WvStream
257 protected WvStream inner
= null;
258 bool hasinner { get { return inner != null; }
}
260 public WvStreamClone(WvStream inner
)
265 public void setinner(WvStream inner
)
267 if (inner
!= this.inner
)
271 this.inner
.onreadable
-= do_readable
;
272 this.inner
.onwritable
-= do_writable
;
273 this.inner
.onclose
-= do_close
;
278 if (can_onreadable
) this.inner
.onreadable
+= do_readable
;
279 if (can_onwritable
) this.inner
.onwritable
+= do_writable
;
280 this.inner
.onclose
+= do_close
;
285 public override bool ok
{
286 get { return base.ok && hasinner && inner.ok; }
289 public override Exception err
{
291 return hasinner
? inner
.err
: base.err
;
301 public override EndPoint localaddr
{
302 get { return hasinner ? inner.localaddr : base.localaddr; }
304 public override EndPoint remoteaddr
{
305 get { return hasinner ? inner.localaddr : base.localaddr; }
308 public override int read(WvBytes b
)
311 return inner
.read(b
);
313 return 0; // 0 bytes read
316 public override int write(WvBytes b
)
319 return inner
.write(b
);
321 return 0; // 0 bytes written
324 public override bool wait(int msec_timeout
,
325 bool readable
, bool writable
)
328 return inner
.wait(msec_timeout
, readable
, writable
);
330 return base.wait(msec_timeout
, readable
, writable
);
333 public override void noread()
340 public override void nowrite()
347 public override bool flush(int msec_timeout
)
350 return inner
.flush(msec_timeout
);
355 // We only want to register our callback with the inner stream if
356 // we *have* a callback, and then only once. Otherwise the stream
357 // might start listening for read when we don't have any readable
358 // handlers, resulting in it spinning forever.
359 public override event Action onreadable
{
360 add { if (!can_onreadable
) inner
.onreadable
+= do_readable
;
361 base.onreadable
+= value; }
362 remove { base.onreadable
-= value;
363 if (!can_onreadable
) inner
.onreadable
-= do_readable
; }
365 public override event Action onwritable
{
366 add { if (!can_onwritable
) inner
.onwritable
+= do_writable
;
367 base.onwritable
+= value; }
368 remove { base.onwritable
-= value;
369 if (!can_onwritable
) inner
.onwritable
-= do_writable
; }
374 /// Adds an input buffer to a WvStream.
375 public class WvInBufStream
: WvStreamClone
377 WvBuf inbuf
= new WvBuf();
379 public WvInBufStream(WvStream inner
) : base(inner
)
383 public override int read(WvBytes b
)
387 int max
= inbuf
.used
> b
.len
? b
.len
: inbuf
.used
;
388 b
.put(0, inbuf
.get(max
));
396 public string _getline(char splitchar
)
398 if (inbuf
.strchr(splitchar
) > 0)
400 post_readable(); // not stalled yet!
401 return inbuf
.get(inbuf
.strchr(splitchar
)).FromUTF8();
406 public string getline(int msec_timeout
, char splitchar
)
408 string l
= _getline(splitchar
);
409 if (l
!= null) return l
;
411 foreach (var remain
in wv
.until(msec_timeout
))
413 if (inner
.wait(remain
, true, false))
414 inner
.read(inbuf
, 4096);
416 l
= _getline(splitchar
);
417 if (l
!= null) return l
;
419 if (inbuf
.used
== 0 && !ok
)
423 if (!ok
&& inbuf
.used
> 0)
424 return inbuf
.getall().FromUTF8();
430 /// Adds an output buffer to a WvStream
431 public class WvOutBufStream
: WvStreamClone
433 WvBuf outbuf
= new WvBuf();
434 bool writereg
= true;
436 public WvOutBufStream(WvStream inner
) : base(inner
)
440 public override int write(WvBytes b
)
451 int wrote
= base.write(outbuf
.peekall());
454 if (outbuf
.used
> 0 && !writereg
)
456 inner
.onwritable
+= _flush
;
459 else if (outbuf
.used
== 0 && writereg
)
461 inner
.onwritable
-= _flush
;
465 if (outbuf
.used
== 0)
469 public override bool flush(int msec_timeout
)
471 foreach (int remain
in wv
.until(msec_timeout
))
474 if (remain
!= 0 && outbuf
.used
> 0)
475 wait(remain
, false, true);
476 if (outbuf
.used
== 0)
484 * A combination of WvInBufStream and WvOutBufStream.
486 * We put the OutBufStream on the inside and the InBufStream on the
487 * outside, because InBufStream actually adds API functions while
488 * OutBufStream doesn't. So getline() will work as expected on this
491 public class WvBufStream
: WvInBufStream
493 public WvBufStream(WvStream inner
)
494 : base(new WvOutBufStream(inner
))