Initial support for uploading subscriptions to my.gpodder.org

This allows us to create a podcast toplist in the future
and to provide some other user-based services.
This commit is contained in:
Thomas Perl 2008-12-08 17:10:53 +01:00
parent b398eb42f2
commit daf98f5235
4 changed files with 170 additions and 3 deletions

View File

@ -424,7 +424,23 @@
<property name="use_underline">True</property>
<signal name="activate" handler="on_item_email_subscriptions_activate" last_modification_time="Mon, 28 Apr 2008 17:50:23 GMT"/>
</widget>
</child>
<child>
<widget class="GtkSeparatorMenuItem" id="separatorUpload">
<property name="visible">True</property>
</widget>
</child>
<child>
<widget class="GtkMenuItem" id="itemUploadToMygpo">
<property name="visible">True</property>
<property name="label" translatable="yes">Upload list to my.gpodder.org</property>
<property name="use_underline">True</property>
<signal name="activate" handler="on_upload_to_mygpo"/>
</widget>
</child>
</widget>
</child>
</widget>

View File

@ -128,6 +128,10 @@ gPodderSettings = {
# Hide the cover/pill from the podcast sidebar when it gets too small
'podcast_sidebar_save_space': (bool, False),
# Settings for my.gpodder.org
'my_gpodder_username': (str, ''),
'my_gpodder_password': (str, ''),
# Paned position
'paned_position': ( int, 200 ),
}

View File

@ -45,6 +45,7 @@ from gpodder import services
from gpodder import sync
from gpodder import download
from gpodder import SimpleGladeApp
from gpodder import my
from gpodder.liblogger import log
from gpodder.dbsqlite import db
from gpodder import resolver
@ -223,7 +224,7 @@ class GladeWidget(SimpleGladeApp.SimpleGladeApp):
return response == affirmative
def UsernamePasswordDialog( self, title, message ):
def UsernamePasswordDialog( self, title, message, username=None, password=None, username_prompt=_('Username')):
""" An authentication dialog based on
http://ardoris.wordpress.com/2008/07/05/pygtk-text-entry-dialog/ """
@ -236,22 +237,29 @@ class GladeWidget(SimpleGladeApp.SimpleGladeApp):
dialog.set_markup('<span weight="bold" size="larger">' + title + '</span>')
dialog.set_title(title)
dialog.format_secondary_markup(message)
dialog.set_default_response(gtk.RESPONSE_OK)
username_entry = gtk.Entry()
username_entry.set_width_chars(25)
username_entry.set_activates_default(True)
password_entry = gtk.Entry()
password_entry.set_width_chars(25)
password_entry.set_visibility(False)
password_entry.set_activates_default(True)
if username is not None:
username_entry.set_text(username)
if password is not None:
password_entry.set_text(password)
username_hbox = gtk.HBox()
username_label = gtk.Label()
username_label.set_markup('<b>' + _('Username:') + '</b>')
username_label.set_markup('<b>' + username_prompt + ':</b>')
username_hbox.pack_start(username_label, False, 5, 5)
username_hbox.pack_end(username_entry, False)
password_hbox = gtk.HBox()
password_label = gtk.Label()
password_label.set_markup('<b>' + _('Password:') + '</b>')
password_label.set_markup('<b>' + _('Password') + ':</b>')
password_hbox.pack_start(password_label, False, 5, 5)
password_hbox.pack_end(password_entry, False)
@ -2098,6 +2106,25 @@ class gPodder(GladeWidget):
gPodderAddPodcastDialog(url_callback=add_google_video_search, custom_title=_('Add Google Video search'), custom_label=_('Search for:'))
def on_upload_to_mygpo(self, widget):
if not gl.config.my_gpodder_username or not gl.config.my_gpodder_password:
success, authentication = self.UsernamePasswordDialog(_('My gPodder Login'), _('Please enter your e-mail address as username and pick a password. If the account does not exist, it will automatically be created.'), username=gl.config.my_gpodder_username, password=gl.config.my_gpodder_password, username_prompt=_('E-Mail Address'))
if success:
gl.config.my_gpodder_username, gl.config.my_gpodder_password = authentication
else:
return
if gl.config.my_gpodder_username and gl.config.my_gpodder_password:
client = my.MygPodderClient(gl.config.my_gpodder_username, gl.config.my_gpodder_password)
save_channels(self.channels)
success, messages = client.upload_subscriptions(gl.channel_opml_file)
self.show_message('\n'.join(messages), _('Results of upload'))
if not success:
gl.config.my_gpodder_password = ''
self.on_upload_to_mygpo(widget)
else:
self.show_message(_('Please set up your username and password first.'), _('Username and password needed'))
def on_itemAddChannel_activate(self, widget, *args):
gPodderAddPodcastDialog(url_callback=self.add_new_channel)

120
src/gpodder/my.py Normal file
View File

@ -0,0 +1,120 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# gPodder - A media aggregator and podcast client
# Copyright (c) 2005-2008 Thomas Perl and the gPodder Team
#
# gPodder 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.
#
# gPodder 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/>.
#
#
# my.py -- "my gPodder" service client
# Thomas Perl <thp@gpodder.org> 2008-12-08
#
########################################################################
# Based on upload_test.py
# Copyright Michael Foord, 2004 & 2005.
# Released subject to the BSD License
# Please see http://www.voidspace.org.uk/documents/BSD-LICENSE.txt
# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
# E-mail fuzzyman@voidspace.org.uk
########################################################################
import urllib2
import mimetypes
import mimetools
import webbrowser
def encode_multipart_formdata(fields, files, BOUNDARY = '-----'+mimetools.choose_boundary()+'-----'):
""" Encodes fields and files for uploading.
fields is a sequence of (name, value) elements for regular form fields - or a dictionary.
files is a sequence of (name, filename, value) elements for data to be uploaded as files.
Return (content_type, body) ready for urllib2.Request instance
You can optionally pass in a boundary string to use or we'll let mimetools provide one.
"""
CRLF = '\r\n'
L = []
if isinstance(fields, dict):
fields = fields.items()
for (key, value) in fields:
L.append('--' + BOUNDARY)
L.append('Content-Disposition: form-data; name="%s"' % key)
L.append('')
L.append(value)
for (key, filename, value) in files:
filetype = mimetypes.guess_type(filename)[0] or 'application/octet-stream'
L.append('--' + BOUNDARY)
L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename))
L.append('Content-Type: %s' % filetype)
L.append('')
L.append(value)
L.append('--' + BOUNDARY + '--')
L.append('')
body = CRLF.join(L)
content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
return content_type, body
def build_request(theurl, fields, files, txheaders=None):
"""Given the fields to set and the files to encode it returns a fully formed urllib2.Request object.
You can optionally pass in additional headers to encode into the opject. (Content-type and Content-length will be overridden if they are set).
fields is a sequence of (name, value) elements for regular form fields - or a dictionary.
files is a sequence of (name, filename, value) elements for data to be uploaded as files.
"""
content_type, body = encode_multipart_formdata(fields, files)
if not txheaders: txheaders = {}
txheaders['Content-type'] = content_type
txheaders['Content-length'] = str(len(body))
return urllib2.Request(theurl, body, txheaders)
class MygPodderClient(object):
WEBSERVICE = 'http://my.gpodder.org'
def __init__(self, username, password):
self.username = username
self.password = password
def upload_subscriptions(self, filename):
theurl = self.WEBSERVICE+'/upload'
action = 'update-subscriptions'
fields = {'username': self.username, 'password': self.password, 'action': 'update-subscriptions', 'protocol': '0'}
opml_file = ('opml', 'subscriptions.opml', open(filename).read())
result = urllib2.urlopen(build_request(theurl, fields, [opml_file])).read()
messages = []
success = False
if '@NEWUSER' in result:
messages.append(_('A new user account has been created.'))
if '@GOTOMYGPODDER' in result:
webbrowser.open(self.WEBSERVICE, new=1)
messages.append(_('Please have a look at the website for more information.'))
if '@SUCCESS' in result:
messages.append(_('Subscriptions uploaded.'))
success = True
elif '@AUTHFAIL' in result:
messages.append(_('Authentication failed.'))
elif '@PROTOERROR' in result:
messages.append(_('Protocol error.'))
else:
messages.append(_('Unknown response.'))
return success, messages