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 0967387..47df38f 100644 --- a/cli/src/main/groovy/com/jdbernard/wdiwtlt/cli/CommandLineInterface.groovy +++ b/cli/src/main/groovy/com/jdbernard/wdiwtlt/cli/CommandLineInterface.groovy @@ -83,6 +83,10 @@ Configuration: new ANSI().eraseLine(Erase.All).cursorPrevLine().eraseLine(Erase.All) .cursorPrevLine().eraseLine(Erase.All).toString() + public final static modelClass = [ + 'album': Album, 'artist': Artist, 'bookmark': Bookmark, + 'mediaFile': MediaFile, 'playlist': Playlist, 'tag': Tag ] + private int displayWidth = 79 private long msgTimeout private ScrollText currentlyPlaying = new ScrollText( @@ -93,7 +97,7 @@ Configuration: private SimpleDateFormat sdf = new SimpleDateFormat('EEE-HH-SSS') /// Current play queue and selection data - def selection = [:] + Selection currentSelection = new Selection() Playlist playQueue = library.save(new Playlist( name: "CLI Queue ${sdf.format(new Date())}")) Bookmark playBookmark @@ -246,10 +250,10 @@ Configuration: logger.debug("command: $command") switch(command?.toLowerCase()) { case 'scan': return scanMediaLibrary() - case 'select': return processSelect(line) - case 'list': return processList(line) + case 'select': return processSelect(line, currentSelection) + case 'list': return processList(line, currentSelection) case 'add': return processAdd(line) - case 'enque': return processEnque(line) + case 'enqueue': return processEnqueue(line) case 'tag': return processTag(line) case 'clear': return processClear(line) case 'play': return line.size() == 0 ? @@ -296,10 +300,12 @@ Configuration: dismissMsgDate = new Date(new Date().time + msgTimeout) return library } - private def processSelect(LinkedList line) { + private def processSelect(LinkedList line, def sel = null) { String option = line.poll() boolean current = option == "current" + if (!sel) sel = new Selection() def items + if (current) { if (!curMediaFile) { setErr "No media is currently playing." @@ -308,18 +314,21 @@ Configuration: option = line.poll() switch (option) { case 'album': - return select(Album, getExactlyOne(Album, - library.getAlbumsWhere({ mediaFileId: curMediaFile.id}))) + sel.album = ensureExactlyOne( + library.getAlbumsWhere({ mediaFileId: curMediaFile.id})) + return sel case 'artist': - return selectOneMatch(library.getArtistsWhere({ - mediaFileId: curMediaFile.id})) + sel.artist = ensureExactlyOne( + library.getArtistsWhere({ mediaFileId: curMediaFile.id})) + return sel case 'playlist': - return selectOneMatch(playQueue) + sel.playlist = playQueue; return sel case 'file': - return selectOneMatch(curMediaFile) + sel.mediaFile = curMediaFile; return sel case 'tags': - return select(tags: library.getTagsWhere({ - mediaFileId: curMediaFile.id})) + sel.tags = library.getTagsWhere({ + mediaFileId: curMediaFile.id}) + return sel default: setErr("Unrecognized option to ${promptStyle}select " + "current${errorStyle}.") @@ -328,16 +337,15 @@ Configuration: } switch (option) { - case 'album': return selectOneMatch( - library.getByIdOrName(Album, line.join(' '))) - case 'artist': return selectOneMatch( - library.getByIdOrName(Artist, line.join(' '))) - case 'playlist': return selectOneMatch( - library.getByIdOrName(Playlist, line.join(' '))) - case 'file': return selectOneMatch( - library.getByIdOrName(MediaFile, line.join(' '))) - case 'tags': return select(tags: - line.map { library.getByIdOrName(Tag, it) }.filter().flatten()) + case 'file': option = 'mediaFile' + case 'album': case 'artist': case 'playlist': + sel[option] = ensureExactlyOne( + library.getByIdOrName(modelClass[option], line.join(' '))) + return sel + case 'tags': + sel.tags = line.collect { library.getByIdOrName(Tag, it) } + .findAll().flatten() + return sel default: setErr("Unrecognized option to ${promptStyle}select${errorStyle}") @@ -390,7 +398,7 @@ Configuration: switch(option) { case 'queue': return library.removeAllFromPlaylist(playQueue.id) case 'selected playlist': - if (!selection.playlist) { + if (!currentSelection.playlist) { printLongMessage("No playlist currently selected.") return null } return library.removeAllFromPlaylist(selected.playlist.id) @@ -407,16 +415,19 @@ Configuration: return null } return library.removeAllFromPlaylist(playlist.id) case 'selection': + currentSelection = new Selection() + break default: printLongMessage("Unrecognized option to the ${promptStyle}" + - "add${normalStyle} command. Use ${promptStyle}help clear" + + "clear${normalStyle} command. Use ${promptStyle}help clear" + "${normalStyle} to see a list of valid options.") return null } } - private def processList(LinkedList options) { + private def processList(LinkedList options, def selection) { logger.debug("Listing things. Options: $options") + if (!selection) selection = new Selection() def option = options.poll() boolean all = option == 'all' if (all) option = options.poll() @@ -424,6 +435,7 @@ Configuration: logger.debug("Option: $option") def list + switch(option) { case 'albums': if (all) list = library.getAlbums() @@ -435,7 +447,7 @@ Configuration: String albumMatch = options?.join(" ")?.trim() if (albumMatch) list = list.findAll { it.name =~ albumMatch } - printLongMessage(makeList(Album, list, all, + printLongMessage(makeList(selection, Album, list, all, { "${it.id}: ${it}" })) break @@ -449,19 +461,20 @@ Configuration: String artistMatch = options?.join(" ")?.trim() if (artistMatch) list = list.findAll { it.name =~ artistMatch } - printLongMessage(makeList(Artist, list, all, + printLongMessage(makeList(selection, Artist, list, all, { "${it.id}: ${it.name}" })) break case 'files': case 'selection': if (all) list = library.getMediaFiles() - else list = getSelectedMediaFiles() + else list = selection.selectedFiles + if (selection.album) list = list.sort { it.trackNumber } String mediaFileMatch = options?.join(" ")?.trim() if (mediaFileMatch) list = list.findAll { it.name =~ mediaFileMatch } - printLongMessage(makeList(MediaFile, list, all + printLongMessage(makeList(selection, MediaFile, list, all, { "${it.id}: ${it.trackNumber} - ${it.name}" })) break @@ -473,7 +486,7 @@ Configuration: String bookmarkMatch = options?.join(" ")?.trim() if (boolmarkMatch) list = list.findAll { it.name =~ bookmarkMatch } - printLongMessage(makeList(Bookmark, list, all, + printLongMessage(makeList(selection, Bookmark, list, all, { "${it.id}: ${it.name} ${it.userCreated ? '' : ' (auto)'}" })) break @@ -487,7 +500,7 @@ Configuration: String playlistMatch = options?.join(" ")?.trim() if (playlistMatch) list = list.findAll { it.name =~ playlistMatch } - printLongMessage(makeList(Playlist, list, all, + printLongMessage(makeList(selection, Playlist, list, all, { "${it.id}: ${it.name} ${it.userCreated ? '' : ' (auto)'}" })) break @@ -500,7 +513,7 @@ Configuration: String tagMatch = options?.join(" ")?.trim() if (tagMatch) list = list.findAll { it.name =~ tagMatch } - printLongMessage(makeList(Tag, list, all, + printLongMessage(makeList(selection, Tag, list, all, { "${it.id}: ${it.name}" })) break @@ -511,19 +524,16 @@ Configuration: return null } + resetStatus() return list } - public def unselect(Class modelClass) { - String key = uncapitalize(modelClass.simpleName) - this.selection[key] = null } - public def ensureExactlyOne(def matches) { if (!matches) { setErr("Nothing matches."); return null } - String englishName = toEnglish(modelClass.simpleName) + String englishName = toEnglish(matches[0].class.simpleName) if (matches.size() > 1) { setErr("Multiple ${englishName}s match: " + matches.collect { "${it.id}: ${it.name}" }.join(', ')) @@ -534,11 +544,6 @@ Configuration: public def getExactlyOne(Class modelClass, def criteria) { return ensureExactlyOne(library.getByIdOrName(modelClass, criteria)) } - public def selectOneMatch(def matches) { - def match = ensureExactlyOne(matches) - if (match) selection[uncapitalize(match.class.simpleName)] = match - return match } - private void drawLeader(afterOutput = false) { String leader = beforeLeader + getLeader() + @@ -581,16 +586,16 @@ Configuration: status.text = errorStyle + errMsg dismissMsgDate = new Date(new Date().time + msgTimeout) } - private String makeList(Class modelClass, def items, - boolean listAll = false, Closure toString = null) { + private String makeList(Selection selection, Class modelClass, + def items, boolean listAll = false, Closure toString = null) { def result = new StringBuilder() - .append("--------------------\n${modelClass.simpleName}") + .append("--------------------\n${modelClass.simpleName}s") if (!listAll && (selection.playlist || selection.artist || selection.mediaFile)) result.append("\n(for selection: ") - .append(describeSelection()) + .append(selection.toString()) .append(normalStyle) .append(")") @@ -600,49 +605,8 @@ Configuration: return result.toString() } - private String describeSelection() { - StringBuilder s = new StringBuilder() - - if (selection.playlist) s.append(playlistStyle) - .append(selection.playlist) - .append(normalStyle) - .append(": ") - - if (selection.artist) s.append(artistStyle) - .append(selection.artist) - .append(normalStyle) - .append(" / ") - - if (selection.album) s.append(albumStyle) - .append(selection.album) - .append(normalStyle) - .append(" / ") - - if (selection.mediaFile) s.append(fileStyle) - .append(selection.mediaFile) - .append(normalStyle) - - return s.toString() - } - - private getSelectedMediaFiles() { - if (selection.mediaFile) return selection.mediaFile - - return list = library.getMediaFilesWhere( - playlistId: selection?.playlist?.id, - artistId: selection?.artist?.id, - albumId: selection?.album?.id, - tags: selection?.tags) } - - private String select(Map s) { - ['artist', 'album', 'playlist', 'file', 'tags'].each { - this.selection[it] = s[it] } - resetStatus() - return selection - } - private String resetStatus() { - String s = describeSelection() + String s = currentSelection.toString() if (s.size() == 0) status.text = "No current media selections." else status.text = s @@ -657,4 +621,62 @@ Configuration: private static String toEnglish(String modelName) { return UPPERCASE_PATTERN.matcher(modelName). replaceAll(/$1 $2/).toLowerCase() } + + public class Selection { + Album album + Artist artist + MediaFile mediaFile + Playlist playlist + List tags + + public def unselect(Class modelClass) { + String key = uncapitalize(modelClass.simpleName) + def value = this[key] + this[key] = null + return value } + + public Selection select(Map s) { + ['artist', 'album', 'playlist', 'file', 'tags'].each { + this[it] = s[it] } + return this + } + + public def select(def value) { + if (value) this[uncapitalize(match.class.simpleName)] = value + return value } + + public List getSelectedFiles() { + return library.getMediaFilesWhere( + playlistId: playlist?.id, + artistId: artist?.id, + albumId: album?.id, + tags: tags) } + + public String toString() { + StringBuilder s = new StringBuilder() + + if (playlist) s.append(playlistStyle) + .append(playlist) + .append(normalStyle) + .append(": ") + + if (artist) s.append(artistStyle) + .append(artist) + .append(normalStyle) + .append(" / ") + + if (album) s.append(albumStyle) + .append(album) + .append(normalStyle) + .append(" / ") + + if (mediaFile) s.append(fileStyle) + .append(mediaFile) + .append(normalStyle) + + return s.toString() + } + + + } }