WIP Continuing to implement ORM.
This commit is contained in:
parent
32904e19cd
commit
d72ce4467f
@ -5,5 +5,6 @@ allprojects {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
maven { url "https://dl.bintray.com/ijabz/maven" }
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'groovy'
|
||||
apply plugin: 'war'
|
||||
|
||||
@ -6,8 +7,12 @@ dependencies {
|
||||
compile 'ch.qos.logback:logback-classic:1.1.2'
|
||||
compile 'ch.qos.logback:logback-core:1.1.2'
|
||||
compile 'org.slf4j:slf4j-api:1.7.10'
|
||||
compile 'com.zaxxer:HikariCP:2.4.3'
|
||||
compile 'net.jthink:jaudiotagger:2.2.3'
|
||||
compile 'commons-codec:commons-codec:1.10'
|
||||
|
||||
testCompile 'junit:junit:4.12'
|
||||
|
||||
runtime 'com.h2database:h2:1.4.185'
|
||||
runtime 'org.postgresql:postgresql:9.4.1207.jre7'
|
||||
}
|
||||
|
@ -0,0 +1,133 @@
|
||||
package com.jdbernard.wdiwtlt
|
||||
|
||||
import com.jdbernard.wdiwtlt.db.ORM
|
||||
import com.jdbernard.wdiwtlt.db.models.*
|
||||
|
||||
import org.jaudiotagger.audio.AudioFile
|
||||
import org.jaudiotagger.audio.AudioFileIO
|
||||
import org.jaudiotagger.tag.Tag as JATag
|
||||
import org.jaudiotagger.tag.FieldKey
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import static org.jaudiotagger.tag.FieldKey.*
|
||||
|
||||
public class MediaLibrary {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(MediaLibrary)
|
||||
|
||||
private ORM orm
|
||||
private File libraryRoot
|
||||
|
||||
public MediaLibrary(ORM orm, File rootDir) {
|
||||
logger.debug("Creating a MediaLibrary rooted at: {}",
|
||||
rootDir.canonicalPath)
|
||||
|
||||
this.orm = org
|
||||
this.libraryRoot = rootDir }
|
||||
|
||||
public void clean() {
|
||||
orm.removeEmptyAlbums()
|
||||
orm.removeEmptyArtists()
|
||||
orm.removeEmptyPlaylists()
|
||||
}
|
||||
|
||||
public MediaFile addFile(File f) {
|
||||
if (!f.exists() || !f.isFile()) {
|
||||
logger.debug("Ignoring non-existant file: {}", f.canonicalPath)
|
||||
return null }
|
||||
|
||||
def relPath = getRelativePath(libraryRoot, f)
|
||||
MediaFile mf = orm.getMediaFileByFilePath(relPath)
|
||||
|
||||
if (mf) {
|
||||
logger.debug("Ignoring a media file I already know about: {}",
|
||||
relPath)
|
||||
return mf }
|
||||
|
||||
// Read in the media's tags
|
||||
mf = new MediaFile()
|
||||
def af = AudioFileIO.read(f)
|
||||
def fileTag = af.tag
|
||||
|
||||
mf.name = fileTag.getFirst(TITLE).trim() ?: f.name
|
||||
mf.filePath = relPath
|
||||
mf.comment = fileTag.getFirst(COMMENT).trim()
|
||||
|
||||
def folderParts = mf.filePath.split("[\\\\/]")[1..<-1]
|
||||
|
||||
// Find artist and album names (if any)
|
||||
def artistName = fileTag.getFirst(ARTIST).trim()
|
||||
def albumName = fileTag.getFirst(ALBUM).trim()
|
||||
|
||||
if (!artistName) {
|
||||
mf.metaInfoSource = MediaFile.FILE_LOCATION
|
||||
artistName = folderParts.size() >= 2 ? folderParts[0] : null }
|
||||
|
||||
if (!albumName) {
|
||||
mf.metaInfoSource = MediaFile.FILE_LOCATION
|
||||
albumName = folderParts.peekLast() }
|
||||
|
||||
// Hash the file
|
||||
mf.fileHash = f.withInputStream { DigestUtils.md5Hex(it) }
|
||||
orm.create(mf)
|
||||
|
||||
associateWithArtistAndAlbum(mf, artistName, albumName)
|
||||
}
|
||||
|
||||
private void associateWithArtistAndAlbum(MediaFile mf, String artistName,
|
||||
String albumName) {
|
||||
Artist artist = null
|
||||
Album album = null
|
||||
boolean newAlbumOrArtist = false
|
||||
|
||||
if (albumName) {
|
||||
album = orm.findAlbumByName(albumName)
|
||||
if (!album) {
|
||||
newAlbumOrArtist = true
|
||||
album = new Album(name: albumName,
|
||||
year: fileTag.getFirst(YEAR) ?: null)
|
||||
orm.create(album) } }
|
||||
|
||||
if (artistName) {
|
||||
artist = orm.findArtistByName(artistName)
|
||||
if (!artist) {
|
||||
newAlbumOrArtist = true
|
||||
artist = new Artist(name: artistName)
|
||||
orm.create(artist) } }
|
||||
|
||||
// TODO: need to rethink for case where another album does alrady exist
|
||||
// by this name, but is already associated with a different artist.
|
||||
|
||||
if (artist && album && newAlbumOrArtist)
|
||||
orm.addAlbumArtist(album.id, artist.id)
|
||||
|
||||
if (artist) orm.associateMediaFileWithArtist(mf.id, artist.id)
|
||||
if (album) orm.associateMediaFileWithAlbum(mf.id, album.id)
|
||||
}
|
||||
|
||||
/** #### `getRelativePath`
|
||||
* Given a parent path and a child path, assuming the child path is
|
||||
* contained within the parent path, return the relative path from the
|
||||
* parent to the child. */
|
||||
public static String getRelativePath(File parent, File child) {
|
||||
def parentPath = parent.canonicalPath.split("[\\\\/]")
|
||||
def childPath = child.canonicalPath.split("[\\\\/]")
|
||||
|
||||
/// If the parent path is longer it cannot contain the child path and
|
||||
/// we cannot construct a relative path without backtracking.
|
||||
if (parentPath.length > childPath.length) return ""
|
||||
|
||||
/// Compare the parent and child path up until the end of the parent
|
||||
/// path.
|
||||
int b = 0
|
||||
while (b < parentPath.length && parentPath[b] == childPath[b] ) b++
|
||||
|
||||
/// If we stopped before reaching the end of the parent path it must be
|
||||
/// that the paths do not match. The parent cannot contain the child and
|
||||
/// we cannot build a relative path without backtracking.
|
||||
if (b != parentPath.length) return ""
|
||||
return (['.'] + childPath[b..<childPath.length]).join('/') }
|
||||
|
||||
|
||||
}
|
@ -1,103 +0,0 @@
|
||||
package com.jdbernard.wdiwtlt.db
|
||||
|
||||
import java.sql.Connection
|
||||
import java.sql.PreparedStatement
|
||||
import java.sql.ResultSet
|
||||
import java.text.SimpleDateFormat
|
||||
import com.zaxxer.hikari.HikariDataSource
|
||||
|
||||
import groovy.sql.Sql
|
||||
import groovy.transform.CompileStatic
|
||||
|
||||
public class DBv1 {
|
||||
|
||||
private HikariDataSource dataSource
|
||||
private Sql sq
|
||||
|
||||
public DBv1(HikariDataSourec dataSource) {
|
||||
this.dataSource = dataSource
|
||||
this.sql = new Sql(dataSource) }
|
||||
|
||||
public void shutdown() { dataSource.shutdown() }
|
||||
|
||||
/// ### Common
|
||||
public def save(def model) {
|
||||
if (model.id > 0) return update(model)
|
||||
else return (create(model)) }
|
||||
|
||||
public def update(def model) {
|
||||
def setClauses = []
|
||||
def params = []
|
||||
|
||||
model.properties
|
||||
.findAll { it.key != 'class' && it.key != 'id' }
|
||||
.each {
|
||||
setClauses << '"' + it.key.replaceAll(
|
||||
/(\p{javaUpperCase})/, /_$1/).toLowerCase() + '"= ?'
|
||||
params << it.value }
|
||||
|
||||
def sql = new StringBuilder()
|
||||
.append("UPDATE ")
|
||||
.append(model.class.name)
|
||||
.append(" SET ")
|
||||
.append(setClauses.join(', '))
|
||||
.append(" WHERE id = ")
|
||||
.append(model.id)
|
||||
.toString()
|
||||
return sql.executeUpdate(sql, params) }
|
||||
|
||||
public def create(def model) {
|
||||
def columns = []
|
||||
def params = []
|
||||
|
||||
model.properties
|
||||
.findAll { it.key != 'class' && it.key != 'id' }
|
||||
.each {
|
||||
setClauses << '"' + it.key.replaceAll(
|
||||
/(\p{javaUpperCase})/, /_$1/).toLowerCase() + '"'
|
||||
params << it.value }
|
||||
|
||||
def sql = new StringBuilder()
|
||||
.append("INSERT INTO ")
|
||||
.append(model.class.name)
|
||||
.append(" (")
|
||||
.append(columns.join(', '))
|
||||
.append(") VALUES (")
|
||||
.append((1..columns.size()).collect { '?' }.join(', '))
|
||||
.append(")")
|
||||
|
||||
return sql.executeInsert(sql, params) }
|
||||
|
||||
public def delete(def model) {
|
||||
sql.execute("DELETE FROM ${model.class.name} WHERE id = ?", [model.id])
|
||||
return sql.updateCount }
|
||||
|
||||
/// ### Utility functions
|
||||
static def recordToModel(def record, Class clazz) {
|
||||
if (record == null) return null
|
||||
|
||||
def model = clazz.newInstance()
|
||||
|
||||
record.each { recordKey, v ->
|
||||
def pts = recordKey.toLowerCase().split('_')
|
||||
def modelKey = pts.length == 1 ? pts[0] :
|
||||
pts[0] + pts[1..-1].collect { it.capitalize() }.join()
|
||||
|
||||
model[modelKey] = v }
|
||||
return model }
|
||||
|
||||
static def modelToRecord(def model) {
|
||||
if (model == null) return null
|
||||
|
||||
def record = [:]
|
||||
|
||||
model.properties.each { modelKey, v ->
|
||||
if (modelKey == "class") return
|
||||
def recordKey = modelKey.
|
||||
replaceAll(/(\p{javaUpperCase})/, /_$1/).toLowerCase()
|
||||
|
||||
record[recordKey] = v }
|
||||
return record }
|
||||
|
||||
|
||||
}
|
234
service/src/main/groovy/com/jdbernard/wdiwtlt/db/ORM.groovy
Normal file
234
service/src/main/groovy/com/jdbernard/wdiwtlt/db/ORM.groovy
Normal file
@ -0,0 +1,234 @@
|
||||
package com.jdbernard.wdiwtlt.db
|
||||
|
||||
import java.sql.Connection
|
||||
import java.sql.PreparedStatement
|
||||
import java.sql.ResultSet
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.regex.Pattern
|
||||
import javax.sql.DataSource
|
||||
|
||||
import groovy.sql.Sql
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.TailRecursive
|
||||
|
||||
import com.jdbernard.wdiwtlt.db.models.*
|
||||
|
||||
public class ORM {
|
||||
|
||||
private DataSource dataSource
|
||||
private Sql sql
|
||||
|
||||
private static UPPERCASE_PATTERN = Pattern.compile(/(.)(\p{javaUpperCase})/)
|
||||
|
||||
public ORM(DataSource dataSource) {
|
||||
this.dataSource = dataSource
|
||||
this.sql = new Sql(dataSource) }
|
||||
|
||||
public void shutdown() { dataSource.shutdown() }
|
||||
|
||||
/// ### Common
|
||||
public def getById(int id, Class modelClass) {
|
||||
def query = new StringBuilder()
|
||||
.append("SELECT * FROM ")
|
||||
.append(pluralize(nameFromModel(modelClass.simpleName)))
|
||||
.append(" WHERE id = ?")
|
||||
.toString()
|
||||
|
||||
return recordToModel(sql.firstRow(query, id), modelClass) }
|
||||
|
||||
public def getByName(String name, Class modelClass) {
|
||||
def query = new StringBuilder()
|
||||
.append("SELECT * FROM ")
|
||||
.append(pluralize(nameFromModel(modelClass.simpleName)))
|
||||
.append(" WHERE name = ?")
|
||||
.toString()
|
||||
|
||||
return recordToModel(sql.firstRow(query, name), modelClass) }
|
||||
|
||||
public def getBy(List<String> columns, List<Object> values,
|
||||
Class modelClass) {
|
||||
def query = new StringBuilder()
|
||||
.append("SELECT * FROM ")
|
||||
.append(pluralize(nameFromModel(modelClass.simpleName)))
|
||||
.append(" WHERE ")
|
||||
.append(columns.collect { it + " = ?" }.join(' AND '))
|
||||
.toString()
|
||||
|
||||
return sql.rows(query, values)
|
||||
.collect { recordToModel(it, modelClass) } }
|
||||
|
||||
public def save(def model) {
|
||||
if (model.id > 0) return update(model)
|
||||
else return create(model) }
|
||||
|
||||
public def update(def model) {
|
||||
def setClauses = []
|
||||
def params = []
|
||||
|
||||
model.class.fields
|
||||
.findAll { it.name != 'id' }
|
||||
.each { field ->
|
||||
setClauses << '"' + nameFromModel(field.name) + '"= ?'
|
||||
params << field.get(model) }
|
||||
|
||||
def query = new StringBuilder()
|
||||
.append("UPDATE ")
|
||||
.append(pluralize(nameFromModel(model.class.simpleName)))
|
||||
.append(" SET ")
|
||||
.append(setClauses.join(', '))
|
||||
.append(" WHERE id = ")
|
||||
.append(model.id)
|
||||
.toString()
|
||||
|
||||
return sql.executeUpdate(query, params) }
|
||||
|
||||
public def create(def model) {
|
||||
def columns = []
|
||||
def params = []
|
||||
|
||||
model.class.fields
|
||||
.findAll { it.name != 'id' }
|
||||
.each { field ->
|
||||
//if (field.class.getAnnotation(Model)) // check to see if we
|
||||
// have nested models
|
||||
columns << '"' + nameFromModel(field.name) + '"'
|
||||
params << field.get(model) }
|
||||
|
||||
def query= new StringBuilder()
|
||||
.append("INSERT INTO ")
|
||||
.append(pluralize(nameFromModel(model.class.simpleName)))
|
||||
.append(" (")
|
||||
.append(columns.join(', '))
|
||||
.append(") VALUES (")
|
||||
.append((1..columns.size()).collect { '?' }.join(', '))
|
||||
.append(")").toString()
|
||||
|
||||
model.id = sql.executeInsert(query, params)[0][0]
|
||||
return 1 }
|
||||
|
||||
public def delete(def model) {
|
||||
sql.execute("DELETE FROM ${model.class.simpleName} WHERE id = ?", [model.id])
|
||||
return sql.updateCount }
|
||||
|
||||
public def associate(String linkTable, int firstId, int secondId) {
|
||||
return sql.executeInsert(
|
||||
"INSERT INTO $linkTable VALUES (?, ?) ON CONFLICT DO NOTHING",
|
||||
[firstId, secondId]) }
|
||||
|
||||
public def associate(def m1, def m2) {
|
||||
return associate(pluralize(nameFromModel(m1.class.simpleName)) +
|
||||
"_" + pluralize(nameFromModel(m2.class.simpleName)),
|
||||
m1.id, m2.id) }
|
||||
|
||||
/// ### Album-specific methods
|
||||
public Album getAlbumById(int id) { return getById(id, Album) }
|
||||
public Album getAlbumByName(String name) { return getByName(name, Album) }
|
||||
public Album getAlbumByNameAndArtistId(String name, int artistId) {
|
||||
def albums = sql.rows("""\
|
||||
SELECT al.*
|
||||
FROM albums al JOIN
|
||||
artists_albums aa ON
|
||||
al.id = aa.album_id AND
|
||||
aa.artist_id = ?
|
||||
WHERE al.name = ?""",
|
||||
[artistId, name]).collect { recordToModel(it, Album) }
|
||||
return albums ? albums[0] : null }
|
||||
|
||||
public Album getAlbumsByArtistId(int artistId) {
|
||||
return sql.rows("""\
|
||||
SELECT al.*
|
||||
FROM albums al JOIN
|
||||
artists_albums aa ON
|
||||
al.id = aa.album_id AND
|
||||
aa.artist_id = ?""",
|
||||
[artistId]).collect { recordToModel(it, Album) } }
|
||||
|
||||
public List<Album> removeEmptyAlbums() {
|
||||
throw new UnsupportedOperationException("Not yet implemented.");
|
||||
}
|
||||
|
||||
/// ### Artist-specific methods
|
||||
public Artist getArtistById(int id) { return getById(id, Artist) }
|
||||
public Artist getArtistByName(String name) { return getByName(name, Artist) }
|
||||
public Artist getArtistsByAlbum(int albumId) {
|
||||
return sql.rows("""\
|
||||
SELECT ar.*
|
||||
FROM artists ar JOIN
|
||||
artists_albums aa ON
|
||||
ar.id = aa.artist_id AND
|
||||
aa.album_id = ?""",
|
||||
[albumId]).collect { recordToModel(it, Artist) } }
|
||||
|
||||
public List<Artist> removeEmptyArtists() {
|
||||
throw new UnsupportedOperationException("Not yet implemented.");
|
||||
}
|
||||
|
||||
public def addAlbumArtist(int albumId, int artistId) {
|
||||
return associate("artists_albums", artistId, albumId) }
|
||||
|
||||
/// ### Bookmark-specific methods
|
||||
public Bookmark getBookmarkById(int id) { return getById(id, Bookmark) }
|
||||
public Bookmark getBookmarkByName(String name) { return getByName(name, Bookmark) }
|
||||
|
||||
/// ### Image-specific methods
|
||||
public Image getImageById(int id) { return getById(id, Image) }
|
||||
|
||||
/// ### MediaFile-specific methods
|
||||
public MediaFile getMediaFileById(int id) { return getById(id, MediaFile) }
|
||||
public MediaFile getMediaFileByName(String name) { return getByName(name, MediaFile) }
|
||||
|
||||
public def associateMediaFileWithAlbum(int mediaFileId, int albumId) {
|
||||
return associate("albums_media_files", albumId, mediaFileId) }
|
||||
|
||||
public def associateMediaFileWithArtist(int mediaFileId, int artistId) {
|
||||
return associate("artists_media_files", artistId, mediaFileId) }
|
||||
|
||||
/// ### Playlist-specific methods
|
||||
public Playlist getPlaylistById(int id) { return getById(id, Playlist) }
|
||||
public Playlist getPlaylistByName(String name) { return getByName(name, Playlist) }
|
||||
|
||||
public List<Playlist> removeEmptyPlaylists() {
|
||||
throw new UnsupportedOperationException("Not yet implemented.");
|
||||
}
|
||||
|
||||
/// ### Tag-specific methods
|
||||
public Tag getTagById(int id) { return getById(id, Tag) }
|
||||
public Tag getTagByName(String name) { return getByName(name, Tag) }
|
||||
|
||||
/// ### Utility functions
|
||||
public static String nameToModel(String name) {
|
||||
def pts = name.toLowerCase().split('_')
|
||||
return pts.length == 1 ? pts[0] :
|
||||
pts[0] + pts[1..-1].collect { it.capitalize() }.join() }
|
||||
|
||||
public static String nameFromModel(String name) {
|
||||
return UPPERCASE_PATTERN.matcher(name).
|
||||
replaceAll(/$1_$2/).toLowerCase() }
|
||||
|
||||
public static String pluralize(String name) { return name + "s" }
|
||||
|
||||
static def updateModel(def record, def model) {
|
||||
model.class.fields.each { field ->
|
||||
field.set(model, record[nameFromModel(field.name)]) }
|
||||
return model }
|
||||
|
||||
static def recordToModel(def record, Class clazz) {
|
||||
if (record == null) return null
|
||||
|
||||
def model = clazz.newInstance()
|
||||
|
||||
model.class.fields.each { field ->
|
||||
field.set(model, record[nameFromModel(field.name)]) }
|
||||
|
||||
return model }
|
||||
|
||||
static def modelToRecord(def model) {
|
||||
if (model == null) return null
|
||||
|
||||
def record = [:]
|
||||
|
||||
model.class.fields.each { field ->
|
||||
record[nameFromModel(field.name)] = field.get(model) }
|
||||
|
||||
return record }
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package com.jdbernard.wdiwtlt.db.models;
|
||||
|
||||
@Model
|
||||
public class Album {
|
||||
public int id;
|
||||
public String name;
|
||||
public Integer year;
|
||||
|
||||
public String toString() { return name + " (" + year + ")"; }
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.jdbernard.wdiwtlt.db.models;
|
||||
|
||||
@Model
|
||||
public class Artist {
|
||||
public int id;
|
||||
public String name;
|
||||
|
||||
public String toString() { return name; }
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package com.jdbernard.wdiwtlt.db.models;
|
||||
|
||||
@Model
|
||||
public class Bookmark {
|
||||
public int id;
|
||||
public String name;
|
||||
public int playlistId;
|
||||
public int mediaFileId;
|
||||
public int playIndex;
|
||||
|
||||
public String toString() { return name; }
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.jdbernard.wdiwtlt.db.models;
|
||||
|
||||
@Model
|
||||
public class Image {
|
||||
public int id;
|
||||
public String url;
|
||||
|
||||
public String toString() { return url; }
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.jdbernard.wdiwtlt.db.models;
|
||||
|
||||
@Model
|
||||
public class MediaFile {
|
||||
public static final String TAG_INFO = "tag info";
|
||||
public static final String FILE_LOCATION = "file location";
|
||||
|
||||
public int id;
|
||||
public String name;
|
||||
public int playCount = 0;
|
||||
public String filePath;
|
||||
public String fileHash;
|
||||
public String metaInfoSource = TAG_INFO;
|
||||
public String comment;
|
||||
|
||||
public String toString() { return name; }
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.jdbernard.wdiwtlt.db.models;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Model { }
|
@ -0,0 +1,10 @@
|
||||
package com.jdbernard.wdiwtlt.db.models;
|
||||
|
||||
@Model
|
||||
public class Playlist {
|
||||
public int id;
|
||||
public String name;
|
||||
public int modCount;
|
||||
|
||||
public String toString() { return name; }
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.jdbernard.wdiwtlt.db.models;
|
||||
|
||||
@Model
|
||||
public class Tag {
|
||||
public int id;
|
||||
public String name;
|
||||
|
||||
public String toString() { return name; }
|
||||
}
|
@ -2,10 +2,13 @@ DROP TABLE artists_albums;
|
||||
DROP TABLE albums_images;
|
||||
DROP TABLE artists_images;
|
||||
DROP TABLE images;
|
||||
DROP TABLE albums;
|
||||
DROP TABLE artists;
|
||||
DROP TABLE media_files_tags;
|
||||
DROP TABLE bookmarks;
|
||||
DROP TABLE playlists_media_files;
|
||||
DROP TABLE playlists;
|
||||
DROP TABLE tags;
|
||||
DROP TABLE artists_media_files;
|
||||
DROP TABLE albums_media_files;
|
||||
DROP TABLE media_files;
|
||||
DROP TABLE albums;
|
||||
DROP TABLE artists;
|
||||
|
@ -1,12 +1,36 @@
|
||||
CREATE TABLE artists (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR UNIQUE NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE albums (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR NOT NULL,
|
||||
year INTEGER
|
||||
);
|
||||
|
||||
CREATE TABLE media_files (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR NOT NULL,
|
||||
artist_id INTEGER,
|
||||
album_id INTEGER,
|
||||
play_count INTEGER NOT NULL DEFAULT 0,
|
||||
file_path VARCHAR NOT NULL,
|
||||
file_hash VARCHAR NOT NULL,
|
||||
meta_info_source VARCHAR NOT NULL, -- 'tag' or 'filesystem'
|
||||
comment VARCHAR DEFAULT ''
|
||||
);
|
||||
|
||||
CREATE TABLE artists_media_files (
|
||||
artist_id INTEGER NOT NULL REFERENCES artists(id) ON DELETE CASCADE,
|
||||
media_file_id INTEGER NOT NULL REFERENCES media_files(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (artist_id, media_file_id)
|
||||
);
|
||||
|
||||
CREATE TABLE albums_media_files (
|
||||
album_id INTEGER NOT NULL REFERENCES albums(id) ON DELETE CASCADE,
|
||||
media_file_id INTEGER NOT NULL REFERENCES media_files(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (album_id, media_file_id)
|
||||
);
|
||||
|
||||
CREATE TABLE tags (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR UNIQUE NOT NULL
|
||||
@ -19,41 +43,24 @@ CREATE TABLE playlists (
|
||||
);
|
||||
|
||||
CREATE TABLE playlists_media_files (
|
||||
playlist_id INTEGER NOT NULL,
|
||||
media_file_id INTEGER NOT NULL,
|
||||
playlist_id INTEGER NOT NULL REFERENCES playlists(id) ON DELETE CASCADE,
|
||||
media_file_id INTEGER NOT NULL REFERENCES media_files(id) ON DELETE CASCADE,
|
||||
position INTEGER NOT NULL DEFAULT 0,
|
||||
FOREIGN KEY (playlist_id) REFERENCES playlists(id),
|
||||
FOREIGN KEY (media_file_id) REFERENCES media_files(id),
|
||||
PRIMARY KEY (playlist_id, media_file_id)
|
||||
UNIQUE (playlist_id, media_file_id)
|
||||
);
|
||||
|
||||
CREATE TABLE bookmarks (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR,
|
||||
playlist_id INTEGER NOT NULL,
|
||||
media_file_id INTEGER NOT NULL,
|
||||
play_index INTEGER NOT NULL,
|
||||
FOREIGN KEY (playlist_id) REFERENCES playlists(id),
|
||||
FOREIGN KEY (media_file_id) REFERENCES media_files(id)
|
||||
playlist_id INTEGER NOT NULL REFERENCES playlists(id) ON DELETE CASCADE,
|
||||
media_file_id INTEGER NOT NULL REFERENCES media_files(id) ON DELETE CASCADE,
|
||||
play_index INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE media_files_tags (
|
||||
id SERIAL PRIMARY KEY,
|
||||
media_file_id INTEGER,
|
||||
tag_id INTEGER,
|
||||
FOREIGN KEY (media_file_id) REFERENCES media_files(id),
|
||||
FOREIGN KEY (tag_id) REFERENCES tags(id)
|
||||
);
|
||||
|
||||
CREATE TABLE artists (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE albums (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR NOT NULL,
|
||||
year INTEGER
|
||||
media_file_id INTEGER REFERENCES media_files(id) ON DELETE CASCADE,
|
||||
tag_id INTEGER REFERENCES tags(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE images (
|
||||
@ -62,25 +69,19 @@ CREATE TABLE images (
|
||||
);
|
||||
|
||||
CREATE TABLE artists_images (
|
||||
id SERIAL PRIMARY KEY,
|
||||
artist_id INTEGER,
|
||||
image_id INTEGER,
|
||||
FOREIGN KEY (artist_id) REFERENCES artists (id),
|
||||
FOREIGN KEY (image_id) REFERENCES images (id)
|
||||
artist_id INTEGER REFERENCES artists (id) ON DELETE CASCADE,
|
||||
image_id INTEGER REFERENCES images (id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (artist_id, image_id)
|
||||
);
|
||||
|
||||
CREATE TABLE albums_images (
|
||||
id SERIAL PRIMARY KEY,
|
||||
album_id INTEGER,
|
||||
image_id INTEGER,
|
||||
FOREIGN KEY (album_id) REFERENCES albums (id),
|
||||
FOREIGN KEY (image_id) REFERENCES images (id)
|
||||
album_id INTEGER REFERENCES albums (id) ON DELETE CASCADE,
|
||||
image_id INTEGER REFERENCES images (id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (album_id, image_id)
|
||||
);
|
||||
|
||||
CREATE TABLE artists_albums (
|
||||
artist_id INTEGER NOT NULL,
|
||||
album_id INTEGER NOT NULL,
|
||||
PRIMARY KEY (artist_id, album_id),
|
||||
FOREIGN KEY (artist_id) REFERENCES artists (id),
|
||||
FOREIGN KEY (album_id) REFERENCES albums (id)
|
||||
artist_id INTEGER NOT NULL REFERENCES artists (id) ON DELETE CASCADE,
|
||||
album_id INTEGER NOT NULL REFERENCES albums (id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (artist_id, album_id)
|
||||
);
|
||||
|
23
service/test.groovy
Normal file
23
service/test.groovy
Normal file
@ -0,0 +1,23 @@
|
||||
import groovy.grape.Grape
|
||||
import groovy.sql.Sql
|
||||
Grape.grab(group: 'com.zaxxer', module: 'HikariCP', version: '2.4.3')
|
||||
Grape.grab(group: 'org.postgresql', module: 'postgresql', version: '9.4.1207.jre7')
|
||||
|
||||
import com.jdbernard.wdiwtlt.db.ORM
|
||||
import com.jdbernard.wdiwtlt.db.models.*
|
||||
import com.zaxxer.hikari.*
|
||||
import org.jaudiotagger.audio.*
|
||||
import org.jaudiotagger.tag.*
|
||||
|
||||
config = new Properties()
|
||||
config.dataSourceClassName = "org.postgresql.ds.PGSimpleDataSource"
|
||||
config."dataSource.databaseName" = "wdiwtlt"
|
||||
config."dataSource.user" = "jonathan"
|
||||
config."dataSource.password" = ""
|
||||
|
||||
hcfg = new HikariConfig(config)
|
||||
hds = new HikariDataSource(hcfg)
|
||||
|
||||
db = new ORM(hds)
|
||||
|
||||
musicDir = new File('/Users/jonathan/Music')
|
Loading…
Reference in New Issue
Block a user