mirror of
https://github.com/spl0k/supysonic.git
synced 2024-12-22 17:06:17 +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 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
18
db.py
@ -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),
|
||||||
|
38
scanner.py
38
scanner.py
@ -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
28
web.py
@ -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'))
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user