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 ();
198 this.main_loop.quit ();
199 this.client_done = true;
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 ();
209 private void on_message_received (HTTPServer server,
211 this.handle_client_message.begin (msg);
214 private async void handle_client_message (Soup.Message msg) {
216 var request = this.create_request (msg);
218 yield request.run ();
220 assert ((request as HTTPGet).item != null);
222 debug ("status.code: %d", (int) msg.status_code);
223 assert (msg.status_code == this.current_request.expected_code);
225 if (this.client_done) {
226 this.main_loop.quit ();
229 this.server_done = true;
230 } catch (Error error) {
232 this.main_loop.quit ();
238 private bool on_timeout () {
239 this.error = new TestError.TIMEOUT ("Timeout");
240 this.main_loop.quit ();
246 public class Rygel.HTTPServer : GLib.Object {
247 private const string SERVER_PATH = "/RygelHTTPServer/Rygel/Test";
248 public string path_root {
254 public MediaContainer root_container;
255 public GUPnP.Context context;
259 return create_uri("VideoItem");
263 public string create_uri (string item_id) {
264 var item = new VideoItem ();
267 var item_uri = new HTTPItemURI (item, this);
269 return item_uri.to_string ();
272 public signal void message_received (Soup.Message message);
274 public HTTPServer () throws TestError {
276 this.context = new GUPnP.Context (null, "lo", 0);
277 } catch (Error error) {
278 throw new TestError.SKIP ("Network context not available");
281 assert (this.context != null);
282 assert (this.context.host_ip != null);
283 assert (this.context.port > 0);
285 this.context.server.add_handler (SERVER_PATH, this.server_cb);
287 this.root_container = new MediaContainer ();
290 private void server_cb (Server server,
294 ClientContext client) {
295 this.context.server.pause_message (msg);
296 this.message_received (msg);
299 public Transcoder get_transcoder (string target) throws Error {
300 if (target == "MP3") {
301 return new Transcoder ("mp3");
303 throw new HTTPRequestError.NOT_FOUND (
304 "No transcoder available for target format '%s'",
309 public class Rygel.HTTPClient : GLib.Object, StateMachine {
310 public GUPnP.Context context;
311 public Soup.Message msg;
313 public Cancellable cancellable { get; set; }
315 public HTTPClient (GUPnP.Context context) {
316 this.context = context;
319 public async void run () {
320 SourceFunc run_continue = run.callback;
322 this.context.session.queue_message (this.msg, (session, msg) => {
332 public class Rygel.MediaContainer : Rygel.MediaObject {
334 public async MediaObject? find_object (string item_id,
335 Cancellable? cancellable)
337 SourceFunc find_object_continue = find_object.callback;
339 find_object_continue ();
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 ();
357 internal abstract class Rygel.HTTPGetHandler {
358 public HTTPResponse render_body (HTTPGet get_request) {
359 return new HTTPResponse (get_request);
362 public void add_response_headers (HTTPGet get_request) {}
364 public bool knows_size (HTTPGet request) { return false; }
367 internal class Rygel.HTTPTranscodeHandler : Rygel.HTTPGetHandler {
368 public HTTPTranscodeHandler (Transcoder transcoder,
369 Cancellable cancellable) {}
372 internal class Rygel.HTTPIdentityHandler : Rygel.HTTPGetHandler {
373 public HTTPIdentityHandler (Cancellable cancellable) {}
376 internal class Rygel.HTTPPlaylistHandler : Rygel.HTTPGetHandler {
377 public HTTPPlaylistHandler (Cancellable cancellable) {}
380 public abstract class Rygel.MediaItem : Rygel.MediaObject {
381 public long size = 1024;
382 public ArrayList<Subtitle> subtitles = new ArrayList<Subtitle> ();
383 public ArrayList<Thumbnail> thumbnails = new ArrayList<Thumbnail> ();
384 public ArrayList<string> uris = new ArrayList<string> ();
386 public bool place_holder = false;
388 public bool is_live_stream () {
389 if (this.id == "VideoItem")
395 public bool streamable () {
400 private class Rygel.AudioItem : MediaItem {
401 public int64 duration = 2048;
403 public AudioItem () {
404 this.id = "AudioItem";
408 private interface Rygel.VisualItem : MediaItem {
409 public abstract int width { get; set; }
410 public abstract int height { get; set; }
411 public abstract int color_depth { get; set; }
413 public abstract ArrayList<Thumbnail> thumbnails { get; protected set; }
415 public bool is_live_stream () {
419 public bool streamable () {
424 private class Rygel.VideoItem : AudioItem, VisualItem {
425 public int width { get; set; default = -1; }
426 public int height { get; set; default = -1; }
427 public int color_depth { get; set; default = -1; }
429 private ArrayList<Thumbnail> ts;
431 public VideoItem () {
432 this.id = "VideoItem";
435 public ArrayList<Thumbnail> thumbnails {
437 this.ts = new ArrayList<Thumbnail>();
438 ts.add(new Rygel.Thumbnail());
445 public ArrayList<Subtitle> subtitles;
448 private class Rygel.MusicItem : AudioItem {
449 public Thumbnail album_art;
452 public class Rygel.Thumbnail {
453 public long size = 1024;
454 public string file_extension;
457 public class Rygel.Subtitle {
458 public long size = 1024;
459 public string caption_type;
462 internal class Rygel.HTTPResponse : Rygel.StateMachine, GLib.Object {
463 public Cancellable cancellable { get; set; }
465 private Soup.Message msg;
466 private Soup.Server server;
468 public HTTPResponse (HTTPGet get_request) {
469 this.msg = get_request.msg;
470 this.msg.response_headers.set_encoding (Soup.Encoding.CONTENT_LENGTH);
471 this.server = get_request.server;
474 public async void run () {
475 SourceFunc run_continue = run.callback;
485 this.msg.set_status (Soup.KnownStatusCode.OK);
486 this.server.unpause_message (msg);
492 public class Rygel.MediaObject {
494 public string mime_type = "";
497 public class Rygel.Transcoder : GLib.Object {
498 public string extension { get; protected set; }
500 public Transcoder (string extension) {
501 this.extension = extension;