2 # Copyright (C) 2008 jerous <jerous@gmail.com>
3 # Copyright (C) 2009 Anton Khirnov <wyskas@gmail.com>
5 # Nephilim is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
10 # Nephilim is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with Nephilim. If not, see <http://www.gnu.org/licenses/>.
19 from PyQt4
import QtCore
, QtGui
, QtNetwork
20 from PyQt4
.QtCore
import pyqtSignal
as Signal
25 from htmlentitydefs
import name2codepoint
as n2cp
27 socket
.setdefaulttimeout(8)
29 appIcon
= ':icons/nephilim_small.png'
33 # custom mimetypes used for drag&drop
34 MIMETYPES
= {'songs' : 'application/x-mpd-songlist', 'plistsongs' : 'application/x-mpd-playlistsonglist'}
37 """Converts seconds to min:sec."""
40 if sec
<10:sec
='0'+str(sec
)
41 return str(min)+':'+str(sec
)
43 class Button(QtGui
.QPushButton
):
45 """A simple Button class which calls $onClick when clicked."""
46 def __init__(self
, caption
, onClick
=None, iconPath
=None, iconOnly
=False, parent
=None):
47 QtGui
.QPushButton
.__init
__(self
, parent
)
50 self
.clicked
.connect(onClick
)
52 self
.changeIcon(iconPath
)
54 if not(iconPath
and iconOnly
):
55 QtGui
.QPushButton
.setText(self
, caption
)
57 self
.setToolTip(caption
)
59 def setText(self
, caption
):
60 self
.setToolTip(caption
)
64 def changeIcon(self
, iconPath
):
66 icon
.addFile(iconPath
, QtCore
.QSize(self
.iconSize
, self
.iconSize
))
69 def expand_tags(string
, expanders
):
70 for expander
in expanders
:
71 string
= expander
.expand_tags(string
)
73 #remove unexpanded tags
74 return re
.sub('\$\{.*\}', '', string
)
76 def generate_metadata_path(song
, dir_tag
, file_tag
):
77 """Generate dirname and (db files only) full file path for reading/writing metadata files
78 (cover, lyrics) from $tags in dir/filename."""
79 if QtCore
.QDir
.isAbsolutePath(song
['file']):
80 dirname
= os
.path
.dirname(song
['file'])
82 elif '://' in song
['file']: # we are streaming
86 dirname
= expand_tags(dir_tag
, (QtGui
.QApplication
.instance(), song
))
87 filepath
= '%s/%s'%(dirname
, expand_tags(file_tag
, (QtGui
.QApplication
.instance(), song
)).replace('/', '_'))
89 return dirname
, filepath
91 def substitute_entity(match
):
93 if match
.group(1) == "#":
94 if match
.group(2) == '':
95 return unichr(int(ent
))
96 elif match
.group(2) == 'x':
97 return unichr(int('0x'+ent
, 16))
105 def decode_htmlentities(string
):
106 entity_re
= re
.compile(r
'&(#?)(x?)(\w+);')
107 return entity_re
.subn(substitute_entity
, string
)[0]
109 class MetadataFetcher(QtCore
.QObject
):
110 """A basic class for metadata fetchers. Provides a fetch(song) function,
111 emits a finished(song, metadata) signal when done; lyrics is either a Python
112 unicode string or None if not found."""
118 nam
= None # NetworkAccessManager
119 rep
= None # current NetworkReply.
120 song
= None # current song
123 finished
= Signal(['song', 'metadata'])
126 def __init__(self
, plugin
):
127 QtCore
.QObject
.__init
__(self
, plugin
)
129 self
.nam
= QtNetwork
.QNetworkAccessManager()
130 self
.logger
= plugin
.logger
132 def fetch2(self
, song
, url
):
133 """A private convenience function to initiate fetch process."""
134 # abort any existing connections
138 self
.logger
.info('Searching %s: %s.'%(self
. name
, url
.toString()))
139 self
.rep
= self
.nam
.get(QtNetwork
.QNetworkRequest(url
))
140 self
.rep
.error
.connect(self
.handle_error
)
142 def finish(self
, metadata
= None):
143 """A private convenience function to clean up and emit finished().
144 Feel free to reimplement/not use it."""
146 self
.finished
.emit(self
.song
, metadata
)
149 def handle_error(self
):
150 """Print the error and abort."""
151 self
.logger
.error(self
.rep
.errorString())
156 def fetch(self
, song
):
157 """Reimplement this in subclasses."""
161 """Abort all downloads currently in progress."""
163 self
.rep
.blockSignals(True)
167 class SongsMimeData(QtCore
.QMimeData
):
172 def set_songs(self
, songs
):
178 def set_plistsongs(self
, songs
):
179 self
.__plistsongs
= songs
181 def plistsongs(self
):
182 return self
.__plistsongs
185 types
= QtCore
.QMimeData
.formats(self
)
187 types
+= MIMETYPES
['songs']
188 if self
.__plistsongs
:
189 types
+= MIMETYPES
['plistsongs']
192 def hasFormat(self
, format
):
193 if format
== MIMETYPES
['songs'] and self
.__songs
:
195 elif format
== MIMETYPES
['plistsongs'] and self
.__plistsongs
:
197 return QtCore
.QMimeData
.hasFormat(self
, format
)