The branch, frodo has been updated
       via  da3544a2b3ffd6dc20aba536499597a84ebb6d5f (commit)
       via  b485d7be79860ca0bfb3ec3d67970822b7199fa4 (commit)
       via  f76b6e2728f664604bef7423a752d1ef5c432829 (commit)
      from  076fa9b16e46d30026b9737c07d3de3c08392e16 (commit)

- Log -----------------------------------------------------------------
http://xbmc.git.sourceforge.net/git/gitweb.cgi?p=xbmc/plugins;a=commit;h=da3544a2b3ffd6dc20aba536499597a84ebb6d5f

commit da3544a2b3ffd6dc20aba536499597a84ebb6d5f
Author: beenje <[email protected]>
Date:   Tue Dec 31 15:09:30 2013 +0100

    [plugin.audio.di.fm] updated to version 3.0.0

diff --git a/plugin.audio.di.fm/addon.xml b/plugin.audio.di.fm/addon.xml
index fd939f6..49aadba 100644
--- a/plugin.audio.di.fm/addon.xml
+++ b/plugin.audio.di.fm/addon.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
 <addon id="plugin.audio.di.fm"
        name="Digitally Imported"
-       version="2.1.2"
+       version="3.0.0"
        provider-name="qualisoft.dk - Tim C. Steinmetz">
        <requires>
                <import addon="xbmc.python" version="2.1.0"/>
diff --git a/plugin.audio.di.fm/changelog.txt b/plugin.audio.di.fm/changelog.txt
index 117259b..ee33635 100644
--- a/plugin.audio.di.fm/changelog.txt
+++ b/plugin.audio.di.fm/changelog.txt
@@ -2,6 +2,14 @@
 * Fixed/improved feature
 - Removed feature
 
+
+31 December 2013 v3.0.0
++ Rewrote whole plugin
++ Threading of playlists parsing to speed up first run and refreshing of 
playlists - 8 threads
+* Now fully support the new di.fm design
+- Check-in with stats are gone for now
+
+
 20 May 2013 v2.1.2
 * Fixed problems related to new Digitally Imported webpage
   Thanks to Vidar Waagbø for the regex patch
diff --git a/plugin.audio.di.fm/config.ini b/plugin.audio.di.fm/config.ini
index 397e01c..f75518e 100644
--- a/plugin.audio.di.fm/config.ini
+++ b/plugin.audio.di.fm/config.ini
@@ -1,21 +1,25 @@
 # DI.fm XBMC config
 [plugin]
 id                  = plugin.audio.di.fm
-date                = 20. May 2013
+date                = 31. December 2013
 checkinkey          = a57ab7ceada3fefeaa70a7136ab05f9af5ebac82
-# Will get stripped from the favorite channels name
-playlistStripName   = Digitally Imported -
+
+[cache]
+cachePremiumConfig  = cachePremiumConfig.json
+cacheChannels       = cacheChannels.json
 
 [urls]
-baseUrl                = http://www.di.fm
-loginUrl               = http://www.di.fm/login
-listenkeyUrl           = http://www.di.fm/member/listen_key
-publicStreamsJson40k   = http://listen.di.fm/public2
-premiumStreamsJson40k  = http://listen.di.fm/premium_low/              
-premiumStreamsJson64k  = http://listen.di.fm/premium_medium/
-premiumStreamsJson128k = http://listen.di.fm/premium/  
-premiumStreamsJson256k = http://listen.di.fm/premium_high/     
-favoritesStreamJson40k = http://listen.di.fm/premium_low/favorites.pls
-favoritesStreamJson64k = http://listen.di.fm/premium_medium/favorites.pls      
-favoritesStreamJson128k = http://listen.di.fm/premium/favorites.pls
-favoritesStreamJson256k = http://listen.di.fm/premium_high/favorites.pls
\ No newline at end of file
+frontpage                  = http://www.di.fm
+login               = http://www.di.fm/login
+listenkey          = http://www.di.fm/member/listen_key
+
+[streams]
+public = http://listen.di.fm/public2
+premium40k     = http://listen.di.fm/premium_low/
+premium64k     = http://listen.di.fm/premium_medium/
+premium128k    = http://listen.di.fm/premium/
+premium256k    = http://listen.di.fm/premium_high/
+favorites40k   = http://listen.di.fm/premium_low/favorites.pls
+favorites64k   = http://listen.di.fm/premium_medium/favorites.pls
+favorites128k   = http://listen.di.fm/premium/favorites.pls
+favorites256k   = http://listen.di.fm/premium_high/favorites.pls
\ No newline at end of file
diff --git a/plugin.audio.di.fm/default.py b/plugin.audio.di.fm/default.py
index 3862400..90ed3af 100644
--- a/plugin.audio.di.fm/default.py
+++ b/plugin.audio.di.fm/default.py
@@ -1,3 +1,5 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
 ########################################
 #  Digitally Imported XBMC plugin
 #  by Tim C. 'Bitcrusher' Steinmetz
@@ -22,20 +24,24 @@
 #
 
 import os
+import pickle
 import sys
 import re
-import urllib
-import urllib2
 import string
+import urllib
+
 import xbmc
 import xbmcgui
 import xbmcplugin
 import xbmcaddon
-import pickle
 import time
 from xml.dom import minidom
 from httpcomm import HTTPComm
 from ConfigParser import SafeConfigParser
+import json
+from random import randrange
+import Queue
+import threading
 
 # Import JSON - compatible with Python<v2.6
 try:
@@ -43,378 +49,376 @@ try:
 except ImportError:
     import simplejson as json
 
+
 # Config parser
-parser = SafeConfigParser()
-parser.read( os.path.dirname(__file__) + "/config.ini" )
+pluginConfig = SafeConfigParser()
+pluginConfig.read(os.path.dirname(__file__) + "/config.ini")
+
 
 # Various constants used throughout the script
 HANDLE = int(sys.argv[1])
-ADDON = xbmcaddon.Addon(id=parser.get('plugin', 'id'))
+ADDON = xbmcaddon.Addon(id=pluginConfig.get('plugin', 'id'))
 
 # Plugin constants
-__plugin__       = ADDON.getAddonInfo('name')
-__author__       = "Tim C. Steinmetz"
-__url__          = "http://qualisoft.dk/";
-__platform__     = "xbmc media center, [LINUX, OS X, WIN32]"
-__date__         = parser.get('plugin', 'date')
-__version__      = ADDON.getAddonInfo('version')
+__plugin__ = ADDON.getAddonInfo('name')
+__author__ = "Tim C. Steinmetz"
+__url__ = "http://qualisoft.dk/";
+__platform__ = "xbmc media center, [LINUX, OS X, WIN32]"
+__date__ = pluginConfig.get('plugin', 'date')
+__version__ = ADDON.getAddonInfo('version')
+
+
+"""
+ Thread class used for scraping individual playlists for each channel
+"""
+class scraperThread(threading.Thread):
+    def __init__(self, musicObj, channel, channelMeta, channelCount):
+        threading.Thread.__init__(self)
+        self.musicObj = musicObj
+        self.channel = channel
+        self.channelMeta = channelMeta
+        self.channelCount = channelCount
+
+    def run(self):
+        self.musicObj.addChannel(self.channel, self.channelMeta, 
self.channelCount)
+
 
 class musicAddonXbmc:
-       _addonProfilePath = xbmc.translatePath( ADDON.getAddonInfo('profile') 
).decode('utf-8') # Dir where plugin settings and cache will be stored
-
-       _cacheStreams   = _addonProfilePath + "cache_streamlist.dat"
-       _cacheListenkey = _addonProfilePath + "cache_listenkey.dat"
-       _checkinFile    = _addonProfilePath + "cache_lastcheckin.dat"
-       
-       _baseUrl                = parser.get('urls', 'baseUrl')
-       _loginUrl               = parser.get('urls', 'loginUrl')
-       _listenkeyUrl           = parser.get('urls', 'listenkeyUrl')
-       
-       _publicStreamsJson40k   = parser.get('urls', 'publicStreamsJson40k')    
# Public AAC 40k/sec AAC+ JSON url
-       _premiumStreamsJson40k  = parser.get('urls', 'premiumStreamsJson40k')   
# AAC 40k/sec AAC+ JSON url
-       _premiumStreamsJson64k  = parser.get('urls', 'premiumStreamsJson64k')   
# AAC 64k/sec AAC+ JSON url
-       _premiumStreamsJson128k = parser.get('urls', 'premiumStreamsJson128k')  
# AAC 128k/sec AAC+ JSON url
-       _premiumStreamsJson256k = parser.get('urls', 'premiumStreamsJson256k')  
# MP3 256k/sec AAC+ JSON url
-       _favoritesStreamJson40k = parser.get('urls', 'favoritesStreamJson40k')  
# Favorites AAC 40k/sec AAC+ playlist url
-       _favoritesStreamJson64k = parser.get('urls', 'favoritesStreamJson64k')  
# Favorites AAC 64k/sec AAC+ playlist url
-       _favoritesStreamJson128k= parser.get('urls', 'favoritesStreamJson128k') 
# Favorites AAC 128k/sec AAC+ playlist url
-       _favoritesStreamJson256k= parser.get('urls', 'favoritesStreamJson256k') 
# Favorites MP3 256k/sec AAC+ playlist url
-       
-       _httpComm = HTTPComm() # Init CURL thingy
-       _frontpageHtml = ""
-       
-       _newChannels = 0
-       _bitrate = 40
-       _streamMimeType = 'audio/aac'
-       
-       def __init__( self ) :
-               # If stats is allowed and its been at least 24 hours since last 
checkin
-               if (ADDON.getSetting('allowstats') == "true") and 
(self.checkFileTime(self._checkinFile, self._addonProfilePath, 86400) == True) :
-                       open(self._checkinFile, "w")
-                       
-                       account = 'public'
-                       if ADDON.getSetting('username') != "" :
-                               account = 'premium'
-               
-                       xbmc.log( 'Submitting stats', xbmc.LOGNOTICE )
-                       
self._httpComm.get('http://stats.qualisoft.dk/?plugin='+ 
ADDON.getAddonInfo('id') + '&version=' + __version__ + '&account=' + account + 
'&key=' + parser.get('plugin', 'checkinkey'))
-               
-               xbmc.log( "[PLUGIN] %s v%s (%s)" % ( __plugin__, __version__, 
__date__ ), xbmc.LOGNOTICE )
-
-               
-       # Let's get some tunes!
-       def start( self ) :
-               jsonList = []   # list that data from the JSON will be put in
-               streamList = [] # the final list of channels, with small custom 
additions
-
-               # Check if cachefile has expired
-               if ADDON.getSetting("forceupdate") == "true" or ((int( 
ADDON.getSetting("cacheexpire") ) * 60) != 0 and 
self.checkFileTime(self._cacheStreams, self._addonProfilePath, (int( 
ADDON.getSetting("cacheexpire") ) * 60)) == True) :
-                       listenkey = ""  # will contain the premium listenkey
-                       
-                       if ADDON.getSetting('username') != "" and 
ADDON.getSetting("usefavorites") == 'false' : # if username is set and not 
using favorites
-                               xbmc.log( "Going for Premium streams", 
xbmc.LOGNOTICE )
-                               
-                               # Checks if forceupdate is set and if the 
listenkey cachefile exists
-                               if ADDON.getSetting("forceupdate") == "true" or 
not os.path.exists(self._cacheListenkey) :
-                                       listenkey = self.getListenkey()
-                                       pickle.dump( listenkey, 
open(self._cacheListenkey, "w"), protocol=0 ) # saves listenkey for further use
-                               else :
-                                       listenkey = pickle.load( 
open(self._cacheListenkey, "r") )
-                               
-                               if ADDON.getSetting('bitrate') == '0' :
-                                       self._bitrate = 40
-                                       jsonList = self.getJSONChannelList( 
self._premiumStreamsJson40k )
-                                       streamList = 
self.customizeStreamListAddMenuitem( jsonList, listenkey )
-                               elif ADDON.getSetting('bitrate') == '1' :
-                                       self._bitrate = 64
-                                       jsonList = self.getJSONChannelList( 
self._premiumStreamsJson64k )
-                                       streamList = 
self.customizeStreamListAddMenuitem( jsonList, listenkey )
-                               elif ADDON.getSetting('bitrate') == '2' :
-                                       self._bitrate = 128
-                                       jsonList = self.getJSONChannelList( 
self._premiumStreamsJson128k )
-                                       streamList = 
self.customizeStreamListAddMenuitem( jsonList, listenkey )
-                               else :
-                                       self._bitrate = 256
-                                       self._streamMimeType = 'audio/mpeg'
-                                       jsonList = self.getJSONChannelList( 
self._premiumStreamsJson256k )
-                                       streamList = 
self.customizeStreamListAddMenuitem( jsonList, listenkey )
-                               
-                               xbmc.log( "Bitrate set to " + str( 
self._bitrate ), xbmc.LOGNOTICE )
-                               
-                               
-                       elif ADDON.getSetting('username') != "" and 
ADDON.getSetting("usefavorites") == 'true' : # if username is set and wants to 
use favorites
-                               xbmc.log( "Going for Premium favorite streams", 
xbmc.LOGNOTICE )
-                               listenkey = self.getListenkey()
-                               if ADDON.getSetting('bitrate') == '0' :
-                                       self._bitrate = 40
-                                       streamList = 
self.getFavoriteStreamsList( self._favoritesStreamJson40k + "?" + listenkey )
-                               elif ADDON.getSetting('bitrate') == '1' :
-                                       self._bitrate = 64
-                                       streamList = 
self.getFavoriteStreamsList( self._favoritesStreamJson64k + "?" + listenkey )
-                               elif ADDON.getSetting('bitrate') == '2' :
-                                       self._bitrate = 128
-                                       streamList = 
self.getFavoriteStreamsList( self._favoritesStreamJson128k + "?" + listenkey )
-                               else :
-                                       self._bitrate = 256
-                                       self._streamMimeType = 'audio/mpeg'
-                                       streamList = 
self.getFavoriteStreamsList( self._favoritesStreamJson256k + "?" + listenkey )
-                               
-                               xbmc.log( "Bitrate set to " + 
str(self._bitrate), xbmc.LOGNOTICE )
-                               
-                               for channel in streamList :
-                                       self.addItem( channel['name'], 
channel['playlist'], channel["description"], channel['bitrate'], 
self._addonProfilePath + "art_" + channel['key'] + ".png", channel['isNew'] )
-
-                       else :
-                               xbmc.log( "Going for Public streams", 
xbmc.LOGNOTICE )
-                               jsonList = self.getJSONChannelList( 
self._publicStreamsJson40k )
-                               streamList = 
self.customizeStreamListAddMenuitem(jsonList, "") # sending a blank string as 
listenkey
-
-                       # save streams to cachefile
-                       pickle.dump( streamList, open(self._cacheStreams, "w"), 
protocol=0 )
-                       
-                       if (self._newChannels > 0) : # Yay! New channels found
-                               xbmc.log( ADDON.getLocalizedString(30130) + " " 
+ ADDON.getLocalizedString(30131) + str(self._newChannels) + 
ADDON.getLocalizedString(30132) + " " + ADDON.getLocalizedString(30133) + " " + 
ADDON.getLocalizedString(30134), xbmc.LOGNOTICE )
-                               xbmcgui.Dialog().ok( 
ADDON.getLocalizedString(30130), ADDON.getLocalizedString(30131) + 
str(self._newChannels) + ADDON.getLocalizedString(30132), 
ADDON.getLocalizedString(30133),ADDON.getLocalizedString(30134) )
-
-               else :
-                       xbmc.log( "Using cached streams", xbmc.LOGNOTICE )
-                       streamList = pickle.load( open(self._cacheStreams, "r") 
)
-
-                       # Add streams to GUI
-                       for channel in streamList :
-                           self.addItem( channel['name'].encode('utf-8'), 
channel['playlist'], channel["description"], channel['bitrate'], 
self._addonProfilePath + "art_" + channel['key'] + ".png", channel['isNew'] )
-                               
-               # If streams should be sorted A-Z
-               if ADDON.getSetting('sortaz') == "true" :
-                       xbmcplugin.addSortMethod( HANDLE, 
sortMethod=xbmcplugin.SORT_METHOD_LABEL )
-               
-               # End of channel list
-               xbmcplugin.endOfDirectory( HANDLE, succeeded=True )
-
-               # Resets the 'Force refresh' setting
-               ADDON.setSetting( id="forceupdate", value="false" )
-               
-               return True
-               
-               
-       """return list - False if it fails
-       Gets the favorites playlist and returns the streams as a list
-       Also every channel is added to the GUI from here, as the progress 
indication
-       in the GUI would not reflect that something is actually happening till 
the very end
-       """
-       def customizeStreamListAddMenuitem( self, list, listenkey ) :
-               # Precompiling regexes          
-               streamurl_re = re.compile('File\d+=([^\n]*)', re.I) # streams 
in .pls file
-               
-               streamList = []
-               
-               # Will add list elements to a new list, with a few additions
-               for channel in list :
-                       channel['key'] = self.makeChannelIconname( 
channel['name'] ) # customize the key that is used to find channelart
-                       channel['isNew'] = False # is used to highlight when 
it's a new channel
-                       channelArt = "art_" + channel['key'] + ".png"
-                       channel['bitrate'] = self._bitrate
-                       channel["description"] = 
channel["description"].encode('utf-8')
-                       
-                       if ADDON.getSetting('username') != "" : # append 
listenkey to playlist url if username is set
-                               channel['playlist'] = self.getFirstStream( 
channel['playlist'] + "?" + listenkey, streamurl_re )
-                       else :
-                               channel['playlist'] = self.getFirstStream( 
channel['playlist'], streamurl_re )
-                               
-                       if (not os.path.isfile(self._addonProfilePath + 
channelArt)) : # if channelart is not in cache
-                               xbmc.log( "Channelart for " + 
channel['name'].encode("ascii","ignore") + " not found in cache at " + 
self._addonProfilePath + channelArt, xbmc.LOGNOTICE )
-                               self.getChannelArt( channel['id'], "art_" + 
channel['key'] )
-                               channel['isNew'] = True
-                               self._newChannels = self._newChannels + 1
-                       
-                       streamList.append( channel )
-                       
-                       # I'd have prefeered it if I didn't have to add 
menuitem from within this method
-                       # but I have to, too give the user some visual feedback 
that stuff is happening
-                       self.addItem( channel['name'].encode('utf-8'), 
channel['playlist'], channel["description"], self._bitrate, 
self._addonProfilePath + "art_" + channel['key'] + ".png", channel['isNew'] )
-
-               return streamList # returns the channellist so it can be saved 
to cache
-       
-       
-       """return bool
-       Will check if channelart/icon is present in cache - if not, try to 
download
-       """
-       def getChannelArt( self, channelId, channelKey ) :
-               channelArt_re = re.compile('data-channel-id="' + str(channelId) 
+'">(?:[\n\s]*)<a(?:[^>]*)>(?:[\n\s]*)<img(?:[^>]*)src="([^"]*)"', re.I)
-
-               try :
-                       if (self._frontpageHtml == "") : # If frontpage html 
has not already been downloaded, do it
-                               self._frontpageHtml = self._httpComm.get( 
self._baseUrl )
-                       channelartDict = channelArt_re.findall( 
self._frontpageHtml )
-
-                       # Return false if no channel art is found
-                       if len(channelartDict) < 1:
-                               return False
-
-                       # Add HTTP to // syntax links
-                       if not "http" in channelartDict[0]:
-                               channelartDict[0] = "http:" + channelartDict[0]
-
-                       # Will download and save the channelart to the cache
-                       self._httpComm.getImage( channelartDict[0], 
self._addonProfilePath + channelKey + ".png" )
-                       return True
-
-               except Exception :
-                       sys.exc_clear() # Clears all exceptions so the script 
will continue to run
-                       xbmcgui.Dialog().ok( ADDON.getLocalizedString(30160), 
ADDON.getLocalizedString(30161), ADDON.getLocalizedString(30162) + 
channelartDict[0] )
-                       xbmc.log( ADDON.getLocalizedString(30160) + " " + 
ADDON.getLocalizedString(30161) + channelKey + " " + 
ADDON.getLocalizedString(30162)+ channelartDict[0], xbmc.LOGERROR )
-                       return False
-               
-               return True
-       
-       
-       """return String
-       Extracts the premium listenkey from the listenkey page html
-       """
-       def getListenkey( self ) :
-               listenkey_re = re.compile('Key is:<br 
/>[^<]*<strong>([\w\d]*)<', re.DOTALL)
-               
-               try :
-                       logindata = urllib.urlencode({  
'member_session[username]':  ADDON.getSetting('username'),
-                                                                               
        'member_session[password]':  ADDON.getSetting('password') })
-                       
-                       self._httpComm.post( self._loginUrl, logindata ) # logs 
in so the listenkey page is accessible
-                       
-                       listenkeyHtml = self._httpComm.get( self._listenkeyUrl)
-                       listenkeyDict = listenkey_re.findall( listenkeyHtml )
-                       
-                       xbmc.log( "Found listenkey", xbmc.LOGNOTICE )
-                       return listenkeyDict[0]
-
-               except Exception :
-                       sys.exc_clear() # Clears all exceptions so the script 
will continue to run
-                       xbmcgui.Dialog().ok( ADDON.getLocalizedString(30100), 
ADDON.getLocalizedString(30101), ADDON.getLocalizedString(30102) )
-                       xbmc.log( ADDON.getLocalizedString(30100) + " " + 
ADDON.getLocalizedString(30101) + " " + ADDON.getLocalizedString(30102), 
xbmc.LOGERROR )
-                       return False
-               
-               return False
-
-       
-       """return list - False if it fails
-       Will get a HTML page containing JSON data, decode it and return
-       """
-       def getJSONChannelList( self, url ) :
-               try :
-                       jsonData = self._httpComm.get( url )
-                       jsonData = json.loads(jsonData)
-               except Exception : # Show error message in XBMC GUI if failing 
to parse JSON
-                       sys.exc_clear() # Clears all exceptions so the script 
will continue to run
-                       xbmcgui.Dialog().ok( ADDON.getLocalizedString(30100), 
ADDON.getLocalizedString(30101), ADDON.getLocalizedString(30102) )
-                       xbmc.log( ADDON.getLocalizedString(30100) + " " + 
ADDON.getLocalizedString(30101) + " " + ADDON.getLocalizedString(30102), 
xbmc.LOGERROR )
-                       return False
-
-               return jsonData
-       
-       
-       """return list - False if it fails
-       Gets the favorites playlist and returns the streams as a list
-       """
-       def getFavoriteStreamsList( self, url ) :
-               try :
-                       favoritesPlaylist = self._httpComm.get( url ) # 
favorites .pls playlist in plaintext
-                       favoritesList = [] # list that will contain streamlist
-                       
-                       streamurl_re    = re.compile( 'File\d+=([^\n]*)', re.I 
) # first stream in .pls file
-                       channeltitle_re = re.compile( 'Title\d+=([^\n]*)', re.I 
)
-                       
-                       streamTitles = channeltitle_re.findall( 
favoritesPlaylist )
-                       streamUrls      = streamurl_re.findall( 
favoritesPlaylist )
-                       
-                       if len(streamUrls) == len( streamTitles ) : # only 
continue if the count of urls and titles are equal
-                           
-                               for i in range(len(streamUrls)) :
-                                       listitem = {}
-                                       listitem['playlist'] = streamUrls[i]
-                                       listitem['name'] = 
streamTitles[i].replace( parser.get('plugin', 'playlistStripName') + " ", "" ) 
# favorite stream titles has some "fluff" text it that is removed
-                                       listitem['key'] = 
self.makeChannelIconname( listitem['name'] )
-                                       listitem['isNew'] = False
-                                       listitem['bitrate'] = self._bitrate
-                                       listitem['description'] = ""
-                                       favoritesList.append( listitem )
-                                       
-                       else :
-                               return False
-
-                       return favoritesList
-                       
-               except Exception : # Show error message in XBMC GUI if failing 
to parse JSON
-                       #sys.exc_clear() # Clears all exceptions so the script 
will continue to run
-                       xbmcgui.Dialog().ok( ADDON.getLocalizedString(30120), 
ADDON.getLocalizedString(30111), url )
-                       xbmc.log( ADDON.getLocalizedString(30120) + " " + 
ADDON.getLocalizedString(30111) + " " + url, xbmc.LOGERROR )
-                       return False
-
-               return favoritesList
-
-       
-       """return string
-       Will take a channelname, lowercase it and remove spaces, dashes and 
other special characters
-       The string returned is normally used as part of the filename for the 
channelart
-       """
-       def makeChannelIconname( self, channelname ) :
-               iconreplacement_re = re.compile('[^a-z0-9]', re.I) # regex that 
hits everything but a-z and 0-9
-               iconname = string.lower(iconreplacement_re.sub( '', 
channelname) )
-               return iconname
-       
-       
-       """return bool
-       Simply adds a music item to the XBMC GUI
-       """
-       # Adds item to XBMC itemlist
-       def addItem( self, channelTitle, streamUrl, streamDescription, 
streamBitrate, icon, isNewChannel ) :
-
-               if isNewChannel == True : # tart it up a bit if it's a new 
channel
-                       li = xbmcgui.ListItem(label="[COLOR FF007EFF]" + 
channelTitle + "[/COLOR]",thumbnailImage=icon)
-                       xbmc.log( "New channel found: " + channelTitle, 
xbmc.LOGERROR )
-               else :
-                       li = xbmcgui.ListItem(label=channelTitle, 
thumbnailImage=icon)
-
-               li.setProperty("mimetype", self._streamMimeType)
-               li.setInfo( type="Music", infoLabels={ "label": channelTitle, 
"Genre": channelTitle, "Comment": streamDescription, "Size": (streamBitrate * 
1024) })
-               li.setProperty("IsPlayable", "true")
-               li.setProperty("IsLive", "true")
-
-               xbmcplugin.addDirectoryItem(handle=HANDLE, url=streamUrl, 
listitem=li, isFolder=False)
-               
-               return True
-
-       
-       """return string
-       Gets the first stream from a playlist
-       """
-       def getFirstStream( self, playlistUrl, regex ) :
-               plsData = self._httpComm.get( playlistUrl )
-
-               streamurls = regex.findall(plsData)
-               
-               return streamurls[0]
-       
-
-       """return bool
-       Checks if a file is older than x seconds
-       """
-       def checkFileTime( self, tmpfile, cachedir, timesince ) :
-               if not os.path.exists( cachedir ) :
-                       os.makedirs( cachedir )
-                       return False
-               
-               # If file exists, check timestamp
-               if os.path.exists( tmpfile ) :
-                       if os.path.getmtime( tmpfile ) > ( time.time() - 
timesince ) :
-                               xbmc.log( 'It has not been ' + str( 
timesince/60 ) + ' minutes since ' + tmpfile + ' was last updated', 
xbmc.LOGNOTICE )
-                               return False
-                       else :
-                               xbmc.log( 'The cachefile ' + tmpfile + ' + has 
expired', xbmc.LOGNOTICE )
-                               return True
-               # If file does not exist, return true so the file will be 
created by scraping the page
-               else :
-                       xbmc.log( 'The cachefile ' + tmpfile + ' does not 
exist', xbmc.LOGNOTICE )
-                       return True
-
-                       
+    # settings used for multithreading
+    threadMax = 8
+    workQueue = Queue.Queue()
+
+    # Resolves path to where plugin settings and cache will be stored
+    addonProfilePath = 
xbmc.translatePath(ADDON.getAddonInfo('profile')).decode('utf-8')
+
+    # Init CURL thingy
+    curler = HTTPComm()
+
+    # regex used to find all streams in a .pls
+    re_playlistStreams = re.compile("File\d=([^\n]+)\s*Title\d=([^\n]+)", re.M 
| re.I)
+
+    #dictBitrate = {1: 40, 2: 64, 3: 128, 4: 256}
+    dictBitrate = [40, 64, 128, 256]
+
+    # list of channels used when caching and retrieving from cache
+    channelsList = []
+
+    # unique listenkey if a premium member
+    listenKey = ''
+
+    # stores how many new channels/channelart was found
+    newChannels = 0
+
+    def __init__(self):
+        # If stats is allowed and its been at least 24 hours since last checkin
+        #if (ADDON.getSetting('allowstats') == "true") and (
+        #        self.checkFileTime(self._checkinFile, self._addonProfilePath, 
86400) == True):
+        #    open(self._checkinFile, "w")
+        #
+        #    account = 'public'
+        #    if ADDON.getSetting('username') != "":
+        #        account = 'premium'
+        #
+        #    xbmc.log('Submitting stats', xbmc.LOGNOTICE)
+        #    self._httpComm.get('http://stats.qualisoft.dk/?plugin=' + 
ADDON.getAddonInfo(
+        #        'id') + '&version=' + __version__ + '&account=' + account + 
'&key=' + parser.get('plugin',
+        #                                                                      
                   'checkinkey'))
+        #
+        xbmc.log("[PLUGIN] %s v%s (%s)" % ( __plugin__, __version__, __date__ 
), xbmc.LOGNOTICE)
+
+
+    """
+     Let's get some tunes!
+    """
+    def run(self):
+        threads = []
+
+        # check if cache has expired
+        if not ADDON.getSetting("forceupdate") == "true"\
+            and not int(ADDON.getSetting("cacheexpire_days")) == 0\
+            and self.checkFileTime(pluginConfig.get('cache', 'cacheChannels'),
+                                   ADDON.getSetting("cacheexpire_days")):
+            ADDON.setSetting(id="forceupdate", value="true")
+
+        if ADDON.getSetting("forceupdate") == "true":
+            channels = []
+            html = ""
+
+            # if username is set, try to login and go for premium channels
+            if ADDON.getSetting('username') != "":
+                loginData = urllib.urlencode({'member_session[username]': 
ADDON.getSetting('username'),
+                                              'member_session[password]': 
ADDON.getSetting('password')})
+
+                # post login info and get frontpage html
+                html = self.curler.request(pluginConfig.get('urls', 'login'), 
'post', loginData)
+
+                # if we could not reach di.fm at all
+                if not bool(html):
+                    xbmc.log('di.fm could not be reached', xbmc.LOGWARNING)
+                    xbmcgui.Dialog().ok(ADDON.getLocalizedString(30100),
+                                        ADDON.getLocalizedString(30101),
+                                        ADDON.getLocalizedString(30102),
+                                        ADDON.getLocalizedString(30103))
+                    xbmcplugin.endOfDirectory(HANDLE, succeeded=True)
+                    return True
+
+                channels = 
json.loads(self.curler.request(pluginConfig.get('streams', 'premium%sk' % 
self.dictBitrate[int(ADDON.getSetting('bitrate'))]), 'get'))
+
+                premiumConfig = self.getPremiumConfig(html)
+
+                # if premiumConfig['listenKey'] is blank, we did not log in 
correctly
+                if premiumConfig['listenKey'] == '':
+                    xbmc.log('Login did not succeed', xbmc.LOGWARNING)
+                    xbmcgui.Dialog().ok(ADDON.getLocalizedString(30170),
+                                        ADDON.getLocalizedString(30171),
+                                        ADDON.getLocalizedString(30172))
+                    xbmcplugin.endOfDirectory(HANDLE, succeeded=True)
+                    return True
+
+                # if we should get the favorites or all channels
+                if ADDON.getSetting("usefavorites") == 'true':
+                    channels = self.getFavoriteChannels(html, channels)
+                    if len(channels) == 0:
+                        xbmcgui.Dialog().ok(ADDON.getLocalizedString(30180),
+                                            ADDON.getLocalizedString(30181),
+                                            ADDON.getLocalizedString(30182))
+                        xbmcplugin.endOfDirectory(HANDLE, succeeded=True)
+                        return True
+
+                # add listenkey to playlist urls
+                for channel in channels:
+                    channel['playlist'] = '%s?%s' % (channel['playlist'], 
premiumConfig['listenKey'])
+
+            # go for free/public channels
+            else:
+                html = self.curler.request(pluginConfig.get('urls', 
'frontpage'), 'get')
+
+                # if we could not reach di.fm at all
+                if not html:
+                    xbmc.log('di.fm could not be reached', xbmc.LOGWARNING)
+                    xbmcgui.Dialog().ok(ADDON.getLocalizedString(30100),
+                                        ADDON.getLocalizedString(30101),
+                                        ADDON.getLocalizedString(30102),
+                                        ADDON.getLocalizedString(30103))
+                    xbmcplugin.endOfDirectory(HANDLE, succeeded=True)
+                    return True
+
+                channels = 
json.loads(self.curler.request(pluginConfig.get('streams', 'public'), 'get'))
+
+            re_channelData = 
re.compile("NS\('AudioAddict'\).Channels\s*=\s*([^;]+);", re.M | re.I)
+            channelMeta = json.loads(re_channelData.findall(html)[0])
+
+            # put each playlist in a worker queue for threading
+            for channel in channels:
+                self.workQueue.put(channel)
+
+            # starts 8 threads to download streams for each channel
+            while not self.workQueue.empty():
+                xbmc.log('Worker queue size is %s' % 
(str(self.workQueue.qsize())), xbmc.LOGNOTICE)
+
+                if threading.activeCount() < self.threadMax and not 
self.workQueue.empty():
+                    threads.append(scraperThread(self, self.workQueue.get(), 
channelMeta, len(channels)))
+                    threads[-1].start()
+                else:
+                    time.sleep(0.1)
+
+            # wait for all threads to finish before continuing
+            for t in threads:
+                t.join()
+
+            # Saves channels to cache and reset the "force update" flag
+            if len(channels) > 0:
+                pickle.dump(self.channelsList, open("%s/%s" % 
(self.addonProfilePath, pluginConfig.get('cache', 'cacheChannels')), "w"), 
protocol=0)
+                ADDON.setSetting(id="forceupdate", value="false")
+
+        # else load channels from cache file
+        else:
+            self.channelsList = pickle.load(open(("%s/%s" % 
(self.addonProfilePath, pluginConfig.get('cache', 'cacheChannels'))), "r"))
+
+            for channel in self.channelsList:
+                self.addItem(channel['name'],
+                             channel['streamUrl'],
+                             channel['description'],
+                             channel['bitrate'],
+                             channel['asset'],
+                             channel['isNew'],
+                             len(self.channelsList))
+
+        # Should channels be sorted A-Z?
+        if ADDON.getSetting('sortaz') == "true":
+            xbmcplugin.addSortMethod(HANDLE, 
sortMethod=xbmcplugin.SORT_METHOD_LABEL)
+
+        # tell XMBC there are no more items to list
+        xbmcplugin.endOfDirectory(HANDLE, succeeded=True)
+
+        if self.newChannels > 0:
+            xbmcgui.Dialog().ok(ADDON.getLocalizedString(30130),
+                                ADDON.getLocalizedString(30131) + 
str(self.newChannels) + ADDON.getLocalizedString(30132),
+                                ADDON.getLocalizedString(30133),
+                                ADDON.getLocalizedString(30134))
+
+        return True
+
+
+    """
+     Parses a playlist (if needed) and calls the function that adds it to the 
GUI
+    """
+    def addChannel(self, channel, channelMeta, channelCount):
+        # set to true if new channelart is downloaded
+        isNew = 0
+
+        if not os.path.exists(self.addonProfilePath + str(channel['id']) + 
'.png'):
+            isNew = 1
+            self.newChannels += 1
+            self.getChannelAsset(str(channel['id']), 
channelMeta[str(channel['id'])]['asset_url'])
+
+        if ADDON.getSetting('randomstream') == "true":
+            playlist = self.curler.request(channel['playlist'] + 
self.listenKey, 'get')
+
+            playlistStreams = self.re_playlistStreams.findall(playlist)
+
+            # gets a random stream from the channels playlist
+            streamUrl = playlistStreams[randrange(len(playlistStreams))][0]
+        else:
+            streamUrl = channel['playlist'] + self.listenKey
+
+        bitrate = 40
+
+        if ADDON.getSetting('username') != "":
+            bitrate = self.dictBitrate[int(ADDON.getSetting('bitrate'))]
+
+        # stores channel in list to be cached later
+        co = {'name': channel['name'],
+              'streamUrl': streamUrl,
+              'description': channel['description'],
+              'bitrate': bitrate,
+              'asset': self.addonProfilePath + str(channel['id']) + '.png',
+              'isNew': isNew}
+        self.channelsList.append(dict(co))
+
+        self.addItem(channel['name'],
+                     streamUrl,
+                     channel['description'],
+                     bitrate,
+                     self.addonProfilePath + str(channel['id']) + '.png',
+                     isNew,
+                     channelCount)
+
+
+    """ Returns a bool
+     Adds item to the XBMC GUI
+    """
+    def addItem(self, channelTitle, streamUrl, streamDescription, bitrate, 
icon, isNewChannel, totalItems):
+        # tart it up a bit if it's a new channel
+        if isNewChannel:
+            li = xbmcgui.ListItem(label="[COLOR FF007EFF]" + channelTitle + 
"[/COLOR]", thumbnailImage=icon)
+            xbmc.log("New channel found: " + channelTitle, xbmc.LOGERROR)
+        else:
+            li = xbmcgui.ListItem(label=channelTitle, thumbnailImage=icon)
+
+        # 256kb/sec is MP3
+        if bitrate == 256 and ADDON.getSetting('username') != "":
+            li.setProperty("mimetype", 'audio/mpeg')
+        else:
+            li.setProperty("mimetype", 'audio/aac')
+
+        li.setInfo(type="Music", infoLabels={"Genre": channelTitle,
+                                             "Comment": streamDescription,
+                                             "Size": bitrate * 1024})
+        li.setProperty("IsPlayable", "true")
+
+        xbmcplugin.addDirectoryItem(handle=HANDLE, url=streamUrl, listitem=li, 
isFolder=False, totalItems=totalItems)
+
+        return True
+
+
+    """ Returns a bool
+     Will check if channel asset/art is present in cache - if not, try to 
download
+    """
+    def getChannelAsset(self, channelId, assetUrl):
+        try:
+            data = self.curler.request(assetUrl, 'get')
+            filepath = self.addonProfilePath + channelId + '.png'
+            open(filepath, 'wb').write(data)
+            xbmc.log('Found new channel art for with ID %s' % str(channelId), 
xbmc.LOGINFO)
+        except Exception:
+            sys.exc_clear() # Clears all exceptions so the script will 
continue to run
+            xbmcgui.Dialog().ok(ADDON.getLocalizedString(30160), 
ADDON.getLocalizedString(30161),
+                                ADDON.getLocalizedString(30162) + channelId)
+            xbmc.log(ADDON.getLocalizedString(30160) + " " + 
ADDON.getLocalizedString(
+                30161) + str(channelId) + " " + 
ADDON.getLocalizedString(30162) + str(channelId), xbmc.LOGERROR)
+            return False
+
+        return True
+
+
+    """ Return a list containing a dictonary
+     Filters out all channels that are not in the favorites list
+    """
+    def getFavoriteChannels(self, html, allChannels):
+        channels = []
+        re_favoriteData = 
re.compile("NS\('AudioAddict.API.Config.member'\).Favorites\s*=\s*([^;]+);", 
re.M | re.I)
+        m = re_favoriteData.findall(html)
+
+        # if favorites list is empty, return empty list
+        if m[0] == '[]':
+            return channels
+        else:
+            favorites = json.loads(re_favoriteData.findall(html)[0])
+
+        # sort favorites after user selected positions
+        favorites = sorted(favorites, key=lambda k: k['position'])
+
+        for fav in favorites:
+            for channel in allChannels:
+                if fav['channel_id'] == channel['id']:
+                    channels.append(dict(channel))
+
+        return channels
+
+
+    """ Return a list containing a dictonary or false
+     Returns the logged in users premium config
+    """
+    def getPremiumConfig(self, html):
+        try:
+            if ADDON.getSetting("forceupdate") == "true":
+                re_config = 
re.compile("NS\('AudioAddict.API'\).Config\s*=\s*([^;]+);", re.M | re.I)
+                pickle.dump(re_config.findall(html)[0], open("%s/%s" % 
(self.addonProfilePath, pluginConfig.get('cache', 'cachePremiumConfig')), "w"), 
protocol=0)
+                premiumConfig = json.loads(re_config.findall(html)[0])
+            else:
+                premiumConfig = json.loads(pickle.load(open(("%s/%s" % 
(self.addonProfilePath, pluginConfig.get('cache', 'cachePremiumConfig'))), 
"r")))
+            return premiumConfig
+        except Exception:
+            sys.exc_clear() # Clears all exceptions so the script will 
continue to run
+            return False
+
+
+    """ Return a bool
+     Checks if a file is older than x days
+    """
+    def checkFileTime(self, filename, days):
+        if not os.path.exists(self.addonProfilePath):
+            os.makedirs(self.addonProfilePath)
+            return False
+
+        daysInSecs = int(days)*60*60*24
+
+        file = "%s%s" % (self.addonProfilePath, filename)
+
+        # If file exists, check timestamp
+        if os.path.exists(file):
+            if os.path.getmtime(file) > (time.time() - daysInSecs):
+                xbmc.log('It has not been %s days since %s was last updated' % 
(days, file), xbmc.LOGNOTICE)
+                return False
+            else:
+                xbmc.log('The cache file %s + has expired' % file, 
xbmc.LOGNOTICE)
+                return True
+        # If file does not exist, return true so the file will be created by 
scraping the page
+        else:
+            xbmc.log('The cache file %s does not exist' % file, xbmc.LOGNOTICE)
+            return True
+
+
 MusicAddonInstance = musicAddonXbmc()
-MusicAddonInstance.start()
+MusicAddonInstance.run()
diff --git a/plugin.audio.di.fm/httpcomm.py b/plugin.audio.di.fm/httpcomm.py
index e7d0b05..94e99dd 100644
--- a/plugin.audio.di.fm/httpcomm.py
+++ b/plugin.audio.di.fm/httpcomm.py
@@ -6,100 +6,53 @@ import gzip
 import StringIO
 import cookielib
 import socket
-import urllib2
-
-class HTTPComm :
-       # Login
-       
-       _cj = cookielib.CookieJar()
-       
-       def post( self, url, postdata ) :
-               timeout = 10
-               socket.setdefaulttimeout(timeout)
-
-               
-               # create an opener
-               opener = 
urllib2.build_opener(urllib2.HTTPCookieProcessor(self._cj))
-
-               # Add useragent, sites don't like to interact with scripts
-               opener.addheaders = [
-                       ('User-Agent', 'Mozilla/5.0 (X11; U; Linux i686; en-US; 
rv:1.9.2.8) Gecko/20100723 Ubuntu/10.04 (lucid) Firefox/3.6.8'),
-                       ('Accept', 
'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'),
-                       ('Accept-Language', 'en-gb,en;q=0.5'),
-                       ('Accept-Encoding', 'gzip,deflate'),
-                       ('Accept-Charset', 'ISO-8859-1,utf-8;q=0.7,*;q=0.7'),
-                       ('Keep-Alive', '115'),
-                       ('Connection', 'keep-alive'),
-                       ('Cache-Control', 'max-age=0'),
-                       ]
-               
-               resp = opener.open(url, postdata)
-       
-               # Compressed (gzip) response...
-               if resp.headers.get( "content-encoding" ) == "gzip":
-                       htmlGzippedData = resp.read()
-                       stringIO        = StringIO.StringIO( htmlGzippedData )
-                       gzipper         = gzip.GzipFile( fileobj = stringIO )
-                       htmlData        = gzipper.read()
-               else :
-                       htmlData = resp.read()
-                       
-               resp.close()
-               
-               # Return html
-               return htmlData
-
-       # GET
-       def get( self, url ):
-               timeout = 10
-               socket.setdefaulttimeout(timeout)
-
-               #create an opener
-               opener = 
urllib2.build_opener(urllib2.HTTPCookieProcessor(self._cj))
-               #Add useragent, sites don't like to interact with scripts
-               opener.addheaders = [
-                       ('User-Agent', 'Mozilla/5.0 (X11; U; Linux i686; en-US; 
rv:1.9.2.8) Gecko/20100723 Ubuntu/10.04 (lucid) Firefox/3.6.8'),
-                       ('Accept', 
'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'),
-                       ('Accept-Language', 'en-gb,en;q=0.5'),
-                       ('Accept-Encoding', 'gzip,deflate'),
-                       ('Accept-Charset', 'ISO-8859-1,utf-8;q=0.7,*;q=0.7'),
-                       ('Keep-Alive', '115'),
-                       ('Connection', 'keep-alive'),
-                       ('Cache-Control', 'max-age=0'),
-                       ]
-               try :
-                       resp = opener.open(url)
-
-                       # Compressed (gzip) response...
-                       if resp.headers.get( "content-encoding" ) == "gzip":
-                               htmlGzippedData = resp.read()
-                               stringIO        = StringIO.StringIO( 
htmlGzippedData )
-                               gzipper         = gzip.GzipFile( fileobj = 
stringIO )
-                               htmlData        = gzipper.read()
-                       else :
-                               htmlData = resp.read()
-               
-                       resp.close()
-               
-                       # Return html
-                       return htmlData
-               except Exception:
-                       return False
-
-       def getImage( self, url, path ) :
-               timeout = 10
-               socket.setdefaulttimeout(timeout)
-
-               try :
-                       # Set useragent, sites don't like to interact with 
scripts
-                       headers = { 'User-Agent':'Mozilla/5.0 (X11; U; Linux 
i686; en-US; rv:1.9.2.8) Gecko/20100723 Ubuntu/10.04 (lucid) 
Firefox/3.6.8','Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8','Accept-Language':'en-us,en;q=0.5','Accept-Charset':'ISO-8859-1,utf-8;q=0.7,*;q=0.7'}
-                       req = urllib2.Request(url=url, headers=headers)
-                       f = urllib2.urlopen(req)
-                       imagedata = f.read()            # Downloads imagedata
 
-                       open(path, 'wb').write(imagedata)
 
-                       # Return true
-                       return True
-               except Exception:
-                       return False
+class HTTPComm:
+    cj = None
+    curlinstance = None
+
+    def __init__(self):
+        self.cj = cookielib.CookieJar()
+        self.curlinstance = 
urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cj))
+
+    def request(self, url, mode, postdata=None):
+        timeout = 10
+        socket.setdefaulttimeout(timeout)
+
+        # Add useragent and tries to look like a real browser, as the site 
doesn't like to interact with scripts
+        self.curlinstance.addheaders = [
+            ('User-Agent',
+             'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:26.0) Gecko/20100101 
Firefox/26.0'),
+            ('Accept', 
'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'),
+            ('Accept-Language', 'en-gb,en;q=0.5'),
+            ('Accept-Encoding', 'gzip,deflate'),
+            ('Accept-Charset', 'ISO-8859-1,utf-8;q=0.7,*;q=0.7'),
+            ('Keep-Alive', '115'),
+            ('Connection', 'keep-alive'),
+            ('Cache-Control', 'max-age=0'),
+        ]
+
+        try:
+            if mode=='get':
+                resp = self.curlinstance.open(url)
+            elif mode is 'post' and postdata:
+                resp = self.curlinstance.open(url, postdata)
+            else:
+                return False
+
+            # decompress if gzipped response...
+            if resp.headers.get("content-encoding") == "gzip":
+                htmlGzippedData = resp.read()
+                stringIO = StringIO.StringIO(htmlGzippedData)
+                gzipper = gzip.GzipFile(fileobj=stringIO)
+                data = gzipper.read()
+            else:
+                data = resp.read()
+
+            resp.close()
+
+            # Return response data
+            return data
+        except Exception:
+            return False
diff --git a/plugin.audio.di.fm/resources/language/English/strings.xml 
b/plugin.audio.di.fm/resources/language/English/strings.xml
index 237d247..d24aff2 100644
--- a/plugin.audio.di.fm/resources/language/English/strings.xml
+++ b/plugin.audio.di.fm/resources/language/English/strings.xml
@@ -1,24 +1,30 @@
 <?xml version="1.0" encoding="utf-8" standalone="yes"?>
 <strings>
-       <!-- The strings 30000 to 30999 is reserved for plug-in paramters -->
+       <!-- The strings 30000 to 30999 is reserved for plug-in parameters -->
        <!--GENERAL STRINGS -->
-       <string id="30001">Stream source</string>
+
+       <string id="30014">General</string>
+       <string id="30011">Sort A-Z (also affects 'My Favorites')</string>
+       <string id="30030">Use first stream for each channel (starts playing 
faster)</string>
+       <string id="30012">Stream cache expiration in days (0 = never)</string>
        <string id="30010">Force cache refresh (use when changing 
settings)</string>
+    <string id="30031">Download random stream from each channel (faster after 
caching)</string>
+
+       <string id="30001">Stream source</string>
+
        <string id="30002">Premium</string>
        <string id="30003">E-mail/username</string>
        <string id="30004">Password</string>
        <string id="30005">Use 'My favorites' playlist</string>
        <string id="30013">Stream bitrate</string>
+
        <string id="30006">About</string>
        <string id="30007">Version</string>
        <string id="30008">Author</string>
        <string id="30009">Homepage</string>
-       <string id="30011">Sort A-Z (also affects 'My Favorites')</string>
-       <string id="30012">Stream cache expiration in minutes (0 = 
never)</string>
-       <string id="30014">General</string>
 
-       <string id="30015">Get HQ streams with DI Premium at 
http://www.di.fm</string>
-       <string id="30016">Version:  2.1.2</string>
+       <string id="30015">Get HQ 256kb/sec streams with DI Premium at 
http://www.di.fm</string>
+       <string id="30016">Version:  3.0.0</string>
        <string id="30017">Author:  Tim C. 'Bitcrusher' Steinmetz</string>
        <string id="30018">Homepage:  http://qualisoft.dk</string>
        <string id="30019">E-mail:  [email protected]</string>
@@ -33,15 +39,14 @@
        <string id="30027">Accounttype (public/premium), plugin version, 
country.</string>
        <string id="30028">I know it's a bit of a cheap trick, having it on by 
default ;)</string>
        <string id="30029">-Tim</string>
-       
-       <string id="30030">Use first stream from each channel (Starts playing 
faster)</string>
 
        <string id="30100">Connection error</string>
        <string id="30101">Could not connect to di.fm</string>
        <string id="30102">Check your internet connection</string>
+    <string id="30103">and the website in your browser</string>
 
        <string id="30110">No streams found</string>
-       <string id="30111">Maybe your DI Premium membership expired</string>
+       <string id="30111">Maybe your DI Premium membership expired?</string>
        <string id="30112">or the plugin has broken (the HTML layout 
changed)</string>
 
        <string id="30120">Connection timed out</string>
@@ -51,7 +56,7 @@
        <string id="30131">There was found </string>
        <string id="30132"> new piece(s) of channelart</string>
        <string id="30133">meaning there could be new channels.</string>
-       <string id="30134">The new channels are highlighted in blue</string>
+       <string id="30134">New channels are highlighted in blue</string>
 
        <string id="30140">Cachefiles are missing</string>
        <string id="30141">At least one of the cachefiles is missing</string>
@@ -68,4 +73,13 @@
        <string id="30161">There was a problem downloading channelart for: 
</string>
        <string id="30162">From URL: </string>
 
+    <string id="30170">Login failed</string>
+    <string id="30171">Invalid Username or Password</string>
+    <string id="30172">Has your account perhaps expired?</string>
+
+    <string id="30180">No favorites found</string>
+    <string id="30181">Login to di.fm in your browser and go to 'Manage 
favorites'</string>
+    <string id="30182">or uncheck 'Use my favorites' from this plugins 
settings</string>
+
+
 </strings>
diff --git a/plugin.audio.di.fm/resources/settings.xml 
b/plugin.audio.di.fm/resources/settings.xml
index e392fff..4f47d2f 100644
--- a/plugin.audio.di.fm/resources/settings.xml
+++ b/plugin.audio.di.fm/resources/settings.xml
@@ -2,35 +2,25 @@
 <settings>
        <category label="30014">
                <setting id="sortaz" type="bool" label="30011" default="true" />
-               <setting id="cacheexpire" type="number" label="30012" 
default="720" />
-               <setting id="forceupdate" type="bool" label="30010" 
default="false" />
+               <setting id="randomstream" type="bool" label="30031" 
default="true" />
+        <setting id="cacheexpire_days" type="number" label="30012" default="7" 
/>
+        <setting id="forceupdate" type="bool" label="30010" default="false" />
                <setting id="getpremium" type="lsep" label="30015" />
-               <setting type="lsep" label="30017" />
-               <setting type="lsep" label="30018" />
-               <setting type="lsep" label="30019" />
-               <setting type="lsep" label="30016" />
        </category>
        <category label="30002">
                <setting id="username" type="text" label="30003" />
                <setting id="password" type="text" option="hidden" 
label="30004" />
-               <setting id="bitrate" type="enum" label="30013"
-               values="40|64|128|256" default="3" />
+               <setting id="bitrate" type="enum" label="30013" 
values="40|64|128|256" default="4" />
                <setting id="usefavorites" type="bool" label="30005" 
default="false" />
                <setting id="forceupdate" type="bool" label="30010" 
default="false" />
+               <setting id="getpremium" type="lsep" label="30015" />
+       </category>
+
+       <category label="30006">
+               <setting id="getpremium" type="lsep" label="30015" />
                <setting type="lsep" label="30017" />
                <setting type="lsep" label="30018" />
                <setting type="lsep" label="30019" />
                <setting type="lsep" label="30016" />
        </category>
-       <category label="30020">
-               <setting id="allowstats" type="bool" label="30021" 
default="true" />
-               <setting type="lsep" label="30022" />
-               <setting type="lsep" label="30023" />
-               <setting type="lsep" label="30024" />
-               <setting type="lsep" label="30025" />
-               <setting type="lsep" label="30026" />
-               <setting type="lsep" label="30027" />
-               <setting type="lsep" label="30028" />
-               <setting type="lsep" label="30029" />
-       </category>
-</settings> 
+</settings>

http://xbmc.git.sourceforge.net/git/gitweb.cgi?p=xbmc/plugins;a=commit;h=b485d7be79860ca0bfb3ec3d67970822b7199fa4


http://xbmc.git.sourceforge.net/git/gitweb.cgi?p=xbmc/plugins;a=commit;h=f76b6e2728f664604bef7423a752d1ef5c432829


-----------------------------------------------------------------------

Summary of changes:
 plugin.audio.di.fm/addon.xml                       |    2 +-
 plugin.audio.di.fm/changelog.txt                   |    8 +
 plugin.audio.di.fm/config.ini                      |   34 +-
 plugin.audio.di.fm/default.py                      |  742 ++++++++++----------
 plugin.audio.di.fm/httpcomm.py                     |  143 ++---
 .../resources/language/English/strings.xml         |   36 +-
 plugin.audio.di.fm/resources/settings.xml          |   30 +-
 .../LICENSE.txt                                    |    0
 plugin.video.sbview/addon.xml                      |   32 +
 plugin.video.sbview/default.py                     |  343 +++++++++
 plugin.video.sbview/icon.png                       |  Bin 0 -> 27410 bytes
 .../resources/__init__.py                          |    0
 .../resources/language/english/strings.xml         |   15 +
 .../resources/lib}/__init__.py                     |    0
 plugin.video.sbview/resources/settings.xml         |    9 +
 plugin.video.udacity/LICENSE.txt                   |   19 +
 plugin.video.udacity/README.md                     |   24 +
 plugin.video.udacity/addon.py                      |  183 +++++
 plugin.video.udacity/addon.xml                     |   25 +
 plugin.video.udacity/changelog.txt                 |   33 +
 plugin.video.udacity/fanart.jpg                    |  Bin 0 -> 142922 bytes
 plugin.video.udacity/icon.png                      |  Bin 0 -> 46073 bytes
 .../resources/__init__.py                          |    0
 .../resources/language/English/strings.xml         |   15 +
 .../resources/lib}/__init__.py                     |    0
 plugin.video.udacity/resources/lib/controls.py     |  146 ++++
 plugin.video.udacity/resources/lib/udacity.py      |  226 ++++++
 plugin.video.udacity/resources/media/blank.png     |  Bin 0 -> 3630 bytes
 plugin.video.udacity/resources/settings.xml        |    8 +
 plugin.video.udacity/resources/tests/test_addon.py |   41 ++
 30 files changed, 1603 insertions(+), 511 deletions(-)
 copy {plugin.audio.dradio => plugin.video.sbview}/LICENSE.txt (100%)
 create mode 100644 plugin.video.sbview/addon.xml
 create mode 100644 plugin.video.sbview/default.py
 create mode 100644 plugin.video.sbview/icon.png
 copy {plugin.image.ilpiolatracker => 
plugin.video.sbview}/resources/__init__.py (100%)
 create mode 100644 plugin.video.sbview/resources/language/english/strings.xml
 copy {plugin.image.ilpiolatracker/resources => 
plugin.video.sbview/resources/lib}/__init__.py (100%)
 create mode 100644 plugin.video.sbview/resources/settings.xml
 create mode 100644 plugin.video.udacity/LICENSE.txt
 create mode 100644 plugin.video.udacity/README.md
 create mode 100644 plugin.video.udacity/addon.py
 create mode 100644 plugin.video.udacity/addon.xml
 create mode 100644 plugin.video.udacity/changelog.txt
 create mode 100755 plugin.video.udacity/fanart.jpg
 create mode 100755 plugin.video.udacity/icon.png
 copy {plugin.audio.jambmc => plugin.video.udacity}/resources/__init__.py (100%)
 create mode 100644 plugin.video.udacity/resources/language/English/strings.xml
 copy {plugin.audio.jambmc/resources => 
plugin.video.udacity/resources/lib}/__init__.py (100%)
 create mode 100644 plugin.video.udacity/resources/lib/controls.py
 create mode 100644 plugin.video.udacity/resources/lib/udacity.py
 create mode 100644 plugin.video.udacity/resources/media/blank.png
 create mode 100644 plugin.video.udacity/resources/settings.xml
 create mode 100644 plugin.video.udacity/resources/tests/test_addon.py


hooks/post-receive
-- 
Plugins

------------------------------------------------------------------------------
Rapidly troubleshoot problems before they affect your business. Most IT 
organizations don't have a clear picture of how application performance 
affects their revenue. With AppDynamics, you get 100% visibility into your 
Java,.NET, & PHP application. Start your 15-day FREE TRIAL of AppDynamics Pro!
http://pubads.g.doubleclick.net/gampad/clk?id=84349831&iu=/4140/ostg.clktrk
_______________________________________________
Xbmc-addons mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/xbmc-addons

Reply via email to