7 public interface IWvStream
: IDisposable
10 Exception err { get; }
11 EndPoint localaddr { get; }
12 EndPoint remoteaddr { get; }
16 bool flush(int msec_timeout
);
18 event Action onreadable
;
19 event Action onwritable
;
27 public class WvStream
: IWvStream
29 public static IWvEventer ev
= new WvEventer();
31 public static void runonce()
36 public static void runonce(int msec_timeout
)
38 ev
.runonce(msec_timeout
);
50 bool is_readable
= false, is_writable
= false;
51 event Action _onreadable
, _onwritable
, _onclose
;
53 protected bool can_onreadable { get { return _onreadable != null; }
}
54 protected bool can_onwritable { get { return _onwritable != null; }
}
56 protected void do_readable()
65 protected void do_writable()
74 protected void do_close()
80 object pr_obj
= new object();
81 protected void post_readable()
84 ev
.addpending(pr_obj
, do_readable
);
87 object pw_obj
= new object();
88 protected void post_writable()
91 ev
.addpending(pw_obj
, do_writable
);
94 public virtual event Action onreadable
{
95 add { _onreadable += value; if (is_readable) post_readable(); }
96 remove { _onreadable -= value; }
98 public virtual event Action onwritable
{
99 add { _onwritable += value; if (is_writable) post_writable(); }
100 remove { _onwritable -= value; }
102 public virtual event Action onclose
{
103 add { _onclose += value; }
104 remove { _onclose -= value; }
108 public virtual bool ok { get { return isopen && err == null; }
}
111 public virtual Exception err
{
116 if (_err
== null) // remember the *first* error
124 public virtual void close()
126 ev
.delpending(pr_obj
);
127 ev
.delpending(pw_obj
);
134 if (_onclose
!= null) _onclose();
136 GC
.SuppressFinalize(this);
139 public virtual EndPoint localaddr
{
143 public virtual EndPoint remoteaddr
{
147 public virtual int read(WvBytes b
)
152 // for convenience. Note: always returns non-null, but the returned
153 // array size might be zero.
154 public WvBytes
read(int len
)
156 WvBytes bytes
= new byte[len
];
157 int got
= read(bytes
);
158 return bytes
.sub(0, got
);
161 public void read(WvBuf b
, int max
)
163 int got
= read(b
.alloc(max
));
167 public virtual int write(WvBytes b
)
169 return b
.len
; // lie: we "wrote" all the bytes to nowhere
173 * Wait up to msec_timeout milliseconds for the stream to become
174 * readable or writable, respectively.
176 * Our default implementation always returns immediately, consistent
177 * with the Unix select() behaviour of returning immediately on
178 * non-select()able file handles.
180 * Returns true if the stream is readable or writable before the
181 * timeout, false otherwise.
183 * Waiting synchronously is usually a bad idea in WvStreams
184 * programs. Use onreadable/onwritable/onclose instead whenever
187 public virtual bool wait(int msec_timeout
,
188 bool readable
, bool writable
)
190 if (readable
&& is_readable
)
192 if (writable
&& is_writable
)
194 if (!ok
|| (readable
&& !canread
) || (writable
&& !canwrite
))
199 // Don't make these anything but private! They're tempting, but
200 // they're not *really* what you want to know. Use these instead:
201 // ok -> whether or not your stream is useful at all
202 // wait(0, true, false) -> whether your stream has bytes *right now*.
203 // If canread goes false, wait(-1, true, false) will return false.
204 bool canread
= true, canwrite
= true;
206 public virtual void noread()
212 public virtual void nowrite()
218 public void maybe_autoclose()
220 if (!canread
&& !canwrite
)
224 public virtual bool flush(int msec_timeout
)
226 return true; // no buffer
229 // WARNING: assumes the write() will succeed! Use only on WvStreams
230 // with a write buffer.
231 public void print(string fmt
, params object[] args
)
233 print((object)wv
.fmt(fmt
, args
));
236 // WARNING: assumes the write() will succeed! Use only on WvStreams
237 // with a write buffer.
238 public virtual void print(object o
)
240 byte[] b
= o
.ToUTF8();
242 wv
.assert(n
== b
.Length
,
243 "Don't use print() on an unbuffered WvStream!");
247 // Wraps a WvStream in another WvStream, allowing us to override some
248 // behaviour. By default, a WvStreamClone just passes everything through
249 // to the inner stream.
250 public class WvStreamClone
: WvStream
252 protected WvStream inner
= null;
253 bool hasinner { get { return inner != null; }
}
255 public WvStreamClone(WvStream inner
)
260 public void setinner(WvStream inner
)
262 if (inner
!= this.inner
)
266 this.inner
.onreadable
-= do_readable
;
267 this.inner
.onwritable
-= do_writable
;
268 this.inner
.onclose
-= do_close
;
273 if (can_onreadable
) this.inner
.onreadable
+= do_readable
;
274 if (can_onwritable
) this.inner
.onwritable
+= do_writable
;
275 this.inner
.onclose
+= do_close
;
280 public override bool ok
{
281 get { return base.ok && hasinner && inner.ok; }
284 public override Exception err
{
286 return hasinner
? inner
.err
: base.err
;
296 public override EndPoint localaddr
{
297 get { return hasinner ? inner.localaddr : base.localaddr; }
299 public override EndPoint remoteaddr
{
300 get { return hasinner ? inner.localaddr : base.localaddr; }
303 public override int read(WvBytes b
)
306 return inner
.read(b
);
308 return 0; // 0 bytes read
311 public override int write(WvBytes b
)
314 return inner
.write(b
);
316 return 0; // 0 bytes written
319 public override bool wait(int msec_timeout
,
320 bool readable
, bool writable
)
323 return inner
.wait(msec_timeout
, readable
, writable
);
325 return base.wait(msec_timeout
, readable
, writable
);
328 public override void noread()
335 public override void nowrite()
342 public override bool flush(int msec_timeout
)
345 return inner
.flush(msec_timeout
);
350 // We only want to register our callback with the inner stream if
351 // we *have* a callback, and then only once. Otherwise the stream
352 // might start listening for read when we don't have any readable
353 // handlers, resulting in it spinning forever.
354 public override event Action onreadable
{
355 add { if (!can_onreadable
) inner
.onreadable
+= do_readable
;
356 base.onreadable
+= value; }
357 remove { base.onreadable
-= value;
358 if (!can_onreadable
) inner
.onreadable
-= do_readable
; }
360 public override event Action onwritable
{
361 add { if (!can_onwritable
) inner
.onwritable
+= do_writable
;
362 base.onwritable
+= value; }
363 remove { base.onwritable
-= value;
364 if (!can_onwritable
) inner
.onwritable
-= do_writable
; }
369 /// Adds an input buffer to a WvStream.
370 public class WvInBufStream
: WvStreamClone
372 WvBuf inbuf
= new WvBuf();
374 public WvInBufStream(WvStream inner
) : base(inner
)
378 public override int read(WvBytes b
)
382 int max
= inbuf
.used
> b
.len
? b
.len
: inbuf
.used
;
383 b
.put(0, inbuf
.get(max
));
391 public string _getline(char splitchar
)
393 if (inbuf
.strchr(splitchar
) > 0)
395 post_readable(); // not stalled yet!
396 return inbuf
.get(inbuf
.strchr(splitchar
)).FromUTF8();
401 public string getline(int msec_timeout
, char splitchar
)
403 string l
= _getline(splitchar
);
404 if (l
!= null) return l
;
406 foreach (var remain
in wv
.until(msec_timeout
))
408 if (inner
.wait(remain
, true, false))
409 inner
.read(inbuf
, 4096);
411 l
= _getline(splitchar
);
412 if (l
!= null) return l
;
414 if (inbuf
.used
== 0 && !ok
)
418 if (!ok
&& inbuf
.used
> 0)
419 return inbuf
.getall().FromUTF8();
425 /// Adds an output buffer to a WvStream
426 public class WvOutBufStream
: WvStreamClone
428 WvBuf outbuf
= new WvBuf();
429 bool writereg
= true;
431 public WvOutBufStream(WvStream inner
) : base(inner
)
435 public override int write(WvBytes b
)
446 int wrote
= base.write(outbuf
.peekall());
449 if (outbuf
.used
> 0 && !writereg
)
451 inner
.onwritable
+= _flush
;
454 else if (outbuf
.used
== 0 && writereg
)
456 inner
.onwritable
-= _flush
;
460 if (outbuf
.used
== 0)
464 public override bool flush(int msec_timeout
)
466 foreach (int remain
in wv
.until(msec_timeout
))
469 if (remain
!= 0 && outbuf
.used
> 0)
470 wait(remain
, false, true);
471 if (outbuf
.used
== 0)
479 * A combination of WvInBufStream and WvOutBufStream.
481 * We put the OutBufStream on the inside and the InBufStream on the
482 * outside, because InBufStream actually adds API functions while
483 * OutBufStream doesn't. So getline() will work as expected on this
486 public class WvBufStream
: WvInBufStream
488 public WvBufStream(WvStream inner
)
489 : base(new WvOutBufStream(inner
))