"""
scrobble.py: a program for obtaining and parsing a RSS feed from last.fm
             for display on a pyblosxom blog.
Copyright 2006 Michael Cornelius

    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 2 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, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

    A copy of the license can be obtained at the following URL:
    http://www.gnu.org/licenses/gpl.txt

The scrobble plugin parses an audioscrobbler "recently played" RSS feed and
produces an html list, which is stored in $scrobble, that can be used in head
or foot templates. Because downloading the feed data from the last.fm server
is a potentially costly operation, the data is cached for a user-specifiable
period of time.

REQUIREMENTS
	Python
	ElementTree

CONFIGURATION SETTINGS

These settings must be included in your config.py file.

    py["scrobble_user"] = "<Your last.fm user name>"
    py["scrobble_cache"] = "/home/michael/.scrobble_feed"

py["scrobble_user"] is your username on last.fm, used for formulating the
feed URL. 

py["scrobble_cache"] is the file where your cached feed data should be stored.

The follwing settings are optional, and defaults will be used if they are
present in config.py. Below, the default values are shown.

    py["scrobble_ttl"] = 300            # Number of seconds cache is valid.

    py["scrobble_encoding"] = "utf-8"   # feed encoding

    py["scrobble_format"] = '<a href="%(link)s" title="%(time_played)s">%(title)s</a>'                                  # Chart item format.

    py["scrobble_ignore_empty"] = False # If True, ignore empty last.fm feed,
                                        # and just use old cache data.


"""

__author__      = "Michael Cornelius - michael at ninthorder dot com"
__version__     = "0.03"
__url__         = "http://cornelii.org/~michael/"
__description__ = "Display your recently played tracks from audioscrobbler."
__depends__     = ["Python 2.4",
                   "elementtree module (http://effbot.org/zone/element-index.htm)"]

import os, urllib, time, codecs
from elementtree import ElementTree
import calendar

USER="scrobble_user"
CACHE="scrobble_cache"

TTL = "scrobble_ttl"
ENCODING = "scrobble_encoding"
FMT = "scrobble_format"
IGNORE_EMPTY = "scrobble_ignore_empty"

DEFAULT_FORMAT = '<a href="%(link)s" title="%(time_played)s">%(title)s</a>'

def verify_installation(request):
    cfg = request.getConfiguration()
    if not cfg.has_key(USER):
        print "missing config property '%s', which specifies " % USER
        print "which last.fm user feed to download."
        return false
    if not cfg.has_key(CACHE):
        print "missing config property '%s', which specifies " % CACHE
        print "where to store cached feed data."
        return false
    if not cfg.has_key(TTL):
        print "missing optional config property '%s', which specifies " % TTL
        print "how long cached feed data should be used. Default = 300"
    if not cfg.has_key(ENCODING):
        print "missing optional config property '%s', which specifies " % ENCODING
        print "the character encoding of the feed. Default = 'utf-8'"
    if not cfg.has_key(FMT):
        print "missing optional config property '%s', which specifies " % FMT
        print "how feed data will be displayed. Default = %s" % DEFAULT_FORMAT
    if not cfg.has_key(IGNORE_EMPTY):
        print "missing optional config property '%s', which specifies " % IGNORE_EMPTY
        print "whether or not to ignore the last.fm feed in favor of the cache, "
        print "in case the feed contains no tracks."
    return True

def cache_is_valid(path, ttl):
    return time.time() < os.stat(path).st_mtime + ttl

def decode_date(date):
    return time.strftime("%c", 
        time.localtime(
            calendar.timegm(
                time.strptime(date, "%a, %d %b %Y %H:%M:%S +0000"))))

def get_chart(cfg):

    ttl = cfg.get(TTL, 300)
    encoding = cfg.get(ENCODING, "utf-8")
    fmt = cfg.get(FMT, DEFAULT_FORMAT)
    cache_path = cfg[CACHE]
    user = cfg[USER]
    ignore_empty = cfg.get(IGNORE_EMPTY, False)
    from_cache = False
    cache_exists = os.path.exists(cache_path)

    if cache_exists and cache_is_valid(cache_path, ttl):
        f = codecs.open(cache_path, "r", encoding)
        from_cache = True
    else:
        url = "http://ws.audioscrobbler.com/1.0/user/%s/recenttracks.rss" % user
        try:
            f = urllib.urlopen(url)
        except AttributeError:
            return '<div class="error">Unable to load.</div>'

    data = f.read()
    f.close()
    t = ElementTree.fromstring(data)
    items = t.findall("channel/item")

    if not from_cache:
        if not len(items) and cache_exists and ignore_empty:
            items = ElementTree.fromstring(
                codecs.open(cache_path, "r", encoding).read()
                ).findall("channel/item")
        else:
            codecs.open(cache_path, "w", encoding).write(data)

    result = ['<ol class="scrobbler_chart">']
    li = '<li>' + fmt + '</li>'
    for i in items:
        title = i.findtext("title").strip()
        link = i.findtext("link").strip()
        played = decode_date(i.findtext("pubDate").strip())
        result.append(li % {"link":link, "time_played":played, "title":title})
    result.append("</ol>")
    return "\n".join(result)

class Scrobble(object):
    def __init__(self, request):
        self.request = request
        self.result = None

    def __str__(self):
        if not self.result: self.gen_result()
        return self.result

    def gen_result(self):
        cfg = self.request.getConfiguration()

        ttl = cfg.get(TTL, 300)
        encoding = cfg.get(ENCODING, "utf-8")

        try:
            self.result = get_chart(cfg)
        except IOError:
            self.result = ""


def cb_prepare(args):
    request = args["request"]
    data = request.getData()
    data["scrobble"] = Scrobble(request)

# vim: ts=4 sw=4 et ai
