Playlist: add empty header_state to default settings
[nephilim.git] / nephilim / common.py
blob3bf9ff9657325e56d7a1c9b9d9404e75274c9264
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 import socket
21 import logging
22 import os
23 import re
24 from htmlentitydefs import name2codepoint as n2cp
26 socket.setdefaulttimeout(8)
28 appIcon = ':icons/nephilim_small.png'
29 APPNAME = 'nephilim'
30 ORGNAME = 'nephilim'
32 # custom mimetypes used for drag&drop
33 MIMETYPES = {'songs' : 'application/x-mpd-songlist', 'plistsongs' : 'application/x-mpd-playlistsonglist'}
35 def sec2min(secs):
36 """Converts seconds to min:sec."""
37 min=int(secs/60)
38 sec=secs%60
39 if sec<10:sec='0'+str(sec)
40 return str(min)+':'+str(sec)
42 class Button(QtGui.QPushButton):
43 iconSize=32
44 """A simple Button class which calls $onClick when clicked."""
45 def __init__(self, caption, onClick=None, iconPath=None, iconOnly=False, parent=None):
46 QtGui.QPushButton.__init__(self, parent)
48 if onClick:
49 self.clicked.connect(onClick)
50 if iconPath:
51 self.changeIcon(iconPath)
53 if not(iconPath and iconOnly):
54 QtGui.QPushButton.setText(self, caption)
56 self.setToolTip(caption)
58 def setText(self, caption):
59 self.setToolTip(caption)
60 if self.icon()==None:
61 self.setText(caption)
63 def changeIcon(self, iconPath):
64 icon=QtGui.QIcon()
65 icon.addFile(iconPath, QtCore.QSize(self.iconSize, self.iconSize))
66 self.setIcon(icon)
68 def expand_tags(string, expanders):
69 for expander in expanders:
70 string = expander.expand_tags(string)
72 #remove unexpanded tags
73 return re.sub('\$\{.*\}', '', string)
75 def generate_metadata_path(song, dir_tag, file_tag):
76 """Generate dirname and (db files only) full file path for reading/writing metadata files
77 (cover, lyrics) from $tags in dir/filename."""
78 if QtCore.QDir.isAbsolutePath(song['file']):
79 dirname = os.path.dirname(song['file'])
80 filepath = ''
81 elif '://' in song['file']: # we are streaming
82 dirname = ''
83 filepath = ''
84 else:
85 dirname = expand_tags(dir_tag, (QtGui.QApplication.instance(), song))
86 filepath = '%s/%s'%(dirname, expand_tags(file_tag, (QtGui.QApplication.instance(), song)).replace('/', '_'))
88 return dirname, filepath
90 def substitute_entity(match):
91 ent = match.group(3)
92 if match.group(1) == "#":
93 if match.group(2) == '':
94 return unichr(int(ent))
95 elif match.group(2) == 'x':
96 return unichr(int('0x'+ent, 16))
97 else:
98 cp = n2cp.get(ent)
99 if cp:
100 return unichr(cp)
101 else:
102 return match.group()
104 def decode_htmlentities(string):
105 entity_re = re.compile(r'&(#?)(x?)(\w+);')
106 return entity_re.subn(substitute_entity, string)[0]
108 class MetadataFetcher(QtCore.QObject):
109 """A basic class for metadata fetchers. Provides a fetch(song) function,
110 emits a finished(song, metadata) signal when done; lyrics is either a Python
111 unicode string or None if not found."""
112 #public, read-only
113 logger = None
114 name = ''
116 #private
117 nam = None # NetworkAccessManager
118 rep = None # current NetworkReply.
119 song = None # current song
121 # SIGNALS
122 finished = QtCore.pyqtSignal(['song', 'metadata'])
124 #### private ####
125 def __init__(self, plugin):
126 QtCore.QObject.__init__(self, plugin)
128 self.nam = QtNetwork.QNetworkAccessManager()
129 self.logger = plugin.logger
131 def fetch2(self, song, url):
132 """A private convenience function to initiate fetch process."""
133 # abort any existing connections
134 self.abort()
135 self.song = song
137 self.logger.info('Searching %s: %s.'%(self. name, url.toString()))
138 self.rep = self.nam.get(QtNetwork.QNetworkRequest(url))
139 self.rep.error.connect(self.handle_error)
141 def finish(self, metadata = None):
142 """A private convenience function to clean up and emit finished().
143 Feel free to reimplement/not use it."""
144 self.rep = None
145 self.finished.emit(self.song, metadata)
146 self.song = None
148 def handle_error(self):
149 """Print the error and abort."""
150 self.logger.error(self.rep.errorString())
151 self.abort()
152 self.finish()
154 #### public ####
155 def fetch(self, song):
156 """Reimplement this in subclasses."""
157 pass
159 def abort(self):
160 """Abort all downloads currently in progress."""
161 if self.rep:
162 self.rep.blockSignals(True)
163 self.rep.abort()
164 self.rep = None
166 class SongsMimeData(QtCore.QMimeData):
167 # private
168 __songs = None
169 __plistsongs = None
171 def set_songs(self, songs):
172 self.__songs = songs
174 def songs(self):
175 return self.__songs
177 def set_plistsongs(self, songs):
178 self.__plistsongs = songs
180 def plistsongs(self):
181 return self.__plistsongs
183 def formats(self):
184 types = QtCore.QMimeData.formats(self)
185 if self.__songs:
186 types += MIMETYPES['songs']
187 if self.__plistsongs:
188 types += MIMETYPES['plistsongs']
189 return types
191 def hasFormat(self, format):
192 if format == MIMETYPES['songs'] and self.__songs:
193 return True
194 elif format == MIMETYPES['plistsongs'] and self.__plistsongs:
195 return True
196 return QtCore.QMimeData.hasFormat(self, format)