tests: Avoid shadowed member variables.
[profile/ivi/rygel.git] / tests / rygel-http-get-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 using Gee;
26
27 public errordomain Rygel.TestError {
28     SKIP = 77,
29     TIMEOUT
30 }
31
32 public errordomain Rygel.ClientHacksError {
33     NA
34 }
35
36 public class Rygel.ClientHacks {
37     public static ClientHacks create (Message? message) throws Error {
38         var headers = message.request_headers;
39         if (headers.get_one ("clienthacks.test.rygel") != null) {
40             return new ClientHacks ();
41         } else {
42             throw new ClientHacksError.NA ("");
43         }
44     }
45
46     public void apply (MediaItem? item) {
47     }
48 }
49
50 public class Rygel.TestRequestFactory {
51     public Soup.Message msg;
52     public Soup.KnownStatusCode expected_code;
53
54     public TestRequestFactory (Soup.Message msg,
55                                Soup.KnownStatusCode expected_code) {
56         this.msg = msg;
57         this.expected_code = expected_code;
58     }
59
60     internal HTTPGet create_get (HTTPServer http_server,
61                                  Soup.Server server,
62                                  Soup.Message msg) {
63         HTTPGet request = new HTTPGet (http_server, server, msg);
64         request.handler = null;
65
66         return request;
67     }
68 }
69
70 public class Rygel.HTTPGetTest : GLib.Object {
71     protected HTTPServer server;
72     protected HTTPClient client;
73
74     private bool server_done;
75     private bool client_done;
76
77     private MainLoop main_loop;
78
79     private Error error;
80
81     private ArrayList<TestRequestFactory> requests;
82     private TestRequestFactory current_request;
83
84     public static int main (string[] args) {
85         try {
86             var test = new HTTPGetTest ();
87
88             test.run ();
89         } catch (TestError.SKIP error) {
90             return error.code;
91         } catch (Error error) {
92             critical ("%s", error.message);
93
94             return -1;
95         }
96
97         return 0;
98     }
99
100     public HTTPGetTest () throws Error {
101         this.server = new HTTPServer ();
102         this.client = new HTTPClient (this.server.context);
103         this.main_loop = new MainLoop (null, false);
104         this.create_test_messages();
105     }
106
107     public virtual void run () throws Error {
108         Timeout.add_seconds (3, this.on_timeout);
109         this.server.message_received.connect (this.on_message_received);
110         this.client.completed.connect (this.on_client_completed);
111
112         this.start_next_test_request ();
113
114         this.main_loop.run ();
115
116         if (this.error != null) {
117             throw this.error;
118         }
119     }
120
121     private void create_test_messages () {
122         requests = new ArrayList<TestRequestFactory> ();
123
124         Soup.Message request = new Soup.Message ("POST", this.server.uri);
125         requests.add (new TestRequestFactory (request,
126                       Soup.KnownStatusCode.BAD_REQUEST));
127
128         request = new Soup.Message ("HEAD", this.server.uri);
129         requests.add (new TestRequestFactory (request, Soup.KnownStatusCode.OK));
130
131         request = new Soup.Message ("GET", this.server.uri);
132         requests.add (new TestRequestFactory (request, Soup.KnownStatusCode.OK));
133
134         string uri = this.server.create_uri ("VideoItem");
135         uri = uri + "/tr/MP3";
136         request = new Soup.Message ("HEAD", uri);
137         requests.add (new TestRequestFactory (request, Soup.KnownStatusCode.OK));
138
139         request = new Soup.Message ("GET", this.server.uri);
140         request.request_headers.append ("transferMode.dlna.org", "Streaming");
141         requests.add (new TestRequestFactory (request, Soup.KnownStatusCode.OK));
142
143         request = new Soup.Message ("GET", this.server.uri);
144         request.request_headers.append ("transferMode.dlna.org", "Interactive");
145         requests.add (new TestRequestFactory (request,
146                       Soup.KnownStatusCode.NOT_ACCEPTABLE));
147
148         request = new Soup.Message ("GET", this.server.uri);
149         request.request_headers.append ("Range", "bytes=1-2");
150         requests.add (new TestRequestFactory (request,
151                       Soup.KnownStatusCode.OK));
152
153         uri = this.server.create_uri ("AudioItem");
154         uri = uri + "/th/0";
155
156         request = new Soup.Message ("GET", uri);
157         requests.add (new TestRequestFactory (request,
158                       Soup.KnownStatusCode.NOT_FOUND));
159
160         request = new Soup.Message ("GET", this.server.uri);
161         request.request_headers.append ("TimeSeekRange.dlna.org", "0");
162         requests.add (new TestRequestFactory (request,
163                       Soup.KnownStatusCode.NOT_ACCEPTABLE));
164
165         uri = this.server.create_uri ("AudioItem");
166         request = new Soup.Message ("GET", uri);
167         request.request_headers.append ("TimeSeekRange.dlna.org", "0");
168         requests.add (new TestRequestFactory (request,
169                       Soup.KnownStatusCode.BAD_REQUEST));
170
171         uri = this.server.create_uri ("AudioItem");
172         request = new Soup.Message ("GET", uri);
173         request.request_headers.append ("TimeSeekRange.dlna.org", "npt=1-2049");
174         requests.add (new TestRequestFactory (request,
175                       Soup.KnownStatusCode.REQUESTED_RANGE_NOT_SATISFIABLE));
176
177         request = new Soup.Message ("GET", this.server.uri);
178         request.request_headers.append ("clienthacks.test.rygel", "f");
179         requests.add (new TestRequestFactory (request,
180                       Soup.KnownStatusCode.OK));
181
182         request = new Soup.Message ("GET", this.server.uri);
183         request.request_headers.append ("clienthacks.test.rygel", "t");
184         requests.add (new TestRequestFactory (request,
185                       Soup.KnownStatusCode.OK));
186     }
187
188     private HTTPGet create_request (Soup.Message msg) throws Error {
189         HTTPGet request = this.current_request.create_get (this.server,
190                             this.server.context.server, msg);
191         return request;
192     }
193
194     private void on_client_completed (StateMachine client) {
195         if (requests.size > 0) {
196             this.start_next_test_request ();
197         } else {
198             this.main_loop.quit ();
199             this.client_done = true;
200         }
201     }
202
203     private void start_next_test_request() {
204         this.current_request = requests.remove_at (0);
205         this.client.msg = this.current_request.msg;
206         this.client.run.begin ();
207     }
208
209     private void on_message_received (HTTPServer   server,
210                                       Soup.Message msg) {
211         this.handle_client_message.begin (msg);
212     }
213
214     private async void handle_client_message (Soup.Message msg) {
215         try {
216             var request = this.create_request (msg);
217
218             yield request.run ();
219
220             assert ((request as HTTPGet).object != null);
221
222             debug ("status.code: %d", (int) msg.status_code);
223             assert (msg.status_code == this.current_request.expected_code);
224
225             if (this.client_done) {
226                 this.main_loop.quit ();
227             }
228
229             this.server_done = true;
230         } catch (Error error) {
231             this.error = error;
232             this.main_loop.quit ();
233
234             return;
235         }
236     }
237
238     private bool on_timeout () {
239         this.error = new TestError.TIMEOUT ("Timeout");
240         this.main_loop.quit ();
241
242         return false;
243     }
244 }
245
246 public class Rygel.HTTPServer : GLib.Object {
247     private const string SERVER_PATH = "/RygelHTTPServer/Rygel/Test";
248     public string path_root {
249         get {
250             return SERVER_PATH;
251         }
252     }
253
254     public MediaContainer root_container;
255     public GUPnP.Context context;
256
257     public string uri {
258         owned get {
259             return create_uri("VideoItem");
260         }
261     }
262
263     public string create_uri (string item_id) {
264         var item = new VideoItem ();
265         item.id = item_id;
266
267         var item_uri = new HTTPItemURI (item, this);
268
269         return item_uri.to_string ();
270     }
271
272     public signal void message_received (Soup.Message message);
273
274     public HTTPServer () throws TestError {
275         try {
276             this.context = new GUPnP.Context (null, "lo", 0);
277         } catch (Error error) {
278             throw new TestError.SKIP ("Network context not available");
279         }
280
281         assert (this.context != null);
282         assert (this.context.host_ip != null);
283         assert (this.context.port > 0);
284
285         this.context.server.add_handler (SERVER_PATH, this.server_cb);
286
287         this.root_container = new MediaContainer ();
288     }
289
290     private void server_cb (Server        server,
291                             Soup.Message  msg,
292                             string        path,
293                             HashTable?    query,
294                             ClientContext client) {
295         this.context.server.pause_message (msg);
296         this.message_received (msg);
297     }
298
299     public Transcoder get_transcoder (string target) throws Error {
300         if (target == "MP3") {
301             return new Transcoder ("mp3");
302         }
303         throw new HTTPRequestError.NOT_FOUND (
304                             "No transcoder available for target format '%s'",
305                             target);
306     }
307 }
308
309 public class Rygel.HTTPClient : GLib.Object, StateMachine {
310     public GUPnP.Context context;
311     public Soup.Message msg;
312
313     public Cancellable cancellable { get; set; }
314
315     public HTTPClient (GUPnP.Context context) {
316         this.context = context;
317     }
318
319     public async void run () {
320         SourceFunc run_continue = run.callback;
321
322         this.context.session.queue_message (this.msg, (session, msg) => {
323             run_continue ();
324         });
325
326         yield;
327
328         this.completed ();
329     }
330 }
331
332 public class Rygel.MediaContainer : Rygel.MediaObject {
333
334     public async MediaObject? find_object (string       item_id,
335                                            Cancellable? cancellable)
336                                            throws Error {
337         SourceFunc find_object_continue = find_object.callback;
338         Idle.add (() => {
339             find_object_continue ();
340
341             return false;
342         });
343
344         yield;
345
346         debug ("item id: %s", item_id);
347         if (item_id == "VideoItem") {
348             return new VideoItem ();
349         } else if (item_id == "AudioItem") {
350             return new AudioItem ();
351         } else {
352             return null;
353         }
354     }
355 }
356
357 internal abstract class Rygel.HTTPGetHandler {
358     public HTTPResponse render_body (HTTPGet get_request) {
359         return new HTTPResponse (get_request);
360     }
361
362     public void add_response_headers (HTTPGet get_request) {}
363
364     public bool knows_size (HTTPGet request) { return false; }
365 }
366
367 internal class Rygel.HTTPTranscodeHandler : Rygel.HTTPGetHandler {
368     public HTTPTranscodeHandler (Transcoder  transcoder,
369                                  Cancellable cancellable) {}
370 }
371
372 internal class Rygel.HTTPIdentityHandler : Rygel.HTTPGetHandler {
373     public HTTPIdentityHandler (Cancellable cancellable) {}
374 }
375
376 internal class Rygel.HTTPPlaylistHandler : Rygel.HTTPGetHandler {
377     public HTTPPlaylistHandler (Cancellable cancellable) {}
378 }
379
380 public abstract class Rygel.MediaItem : Rygel.MediaObject {
381     public long size = 1024;
382     public ArrayList<string> uris = new ArrayList<string> ();
383
384     public bool place_holder = false;
385
386     public bool is_live_stream () {
387         if (this.id == "VideoItem")
388             return false;
389         else
390             return true;
391     }
392
393     public bool streamable () {
394         return true;
395     }
396 }
397
398 private class Rygel.AudioItem : MediaItem {
399     public int64 duration = 2048;
400
401     public AudioItem () {
402         this.id = "AudioItem";
403     }
404 }
405
406 private interface Rygel.VisualItem : MediaItem {
407     public abstract int width { get; set; }
408     public abstract int height { get; set; }
409     public abstract int color_depth { get; set; }
410
411     public abstract ArrayList<Thumbnail> thumbnails { get; protected set; }
412
413     public bool is_live_stream () {
414         return false;
415     }
416
417     public bool streamable () {
418         return false;
419     }
420 }
421
422 private class Rygel.VideoItem : AudioItem, VisualItem {
423     public int width { get; set; default = -1; }
424     public int height { get; set; default = -1; }
425     public int color_depth { get; set; default = -1; }
426
427     private ArrayList<Thumbnail> ts;
428
429     public VideoItem () {
430         this.id = "VideoItem";
431     }
432
433     public ArrayList<Thumbnail> thumbnails {
434         get {
435             this.ts = new ArrayList<Thumbnail>();
436             ts.add(new Rygel.Thumbnail());
437             return this.ts;
438         }
439
440         protected set {}
441     }
442
443     public ArrayList<Subtitle> subtitles = new ArrayList<Subtitle> ();
444 }
445
446 private class Rygel.MusicItem : AudioItem {
447     public Thumbnail album_art;
448 }
449
450 public class Rygel.Thumbnail {
451     public long size = 1024;
452     public string file_extension;
453 }
454
455 public class Rygel.Subtitle {
456     public long size = 1024;
457     public string caption_type;
458 }
459
460 internal class Rygel.HTTPResponse : Rygel.StateMachine, GLib.Object {
461     public Cancellable cancellable { get; set; }
462
463     private Soup.Message msg;
464     private Soup.Server server;
465
466     public HTTPResponse (HTTPGet get_request) {
467         this.msg = get_request.msg;
468         this.msg.response_headers.set_encoding (Soup.Encoding.CONTENT_LENGTH);
469         this.server = get_request.server;
470     }
471
472     public async void run () {
473         SourceFunc run_continue = run.callback;
474
475         Idle.add (() => {
476             run_continue ();
477
478             return false;
479         });
480
481         yield;
482
483         this.msg.set_status (Soup.KnownStatusCode.OK);
484         this.server.unpause_message (msg);
485
486         this.completed ();
487     }
488 }
489
490 public class Rygel.MediaObject {
491     public string id;
492     public string mime_type = "";
493 }
494
495 public class Rygel.Transcoder : GLib.Object {
496     public string extension { get; protected set; }
497
498     public Transcoder (string extension) {
499         this.extension = extension;
500     }
501 }