mirror of
https://github.com/spl0k/supysonic.git
synced 2024-11-09 19:52:16 +00:00
Taking folder hierarchy into account
This commit is contained in:
parent
4d4706a43a
commit
7e2e93bcfd
@ -2,7 +2,7 @@
|
||||
|
||||
from flask import request
|
||||
from web import app
|
||||
from db import MusicFolder, Artist, Album, Track
|
||||
from db import Folder, Artist, Album, Track
|
||||
import uuid, time, string
|
||||
|
||||
@app.route('/rest/getMusicFolders.view')
|
||||
@ -12,7 +12,7 @@ def list_folders():
|
||||
'musicFolder': [ {
|
||||
'id': str(f.id),
|
||||
'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')
|
||||
|
||||
if musicFolderId is None:
|
||||
folder = MusicFolder.query.all()
|
||||
folder = Folder.query.filter(Folder.root == True).all()
|
||||
else:
|
||||
try:
|
||||
mfid = uuid.UUID(musicFolderId)
|
||||
except:
|
||||
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')
|
||||
|
||||
last_modif = max(map(lambda f: f.last_scan, folder)) if type(folder) is list else folder.last_scan
|
||||
|
18
db.py
18
db.py
@ -55,14 +55,18 @@ class User(Base):
|
||||
salt = Column(String(6))
|
||||
admin = Column(Boolean)
|
||||
|
||||
class MusicFolder(Base):
|
||||
class Folder(Base):
|
||||
__tablename__ = 'folder'
|
||||
|
||||
id = UUID.gen_id_column()
|
||||
name = Column(String, unique = True)
|
||||
path = Column(String)
|
||||
root = Column(Boolean, default = False)
|
||||
name = Column(String)
|
||||
path = Column(String, unique = True)
|
||||
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):
|
||||
__tablename__ = 'artist'
|
||||
|
||||
@ -92,13 +96,15 @@ class Track(Base):
|
||||
path = Column(String, unique = True)
|
||||
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 = relationship('MusicFolder')
|
||||
folder = relationship('Folder', primaryjoin = Folder.id == folder_id, backref = 'tracks')
|
||||
|
||||
def as_subsonic_child(self):
|
||||
info = {
|
||||
'id': str(self.id),
|
||||
'parent': str(self.album.id),
|
||||
'parent': str(self.folder.id),
|
||||
'isDir': False,
|
||||
'title': self.title,
|
||||
'album': self.album.name,
|
||||
@ -109,7 +115,7 @@ class Track(Base):
|
||||
'suffix': 'mp3', # same as above
|
||||
'duration': self.duration,
|
||||
'bitRate': self.bitrate,
|
||||
'path': self.path[len(self.folder.path) + 1:],
|
||||
'path': self.path[len(self.root_folder.path) + 1:],
|
||||
'isVideo': False,
|
||||
'discNumber': self.disc,
|
||||
'albumId': str(self.album.id),
|
||||
|
38
scanner.py
38
scanner.py
@ -1,6 +1,6 @@
|
||||
# coding: utf-8
|
||||
|
||||
import os.path
|
||||
import os, os.path
|
||||
import datetime
|
||||
import eyeD3
|
||||
|
||||
@ -10,6 +10,8 @@ class Scanner:
|
||||
def __init__(self, session):
|
||||
self.__session = session
|
||||
self.__artists = db.Artist.query.all()
|
||||
self.__folders = db.Folder.query.all()
|
||||
|
||||
self.__added_artists = 0
|
||||
self.__added_albums = 0
|
||||
self.__added_tracks = 0
|
||||
@ -27,9 +29,10 @@ class Scanner:
|
||||
def prune(self, folder):
|
||||
for artist in db.Artist.query.all():
|
||||
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):
|
||||
album.tracks.remove(track)
|
||||
track.folder.tracks.remove(track)
|
||||
self.__session.delete(track)
|
||||
self.__deleted_tracks += 1
|
||||
if len(album.tracks) == 0:
|
||||
@ -40,6 +43,8 @@ class Scanner:
|
||||
self.__session.delete(artist)
|
||||
self.__deleted_artists += 1
|
||||
|
||||
self.__cleanup_folder(folder)
|
||||
|
||||
def __scan_file(self, path, folder):
|
||||
tag = eyeD3.Tag()
|
||||
tag.link(path)
|
||||
@ -48,7 +53,7 @@ class Scanner:
|
||||
al = self.__find_album(tag.getArtist(), tag.getAlbum())
|
||||
tr = filter(lambda t: t.path == path, al.tracks)
|
||||
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
|
||||
else:
|
||||
tr = tr[0]
|
||||
@ -85,6 +90,33 @@ class Scanner:
|
||||
|
||||
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):
|
||||
return (self.__added_artists, self.__added_albums, self.__added_tracks), (self.__deleted_artists, self.__deleted_albums, self.__deleted_tracks)
|
||||
|
||||
|
26
web.py
26
web.py
@ -23,7 +23,7 @@ def index():
|
||||
flash('Not configured. Please create the first admin 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(),
|
||||
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, ''):
|
||||
flash('The name is required.')
|
||||
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.')
|
||||
error = True
|
||||
if path in (None, ''):
|
||||
@ -103,14 +103,14 @@ def add_folder():
|
||||
if not os.path.isdir(path):
|
||||
flash("The path '%s' doesn't exists or isn't a directory" % path)
|
||||
error = True
|
||||
folder = db.MusicFolder.query.filter(db.MusicFolder.name == name).first()
|
||||
folder = db.Folder.query.filter(db.Folder.path == path).first()
|
||||
if folder:
|
||||
flash("This path is already registered with the name '%s'" % folder.name)
|
||||
flash("This path is already registered")
|
||||
error = True
|
||||
if error:
|
||||
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.commit()
|
||||
flash("Folder '%s' created. You should now run a scan" % name)
|
||||
@ -125,7 +125,7 @@ def del_folder(id):
|
||||
flash('Invalid folder id')
|
||||
return redirect(url_for('index'))
|
||||
|
||||
folder = db.MusicFolder.query.get(idid)
|
||||
folder = db.Folder.query.get(idid)
|
||||
if folder is None:
|
||||
flash('No such folder')
|
||||
return redirect(url_for('index'))
|
||||
@ -133,7 +133,7 @@ def del_folder(id):
|
||||
# delete associated tracks and prune empty albums/artists
|
||||
for artist in db.Artist.query.all():
|
||||
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)
|
||||
db.session.delete(track)
|
||||
if len(album.tracks) == 0:
|
||||
@ -141,8 +141,14 @@ def del_folder(id):
|
||||
db.session.delete(album)
|
||||
if len(artist.albums) == 0:
|
||||
db.session.delete(artist)
|
||||
|
||||
def cleanup_folder(folder):
|
||||
for f in folder.children:
|
||||
cleanup_folder(f)
|
||||
db.session.delete(folder)
|
||||
|
||||
cleanup_folder(folder)
|
||||
|
||||
db.session.commit()
|
||||
flash("Deleted folder '%s'" % folder.name)
|
||||
|
||||
@ -153,7 +159,7 @@ def del_folder(id):
|
||||
def scan_folder(id = None):
|
||||
s = Scanner(db.session)
|
||||
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.prune(folder)
|
||||
else:
|
||||
@ -163,8 +169,8 @@ def scan_folder(id = None):
|
||||
flash('Invalid folder id')
|
||||
return redirect(url_for('index'))
|
||||
|
||||
folder = db.MusicFolder.query.get(idid)
|
||||
if folder is None:
|
||||
folder = db.Folder.query.get(idid)
|
||||
if folder is None or not folder.root:
|
||||
flash('No such folder')
|
||||
return redirect(url_for('index'))
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user