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 throw new ClientHacksError.NA ("");
41 public void apply (MediaItem item) {
45 public class Rygel.TestRequestFactory {
46 public Soup.Message msg;
47 public Soup.Status? expected_code;
50 public TestRequestFactory (Soup.Message msg,
51 Soup.Status? expected_code,
52 bool cancel = false) {
54 this.expected_code = expected_code;
58 internal HTTPPost create_post (HTTPServer http_server,
61 HTTPPost request = new HTTPPost (http_server, server, msg);
67 public class Rygel.HTTPPostTest : GLib.Object {
68 protected HTTPServer server;
69 protected HTTPClient client;
70 private bool server_done;
71 private bool client_done;
74 private MainLoop main_loop;
77 private ArrayList<TestRequestFactory> requests;
78 private TestRequestFactory current_request;
80 public static int main (string[] args) {
82 var test = new HTTPPostTest ();
85 } catch (TestError.SKIP error) {
87 } catch (Error error) {
88 critical ("%s", error.message);
96 public HTTPPostTest () throws Error {
97 this.server = new HTTPServer ();
98 this.client = new HTTPClient (this.server.context);
99 this.main_loop = new MainLoop (null, false);
100 this.create_test_messages();
103 public virtual void run () throws Error {
105 var file = File.new_for_uri (MediaItem.URI);
106 FileUtils.remove (file.get_path ());
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 var request = new Soup.Message ("POST", this.server.uri);
125 requests.add (new TestRequestFactory (request, Soup.Status.OK));
127 request = new Soup.Message ("POST", this.server.uri);
128 requests.add (new TestRequestFactory (request,
129 Soup.Status.NOT_FOUND));
131 request = new Soup.Message ("POST", this.server.create_uri ("NullItem"));
132 requests.add (new TestRequestFactory (request,
133 Soup.Status.BAD_REQUEST));
135 request = new Soup.Message ("POST",
136 this.server.create_uri ("ErrorItem"));
137 requests.add (new TestRequestFactory (request,
140 request = new Soup.Message ("POST",
141 this.server.create_uri ("CancelItem"));
142 requests.add (new TestRequestFactory (request,
143 Soup.Status.OK, true));
145 request = new Soup.Message ("POST",
146 this.server.create_uri ("VanishingItem"));
147 requests.add (new TestRequestFactory (request, Soup.Status.OK));
150 private HTTPRequest create_request (Soup.Message msg) throws Error {
151 var srv = this.server.context.server;
152 var request = this.current_request.create_post (this.server, srv, msg);
157 private void start_next_test_request() {
158 this.current_request = requests.remove_at (0);
159 this.client.msg = this.current_request.msg;
161 this.client.run.begin ();
164 private void on_client_completed (StateMachine client) {
165 if (requests.size > 0) {
166 if (this.server_done) {
167 this.server_done = false;
168 this.start_next_test_request ();
173 if (this.server_done) {
174 this.main_loop.quit ();
176 this.client_done = true;
180 private void on_message_received (HTTPServer server,
182 this.handle_client_message.begin (msg);
185 private async void handle_client_message (Soup.Message msg) {
187 var request = this.create_request (msg);
189 if (this.current_request.cancel) {
190 request.cancellable.cancel ();
192 yield request.run ();
194 debug ("status.code: %d", (int) msg.status_code);
195 assert (msg.status_code == this.current_request.expected_code);
197 this.check_result.begin ();
200 if (this.client_done) {
201 this.main_loop.quit ();
202 } else if (this.ready) {
205 start_next_test_request ();
207 this.server_done = true;
209 } catch (Error error) {
211 this.main_loop.quit ();
217 private bool on_timeout () {
218 this.error = new TestError.TIMEOUT ("Timeout");
219 this.main_loop.quit ();
224 private async void check_result () {
226 var file = this.server.root_container.item.file;
227 var stream = yield file.read_async (Priority.HIGH, null);
228 var buffer = new uint8[HTTPClient.LENGTH];
229 yield stream.read_async (buffer, Priority.HIGH, null);
231 for (var i = 0; i < HTTPClient.LENGTH; i++) {
232 assert (buffer[i] == this.client.content[i]);
234 } catch (IOError.NOT_FOUND e) {
236 } catch (Error error) {
239 this.main_loop.quit ();
244 public class Rygel.HTTPServer : GLib.Object {
245 private const string SERVER_PATH = "/RygelHTTPServer/Rygel/Test";
246 public string path_root {
252 public MediaContainer root_container;
253 public GUPnP.Context context;
257 var item = new MediaItem (MediaContainer.ITEM_ID, this.root_container);
258 var item_uri = new HTTPItemURI (item, this);
259 return item_uri.to_string ();
263 public string create_uri(string item_id) {
264 var item = new MediaItem (item_id, this.root_container);
265 var item_uri = new HTTPItemURI (item, this);
266 return item_uri.to_string ();
269 public signal void message_received (Soup.Message message);
271 public HTTPServer () throws TestError {
273 this.context = new GUPnP.Context (null, "lo", 0);
274 } catch (Error error) {
275 throw new TestError.SKIP ("Network context not available");
278 assert (this.context != null);
279 assert (this.context.host_ip != null);
280 assert (this.context.port > 0);
282 context.server.request_started.connect (this.on_request_started);
284 this.root_container = new MediaContainer ();
287 private void on_request_started (Soup.Server server,
289 Soup.ClientContext client) {
290 msg.got_headers.connect (this.on_got_headers);
293 private void on_got_headers (Soup.Message msg) {
294 this.message_received (msg);
297 public Transcoder get_transcoder (string target) throws Error {
298 if (target == "MP3") {
299 return new Transcoder ("mp3");
301 throw new HTTPRequestError.NOT_FOUND (
302 "No transcoder available for target format '%s'",
307 public class Rygel.HTTPClient : GLib.Object, StateMachine {
308 public const size_t LENGTH = 1024;
310 public uint8[] content;
312 public GUPnP.Context context;
313 public Soup.Message msg;
315 public Cancellable cancellable { get; set; }
317 public HTTPClient (GUPnP.Context context) {
318 this.context = context;
319 this.content = new uint8[1024];
322 public async void run () {
323 SourceFunc run_continue = run.callback;
325 this.msg.request_body.append (MemoryUse.COPY, content);
327 this.context.session.queue_message (this.msg, (session, msg) => {
337 public class Rygel.MediaContainer : Rygel.MediaObject {
338 public const string ITEM_ID = "TestItem";
340 public signal void container_updated (MediaContainer container);
342 public MediaItem item;
347 private FileMonitor monitor;
349 public MediaContainer () {
350 this.file = File.new_for_uri (MediaItem.URI);
351 this.item = new MediaItem (ITEM_ID, this);
354 this.id = "TesContainer";
357 this.monitor = this.file.monitor_file (FileMonitorFlags.NONE);
358 } catch (GLib.Error error) {
359 assert_not_reached ();
362 this.monitor.changed.connect (this.on_file_changed);
365 public async MediaObject? find_object (string item_id,
366 Cancellable? cancellable)
368 SourceFunc find_object_continue = find_object.callback;
370 find_object_continue ();
377 if (item_id == "ErrorItem" && this.error) {
378 var msg = _("Fake error caused by %s object.");
379 this.file.delete (null);
381 throw new ContentDirectoryError.INVALID_ARGS (msg, item_id);
382 } else if (item_id == "ErrorItem" && !this.error) {
386 if (item_id == "VanishingItem" && this.vanish) {
387 this.file.delete (null);
390 } else if (item_id == "VanishingItem" && !this.vanish) {
394 if (item_id != this.item.id) {
395 this.item = new MediaItem (item_id, this);
401 public void on_file_changed (FileMonitor monitor,
404 FileMonitorEvent event_type) {
405 this.item.place_holder = false;
407 this.container_updated (this);
412 this.file.delete (null);
413 } catch (GLib.Error error) {
414 assert_not_reached ();
419 public class Rygel.MediaItem : Rygel.MediaObject {
420 public const string URI = "file:///tmp/rygel-upload-test.wav";
422 public long size = 1024;
423 public long duration = 1024;
424 public ArrayList<string> uris = new ArrayList<string> ();
426 public bool place_holder = true;
430 public MediaItem.for_visual_item () {}
432 public MediaItem (string id, MediaContainer parent) {
434 this.parent = parent;
436 this.file = parent.file;
439 public async File? get_writable (Cancellable? cancellable) throws Error {
440 SourceFunc get_writable_continue = get_writable.callback;
443 get_writable_continue ();
450 if (this.id == "NullItem") {
451 this.file.delete (null);
460 internal class Rygel.HTTPResponse : Rygel.StateMachine, GLib.Object {
461 public Cancellable cancellable { get; set; }
463 private Soup.Message msg;
464 private Soup.Server server;
466 public HTTPResponse (HTTPPost get_request) {
467 this.msg = get_request.msg;
469 this.server = get_request.server;
472 public async void run () {
473 SourceFunc run_continue = run.callback;
483 this.msg.set_status (Soup.Status.OK);
484 this.server.unpause_message (msg);
490 public class Rygel.ObjectRemovalQueue: GLib.Object {
491 public static ObjectRemovalQueue get_default () {
492 return new ObjectRemovalQueue ();
495 public bool dequeue (MediaObject item) {
499 public async void remove_now (MediaObject item, Cancellable? cancellable) {
500 Idle.add (remove_now.callback);
506 public class Rygel.MediaObject : GLib.Object {
508 public unowned MediaContainer parent;
509 public string mime_type = "";
512 public class Rygel.Thumbnail : GLib.Object {
513 public string file_extension;
516 public class Rygel.VisualItem : Rygel.MediaItem {
517 public ArrayList<Thumbnail> thumbnails = new ArrayList<Thumbnail> ();
519 public VisualItem () {
520 base.for_visual_item();
524 private class Rygel.Subtitle : GLib.Object {
525 public string caption_type;
528 private class Rygel.VideoItem : Rygel.VisualItem {
529 public ArrayList<Subtitle> subtitles = new ArrayList<Subtitle> ();
532 private class Rygel.MusicItem : MediaItem {
533 public Thumbnail album_art;
535 public MusicItem (string id, MediaContainer parent) {
540 public errordomain Rygel.ContentDirectoryError {
544 public class Rygel.Transcoder : GLib.Object {
545 public string extension { get; protected set; }
547 public Transcoder (string extension) {
548 this.extension = extension;