Merge pull request #5714 from alexischr/update_bockbuild
[mono.git] / mcs / class / System.Net.Http / CFContentStream.cs
1 //
2 // CFContentStream.cs
3 //
4 // Authors:
5 //      Marek Safar  <marek.safar@gmail.com>
6 //
7 // Copyright (C) 2013 Xamarin Inc (http://www.xamarin.com)
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28
29 using System.Threading;
30 using System.Threading.Tasks;
31 using System.IO;
32 using System.Net;
33 using System.Runtime.ExceptionServices;
34
35 #if XAMCORE_4_0
36 using CFNetwork;
37 using CoreFoundation;
38 #elif XAMCORE_2_0
39 using CoreServices;
40 using CoreFoundation;
41 #else
42 using MonoTouch.CoreServices;
43 using MonoTouch.CoreFoundation;
44 #endif
45
46 namespace System.Net.Http
47 {
48         class BufferData 
49         {
50                 public byte[] Buffer;
51                 public int Length;
52         }
53
54         class CFContentStream : HttpContent
55         {
56                 readonly CFHTTPStream http_stream;
57                 BufferData data;
58                 Mutex data_mutex;
59                 AutoResetEvent data_event;
60                 AutoResetEvent data_read_event;
61                 ExceptionDispatchInfo http_exception;
62
63                 // The requirements are:
64                 // * We must read at least one byte from the stream every time
65                 //   we get a HasBytesAvailable event.
66                 // * SerializeToStreamAsync is executed on a separate thread,
67                 //   so reads must somehow be synchronized with that thread.
68                 //
69                 // Current implementation:
70                 // * We read data in ReadStreamData (on the same thread
71                 //   we got the HasBytesAvailable event, i.e. inside the 
72                 //   HasBytesAvailable event handler).
73                 // * Data is stored in a class-level buffer.
74                 // * SerializeToStreamAsync blocks while waiting for
75                 //   data from ReadStreamData.
76                 // * ReadStreamData will only read more data once SerializeToStreamAsync
77                 //   has consumed any existing data. This means we'll be
78                 //   blocking in the HasBytesAvailable event handler until
79                 //   any previously read data has been processed (this prevents
80                 //   any unbound memory growth).
81
82                 public CFContentStream (CFHTTPStream stream)
83                 {
84                         this.http_stream = stream;
85                         this.http_stream.ErrorEvent += HandleErrorEvent;
86                         data = new BufferData () {
87                                 Buffer = new byte [4096],
88                         };
89                         data_event = new AutoResetEvent (false);
90                         data_read_event = new AutoResetEvent (true);
91                         data_mutex = new Mutex ();
92                 }
93
94                 void HandleErrorEvent (object sender, CFStream.StreamEventArgs e)
95                 {
96                         var gotMutex = data_mutex.WaitOne ();
97                         if (gotMutex) {
98                                 var stream = (CFHTTPStream)sender;
99                                 if (e.EventType == CFStreamEventType.ErrorOccurred)
100                                         Volatile.Write (ref http_exception, ExceptionDispatchInfo.Capture (stream.GetError ()));
101                                 data_mutex.ReleaseMutex ();
102                         }
103                 }
104
105                 public void ReadStreamData ()
106                 {
107                         data_read_event.WaitOne (); // make sure there's no pending data.
108
109                         data_mutex.WaitOne ();
110                         data.Length = (int) http_stream.Read (data.Buffer, 0, data.Buffer.Length);
111                         data_mutex.ReleaseMutex ();
112
113                         data_event.Set ();
114                 }
115
116                 public void Close ()
117                 {
118                         data_read_event.WaitOne (); // make sure there's no pending data
119
120                         data_mutex.WaitOne ();
121                         data = null;
122                         this.http_stream.ErrorEvent -= HandleErrorEvent;
123                         data_mutex.ReleaseMutex ();
124
125                         data_event.Set ();
126                 }
127
128                 protected internal override async Task SerializeToStreamAsync (Stream stream, TransportContext context)
129                 {
130                         while (data_event.WaitOne ()) {
131                                 data_mutex.WaitOne ();
132                                 if (http_exception != null) {
133                                         http_exception.Throw ();
134                                         data_mutex.ReleaseMutex ();
135                                         break;
136                                 }
137                                 if (data == null || data.Length <= 0) {
138                                         data_mutex.ReleaseMutex ();
139                                         data_read_event.Set ();
140                                         break;
141                                 }
142
143                                 await stream.WriteAsync (data.Buffer, 0, data.Length).ConfigureAwait (false);
144                                 data_mutex.ReleaseMutex ();
145
146                                 data_read_event.Set ();
147                         }
148                 }
149
150                 protected internal override bool TryComputeLength (out long length)
151                 {
152                         length = 0;
153                         return false;
154                 }
155         }
156 }