conduit r1267 - in trunk: . conduit/modules conduit/modules/GoogleModule conduit/modules/YouTubeModule
- From: thomasvm svn gnome org
- To: svn-commits-list gnome org
- Subject: conduit r1267 - in trunk: . conduit/modules conduit/modules/GoogleModule conduit/modules/YouTubeModule
- Date: Mon, 28 Jan 2008 07:22:51 +0000 (GMT)
Author: thomasvm
Date: Mon Jan 28 07:22:50 2008
New Revision: 1267
URL: http://svn.gnome.org/viewvc/conduit?rev=1267&view=rev
Log:
2008-01-28 Thomas Van Machelen <thomas vanmachelen gmail com>:
* conduit/modules/GoogleModule/youtube-config.glade:
* conduit/modules/GoogleModule/Makefile.am:
* conduit/modules/GoogleModule/GoogleModule.py: move youtube dataprovider
to GoogleModule, and make it use gdata api (original patch by Renato Araujo
Oliveira Filho)
* conduit/modules/Makefile.am: deleted and moved to GoogleModule
* conduit/modules/YouTubeModule/*: deleted and moved to GoogleModule
Added:
trunk/conduit/modules/GoogleModule/youtube-config.glade
Removed:
trunk/conduit/modules/YouTubeModule/Makefile.am
trunk/conduit/modules/YouTubeModule/YouTubeModule.py
trunk/conduit/modules/YouTubeModule/config.glade
Modified:
trunk/ChangeLog
trunk/conduit/modules/GoogleModule/GoogleModule.py
trunk/conduit/modules/GoogleModule/Makefile.am
trunk/conduit/modules/Makefile.am
Modified: trunk/conduit/modules/GoogleModule/GoogleModule.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/GoogleModule.py (original)
+++ trunk/conduit/modules/GoogleModule/GoogleModule.py Mon Jan 28 07:22:50 2008
@@ -14,9 +14,12 @@
from conduit.datatypes import Rid
import conduit.datatypes.Event as Event
import conduit.datatypes.Photo as Photo
+import conduit.datatypes.Video as Video
from gettext import gettext as _
+import re
+
#Distributors, if you ship python gdata >= 1.0.10 then remove this line
#and the appropriate directories
Utils.dataprovider_add_dir_to_path(__file__)
@@ -33,7 +36,8 @@
MODULES = {
"GoogleCalendarTwoWay" : { "type": "dataprovider" },
- "PicasaTwoWay" : { "type": "dataprovider" }
+ "PicasaTwoWay" : { "type": "dataprovider" },
+ "YouTubeSource" : { "type": "dataprovider" },
}
@@ -684,4 +688,192 @@
def get_UID(self):
return self.username
-
+class YouTubeSource(DataProvider.DataSource):
+ _name_ = _("YouTube")
+ _description_ = _("Sync data from YouTube")
+ _category_ = conduit.dataproviders.CATEGORY_MISC
+ _module_type_ = "source"
+ _out_type_ = "file/video"
+ _icon_ = "youtube"
+
+ _const_users_feed = "http://gdata.youtube.com/feeds/users"
+ _const_std_feeds = "http://gdata.youtube.com/feeds/standardfeeds"
+ _video_name_re = re.compile(r', "t": "([^"]+)"')
+
+ #Config args
+ max = 0
+ #filter type {0 = mostviewed, 1 = toprated, 2 = user}
+ filter_type = 0
+ #filter user type {0 = upload, 1 = favorites}
+ user_filter_type = 0
+ username = ""
+
+
+ def __init__(self, *args):
+ DataProvider.DataSource.__init__(self)
+ self.entries = None
+
+ def initialize(self):
+ return True
+
+ def configure(self, window):
+ tree = Utils.dataprovider_glade_get_widget (
+ __file__,
+ "youtube-config.glade",
+ "YouTubeSourceConfigDialog")
+
+ dlg = tree.get_widget ("YouTubeSourceConfigDialog")
+ mostviewedRb = tree.get_widget("mostviewed")
+ topratedRb = tree.get_widget("toprated")
+ byuserRb = tree.get_widget("byuser")
+ user_frame = tree.get_widget("frame")
+ uploadedbyRb = tree.get_widget("uploadedby")
+ favoritesofRb = tree.get_widget("favoritesof")
+ user = tree.get_widget("user")
+ maxdownloads = tree.get_widget("maxdownloads")
+
+ byuserRb.connect("toggled", self._filter_user_toggled_cb, user_frame)
+
+ if self.filter_type == 0:
+ mostviewedRb.set_active(True)
+ elif self.filter_type == 1:
+ topratedRb.set_active(True)
+ else:
+ byuserRb.set_active(True)
+ user_frame.set_sensitive(True)
+ if self.user_filter_type == 0:
+ uploadedbyRb.set_active(True)
+ else:
+ favoritesofRb.set_active(True)
+ user.set_text(self.username)
+
+ log.debug("Max")
+ log.debug(self.max)
+ maxdownloads.set_value(self.max)
+
+ response = Utils.run_dialog(dlg, window)
+ if response == True:
+ if mostviewedRb.get_active():
+ self.filter_type = 0
+ elif topratedRb.get_active():
+ self.filter_type = 1
+ else:
+ self.filter_type = 2
+ if uploadedbyRb.get_active():
+ self.user_filter_type = 0
+ else:
+ self.user_filter_type = 1
+ self.username = user.get_text()
+ self.max = int(maxdownloads.get_value())
+
+ dlg.destroy()
+
+ def refresh(self):
+ DataProvider.DataSource.refresh(self)
+
+ self.entries = {}
+ try:
+ feedUrl = ""
+ if self.filter_type == 0:
+ videos = self._most_viewed ()
+ elif self.filter_type == 1:
+ videos = self._top_rated ()
+ else:
+ if self.usr_filter_type == 0:
+ videos = self._videos_upload_by (self.username)
+ else:
+ videos = self._favorite_videos (self.username)
+
+ for video in videos:
+ self.entries[video.title.text] = self._get_flv_video_url (video.link[1].href)
+ except Exception, err:
+ log.debug("Error getting/parsing feed (%s)" % err)
+ raise Exceptions.RefreshError
+
+ def get_all(self):
+ return self.entries.keys()
+
+ def get(self, LUID):
+ DataProvider.DataSource.get(self, LUID)
+ url = self.entries[LUID]
+ log.debug("Title: '%s', Url: '%s'"%(LUID, url))
+
+ f = Video.Video(URI=url)
+ f.set_open_URI(url)
+ f.set_UID(LUID)
+ f.force_new_filename (str(LUID) + ".flv")
+
+ return f
+
+ def finish(self, aborted, error, conflict):
+ DataProvider.DataSource.finish(self)
+ self.files = None
+
+ def get_configuration(self):
+ return {
+ "filter_type" : self.filter_type,
+ "user_filter_type" : self.user_filter_type,
+ "username" : self.username,
+ "max" : self.max
+ }
+
+
+ def get_UID(self):
+ return Utils.get_user_string()
+
+ #ui callbacks
+ def _filter_user_toggled_cb (self, toggle, frame):
+ frame.set_sensitive(toggle.get_active())
+
+
+ """
+ Code based on youtube client from : Philippe Normand (phil at base-art dot net)
+ """
+ def _format_url (self, url):
+ if self.max > 0:
+ url = ("%s?max-results=%d" % (url, self.max))
+
+ return url
+
+ def _request(self, feed, *params):
+ service = gdata.service.GDataService(server="gdata.youtube.com")
+ return service.Get(feed % params)
+
+ def _top_rated(self):
+ url = self._format_url ("%s/top_rated" % self._const_std_feeds)
+ return self._request(url).entry
+
+ def _most_viewed(self):
+ url = self._format_url ("%s/most_viewed" % self._const_std_feeds)
+ return self._request(url).entry
+
+ def _videos_upload_by(self, username):
+ url = self._format_url ("%s/%s/uploads" % (self._const_users_feed, username))
+ return self._request(url).entry
+
+ def _favorite_videos(self, username):
+ url = self._format_url ("%s/%s/favorites" % (self._const_users_feed, username))
+ return self._request(url).entry
+
+
+ # Generic extract step
+ def _get_flv_video_url (self, url):
+ import urllib2
+ flv_url = ''
+ doc = urllib2.urlopen(url)
+ data = doc.read()
+
+ # extract video name
+ match = self._video_name_re.search(data)
+ if match is not None:
+ video_name = match.group(1)
+
+ # extract video id
+ url_splited = url.split("watch?v=")
+ video_id = url_splited[1]
+
+ flv_url = "http://www.youtube.com/get_video?video_id=%s&t=%s"
+ flv_url = flv_url % (video_id, video_name)
+
+ return flv_url
+
Modified: trunk/conduit/modules/GoogleModule/Makefile.am
==============================================================================
--- trunk/conduit/modules/GoogleModule/Makefile.am (original)
+++ trunk/conduit/modules/GoogleModule/Makefile.am Mon Jan 28 07:22:50 2008
@@ -3,8 +3,8 @@
conduit_handlersdir = $(libdir)/conduit/modules/GoogleModule
conduit_handlers_PYTHON = GoogleModule.py
-conduit_handlers_DATA = calendar-config.glade picasa-config.glade
-EXTRA_DIST = calendar-config.glade picasa-config.glade
+conduit_handlers_DATA = calendar-config.glade picasa-config.glade youtube-config.glade
+EXTRA_DIST = calendar-config.glade picasa-config.glade youtube-config.glade
clean-local:
rm -rf *.pyc *.pyo
Added: trunk/conduit/modules/GoogleModule/youtube-config.glade
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/youtube-config.glade Mon Jan 28 07:22:50 2008
@@ -0,0 +1,422 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkDialog" id="YouTubeSourceConfigDialog">
+ <property name="border_width">5</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="title" translatable="yes">YouTube Source</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+ <property name="modal">False</property>
+ <property name="resizable">False</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="urgency_hint">False</property>
+ <property name="has_separator">True</property>
+
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">2</property>
+
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+
+ <child>
+ <placeholder/>
+ </child>
+
+ <child>
+ <widget class="GtkButton" id="button2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-ok</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="response_id">-5</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkButton" id="button1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-cancel</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="response_id">-6</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkTable" id="table1">
+ <property name="visible">True</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">5</property>
+ <property name="column_spacing">5</property>
+
+ <child>
+ <widget class="GtkSpinButton" id="maxdownloads">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="climb_rate">1</property>
+ <property name="digits">0</property>
+ <property name="numeric">False</property>
+ <property name="update_policy">GTK_UPDATE_ALWAYS</property>
+ <property name="snap_to_ticks">False</property>
+ <property name="wrap">False</property>
+ <property name="adjustment">0 0 100 1 10 10</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Max retrieved videos (0 is unlimited):</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkFrame" id="frame1">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="label_yalign">0.5</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+
+ <child>
+ <widget class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">0</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">0</property>
+
+ <child>
+ <widget class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">5</property>
+
+ <child>
+ <widget class="GtkRadioButton" id="mostviewed">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Most viewed</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">True</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkRadioButton" id="toprated">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Top rated</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">mostviewed</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkVBox" id="vbox3">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">5</property>
+
+ <child>
+ <widget class="GtkRadioButton" id="byuser">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">By user</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">mostviewed</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkFrame" id="frame">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="label_xalign">0</property>
+ <property name="label_yalign">0.5</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+
+ <child>
+ <widget class="GtkAlignment" id="alignment2">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">0</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">0</property>
+
+ <child>
+ <widget class="GtkTable" id="table2">
+ <property name="visible">True</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">5</property>
+ <property name="column_spacing">5</property>
+
+ <child>
+ <widget class="GtkHBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkRadioButton" id="uploadedby">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Uploaded by</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">True</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkRadioButton" id="favoritesof">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Favorites of</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">uploadedby</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">User: </property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="user">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">â</property>
+ <property name="activates_default">False</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options"></property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"><b>Donwload Videos</b></property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+</glade-interface>
Modified: trunk/conduit/modules/Makefile.am
==============================================================================
--- trunk/conduit/modules/Makefile.am (original)
+++ trunk/conduit/modules/Makefile.am Mon Jan 28 07:22:50 2008
@@ -10,7 +10,6 @@
GmailModule \
SmugMugModule \
EvolutionModule \
- YouTubeModule \
GoogleModule \
ShutterflyModule \
RhythmboxModule \
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]