core: Append extensions to served files
[profile/ivi/rygel.git] / tests / rygel-http-post-test.vala
1 /*
2  * Copyright (C) 2010 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 Soup;
25 using Gee;
26
27 public errordomain Rygel.TestError {
28     SKIP = 77,
29     TIMEOUT
30 }
31
32 public errordomain Rygel.ClientHacksError {
33     NA
34 }
35
36 public class Rygel.ClientHacks {
37     public static ClientHacks create (Message? message) throws Error {
38         throw new ClientHacksError.NA ("");
39     }
40
41     public void apply (MediaItem item) {
42     }
43 }
44
45 public class Rygel.HTTPPostTest : GLib.Object {
46     protected HTTPServer server;
47     protected HTTPClient client;
48
49     private bool server_done;
50     private bool client_done;
51
52     private MainLoop main_loop;
53
54     private Error error;
55
56     public static int main (string[] args) {
57         try {
58             var test = new HTTPPostTest ();
59
60             test.run ();
61         } catch (TestError.SKIP error) {
62             return error.code;
63         } catch (Error error) {
64             critical ("%s", error.message);
65
66             return -1;
67         }
68
69         return 0;
70     }
71
72     public HTTPPostTest () throws Error {
73         this.server = new HTTPServer ();
74         this.client = new HTTPClient (this.server.context,
75                                       this.server.uri);
76         this.main_loop = new MainLoop (null, false);
77     }
78
79     public virtual void run () throws Error {
80         // cleanup
81         var file = File.new_for_uri (MediaItem.URI);
82         FileUtils.remove (file.get_path ());
83         Timeout.add_seconds (3, this.on_timeout);
84         this.server.message_received.connect (this.on_message_received);
85         this.client.completed.connect (this.on_client_completed);
86
87         this.client.run.begin ();
88
89         this.main_loop.run ();
90
91         if (this.error != null) {
92             throw this.error;
93         }
94     }
95
96     private HTTPRequest create_request (Soup.Message msg) throws Error {
97         return new HTTPPost (this.server,
98                             this.server.context.server,
99                             msg);
100     }
101
102     private void on_client_completed (StateMachine client) {
103         this.client_done = true;
104         this.check_and_exit.begin ();
105     }
106
107     private void on_message_received (HTTPServer   server,
108                                       Soup.Message msg) {
109         this.handle_client_message.begin (msg);
110     }
111
112     private async void handle_client_message (Soup.Message msg) {
113         try {
114             var request = this.create_request (msg);
115
116             yield request.run ();
117
118             assert ((request as HTTPPost).item != null);
119
120             this.server_done = true;
121             this.check_and_exit.begin ();
122         } catch (Error error) {
123             this.error = error;
124             this.main_loop.quit ();
125
126             return;
127         }
128     }
129
130     private bool on_timeout () {
131         this.error = new TestError.TIMEOUT ("Timeout");
132         this.main_loop.quit ();
133
134         return false;
135     }
136
137     private async void check_and_exit () {
138         if (!(this.server_done && this.client_done)) {
139             return;
140         }
141
142         try {
143             var file = this.server.root_container.item.file;
144             var stream = yield file.read_async (Priority.HIGH, null);
145             var buffer = new uint8[HTTPClient.LENGTH];
146
147             yield stream.read_async (buffer, Priority.HIGH, null);
148
149             for (var i = 0; i < HTTPClient.LENGTH; i++) {
150                 assert (buffer[i] == this.client.content[i]);
151             }
152         } catch (Error error) {
153             this.error = error;
154         }
155
156         this.main_loop.quit ();
157     }
158 }
159
160 public class Rygel.HTTPServer : GLib.Object {
161     private const string SERVER_PATH = "/RygelHTTPServer/Rygel/Test";
162     public string path_root {
163         get {
164             return SERVER_PATH;
165         }
166     }
167
168     public MediaContainer root_container;
169     public GUPnP.Context context;
170
171     public string uri {
172         owned get {
173             var item_uri = new HTTPItemURI (this.root_container.item,
174                                             this);
175
176             return item_uri.to_string ();
177         }
178     }
179
180     public signal void message_received (Soup.Message message);
181
182     public HTTPServer () throws TestError {
183         try {
184             this.context = new GUPnP.Context (null, "lo", 0);
185         } catch (Error error) {
186             throw new TestError.SKIP ("Network context not available");
187         }
188
189         assert (this.context != null);
190         assert (this.context.host_ip != null);
191         assert (this.context.port > 0);
192
193         context.server.request_started.connect (this.on_request_started);
194
195         this.root_container = new MediaContainer ();
196     }
197
198     private void on_request_started (Soup.Server        server,
199                                      Soup.Message       msg,
200                                      Soup.ClientContext client) {
201         msg.got_headers.connect (this.on_got_headers);
202     }
203
204     private void on_got_headers (Soup.Message msg) {
205         this.message_received (msg);
206     }
207
208     public Transcoder get_transcoder (string target) throws Error {
209         if (target == "MP3") {
210             return new Transcoder ("mp3");
211         }
212         throw new HTTPRequestError.NOT_FOUND (
213                             "No transcoder available for target format '%s'",
214                             target);
215     }
216 }
217
218 public class Rygel.HTTPClient : GLib.Object, StateMachine {
219     public const size_t LENGTH = 1024;
220
221     public uint8[] content;
222
223     public GUPnP.Context context;
224     public Soup.Message msg;
225
226     public Cancellable cancellable { get; set; }
227
228     public HTTPClient (GUPnP.Context context,
229                        string        uri) {
230         this.context = context;
231         this.content = new uint8[1024];
232
233         this.msg = new Soup.Message ("POST",  uri);
234         assert (this.msg != null);
235     }
236
237     public async void run () {
238         SourceFunc run_continue = run.callback;
239
240         this.msg.request_body.append (MemoryUse.COPY, content);
241
242         this.context.session.queue_message (this.msg, (session, msg) => {
243             run_continue ();
244         });
245
246         yield;
247
248         this.completed ();
249     }
250 }
251
252 public class Rygel.MediaContainer : Rygel.MediaObject {
253     public const string ITEM_ID = "TestItem";
254
255     public signal void container_updated (MediaContainer container);
256
257     public string id = "TesContainer";
258     public MediaItem item;
259
260     private FileMonitor monitor;
261
262     public MediaContainer () {
263         this.item = new MediaItem (ITEM_ID, this);
264
265         this.monitor = this.item.file.monitor_file (FileMonitorFlags.NONE);
266         this.monitor.changed.connect (this.on_file_changed);
267     }
268
269     public async MediaObject? find_object (string       item_id,
270                                            Cancellable? cancellable)
271                                            throws Error {
272         SourceFunc find_object_continue = find_object.callback;
273         Idle.add (() => {
274             find_object_continue ();
275
276             return false;
277         });
278
279         yield;
280
281         if (item_id == ITEM_ID) {
282             return this.item;
283         } else {
284             return null;
285         }
286     }
287
288     public void on_file_changed (FileMonitor      monitor,
289                                  File             file,
290                                  File?            other_file,
291                                  FileMonitorEvent event_type) {
292         this.item.place_holder = false;
293         this.container_updated (this);
294     }
295 }
296
297 public class Rygel.MediaItem : Rygel.MediaObject {
298     public const string URI = "file:///tmp/rygel-upload-test.wav";
299
300     public weak MediaContainer parent;
301
302     public string id;
303     public long size = 1024;
304     public long duration = 1024;
305     public ArrayList<string> uris = new ArrayList<string> ();
306
307     public bool place_holder = true;
308
309     public File file;
310
311     public MediaItem.for_visual_item () {}
312
313     public MediaItem (string id, MediaContainer parent) {
314         this.id = id;
315         this.parent = parent;
316
317         this.file = File.new_for_uri (URI);
318     }
319
320     ~MediaItem() {
321         try {
322             this.file.delete (null);
323         } catch (GLib.Error error) {
324             assert_not_reached ();
325         }
326     }
327
328     public async File? get_writable (Cancellable? cancellable) throws Error {
329         SourceFunc get_writable_continue = get_writable.callback;
330
331         Idle.add (() => {
332             get_writable_continue ();
333
334             return false;
335         });
336
337         yield;
338
339         return this.file;
340     }
341 }
342
343 internal class Rygel.HTTPResponse : Rygel.StateMachine, GLib.Object {
344     public Cancellable cancellable { get; set; }
345
346     private Soup.Message msg;
347     private Soup.Server server;
348
349     public HTTPResponse (HTTPPost get_request) {
350         this.msg = get_request.msg;
351         this.server = get_request.server;
352     }
353
354     public async void run () {
355         SourceFunc run_continue = run.callback;
356
357         Idle.add (() => {
358             run_continue ();
359
360             return false;
361         });
362
363         yield;
364
365         this.msg.set_status (Soup.KnownStatusCode.OK);
366         this.server.unpause_message (msg);
367
368         this.completed ();
369     }
370 }
371
372 public class Rygel.ItemRemovalQueue: GLib.Object {
373     public static ItemRemovalQueue get_default () {
374        return new ItemRemovalQueue ();
375     }
376
377     public bool dequeue (MediaItem item) {
378         return true;
379     }
380
381     public async void remove_now (MediaItem item, Cancellable? cancellable) {
382         Idle.add (remove_now.callback);
383
384         yield;
385     }
386 }
387
388 public class Rygel.MediaObject : GLib.Object {
389     public string mime_type = "";
390 }
391
392 public class Rygel.Thumbnail : GLib.Object {
393     public string file_extension;
394 }
395
396 public class Rygel.VisualItem : Rygel.MediaItem {
397     public ArrayList<Thumbnail> thumbnails = new ArrayList<Thumbnail> ();
398
399     public VisualItem () {
400         base.for_visual_item();
401     }
402 }
403
404 private class Rygel.Subtitle : GLib.Object {
405     public string caption_type;
406 }
407
408 private class Rygel.VideoItem : Rygel.VisualItem {
409     public ArrayList<Subtitle> subtitles = new ArrayList<Subtitle> ();
410 }
411
412 public errordomain Rygel.ContentDirectoryError {
413     INVALID_ARGS = 402
414 }
415
416 public class Rygel.Transcoder : GLib.Object {
417     public string extension { get; protected set; }
418
419     public Transcoder (string extension) {
420         this.extension = extension;
421     }
422 }