2 * Copyright (C) 2008, 2009 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.
28 internal class Rygel.HTTPServer : Rygel.TranscodeManager, Rygel.StateMachine {
29 private const string SERVER_PATH_PREFIX = "/RygelHTTPServer";
30 public string path_root { get; private set; }
32 // Reference to root container of associated ContentDirectory
33 public MediaContainer root_container;
34 public GUPnP.Context context;
35 private ArrayList<HTTPRequest> requests;
37 public Cancellable cancellable { get; set; }
39 public HTTPServer (ContentDirectory content_dir,
40 string name) throws GLib.Error {
43 this.root_container = content_dir.root_container;
44 this.context = content_dir.context;
45 this.requests = new ArrayList<HTTPRequest> ();
46 this.cancellable = content_dir.cancellable;
48 this.path_root = SERVER_PATH_PREFIX + "/" + name;
51 public async void run () {
52 context.server.add_handler (this.path_root, server_handler);
53 context.server.request_aborted.connect (this.on_request_aborted);
54 context.server.request_started.connect (this.on_request_started);
56 if (this.cancellable != null) {
57 this.cancellable.cancelled += this.on_cancelled;
61 /* We prepend these resources into the original resource list instead of
62 * appending them because some crappy MediaRenderer/ControlPoint
63 * implemenation out there just choose the first one in the list instead of
64 * the one they can handle.
66 internal override void add_resources (DIDLLiteItem didl_item,
70 foreach (var subtitle in item.subtitles) {
71 if (!is_http_uri (subtitle.uri)) {
72 var uri = subtitle.uri; // Save the original URI
73 var index = item.subtitles.index_of (subtitle);
75 subtitle.uri = this.create_uri_for_item (item,
79 subtitle.add_didl_node (didl_item);
81 // Now restore the original URI
86 if (!this.http_uri_present (item)) {
87 this.add_proxy_resource (didl_item, item);
90 base.add_resources (didl_item, item);
92 // Thumbnails comes in the end
93 foreach (var thumbnail in item.thumbnails) {
94 if (!is_http_uri (thumbnail.uri)) {
95 var uri = thumbnail.uri; // Save the original URI
96 var index = item.thumbnails.index_of (thumbnail);
98 thumbnail.uri = this.create_uri_for_item (item,
102 thumbnail.add_resource (didl_item, this.get_protocol ());
104 // Now restore the original URI
110 internal void add_proxy_resource (DIDLLiteItem didl_item,
113 var uri = this.create_uri_for_item (item, -1, -1, null);
115 item.add_resource (didl_item,
117 this.get_protocol (),
121 private bool http_uri_present (MediaItem item) {
122 bool present = false;
124 foreach (var uri in item.uris) {
125 if (this.is_http_uri (uri)) {
135 private bool is_http_uri (string uri) {
136 return Uri.parse_scheme (uri) == "http";
139 private void on_cancelled (Cancellable cancellable) {
140 // Cancel all state machines
141 this.cancellable.cancel ();
143 context.server.remove_handler (this.path_root);
148 internal override string create_uri_for_item (MediaItem item,
151 string? transcode_target) {
152 var uri = new HTTPItemURI (item.id,
158 return uri.to_string ();
161 internal override string get_protocol () {
165 internal override string get_protocol_info () {
166 var protocol_info = this.get_protocol () + ":*:*:*";
167 var base_info = base.get_protocol_info ();
170 protocol_info += "," + base_info;
172 return protocol_info;
175 private void on_request_completed (HTTPRequest request) {
176 this.requests.remove (request);
178 debug ("HTTP %s request for URI '%s' handled.",
180 request.msg.get_uri ().to_string (false));
183 private void server_handler (Soup.Server server,
186 HashTable<string,string>? query,
187 Soup.ClientContext soup_client) {
188 debug ("HTTP %s request for URI '%s'. Headers:",
190 msg.get_uri ().to_string (false));
191 msg.request_headers.foreach ((name, value) => {
192 debug ("%s : %s", name, value);
195 this.queue_request (new HTTPGet (this, server, msg));
198 private void on_request_aborted (Soup.Server server,
199 Soup.Message message,
200 Soup.ClientContext client) {
201 foreach (var request in this.requests) {
202 if (request.msg == message) {
203 request.cancellable.cancel ();
204 debug ("HTTP client aborted %s request for URI '%s'.",
206 request.msg.get_uri ().to_string (false));
213 private void on_request_started (Soup.Server server,
214 Soup.Message message,
215 Soup.ClientContext client) {
216 message.got_headers.connect (this.on_got_headers);
219 private void on_got_headers (Soup.Message msg) {
220 if (msg.method == "POST" &&
221 msg.uri.path.has_prefix (this.path_root)) {
222 debug ("HTTP POST request for URI '%s'",
223 msg.get_uri ().to_string (false));
225 this.queue_request (new HTTPPost (this, this.context.server, msg));
229 private void queue_request (HTTPRequest request) {
230 request.completed += this.on_request_completed;
231 this.requests.add (request);
232 request.run.begin ();