/* 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);
}
void on_got_chunk (Buffer chunk) {
weak string found;
if (poster_uri != null)
return;
// 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 signal void got_poster_uri (GoogleImageSearch search);
}
// 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";
}
private void on_got_poster_uri (GoogleImageSearch message) {
session.cancel_message (message, Soup.KnownStatusCode.OK);
}
private void google_search_finished (Session session, Message message) {
if (cancelled)
return;
var search_message = (GoogleImageSearch) message;
print ("Finished search: %s\n", message.uri.to_string (false));
var poster_message = new Soup.Message ("GET", search_message.poster_uri);
session.queue_message (poster_message, poster_downloaded);
}
private void poster_downloaded (Session session, Message message) {
if (cancelled)
return;
print ("Downloaded poster: %s\n", message.uri.to_string (false));
// 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);
// Make sure the directory .album_arts is available
DirUtils.create_with_parents (cache_dir, 0770);
try {
var file = File.new_for_path (cache_path + ".part");
var stream = file.create (FileCreateFlags.NONE, null);
stream.write (message.response_body.data, (size_t) message.response_body.length, null);
FileUtils.rename (cache_path + ".part", cache_path);
print ("Stored as: %s\n", cache_path);
downloader.fetched (handle, cache_path);
downloader.timeout_quit ();
} catch (Error e) {
stdout.printf ("Failed to store poster: %s\n", e.message);
}
}
public void cancel () {
cancelled = true;
}
}
// 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;
public GooglePosterDownloader () {
loop = new MainLoop (null);
session = new SessionAsync ();
session.max_conns_per_host = 7;
}
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);
}
private bool quit () {
loop.quit ();
// One-shot only
return false;
}
public void run () {
loop.run ();
}
// 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);
downloads.append (download);
return fetch_handle;
}
public int FetchThumbnail (string title, string year, string kind) throws DBus.Error {
var download = new GooglePosterDownload (title, year, true, ++fetch_handle, this);
downloads.append (download);
return fetch_handle;
}
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;
}
}
if (download != null) {
downloads.remove (download);
}
}
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);
}
}
}