Implementing artist/album lookup in CLI.
This commit is contained in:
parent
6e6defe544
commit
90a11569da
@ -11,8 +11,8 @@ dependencies {
|
||||
compile 'ch.qos.logback:logback-core:1.1.3'
|
||||
compile 'org.slf4j:slf4j-api:1.7.14'
|
||||
compile 'com.offbytwo:docopt:0.6.0.20150202'
|
||||
compile 'com.jdbernard:jdb-util:4.2'
|
||||
compile 'org.fusesource.jansi:jansi-project:1.11'
|
||||
compile 'com.jdbernard:jdb-util:4.3'
|
||||
compile 'jline:jline:2.12'
|
||||
compile project(":wdiwtlt-core")
|
||||
|
||||
testCompile 'junit:junit:4.12'
|
||||
|
@ -3,11 +3,16 @@ package com.jdbernard.wdiwtlt.cli
|
||||
import com.jdbernard.wdiwtlt.ConfigWrapper
|
||||
import com.jdbernard.wdiwtlt.MediaLibrary
|
||||
import com.jdbernard.wdiwtlt.db.ORM
|
||||
import com.jdbernard.wdiwtlt.db.models.*
|
||||
import com.jdbernard.io.NonBlockingInputStreamReader
|
||||
import com.jdbernard.util.AnsiEscapeCodeSequence as ANSI
|
||||
import com.zaxxer.hikari.HikariConfig
|
||||
import com.zaxxer.hikari.HikariDataSource
|
||||
import org.docopt.Docopt
|
||||
import jline.console.ConsoleReader
|
||||
import java.util.regex.Matcher
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import static com.jdbernard.util.AnsiEscapeCodeSequence.*
|
||||
|
||||
@ -44,33 +49,46 @@ Options:
|
||||
Configuration:
|
||||
"""
|
||||
|
||||
private static Logger logger =
|
||||
LoggerFactory.getLogger(CommandLineInterface)
|
||||
private Properties cliConfig
|
||||
private MediaLibrary library
|
||||
|
||||
private String[] linesBuffer = new String[1024]
|
||||
private StringBuilder currentLine = new StringBuilder()
|
||||
private int nextAvailableLineIdx = 0
|
||||
private int newLineIdx = 0
|
||||
private String selectedLineIdx = 0
|
||||
private InputStream sin
|
||||
private InputStream inStream
|
||||
private OutputStream outStream
|
||||
|
||||
private PrintStream out
|
||||
private ConsoleReader reader
|
||||
private Thread consoleReaderThread
|
||||
private List<String> consoleReadBuffer =
|
||||
Collections.synchronizedList(new ArrayList<String>())
|
||||
private synchronized boolean running
|
||||
|
||||
private boolean running
|
||||
|
||||
private String titleStyle, normalStyle, promptStyle, errorStyle
|
||||
private String clearRemainingLine = new ANSI().eraseLine(Erase.ToEnd).toString()
|
||||
private String titleStyle, normalStyle, statusStyle, promptStyle,
|
||||
artistStyle, albumStyle, fileStyle, errorStyle
|
||||
private String clearLine = new ANSI().eraseLine(Erase.All).toString()
|
||||
private String afterInput =
|
||||
new ANSI().eraseLine(Erase.All).scrollUp().cursorUp().toString()
|
||||
private String beforeLeader =
|
||||
new ANSI().saveCursor().cursorPrevLine(2).toString()
|
||||
private String afterLeader =
|
||||
new ANSI().restoreCursor().eraseLine(Erase.ToEnd).toString()
|
||||
private String eraseLeader =
|
||||
new ANSI().cursorPrevLine().eraseLine(Erase.All).cursorPrevLine().eraseLine(Erase.All).toString()
|
||||
|
||||
private String errorMsg = ""
|
||||
private int lastPromptLineCount = 0
|
||||
private int displayWidth = 79
|
||||
private long msgTimeout
|
||||
private ScrollText currentlyPlaying = new ScrollText(
|
||||
maxWidth: displayWidth - 15 - VERSION.size(),
|
||||
text: "No media currently playing.")
|
||||
private ScrollText status = new ScrollText(maxWidth: displayWidth)
|
||||
private Date dismissMsgDate = new Date()
|
||||
|
||||
def selection = [:]
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
def opts = new Docopt(DOC).withVersion("wdiwtlt v$VERSION").parse(args)
|
||||
|
||||
println opts
|
||||
|
||||
def exitErr = { msg ->
|
||||
System.out.err.println("wdiwtlt: $msg")
|
||||
System.exit(1) }
|
||||
@ -148,164 +166,248 @@ Configuration:
|
||||
}
|
||||
|
||||
public CommandLineInterface(MediaLibrary library, InputStream sin,
|
||||
PrintStream out, Properties cliConfig) {
|
||||
OutputStream out, Properties cliConfig) {
|
||||
this.cliConfig = cliConfig
|
||||
this.library = library
|
||||
this.out = out
|
||||
this.sin = sin
|
||||
this.inStream = sin
|
||||
this.outStream = out
|
||||
|
||||
this.msgTimeout = cliConfig["message.timeout"] ?: 5000l
|
||||
|
||||
setupTextStyles()
|
||||
|
||||
this.consoleReaderThread = new Thread().start(this.&runReaderThread)
|
||||
}
|
||||
|
||||
void setupTextStyles() {
|
||||
titleStyle = new ANSI().color(Colors.BLUE, Colors.DEFAULT, true).toString()
|
||||
titleStyle = new ANSI().color(Colors.GREEN, Colors.DEFAULT, false).toString()
|
||||
normalStyle = new ANSI().resetText().toString()
|
||||
promptStyle = new ANSI().color(Colors.YELLOW, Colors.DEFAULT, true).toString()
|
||||
statusStyle = new ANSI().color(Colors.CYAN, Colors.DEFAULT, false).toString()
|
||||
artistStyle = new ANSI().color(Colors.RED, Colors.DEFAULT, false).toString()
|
||||
albumStyle = new ANSI().color(Colors.BLUE, Colors.DEFAULT, false).toString()
|
||||
fileStyle = new ANSI().color(Colors.GREEN, Colors.DEFAULT, false).toString()
|
||||
errorStyle = new ANSI().color(Colors.RED, Colors.DEFAULT, true).toString()
|
||||
}
|
||||
|
||||
private void runReaderThread() {
|
||||
this.reader = new ConsoleReader(this.inStream, this.outStream)
|
||||
this.reader.setPrompt(promptStyle + "> " + normalStyle)
|
||||
|
||||
String line
|
||||
while(this.running && !Thread.currentThread().isInterrupted()) {
|
||||
line = reader.readLine()
|
||||
if (line == null || line == "quit" || line == "exit") running = false
|
||||
else {
|
||||
consoleReadBuffer.add(line)
|
||||
outStream.print(afterInput)
|
||||
outStream.flush() } } }
|
||||
|
||||
public void repl() {
|
||||
this.running = true
|
||||
String line = null
|
||||
|
||||
printPrompt()
|
||||
outStream.println ""
|
||||
outStream.println ""
|
||||
drawLeader()
|
||||
|
||||
while(this.running) {
|
||||
|
||||
line = readInputLine()
|
||||
if (line != null) {
|
||||
out.flush()
|
||||
errorMsg = ""
|
||||
if (new Date() > dismissMsgDate) resetStatus()
|
||||
if (consoleReadBuffer.size() > 0) {
|
||||
line = consoleReadBuffer.remove(0)
|
||||
switch(line) {
|
||||
case ~/quit|exit|\u0004/:
|
||||
case ~/quit|exit|.+\u0004$/:
|
||||
running = false
|
||||
consoleReaderThread.interrupt()
|
||||
break
|
||||
|
||||
case 'scan':
|
||||
scanMediaLibrary()
|
||||
break
|
||||
|
||||
case 'debug':
|
||||
outStream.println(
|
||||
"\n\nConfig: \n" +
|
||||
cliConfig.collect { "\t${it.key}: ${it.value}" }
|
||||
.join("\n") +
|
||||
"\n\n\n")
|
||||
drawLeader(true)
|
||||
break
|
||||
|
||||
case "artist":
|
||||
selection.artist = null
|
||||
resetStatus()
|
||||
break
|
||||
|
||||
case ~/artist (.+)/:
|
||||
def input = Matcher.lastMatcher[0][1]?.trim()
|
||||
def match = library.getByIdOrName(input, Artist)
|
||||
if (!match) setErr("No artist matches '$input'.")
|
||||
else if (match.size() > 1)
|
||||
setErr("Multiple artists match '$input': " +
|
||||
match.collect { "${it.id}: ${it.name}" }
|
||||
.join(", "))
|
||||
else {
|
||||
selection.artist = match[0]
|
||||
resetStatus() }
|
||||
break
|
||||
|
||||
case "album":
|
||||
selection.album = null
|
||||
resetStatus()
|
||||
break
|
||||
|
||||
case ~/album (.+)/:
|
||||
def input = Matcher.lastMatcher[0][1]?.trim()
|
||||
def match = library.getByIdOrName(input, Album)
|
||||
if (!match) setErr("No album matches '$input'.")
|
||||
else if (match.size() > 1)
|
||||
setErr("Multiple albums match '$input': " +
|
||||
match.collect { "${it.id}: ${it.name}" }
|
||||
.join(", "))
|
||||
else {
|
||||
selection.album = match[0]
|
||||
resetStatus() }
|
||||
break
|
||||
|
||||
case "list artists for album":
|
||||
if (!selection.album) {
|
||||
setErr("No album is selected.")
|
||||
break }
|
||||
outStream.println(makeArtistList(
|
||||
library.getArtistsByAlbumId(selection.album.id),
|
||||
selection.album))
|
||||
drawLeader(true)
|
||||
break
|
||||
|
||||
case ~/list artists( .+)?/:
|
||||
def name = Matcher.lastMatcher[0][1]?.trim()
|
||||
def artists
|
||||
if (name) artists = library.getArtistsByName(name)
|
||||
else artists = library.getArtists()
|
||||
|
||||
outStream.println(makeArtistList(artists))
|
||||
drawLeader(true)
|
||||
break
|
||||
|
||||
case "list albums for artist":
|
||||
if (!selection.artist) {
|
||||
setErr("No artist selected.")
|
||||
break }
|
||||
outStream.println(makeAlbumList(
|
||||
library.getAlbumsByArtistId(selection.artist.id),
|
||||
selection.artist))
|
||||
drawLeader(true)
|
||||
break
|
||||
|
||||
case ~/list albums( .+)?/:
|
||||
def name = Matcher.lastMatcher[0][1]?.trim()
|
||||
def albums
|
||||
if (name) albums = library.getAlbumsByName(name)
|
||||
else albums = library.getAlbums()
|
||||
|
||||
outStream.println(makeAlbumList(albums))
|
||||
drawLeader(true)
|
||||
break
|
||||
|
||||
default:
|
||||
errorMsg = "Unrecognized command: '$line'"
|
||||
Thread.sleep(200)
|
||||
status.text = errorStyle +
|
||||
"Unrecognized command: '$line'${normalStyle}"
|
||||
dismissMsgDate = new Date(new Date().time + msgTimeout)
|
||||
drawLeader()
|
||||
Thread.sleep(250)
|
||||
break
|
||||
}
|
||||
printPrompt(true)
|
||||
} else {
|
||||
printPrompt(true)
|
||||
Thread.sleep(200)
|
||||
drawLeader()
|
||||
Thread.sleep(250)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void printPrompt(boolean redraw = false) {
|
||||
StringBuilder prompt = new StringBuilder()
|
||||
private void scanMediaLibrary() {
|
||||
status.text = "Scanning media library..."
|
||||
library.rescanLibrary()
|
||||
status.text = "Scanned ? files."
|
||||
dismissMsgDate = new Date(new Date().time + msgTimeout) }
|
||||
|
||||
if (redraw) prompt.append(new ANSI()
|
||||
.saveCursor()
|
||||
.cursorPrevLine(lastPromptLineCount))
|
||||
private void drawLeader(afterOutput = false) {
|
||||
|
||||
String leader = beforeLeader + getLeader() +
|
||||
(afterOutput ?
|
||||
(promptStyle + "> " + normalStyle) :
|
||||
afterLeader)
|
||||
|
||||
outStream.print(leader)
|
||||
outStream.flush() }
|
||||
|
||||
private String getLeader() {
|
||||
StringBuilder leader = new StringBuilder()
|
||||
.append(clearLine)
|
||||
|
||||
prompt.append(titleStyle)
|
||||
.append(titleStyle)
|
||||
.append("WDIWTLT - v")
|
||||
.append(VERSION)
|
||||
.append("-- ")
|
||||
.append(normalStyle)
|
||||
.append("TODO\n")
|
||||
|
||||
if (errorMsg) prompt
|
||||
.append(" -- ")
|
||||
.append(statusStyle)
|
||||
.append(currentlyPlaying)
|
||||
.append("\n")
|
||||
.append(clearLine)
|
||||
.append(errorStyle)
|
||||
.append(errorMsg)
|
||||
.append(normalStyle)
|
||||
.append(status)
|
||||
.append("\n")
|
||||
|
||||
if (redraw) prompt.append(new ANSI().restoreCursor())
|
||||
else prompt.append(promptStyle)
|
||||
.append("> ")
|
||||
return leader.toString() }
|
||||
|
||||
private String setErr(String errMsg) {
|
||||
status.text = errorStyle + errMsg
|
||||
dismissMsgDate = new Date(new Date().time + msgTimeout) }
|
||||
|
||||
private String makeAlbumList(def albums, Artist artist = null) {
|
||||
def result = new StringBuilder()
|
||||
.append(eraseLeader)
|
||||
.append("--------------------\nAlbums")
|
||||
|
||||
if (artist) result.append(" (for artist '$artist'):\n")
|
||||
else result.append(":\n\n")
|
||||
|
||||
result.append(albums.collect { "${it.id}: ${it}" }.join("\n"))
|
||||
.append("\n\n\n")
|
||||
|
||||
return result.toString() }
|
||||
|
||||
private String makeArtistList(def artists, Album album = null) {
|
||||
def result = new StringBuilder()
|
||||
.append(eraseLeader)
|
||||
.append("--------------------\nArists")
|
||||
|
||||
if (album) result.append(" (for album '$album'):\n")
|
||||
else result.append(":\n\n")
|
||||
|
||||
result.append(artists.collect { "${it.id}: ${it.name}" }.join("\n"))
|
||||
.append("\n\n\n")
|
||||
|
||||
return result.toString() }
|
||||
|
||||
private String resetStatus() {
|
||||
StringBuilder s = new StringBuilder()
|
||||
|
||||
if (selection.artist) s.append(artistStyle)
|
||||
.append(selection.artist)
|
||||
.append(normalStyle)
|
||||
.append(" / ")
|
||||
|
||||
if (selection.album) s.append(albumStyle)
|
||||
.append(selection.album)
|
||||
.append(normalStyle)
|
||||
.append(" / ")
|
||||
|
||||
lastPromptLineCount = 1 + (errorMsg ? 1 : 0)
|
||||
if (selection.mediaFile) s.append(fileStyle)
|
||||
.append(selection.mediaFile)
|
||||
|
||||
out.print(prompt.toString())
|
||||
out.flush()
|
||||
}
|
||||
if (s.size() == 0) status.text = "No current media selections."
|
||||
else status.text = s.toString()
|
||||
|
||||
private void replaceCurrentLine(String newLine) {
|
||||
String toPrint = new StringBuilder()
|
||||
.append(new ANSI().cursorHorizontalAbsolute(3))
|
||||
.append(clearRemainingLine)
|
||||
.append(newLine)
|
||||
.toString()
|
||||
currentLine = new StringBuilder(newLine)
|
||||
out.print(toPrint)
|
||||
out.flush()
|
||||
}
|
||||
|
||||
private String readInputLine() {
|
||||
int bytesAvailable = sin.available()
|
||||
if (bytesAvailable) {
|
||||
byte[] input = new byte[bytesAvailable]
|
||||
sin.read(input)
|
||||
|
||||
int idx = 0
|
||||
while (idx < input.length) {
|
||||
switch (input[idx]) {
|
||||
case 0x08: // backspace
|
||||
case 0x0B: // enter
|
||||
linesBuffer[newLineIdx] = currentLine.toString()
|
||||
newLineIdx = (newLineIdx + 1) % linesBuffer.length
|
||||
linesBuffer[newLineIdx] = null
|
||||
replaceCurrentLine("")
|
||||
break
|
||||
case 0x1B: // escape (prefix to arrows, etc)
|
||||
// arrows
|
||||
if (input.length > idx + 2 && input[idx + 1] == 0x5B) {
|
||||
switch(input[idx + 2]) {
|
||||
case 0x41: // up
|
||||
int ni = selectedLineIdx
|
||||
ni = (ni - 1 + linesBuffer.length) %
|
||||
linesBuffer.length
|
||||
if (linesBuffer[ni] != null) {
|
||||
selectedLineIdx = ni
|
||||
replaceCurrentLine(linesBuffer[ni]) }
|
||||
else {
|
||||
out.print(new ANSI().cursorBackwards(4) +
|
||||
clearRemainingLine)
|
||||
out.flush() }
|
||||
break
|
||||
case 0x42: // down
|
||||
int ni = selectedLineIdx
|
||||
ni = (ni + 1) % linesBuffer.length
|
||||
if (linesBuffer[ni] != null) {
|
||||
selectedLineIdx = ni
|
||||
replaceCurrentLine(linesBuffer[ni]) }
|
||||
else {
|
||||
out.print(new ANSI().cursorBackwards(4) +
|
||||
clearRemainingLine)
|
||||
out.flush() }
|
||||
break
|
||||
case 0x43: case 0x44: // right & left
|
||||
out.print(new ANSI().cursorBackwards(4) +
|
||||
clearRemainingLine)
|
||||
out.flush()
|
||||
break
|
||||
default: break
|
||||
}
|
||||
|
||||
idx += 2
|
||||
}
|
||||
|
||||
else currentLine.append((char)input[idx])
|
||||
break
|
||||
default:
|
||||
currentLine.append((char)input[idx])
|
||||
break
|
||||
}
|
||||
idx++
|
||||
}
|
||||
}
|
||||
|
||||
if (linesBuffer[nextAvailableLineIdx] != null) {
|
||||
String line = linesBuffer[nextAvailableLineIdx]
|
||||
nextAvailableLineIdx =
|
||||
(nextAvailableLineIdx + 1) % linesBuffer.length
|
||||
return line }
|
||||
else return null
|
||||
}
|
||||
return status.text}
|
||||
}
|
||||
|
@ -0,0 +1,31 @@
|
||||
package com.jdbernard.wdiwtlt.cli
|
||||
|
||||
public class ScrollText {
|
||||
|
||||
public String text = ""
|
||||
public int maxWidth
|
||||
|
||||
private int scrollIdx
|
||||
|
||||
public void setMaxWidth(int max) {
|
||||
this.maxWidth = Math.max(max, 1) }
|
||||
|
||||
public void setText(String t) {
|
||||
this.text = t
|
||||
this.scrollIdx = Math.max(0, text.size() - 10) }
|
||||
|
||||
public String getNextScroll() {
|
||||
if (text.size() < maxWidth) return text
|
||||
else {
|
||||
scrollIdx = (scrollIdx + 1) % text.size()
|
||||
int endIdx = Math.min(scrollIdx + maxWidth, text.size())
|
||||
int wrapIdx = (scrollIdx + maxWidth) % text.size()
|
||||
|
||||
// don't need to wrap past the end
|
||||
if (wrapIdx == endIdx) return text[scrollIdx..<endIdx]
|
||||
return new StringBuilder()
|
||||
.append(text[scrollIdx..<endIdx])
|
||||
.append(" ").append(text[0..<wrapIdx]) } }
|
||||
|
||||
public String toString() { return getNextScroll() }
|
||||
}
|
19
cli/src/main/resources/logback.groovy
Normal file
19
cli/src/main/resources/logback.groovy
Normal file
@ -0,0 +1,19 @@
|
||||
import ch.qos.logback.core.*;
|
||||
import ch.qos.logback.core.encoder.*;
|
||||
import ch.qos.logback.core.read.*;
|
||||
import ch.qos.logback.core.rolling.*;
|
||||
import ch.qos.logback.core.status.*;
|
||||
import ch.qos.logback.classic.net.*;
|
||||
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
|
||||
|
||||
|
||||
appender("FILE", FileAppender) {
|
||||
file = "wdiwtlt.log"
|
||||
append = true
|
||||
encoder(PatternLayoutEncoder) {
|
||||
pattern = "%level %logger - %msg%n"
|
||||
}
|
||||
}
|
||||
|
||||
root(OFF)
|
||||
logger("com.jdbernard.wdiwtlt", DEBUG, ["FILE"])
|
@ -17,7 +17,7 @@ public class MediaLibrary {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(MediaLibrary)
|
||||
|
||||
private ORM orm
|
||||
@Delegate ORM orm
|
||||
private File libraryRoot
|
||||
|
||||
public MediaLibrary(ORM orm, File rootDir) {
|
||||
@ -66,7 +66,7 @@ public class MediaLibrary {
|
||||
mf.name = fileTag?.getFirst(TITLE)?.trim() ?: f.name
|
||||
mf.filePath = relPath
|
||||
mf.comment = fileTag?.getFirst(COMMENT)?.trim()
|
||||
mf.trackNumber = (fileTag?.getFirst(TRACK) ?: null) as Integer
|
||||
mf.trackNumber = safeToInteger(fileTag?.getFirst(TRACK))
|
||||
|
||||
def folderParts = mf.filePath.split("[\\\\/]")[1..<-1] as LinkedList
|
||||
|
||||
@ -91,6 +91,20 @@ public class MediaLibrary {
|
||||
}
|
||||
}
|
||||
|
||||
public def getByIdOrName(String input, Class modelClass) {
|
||||
def match
|
||||
if (safeToInteger(input)) match = [orm.getById(safeToInteger(input), modelClass)]
|
||||
else {
|
||||
match = orm.getByName(input, modelClass)
|
||||
if (!match) match = orm.getLike(["name"], [input], modelClass) }
|
||||
return match }
|
||||
|
||||
public List<Artist> getArtistsByName(String name) {
|
||||
return orm.getArtistByName(name) ?: orm.getArtistsLikeName(name) }
|
||||
|
||||
public List<Album> getAlbumsByName(String name) {
|
||||
return orm.getAlbumByName(name) ?: orm.getAlbumsLikeName(name) }
|
||||
|
||||
private void associateWithArtistAndAlbum(MediaFile mf, String artistName,
|
||||
String albumName, JATag fileTag) {
|
||||
Artist artist = null
|
||||
@ -108,9 +122,13 @@ public class MediaLibrary {
|
||||
album = orm.getAlbumByName(albumName)
|
||||
if (!album) {
|
||||
newAlbumOrArtist = true
|
||||
album = new Album(name: albumName,
|
||||
year: (fileTag?.getFirst(YEAR) ?: null) as Integer,
|
||||
trackTotal: (fileTag?.getFirst(TRACK_TOTAL) ?: null) as Integer)
|
||||
try {
|
||||
album = new Album(name: albumName,
|
||||
year: safeToInteger(fileTag?.getFirst(YEAR)),
|
||||
trackTotal: safeToInteger(fileTag?.getFirst(TRACK_TOTAL))) }
|
||||
catch (UnsupportedOperationException use) {
|
||||
album = new Album(name: albumName,
|
||||
year: safeToInteger(fileTag?.getFirst(YEAR))) }
|
||||
orm.create(album) } }
|
||||
|
||||
if (artist && album && newAlbumOrArtist)
|
||||
@ -144,4 +162,8 @@ public class MediaLibrary {
|
||||
return (['.'] + childPath[b..<childPath.length]).join('/') }
|
||||
|
||||
|
||||
public static Integer safeToInteger(def val) {
|
||||
if (val == null) return null
|
||||
try { return val as Integer }
|
||||
catch (NumberFormatException nfe) { return null } }
|
||||
}
|
||||
|
@ -32,6 +32,12 @@ public class ORM {
|
||||
public void shutdown() { dataSource.shutdown() }
|
||||
|
||||
/// ### Common
|
||||
public def getAll(Class modelClass) {
|
||||
def query = "SELECT * FROM " +
|
||||
pluralize(nameFromModel(modelClass.simpleName))
|
||||
logger.debug("Selecting models.\n\tSQL: {}", query)
|
||||
return sql.rows(query).collect { recordToModel(it, modelClass) } }
|
||||
|
||||
public def getById(int id, Class modelClass) {
|
||||
def query = new StringBuilder()
|
||||
.append("SELECT * FROM ")
|
||||
@ -50,7 +56,7 @@ public class ORM {
|
||||
.toString()
|
||||
|
||||
logger.debug("Selecting model.\n\tSQL: {}\n\tPARAMS: {}", query, name)
|
||||
return recordToModel(sql.firstRow(query, [name]), modelClass) }
|
||||
return sql.rows(query, [name]).collect { recordToModel(it, modelClass) } }
|
||||
|
||||
public def getBy(List<String> columns, List<Object> values,
|
||||
Class modelClass) {
|
||||
@ -65,6 +71,20 @@ public class ORM {
|
||||
return sql.rows(query, values)
|
||||
.collect { recordToModel(it, modelClass) } }
|
||||
|
||||
public def getLike(List<String> columns, List<Object> values,
|
||||
Class modelClass) {
|
||||
values = values.collect { "%$it%".toString() }
|
||||
String query = new StringBuilder()
|
||||
.append("SELECT * FROM ")
|
||||
.append(pluralize(nameFromModel(modelClass.simpleName)))
|
||||
.append(" WHERE ")
|
||||
.append(columns.collect { """"$it" LIKE ?"""}.join(' AND '))
|
||||
.toString()
|
||||
|
||||
logger.debug("Selecting models.\n\tSQL: {}\n\tPARAMS: {}", query, values)
|
||||
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) }
|
||||
@ -130,13 +150,13 @@ public class ORM {
|
||||
public def associate(Class modelClass1, Class modelClass2 , Integer firstId, Integer secondId) {
|
||||
String linkTable = pluralize(nameFromModel(modelClass1.simpleName)) +
|
||||
"_" + pluralize(nameFromModel(modelClass2.simpleName))
|
||||
String col1 = nameFromModel(modelClass1) + "_id"
|
||||
String col2 = nameFromModel(modelClass2) + "_id"
|
||||
String col1 = nameFromModel(modelClass1.simpleName) + "_id"
|
||||
String col2 = nameFromModel(modelClass2.simpleName) + "_id"
|
||||
|
||||
withTransaction {
|
||||
def query = """\
|
||||
SELECT * FROM $linkTable
|
||||
WHERE "${col1}"_id = ? AND "${col2}" = ?"""
|
||||
WHERE "${col1}" = ? AND "${col2}" = ?"""
|
||||
def params = [firstId, secondId]
|
||||
|
||||
// Look first for an existing association before creating one.
|
||||
@ -154,7 +174,10 @@ public class ORM {
|
||||
|
||||
/// ### Album-specific methods
|
||||
public Album getAlbumById(int id) { return getById(id, Album) }
|
||||
public Album getAlbumByName(String name) { return getByName(name, Album) }
|
||||
|
||||
public List<Album> getAlbumByName(String name) {
|
||||
return getByName(name, Album) }
|
||||
|
||||
public Album getAlbumByNameAndArtistId(String name, int artistId) {
|
||||
def query = """\
|
||||
SELECT al.*
|
||||
@ -170,7 +193,7 @@ public class ORM {
|
||||
.collect { recordToModel(it, Album) }
|
||||
return albums ? albums[0] : null }
|
||||
|
||||
public Album getAlbumsByArtistId(int artistId) {
|
||||
public List<Album> getAlbumsByArtistId(int artistId) {
|
||||
def query = """\
|
||||
SELECT al.*
|
||||
FROM albums al JOIN
|
||||
@ -182,14 +205,21 @@ public class ORM {
|
||||
return sql.rows(query, [artistId])
|
||||
.collect { recordToModel(it, Album) } }
|
||||
|
||||
public List<Album> getAlbums() { return getAll(Album) }
|
||||
public List<Album> getAlbumsLikeName(String name) {
|
||||
return getLike(["name"], [name], 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) {
|
||||
public List<Artist> getArtistByName(String name) {
|
||||
return getByName(name, Artist) }
|
||||
|
||||
public List<Artist> getArtists() { return getAll(Artist) }
|
||||
public List<Artist> getArtistsByAlbumId(int albumId) {
|
||||
var query = """\
|
||||
SELECT ar.*
|
||||
FROM artists ar JOIN
|
||||
@ -200,6 +230,9 @@ public class ORM {
|
||||
return sql.rows(query, [albumId])
|
||||
.collect { recordToModel(it, Artist) } }
|
||||
|
||||
public List<Artist> getArtistsLikeName(String name) {
|
||||
return getLike(["name"], [name], Artist) }
|
||||
|
||||
public List<Artist> removeEmptyArtists() {
|
||||
throw new UnsupportedOperationException("Not yet implemented.");
|
||||
}
|
||||
@ -209,14 +242,25 @@ public class ORM {
|
||||
|
||||
/// ### Bookmark-specific methods
|
||||
public Bookmark getBookmarkById(int id) { return getById(id, Bookmark) }
|
||||
public Bookmark getBookmarkByName(String name) { return getByName(name, Bookmark) }
|
||||
|
||||
public List<Bookmark> getBookmarkByName(String name) {
|
||||
return getByName(name, Bookmark) }
|
||||
|
||||
public List<Bookmark> getBookmarks() { return getAll(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 List<MediaFile> getMediaFileByName(String name) {
|
||||
return getByName(name, MediaFile) }
|
||||
|
||||
public List<MediaFile> getMediaFiles() { return getAll(MediaFile) }
|
||||
|
||||
public List<MediaFile> getMediaFilesLikeName(String name) {
|
||||
return getLike(["name"], [name], MediaFile) }
|
||||
|
||||
public MediaFile getMediaFileByFilePath(String filePath) {
|
||||
def files = getBy(["file_path"], [filePath], MediaFile)
|
||||
@ -245,7 +289,11 @@ public class ORM {
|
||||
|
||||
/// ### Playlist-specific methods
|
||||
public Playlist getPlaylistById(int id) { return getById(id, Playlist) }
|
||||
public Playlist getPlaylistByName(String name) { return getByName(name, Playlist) }
|
||||
|
||||
public List<Playlist> getPlaylistByName(String name) {
|
||||
return getByName(name, Playlist) }
|
||||
|
||||
public List<Playlist> getPlaylists() { return getAll(Playlist) }
|
||||
|
||||
public List<Playlist> removeEmptyPlaylists() {
|
||||
throw new UnsupportedOperationException("Not yet implemented.");
|
||||
@ -253,10 +301,13 @@ public class ORM {
|
||||
|
||||
/// ### Tag-specific methods
|
||||
public Tag getTagById(int id) { return getById(id, Tag) }
|
||||
public Tag getTagByName(String name) { return getByName(name, Tag) }
|
||||
public Tag getTagByName(String name) { return getByName(name, Tag)[0] }
|
||||
|
||||
/// ### Utility functions
|
||||
public void withTransaction(Closure c) { sql.withTransaction(c) }
|
||||
public void withTransaction(Closure c) {
|
||||
try { sql.execute("BEGIN TRANSACTION"); c() }
|
||||
finally { sql.execute("COMMIT") }
|
||||
}
|
||||
public static String nameToModel(String name) {
|
||||
def pts = name.toLowerCase().split('_')
|
||||
return pts.length == 1 ? pts[0] :
|
||||
|
Loading…
Reference in New Issue
Block a user