1
0
mirror of https://github.com/spl0k/supysonic.git synced 2024-09-19 19:01:03 +00:00

Taking folder hierarchy into account

This commit is contained in:
Alban 2012-10-21 16:18:35 +02:00
parent 4d4706a43a
commit 7e2e93bcfd
4 changed files with 69 additions and 25 deletions

View File

@ -2,7 +2,7 @@
from flask import request from flask import request
from web import app from web import app
from db import MusicFolder, Artist, Album, Track from db import Folder, Artist, Album, Track
import uuid, time, string import uuid, time, string
@app.route('/rest/getMusicFolders.view') @app.route('/rest/getMusicFolders.view')
@ -12,7 +12,7 @@ def list_folders():
'musicFolder': [ { 'musicFolder': [ {
'id': str(f.id), 'id': str(f.id),
'name': f.name 'name': f.name
} for f in MusicFolder.query.order_by(MusicFolder.name).all() ] } for f in Folder.query.filter(Folder.root == True).order_by(Folder.name).all() ]
} }
}) })
@ -27,16 +27,16 @@ def list_indexes():
return request.error_formatter(0, 'Invalid timestamp') return request.error_formatter(0, 'Invalid timestamp')
if musicFolderId is None: if musicFolderId is None:
folder = MusicFolder.query.all() folder = Folder.query.filter(Folder.root == True).all()
else: else:
try: try:
mfid = uuid.UUID(musicFolderId) mfid = uuid.UUID(musicFolderId)
except: except:
return request.error_formatter(0, 'Invalid id') return request.error_formatter(0, 'Invalid id')
folder = MusicFolder.query.get(mfid) folder = Folder.query.get(mfid)
if not folder: if not folder or not folder.root:
return request.error_formatter(70, 'Folder not found') return request.error_formatter(70, 'Folder not found')
last_modif = max(map(lambda f: f.last_scan, folder)) if type(folder) is list else folder.last_scan last_modif = max(map(lambda f: f.last_scan, folder)) if type(folder) is list else folder.last_scan

18
db.py
View File

@ -55,14 +55,18 @@ class User(Base):
salt = Column(String(6)) salt = Column(String(6))
admin = Column(Boolean) admin = Column(Boolean)
class MusicFolder(Base): class Folder(Base):
__tablename__ = 'folder' __tablename__ = 'folder'
id = UUID.gen_id_column() id = UUID.gen_id_column()
name = Column(String, unique = True) root = Column(Boolean, default = False)
path = Column(String) name = Column(String)
path = Column(String, unique = True)
last_scan = Column(DateTime, default = datetime.datetime.min) last_scan = Column(DateTime, default = datetime.datetime.min)
parent_id = Column(UUID, ForeignKey('folder.id'), nullable = True)
children = relationship('Folder', backref = backref('parent', remote_side = [ id ]))
class Artist(Base): class Artist(Base):
__tablename__ = 'artist' __tablename__ = 'artist'
@ -92,13 +96,15 @@ class Track(Base):
path = Column(String, unique = True) path = Column(String, unique = True)
bitrate = Column(Integer) bitrate = Column(Integer)
root_folder_id = Column(UUID, ForeignKey('folder.id'))
root_folder = relationship('Folder', primaryjoin = Folder.id == root_folder_id)
folder_id = Column(UUID, ForeignKey('folder.id')) folder_id = Column(UUID, ForeignKey('folder.id'))
folder = relationship('MusicFolder') folder = relationship('Folder', primaryjoin = Folder.id == folder_id, backref = 'tracks')
def as_subsonic_child(self): def as_subsonic_child(self):
info = { info = {
'id': str(self.id), 'id': str(self.id),
'parent': str(self.album.id), 'parent': str(self.folder.id),
'isDir': False, 'isDir': False,
'title': self.title, 'title': self.title,
'album': self.album.name, 'album': self.album.name,
@ -109,7 +115,7 @@ class Track(Base):
'suffix': 'mp3', # same as above 'suffix': 'mp3', # same as above
'duration': self.duration, 'duration': self.duration,
'bitRate': self.bitrate, 'bitRate': self.bitrate,
'path': self.path[len(self.folder.path) + 1:], 'path': self.path[len(self.root_folder.path) + 1:],
'isVideo': False, 'isVideo': False,
'discNumber': self.disc, 'discNumber': self.disc,
'albumId': str(self.album.id), 'albumId': str(self.album.id),

View File

@ -1,6 +1,6 @@
# coding: utf-8 # coding: utf-8
import os.path import os, os.path
import datetime import datetime
import eyeD3 import eyeD3
@ -10,6 +10,8 @@ class Scanner:
def __init__(self, session): def __init__(self, session):
self.__session = session self.__session = session
self.__artists = db.Artist.query.all() self.__artists = db.Artist.query.all()
self.__folders = db.Folder.query.all()
self.__added_artists = 0 self.__added_artists = 0
self.__added_albums = 0 self.__added_albums = 0
self.__added_tracks = 0 self.__added_tracks = 0
@ -27,9 +29,10 @@ class Scanner:
def prune(self, folder): def prune(self, folder):
for artist in db.Artist.query.all(): for artist in db.Artist.query.all():
for album in artist.albums[:]: for album in artist.albums[:]:
for track in filter(lambda t: t.folder.id == folder.id, album.tracks): for track in filter(lambda t: t.root_folder.id == folder.id, album.tracks):
if not os.path.exists(track.path): if not os.path.exists(track.path):
album.tracks.remove(track) album.tracks.remove(track)
track.folder.tracks.remove(track)
self.__session.delete(track) self.__session.delete(track)
self.__deleted_tracks += 1 self.__deleted_tracks += 1
if len(album.tracks) == 0: if len(album.tracks) == 0:
@ -40,6 +43,8 @@ class Scanner:
self.__session.delete(artist) self.__session.delete(artist)
self.__deleted_artists += 1 self.__deleted_artists += 1
self.__cleanup_folder(folder)
def __scan_file(self, path, folder): def __scan_file(self, path, folder):
tag = eyeD3.Tag() tag = eyeD3.Tag()
tag.link(path) tag.link(path)
@ -48,7 +53,7 @@ class Scanner:
al = self.__find_album(tag.getArtist(), tag.getAlbum()) al = self.__find_album(tag.getArtist(), tag.getAlbum())
tr = filter(lambda t: t.path == path, al.tracks) tr = filter(lambda t: t.path == path, al.tracks)
if not tr: if not tr:
tr = db.Track(path = path, folder = folder) tr = db.Track(path = path, root_folder = folder, folder = self.__find_folder(path, folder))
self.__added_tracks += 1 self.__added_tracks += 1
else: else:
tr = tr[0] tr = tr[0]
@ -85,6 +90,33 @@ class Scanner:
return ar return ar
def __find_folder(self, path, folder):
path = os.path.dirname(path)
fold = filter(lambda f: f.path == path, self.__folders)
if fold:
return fold[0]
full_path = folder.path
path = path[len(folder.path) + 1:]
for name in path.split(os.sep):
full_path = os.path.join(full_path, name)
fold = filter(lambda f: f.path == full_path, self.__folders)
if fold:
folder = fold[0]
else:
folder = db.Folder(root = False, name = name, path = full_path, parent = folder)
self.__folders.append(folder)
return folder
def __cleanup_folder(self, folder):
for f in folder.children:
self.__cleanup_folder(f)
if len(folder.children) == 0 and len(folder.tracks) == 0 and not folder.root:
folder.parent = None
self.__session.delete(folder)
def stats(self): def stats(self):
return (self.__added_artists, self.__added_albums, self.__added_tracks), (self.__deleted_artists, self.__deleted_albums, self.__deleted_tracks) return (self.__added_artists, self.__added_albums, self.__added_tracks), (self.__deleted_artists, self.__deleted_albums, self.__deleted_tracks)

28
web.py
View File

@ -23,7 +23,7 @@ def index():
flash('Not configured. Please create the first admin user') flash('Not configured. Please create the first admin user')
return redirect(url_for('add_user')) return redirect(url_for('add_user'))
""" """
return render_template('home.html', users = db.User.query.all(), folders = db.MusicFolder.query.all(), return render_template('home.html', users = db.User.query.all(), folders = db.Folder.query.filter(db.Folder.root == True).all(),
artists = db.Artist.query.order_by(db.Artist.name).all(), artists = db.Artist.query.order_by(db.Artist.name).all(),
albums = db.Album.query.join(db.Album.artist).order_by(db.Artist.name, db.Album.name).all()) albums = db.Album.query.join(db.Album.artist).order_by(db.Artist.name, db.Album.name).all())
@ -92,7 +92,7 @@ def add_folder():
if name in (None, ''): if name in (None, ''):
flash('The name is required.') flash('The name is required.')
error = True error = True
elif db.MusicFolder.query.filter(db.MusicFolder.name == name).first(): elif db.Folder.query.filter(db.Folder.name == name and db.Folder.root).first():
flash('There is already a folder with that name. Please pick another one.') flash('There is already a folder with that name. Please pick another one.')
error = True error = True
if path in (None, ''): if path in (None, ''):
@ -103,14 +103,14 @@ def add_folder():
if not os.path.isdir(path): if not os.path.isdir(path):
flash("The path '%s' doesn't exists or isn't a directory" % path) flash("The path '%s' doesn't exists or isn't a directory" % path)
error = True error = True
folder = db.MusicFolder.query.filter(db.MusicFolder.name == name).first() folder = db.Folder.query.filter(db.Folder.path == path).first()
if folder: if folder:
flash("This path is already registered with the name '%s'" % folder.name) flash("This path is already registered")
error = True error = True
if error: if error:
return render_template('addfolder.html') return render_template('addfolder.html')
folder = db.MusicFolder(name = name, path = path) folder = db.Folder(root = True, name = name, path = path)
db.session.add(folder) db.session.add(folder)
db.session.commit() db.session.commit()
flash("Folder '%s' created. You should now run a scan" % name) flash("Folder '%s' created. You should now run a scan" % name)
@ -125,7 +125,7 @@ def del_folder(id):
flash('Invalid folder id') flash('Invalid folder id')
return redirect(url_for('index')) return redirect(url_for('index'))
folder = db.MusicFolder.query.get(idid) folder = db.Folder.query.get(idid)
if folder is None: if folder is None:
flash('No such folder') flash('No such folder')
return redirect(url_for('index')) return redirect(url_for('index'))
@ -133,7 +133,7 @@ def del_folder(id):
# delete associated tracks and prune empty albums/artists # delete associated tracks and prune empty albums/artists
for artist in db.Artist.query.all(): for artist in db.Artist.query.all():
for album in artist.albums[:]: for album in artist.albums[:]:
for track in filter(lambda t: t.folder.id == folder.id, album.tracks): for track in filter(lambda t: t.root_folder.id == folder.id, album.tracks):
album.tracks.remove(track) album.tracks.remove(track)
db.session.delete(track) db.session.delete(track)
if len(album.tracks) == 0: if len(album.tracks) == 0:
@ -141,7 +141,13 @@ def del_folder(id):
db.session.delete(album) db.session.delete(album)
if len(artist.albums) == 0: if len(artist.albums) == 0:
db.session.delete(artist) db.session.delete(artist)
db.session.delete(folder)
def cleanup_folder(folder):
for f in folder.children:
cleanup_folder(f)
db.session.delete(folder)
cleanup_folder(folder)
db.session.commit() db.session.commit()
flash("Deleted folder '%s'" % folder.name) flash("Deleted folder '%s'" % folder.name)
@ -153,7 +159,7 @@ def del_folder(id):
def scan_folder(id = None): def scan_folder(id = None):
s = Scanner(db.session) s = Scanner(db.session)
if id is None: if id is None:
for folder in db.MusicFolder.query.all(): for folder in db.Folder.query.filter(db.Folder.root == True).all():
s.scan(folder) s.scan(folder)
s.prune(folder) s.prune(folder)
else: else:
@ -163,8 +169,8 @@ def scan_folder(id = None):
flash('Invalid folder id') flash('Invalid folder id')
return redirect(url_for('index')) return redirect(url_for('index'))
folder = db.MusicFolder.query.get(idid) folder = db.Folder.query.get(idid)
if folder is None: if folder is None or not folder.root:
flash('No such folder') flash('No such folder')
return redirect(url_for('index')) return redirect(url_for('index'))