2 # -*- coding: utf-8 -*-
4 # Copyright (c) 2010-2015 Jack Kaliko <kaliko@azylum.org>
6 # This file is part of MPD_sima
8 # MPD_sima is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
13 # MPD_sima is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with MPD_sima. If not, see <http://www.gnu.org/licenses/>.
28 from argparse import (ArgumentParser, SUPPRESS, Action)
29 from difflib import get_close_matches
30 from locale import getpreferredencoding
31 from os import (environ, chmod, makedirs)
32 from os.path import (join, isdir, isfile, expanduser)
33 from sys import (exit, stdout, stderr)
35 from sima.lib.track import Track
36 from sima.utils import utils
37 from sima.lib import simadb
38 from musicpd import MPDClient, ConnectionError
42 simadb_cli helps you to edit entries in your own DB of similarity
47 # pop out 'sw' value before creating ArgumentParser object.
50 'sw':['-d', '--dbfile'],
53 'action': utils.Wfile,
54 'help': 'File to read/write database from/to'},
56 'sw': ['-S', '--host'],
60 'help': 'MPD host, as IP or FQDN (default: localhost|MPD_HOST).'},
62 'sw': ['-P', '--port'],
66 'help': 'Port MPD in listening on (default: 6600|MPD_PORT).'},
75 'action': 'store_true',
77 'help': 'View black list.'},
79 'sw': ['--remove_bl'],
81 'help': 'Suppress a black list entry, by row id. Use --view_bl to get row id.'},
85 'metavar': 'ARTIST_NAME',
86 'help': 'Black list artist.'},
88 'sw': ['--bl_curr_art'],
89 'action': 'store_true',
90 'help': 'Black list currently playing artist.'},
92 'sw': ['--bl_curr_alb'],
93 'action': 'store_true',
94 'help': 'Black list currently playing album.'},
96 'sw': ['--bl_curr_trk'],
97 'action': 'store_true',
98 'help': 'Black list currently playing track.'},
100 'sw':['--purge_hist'],
101 'action': 'store_true',
102 'dest': 'do_purge_hist',
103 'help': 'Purge play history.'}])
106 class SimaDB_CLI(object):
107 """Command line management.
111 self.dbfile = self._get_default_dbfile()
113 self.options = dict({})
114 self.localencoding = 'UTF-8'
118 def _get_encoding(self):
119 """Get local encoding"""
120 localencoding = getpreferredencoding()
122 self.localencoding = localencoding
124 def _get_mpd_env_var(self):
126 MPD host/port environement variables are used if command line does not
127 provide host|port|passwd.
129 host, port, passwd = utils.get_mpd_environ()
130 if self.options.passwd is None and passwd:
131 self.options.passwd = passwd
132 if self.options.mpdhost is None:
134 self.options.mpdhost = host
136 self.options.mpdhost = 'localhost'
137 if self.options.mpdport is None:
139 self.options.mpdport = port
141 self.options.mpdport = 6600
144 """Upgrades DB if necessary, create one if not existing."""
145 if not isfile(self.dbfile): # No db file
147 db = simadb.SimaDB(db_path=self.dbfile)
150 def _declare_opts(self):
152 Declare options in ArgumentParser object.
154 self.parser = ArgumentParser(description=DESCRIPTION,
155 usage='%(prog)s [-h|--help] [options]',
157 epilog='Happy Listening',
160 self.parser.add_argument('--version', action='version',
161 version='%(prog)s {0}'.format(__version__))
162 # Add all options declare in OPTS
164 opt_names = opt.pop('sw')
165 self.parser.add_argument(*opt_names, **opt)
167 def _get_default_dbfile(self):
169 Use XDG directory standard if exists
170 else use "HOME/.local/share/mpd_sima/"
171 http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html
173 homedir = expanduser('~')
175 if environ.get('XDG_DATA_HOME'):
176 data_dir = join(environ.get('XDG_DATA_HOME'), dirname)
178 data_dir = join(homedir, '.local', 'share', dirname)
179 if not isdir(data_dir):
181 chmod(data_dir, 0o700)
182 return join(data_dir, DB_NAME)
184 def _get_mpd_client(self):
186 # TODO: encode properly host name
187 host = self.options.mpdhost
188 port = self.options.mpdport
191 cli.connect(host=host, port=port)
192 except ConnectionError as err:
193 mess = 'ERROR: fail to connect MPD (host: %s:%s): %s' % (
195 print(mess, file=stderr)
199 def _create_db(self):
200 """Create database if necessary"""
201 if isfile(self.dbfile):
203 print('Creating database!')
204 open(self.dbfile, 'a').close()
205 simadb.SimaDB(db_path=self.dbfile).create_db()
207 def _get_art_from_db(self, art):
208 """Return (id, name, self...) from DB or None is not in DB"""
209 db = simadb.SimaDB(db_path=self.dbfile)
210 art_db = db.get_artist(art, add_not=True)
212 print('ERROR: "%s" not in data base!' % art, file=stderr)
216 def _control_similarity(self):
218 * Regex check of command line similarity
219 * Controls artist presence in MPD library
221 usage = ('USAGE: "main artist,similar artist:<match score>,other' +
222 'similar artist:<match score>,..."')
223 cli_sim = self.options.similarity
224 pattern = '^([^,]+?),([^:,]+?:\d{1,2},?)+$'
225 regexp = re.compile(pattern, re.U).match(cli_sim)
227 mess = 'ERROR: similarity badly formated: "%s"' % cli_sim
228 print(mess, file=stderr)
229 print(usage, file=stderr)
231 if self.options.check_names:
232 if not self._control_artist_names():
233 mess = 'ERROR: some artist names not found in MPD library!'
234 print(mess, file=stderr)
237 def _control_artist_names(self):
238 """Controls artist names exist in MPD library"""
239 mpd_cli = self._get_mpd_client()
240 artists_list = mpd_cli.list('artist')
241 sim_formated = self._parse_similarity()
243 if sim_formated[0] not in artists_list:
244 mess = 'WARNING: Main artist not found in MPD: %s' % sim_formated[0]
247 for sart in sim_formated[1]:
248 art = sart.get('artist')
249 if art not in artists_list:
250 mess = str('WARNING: Similar artist not found in MPD: %s' % art)
257 """Black list artist"""
258 mpd_cli = self._get_mpd_client()
261 artists_list = mpd_cli.list('artist')
262 # Unicode cli given artist name
263 cli_artist_to_bl = self.options.bl_art
264 if cli_artist_to_bl not in artists_list:
265 print('Artist not found in MPD library.')
266 match = get_close_matches(cli_artist_to_bl, artists_list, 50, 0.78)
268 print('You may be refering to %s' %
269 '/'.join([m_a for m_a in match]))
271 print('Black listing artist: %s' % cli_artist_to_bl)
272 db = simadb.SimaDB(db_path=self.dbfile)
273 db.get_bl_artist(cli_artist_to_bl)
275 def bl_current_artist(self):
276 """Black list current artist"""
277 mpd_cli = self._get_mpd_client()
280 artist = mpd_cli.currentsong().get('artist', '')
282 print('No artist found.')
284 print('Black listing artist: %s' % artist)
285 db = simadb.SimaDB(db_path=self.dbfile)
286 db.get_bl_artist(artist)
288 def bl_current_album(self):
289 """Black list current artist"""
290 mpd_cli = self._get_mpd_client()
293 track = Track(**mpd_cli.currentsong())
295 print('No album set for this track: %s' % track)
297 print('Black listing album: {0}'.format(track.album))
298 db = simadb.SimaDB(db_path=self.dbfile)
299 db.get_bl_album(track)
301 def bl_current_track(self):
302 """Black list current artist"""
303 mpd_cli = self._get_mpd_client()
306 track = Track(**mpd_cli.currentsong())
307 print('Black listing track: %s' % track)
308 db = simadb.SimaDB(db_path=self.dbfile)
309 db.get_bl_track(track)
311 def purge_history(self):
312 """Purge all entries in history"""
313 db = simadb.SimaDB(db_path=self.dbfile)
314 print('Purging history...')
315 db.purge_history(duration=int(0))
317 print('Cleaning database...')
322 """Print out black list."""
323 # TODO: enhance output formating
324 db = simadb.SimaDB(db_path=self.dbfile)
325 for bl_e in db.get_black_list():
326 print('\t# '.join([str(e) for e in bl_e]))
328 def remove_black_list_entry(self):
330 db = simadb.SimaDB(db_path=self.dbfile)
331 db._remove_bl(int(self.options.remove_bl))
335 Parse command line and run actions.
338 self.options = self.parser.parse_args()
339 self._get_mpd_env_var()
340 if self.options.dbfile:
341 self.dbfile = self.options.dbfile
342 print('Using db file: %s' % self.dbfile)
343 if self.options.bl_art:
346 if self.options.bl_curr_art:
347 self.bl_current_artist()
349 if self.options.bl_curr_alb:
350 self.bl_current_album()
352 if self.options.bl_curr_trk:
353 self.bl_current_track()
355 if self.options.view_bl:
358 if self.options.remove_bl:
359 self.remove_black_list_entry()
361 if self.options.do_purge_hist:
370 if __name__ == '__main__':
374 # vim: ai ts=4 sw=4 sts=4 expandtab