diff --git a/cli/src/main/groovy/com/jdbernard/wdiwtlt/cli/CommandLineInterface.groovy b/cli/src/main/groovy/com/jdbernard/wdiwtlt/cli/CommandLineInterface.groovy index af42da7..c3d3daa 100644 --- a/cli/src/main/groovy/com/jdbernard/wdiwtlt/cli/CommandLineInterface.groovy +++ b/cli/src/main/groovy/com/jdbernard/wdiwtlt/cli/CommandLineInterface.groovy @@ -52,9 +52,13 @@ Configuration: private static Logger logger = LoggerFactory.getLogger(CommandLineInterface) + + private static UPPERCASE_PATTERN = Pattern.compile(/(.)(\p{javaUpperCase})/) + private Properties cliConfig private MediaLibrary library + /// IO Management private InputStream inStream private OutputStream outStream @@ -64,6 +68,7 @@ Configuration: Collections.synchronizedList(new ArrayList()) private synchronized boolean running + /// Console output data private String titleStyle, normalStyle, statusStyle, promptStyle, artistStyle, albumStyle, fileStyle, errorStyle, playlistStyle private String clearLine = new ANSI().eraseLine(Erase.All).toString() @@ -86,9 +91,12 @@ Configuration: private Date dismissMsgDate = new Date() private SimpleDateFormat sdf = new SimpleDateFormat('EEE-HH-SSS') + /// Current play queue and selection data def selection = [:] - def currentPlaylist = library.save(new Playlist( + Playlist playQueue = library.save(new Playlist( "CLI Queue ${sdf.format(new Date())}")) + Bookmark playBookmark + MediaFile curMediaFile public static void main(String[] args) { @@ -236,23 +244,24 @@ Configuration: String command = line.poll() logger.debug("command: $command") switch(command?.toLowerCase()) { - case 'album': return selectAlbum(line) - case 'artist': return selectArtist(line) - case 'playlist': return selectPlaylist(line) - case 'current': return selectCurrent(line) case 'scan': return scanMediaLibrary() - case 'new': return processNew(line) + case 'select': return processSelect(line) + case 'list': return processList(line) case 'add': return processAdd(line) - case 'tag': return tagMediaFiles(line) - case 'split': return processSplit(line) - - case 'list': - String nextArg = line.poll() - if (nextArg.toLowerCase() == 'all') - return processList(line, true) - else { - if (nextArg) line.addFirst(nextArg) - return processList(line, false) } + case 'enque': return processEnque(line) + case 'tag': return processTag(line) + case 'clear': return processClear(line) + case 'play': return line.size() == 0 ? + transport.play() : + processPlay(line) + case 'pause': return transport.pause() + case 'stop': return transport.stop() + case 'next': return processNext(line) + case 'prev': return processPrev(line) + case 'ff': + case 'fastforward': return processFastForward(line) + case 'rwd': + case 'rewind': return processRewind(line) case 'debug': outStream.println( @@ -268,10 +277,6 @@ Configuration: consoleReaderThread.interrupt() return - selection.album = null - resetStatus() - break - default: status.text = errorStyle + "Unrecognized command: '$line'${normalStyle}" @@ -280,7 +285,58 @@ Configuration: Thread.sleep(250) break } + } + public MediaLibrary scanMediaLibrary() { + status.text = "Scanning media library..." + library.rescanLibrary() + status.text = "Scanned ? files." + dismissMsgDate = new Date(new Date().time + msgTimeout) + return library } + + private def processSelect(LinkedList line) { + String option = line.poll() + boolean current = option == "current" + def items + if (current) { + if (!curMediaFile) { + setErr "No media is currently selected." + return null } + + option = line.poll() + switch (option) { + case 'album': + return select(Album, library.getAlbumsWhere({ + mediaFileId: curMediaFile.id})) + case 'artist': + return select(Artist, library.getArtistsWhere({ + mediaFileId: curMediaFile.id})) + case 'playlist': + return select(Playlist, playQueue) + case 'file': + return select(MediaFile, curMediaFile) + case 'tags': + return select(Tag, library.getTagsWhere({ + mediaFileId: curMediaFile.id}) + default: + setErr("Unrecognized option to ${promptStyle}select " + + "current${errStyle}.") + return null + } + } + + switch (option) { + case 'album': return select(Album, library.getByIdOrName(line.join(' '))) + case 'artist': return select(Artist, library.getByIdOrName(line.join(' '))) + case 'playlist': return select(Playlist, library.getByIdOrName(line.join(' '))) + case 'current': return select(Current, library.getByIdOrName(line.join(' '))) + case 'file': return select(MediaFile, library.getByIdOrName(line.join(' '))) + case 'tags': return select(Tag, library.getByIdOrName(line.join(' '))) + + default: + setErr("Unrecognized option to ${promptStyle}select${errStyle}") + return null + } } private def processNew(LinkedList line) { @@ -310,9 +366,12 @@ Configuration: } } - private def processList(LinkedList options, boolean all) { - logger.debug("Listing albums. Options: $options") + private def processList(LinkedList options) { + logger.debug("Listing things. Options: $options") def option = options.poll() + boolean all = option == 'all' + if (all) option = options.poll() + logger.debug("Option: $option") def list @@ -406,64 +465,23 @@ Configuration: return list } - public Album selectAlbum(LinkedList input) { - String criteria = input.join(" ") + public def unselect(Class modelClass) { + String key = uncapitalize(modelClass.simpleName) + this.selection[key] = null } - if (!criteria) { selection.album = null; resetStatus(); return null } - - def match = library.getByIdOrName(Album, criteria) - - if (!match) { setErr("No album matches '$input'."); return null } + public def select(Class modelClass, def matches) { + String englishName = ${toEnglish(modelClass.simpleName} + if (!matches) { + setErr("No $englishName matches."); + return null } else if (match.size() > 1) { - setErr("Multiple albums match '$input': " + - match.collect { "${it.id}: ${it.name}" }.join(", ")) + setErr("Multiple ${englishName}s match: " + + matches.collect { "${it.id}: ${it.name}" }.join(', ')) return null } - selection.album = match[0] - resetStatus() - return match[0] } - - public Artist selectArtist(LinkedList input) { - String criteria = input.join(" ") - - if (!criteria) { selection.artist = null; resetStatus(); return null } - - def match = library.getByIdOrName(Artist, criteria) - - if (!match) { setErr("No artist matches '$input'."); return null } - else if (match.size() > 1) { - setErr("Multiple artists match '$input': " + - match.collect { "${it.id}: ${it.name}" }.join(", ")) - return null } - - selection.artist = match[0] - resetStatus() - return match[0] } - - public Playlist selectPlaylist(LinkedList input) { - String criteria = input.join(" ") - - if (!criteria) { selection.playlist = null; resetStatus(); return null } - // currentPlaylist = library.save( - // new Playlist(name: "CLI Queue ${sdf.format(new Date())}")) } - - def match = library.getByIdOrName(Playlist, criteria) - - if (!match) { setErr("No playlist matches: '$input'."); return null } - else if (match.size() > 1) { - setErr("Multiple playlists match '$input': " + - match.collect { "${it.id}: ${it.name}" }.join(", ")) - return null } - selection.playlist = match[0] - resetStatus() - return match[0] } - - public MediaLibrary scanMediaLibrary() { - status.text = "Scanning media library..." - library.rescanLibrary() - status.text = "Scanned ? files." - dismissMsgDate = new Date(new Date().time + msgTimeout) - return library } + selection[uncapitalize(modelClass.simpleName)] = matches[0] + resetStatus + return matches[0] } private void drawLeader(afterOutput = false) { @@ -559,11 +577,26 @@ Configuration: artistId: selection?.artist?.id, albumId: selection?.album?.id) } + private String setSelection(Map s) { + ['artist', 'album', 'playlist', 'file', 'tags'].each { + this.selection[it] = s[it] } + resetStatus() + } + private String resetStatus() { String s = describeSelection() if (s.size() == 0) status.text = "No current media selections." else status.text = s - return status.text} + return status.text } + + private static String uncapitalize(String s) { + if (s == null) return null; + if (s.lengh() < 2) return s.toLowerCase(); + return s[0].toLowerCase() + s[1..-1] } + + private static String toEnglish(String modelName) { + return UPPERCASE_PATTERN.matcher(name). + replaceAll(/$1 $2/).toLowerCase() } } diff --git a/core/src/main/groovy/com/jdbernard/wdiwtlt/MediaLibrary.groovy b/core/src/main/groovy/com/jdbernard/wdiwtlt/MediaLibrary.groovy index 8744b26..044bbbb 100644 --- a/core/src/main/groovy/com/jdbernard/wdiwtlt/MediaLibrary.groovy +++ b/core/src/main/groovy/com/jdbernard/wdiwtlt/MediaLibrary.groovy @@ -210,6 +210,6 @@ public class MediaLibrary { public static Integer safeToInteger(def val) { if (val == null) return null - try { return val as Integer } + try { return val.trim() as Integer } catch (NumberFormatException nfe) { return null } } }