SongInfo: fix saving settings
[nephilim.git] / nephilim / common.py
blob35e041f034f5c2fe184edf9bd53569eca888ac78
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
21 import socket
22 import logging
23 import os
24 import re
25 from htmlentitydefs import name2codepoint as n2cp
27 socket.setdefaulttimeout(8)
29 appIcon = ':icons/nephilim_small.png'
30 APPNAME = 'nephilim'
31 ORGNAME = 'nephilim'
33 # custom mimetypes used for drag&drop
34 MIMETYPES = {'songs' : 'application/x-mpd-songlist', 'plistsongs' : 'application/x-mpd-playlistsonglist'}
36 def sec2min(secs):
37 """Converts seconds to min:sec."""
38 min=int(secs/60)
39 sec=secs%60
40 if sec<10:sec='0'+str(sec)
41 return str(min)+':'+str(sec)
43 class Button(QtGui.QPushButton):
44 iconSize=32
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)
49 if onClick:
50 self.clicked.connect(onClick)
51 if iconPath:
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)
61 if self.icon()==None:
62 self.setText(caption)
64 def changeIcon(self, iconPath):
65 icon=QtGui.QIcon()
66 icon.addFile(iconPath, QtCore.QSize(self.iconSize, self.iconSize))
67 self.setIcon(icon)
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'])
81 filepath = ''
82 elif '://' in song['file']: # we are streaming
83 dirname = ''
84 filepath = ''
85 else:
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):
92 ent = match.group(3)
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))
98 else:
99 cp = n2cp.get(ent)
100 if cp:
101 return unichr(cp)
102 else:
103 return match.group()
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."""
113 #public, read-only
114 logger = None
115 name = ''
117 #private
118 nam = None # NetworkAccessManager
119 rep = None # current NetworkReply.
120 song = None # current song
122 # SIGNALS
123 finished = Signal(['song', 'metadata'])
125 #### private ####
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
135 self.abort()
136 self.song = song
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."""
145 self.rep = None
146 self.finished.emit(self.song, metadata)
147 self.song = None
149 def handle_error(self):
150 """Print the error and abort."""
151 self.logger.error(self.rep.errorString())
152 self.abort()
153 self.finish()
155 #### public ####
156 def fetch(self, song):
157 """Reimplement this in subclasses."""
158 pass
160 def abort(self):
161 """Abort all downloads currently in progress."""
162 if self.rep:
163 self.rep.blockSignals(True)
164 self.rep.abort()
165 self.rep = None
167 class SongsMimeData(QtCore.QMimeData):
168 # private
169 __songs = None
170 __plistsongs = None
172 def set_songs(self, songs):
173 self.__songs = songs
175 def songs(self):
176 return self.__songs
178 def set_plistsongs(self, songs):
179 self.__plistsongs = songs
181 def plistsongs(self):
182 return self.__plistsongs
184 def formats(self):
185 types = QtCore.QMimeData.formats(self)
186 if self.__songs:
187 types += MIMETYPES['songs']
188 if self.__plistsongs:
189 types += MIMETYPES['plistsongs']
190 return types
192 def hasFormat(self, format):
193 if format == MIMETYPES['songs'] and self.__songs:
194 return True
195 elif format == MIMETYPES['plistsongs'] and self.__plistsongs:
196 return True
197 return QtCore.QMimeData.hasFormat(self, format)