Updated French translation
[profile/ivi/rygel.git] / tests / rygel-http-response-test.vala
1 /*
2  * Copyright (C) 2010 Nokia Corporation.
3  *
4  * Author: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
5  *                               <zeeshan.ali@nokia.com>
6  *
7  * This file is part of Rygel.
8  *
9  * Rygel is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * Rygel is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, write to the Free Software Foundation,
21  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22  */
23
24 using Soup;
25
26 public errordomain Rygel.TestError {
27     SKIP = 77,
28     TIMEOUT
29 }
30
31 public errordomain Rygel.HTTPRequestError {
32     NOT_FOUND = Soup.Status.NOT_FOUND
33 }
34
35 public class Rygel.HTTPResponseTest : GLib.Object {
36     public const long MAX_BYTES = 102400;
37
38     protected HTTPServer server;
39     protected HTTPClient client;
40
41     private bool server_done;
42     private bool client_done;
43
44     private MediaItem item;
45
46     private MainLoop main_loop;
47
48     protected Cancellable cancellable;
49     private Error error;
50
51     public static int main (string[] args) {
52         try {
53             var test = new HTTPResponseTest.complete ();
54             test.run ();
55
56             test = new HTTPResponseTest.abort ();
57             test.run ();
58         } catch (TestError.SKIP error) {
59             return error.code;
60         } catch (Error error) {
61             critical ("%s", error.message);
62
63             return -1;
64         }
65
66         return 0;
67     }
68
69     public HTTPResponseTest (Cancellable? cancellable = null) throws Error {
70         this.cancellable = cancellable;
71
72         this.server = new HTTPServer ();
73         this.client = new HTTPClient (this.server.context,
74                                       this.server.uri,
75                                       MAX_BYTES,
76                                       cancellable != null);
77         this.main_loop = new MainLoop (null, false);
78     }
79
80     public HTTPResponseTest.complete () throws Error {
81         this ();
82
83         this.item = new MediaItem.fixed_size ();
84     }
85
86     public HTTPResponseTest.abort () throws Error {
87         this (new Cancellable ());
88
89         this.item = new MediaItem ();
90     }
91
92     public virtual void run () throws Error {
93         Timeout.add_seconds (3, this.on_timeout);
94         this.server.message_received.connect (this.on_message_received);
95         this.server.message_aborted.connect (this.on_message_aborted);
96         if (this.cancellable == null) {
97             this.client.completed.connect (this.on_client_completed);
98         } else {
99             this.client_done = true;
100         }
101
102         this.client.run.begin ();
103
104         this.main_loop.run ();
105
106         if (this.error != null) {
107             throw this.error;
108         }
109     }
110
111     private HTTPResponse create_response (Soup.Message msg) throws Error {
112         var seek = null as HTTPSeek;
113
114         if (!this.item.is_live_stream ()) {
115             seek = new HTTPByteSeek (0, MAX_BYTES - 1, this.item.size);
116             msg.response_headers.set_content_length (seek.length);
117         }
118
119         var request = new HTTPGet (this.server.context.server,
120                                    msg,
121                                    this.item,
122                                    seek,
123                                    this.cancellable);
124         var handler = new HTTPGetHandler (this.cancellable);
125         var src = this.item.create_stream_source ();
126
127         return new HTTPResponse (request, handler, src);
128     }
129
130     private void on_client_completed (StateMachine client) {
131         if (this.server_done) {
132             this.main_loop.quit ();
133         }
134
135         this.client_done = true;
136     }
137
138     private void on_response_completed (StateMachine response) {
139         if (this.client_done) {
140             this.main_loop.quit ();
141         }
142
143         this.server_done = true;
144     }
145
146     private void on_message_received (HTTPServer   server,
147                                       Soup.Message msg) {
148         try {
149             var response = this.create_response (msg);
150
151             response.run.begin ();
152
153             response.completed.connect (this.on_response_completed);
154         } catch (Error error) {
155             this.error = error;
156             this.main_loop.quit ();
157
158             return;
159         }
160     }
161
162     private void on_message_aborted (HTTPServer   server,
163                                      Soup.Message msg) {
164         this.cancellable.cancel ();
165     }
166
167     private bool on_timeout () {
168         this.error = new TestError.TIMEOUT ("Timeout");
169         this.main_loop.quit ();
170
171         return false;
172     }
173 }
174
175 public class Rygel.HTTPServer : GLib.Object {
176     private const string SERVER_PATH = "/RygelHTTPServer/Rygel/Test";
177
178     public GUPnP.Context context;
179
180     public string uri {
181         owned get { return "http://" +
182                            this.context.host_ip + ":" +
183                            this.context.port.to_string () +
184                            SERVER_PATH;
185         }
186     }
187
188     public signal void message_received (Soup.Message message);
189     public signal void message_aborted (Soup.Message message);
190
191     public HTTPServer () throws TestError {
192         try {
193             this.context = new GUPnP.Context (null, "lo", 0);
194         } catch (Error error) {
195             throw new TestError.SKIP ("Network context not available");
196         }
197
198         assert (this.context != null);
199         assert (this.context.host_ip != null);
200         assert (this.context.port > 0);
201
202         this.context.server.add_handler (SERVER_PATH, this.server_cb);
203         this.context.server.request_aborted.connect (this.on_request_aborted);
204     }
205
206     private void server_cb (Server        server,
207                             Soup.Message  msg,
208                             string        path,
209                             HashTable?    query,
210                             ClientContext client) {
211         this.context.server.pause_message (msg);
212         this.message_received (msg);
213     }
214
215     private void on_request_aborted (Soup.Server        server,
216                                      Soup.Message       message,
217                                      Soup.ClientContext client) {
218         this.message_aborted (message);
219     }
220 }
221
222 public class Rygel.HTTPClient : GLib.Object, StateMachine {
223     public GUPnP.Context context;
224     public Soup.Message msg;
225     public size_t total_bytes;
226
227     public Cancellable cancellable { get; set; }
228
229     public HTTPClient (GUPnP.Context context,
230                        string        uri,
231                        size_t        total_bytes,
232                        bool          active) {
233         this.context = context;
234         this.total_bytes = total_bytes;
235
236         this.msg = new Soup.Message ("HTTP",  uri);
237         assert (this.msg != null);
238         this.msg.response_body.set_accumulate (false);
239
240         if (active) {
241             this.cancellable = new Cancellable ();
242             this.cancellable.cancelled.connect (this.on_cancelled);
243         }
244     }
245
246     public async void run () {
247         SourceFunc run_continue = run.callback;
248         size_t bytes_received = 0;
249
250         this.msg.got_chunk.connect ((msg, chunk) => {
251             bytes_received += chunk.length;
252
253             if (bytes_received >= this.total_bytes &&
254                 this.cancellable != null) {
255
256                 this.cancellable.cancel ();
257             }
258         });
259
260         this.context.session.queue_message (this.msg, (session, msg) => {
261             assert (cancellable != null || bytes_received == this.total_bytes);
262
263             run_continue ();
264         });
265
266         yield;
267
268         this.completed ();
269     }
270
271     private void on_cancelled (Cancellable cancellable) {
272         this.context.session.cancel_message (this.msg,
273                                              Status.CANCELLED);
274         this.completed ();
275     }
276 }
277
278 public class Rygel.HTTPSeek : GLib.Object {
279     public int64 start { get; private set; }
280     public int64 stop { get; private set; }
281     public int64 length { get; private set; }
282     public int64 total_length { get; private set; }
283
284     public HTTPSeek (int64 start, int64 stop, int64 total_length) {
285         this.start = start;
286         this.stop = stop;
287         this.total_length = total_length;
288
289         this.length = stop - start + 1;
290     }
291 }
292
293 public class Rygel.HTTPByteSeek : Rygel.HTTPSeek {
294     public HTTPByteSeek (int64 start, int64 stop, int64 total_length) {
295         base (start, stop, total_length);
296     }
297 }
298
299 public class Rygel.HTTPTimeSeek : Rygel.HTTPSeek {
300     public HTTPTimeSeek (int64 start, int64 stop, int64 total_length) {
301         base (start, stop, total_length);
302     }
303 }
304
305 public class Rygel.HTTPGet : GLib.Object {
306     public Soup.Server server;
307     public Soup.Message msg;
308
309     public Cancellable cancellable;
310
311     public MediaItem item;
312
313     internal HTTPSeek seek;
314
315     public HTTPGet (Soup.Server  server,
316                     Soup.Message msg,
317                     MediaItem    item,
318                     HTTPSeek?    seek,
319                     Cancellable? cancellable) {
320         this.server = server;
321         this.msg = msg;
322         this.item = item;
323         this.seek = seek;
324         this.cancellable = cancellable;
325         this.msg.response_headers.set_encoding (Soup.Encoding.EOF);
326         this.msg.set_status (Soup.Status.OK);
327     }
328 }
329
330 public class Rygel.HTTPGetHandler : GLib.Object {
331     public Cancellable cancellable;
332
333     public HTTPGetHandler (Cancellable? cancellable) {
334         this.cancellable = cancellable;
335     }
336 }
337
338 internal class Rygel.TestDataSource : Rygel.DataSource, Object {
339     private long block_size;
340     private long buffers;
341     private uint64 data_sent;
342     private bool frozen;
343
344     public TestDataSource (long block_size, long buffers) {
345         this.block_size = block_size;
346         this.buffers = buffers;
347         this.data_sent = 0;
348     }
349
350     public void start (HTTPSeek? seek) throws Error {
351         Idle.add ( () => {
352             if (frozen) {
353                 return false;
354             }
355
356             var data = new uint8[block_size];
357             this.data_sent += block_size;
358             if (this.data_sent > HTTPResponseTest.MAX_BYTES) {
359                 this.done ();
360
361                 return false;
362             }
363
364             this.data_available (data);
365
366             return true;
367         });
368     }
369
370     public void freeze () {
371         this.frozen = true;
372     }
373
374     public void thaw () {
375         if (!this.frozen) {
376             return;
377         }
378
379         this.frozen = false;
380
381         try {
382             this.start (null);
383         } catch (GLib.Error error) {
384             assert_not_reached ();
385         }
386     }
387
388     public void stop () {
389         this.freeze ();
390     }
391 }
392
393 public class Rygel.MediaItem {
394     private static const long BLOCK_SIZE = HTTPResponseTest.MAX_BYTES / 16;
395     private static const long MAX_BUFFERS = 25;
396
397     public int64 size {
398         get {
399             return MAX_BUFFERS * BLOCK_SIZE;
400         }
401     }
402
403     private DataSource src;
404     bool is_live = false;
405
406     public MediaItem () {
407         this.src = new TestDataSource (BLOCK_SIZE, MAX_BUFFERS);
408         this.is_live = true;
409     }
410
411     public MediaItem.fixed_size () {
412         this ();
413     }
414
415     public DataSource? create_stream_source () {
416         return this.src;
417     }
418
419     public bool is_live_stream () {
420         return this.is_live;
421     }
422 }