3f8a157b7284be3e9ddc53e29450013dc619f1b8
[profile/ivi/rygel.git] / src / rygel / rygel-http-server.vala
1 /*
2  * Copyright (C) 2008, 2009 Nokia Corporation.
3  *
4  * Author: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
5  *                               <zeeshan.ali@nokia.com>
6  *
7  * This file is part of Rygel.
8  *
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.
13  *
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.
18  *
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.
22  */
23
24 using Gst;
25 using GUPnP;
26 using Gee;
27
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; }
31
32     // Reference to root container of associated ContentDirectory
33     public MediaContainer root_container;
34     public GUPnP.Context context;
35     private ArrayList<HTTPRequest> requests;
36
37     public Cancellable cancellable { get; set; }
38
39     public HTTPServer (ContentDirectory content_dir,
40                        string           name) throws GLib.Error {
41         base ();
42
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;
47
48         this.path_root = SERVER_PATH_PREFIX + "/" + name;
49     }
50
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);
55
56         if (this.cancellable != null) {
57             this.cancellable.cancelled += this.on_cancelled;
58         }
59     }
60
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.
65      */
66     internal override void add_resources (DIDLLiteItem didl_item,
67                                           MediaItem    item)
68                                           throws Error {
69         // Subtitles first
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);
74
75                 subtitle.uri = this.create_uri_for_item (item,
76                                                          -1,
77                                                          index,
78                                                          null);
79                 subtitle.add_didl_node (didl_item);
80
81                 // Now restore the original URI
82                 subtitle.uri = uri;
83             }
84         }
85
86         if (!this.http_uri_present (item)) {
87             this.add_proxy_resource (didl_item, item);
88         }
89
90         base.add_resources (didl_item, item);
91
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);
97
98                 thumbnail.uri = this.create_uri_for_item (item,
99                                                           index,
100                                                           -1,
101                                                           null);
102                 thumbnail.add_resource (didl_item, this.get_protocol ());
103
104                 // Now restore the original URI
105                 thumbnail.uri = uri;
106             }
107         }
108     }
109
110     internal void add_proxy_resource (DIDLLiteItem didl_item,
111                                       MediaItem    item)
112                                       throws Error {
113         var uri = this.create_uri_for_item (item, -1, -1, null);
114
115         item.add_resource (didl_item,
116                            uri.to_string (),
117                            this.get_protocol (),
118                            uri.to_string ());
119     }
120
121     private bool http_uri_present (MediaItem item) {
122         bool present = false;
123
124         foreach (var uri in item.uris) {
125             if (this.is_http_uri (uri)) {
126                 present = true;
127
128                 break;
129             }
130         }
131
132         return present;
133     }
134
135     private bool is_http_uri (string uri) {
136         return Uri.parse_scheme (uri) == "http";
137     }
138
139     private void on_cancelled (Cancellable cancellable) {
140         // Cancel all state machines
141         this.cancellable.cancel ();
142
143         context.server.remove_handler (this.path_root);
144
145         this.completed ();
146     }
147
148     internal override string create_uri_for_item (MediaItem item,
149                                                   int       thumbnail_index,
150                                                   int       subtitle_index,
151                                                   string?   transcode_target) {
152         var uri = new HTTPItemURI (item.id,
153                                    this,
154                                    thumbnail_index,
155                                    subtitle_index,
156                                    transcode_target);
157
158         return uri.to_string ();
159     }
160
161     internal override string get_protocol () {
162         return "http-get";
163     }
164
165     internal override string get_protocol_info () {
166         var protocol_info = this.get_protocol () + ":*:*:*";
167         var base_info = base.get_protocol_info ();
168
169         if (base_info != "")
170             protocol_info += "," + base_info;
171
172         return protocol_info;
173     }
174
175     private void on_request_completed (HTTPRequest request) {
176         this.requests.remove (request);
177
178         debug ("HTTP %s request for URI '%s' handled.",
179                request.msg.method,
180                request.msg.get_uri ().to_string (false));
181     }
182
183     private void server_handler (Soup.Server               server,
184                                  Soup.Message              msg,
185                                  string                    server_path,
186                                  HashTable<string,string>? query,
187                                  Soup.ClientContext        soup_client) {
188         debug ("HTTP %s request for URI '%s'. Headers:",
189                msg.method,
190                msg.get_uri ().to_string (false));
191         msg.request_headers.foreach ((name, value) => {
192                 debug ("%s : %s", name, value);
193         });
194
195         this.queue_request (new HTTPGet (this, server, msg));
196     }
197
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'.",
205                        request.msg.method,
206                        request.msg.get_uri ().to_string (false));
207
208                 break;
209             }
210         }
211     }
212
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);
217     }
218
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));
224
225             this.queue_request (new HTTPPost (this, this.context.server, msg));
226         }
227     }
228
229     private void queue_request (HTTPRequest request) {
230         request.completed += this.on_request_completed;
231         this.requests.add (request);
232         request.run.begin ();
233     }
234 }
235