Switch to side-by-side view

--- a/src/mediaserver/cdplugins/uprcl/uprcltags.py
+++ b/src/mediaserver/cdplugins/uprcl/uprcltags.py
@@ -25,27 +25,6 @@
 from uprclutils import g_myprefix, audiomtypes, docfolder, \
      rcldirentry, rcldoctoentry, cmpentries
 
-# After initialization, this holds the list of all records out of
-# recoll (it's a reference to the original in uprclfolders)
-g_alldocs = []
-sqconn = None
-
-def _sqconn():
-    # We use a separate thread for building the db to ensure
-    # responsiveness during this phase.  :memory: handles normally
-    # can't be shared between threads, and different :memory: handles
-    # access different dbs. The following would work, but it needs
-    # python 3.4
-    #sqconn = sqlite3.connect('file:uprcl_db?mode=memory&cache=shared')
-    # As we can guarantee that 2 threads will never access the db at
-    # the same time (the init thread just goes away when it's done),
-    # we just disable the same_thread checking on :memory:
-    global sqconn
-    if sqconn is None:
-        sqconn = sqlite3.connect(':memory:', check_same_thread=False)
-        #sqconn = sqlite3.connect('/tmp/tracks.sqlite', check_same_thread=False)
-    return sqconn
-
 # Tags for which we create auxiliary tables for facet descent.
 #
 # TBD: The list will come from the config file one day
@@ -55,7 +34,7 @@
 #
 # Maybe we'd actually need a 3rd value for the recoll field name, but
 # it can be the same for the currently relevant fields.
-tagtables = {
+_tagtotable = {
     'Artist' : 'artist',
     'Date' : 'date',
     'Genre' : 'genre',
@@ -69,434 +48,464 @@
 
 # Translation only used when fetching fields from the recoll
 # record. None at the moment
-coltorclfield = {
+_coltorclfield = {
     }
 
 
 def _clid(col):
     return col + '_id'
 
-# Create the db. Each tag table has 2 columns: <tagname>_id and
-# value. The join column in the main tracks table is also named
-# <tagname>_id
-def _createsqdb(conn):
-    c = conn.cursor()
-    try:
-        c.execute('''DROP TABLE albums''')
-        c.execute('''DROP TABLE tracks''')
-    except:
-        pass
-    c.execute(
-        '''CREATE TABLE albums (album_id INTEGER PRIMARY KEY, artist_id INT,
-           albtitle TEXT, albfolder TEXT, albdate TEXT, albarturi TEXT)''')
-
-    tracksstmt = '''CREATE TABLE tracks
-    (docidx INT, album_id INT, trackno INT, title TEXT'''
-
-    for tb in tagtables.itervalues():
+class Tagged(object):
+    def __init__(self, docs, httphp, pathprefix):
+        self._httphp = httphp
+        self._pprefix = pathprefix
+        self._conn = None
+        self._rcldocs = docs
+        self._init_sqconn()
+        self._recolltosql(docs)
+        
+
+    def _init_sqconn(self):
+        # We use a separate thread for building the db to ensure
+        # responsiveness during this phase.  :memory: handles normally
+        # can't be shared between threads, and different :memory: handles
+        # access different dbs. The following would work, but it needs
+        # python 3.4
+        #self._conn = sqlite3.connect('file:uprcl_db?mode=memory&cache=shared')
+        # As we can guarantee that 2 threads will never access the db at
+        # the same time (the init thread just goes away when it's done),
+        # we just disable the same_thread checking on :memory:
+        if self._conn is None:
+            self._conn = sqlite3.connect(':memory:', check_same_thread=False)
+
+
+    # Create the db. Each tag table has 2 columns: <tagname>_id and
+    # value. The join column in the main tracks table is also named
+    # <tagname>_id
+    def _createsqdb(self):
+        c = self._conn.cursor()
         try:
-            c.execute('DROP TABLE ' + tb)
+            c.execute('''DROP TABLE albums''')
+            c.execute('''DROP TABLE tracks''')
         except:
             pass
-        stmt = 'CREATE TABLE ' + tb + \
+        c.execute(
+            '''CREATE TABLE albums (album_id INTEGER PRIMARY KEY, artist_id INT,
+            albtitle TEXT, albfolder TEXT, albdate TEXT, albarturi TEXT)''')
+
+        tracksstmt = '''CREATE TABLE tracks
+        (docidx INT, album_id INT, trackno INT, title TEXT'''
+
+        for tb in _tagtotable.values():
+            try:
+                c.execute('DROP TABLE ' + tb)
+            except:
+                pass
+            stmt = 'CREATE TABLE ' + tb + \
                ' (' + _clid(tb) + ' INTEGER PRIMARY KEY, value TEXT)'
-        c.execute(stmt)
-        tracksstmt += ',' + _clid(tb) + ' INT'
-
-    tracksstmt += ')'
-    c.execute(tracksstmt)
+            c.execute(stmt)
+            tracksstmt += ',' + _clid(tb) + ' INT'
+
+        tracksstmt += ')'
+        c.execute(tracksstmt)
     
 
-# Insert new value if not existing, return rowid of new or existing row
-def _auxtableinsert(sqconn, tb, value):
-    c = sqconn.cursor()
-    stmt = 'SELECT ' + _clid(tb) + ' FROM ' + tb + ' WHERE value = ?'
-    c.execute(stmt, (value,))
-    r = c.fetchone()
-    if r:
-        rowid = r[0]
-    else:
-        stmt = 'INSERT INTO ' + tb + '(value) VALUES(?)'
+    # Insert new value if not existing, return rowid of new or existing row
+    def _auxtableinsert(self, tb, value):
+        c = self._conn.cursor()
+        stmt = 'SELECT ' + _clid(tb) + ' FROM ' + tb + ' WHERE value = ?'
         c.execute(stmt, (value,))
-        rowid = c.lastrowid
-
-    return rowid
-
-# Create the db and fill it up with the values we need, taken out of
-# the recoll records list
-def recolltosql(docs):
-    global g_alldocs
-    g_alldocs = docs
-    start = timer()
-
-    sqconn = _sqconn()
-    _createsqdb(sqconn)
-
-    # Compute a list of table names and corresponding recoll
-    # fields. most often they are identical
-    tabfields = []
-    for tb in tagtables.itervalues():
-        if tb in coltorclfield:
-            rclfld = coltorclfield[tb]
-        else:
-            rclfld = tb
-        tabfields.append((tb, rclfld))
-        
-    c = sqconn.cursor()
-    maxcnt = 0
-    totcnt = 0
-    for docidx in range(len(docs)):
-        doc = docs[docidx]
-        totcnt += 1
-
-        if totcnt % 1000 == 0:
-            time.sleep(0)
-            
-        # No need to include non-audio types in the visible tree.
-        if doc.mtype not in audiomtypes or doc.mtype == 'inode/directory':
-            continue
-
-        # Create album record if needed.
-        # The albums table is special, can't use auxtableinsert()
-        folder = docfolder(doc).decode('utf-8', errors = 'replace')
-        album = getattr(doc, 'album', None)
-        if not album:
-            album = os.path.basename(folder)
-            #uplog("Using %s for alb: mime %s title %s" %
-                  #(album,doc.mtype, doc.url))
-        if doc.albumartist:
-            albartist_id = _auxtableinsert(sqconn, 'artist', doc.albumartist)
-        else:
-            albartist_id = None
-        c.execute('''SELECT album_id,artist_id FROM albums
-        WHERE albtitle = ? AND albfolder = ?''', (album, folder))
         r = c.fetchone()
         if r:
-            album_id = r[0]
-            albartist_id = r[1]
+            rowid = r[0]
         else:
-            c.execute('''INSERT INTO albums(albtitle, albfolder, artist_id,
-            albdate, albarturi)
-            VALUES (?,?,?,?,?)''', (album, folder, albartist_id, doc.date,
-                                    doc.albumarturi))
-            album_id = c.lastrowid
-
-        # tracknos like n/max are now supposedly processed by rclaudio
-        # and should not arrive here
-        try:
-            l= doc.tracknumber.split('/')
-            trackno = int(l[0])
-        except:
-            trackno = 0
-
-        # Set base values for column names, values list, placeholders,
-        # then append data from auxiliary tables array
-        columns = 'docidx,album_id,trackno,title'
-        values = [docidx, album_id, trackno, doc.title]
-        placehold = '?,?,?,?'
-        for tb,rclfld in tabfields:
-            value = getattr(doc, rclfld, None)
-            if not value:
+            stmt = 'INSERT INTO ' + tb + '(value) VALUES(?)'
+            c.execute(stmt, (value,))
+            rowid = c.lastrowid
+        return rowid
+
+
+    # Create the db and fill it up with the values we need, taken out of
+    # the recoll records list
+    def _recolltosql(self, docs):
+        start = timer()
+
+        self._createsqdb()
+
+        # Compute a list of table names and corresponding recoll
+        # fields. most often they are identical
+        tabfields = []
+        for tb in _tagtotable.values():
+            if tb in _coltorclfield:
+                rclfld = _coltorclfield[tb]
+            else:
+                rclfld = tb
+            tabfields.append((tb, rclfld))
+        
+        c = self._conn.cursor()
+        maxcnt = 0
+        totcnt = 0
+        for docidx in range(len(docs)):
+            doc = docs[docidx]
+            totcnt += 1
+
+            if totcnt % 1000 == 0:
+                time.sleep(0)
+            
+            # No need to include non-audio types in the visible tree.
+            if doc.mtype not in audiomtypes or doc.mtype == 'inode/directory':
                 continue
-            rowid = _auxtableinsert(sqconn, tb, value)
-            columns += ',' + _clid(tb)
-            values.append(rowid)
-            placehold += ',?'
-
-        # Finally create the main record in the tracks table with
-        # references to the aux tables
-        stmt = 'INSERT INTO tracks(' + columns + ') VALUES(' + placehold + ')'
+
+            # Create album record if needed.
+            # The albums table is special, can't use auxtableinsert()
+            folder = docfolder(doc).decode('utf-8', errors = 'replace')
+            album = getattr(doc, 'album', None)
+            if not album:
+                album = os.path.basename(folder)
+                #uplog("Using %s for alb: mime %s title %s" %
+                    #(album,doc.mtype, doc.url))
+            if doc.albumartist:
+                albartist_id = self._auxtableinsert('artist', doc.albumartist)
+            else:
+                albartist_id = None
+            c.execute('''SELECT album_id,artist_id FROM albums
+            WHERE albtitle = ? AND albfolder = ?''', (album, folder))
+            r = c.fetchone()
+            if r:
+                album_id = r[0]
+                albartist_id = r[1]
+            else:
+                c.execute('''INSERT INTO albums(albtitle, albfolder, artist_id,
+                albdate, albarturi)
+                VALUES (?,?,?,?,?)''', (album, folder, albartist_id, doc.date,
+                                        doc.albumarturi))
+                album_id = c.lastrowid
+
+            # tracknos like n/max are now supposedly processed by rclaudio
+            # and should not arrive here
+            try:
+                l= doc.tracknumber.split('/')
+                trackno = int(l[0])
+            except:
+                trackno = 0
+
+            # Set base values for column names, values list, placeholders,
+            # then append data from auxiliary tables array
+            columns = 'docidx,album_id,trackno,title'
+            values = [docidx, album_id, trackno, doc.title]
+            placehold = '?,?,?,?'
+            for tb,rclfld in tabfields:
+                value = getattr(doc, rclfld, None)
+                if not value:
+                    continue
+                rowid = self._auxtableinsert(tb, value)
+                columns += ',' + _clid(tb)
+                values.append(rowid)
+                placehold += ',?'
+
+            # Finally create the main record in the tracks table with
+            # references to the aux tables
+            stmt='INSERT INTO tracks(' + columns + ') VALUES(' + placehold + ')'
+            c.execute(stmt, values)
+            #uplog(doc.title)
+
+            if not albartist_id:
+                lcols = columns.split(',')
+                try:
+                    i = lcols.index('artist_id')
+                    artist_id = values[i]
+                    stmt = 'UPDATE albums SET artist_id = ? WHERE album_id = ?'
+                    c.execute(stmt, (artist_id, album_id))
+                except:
+                    pass
+                      
+        self._conn.commit()
+        end = timer()
+        uplog("recolltosql: processed %d docs in %.2f Seconds" %
+              (totcnt, end-start))
+
+
+    # Create our top-level directories, with fixed entries, and stuff from
+    # the tags tables
+    def rootentries(self, pid):
+        c = self._conn.cursor()
+        c.execute("SELECT COUNT(*) from albums")
+        nalbs = str(c.fetchone()[0])
+        c.execute("SELECT COUNT(*) from tracks")
+        nitems = str(c.fetchone()[0])
+        entries = [rcldirentry(pid + 'albums', pid, nalbs + ' albums'),
+                   rcldirentry(pid + 'items', pid, nitems + ' items')]
+        for tt in sorted(_tagtotable.keys()):
+            entries.append(rcldirentry(pid + '=' + tt , pid, tt))
+        return entries
+
+
+    # Check what tags still have multiple values inside the selected set,
+    # and return their list.
+    def _subtreetags(self, docidsl):
+        docids = ','.join([str(i) for i in docidsl])
+        uplog("subtreetags, docids %s" % docids)
+        c = self._conn.cursor()
+        tags = []
+        for tt,tb in _tagtotable.items():
+            stmt = 'SELECT COUNT(DISTINCT ' + _clid(tb) + \
+                   ') FROM tracks WHERE docidx IN (' + docids + ')'
+            uplog("subtreetags: executing: <%s>" % stmt)
+            c.execute(stmt)
+            for r in c:
+                cnt = r[0]
+                uplog("Found %d distinct values for %s" % (cnt, tb))
+                if cnt > 1:
+                    tags.append(tt)
+        return tags
+
+
+    def _trackentriesforstmt(self, stmt, values, pid):
+        c = self._conn.cursor()
         c.execute(stmt, values)
-        #uplog(doc.title)
-
-        if not albartist_id:
-            lcols = columns.split(',')
-            try:
-                i = lcols.index('artist_id')
-                artist_id = values[i]
-                stmt = 'UPDATE albums SET artist_id = ? WHERE album_id = ?'
-                c.execute(stmt, (artist_id, album_id))
-            except:
-                pass
-                      
-    sqconn.commit()
-    end = timer()
-    uplog("recolltosql: processed %d docs in %.2f Seconds" %
-          (totcnt, end-start))
-
-
-# Create our top-level directories, with fixed entries, and stuff from
-# the tags tables
-def rootentries(pid):
-    c = _sqconn().cursor()
-    c.execute("SELECT COUNT(*) from albums")
-    nalbs = str(c.fetchone()[0])
-    c.execute("SELECT COUNT(*) from tracks")
-    nitems = str(c.fetchone()[0])
-    entries = [rcldirentry(pid + 'albums', pid, nalbs + ' albums'),
-               rcldirentry(pid + 'items', pid, nitems + ' items')]
-    for tt in sorted(tagtables.iterkeys()):
-        entries.append(rcldirentry(pid + '=' + tt , pid, tt))
-    return entries
-
-# Check what tags still have multiple values inside the selected set,
-# and return their list.
-def _subtreetags(docidsl):
-    docids = ','.join([str(i) for i in docidsl])
-    uplog("subtreetags, docids %s" % docids)
-    c = _sqconn().cursor()
-    tags = []
-    for tt,tb in tagtables.iteritems():
-        stmt = 'SELECT COUNT(DISTINCT ' + _clid(tb) + \
-               ') FROM tracks WHERE docidx IN (' + docids + ')'
-        uplog("subtreetags: executing: <%s>" % stmt)
+        entries = []
+        for r in c:
+            docidx = r[0]
+            id = pid + '$i' + str(docidx)
+            entries.append(rcldoctoentry(id, pid, self._httphp, self._pprefix,
+                                         self._rcldocs[docidx]))
+        return sorted(entries, cmp=cmpentries)
+    
+
+    # Return a list of trackids as selected by the current
+    # path <selwhere> is like: WHERE col1_id = ? AND col2_id = ? [...], and
+    # <values> holds the corresponding values
+    def _docidsforsel(self, selwhere, values):
+        c = self._conn.cursor()
+        stmt = 'SELECT docidx FROM tracks ' + selwhere + ' ORDER BY trackno'
+        uplog("docidsforsel: executing <%s> values %s" % (stmt, values))
+        c.execute(stmt, values)
+        return [r[0] for r in c.fetchall()]
+
+
+    def _trackentriesforalbum(self, albid, pid):
+        stmt = 'SELECT docidx FROM tracks WHERE album_id = ? ORDER BY trackno'
+        return self._trackentriesforstmt(stmt,(albid,), pid)
+
+
+    def _direntriesforalbums(self, pid, where):
+        entries = []
+        c = self._conn.cursor()
+        if not where:
+            where = ' LEFT JOIN artist ON artist.artist_id = albums.artist_id '
+            frm = ' FROM albums '
+        else:
+            where += ' AND artist.artist_id = albums.artist_id '
+            frm = ' FROM albums,artist '
+
+        stmt = 'SELECT album_id, albtitle, albarturi, albdate, artist.value' + \
+               frm + where + ' ORDER BY albtitle'
+        uplog('direntriesforalbums: %s' % stmt)
         c.execute(stmt)
         for r in c:
-            cnt = r[0]
-            uplog("Found %d distinct values for %s" % (cnt, tb))
-            if cnt > 1:
-                tags.append(tt)
-    return tags
-
-def _trackentriesforstmt(stmt, values, pid, httphp, pathprefix):
-    c = _sqconn().cursor()
-    c.execute(stmt, values)
-    entries = []
-    for r in c:
-        docidx = r[0]
-        id = pid + '$i' + str(docidx)
-        entries.append(rcldoctoentry(id, pid, httphp, pathprefix,
-                                     g_alldocs[docidx]))
-    return sorted(entries, cmp=cmpentries)
+            id = pid + '$' + str(r[0])
+            entries.append(
+                rcldirentry(id, pid, r[1], arturi=r[2], date=r[3],artist=r[4], 
+                            upnpclass='object.container.album.musicAlbum'))
+        return entries
+
+
+    # This is called when an 'albums' element is encountered in the
+    # selection path. i is the index of the albums element. The tree under
+    # albums has a well defined structure: ql=len(qpath), we have an
+    # albums list if i is the last element (i == ql-1), an album track
+    # list for i == ql-2 (we then have an albid at ql-1), and a 'Complete
+    # album' query if i == ql-3 (...$albums$xxx$showca)
+    def _tagsbrowsealbums(self, pid, qpath, i, selwhere, values):
+        uplog("_tagsbrowsealbums: pid %s qpath %s i %s selwhere %s values %s" %
+              (pid, qpath, i, selwhere, values))
+        c = self._conn.cursor()
+        docidsl = self._docidsforsel(selwhere, values)
+        entries = []
+        if i == len(qpath)-1:
+            albidsl = self._subtreealbums(docidsl)
+            albids = ','.join([str(a) for a in albidsl])
+            where = ' WHERE album_id in (' + albids + ') '
+            entries = self._direntriesforalbums(pid, where)
+        elif i == len(qpath)-2:
+            albid = int(qpath[-1])
+            docids = ','.join([str(i) for i in docidsl])
+            stmt = 'SELECT COUNT(docidx) FROM tracks WHERE album_id = ?'
+            c.execute(stmt, (albid,))
+            r = c.fetchone()
+            ntracks = int(r[0])
+            stmt = 'SELECT docidx FROM tracks ' + \
+                   'WHERE album_id = ? AND docidx IN ('+ docids + ')'
+            entries = self._trackentriesforstmt(stmt, (albid,), pid)
+            if ntracks != len(entries):
+                id = pid + '$' + 'showca'
+                entries = [rcldirentry(id, pid, '>> Complete Album')] + entries
+        elif i == len(qpath)-3:
+            # Note that minim has an additional level here, probably to
+            # present groups or multiple groups ? The trackids ids are
+            # like: 
+            #    0$=Composer$17738$albums$2$showca.0$hcalbum$*i13458
+            # I don't know what the .0 is for.
+            # The 'hcalbum' level usually has 2 entries '>> Hide Content' 
+            # and the album title. TBD
+            albid = int(qpath[-2])
+            entries = self._trackentriesforalbum(albid, pid)
+        
+        return entries
+
+
+    # This is called when an 'items' element is encountered in the
+    # selection path. We just list the selected tracks
+    # TBD: need Complete Album here too ?
+    def _tagsbrowseitems(self, pid, qpath, i, selwhere, values):
+        stmt = 'SELECT docidx FROM tracks ' + selwhere
+        return self._trackentriesforstmt(stmt, values, pid)
+
+
+    # Return all albums ids to which any of the currently selected tracks
+    # (designated by a docid set) belong
+    def _subtreealbums(self, docidsl):
+        docids = ','.join([str(r) for r in docidsl])
+        albids = []
+        stmt = 'SELECT album_id from tracks where docidx IN (' \
+               + docids + ') ' + 'GROUP BY album_id'
+        c = self._conn.cursor()
+        uplog('subtreealbums: executing %s' % stmt)
+        c.execute(stmt)
+        for r in c:
+            albids.append(r[0])
+        uplog('subtreealbums: returning %s' % albids)
+        return albids
     
 
-# Return a list of trackids as selected by the current
-# path <selwhere> is like: WHERE col1_id = ? AND col2_id = ? [...], and
-# <values> holds the corresponding values
-def _docidsforsel(selwhere, values):
-    c = _sqconn().cursor()
-    stmt = 'SELECT docidx FROM tracks ' + selwhere + ' ORDER BY trackno'
-    uplog("docidsforsel: executing <%s> values %s" % (stmt, values))
-    c.execute(stmt, values)
-    return [r[0] for r in c.fetchall()]
-
-def _trackentriesforalbum(albid, pid, httphp, pathprefix):
-    stmt = 'SELECT docidx FROM tracks WHERE album_id = ? ORDER BY trackno'
-    return _trackentriesforstmt(stmt, (albid,), pid, httphp, pathprefix)
-
-def _direntriesforalbums(pid, where):
-    entries = []
-    c = _sqconn().cursor()
-    if not where:
-        where = ' LEFT JOIN artist ON artist.artist_id = albums.artist_id '
-        frm = ' FROM albums '
-    else:
-        where += ' AND artist.artist_id = albums.artist_id '
-        frm = ' FROM albums,artist '
-
-    stmt = 'SELECT album_id, albtitle, albarturi, albdate, artist.value' + \
-           frm + where + ' ORDER BY albtitle'
-    uplog('direntriesforalbums: %s' % stmt)
-    c.execute(stmt)
-    for r in c:
-        id = pid + '$' + str(r[0])
-        entries.append(
-            rcldirentry(id, pid, r[1], arturi=r[2], date=r[3],artist=r[4], 
-                        upnpclass='object.container.album.musicAlbum'))
-    return entries
-
-# This is called when an 'albums' element is encountered in the
-# selection path. i is the index of the albums element. The tree under
-# albums has a well defined structure: ql=len(qpath), we have an
-# albums list if i is the last element (i == ql-1), an album track
-# list for i == ql-2 (we then have an albid at ql-1), and a 'Complete
-# album' query if i == ql-3 (...$albums$xxx$showca)
-def _tagsbrowsealbums(pid, qpath, i, selwhere, values, httphp, pathprefix):
-    uplog("_tagsbrowsealbums: pid %s qpath %s i %s selwhere %s values %s" %
-          (pid, qpath, i, selwhere, values))
-    c = _sqconn().cursor()
-    docidsl = _docidsforsel(selwhere, values)
-    entries = []
-    if i == len(qpath)-1:
-        albidsl = _subtreealbums(docidsl)
-        albids = ','.join([str(a) for a in albidsl])
-        where = ' WHERE album_id in (' + albids + ') '
-        entries = _direntriesforalbums(pid, where)
-    elif i == len(qpath)-2:
-        albid = int(qpath[-1])
-        docids = ','.join([str(i) for i in docidsl])
-        stmt = 'SELECT COUNT(docidx) FROM tracks WHERE album_id = ?'
-        c.execute(stmt, (albid,))
-        r = c.fetchone()
-        ntracks = int(r[0])
-        stmt = 'SELECT docidx FROM tracks WHERE album_id = ? AND docidx IN (' +\
-               docids + ')'
-        entries = _trackentriesforstmt(stmt, (albid,), pid, httphp, pathprefix)
-        if ntracks != len(entries):
-            id = pid + '$' + 'showca'
-            entries = [rcldirentry(id, pid, '>> Complete Album')] + entries
-    elif i == len(qpath)-3:
-        # Note that minim has an additional level here, probably to
-        # present groups or multiple groups ? The trackids ids are
-        # like: 
-        #    0$=Composer$17738$albums$2$showca.0$hcalbum$*i13458
-        # I don't know what the .0 is for.
-        # The 'hcalbum' level usually has 2 entries '>> Hide Content' 
-        # and the album title. TBD
-        albid = int(qpath[-2])
-        entries = _trackentriesforalbum(albid, pid, httphp, pathprefix)
-        
-    return entries
-
-# This is called when an 'items' element is encountered in the
-# selection path. We just list the selected tracks
-# TBD: need Complete Album here too ?
-def _tagsbrowseitems(pid, qpath, i, selwhere, values, httphp, pathprefix):
-    stmt = 'SELECT docidx FROM tracks ' + selwhere
-    return _trackentriesforstmt(stmt, values, pid, httphp, pathprefix)
-
-
-# Return all albums ids to which any of the currently selected tracks
-# (designated by a docid set) belong
-def _subtreealbums(docidsl):
-    docids = ','.join([str(r) for r in docidsl])
-    albids = []
-    stmt = 'SELECT album_id from tracks where docidx IN (' + docids + ') ' + \
-           'GROUP BY album_id'
-    c = _sqconn().cursor()
-    uplog('subtreealbums: executing %s' % stmt)
-    c.execute(stmt)
-    for r in c:
-        albids.append(r[0])
-    uplog('subtreealbums: returning %s' % albids)
-    return albids
-    
-# Main browsing routine. Given an objid, translate it into a select
-# statement, plus further processing, and return the corresponding
-# records
-def _tagsbrowse(pid, qpath, flag, httphp, pathprefix):
-    uplog("tagsbrowse. pid %s qpath %s" % (pid, qpath))
-    qlen = len(qpath)
-    selwhat = ''
-    selwhere = ''
-    values = []
-    i = 0
-    while i < qlen:
-        elt = qpath[i]
-
-        # '=colname'. Set the current column name, which will be used
-        # in different ways depending if this is the last element or
-        # not.
-        if elt.startswith('='):
-            col = tagtables[elt[1:]] 
-
-        # Detect the special values: albums items etc. here. Their
-        # presence changes how we process the rest (showing tracks and
-        # albums and not dealing with other tags any more)
-        if elt == 'albums':
-            return _tagsbrowsealbums(pid, qpath, i, selwhere, values, httphp,
-                                    pathprefix)
-        elif elt == 'items':
-            return _tagsbrowseitems(pid, qpath, i, selwhere, values, httphp,
-                                    pathprefix)
+    # Main browsing routine. Given an objid, translate it into a select
+    # statement, plus further processing, and return the corresponding
+    # records
+    def _tagsbrowse(self, pid, qpath, flag):
+        uplog("tagsbrowse. pid %s qpath %s" % (pid, qpath))
+        qlen = len(qpath)
+        selwhat = ''
+        selwhere = ''
+        values = []
+        i = 0
+        while i < qlen:
+            elt = qpath[i]
+
+            # '=colname'. Set the current column name, which will be used
+            # in different ways depending if this is the last element or
+            # not.
+            if elt.startswith('='):
+                col = _tagtotable[elt[1:]] 
+
+            # Detect the special values: albums items etc. here. Their
+            # presence changes how we process the rest (showing tracks and
+            # albums and not dealing with other tags any more)
+            if elt == 'albums':
+                return self._tagsbrowsealbums(pid, qpath, i, selwhere, values)
+            elif elt == 'items':
+                return self._tagsbrowseitems(pid, qpath, i, selwhere, values)
             
-        selwhere = selwhere + ' AND ' if selwhere else ' WHERE '
-        if i == qlen - 1:
-            # We want to display all unique values for the column
-            # artist.artist_id, artist.value
-            selwhat = col + '.' + _clid(col) + ', ' + col + '.value'
-            # tracks.artist_id = artist.artist_id
-            selwhere += 'tracks.' + _clid(col) + ' = ' + col + '.' + _clid(col)
-        else:
-            # Look at the value specified for the =xx column. The
-            # selwhat value is only used as a flag
-            selwhat = 'tracks.docidx'
-            selwhere += 'tracks.' + _clid(col) + ' =  ?'
+            selwhere = selwhere + ' AND ' if selwhere else ' WHERE '
+            if i == qlen - 1:
+                # We want to display all unique values for the column
+                # artist.artist_id, artist.value
+                selwhat = col + '.' + _clid(col) + ', ' + col + '.value'
+                # tracks.artist_id = artist.artist_id
+                selwhere += 'tracks.' + _clid(col) + ' = ' + col + \
+                            '.' + _clid(col)
+            else:
+                # Look at the value specified for the =xx column. The
+                # selwhat value is only used as a flag
+                selwhat = 'tracks.docidx'
+                selwhere += 'tracks.' + _clid(col) + ' =  ?'
+                i += 1
+                values.append(int(qpath[i]))
             i += 1
-            values.append(int(qpath[i]))
-        i += 1
             
 
-    # TBD: Need a ">> Complete Album" entry if there is a single
-    # album, no subqs and not all the tracks are listed
-    entries = []
-    if selwhat == 'tracks.docidx':
-        docids = _docidsforsel(selwhere, values)
-        albids = _subtreealbums(docids)
-        subqs = _subtreetags(docids)
-        if len(albids) > 1:
-            id = pid + '$albums'
-            entries.append(rcldirentry(id, pid, str(len(albids)) + ' albums'))
-            if subqs:
+        # TBD: Need a ">> Complete Album" entry if there is a single
+        # album, no subqs and not all the tracks are listed
+        entries = []
+        if selwhat == 'tracks.docidx':
+            docids = self._docidsforsel(selwhere, values)
+            albids = self._subtreealbums(docids)
+            subqs = self._subtreetags(docids)
+            if len(albids) > 1:
+                id = pid + '$albums'
+                entries.append(rcldirentry(id, pid, str(len(albids)) +
+                                           ' albums'))
+                if subqs:
+                    id = pid + '$items'
+                    entries.append(rcldirentry(id,pid, str(len(docids)) +
+                                               ' items'))
+            elif len(albids) == 1 and subqs:
                 id = pid + '$items'
                 entries.append(rcldirentry(id,pid, str(len(docids)) + ' items'))
-        elif len(albids) == 1 and subqs:
-            id = pid + '$items'
-            entries.append(rcldirentry(id, pid, str(len(docids)) + ' items'))
-
-        if not subqs:
-            for docidx in docids:
-                id = pid + '$*i' + str(docidx)
-                entries.append(rcldoctoentry(id, pid, httphp, pathprefix,
-                                             g_alldocs[docidx]))
-                entries = sorted(entries, cmp=cmpentries)
+
+            if not subqs:
+                for docidx in docids:
+                    id = pid + '$*i' + str(docidx)
+                    entries.append(rcldoctoentry(id, pid, self._httphp,
+                                                 self._pprefix,
+                                                 self._rcldocs[docidx]))
+                    entries = sorted(entries, cmp=cmpentries)
+            else:
+                for tt in subqs:
+                    id = pid + '$=' + tt
+                    entries.append(rcldirentry(id, pid, tt))
         else:
-            for tt in subqs:
-                id = pid + '$=' + tt
-                entries.append(rcldirentry(id, pid, tt))
-    else:
-        # SELECT col.value FROM tracks, col
-        # WHERE tracks.col_id = col.col_id
-        # GROUP BY tracks.col_id
-        # ORDER BY col.value
-        stmt = "SELECT " + selwhat + " FROM tracks, " + col + \
-               selwhere + \
-               " GROUP BY tracks." + _clid(col) + \
-               " ORDER BY value"
-        uplog("tagsbrowse: executing <%s> values %s" % (stmt,values))
-        c = _sqconn().cursor()
-        c.execute(stmt, values)
-        for r in c:
-            id = pid + '$' + str(r[0])
-            entries.append(rcldirentry(id, pid, r[1]))
-    return entries
-
-
-# Browse the top-level tree named like 'xxx albums'. There are just 2
-# levels: the whole albums list, then for each entry the specified
-# albums track list
-def _albumsbrowse(pid, qpath, flag, httphp, pathprefix):
-    c = _sqconn().cursor()
-    entries = []
-    if len(qpath) == 1:
-        entries = _direntriesforalbums(pid, '')
-    elif len(qpath) == 2:
-        e1 = qpath[1]
-        album_id = int(e1)
-        entries = _trackentriesforalbum(album_id, pid, httphp, pathprefix)
-    else:
-        raise Exception("Bad path in album tree (too deep): <%s>" % qpath)
-
-    return entries
-
-
-# Top level browse routine. Handle the special cases and call the
-# appropriate worker routine.
-def browse(pid, flag, httphp, pathprefix):
-    idpath = pid.replace(g_myprefix, '', 1)
-    uplog('tags:browse: idpath <%s>' % idpath)
-    entries = []
-    qpath = idpath.split('$')
-    if idpath.startswith('items'):
-        stmt = 'SELECT docidx FROM tracks'
-        entries = _trackentriesforstmt(stmt, (), pid, httphp, pathprefix)
-    elif idpath.startswith('albums'):
-        entries = _albumsbrowse(pid, qpath, flag, httphp, pathprefix)
-    elif idpath.startswith('='):
-        entries = _tagsbrowse(pid, qpath, flag, httphp, pathprefix)
-    else:
-        raise Exception('Bad path in tags tree (start): <%s>' % idpath)
-    return entries
+            # SELECT col.value FROM tracks, col
+            # WHERE tracks.col_id = col.col_id
+            # GROUP BY tracks.col_id
+            # ORDER BY col.value
+            stmt = "SELECT " + selwhat + " FROM tracks, " + col + \
+                   selwhere + \
+                   " GROUP BY tracks." + _clid(col) + \
+                   " ORDER BY value"
+            uplog("tagsbrowse: executing <%s> values %s" % (stmt,values))
+            c = self._conn.cursor()
+            c.execute(stmt, values)
+            for r in c:
+                id = pid + '$' + str(r[0])
+                entries.append(rcldirentry(id, pid, r[1]))
+        return entries
+
+
+    # Browse the top-level tree named like 'xxx albums'. There are just 2
+    # levels: the whole albums list, then for each entry the specified
+    # albums track list
+    def _albumsbrowse(self, pid, qpath, flag):
+        c = self._conn.cursor()
+        entries = []
+        if len(qpath) == 1:
+            entries = self._direntriesforalbums(pid, '')
+        elif len(qpath) == 2:
+            e1 = qpath[1]
+            album_id = int(e1)
+            entries = self._trackentriesforalbum(album_id, pid)
+        else:
+            raise Exception("Bad path in album tree (too deep): <%s>" % qpath)
+
+        return entries
+
+
+    # Top level browse routine. Handle the special cases and call the
+    # appropriate worker routine.
+    def browse(self, pid, flag):
+        idpath = pid.replace(g_myprefix, '', 1)
+        uplog('tags:browse: idpath <%s>' % idpath)
+        entries = []
+        qpath = idpath.split('$')
+        if idpath.startswith('items'):
+            stmt = 'SELECT docidx FROM tracks'
+            entries = self._trackentriesforstmt(stmt, (), pid)
+        elif idpath.startswith('albums'):
+            entries = self._albumsbrowse(pid, qpath, flag)
+        elif idpath.startswith('='):
+            entries = self._tagsbrowse(pid, qpath, flag)
+        else:
+            raise Exception('Bad path in tags tree (start): <%s>' % idpath)
+        return entries
 
 
 
@@ -509,7 +518,7 @@
 ############ Misc test/trial code, not used by uprcl ########################
 
 def misctries():
-    c = _sqconn().cursor()
+    c = self._conn.cursor()
     c.execute('''SELECT COUNT(*) FROM tracks''')
     uplog("Count(*) %d" % (c.fetchone()[0],))