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