mirror of
https://github.com/issadarkthing/gomu.git
synced 2025-04-26 13:49:21 +08:00
348 lines
6.0 KiB
Go
348 lines
6.0 KiB
Go
// Copyright (C) 2020 Raziman
|
|
|
|
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"crypto/sha1"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"math/rand"
|
|
"os"
|
|
"path"
|
|
"time"
|
|
|
|
"github.com/gdamore/tcell"
|
|
"github.com/rivo/tview"
|
|
)
|
|
|
|
type Queue struct {
|
|
*tview.List
|
|
SavedQueuePath string
|
|
Items []*AudioFile
|
|
IsLoop bool
|
|
}
|
|
|
|
// highlight the next item in the queue
|
|
func (q *Queue) next() {
|
|
currIndex := q.GetCurrentItem()
|
|
idx := currIndex + 1
|
|
if currIndex == q.GetItemCount()-1 {
|
|
idx = 0
|
|
}
|
|
q.SetCurrentItem(idx)
|
|
}
|
|
|
|
// highlight the previous item in the queue
|
|
func (q *Queue) prev() {
|
|
currIndex := q.GetCurrentItem()
|
|
q.SetCurrentItem(currIndex - 1)
|
|
}
|
|
|
|
// usually used with GetCurrentItem which can return -1 if
|
|
// no item highlighted
|
|
func (q *Queue) DeleteItem(index int) *AudioFile {
|
|
|
|
// deleted audio file
|
|
var dAudio *AudioFile
|
|
|
|
if index != -1 {
|
|
q.RemoveItem(index)
|
|
|
|
var nItems []*AudioFile
|
|
|
|
for i, v := range q.Items {
|
|
|
|
if i == index {
|
|
dAudio = v
|
|
continue
|
|
}
|
|
|
|
nItems = append(nItems, v)
|
|
}
|
|
|
|
q.Items = nItems
|
|
q.UpdateTitle()
|
|
|
|
}
|
|
|
|
return dAudio
|
|
}
|
|
|
|
// Update queue title which shows number of items and total length
|
|
func (q *Queue) UpdateTitle() {
|
|
|
|
var totalLength time.Duration
|
|
|
|
for _, v := range q.Items {
|
|
totalLength += v.Length
|
|
}
|
|
|
|
fmtTime := fmtDuration(totalLength)
|
|
|
|
q.SetTitle(fmt.Sprintf("┤ Queue ├──┤%d|%s├", len(q.Items), fmtTime))
|
|
|
|
}
|
|
|
|
// Add item to the front of the queue
|
|
func (q *Queue) PushFront(audioFile *AudioFile) {
|
|
|
|
q.Items = append([]*AudioFile{audioFile}, q.Items...)
|
|
|
|
songLength := audioFile.Length
|
|
|
|
queueItemView := fmt.Sprintf("[ %s ] %s", fmtDuration(songLength), GetName(audioFile.Name))
|
|
q.InsertItem(0, queueItemView, audioFile.Path, 0, nil)
|
|
q.UpdateTitle()
|
|
}
|
|
|
|
// gets the first item and remove it from the queue
|
|
// app.Draw() must be called after calling this function
|
|
func (q *Queue) Dequeue() (*AudioFile, error) {
|
|
|
|
if q.GetItemCount() == 0 {
|
|
return nil, errors.New("Empty list")
|
|
}
|
|
|
|
first := q.Items[0]
|
|
q.DeleteItem(0)
|
|
q.UpdateTitle()
|
|
|
|
return first, nil
|
|
}
|
|
|
|
// Add item to the list and returns the length of the queue
|
|
func (q *Queue) Enqueue(audioFile *AudioFile) int {
|
|
|
|
if !gomu.Player.IsRunning && "false" == os.Getenv("TEST") {
|
|
|
|
gomu.Player.IsRunning = true
|
|
|
|
go func() {
|
|
|
|
gomu.Player.Run(audioFile)
|
|
|
|
}()
|
|
|
|
return q.GetItemCount()
|
|
|
|
}
|
|
|
|
q.Items = append(q.Items, audioFile)
|
|
songLength, err := GetLength(audioFile.Path)
|
|
|
|
if err != nil {
|
|
appLog(err)
|
|
}
|
|
|
|
queueItemView := fmt.Sprintf("[ %s ] %s", fmtDuration(songLength), GetName(audioFile.Name))
|
|
q.AddItem(queueItemView, audioFile.Path, 0, nil)
|
|
q.UpdateTitle()
|
|
|
|
return q.GetItemCount()
|
|
}
|
|
|
|
// 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
|
|
func (q *Queue) GetItems() []string {
|
|
|
|
items := []string{}
|
|
|
|
for i := 0; i < q.GetItemCount(); i++ {
|
|
|
|
_, second := q.GetItemText(i)
|
|
|
|
items = append(items, second)
|
|
}
|
|
|
|
return items
|
|
}
|
|
|
|
// Save the current queue in a csv file
|
|
func (q *Queue) SaveQueue() error {
|
|
|
|
songPaths := q.GetItems()
|
|
songNames := make([]string, 0, len(songPaths))
|
|
var content string
|
|
|
|
for _, songPath := range songPaths {
|
|
hashed := Sha1Hex(GetName(songPath))
|
|
songNames = append(songNames, hashed)
|
|
}
|
|
|
|
for _, v := range songNames {
|
|
content += v + "\n"
|
|
}
|
|
|
|
cachePath := expandTilde(q.SavedQueuePath)
|
|
err := ioutil.WriteFile(cachePath, []byte(content), 0644)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
// Clears current queue
|
|
func (q *Queue) ClearQueue() {
|
|
|
|
q.Items = []*AudioFile{}
|
|
q.Clear()
|
|
q.UpdateTitle()
|
|
|
|
}
|
|
|
|
// Loads previously saved list
|
|
func (q *Queue) LoadQueue() error {
|
|
|
|
songs, err := q.GetSavedQueue()
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
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) {
|
|
|
|
queuePath := expandTilde(q.SavedQueuePath)
|
|
|
|
if _, err := os.Stat(queuePath); os.IsNotExist(err) {
|
|
|
|
dir, _ := path.Split(queuePath)
|
|
|
|
err := os.MkdirAll(dir, 0744)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_, err = os.Create(queuePath)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return []string{}, nil
|
|
|
|
}
|
|
|
|
f, err := os.Open(queuePath)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
records := []string{}
|
|
scanner := bufio.NewScanner(f)
|
|
|
|
for scanner.Scan() {
|
|
records = append(records, scanner.Text())
|
|
}
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return records, nil
|
|
}
|
|
|
|
func (q *Queue) Shuffle() {
|
|
|
|
rand.Seed(time.Now().UnixNano())
|
|
rand.Shuffle(len(q.Items), func(i, j int) { q.Items[i], q.Items[j] = q.Items[j], q.Items[i] })
|
|
|
|
q.Clear()
|
|
|
|
for _, v := range q.Items {
|
|
audioLen, err := GetLength(v.Path)
|
|
appLog(err)
|
|
|
|
queueText := fmt.Sprintf("[ %s ] %s", fmtDuration(audioLen), v.Name)
|
|
q.AddItem(queueText, v.Path, 0, nil)
|
|
}
|
|
|
|
q.UpdateTitle()
|
|
|
|
}
|
|
|
|
// Initiliaze new queue with default values
|
|
func NewQueue() *Queue {
|
|
|
|
list := tview.NewList().
|
|
ShowSecondaryText(false)
|
|
|
|
queue := &Queue{
|
|
List: list,
|
|
SavedQueuePath: "~/.local/share/gomu/queue.cache",
|
|
Items: []*AudioFile{},
|
|
}
|
|
|
|
queue.SetInputCapture(func(e *tcell.EventKey) *tcell.EventKey {
|
|
|
|
switch e.Rune() {
|
|
case 'j':
|
|
queue.next()
|
|
case 'k':
|
|
queue.prev()
|
|
case 'd':
|
|
queue.DeleteItem(queue.GetCurrentItem())
|
|
case 'D':
|
|
queue.ClearQueue()
|
|
case 'x':
|
|
a := queue.DeleteItem(queue.GetCurrentItem())
|
|
queue.PushFront(a)
|
|
gomu.Player.Skip()
|
|
case 'z':
|
|
isLoop := gomu.Player.ToggleLoop()
|
|
var msg string
|
|
|
|
if isLoop {
|
|
msg = "on"
|
|
} else {
|
|
msg = "off"
|
|
}
|
|
|
|
timedPopup("Loop", msg, getPopupTimeout(), 30, 5)
|
|
case 's':
|
|
queue.Shuffle()
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
queue.UpdateTitle()
|
|
queue.SetBorder(true).SetTitleAlign(tview.AlignLeft)
|
|
queue.
|
|
SetSelectedBackgroundColor(tcell.ColorDarkCyan).
|
|
SetSelectedTextColor(tcell.ColorWhite).
|
|
SetHighlightFullLine(true)
|
|
|
|
return queue
|
|
|
|
}
|
|
|
|
func Sha1Hex(input string) string {
|
|
h := sha1.New()
|
|
h.Write([]byte(input))
|
|
return hex.EncodeToString(h.Sum(nil))
|
|
}
|