|
a/src/mediaserver/cdplugins/uprcl/uprcltags.py |
|
b/src/mediaserver/cdplugins/uprcl/uprcltags.py |
|
... |
|
... |
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16 |
|
16 |
|
17 |
import sys
|
17 |
import sys
|
18 |
import os
|
18 |
import os
|
19 |
import sqlite3
|
19 |
import sqlite3
|
|
|
20 |
import time
|
|
|
21 |
import tempfile
|
20 |
from timeit import default_timer as timer
|
22 |
from timeit import default_timer as timer
|
21 |
|
23 |
|
22 |
from uprclutils import g_myprefix, audiomtypes, docfolder, uplog, \
|
24 |
from uprclutils import g_myprefix, audiomtypes, docfolder, uplog, \
|
23 |
rcldirentry, rcldoctoentry, cmpentries
|
25 |
rcldirentry, rcldoctoentry, cmpentries
|
24 |
|
26 |
|
25 |
# After initialization, this holds the list of all records out of
|
27 |
# After initialization, this holds the list of all records out of
|
26 |
# recoll (it's a reference to the original in uprclfolders)
|
28 |
# recoll (it's a reference to the original in uprclfolders)
|
27 |
g_alldocs = []
|
29 |
g_alldocs = []
|
|
|
30 |
sqconn = None
|
28 |
|
31 |
|
29 |
sqconn = sqlite3.connect(':memory:')
|
32 |
def _sqconn():
|
|
|
33 |
# We use a separate thread for building the db to ensure
|
|
|
34 |
# responsiveness during this phase. :memory: handles normally
|
|
|
35 |
# can't be shared between threads, and different :memory: handles
|
|
|
36 |
# access different dbs. The following would work, but it needs
|
|
|
37 |
# python 3.4
|
|
|
38 |
#sqconn = sqlite3.connect('file:uprcl_db?mode=memory&cache=shared')
|
|
|
39 |
# As we can guarantee that 2 threads will never access the db at
|
|
|
40 |
# the same time (the init thread just goes away when it's done),
|
|
|
41 |
# we just disable the same_thread checking on :memory:
|
|
|
42 |
global sqconn
|
|
|
43 |
if sqconn is None:
|
|
|
44 |
sqconn = sqlite3.connect(':memory:', check_same_thread=False)
|
30 |
#sqconn = sqlite3.connect('/tmp/tracks.sqlite')
|
45 |
#sqconn = sqlite3.connect('/tmp/tracks.sqlite')
|
|
|
46 |
return sqconn
|
31 |
|
47 |
|
32 |
# Tags for which we create auxiliary tables for facet descent.
|
48 |
# Tags for which we create auxiliary tables for facet descent.
|
33 |
#
|
49 |
#
|
34 |
# TBD: The list will come from the config file one day
|
50 |
# TBD: The list will come from the config file one day
|
35 |
# TBD: alias et al configuration
|
51 |
# TBD: alias et al configuration
|
|
... |
|
... |
108 |
# Create the db and fill it up with the values we need, taken out of
|
124 |
# Create the db and fill it up with the values we need, taken out of
|
109 |
# the recoll records list
|
125 |
# the recoll records list
|
110 |
def recolltosql(docs):
|
126 |
def recolltosql(docs):
|
111 |
global g_alldocs
|
127 |
global g_alldocs
|
112 |
g_alldocs = docs
|
128 |
g_alldocs = docs
|
113 |
|
129 |
start = timer()
|
|
|
130 |
|
|
|
131 |
sqconn = _sqconn()
|
114 |
_createsqdb(sqconn)
|
132 |
_createsqdb(sqconn)
|
115 |
|
133 |
|
116 |
# Compute a list of table names and corresponding recoll
|
134 |
# Compute a list of table names and corresponding recoll
|
117 |
# fields. most often they are identical
|
135 |
# fields. most often they are identical
|
118 |
tabfields = []
|
136 |
tabfields = []
|
|
... |
|
... |
128 |
totcnt = 0
|
146 |
totcnt = 0
|
129 |
for docidx in range(len(docs)):
|
147 |
for docidx in range(len(docs)):
|
130 |
doc = docs[docidx]
|
148 |
doc = docs[docidx]
|
131 |
totcnt += 1
|
149 |
totcnt += 1
|
132 |
|
150 |
|
|
|
151 |
if totcnt % 1000 == 0:
|
|
|
152 |
time.sleep(0)
|
|
|
153 |
|
133 |
# No need to include non-audio types in the visible tree.
|
154 |
# No need to include non-audio types in the visible tree.
|
134 |
if doc.mtype not in audiomtypes or doc.mtype == 'inode/directory':
|
155 |
if doc.mtype not in audiomtypes or doc.mtype == 'inode/directory':
|
135 |
continue
|
156 |
continue
|
136 |
|
157 |
|
137 |
# Create album record if needed.
|
158 |
# Create album record if needed.
|
|
... |
|
... |
193 |
c.execute(stmt, (artist_id, album_id))
|
214 |
c.execute(stmt, (artist_id, album_id))
|
194 |
except:
|
215 |
except:
|
195 |
pass
|
216 |
pass
|
196 |
|
217 |
|
197 |
sqconn.commit()
|
218 |
sqconn.commit()
|
|
|
219 |
end = timer()
|
198 |
uplog("recolltosql: processed %d docs" % totcnt)
|
220 |
uplog("recolltosql: processed %d docs in %.2f Seconds" %
|
|
|
221 |
(totcnt, end-start))
|
199 |
|
222 |
|
200 |
|
223 |
|
201 |
# Create our top-level directories, with fixed entries, and stuff from
|
224 |
# Create our top-level directories, with fixed entries, and stuff from
|
202 |
# the tags tables
|
225 |
# the tags tables
|
203 |
def rootentries(pid):
|
226 |
def rootentries(pid):
|
204 |
c = sqconn.cursor()
|
227 |
c = _sqconn().cursor()
|
205 |
c.execute("SELECT COUNT(*) from albums")
|
228 |
c.execute("SELECT COUNT(*) from albums")
|
206 |
nalbs = str(c.fetchone()[0])
|
229 |
nalbs = str(c.fetchone()[0])
|
207 |
c.execute("SELECT COUNT(*) from tracks")
|
230 |
c.execute("SELECT COUNT(*) from tracks")
|
208 |
nitems = str(c.fetchone()[0])
|
231 |
nitems = str(c.fetchone()[0])
|
209 |
entries = [rcldirentry(pid + 'albums', pid, nalbs + ' albums'),
|
232 |
entries = [rcldirentry(pid + 'albums', pid, nalbs + ' albums'),
|
|
... |
|
... |
215 |
# Check what tags still have multiple values inside the selected set,
|
238 |
# Check what tags still have multiple values inside the selected set,
|
216 |
# and return their list.
|
239 |
# and return their list.
|
217 |
def _subtreetags(docidsl):
|
240 |
def _subtreetags(docidsl):
|
218 |
docids = ','.join([str(i) for i in docidsl])
|
241 |
docids = ','.join([str(i) for i in docidsl])
|
219 |
uplog("subtreetags, docids %s" % docids)
|
242 |
uplog("subtreetags, docids %s" % docids)
|
220 |
c = sqconn.cursor()
|
243 |
c = _sqconn().cursor()
|
221 |
tags = []
|
244 |
tags = []
|
222 |
for tt,tb in tagtables.iteritems():
|
245 |
for tt,tb in tagtables.iteritems():
|
223 |
stmt = 'SELECT COUNT(DISTINCT ' + _clid(tb) + \
|
246 |
stmt = 'SELECT COUNT(DISTINCT ' + _clid(tb) + \
|
224 |
') FROM tracks WHERE docidx IN (' + docids + ')'
|
247 |
') FROM tracks WHERE docidx IN (' + docids + ')'
|
225 |
uplog("subtreetags: executing: <%s>" % stmt)
|
248 |
uplog("subtreetags: executing: <%s>" % stmt)
|
|
... |
|
... |
230 |
if cnt > 1:
|
253 |
if cnt > 1:
|
231 |
tags.append(tt)
|
254 |
tags.append(tt)
|
232 |
return tags
|
255 |
return tags
|
233 |
|
256 |
|
234 |
def _trackentriesforstmt(stmt, values, pid, httphp, pathprefix):
|
257 |
def _trackentriesforstmt(stmt, values, pid, httphp, pathprefix):
|
235 |
c = sqconn.cursor()
|
258 |
c = _sqconn().cursor()
|
236 |
c.execute(stmt, values)
|
259 |
c.execute(stmt, values)
|
237 |
entries = []
|
260 |
entries = []
|
238 |
for r in c:
|
261 |
for r in c:
|
239 |
docidx = r[0]
|
262 |
docidx = r[0]
|
240 |
id = pid + '$i' + str(docidx)
|
263 |
id = pid + '$i' + str(docidx)
|
|
... |
|
... |
245 |
|
268 |
|
246 |
# Return a list of trackids as selected by the current
|
269 |
# Return a list of trackids as selected by the current
|
247 |
# path <selwhere> is like: WHERE col1_id = ? AND col2_id = ? [...], and
|
270 |
# path <selwhere> is like: WHERE col1_id = ? AND col2_id = ? [...], and
|
248 |
# <values> holds the corresponding values
|
271 |
# <values> holds the corresponding values
|
249 |
def _docidsforsel(selwhere, values):
|
272 |
def _docidsforsel(selwhere, values):
|
250 |
c = sqconn.cursor()
|
273 |
c = _sqconn().cursor()
|
251 |
stmt = 'SELECT docidx FROM tracks ' + selwhere + ' ORDER BY trackno'
|
274 |
stmt = 'SELECT docidx FROM tracks ' + selwhere + ' ORDER BY trackno'
|
252 |
uplog("docidsforsel: executing <%s> values %s" % (stmt, values))
|
275 |
uplog("docidsforsel: executing <%s> values %s" % (stmt, values))
|
253 |
c.execute(stmt, values)
|
276 |
c.execute(stmt, values)
|
254 |
return [r[0] for r in c.fetchall()]
|
277 |
return [r[0] for r in c.fetchall()]
|
255 |
|
278 |
|
|
... |
|
... |
257 |
stmt = 'SELECT docidx FROM tracks WHERE album_id = ? ORDER BY trackno'
|
280 |
stmt = 'SELECT docidx FROM tracks WHERE album_id = ? ORDER BY trackno'
|
258 |
return _trackentriesforstmt(stmt, (albid,), pid, httphp, pathprefix)
|
281 |
return _trackentriesforstmt(stmt, (albid,), pid, httphp, pathprefix)
|
259 |
|
282 |
|
260 |
def _direntriesforalbums(pid, where):
|
283 |
def _direntriesforalbums(pid, where):
|
261 |
entries = []
|
284 |
entries = []
|
262 |
c = sqconn.cursor()
|
285 |
c = _sqconn().cursor()
|
263 |
if not where:
|
286 |
if not where:
|
264 |
where = 'WHERE artist.artist_id = albums.artist_id'
|
287 |
where = 'WHERE artist.artist_id = albums.artist_id'
|
265 |
else:
|
288 |
else:
|
266 |
where += ' AND artist.artist_id = albums.artist_id'
|
289 |
where += ' AND artist.artist_id = albums.artist_id'
|
267 |
|
290 |
|
|
... |
|
... |
275 |
return entries
|
298 |
return entries
|
276 |
|
299 |
|
277 |
# This is called when an 'albums' element is encountered in the
|
300 |
# This is called when an 'albums' element is encountered in the
|
278 |
# selection path.
|
301 |
# selection path.
|
279 |
def _tagsbrowsealbums(pid, qpath, i, selwhere, values, httphp, pathprefix):
|
302 |
def _tagsbrowsealbums(pid, qpath, i, selwhere, values, httphp, pathprefix):
|
280 |
c = sqconn.cursor()
|
303 |
c = _sqconn().cursor()
|
281 |
docidsl = _docidsforsel(selwhere, values)
|
304 |
docidsl = _docidsforsel(selwhere, values)
|
282 |
entries = []
|
305 |
entries = []
|
283 |
if i == len(qpath)-1:
|
306 |
if i == len(qpath)-1:
|
284 |
albidsl = _subtreealbums(docidsl)
|
307 |
albidsl = _subtreealbums(docidsl)
|
285 |
albids = ','.join([str(a) for a in albidsl])
|
308 |
albids = ','.join([str(a) for a in albidsl])
|
|
... |
|
... |
323 |
def _subtreealbums(docidsl):
|
346 |
def _subtreealbums(docidsl):
|
324 |
docids = ','.join([str(r) for r in docidsl])
|
347 |
docids = ','.join([str(r) for r in docidsl])
|
325 |
albids = []
|
348 |
albids = []
|
326 |
stmt = 'SELECT album_id from tracks where docidx IN (' + docids + ') ' + \
|
349 |
stmt = 'SELECT album_id from tracks where docidx IN (' + docids + ') ' + \
|
327 |
'GROUP BY album_id'
|
350 |
'GROUP BY album_id'
|
328 |
c = sqconn.cursor()
|
351 |
c = _sqconn().cursor()
|
329 |
uplog('subtreealbums: executing %s' % stmt)
|
352 |
uplog('subtreealbums: executing %s' % stmt)
|
330 |
c.execute(stmt)
|
353 |
c.execute(stmt)
|
331 |
for r in c:
|
354 |
for r in c:
|
332 |
albids.append(r[0])
|
355 |
albids.append(r[0])
|
333 |
uplog('subtreealbums: returning %s' % albids)
|
356 |
uplog('subtreealbums: returning %s' % albids)
|
|
... |
|
... |
415 |
stmt = "SELECT " + selwhat + " FROM tracks, " + col + \
|
438 |
stmt = "SELECT " + selwhat + " FROM tracks, " + col + \
|
416 |
selwhere + \
|
439 |
selwhere + \
|
417 |
" GROUP BY tracks." + _clid(col) + \
|
440 |
" GROUP BY tracks." + _clid(col) + \
|
418 |
" ORDER BY value"
|
441 |
" ORDER BY value"
|
419 |
uplog("tagsbrowse: executing <%s> values %s" % (stmt,values))
|
442 |
uplog("tagsbrowse: executing <%s> values %s" % (stmt,values))
|
420 |
c = sqconn.cursor()
|
443 |
c = _sqconn().cursor()
|
421 |
c.execute(stmt, values)
|
444 |
c.execute(stmt, values)
|
422 |
for r in c:
|
445 |
for r in c:
|
423 |
id = pid + '$' + str(r[0])
|
446 |
id = pid + '$' + str(r[0])
|
424 |
entries.append(rcldirentry(id, pid, r[1]))
|
447 |
entries.append(rcldirentry(id, pid, r[1]))
|
425 |
return entries
|
448 |
return entries
|
|
... |
|
... |
427 |
|
450 |
|
428 |
# Browse the top-level tree named like 'xxx albums'. There are just 2
|
451 |
# Browse the top-level tree named like 'xxx albums'. There are just 2
|
429 |
# levels: the whole albums list, then for each entry the specified
|
452 |
# levels: the whole albums list, then for each entry the specified
|
430 |
# albums track list
|
453 |
# albums track list
|
431 |
def _albumsbrowse(pid, qpath, flag, httphp, pathprefix):
|
454 |
def _albumsbrowse(pid, qpath, flag, httphp, pathprefix):
|
432 |
c = sqconn.cursor()
|
455 |
c = _sqconn().cursor()
|
433 |
entries = []
|
456 |
entries = []
|
434 |
if len(qpath) == 1:
|
457 |
if len(qpath) == 1:
|
435 |
entries = _direntriesforalbums(pid, '')
|
458 |
entries = _direntriesforalbums(pid, '')
|
436 |
elif len(qpath) == 2:
|
459 |
elif len(qpath) == 2:
|
437 |
e1 = qpath[1]
|
460 |
e1 = qpath[1]
|
|
... |
|
... |
470 |
|
493 |
|
471 |
|
494 |
|
472 |
############ Misc test/trial code, not used by uprcl ########################
|
495 |
############ Misc test/trial code, not used by uprcl ########################
|
473 |
|
496 |
|
474 |
def misctries():
|
497 |
def misctries():
|
475 |
c = sqconn.cursor()
|
498 |
c = _sqconn().cursor()
|
476 |
c.execute('''SELECT COUNT(*) FROM tracks''')
|
499 |
c.execute('''SELECT COUNT(*) FROM tracks''')
|
477 |
uplog("Count(*) %d" % (c.fetchone()[0],))
|
500 |
uplog("Count(*) %d" % (c.fetchone()[0],))
|
478 |
|
501 |
|
479 |
#for row in c.execute('''SELECT album
|
502 |
#for row in c.execute('''SELECT album
|
480 |
# FROM tracks where artist LIKE "%Gould%"
|
503 |
# FROM tracks where artist LIKE "%Gould%"
|