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.
26 public errordomain Rygel.TestError {
31 public errordomain Rygel.HTTPRequestError {
32 NOT_FOUND = Soup.Status.NOT_FOUND
35 public class Rygel.HTTPResponseTest : GLib.Object {
36 public const long MAX_BYTES = 102400;
38 protected HTTPServer server;
39 protected HTTPClient client;
41 private bool server_done;
42 private bool client_done;
44 private MediaItem item;
46 private MainLoop main_loop;
48 protected Cancellable cancellable;
51 public static int main (string[] args) {
53 var test = new HTTPResponseTest.complete ();
56 test = new HTTPResponseTest.abort ();
58 } catch (TestError.SKIP error) {
60 } catch (Error error) {
61 critical ("%s", error.message);
69 public HTTPResponseTest (Cancellable? cancellable = null) throws Error {
70 this.cancellable = cancellable;
72 this.server = new HTTPServer ();
73 this.client = new HTTPClient (this.server.context,
77 this.main_loop = new MainLoop (null, false);
80 public HTTPResponseTest.complete () throws Error {
83 this.item = new MediaItem.fixed_size ();
86 public HTTPResponseTest.abort () throws Error {
87 this (new Cancellable ());
89 this.item = new MediaItem ();
92 public virtual void run () throws Error {
93 Timeout.add_seconds (3, this.on_timeout);
94 this.server.message_received.connect (this.on_message_received);
95 this.server.message_aborted.connect (this.on_message_aborted);
96 if (this.cancellable == null) {
97 this.client.completed.connect (this.on_client_completed);
99 this.client_done = true;
102 this.client.run.begin ();
104 this.main_loop.run ();
106 if (this.error != null) {
111 private HTTPResponse create_response (Soup.Message msg) throws Error {
112 var seek = null as HTTPSeek;
114 if (!this.item.is_live_stream ()) {
115 seek = new HTTPByteSeek (0, MAX_BYTES - 1, this.item.size);
116 msg.response_headers.set_content_length (seek.length);
119 var request = new HTTPGet (this.server.context.server,
124 var handler = new HTTPGetHandler (this.cancellable);
125 var src = this.item.create_stream_source ();
127 return new HTTPResponse (request, handler, src);
130 private void on_client_completed (StateMachine client) {
131 if (this.server_done) {
132 this.main_loop.quit ();
135 this.client_done = true;
138 private void on_response_completed (StateMachine response) {
139 if (this.client_done) {
140 this.main_loop.quit ();
143 this.server_done = true;
146 private void on_message_received (HTTPServer server,
149 var response = this.create_response (msg);
151 response.run.begin ();
153 response.completed.connect (this.on_response_completed);
154 } catch (Error error) {
156 this.main_loop.quit ();
162 private void on_message_aborted (HTTPServer server,
164 this.cancellable.cancel ();
167 private bool on_timeout () {
168 this.error = new TestError.TIMEOUT ("Timeout");
169 this.main_loop.quit ();
175 public class Rygel.HTTPServer : GLib.Object {
176 private const string SERVER_PATH = "/RygelHTTPServer/Rygel/Test";
178 public GUPnP.Context context;
181 owned get { return "http://" +
182 this.context.host_ip + ":" +
183 this.context.port.to_string () +
188 public signal void message_received (Soup.Message message);
189 public signal void message_aborted (Soup.Message message);
191 public HTTPServer () throws TestError {
193 this.context = new GUPnP.Context (null, "lo", 0);
194 } catch (Error error) {
195 throw new TestError.SKIP ("Network context not available");
198 assert (this.context != null);
199 assert (this.context.host_ip != null);
200 assert (this.context.port > 0);
202 this.context.server.add_handler (SERVER_PATH, this.server_cb);
203 this.context.server.request_aborted.connect (this.on_request_aborted);
206 private void server_cb (Server server,
210 ClientContext client) {
211 this.context.server.pause_message (msg);
212 this.message_received (msg);
215 private void on_request_aborted (Soup.Server server,
216 Soup.Message message,
217 Soup.ClientContext client) {
218 this.message_aborted (message);
222 public class Rygel.HTTPClient : GLib.Object, StateMachine {
223 public GUPnP.Context context;
224 public Soup.Message msg;
225 public size_t total_bytes;
227 public Cancellable cancellable { get; set; }
229 public HTTPClient (GUPnP.Context context,
233 this.context = context;
234 this.total_bytes = total_bytes;
236 this.msg = new Soup.Message ("HTTP", uri);
237 assert (this.msg != null);
238 this.msg.response_body.set_accumulate (false);
241 this.cancellable = new Cancellable ();
242 this.cancellable.cancelled.connect (this.on_cancelled);
246 public async void run () {
247 SourceFunc run_continue = run.callback;
248 size_t bytes_received = 0;
250 this.msg.got_chunk.connect ((msg, chunk) => {
251 bytes_received += chunk.length;
253 if (bytes_received >= this.total_bytes &&
254 this.cancellable != null) {
256 this.cancellable.cancel ();
260 this.context.session.queue_message (this.msg, (session, msg) => {
261 assert (cancellable != null || bytes_received == this.total_bytes);
271 private void on_cancelled (Cancellable cancellable) {
272 this.context.session.cancel_message (this.msg,
278 public class Rygel.HTTPSeek : GLib.Object {
279 public int64 start { get; private set; }
280 public int64 stop { get; private set; }
281 public int64 length { get; private set; }
282 public int64 total_length { get; private set; }
284 public HTTPSeek (int64 start, int64 stop, int64 total_length) {
287 this.total_length = total_length;
289 this.length = stop - start + 1;
293 public class Rygel.HTTPByteSeek : Rygel.HTTPSeek {
294 public HTTPByteSeek (int64 start, int64 stop, int64 total_length) {
295 base (start, stop, total_length);
299 public class Rygel.HTTPTimeSeek : Rygel.HTTPSeek {
300 public HTTPTimeSeek (int64 start, int64 stop, int64 total_length) {
301 base (start, stop, total_length);
305 public class Rygel.HTTPGet : GLib.Object {
306 public Soup.Server server;
307 public Soup.Message msg;
309 public Cancellable cancellable;
311 public MediaItem item;
313 internal HTTPSeek seek;
315 public HTTPGet (Soup.Server server,
319 Cancellable? cancellable) {
320 this.server = server;
324 this.cancellable = cancellable;
325 this.msg.response_headers.set_encoding (Soup.Encoding.EOF);
326 this.msg.set_status (Soup.Status.OK);
330 public class Rygel.HTTPGetHandler : GLib.Object {
331 public Cancellable cancellable;
333 public HTTPGetHandler (Cancellable? cancellable) {
334 this.cancellable = cancellable;
338 internal class Rygel.TestDataSource : Rygel.DataSource, Object {
339 private long block_size;
340 private long buffers;
341 private uint64 data_sent;
344 public TestDataSource (long block_size, long buffers) {
345 this.block_size = block_size;
346 this.buffers = buffers;
350 public void start (HTTPSeek? seek) throws Error {
356 var data = new uint8[block_size];
357 this.data_sent += block_size;
358 if (this.data_sent > HTTPResponseTest.MAX_BYTES) {
364 this.data_available (data);
370 public void freeze () {
374 public void thaw () {
383 } catch (GLib.Error error) {
384 assert_not_reached ();
388 public void stop () {
393 public class Rygel.MediaItem {
394 private static const long BLOCK_SIZE = HTTPResponseTest.MAX_BYTES / 16;
395 private static const long MAX_BUFFERS = 25;
399 return MAX_BUFFERS * BLOCK_SIZE;
403 private DataSource src;
404 bool is_live = false;
406 public MediaItem () {
407 this.src = new TestDataSource (BLOCK_SIZE, MAX_BUFFERS);
411 public MediaItem.fixed_size () {
415 public DataSource? create_stream_source () {
419 public bool is_live_stream () {