ui: Added support for fetching movie images
authorSimón Pena <spenap@gmail.com>
Sun, 20 Jun 2010 12:22:09 +0000 (14:22 +0200)
committerSimón Pena <spenap@gmail.com>
Sun, 20 Jun 2010 12:22:09 +0000 (14:22 +0200)
Initial support for fetching images is added.

* AsyncWorker and ImageDownloader have been taken from SeriesFinale.
They allow asynchronously fetching the images from the Internet
using the url provided from DBus
* The UI displays a placeholder image while the right one is being
fetched
* Images downloaded aren't cached, they are download in /tmp/
* Asynchronous threads aren't canceled if we leave the movie view
before the image is fetched.

ui/maeviesui/maeviesui/gui.py
ui/maeviesui/util/asyncworker.py [new file with mode: 0644]
ui/maeviesui/util/constants.py
ui/maeviesui/util/moviemanager.py
ui/maeviesui/util/util.py [new file with mode: 0644]

index beb191f..ae1393e 100644 (file)
@@ -19,6 +19,7 @@
 ###########################################################################
 
 import pygtk
+import os
 pygtk.require('2.0')
 import gtk
 import hildon
@@ -26,6 +27,8 @@ import pango
 import gobject
 
 from maeviesui.util import constants
+from maeviesui.util.asyncworker import AsyncWorker, AsyncItem
+from maeviesui.util.util import image_downloader
 from maeviesui.util.moviemanager import MovieManager
 
 class Maevies(hildon.StackableWindow):
@@ -222,7 +225,8 @@ class MoviesView(gtk.TreeView):
 
     def add_movies(self, movie_list):
         model = self.get_model()
-        model.add(movie_list)
+        if model:
+            model.add(movie_list)
 
     def get_movie_from_path(self, path):
         model = self.get_model()
@@ -259,6 +263,30 @@ class AboutDialog(gtk.Dialog):
 
 class MovieWindow(hildon.StackableWindow):
 
+    def _create_movie_image(self, movie):
+        image = gtk.Image()
+        image.set_from_pixbuf(gtk.IconTheme().load_icon('general_video',
+                                                        256, 0))
+        banner = hildon.hildon_banner_show_information_with_markup(self,
+                                                                   'ignored',
+                                                                   'Fetching movie cover')
+        banner.set_timeout(constants.TIMEOUT_TIME_MILLIS)
+        hildon.hildon_gtk_window_set_progress_indicator(self, True)
+
+        async_item = AsyncItem(image_downloader, (movie.get_images()[0].get_url(), '/tmp/' + movie.get_title()),
+                               self._set_downloaded_image, (image,))
+        self.async_worker.queue.put(async_item)
+        self.async_worker.start()
+
+        return image
+
+    def _set_downloaded_image(self, image, target, error):
+        image_file = os.path.abspath(target)
+        image.set_from_pixbuf(gtk.gdk.pixbuf_new_from_file_at_size(image_file,
+                                                                   256,
+                                                                   256))
+        hildon.hildon_gtk_window_set_progress_indicator(self, False)
+
     def _create_contents(self, movie):
         main_area = hildon.PannableArea()
 
@@ -267,9 +295,7 @@ class MovieWindow(hildon.StackableWindow):
         upper_content = gtk.HBox(False, 40)
         upper_content.set_border_width(20)
 
-        image = gtk.Image()
-        image.set_from_pixbuf(gtk.IconTheme().load_icon('mediaplayer_default_album',
-                                                        256, 0))
+        image = self._create_movie_image(movie)
 
         side_content = gtk.VBox(False, 30)
 
@@ -306,6 +332,7 @@ class MovieWindow(hildon.StackableWindow):
 
     def __init__(self, movie):
         super(MovieWindow, self).__init__()
+        self.async_worker = AsyncWorker()
         self.set_title('Movie info')
         self.add(self._create_contents(movie))
         self.show_all()
diff --git a/ui/maeviesui/util/asyncworker.py b/ui/maeviesui/util/asyncworker.py
new file mode 100644 (file)
index 0000000..4deb97c
--- /dev/null
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+
+###########################################################################
+#    SeriesFinale
+#    Copyright (C) 2009 Joaquim Rocha <jrocha@igalia.com>
+# 
+#    This program 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.
+#
+#    This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+###########################################################################
+
+from threading import Thread
+import Queue
+import gobject
+import logging
+logging.basicConfig(level=logging.DEBUG)
+
+class AsyncItem(object):
+
+    def __init__(self, target_method, target_method_args, finish_callback=None, finish_callback_args=()):
+        self.target_method = target_method
+        self.target_method_args = target_method_args
+        self.finish_callback = finish_callback
+        self.finish_callback_args = finish_callback_args
+        self.canceled = False
+
+    def run(self):
+        if self.canceled:
+            return
+        results = error = None
+        try:
+            results = self.target_method(*self.target_method_args)
+        except Exception, exception:
+            logging.debug(str(exception))
+            error = exception
+        if self.canceled or not self.finish_callback:
+            return
+        self.finish_callback_args += (results,)
+        self.finish_callback_args += (error,)
+        gobject.idle_add(self.finish_callback, *self.finish_callback_args)
+
+    def cancel(self):
+        self.canceled = True
+
+class AsyncWorker(Thread):
+
+    def __init__(self):
+        Thread.__init__(self)
+        self.queue = Queue.Queue(0)
+        self.stopped = False
+        self.async_item = None
+        self.item_number = -1
+
+    def run(self):
+        while not self.stopped:
+            if self.queue.empty():
+                self.stop()
+                break
+            try:
+                self.async_item = self.queue.get()
+                self.item_number += 1
+                self.async_item.run()
+                self.queue.task_done()
+                self.async_item = None
+            except Exception, exception:
+                logging.debug(str(exception))
+                self.stop()
+
+    def stop(self):
+        self.stopped = True
+        if self.async_item:
+            self.async_item.cancel()
+
index 988c1c0..d08f6e1 100644 (file)
@@ -25,7 +25,7 @@ LOCAL_DATA_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__),
                                               os.pardir,
                                               os.pardir,
                                               'data'))
-TIMEOUT_TIME_MILLIS = 500
+TIMEOUT_TIME_MILLIS = 1000
 LEFT_ALIGNMENT = 0
 CENTER_ALIGNMENT = 0.5
 LOREM_IPSUM = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. \
index d5148c9..8cfc617 100644 (file)
@@ -71,12 +71,29 @@ class MovieManager:
         if self.response_received_cb:
             self.response_received_cb(movies)
 
+class MovieImage:
+    def __init__(self, image_struct):
+        self._type, self._url, self._size, self._id = image_struct
+
+    def get_url(self):
+        return self._url
+
+    def get_id(self):
+        return self._id
+
+    def get_size(self):
+        return self._size
+
+    def get_type(self):
+        return self._type
+
 class MovieProxy:
 
     def __init__(self, bus, object_path):
         self._bus = bus
         self.interface = self._create_movie_interface(object_path)
         self.fields = ['Title', 'Release date', 'Rating', 'Popularity']
+        self._images = self._retrieve_images()
 
     def _create_movie_interface(self, object_path):
         proxy = self._bus.get_object(TMDB_MOVIE_BUS_NAME,
@@ -85,6 +102,13 @@ class MovieProxy:
                                    dbus_interface=TMDB_MOVIE_INTERFACE)
         return interface
 
+    def _retrieve_images(self):
+        images = []
+        dbus_images = self.interface.GetImages()
+        for image in dbus_images:
+            images.append(MovieImage(image))
+        return images
+
     def get_value(self, field):
         if field == 'Title':
             return self.get_title()
@@ -106,6 +130,9 @@ class MovieProxy:
     def get_rating(self):
         return self.interface.GetRating()
 
+    def get_images(self):
+        return self._images
+
     def get_released(self):
         return self.interface.GetReleased()
 
diff --git a/ui/maeviesui/util/util.py b/ui/maeviesui/util/util.py
new file mode 100644 (file)
index 0000000..a1cc206
--- /dev/null
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+
+###########################################################################
+#    Maevies
+#    Copyright (C) 2010 Simón Pena <spenap@gmail.com>
+#
+#    This program 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.
+#
+#    This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+###########################################################################
+
+import urllib
+import os
+
+def image_downloader(url, save_name):
+    image = urllib.URLopener()
+    path, format = os.path.splitext(url)
+    target = save_name + format
+    temp_target = target + '.tmp'
+    image.retrieve(url, temp_target)
+    os.rename(temp_target, target)
+    return target