2 * Copyright (C) 2010 Nokia Corporation.
4 * Author: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
5 * <zeeshan.ali@nokia.com>
7 * This file is part of Rygel.
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.
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.
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.
27 public errordomain Rygel.TestError {
32 public errordomain Rygel.ClientHacksError {
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 ();
42 throw new ClientHacksError.NA ("");
46 public void apply (MediaItem? item) {
50 public class Rygel.TestRequestFactory {
51 public Soup.Message msg;
52 public Soup.KnownStatusCode expected_code;
54 public TestRequestFactory (Soup.Message msg,
55 Soup.KnownStatusCode expected_code) {
57 this.expected_code = expected_code;
60 internal HTTPGet create_get (HTTPServer http_server,
63 HTTPGet request = new HTTPGet (http_server, server, msg);
64 request.handler = null;
70 public class Rygel.HTTPGetTest : GLib.Object {
71 protected HTTPServer server;
72 protected HTTPClient client;
74 private bool server_done;
75 private bool client_done;
77 private MainLoop main_loop;
81 private ArrayList<TestRequestFactory> requests;
82 private TestRequestFactory current_request;
84 public static int main (string[] args) {
86 var test = new HTTPGetTest ();
89 } catch (TestError.SKIP error) {
91 } catch (Error error) {
92 critical ("%s", error.message);
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();
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);
112 this.start_next_test_request ();
114 this.main_loop.run ();
116 if (this.error != null) {
121 private void create_test_messages () {
122 requests = new ArrayList<TestRequestFactory> ();
124 Soup.Message request = new Soup.Message ("POST", this.server.uri);
125 requests.add (new TestRequestFactory (request,
126 Soup.KnownStatusCode.BAD_REQUEST));
128 request = new Soup.Message ("HEAD", this.server.uri);
129 requests.add (new TestRequestFactory (request, Soup.KnownStatusCode.OK));
131 request = new Soup.Message ("GET", this.server.uri);
132 requests.add (new TestRequestFactory (request, Soup.KnownStatusCode.OK));
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));
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));
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));
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));
153 uri = this.server.create_uri ("AudioItem");
156 request = new Soup.Message ("GET", uri);
157 requests.add (new TestRequestFactory (request,
158 Soup.KnownStatusCode.NOT_FOUND));
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));
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));
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));
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));
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));
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);
194 private void on_client_completed (StateMachine client) {
195 if (requests.size > 0) {
196 this.start_next_test_request ();
197 this.client.run.begin ();
199 this.main_loop.quit ();
200 this.client_done = true;
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 ();
210 private void on_message_received (HTTPServer server,
212 this.handle_client_message.begin (msg);
215 private async void handle_client_message (Soup.Message msg) {
217 var request = this.create_request (msg);
219 yield request.run ();
221 assert ((request as HTTPGet).item != null);
223 debug ("status.code: %d", (int) msg.status_code);
224 assert (msg.status_code == this.current_request.expected_code);
226 if (this.client_done) {
227 this.main_loop.quit ();
230 this.server_done = true;
231 } catch (Error error) {
233 this.main_loop.quit ();
239 private bool on_timeout () {
240 this.error = new TestError.TIMEOUT ("Timeout");
241 this.main_loop.quit ();
247 public class Rygel.HTTPServer : GLib.Object {
248 private const string SERVER_PATH = "/RygelHTTPServer/Rygel/Test";
249 public string path_root {
255 public MediaContainer root_container;
256 public GUPnP.Context context;
260 return create_uri("VideoItem");
264 public string create_uri (string item_id) {
265 var item = new VideoItem ();
268 var item_uri = new HTTPItemURI (item, this);
270 return item_uri.to_string ();
273 public signal void message_received (Soup.Message message);
275 public HTTPServer () throws TestError {
277 this.context = new GUPnP.Context (null, "lo", 0);
278 } catch (Error error) {
279 throw new TestError.SKIP ("Network context not available");
282 assert (this.context != null);
283 assert (this.context.host_ip != null);
284 assert (this.context.port > 0);
286 this.context.server.add_handler (SERVER_PATH, this.server_cb);
288 this.root_container = new MediaContainer ();
291 private void server_cb (Server server,
295 ClientContext client) {
296 this.context.server.pause_message (msg);
297 this.message_received (msg);
300 public Transcoder get_transcoder (string target) throws Error {
301 if (target == "MP3") {
302 return new Transcoder ("mp3");
304 throw new HTTPRequestError.NOT_FOUND (
305 "No transcoder available for target format '%s'",
310 public class Rygel.HTTPClient : GLib.Object, StateMachine {
311 public GUPnP.Context context;
312 public Soup.Message msg;
314 public Cancellable cancellable { get; set; }
316 public HTTPClient (GUPnP.Context context) {
317 this.context = context;
320 public async void run () {
321 SourceFunc run_continue = run.callback;
323 this.context.session.queue_message (this.msg, (session, msg) => {
333 public class Rygel.MediaContainer : Rygel.MediaObject {
335 public async MediaObject? find_object (string item_id,
336 Cancellable? cancellable)
338 SourceFunc find_object_continue = find_object.callback;
340 find_object_continue ();
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 ();
358 internal abstract class Rygel.HTTPGetHandler {
359 public HTTPResponse render_body (HTTPGet get_request) {
360 return new HTTPResponse (get_request);
363 public void add_response_headers (HTTPGet get_request) {}
366 internal class Rygel.HTTPTranscodeHandler : Rygel.HTTPGetHandler {
367 public HTTPTranscodeHandler (Transcoder transcoder,
368 Cancellable cancellable) {}
371 internal class Rygel.HTTPIdentityHandler : Rygel.HTTPGetHandler {
372 public HTTPIdentityHandler (Cancellable cancellable) {}
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> ();
381 public bool place_holder = false;
383 public bool is_live_stream () {
384 if (this.id == "VideoItem")
390 public bool streamable () {
395 private class Rygel.AudioItem : MediaItem {
396 public int64 duration = 2048;
398 public AudioItem () {
399 this.id = "AudioItem";
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; }
408 public abstract ArrayList<Thumbnail> thumbnails { get; protected set; }
410 public bool is_live_stream () {
414 public bool streamable () {
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; }
424 private ArrayList<Thumbnail> ts;
426 public VideoItem () {
427 this.id = "VideoItem";
430 public ArrayList<Thumbnail> thumbnails {
432 this.ts = new ArrayList<Thumbnail>();
433 ts.add(new Rygel.Thumbnail());
440 public ArrayList<Subtitle> subtitles;
443 private class Rygel.MusicItem : AudioItem {
444 public Thumbnail album_art;
447 public class Rygel.Thumbnail {
448 public long size = 1024;
449 public string file_extension;
452 public class Rygel.Subtitle {
453 public long size = 1024;
454 public string caption_type;
457 internal class Rygel.HTTPResponse : Rygel.StateMachine, GLib.Object {
458 public Cancellable cancellable { get; set; }
460 private Soup.Message msg;
461 private Soup.Server server;
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;
469 public async void run () {
470 SourceFunc run_continue = run.callback;
480 this.msg.set_status (Soup.KnownStatusCode.OK);
481 this.server.unpause_message (msg);
487 public class Rygel.MediaObject {
489 public string mime_type = "";
492 public class Rygel.Transcoder : GLib.Object {
493 public string extension { get; protected set; }
495 public Transcoder (string extension) {
496 this.extension = extension;