server: VisualItem: Remove pixel-height and pixel-width properties.
[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             this.client.run.begin ();
198         } else {
199             this.main_loop.quit ();
200             this.client_done = true;
201         }
202     }
203
204     private void start_next_test_request() {
205         this.current_request = requests.remove_at (0);
206         this.client.msg = this.current_request.msg;
207         this.client.run.begin ();
208     }
209
210     private void on_message_received (HTTPServer   server,
211                                       Soup.Message msg) {
212         this.handle_client_message.begin (msg);
213     }
214
215     private async void handle_client_message (Soup.Message msg) {
216         try {
217             var request = this.create_request (msg);
218
219             yield request.run ();
220
221             assert ((request as HTTPGet).item != null);
222
223             debug ("status.code: %d", (int) msg.status_code);
224             assert (msg.status_code == this.current_request.expected_code);
225
226             if (this.client_done) {
227                 this.main_loop.quit ();
228             }
229
230             this.server_done = true;
231         } catch (Error error) {
232             this.error = error;
233             this.main_loop.quit ();
234
235             return;
236         }
237     }
238
239     private bool on_timeout () {
240         this.error = new TestError.TIMEOUT ("Timeout");
241         this.main_loop.quit ();
242
243         return false;
244     }
245 }
246
247 public class Rygel.HTTPServer : GLib.Object {
248     private const string SERVER_PATH = "/RygelHTTPServer/Rygel/Test";
249     public string path_root {
250         get {
251             return SERVER_PATH;
252         }
253     }
254
255     public MediaContainer root_container;
256     public GUPnP.Context context;
257
258     public string uri {
259         owned get {
260             return create_uri("VideoItem");
261         }
262     }
263
264     public string create_uri (string item_id) {
265         var item = new VideoItem ();
266         item.id = item_id;
267
268         var item_uri = new HTTPItemURI (item, this);
269
270         return item_uri.to_string ();
271     }
272
273     public signal void message_received (Soup.Message message);
274
275     public HTTPServer () throws TestError {
276         try {
277             this.context = new GUPnP.Context (null, "lo", 0);
278         } catch (Error error) {
279             throw new TestError.SKIP ("Network context not available");
280         }
281
282         assert (this.context != null);
283         assert (this.context.host_ip != null);
284         assert (this.context.port > 0);
285
286         this.context.server.add_handler (SERVER_PATH, this.server_cb);
287
288         this.root_container = new MediaContainer ();
289     }
290
291     private void server_cb (Server        server,
292                             Soup.Message  msg,
293                             string        path,
294                             HashTable?    query,
295                             ClientContext client) {
296         this.context.server.pause_message (msg);
297         this.message_received (msg);
298     }
299
300     public Transcoder get_transcoder (string target) throws Error {
301         if (target == "MP3") {
302             return new Transcoder ("mp3");
303         }
304         throw new HTTPRequestError.NOT_FOUND (
305                             "No transcoder available for target format '%s'",
306                             target);
307     }
308 }
309
310 public class Rygel.HTTPClient : GLib.Object, StateMachine {
311     public GUPnP.Context context;
312     public Soup.Message msg;
313
314     public Cancellable cancellable { get; set; }
315
316     public HTTPClient (GUPnP.Context context) {
317         this.context = context;
318     }
319
320     public async void run () {
321         SourceFunc run_continue = run.callback;
322
323         this.context.session.queue_message (this.msg, (session, msg) => {
324             run_continue ();
325         });
326
327         yield;
328
329         this.completed ();
330     }
331 }
332
333 public class Rygel.MediaContainer : Rygel.MediaObject {
334
335     public async MediaObject? find_object (string       item_id,
336                                            Cancellable? cancellable)
337                                            throws Error {
338         SourceFunc find_object_continue = find_object.callback;
339         Idle.add (() => {
340             find_object_continue ();
341
342             return false;
343         });
344
345         yield;
346
347         debug ("item id: %s", item_id);
348         if (item_id == "VideoItem") {
349             return new VideoItem ();
350         } else if (item_id == "AudioItem") {
351             return new AudioItem ();
352         } else {
353             return null;
354         }
355     }
356 }
357
358 internal abstract class Rygel.HTTPGetHandler {
359     public HTTPResponse render_body (HTTPGet get_request) {
360         return new HTTPResponse (get_request);
361     }
362
363     public void add_response_headers (HTTPGet get_request) {}
364 }
365
366 internal class Rygel.HTTPTranscodeHandler : Rygel.HTTPGetHandler {
367     public HTTPTranscodeHandler (Transcoder  transcoder,
368                                  Cancellable cancellable) {}
369 }
370
371 internal class Rygel.HTTPIdentityHandler : Rygel.HTTPGetHandler {
372     public HTTPIdentityHandler (Cancellable cancellable) {}
373 }
374
375 public abstract class Rygel.MediaItem : Rygel.MediaObject {
376     public long size = 1024;
377     public ArrayList<Subtitle> subtitles = new ArrayList<Subtitle> ();
378     public ArrayList<Thumbnail> thumbnails = new ArrayList<Thumbnail> ();
379     public ArrayList<string> uris = new ArrayList<string> ();
380
381     public bool place_holder = false;
382
383     public bool is_live_stream () {
384         if (this.id == "VideoItem")
385             return false;
386         else
387             return true;
388     }
389
390     public bool streamable () {
391         return true;
392     }
393 }
394
395 private class Rygel.AudioItem : MediaItem {
396     public int64 duration = 2048;
397
398     public AudioItem () {
399         this.id = "AudioItem";
400     }
401 }
402
403 private interface Rygel.VisualItem : MediaItem {
404     public abstract int width { get; set; }
405     public abstract int height { get; set; }
406     public abstract int color_depth { get; set; }
407
408     public abstract ArrayList<Thumbnail> thumbnails { get; protected set; }
409
410     public bool is_live_stream () {
411         return false;
412     }
413
414     public bool streamable () {
415         return false;
416     }
417 }
418
419 private class Rygel.VideoItem : AudioItem, VisualItem {
420     public int width { get; set; default = -1; }
421     public int height { get; set; default = -1; }
422     public int color_depth { get; set; default = -1; }
423
424     private ArrayList<Thumbnail> ts;
425
426     public VideoItem () {
427         this.id = "VideoItem";
428     }
429
430     public ArrayList<Thumbnail> thumbnails {
431         get {
432             this.ts = new ArrayList<Thumbnail>();
433             ts.add(new Rygel.Thumbnail());
434             return this.ts;
435         }
436
437         protected set {}
438     }
439
440     public ArrayList<Subtitle> subtitles;
441 }
442
443 private class Rygel.MusicItem : AudioItem {
444     public Thumbnail album_art;
445 }
446
447 public class Rygel.Thumbnail {
448     public long size = 1024;
449     public string file_extension;
450 }
451
452 public class Rygel.Subtitle {
453     public long size = 1024;
454     public string caption_type;
455 }
456
457 internal class Rygel.HTTPResponse : Rygel.StateMachine, GLib.Object {
458     public Cancellable cancellable { get; set; }
459
460     private Soup.Message msg;
461     private Soup.Server server;
462
463     public HTTPResponse (HTTPGet get_request) {
464         this.msg = get_request.msg;
465         this.msg.response_headers.set_encoding (Soup.Encoding.CONTENT_LENGTH);
466         this.server = get_request.server;
467     }
468
469     public async void run () {
470         SourceFunc run_continue = run.callback;
471
472         Idle.add (() => {
473             run_continue ();
474
475             return false;
476         });
477
478         yield;
479
480         this.msg.set_status (Soup.KnownStatusCode.OK);
481         this.server.unpause_message (msg);
482
483         this.completed ();
484     }
485 }
486
487 public class Rygel.MediaObject {
488     public string id;
489     public string mime_type = "";
490 }
491
492 public class Rygel.Transcoder : GLib.Object {
493     public string extension { get; protected set; }
494
495     public Transcoder (string extension) {
496         this.extension = extension;
497     }
498 }