Permalink
Browse files

initial import

--HG--
extra : convert_revision : svn%3A41726ec3-264d-0410-9c23-a9f1637257cc/trunk%402
  • Loading branch information...
1 parent c1ed60a commit ee7bb4b9e8932cc186e46e7846a2d0006535c0c5 adrian.sampson committed May 14, 2008
View
@@ -0,0 +1,2 @@
+#!/usr/bin/env python
+from beets.library import Library
View
@@ -0,0 +1,167 @@
+#!/usr/bin/env python
+import sqlite3, os, sys, operator
+import beets.tag
+from string import Template
+
+# Fields in the "items" table; all the metadata available for items in the
+# library. These are used directly in SQL; they are vulnerable to injection if
+# accessible to the user.
+metadata_fields = [
+ ('title', 'text'),
+ ('artist', 'text'),
+ ('album', 'text'),
+ ('genre', 'text'),
+ ('composer', 'text'),
+ ('grouping', 'text'),
+ ('year', 'int'),
+ ('track', 'int'),
+ ('maxtrack', 'int'),
+ ('disc', 'int'),
+ ('maxdisc', 'int'),
+ ('lyrics', 'text'),
+ ('comments', 'text'),
+ ('bpm', 'int'),
+ ('comp', 'bool')
+]
+
+class Library(object):
+ def __init__(self, path='library.blb'):
+ self.path = path
+ self.conn = sqlite3.connect(self.path)
+ self.conn.row_factory = sqlite3.Row
+ self.setup()
+
+ def setup(self):
+ "Set up the schema of the library file."
+
+ # options (library data) table
+ setup_sql = """
+ create table if not exists options (
+ key text primary key,
+ value text
+ );"""
+
+ # items (things in the library) table
+ setup_sql += """create table if not exists items (
+ path text primary key, """
+ setup_sql += ', '.join(map(' '.join, metadata_fields))
+ setup_sql += ' );'
+
+ c = self.conn.cursor()
+ c.executescript(setup_sql)
+ c.close()
+ self.conn.commit()
+
+ # DATABASE UTILITY FUNCTIONS
+ def select(self, where='', subvars=[], columns='*'):
+ "Look up items in the library. Returns a cursor."
+ c = self.conn.cursor()
+ if where.strip(): # we have a where clause
+ where = ' where ' + where
+ c.execute('select ' + columns + ' from items' + where,
+ subvars)
+ return c
+ def selects_any(self, where, subvars):
+ "Returns True iff the SELECT query matches any rows."
+ c = self.select(where, subvars)
+ out = (c.fetchone() is not None)
+ c.close()
+
+ # FILE/DB UTILITY FUNCTIONS
+ def add_file(self, path):
+ "Adds a new file to the library."
+
+ # build query part for metadata fields
+ columns = ','.join(map(operator.itemgetter(0),metadata_fields))
+ values = ','.join(['?']*len(metadata_fields))
+ subvars = []
+ f = beets.tag.MediaFile(path)
+ for field, value in metadata_fields:
+ subvars.append(getattr(f, field))
+
+ # other fields
+ columns += ',path'
+ values += ',?'
+ subvars.append(path)
+
+ # issue query
+ c = self.conn.cursor()
+ query = 'insert into items (' + columns + ') values (' + values + ')'
+ c.execute(query, subvars)
+ c.close()
+
+ def update_file(self, path):
+ "Updates a file already in the database with the file's metadata."
+
+ # build query part for metadata fields
+ assignments = ','.join(['?=?']*len(metadata_fields))
+ subvars = []
+ f = beets.tag.MediaFile(path)
+ for field, value in metadata_fields:
+ subvars += [field, getattr(f, field)]
+
+ # build the full query itself
+ query = 'update items set ' + assignments + ' where path=?'
+ subvars.append(path)
+
+ c = self.conn.cursor()
+ c.execute(query, subvars)
+ c.close()
+
+ # MISC. UTILITY FUNCTIONS
+ def mynormpath(self, path):
+ """Provide the canonical form of the path suitable for storing in the
+ database. In the future, options may modify the behavior of this
+ method."""
+ # force absolute paths:
+ # os.path.normpath(os.path.abspath(os.path.expanduser(path)))
+ return os.path.normpath(os.path.expanduser(path))
+ def log(self, msg):
+ """Print a log message."""
+ print >>sys.stderr, msg
+ def pprint(self, item, form='$artist - $title'):
+ print Template(form).substitute(item)
+
+ def add_path(self, path, clobber=False):
+ """Add a file to the library or recursively search a directory and add
+ all its contents."""
+ if os.path.isdir(path):
+ # recurse into all directory contents
+ for ent in os.listdir(path):
+ self.add_path(path + os.sep + ent, clobber)
+ elif os.path.isfile(path):
+ # add _if_ it's legible (otherwise ignore but say so)
+ if self.selects_any('path=?', (self.mynormpath(path),)):
+ if not clobber:
+ self.log(path + ' already in database, skipping')
+ return
+ else:
+ self.update_file(self.mynormpath(path))
+ else:
+ try:
+ self.add_file(self.mynormpath(path))
+ except beets.tag.FileTypeError:
+ self.log(path + ' of unknown type, skipping')
+ elif not os.path.exists(path):
+ raise IOError('file not found: ' + path)
+
+ # high-level (and command-line) interface
+ def add(self, *paths):
+ for path in paths:
+ self.add_path(path, clobber=False)
+ self.conn.commit()
+ def remove(self, *criteria):
+ raise NotImplementedError
+ def update(self, *criteria):
+ #c = self.select(criteria, [], 'path')
+ #for f in c:
+ # self.update_file(f.path)
+ #c.close()
+ raise NotImplementedError
+ def write(self, *criteria):
+ raise NotImplementedError
+ def list(self, *criteria):
+ c = self.select(' '.join(criteria), [])
+ for row in c:
+ self.pprint(row)
+ c.close()
Oops, something went wrong.

0 comments on commit ee7bb4b

Please sign in to comment.