gomu/queue.go

371 lines
6.4 KiB
Go
Raw Normal View History

2020-06-22 00:05:56 +08:00
// Copyright (C) 2020 Raziman
2020-06-19 16:22:20 +08:00
package main
import (
2020-07-10 16:52:37 +08:00
"bufio"
"crypto/sha1"
"encoding/hex"
2020-06-26 12:54:48 +08:00
"errors"
2020-07-06 21:04:18 +08:00
"fmt"
2020-07-10 16:52:37 +08:00
"io/ioutil"
2020-07-18 00:08:47 +08:00
"log"
2020-07-14 01:16:30 +08:00
"math/rand"
2020-07-10 16:52:37 +08:00
"os"
"path"
2020-07-10 22:06:13 +08:00
"time"
2020-06-26 12:54:48 +08:00
2020-06-19 16:22:20 +08:00
"github.com/gdamore/tcell"
"github.com/rivo/tview"
)
2020-06-26 12:54:48 +08:00
type Queue struct {
*tview.List
2020-07-10 16:52:37 +08:00
SavedQueuePath string
Items []*AudioFile
2020-07-10 23:30:28 +08:00
IsLoop bool
2020-06-26 12:54:48 +08:00
}
2020-06-19 16:22:20 +08:00
// highlight the next item in the queue
2020-06-26 12:54:48 +08:00
func (q *Queue) next() {
currIndex := q.GetCurrentItem()
idx := currIndex + 1
if currIndex == q.GetItemCount()-1 {
idx = 0
}
q.SetCurrentItem(idx)
}
2020-06-19 16:22:20 +08:00
// highlight the previous item in the queue
2020-06-26 12:54:48 +08:00
func (q *Queue) prev() {
currIndex := q.GetCurrentItem()
q.SetCurrentItem(currIndex - 1)
}
2020-06-19 16:22:20 +08:00
// usually used with GetCurrentItem which can return -1 if
// no item highlighted
2020-07-20 21:48:13 +08:00
func (q *Queue) DeleteItem(index int) (*AudioFile, error) {
if index > len(q.Items)-1 {
return nil, errors.New("Index out of range")
}
2020-07-16 22:52:12 +08:00
// deleted audio file
var dAudio *AudioFile
2020-06-26 12:54:48 +08:00
if index != -1 {
q.RemoveItem(index)
2020-07-10 22:06:13 +08:00
var nItems []*AudioFile
for i, v := range q.Items {
2020-07-16 22:52:12 +08:00
2020-07-10 22:06:13 +08:00
if i == index {
2020-07-16 22:52:12 +08:00
dAudio = v
2020-07-10 22:06:13 +08:00
continue
}
nItems = append(nItems, v)
}
q.Items = nItems
q.UpdateTitle()
}
2020-07-16 22:52:12 +08:00
2020-07-20 21:48:13 +08:00
return dAudio, nil
2020-07-10 22:06:13 +08:00
}
2020-07-16 22:52:12 +08:00
// Update queue title which shows number of items and total length
2020-07-10 22:06:13 +08:00
func (q *Queue) UpdateTitle() {
var totalLength time.Duration
for _, v := range q.Items {
totalLength += v.Length
2020-06-19 16:22:20 +08:00
}
2020-07-10 22:06:13 +08:00
fmtTime := fmtDuration(totalLength)
2020-07-12 12:21:49 +08:00
q.SetTitle(fmt.Sprintf("┤ Queue ├──┤%d|%s├", len(q.Items), fmtTime))
2020-07-10 22:06:13 +08:00
2020-06-26 12:54:48 +08:00
}
2020-07-16 22:52:12 +08:00
// Add item to the front of the queue
func (q *Queue) PushFront(audioFile *AudioFile) {
q.Items = append([]*AudioFile{audioFile}, q.Items...)
songLength := audioFile.Length
2020-07-20 21:48:13 +08:00
queueItemView := fmt.Sprintf(
"[ %s ] %s", fmtDuration(songLength), GetName(audioFile.Name),
)
2020-07-16 22:52:12 +08:00
q.InsertItem(0, queueItemView, audioFile.Path, 0, nil)
q.UpdateTitle()
}
2020-06-26 12:54:48 +08:00
// gets the first item and remove it from the queue
// app.Draw() must be called after calling this function
2020-07-10 23:30:28 +08:00
func (q *Queue) Dequeue() (*AudioFile, error) {
2020-06-19 16:22:20 +08:00
2020-06-26 12:54:48 +08:00
if q.GetItemCount() == 0 {
2020-07-20 21:48:13 +08:00
return nil, errors.New("Empty list\n")
2020-06-19 16:22:20 +08:00
}
2020-07-10 23:30:28 +08:00
first := q.Items[0]
2020-07-10 16:52:37 +08:00
q.DeleteItem(0)
2020-07-10 22:06:13 +08:00
q.UpdateTitle()
2020-06-26 12:54:48 +08:00
return first, nil
}
2020-07-06 21:04:18 +08:00
// Add item to the list and returns the length of the queue
2020-07-20 21:48:13 +08:00
func (q *Queue) Enqueue(audioFile *AudioFile) (int, error) {
2020-07-06 21:04:18 +08:00
if !gomu.Player.IsRunning && "false" == os.Getenv("TEST") {
2020-07-06 21:04:18 +08:00
gomu.Player.IsRunning = true
go func() {
gomu.Player.Run(audioFile)
2020-07-06 21:04:18 +08:00
}()
2020-07-20 21:48:13 +08:00
return q.GetItemCount(), nil
2020-07-06 21:04:18 +08:00
}
q.Items = append(q.Items, audioFile)
2020-07-06 21:04:18 +08:00
songLength, err := GetLength(audioFile.Path)
if err != nil {
2020-07-20 21:48:13 +08:00
return 0, WrapError("Enqueue", err)
2020-07-06 21:04:18 +08:00
}
2020-07-20 21:48:13 +08:00
queueItemView := fmt.Sprintf(
"[ %s ] %s", fmtDuration(songLength), GetName(audioFile.Name),
)
2020-07-06 21:04:18 +08:00
q.AddItem(queueItemView, audioFile.Path, 0, nil)
2020-07-10 22:06:13 +08:00
q.UpdateTitle()
2020-07-06 21:04:18 +08:00
2020-07-20 21:48:13 +08:00
return q.GetItemCount(), nil
2020-07-06 21:04:18 +08:00
}
// GetItems is used to get the secondary text
// which is used to store the path of the audio file
// this is for the sake of convenience
2020-06-26 12:54:48 +08:00
func (q *Queue) GetItems() []string {
items := []string{}
for i := 0; i < q.GetItemCount(); i++ {
_, second := q.GetItemText(i)
items = append(items, second)
}
return items
}
2020-07-10 16:52:37 +08:00
// Save the current queue in a csv file
func (q *Queue) SaveQueue() error {
songPaths := q.GetItems()
var content string
for _, songPath := range songPaths {
hashed := Sha1Hex(GetName(songPath))
2020-07-20 21:48:13 +08:00
content += hashed + "\n"
2020-07-10 16:52:37 +08:00
}
cachePath := expandTilde(q.SavedQueuePath)
err := ioutil.WriteFile(cachePath, []byte(content), 0644)
if err != nil {
2020-07-20 21:48:13 +08:00
return WrapError("SaveQueue", err)
2020-07-10 16:52:37 +08:00
}
return nil
}
2020-07-10 22:06:13 +08:00
// Clears current queue
func (q *Queue) ClearQueue() {
q.Items = []*AudioFile{}
q.Clear()
q.UpdateTitle()
}
2020-07-10 16:52:37 +08:00
// Loads previously saved list
func (q *Queue) LoadQueue() error {
songs, err := q.GetSavedQueue()
if err != nil {
2020-07-20 21:48:13 +08:00
return WrapError("LoadQueue", err)
2020-07-10 16:52:37 +08:00
}
for _, v := range songs {
audioFile := gomu.Playlist.FindAudioFile(v)
if audioFile != nil {
q.Enqueue(audioFile)
}
}
return nil
}
// Get saved queue, if not exist, create it
func (q *Queue) GetSavedQueue() ([]string, error) {
2020-07-20 21:48:13 +08:00
fnName := "GetSavedQueue"
2020-07-10 16:52:37 +08:00
queuePath := expandTilde(q.SavedQueuePath)
if _, err := os.Stat(queuePath); os.IsNotExist(err) {
dir, _ := path.Split(queuePath)
err := os.MkdirAll(dir, 0744)
if err != nil {
2020-07-20 21:48:13 +08:00
return nil, WrapError(fnName, err)
2020-07-10 16:52:37 +08:00
}
_, err = os.Create(queuePath)
if err != nil {
2020-07-20 21:48:13 +08:00
return nil, WrapError(fnName, err)
2020-07-10 16:52:37 +08:00
}
return []string{}, nil
}
f, err := os.Open(queuePath)
if err != nil {
2020-07-20 21:48:13 +08:00
return nil, WrapError(fnName, err)
2020-07-10 16:52:37 +08:00
}
defer f.Close()
records := []string{}
scanner := bufio.NewScanner(f)
for scanner.Scan() {
records = append(records, scanner.Text())
}
if err := scanner.Err(); err != nil {
2020-07-20 21:48:13 +08:00
return nil, WrapError(fnName, err)
2020-07-10 16:52:37 +08:00
}
return records, nil
}
2020-07-17 15:34:50 +08:00
func (q *Queue) Help() []string {
return []string{
"j down",
"k up",
"l play selected song",
"d remove from queue",
"D clear queue",
"z toggle loop",
"s shuffle",
}
}
2020-07-14 01:16:30 +08:00
func (q *Queue) Shuffle() {
rand.Seed(time.Now().UnixNano())
2020-07-20 21:48:13 +08:00
rand.Shuffle(len(q.Items), func(i, j int) {
q.Items[i], q.Items[j] = q.Items[j], q.Items[i]
})
2020-07-14 01:16:30 +08:00
q.Clear()
for _, v := range q.Items {
audioLen, err := GetLength(v.Path)
2020-07-18 00:08:47 +08:00
log.Println(err)
2020-07-14 01:16:30 +08:00
queueText := fmt.Sprintf("[ %s ] %s", fmtDuration(audioLen), v.Name)
q.AddItem(queueText, v.Path, 0, nil)
}
q.UpdateTitle()
}
2020-07-10 16:52:37 +08:00
// Initiliaze new queue with default values
func NewQueue() *Queue {
2020-06-26 12:54:48 +08:00
list := tview.NewList().
ShowSecondaryText(false)
2020-07-10 16:52:37 +08:00
queue := &Queue{
List: list,
SavedQueuePath: "~/.local/share/gomu/queue.cache",
}
2020-06-26 12:54:48 +08:00
queue.SetInputCapture(func(e *tcell.EventKey) *tcell.EventKey {
2020-06-19 16:22:20 +08:00
2020-06-19 16:42:30 +08:00
switch e.Rune() {
case 'j':
2020-06-26 12:54:48 +08:00
queue.next()
2020-06-19 16:42:30 +08:00
case 'k':
2020-06-26 12:54:48 +08:00
queue.prev()
2020-06-22 13:18:25 +08:00
case 'd':
2020-07-10 16:52:37 +08:00
queue.DeleteItem(queue.GetCurrentItem())
2020-07-10 22:06:13 +08:00
case 'D':
queue.ClearQueue()
2020-07-17 15:34:50 +08:00
case 'l':
2020-07-20 21:48:13 +08:00
a, err := queue.DeleteItem(queue.GetCurrentItem())
if err != nil {
log.Println(err)
}
2020-07-16 22:52:12 +08:00
queue.PushFront(a)
gomu.Player.Skip()
2020-07-12 12:21:49 +08:00
case 'z':
isLoop := gomu.Player.ToggleLoop()
var msg string
if isLoop {
msg = "on"
} else {
msg = "off"
}
timedPopup("Loop", msg, getPopupTimeout(), 30, 5)
2020-07-14 01:16:30 +08:00
case 's':
queue.Shuffle()
2020-06-19 16:42:30 +08:00
}
2020-06-19 16:22:20 +08:00
2020-06-19 16:42:30 +08:00
return nil
})
2020-06-19 16:22:20 +08:00
2020-07-10 22:06:13 +08:00
queue.UpdateTitle()
queue.SetBorder(true).SetTitleAlign(tview.AlignLeft)
queue.
SetSelectedBackgroundColor(tcell.ColorDarkCyan).
SetSelectedTextColor(tcell.ColorWhite).
SetHighlightFullLine(true)
2020-06-19 16:22:20 +08:00
2020-06-26 12:54:48 +08:00
return queue
2020-06-19 16:22:20 +08:00
}
2020-07-10 16:52:37 +08:00
func Sha1Hex(input string) string {
h := sha1.New()
h.Write([]byte(input))
return hex.EncodeToString(h.Sum(nil))
}