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);
97 /* Avoid some warnings about unused methods: */
98 var item = new VideoItem();
99 assert (!item.is_live_stream());
100 assert (!item.streamable());
105 public HTTPGetTest () throws Error {
106 this.server = new HTTPServer ();
107 this.client = new HTTPClient (this.server.context);
108 this.main_loop = new MainLoop (null, false);
109 this.create_test_messages();
112 public virtual void run () throws Error {
113 Timeout.add_seconds (3, this.on_timeout);
114 this.server.message_received.connect (this.on_message_received);
115 this.client.completed.connect (this.on_client_completed);
117 this.start_next_test_request ();
119 this.main_loop.run ();
121 if (this.error != null) {
126 private void create_test_messages () {
127 requests = new ArrayList<TestRequestFactory> ();
129 Soup.Message request = new Soup.Message ("POST", this.server.uri);
130 requests.add (new TestRequestFactory (request,
131 Soup.KnownStatusCode.BAD_REQUEST));
133 request = new Soup.Message ("HEAD", this.server.uri);
134 requests.add (new TestRequestFactory (request, Soup.KnownStatusCode.OK));
136 request = new Soup.Message ("GET", this.server.uri);
137 requests.add (new TestRequestFactory (request, Soup.KnownStatusCode.OK));
139 string uri = this.server.create_uri ("VideoItem");
140 uri = uri + "/tr/MP3";
141 request = new Soup.Message ("HEAD", uri);
142 requests.add (new TestRequestFactory (request, Soup.KnownStatusCode.OK));
144 request = new Soup.Message ("GET", this.server.uri);
145 request.request_headers.append ("transferMode.dlna.org", "Streaming");
146 requests.add (new TestRequestFactory (request, Soup.KnownStatusCode.OK));
148 request = new Soup.Message ("GET", this.server.uri);
149 request.request_headers.append ("transferMode.dlna.org", "Interactive");
150 requests.add (new TestRequestFactory (request,
151 Soup.KnownStatusCode.NOT_ACCEPTABLE));
153 request = new Soup.Message ("GET", this.server.uri);
154 request.request_headers.append ("Range", "bytes=1-2");
155 requests.add (new TestRequestFactory (request,
156 Soup.KnownStatusCode.OK));
158 uri = this.server.create_uri ("AudioItem");
161 request = new Soup.Message ("GET", uri);
162 requests.add (new TestRequestFactory (request,
163 Soup.KnownStatusCode.NOT_FOUND));
165 request = new Soup.Message ("GET", this.server.uri);
166 request.request_headers.append ("TimeSeekRange.dlna.org", "0");
167 requests.add (new TestRequestFactory (request,
168 Soup.KnownStatusCode.NOT_ACCEPTABLE));
170 uri = this.server.create_uri ("AudioItem");
171 request = new Soup.Message ("GET", uri);
172 request.request_headers.append ("TimeSeekRange.dlna.org", "0");
173 requests.add (new TestRequestFactory (request,
174 Soup.KnownStatusCode.BAD_REQUEST));
176 uri = this.server.create_uri ("AudioItem");
177 request = new Soup.Message ("GET", uri);
178 request.request_headers.append ("TimeSeekRange.dlna.org", "npt=1-2049");
179 requests.add (new TestRequestFactory (request,
180 Soup.KnownStatusCode.REQUESTED_RANGE_NOT_SATISFIABLE));
182 request = new Soup.Message ("GET", this.server.uri);
183 request.request_headers.append ("clienthacks.test.rygel", "f");
184 requests.add (new TestRequestFactory (request,
185 Soup.KnownStatusCode.OK));
187 request = new Soup.Message ("GET", this.server.uri);
188 request.request_headers.append ("clienthacks.test.rygel", "t");
189 requests.add (new TestRequestFactory (request,
190 Soup.KnownStatusCode.OK));
193 private HTTPGet create_request (Soup.Message msg) throws Error {
194 HTTPGet request = this.current_request.create_get (this.server,
195 this.server.context.server, msg);
199 private void on_client_completed (StateMachine client) {
200 if (requests.size > 0) {
201 this.start_next_test_request ();
203 this.main_loop.quit ();
204 this.client_done = true;
208 private void start_next_test_request() {
209 this.current_request = requests.remove_at (0);
210 this.client.msg = this.current_request.msg;
211 this.client.run.begin ();
214 private void on_message_received (HTTPServer server,
216 this.handle_client_message.begin (msg);
219 private async void handle_client_message (Soup.Message msg) {
221 var request = this.create_request (msg);
223 yield request.run ();
225 assert ((request as HTTPGet).object != null);
227 debug ("status.code: %d", (int) msg.status_code);
228 assert (msg.status_code == this.current_request.expected_code);
230 if (this.client_done) {
231 this.main_loop.quit ();
234 this.server_done = true;
235 } catch (Error error) {
237 this.main_loop.quit ();
243 private bool on_timeout () {
244 this.error = new TestError.TIMEOUT ("Timeout");
245 this.main_loop.quit ();
251 public class Rygel.HTTPServer : GLib.Object {
252 private const string SERVER_PATH = "/RygelHTTPServer/Rygel/Test";
253 public string path_root {
259 public MediaContainer root_container;
260 public GUPnP.Context context;
264 return create_uri("VideoItem");
268 public string create_uri (string item_id) {
269 var item = new VideoItem ();
272 var item_uri = new HTTPItemURI (item, this);
274 return item_uri.to_string ();
277 public signal void message_received (Soup.Message message);
279 public HTTPServer () throws TestError {
281 this.context = new GUPnP.Context (null, "lo", 0);
282 } catch (Error error) {
283 throw new TestError.SKIP ("Network context not available");
286 assert (this.context != null);
287 assert (this.context.host_ip != null);
288 assert (this.context.port > 0);
290 this.context.server.add_handler (SERVER_PATH, this.server_cb);
292 this.root_container = new MediaContainer ();
295 private void server_cb (Server server,
299 ClientContext client) {
300 this.context.server.pause_message (msg);
301 this.message_received (msg);
304 public Transcoder get_transcoder (string target) throws Error {
305 if (target == "MP3") {
306 return new Transcoder ("mp3");
308 throw new HTTPRequestError.NOT_FOUND (
309 "No transcoder available for target format '%s'",
314 public class Rygel.HTTPClient : GLib.Object, StateMachine {
315 public GUPnP.Context context;
316 public Soup.Message msg;
318 public Cancellable cancellable { get; set; }
320 public HTTPClient (GUPnP.Context context) {
321 this.context = context;
324 public async void run () {
325 SourceFunc run_continue = run.callback;
327 this.context.session.queue_message (this.msg, (session, msg) => {
337 public class Rygel.MediaContainer : Rygel.MediaObject {
339 public async MediaObject? find_object (string item_id,
340 Cancellable? cancellable)
342 SourceFunc find_object_continue = find_object.callback;
344 find_object_continue ();
351 debug ("item id: %s", item_id);
352 if (item_id == "VideoItem") {
353 return new VideoItem ();
354 } else if (item_id == "AudioItem") {
355 return new AudioItem ();
362 internal abstract class Rygel.HTTPGetHandler {
363 public HTTPResponse render_body (HTTPGet get_request) {
364 return new HTTPResponse (get_request);
367 public void add_response_headers (HTTPGet get_request) {}
369 public bool knows_size (HTTPGet request) { return false; }
372 internal class Rygel.HTTPTranscodeHandler : Rygel.HTTPGetHandler {
373 public HTTPTranscodeHandler (Transcoder transcoder,
374 Cancellable cancellable) {}
377 internal class Rygel.HTTPIdentityHandler : Rygel.HTTPGetHandler {
378 public HTTPIdentityHandler (Cancellable cancellable) {}
381 internal class Rygel.HTTPPlaylistHandler : Rygel.HTTPGetHandler {
382 public HTTPPlaylistHandler (Cancellable cancellable) {}
385 public abstract class Rygel.MediaItem : Rygel.MediaObject {
386 public long size = 1024;
387 public ArrayList<string> uris = new ArrayList<string> ();
389 public bool place_holder = false;
391 public bool is_live_stream () {
392 if (this.id == "VideoItem")
398 public bool streamable () {
403 private class Rygel.AudioItem : MediaItem {
404 public int64 duration = 2048;
406 public AudioItem () {
407 this.id = "AudioItem";
411 private interface Rygel.VisualItem : MediaItem {
412 public abstract int width { get; set; }
413 public abstract int height { get; set; }
414 public abstract int color_depth { get; set; }
416 public abstract ArrayList<Thumbnail> thumbnails { get; protected set; }
418 public bool is_live_stream () {
422 public bool streamable () {
427 private class Rygel.VideoItem : AudioItem, VisualItem {
428 public int width { get; set; default = -1; }
429 public int height { get; set; default = -1; }
430 public int color_depth { get; set; default = -1; }
432 private ArrayList<Thumbnail> ts;
434 public VideoItem () {
435 this.id = "VideoItem";
438 public ArrayList<Thumbnail> thumbnails {
440 this.ts = new ArrayList<Thumbnail>();
441 ts.add(new Rygel.Thumbnail());
448 public ArrayList<Subtitle> subtitles = new ArrayList<Subtitle> ();
451 private class Rygel.MusicItem : AudioItem {
452 public Thumbnail album_art;
455 public class Rygel.Thumbnail {
456 public long size = 1024;
457 public string file_extension;
460 public class Rygel.Subtitle {
461 public long size = 1024;
462 public string caption_type;
465 internal class Rygel.HTTPResponse : Rygel.StateMachine, GLib.Object {
466 public Cancellable cancellable { get; set; }
468 private Soup.Message msg;
469 private Soup.Server server;
471 public HTTPResponse (HTTPGet get_request) {
472 this.msg = get_request.msg;
473 this.msg.response_headers.set_encoding (Soup.Encoding.CONTENT_LENGTH);
474 this.server = get_request.server;
477 public async void run () {
478 SourceFunc run_continue = run.callback;
488 this.msg.set_status (Soup.KnownStatusCode.OK);
489 this.server.unpause_message (msg);
495 public class Rygel.MediaObject {
497 public string mime_type = "";
500 public class Rygel.Transcoder : GLib.Object {
501 public string extension { get; protected set; }
503 public Transcoder (string extension) {
504 this.extension = extension;