X-Git-Url: http://git.maemo.org/git/?a=blobdiff_plain;f=src%2Fposter%2Fgoogle-poster-downloader.vala;h=423d017b0b4719df5f9eba1ce8a0434632b1c472;hb=6468272248974610829f2301ddc87e24c4d9f87f;hp=5dee8c2b75496019547a09af4eff47a175a88bb5;hpb=52b7b48e0102fc22f46c8b67f6df6a6d7b38a2f9;p=cinaest diff --git a/src/poster/google-poster-downloader.vala b/src/poster/google-poster-downloader.vala index 5dee8c2..423d017 100644 --- a/src/poster/google-poster-downloader.vala +++ b/src/poster/google-poster-downloader.vala @@ -1,223 +1,234 @@ -using GLib; - -[DBus (name = "org.maemo.movieposter.Provider", signals = "fetched")] -public interface Provider { - public abstract int Fetch (string title, string year, string kind) throws DBus.Error; - public abstract int FetchThumbnail (string title, string year, string kind) throws DBus.Error; - public abstract void Unqueue (int handle) throws DBus.Error; - public signal void fetched (int handle, string path); -} - -// http://live.gnome.org/MediaArtStorageSpec - -// Sample implementation of org.maemo.movieposter.Provider that uses Google -// images's first result as movie poster. There is of course no certainty -// that the first result on Google images is indeed the movie's poster - there -// are quite a few false positives, especially for non-mainstream movies. +/* This file is part of Cinaest. + * + * Copyright (C) 2009 Philipp Zabel + * + * Cinaest is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Cinaest is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Cinaest. If not, see . + */ + +using Soup; + +// A single google image search (parsing and retrieval of the poster image URI) +public class GoogleImageSearch : Soup.Message { + public string poster_uri = null; + private bool thumbnail; + private string last_chunk = null; + + public GoogleImageSearch (string search, bool _thumbnail = false) { + Object (method: "GET"); + uri = new URI ("http://images.google.com/images?imgsz=m&imgar=t&q=" + Uri.escape_string (search, "+", false)); + thumbnail = _thumbnail; + got_chunk.connect (this.on_got_chunk); + } -public class GoogleImages : Object, Provider { - int fetch_handle; - Cancellable cancellable; + void on_got_chunk (Buffer chunk) { + weak string found; - public int Fetch (string title, string year, string kind) throws DBus.Error { - int handle = fetch_handle++; + if (poster_uri != null) + return; - _fetch_async.begin (title, year, kind, false, handle); - return handle; + // FIXME - store a copy of the last chunk and retry if the parser fails, it can't handle partial input + if (last_chunk == null) { + // FIXME - can we avoid this copy without ugly hacks? + found = chunk.data.ndup (chunk.length).str ("dyn.setResults([["); + } else { + found = (last_chunk + chunk.data).str ("dyn.setResults([["); + last_chunk = null; + } + if (found != null) { + var parser = new GoogleImageSearchParser (found); + try { + poster_uri = parser.run (thumbnail); + if (poster_uri != null) + got_poster_uri (this); + } catch (Error e) { + if (e is ParserError.EOF) { + last_chunk = chunk.data.ndup (chunk.length); + } else { + stdout.printf ("Parser error: %s\n", e.message); + } + } + } } - public int FetchThumbnail (string title, string year, string kind) throws DBus.Error { - int handle = fetch_handle++; + signal void got_poster_uri (GoogleImageSearch search); +} - _fetch_async.begin (title, year, kind, true, handle); - return handle; +// Encapsulation of a single poster download (google image search query and image file download) +public class GooglePosterDownload : Object { + private GooglePosterDownloader downloader; + private Soup.Session session; + public int handle; + private string cache_dir; + private string cache_filename; + private bool cancelled = false; + + public GooglePosterDownload (string title, string year, bool thumbnail, int _handle, GooglePosterDownloader _downloader) { + var search = title + "+" + year + "+movie+poster"; + + handle = _handle; + downloader = _downloader; + session = downloader.session; + + var message = new GoogleImageSearch (search, thumbnail); + message.got_poster_uri.connect (on_got_poster_uri); + session.queue_message (message, google_search_finished); + + if (thumbnail) { + // FIXME + cache_dir = Path.build_filename (Environment.get_tmp_dir(), "cinaest-thumbnails"); + } else { + cache_dir = Path.build_filename (Environment.get_user_cache_dir(), "media-art"); + } + cache_filename = "movie-" + + Checksum.compute_for_string (ChecksumType.MD5, (title).down ()) + "-" + + Checksum.compute_for_string (ChecksumType.MD5, (year).down ()) + ".jpeg"; } - public void Unqueue (int handle) { - // FIXME - cancel everything for now - cancellable.cancel (); + private void on_got_poster_uri (GoogleImageSearch message) { + session.cancel_message (message, Soup.KnownStatusCode.OK); } - private async void _fetch_async (string title, string year, string kind, bool thumbnail, int handle) { - uint u = 0; - size_t hread = 0; - string [] pieces = title.split (" ", -1); - string stitched = ""; - bool first = true; + private void google_search_finished (Session session, Message message) { + if (cancelled) + return; - // FIXME - cancel everything for now - cancellable = new Cancellable (); + var search_message = (GoogleImageSearch) message; + print ("Finished search: %s\n", message.uri.to_string (false)); - stdout.printf ("Fetching %s \"%s (%s)\" ...\n", kind, title, year); + var poster_message = new Soup.Message ("GET", search_message.poster_uri); + session.queue_message (poster_message, poster_downloaded); + } - if (title == null || title == "") - title = " "; + private void poster_downloaded (Session session, Message message) { + if (cancelled) + return; - if (year == null || year == "") - year = " "; + print ("Downloaded poster: %s\n", message.uri.to_string (false)); - // Convert the title and year into something that will work for Google images + // Define cache path according to the Media Art Storage Spec (http://live.gnome.org/MediaArtStorageSpec) + string cache_path = Path.build_filename (cache_dir, cache_filename); - while (pieces[u] != null) { - if (!first) - stitched += "+"; - stitched += pieces[u]; - u++; - first = false; - } + // Make sure the directory .album_arts is available + DirUtils.create_with_parents (cache_dir, 0770); - stitched += "+"; + try { + var file = File.new_for_path (cache_path + ".part"); + var stream = file.create (FileCreateFlags.NONE, null); - u = 0; - first = true; - pieces = year.split (" ", -1); + stream.write (message.response_body.data, (size_t) message.response_body.length, null); - while (pieces[u] != null) { - if (!first) - stitched += "+"; - stitched += pieces[u]; - u++; - first = false; - } + FileUtils.rename (cache_path + ".part", cache_path); - stitched += "+movie+poster"; + print ("Stored as: %s\n", cache_path); - // Start the query on Google images + downloader.fetched (handle, cache_path); + downloader.timeout_quit (); + } catch (Error e) { + stdout.printf ("Failed to store poster: %s\n", e.message); + } + } - stdout.printf("GET http://images.google.com/images?q=" + Uri.escape_string (stitched, "+", false) + "\n"); + public void cancel () { + cancelled = true; + } +} - File google_search = File.new_for_uri ("http://images.google.com/images?q=" + Uri.escape_string (stitched, "+", false)); +// The D-Bus service to manage poster downloads +public class GooglePosterDownloader : Object, PosterDownloader { + private MainLoop loop; + public SessionAsync session; + private int fetch_handle = 1; + private List downloads = null; + private uint source_id; - try { - char [] buffer = new char [40000]; - string asstring; - size_t total = 0; + public GooglePosterDownloader () { + loop = new MainLoop (null); - // Fetch the first page + session = new SessionAsync (); + session.max_conns_per_host = 7; + } - InputStream stream = google_search.read (null); + public void timeout_quit () { + // With every change we reset the timer to 3min + if (source_id != 0) { + Source.remove (source_id); + } + source_id = Timeout.add_seconds (180, quit); + } - while (total < 40000) { - hread = yield stream.read_async ((char *)buffer + total, 40000 - total, Priority.DEFAULT_IDLE, cancellable); - total += hread; - if (cancellable.is_cancelled ()) { - stdout.printf ("CANCELED\n"); - return; - } - if (hread == 0) - break; - } - buffer[total] = 0; - - asstring = (string) buffer; - - // Find the first result - - string found = null; - int i; - char end; - if (thumbnail) { - do { - found = asstring.str ("http://t"); - } while (found != null && !found.offset (9).has_prefix (".gstatic.com/images?q=tbn")); - i = 0; - end = ' '; - } else { - found = asstring.str ("href=/imgres?imgurl="); - i = 20; - end = '&'; - } + private bool quit () { + loop.quit (); - if (found != null) { + // One-shot only + return false; + } - StringBuilder url = new StringBuilder (); - long y = found.len(); + public void run () { + loop.run (); + } - while (found[i] != end && i < y) { - url.append_unichar (found[i]); - i++; - } + // Implement the PosterDownloader interface + public int Fetch (string title, string year, string kind) throws DBus.Error { + var download = new GooglePosterDownload (title, year, false, ++fetch_handle, this); - string cache_path; + downloads.append (download); - string cache_dir; - if (thumbnail) { - // FIXME - cache_dir = Path.build_filename (Environment.get_tmp_dir(), "cinaest-thumbnails"); - } else { - cache_dir = Path.build_filename (Environment.get_user_cache_dir(), "media-art"); - } + return fetch_handle; + } - // Define cache path = ~/.album_art/MD5 (down (albumartist)).jpeg - - cache_path = Path.build_filename (cache_dir, kind + "-" + - Checksum.compute_for_string ( - ChecksumType.MD5, - (title).down (), - -1) + "-" + - Checksum.compute_for_string ( - ChecksumType.MD5, - (year).down (), - -1) + - ".jpeg", null); - - // Make sure the directory .album_arts is available - DirUtils.create_with_parents (cache_dir, 0770); - - stdout.printf ("GET %s --> %s\n", url.str, cache_path); - - File online_image = File.new_for_uri (url.str); - File cache_image = File.new_for_path (cache_path + ".part"); - - // Copy from Google images to local cache - - yield online_image.copy_async (cache_image, - FileCopyFlags.NONE, - Priority.DEFAULT_IDLE, - cancellable, - null); - if (cancellable.is_cancelled ()) { - stdout.printf ("CANCELED\n"); - return; - } + public int FetchThumbnail (string title, string year, string kind) throws DBus.Error { + var download = new GooglePosterDownload (title, year, true, ++fetch_handle, this); - FileUtils.rename (cache_path + ".part", cache_path); + downloads.append (download); - fetched (handle, cache_path); + return fetch_handle; + } - stdout.printf ("DONE\n"); - } else { - stdout.printf ("NOT FOUND\n"); - // stdout.printf ("%s\n", asstring); + public void Unqueue (int handle) throws DBus.Error { + GooglePosterDownload download = null; + foreach (GooglePosterDownload d in downloads) { + if (d.handle == handle) { + download = d; + d.cancel (); + break; } - - } catch (GLib.Error error) { - stderr.printf ("Error: %s\n", error.message); + } + if (download != null) { + downloads.remove (download); } } -} - -void main () { - var loop = new MainLoop (null, false); - - try { - var conn = DBus.Bus.get (DBus.BusType.SESSION); - dynamic DBus.Object bus = conn.get_object ("org.freedesktop.DBus", - "/org/freedesktop/DBus", - "org.freedesktop.DBus"); - - // Try to register service in session bus - uint request_name_result = bus.request_name ("org.maemo.movieposter.GoogleImages", (uint) 0); - - if (request_name_result == DBus.RequestNameReply.PRIMARY_OWNER) { - // Start server - var server = new GoogleImages (); - conn.register_object ("/org/maemo/movieposter/GoogleImages", server); - - loop.run (); + static void main () { + try { + var conn = DBus.Bus.get (DBus.BusType.SESSION); + dynamic DBus.Object bus = conn.get_object ("org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus"); + + // Try to register service in session bus + uint res = bus.request_name ("org.maemo.movieposter.GoogleImages", (uint) 0); + if (res == DBus.RequestNameReply.PRIMARY_OWNER) { + // Start server + var server = new GooglePosterDownloader (); + conn.register_object ("/org/maemo/movieposter/GoogleImages", server); + + server.timeout_quit (); + server.run (); + } + } catch (Error e) { + error ("Oops: %s\n", e.message); } - } catch (Error e) { - error ("Oops: %s\n", e.message); } } +