Merge pull request #17 from aditya-K2/pages

Significant Refactoring
This commit is contained in:
Aditya Kurdunkar 2021-12-24 15:50:41 +05:30 committed by GitHub
commit 9390fbf46e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 425 additions and 331 deletions

View File

@ -1,8 +1,11 @@
package main
package client
import (
"errors"
"fmt"
"github.com/fhs/gompd/mpd"
"strings"
"github.com/aditya-K2/gomp/utils"
@ -10,10 +13,23 @@ import (
)
var (
CONN *mpd.Client
ArtistTree map[string]map[string]map[string]string
NotificationServer interface {
Send(string)
}
WHITE_AND_BOLD string = "[#ffffff::b]"
)
func togglePlayBack() error {
func SetConnection(c *mpd.Client) {
CONN = c
}
func SetNotificationServer(n interface{ Send(string) }) {
NotificationServer = n
}
func TogglePlayBack() error {
status, err := CONN.Status()
if status["state"] == "play" && err == nil {
CONN.Pause(true)
@ -39,32 +55,30 @@ func UpdatePlaylist(inputTable *tview.Table) {
}
}
/*
The GenerateContentSlice returns a slice of the content to be displayed on the Search View. The Slice is generated
because the random nature of maps as they return values randomly hence the draw function keeps changing the order
in which the results.
*/
// The GenerateContentSlice returns a slice of the content to be displayed on the Search View. The Slice is generated
// because the random nature of maps as they return values randomly hence the draw function keeps changing the order
// in which the results appear.
func GenerateContentSlice(selectedSuggestion string) ([]interface{}, error) {
var ContentSlice []interface{}
if strings.TrimRight(selectedSuggestion, " ") == "" {
NOTIFICATION_SERVER.Send("Empty Search!")
NotificationServer.Send("Empty Search!")
return nil, errors.New("empty Search String Provided")
}
if _, ok := ARTIST_TREE[selectedSuggestion]; ok {
if _, ok := ArtistTree[selectedSuggestion]; ok {
ContentSlice = append(ContentSlice, WHITE_AND_BOLD+"Artists :")
ContentSlice = append(ContentSlice, selectedSuggestion)
ContentSlice = append(ContentSlice, WHITE_AND_BOLD+"Artist Albums :")
for albumName := range ARTIST_TREE[selectedSuggestion] {
for albumName := range ArtistTree[selectedSuggestion] {
ContentSlice = append(ContentSlice, [2]string{albumName, selectedSuggestion})
}
ContentSlice = append(ContentSlice, WHITE_AND_BOLD+"Artist Tracks :")
for albumName, trackList := range ARTIST_TREE[selectedSuggestion] {
for albumName, trackList := range ArtistTree[selectedSuggestion] {
for track := range trackList {
ContentSlice = append(ContentSlice, [3]string{track, selectedSuggestion, albumName})
}
}
}
if aMap := QueryArtistTreeForAlbums(ARTIST_TREE, selectedSuggestion); len(aMap) != 0 {
if aMap := QueryArtistTreeForAlbums(ArtistTree, selectedSuggestion); len(aMap) != 0 {
ContentSlice = append(ContentSlice, WHITE_AND_BOLD+"Albums :")
for mSlice := range aMap {
ContentSlice = append(ContentSlice, mSlice)
@ -76,7 +90,7 @@ func GenerateContentSlice(selectedSuggestion string) ([]interface{}, error) {
}
}
}
if tMap := QueryArtistTreeForTracks(ARTIST_TREE, selectedSuggestion); len(tMap) != 0 {
if tMap := QueryArtistTreeForTracks(ArtistTree, selectedSuggestion); len(tMap) != 0 {
ContentSlice = append(ContentSlice, WHITE_AND_BOLD+"Tracks :")
for mSlice := range tMap {
ContentSlice = append(ContentSlice, mSlice)
@ -85,11 +99,9 @@ func GenerateContentSlice(selectedSuggestion string) ([]interface{}, error) {
return ContentSlice, nil
}
/*
UpdateSearchView as the name suggests Updates the Search View the idea is to basically keep a fourth option called
Search in the Navigation bar which will render things from a global ContentSlice at least in the context of the main
function this will also help in persisting the Search Results.
*/
// UpdateSearchView as the name suggests Updates the Search View the idea is to basically keep a fourth option called
// Search in the Navigation bar which will render things from a global ContentSlice at least in the context of the main
// function this will also help in persisting the Search Results.
func UpdateSearchView(inputTable *tview.Table, c []interface{}) {
inputTable.Clear()
_, _, width, _ := inputTable.GetInnerRect()
@ -122,8 +134,8 @@ func UpdateSearchView(inputTable *tview.Table, c []interface{}) {
func Update(f []FileNode, inputTable *tview.Table) {
inputTable.Clear()
for i, j := range f {
if len(j.children) == 0 {
_songAttributes, err := CONN.ListAllInfo(j.absolutePath)
if len(j.Children) == 0 {
_songAttributes, err := CONN.ListAllInfo(j.AbsolutePath)
if err == nil && _songAttributes[0]["Title"] != "" {
_, _, w, _ := inputTable.GetInnerRect()
inputTable.SetCell(i, 0,
@ -140,19 +152,21 @@ func Update(f []FileNode, inputTable *tview.Table) {
} else if _songAttributes[0]["Title"] == "" {
inputTable.SetCell(i, 0,
tview.NewTableCell("[blue]"+j.path).
tview.NewTableCell("[blue]"+j.Path).
SetAlign(tview.AlignLeft))
}
} else {
inputTable.SetCell(i, 0,
tview.NewTableCell("[yellow::b]"+j.path).
tview.NewTableCell("[yellow::b]"+j.Path).
SetAlign(tview.AlignLeft))
}
}
}
// GenerateArtistTree Artist Tree is a map of Artist to their Album Map
// Album Tree is a map of the tracks in that particular album.
func GenerateArtistTree() (map[string]map[string]map[string]string, error) {
ArtistTree := make(map[string]map[string]map[string]string)
ArtistTree = make(map[string]map[string]map[string]string)
AllInfo, err := CONN.ListAllInfo("/")
if err == nil {
for _, i := range AllInfo {
@ -184,56 +198,50 @@ func PrintArtistTree(a map[string]map[string]map[string]string) {
}
}
/*
Adds All tracks from a specified album to a playlist
*/
// Adds All tracks from a specified album to a playlist
func AddAlbum(a map[string]map[string]map[string]string, alb string, artist string) {
for _, v := range a[artist][alb] {
err := CONN.Add(v)
if err != nil {
NOTIFICATION_SERVER.Send("Could Not Add Song : " + v)
NotificationServer.Send("Could Not Add Song : " + v)
}
}
NOTIFICATION_SERVER.Send("Album Added : " + alb)
NotificationServer.Send("Album Added : " + alb)
}
/*
Adds All tracks from a specified artist to a playlist
*/
// Adds All tracks from a specified artist to a playlist
func AddArtist(a map[string]map[string]map[string]string, artist string) {
if val, ok := a[artist]; ok {
for _, v := range val {
for _, path := range v {
err := CONN.Add(path)
if err != nil {
NOTIFICATION_SERVER.Send("Could Not Add Song : " + path)
NotificationServer.Send("Could Not Add Song : " + path)
}
}
}
NOTIFICATION_SERVER.Send("Artist Added : " + artist)
NotificationServer.Send("Artist Added : " + artist)
}
}
/*
Adds Specified Track to the Playlist
*/
// Adds Specified Track to the Playlist
func AddTitle(a map[string]map[string]map[string]string, artist, alb, track string, addAndPlay bool) {
if addAndPlay {
id, err := CONN.AddId(a[artist][alb][track], -1)
CONN.PlayId(id)
if err != nil {
NOTIFICATION_SERVER.Send("Could Not Add Track : " + track)
NotificationServer.Send("Could Not Add Track : " + track)
}
} else {
err := CONN.Add(a[artist][alb][track])
if err != nil {
NOTIFICATION_SERVER.Send("Could Not Add Track : " + track)
NotificationServer.Send("Could Not Add Track : " + track)
}
}
NOTIFICATION_SERVER.Send("Track Added : " + track)
NotificationServer.Send("Track Added : " + track)
}
/* Querys the Artist Tree for a track and returns a TrackMap (i.e [3]string{artist, album, track} -> path) which will help us
/* Querys the Artist Tree for a track and returns a TrackMap (i.e [3]string{artist, album, track} -> Path) which will help us
to add tracks to the playlist */
func QueryArtistTreeForTracks(a map[string]map[string]map[string]string, track string) map[[3]string]string {
TrackMap := make(map[[3]string]string)
@ -249,7 +257,7 @@ func QueryArtistTreeForTracks(a map[string]map[string]map[string]string, track s
return TrackMap
}
/* Querys the Artist Tree for an album and returns a AlbumMap (i.e [3]string{artist, album } ->[]path of songs in the album)
/* Querys the Artist Tree for an album and returns a AlbumMap (i.e [3]string{artist, album } ->[]Path of songs in the album)
which will help us to add all album tracks to the playlist */
func QueryArtistTreeForAlbums(a map[string]map[string]map[string]string, album string) map[[2]string][][2]string {
AlbumMap := make(map[[2]string][][2]string)
@ -272,17 +280,17 @@ func AddToPlaylist(a interface{}, addAndPlay bool) {
case [3]string:
{
b := a.([3]string)
AddTitle(ARTIST_TREE, b[1], b[2], b[0], addAndPlay)
AddTitle(ArtistTree, b[1], b[2], b[0], addAndPlay)
}
case [2]string:
{
b := a.([2]string)
AddAlbum(ARTIST_TREE, b[0], b[1])
AddAlbum(ArtistTree, b[0], b[1])
}
case string:
{
b := a.(string)
AddArtist(ARTIST_TREE, b)
AddArtist(ArtistTree, b)
}
}
}

69
client/files.go Normal file
View File

@ -0,0 +1,69 @@
package client
import (
"fmt"
"strings"
)
type FileNode struct {
Children []FileNode
Path string
Parent *FileNode
AbsolutePath string
}
func (f *FileNode) AddChildren(path string) {
if f.Path != "" {
f.Children = append(f.Children, FileNode{Children: make([]FileNode, 0), Path: path, Parent: f, AbsolutePath: f.AbsolutePath + "/" + path})
} else {
f.Children = append(f.Children, FileNode{Children: make([]FileNode, 0), Path: path, Parent: f, AbsolutePath: f.AbsolutePath + path})
}
}
func (f *FileNode) AddChildNode(m FileNode) {
m.Parent = f
f.Children = append(f.Children, m)
}
func GenerateDirectoryTree(path []string) *FileNode {
var head *FileNode = new(FileNode)
var head1 *FileNode = head
for i := range path {
sepPaths := strings.Split(path[i], "/")
for j := range sepPaths {
if len(head.Children) == 0 {
head.AddChildren(sepPaths[j])
head = &(head.Children[len(head.Children)-1])
} else {
var headIsChanged = false
for k := range head.Children {
if head.Children[k].Path == sepPaths[j] {
head = &(head.Children[k])
headIsChanged = true
break
}
}
if !headIsChanged {
head.AddChildren(sepPaths[j])
head = &(head.Children[len(head.Children)-1])
}
}
}
head = head1
}
return head
}
func (f FileNode) Print(count int) {
if len(f.Children) == 0 {
return
} else {
for i := range f.Children {
for j := 0; j < count; j++ {
fmt.Print("---")
}
fmt.Println(f.Children[i].AbsolutePath)
f.Children[i].Print(count + 1)
}
}
}

View File

@ -1,69 +0,0 @@
package main
import (
"fmt"
"strings"
)
type FileNode struct {
children []FileNode
path string
parent *FileNode
absolutePath string
}
func (f *FileNode) addChildren(path string) {
if f.path != "" {
f.children = append(f.children, FileNode{children: make([]FileNode, 0), path: path, parent: f, absolutePath: f.absolutePath + "/" + path})
} else {
f.children = append(f.children, FileNode{children: make([]FileNode, 0), path: path, parent: f, absolutePath: f.absolutePath + path})
}
}
func (f *FileNode) addChildNode(m FileNode) {
m.parent = f
f.children = append(f.children, m)
}
func generateDirectoryTree(path []string) *FileNode {
var head *FileNode = new(FileNode)
var head1 *FileNode = head
for i := range path {
sepPaths := strings.Split(path[i], "/")
for j := range sepPaths {
if len(head.children) == 0 {
head.addChildren(sepPaths[j])
head = &(head.children[len(head.children)-1])
} else {
var headIsChanged = false
for k := range head.children {
if head.children[k].path == sepPaths[j] {
head = &(head.children[k])
headIsChanged = true
break
}
}
if !headIsChanged {
head.addChildren(sepPaths[j])
head = &(head.children[len(head.children)-1])
}
}
}
head = head1
}
return head
}
func (f FileNode) Print(count int) {
if len(f.children) == 0 {
return
} else {
for i := range f.children {
for j := 0; j < count; j++ {
fmt.Print("---")
}
fmt.Println(f.children[i].absolutePath)
f.children[i].Print(count + 1)
}
}
}

172
main.go
View File

@ -4,6 +4,12 @@ import (
"strconv"
"time"
"github.com/aditya-K2/gomp/ui/notify"
"github.com/aditya-K2/gomp/render"
"github.com/aditya-K2/gomp/ui"
"github.com/aditya-K2/gomp/client"
"github.com/aditya-K2/gomp/utils"
"github.com/aditya-K2/fuzzy"
@ -14,92 +20,110 @@ import (
"github.com/spf13/viper"
)
var (
CONN *mpd.Client
UI *Application
NOTIFICATION_SERVER *NotificationServer
Volume int64
Random bool
Repeat bool
InsidePlaylist bool = true
InsideSearchView bool = false
ARTIST_TREE map[string]map[string]map[string]string
)
func main() {
config.ReadConfig()
// Connect to MPD server
var mpdConnectionError error
CONN, mpdConnectionError = mpd.Dial("tcp", "localhost:"+viper.GetString("MPD_PORT"))
CONN, mpdConnectionError := mpd.Dial("tcp", "localhost:"+viper.GetString("MPD_PORT"))
if mpdConnectionError != nil {
panic(mpdConnectionError)
}
defer CONN.Close()
ui.GenerateFocusMap()
client.SetConnection(CONN)
ui.SetConnection(CONN)
render.SetConnection(CONN)
cache.SetCacheDir(viper.GetString("CACHE_DIR"))
r := newRenderer()
Renderer := render.NewRenderer()
// Connecting the Renderer to the Main UI
ui.ConnectRenderer(Renderer)
c, _ := CONN.CurrentSong()
if len(c) != 0 {
r.Start(c["file"])
Renderer.Start(c["file"])
} else {
r.Start("stop")
Renderer.Start("stop")
}
UI = newApplication(r)
UI := ui.NewApplication()
// Connecting the Notification Server to the Main UI
notify.ConnectUI(UI)
fileMap, err := CONN.GetFiles()
dirTree := generateDirectoryTree(fileMap)
UpdatePlaylist(UI.ExpandedView)
// Generating the Directory Tree for File Navigation.
dirTree := client.GenerateDirectoryTree(fileMap)
// Default View upon Opening is of Playlist.
client.UpdatePlaylist(UI.ExpandedView)
_v, _ := CONN.Status()
Volume, _ = strconv.ParseInt(_v["volume"], 10, 64)
Random, _ = strconv.ParseBool(_v["random"])
Repeat, _ = strconv.ParseBool(_v["repeat"])
// Setting Volume, Random and Repeat Values
Volume, _ := strconv.ParseInt(_v["volume"], 10, 64)
Random, _ := strconv.ParseBool(_v["random"])
Repeat, _ := strconv.ParseBool(_v["repeat"])
ARTIST_TREE, err = GenerateArtistTree()
ARTIST_TREE_CONTENT := utils.ConvertToArray(ARTIST_TREE)
NOTIFICATION_SERVER = NewNotificationServer()
NOTIFICATION_SERVER.Start()
ArtistTree, err := client.GenerateArtistTree()
var SEARCH_CONTENT_SLICE []interface{}
// Used for Fuzzy Searching
ArtistTreeContent := utils.ConvertToArray(ArtistTree)
Notify := notify.NewNotificationServer()
Notify.Start()
// Connecting Notification Server to Client and Rendering Module so that they can send Notifications
client.SetNotificationServer(Notify)
render.SetNotificationServer(Notify)
// This is the Slice that is used to Display Content in the SearchView
var SearchContentSlice []interface{}
// This Function Is Responsible for Changing the Focus it uses the Focus Map and Based on it Chooses
// the Draw Function
UI.ExpandedView.SetDrawFunc(func(s tcell.Screen, x, y, width, height int) (int, int, int, int) {
if InsidePlaylist {
UpdatePlaylist(UI.ExpandedView)
} else if InsideSearchView {
UpdateSearchView(UI.ExpandedView, SEARCH_CONTENT_SLICE)
} else {
Update(dirTree.children, UI.ExpandedView)
if ui.HasFocus("Playlist") {
client.UpdatePlaylist(UI.ExpandedView)
} else if ui.HasFocus("SearchView") {
client.UpdateSearchView(UI.ExpandedView, SearchContentSlice)
} else if ui.HasFocus("FileBrowser") {
client.Update(dirTree.Children, UI.ExpandedView)
}
return UI.ExpandedView.GetInnerRect()
})
var FUNC_MAP = map[string]func(){
// Function Maps is used For Mapping Keys According to the Value mapped to the Key the respective Function is called
// For e.g. in the config if the User Maps T to togglePlayBack then whenever in the input handler the T is received
// the respective function in this case togglePlayBack is called.
var FuncMap = map[string]func(){
"showChildrenContent": func() {
r, _ := UI.ExpandedView.GetSelection()
if !InsidePlaylist && !InsideSearchView {
if len(dirTree.children[r].children) == 0 {
id, _ := CONN.AddId(dirTree.children[r].absolutePath, -1)
if ui.HasFocus("FileBrowser") {
if len(dirTree.Children[r].Children) == 0 {
id, _ := CONN.AddId(dirTree.Children[r].AbsolutePath, -1)
CONN.PlayId(id)
} else {
Update(dirTree.children[r].children, UI.ExpandedView)
dirTree = &dirTree.children[r]
client.Update(dirTree.Children[r].Children, UI.ExpandedView)
dirTree = &dirTree.Children[r]
}
} else if InsidePlaylist {
} else if ui.HasFocus("Playlist") {
CONN.Play(r)
} else if InsideSearchView {
} else if ui.HasFocus("SearchView") {
r, _ := UI.ExpandedView.GetSelection()
AddToPlaylist(SEARCH_CONTENT_SLICE[r], true)
client.AddToPlaylist(SearchContentSlice[r], true)
}
},
"togglePlayBack": func() {
togglePlayBack()
client.TogglePlayBack()
},
"showParentContent": func() {
if !InsidePlaylist && !InsideSearchView {
if dirTree.parent != nil {
Update(dirTree.parent.children, UI.ExpandedView)
dirTree = dirTree.parent
if ui.HasFocus("FileBrowser") {
if dirTree.Parent != nil {
client.Update(dirTree.Parent.Children, UI.ExpandedView)
dirTree = dirTree.Parent
}
}
},
@ -108,18 +132,18 @@ func main() {
},
"clearPlaylist": func() {
CONN.Clear()
NOTIFICATION_SERVER.Send("PlayList Cleared")
Notify.Send("Playlist Cleared")
},
"previousSong": func() {
CONN.Previous()
},
"addToPlaylist": func() {
if !InsidePlaylist && !InsideSearchView {
if ui.HasFocus("FileBrowser") {
r, _ := UI.ExpandedView.GetSelection()
CONN.Add(dirTree.children[r].absolutePath)
} else if InsideSearchView {
CONN.Add(dirTree.Children[r].AbsolutePath)
} else if ui.HasFocus("SearchView") {
r, _ := UI.ExpandedView.GetSelection()
AddToPlaylist(SEARCH_CONTENT_SLICE[r], false)
client.AddToPlaylist(SearchContentSlice[r], false)
}
},
"toggleRandom": func() {
@ -151,25 +175,20 @@ func main() {
CONN.SetVolume(int(Volume))
},
"navigateToFiles": func() {
InsidePlaylist = false
InsideSearchView = false
ui.SetFocus("FileBrowser")
UI.Navbar.Select(1, 0)
Update(dirTree.children, UI.ExpandedView)
client.Update(dirTree.Children, UI.ExpandedView)
},
"navigateToPlaylist": func() {
InsidePlaylist = true
InsideSearchView = false
ui.SetFocus("Playlist")
UI.Navbar.Select(0, 0)
UpdatePlaylist(UI.ExpandedView)
client.UpdatePlaylist(UI.ExpandedView)
},
"navigateToMostPlayed": func() {
InsideSearchView = false
InsidePlaylist = false
UI.Navbar.Select(2, 0)
},
"navigateToSearch": func() {
InsideSearchView = true
InsidePlaylist = false
ui.SetFocus("SearchView")
UI.Navbar.Select(3, 0)
},
"quit": func() {
@ -177,17 +196,17 @@ func main() {
},
"stop": func() {
CONN.Stop()
NOTIFICATION_SERVER.Send("Playback Stopped")
Notify.Send("Playback Stopped")
},
"updateDB": func() {
_, err = CONN.Update("")
if err != nil {
panic(err)
}
NOTIFICATION_SERVER.Send("Database Updated")
Notify.Send("Database Updated")
},
"deleteSongFromPlaylist": func() {
if InsidePlaylist {
if ui.HasFocus("Playlist") {
r, _ := UI.ExpandedView.GetSelection()
CONN.Delete(r, -1)
}
@ -197,12 +216,15 @@ func main() {
},
}
config.GenerateKeyMap(FUNC_MAP)
// Generating the Key Map Based on the Function Map Here Basically the Values will be flipped
// In the config if togglePlayBack is mapped to [ T , P, SPACE ] then here Basically we will receive a map
// for each event T, P, SPACE mapped to the same function togglePlayBack
config.GenerateKeyMap(FuncMap)
UI.SearchBar.SetAutocompleteFunc(func(c string) []string {
if c != "" && c != " " && c != " " {
_, _, w, _ := UI.SearchBar.GetRect()
matches := fuzzy.Find(c, ARTIST_TREE_CONTENT)
matches := fuzzy.Find(c, ArtistTreeContent)
var suggestions []string
for i, match := range matches {
if i == 10 {
@ -216,9 +238,10 @@ func main() {
}
})
// Input Handler
UI.ExpandedView.SetInputCapture(func(e *tcell.EventKey) *tcell.EventKey {
if val, ok := config.KEY_MAP[int(e.Rune())]; ok {
FUNC_MAP[val]()
FuncMap[val]()
return nil
} else {
return e
@ -228,12 +251,11 @@ func main() {
UI.SearchBar.SetDoneFunc(func(e tcell.Key) {
if e == tcell.KeyEnter {
UI.ExpandedView.Select(0, 0)
InsideSearchView = true
InsidePlaylist = false
SEARCH_CONTENT_SLICE = nil
SEARCH_CONTENT_SLICE, err = GenerateContentSlice(UI.SearchBar.GetText())
ui.SetFocus("SearchView")
SearchContentSlice = nil
SearchContentSlice, err = client.GenerateContentSlice(UI.SearchBar.GetText())
if err != nil {
NOTIFICATION_SERVER.Send("Could Not Retrieve the Results")
Notify.Send("Could Not Retrieve the Results")
} else {
UI.SearchBar.SetText("")
UI.App.SetFocus(UI.ExpandedView)
@ -241,7 +263,7 @@ func main() {
}
}
if e == tcell.KeyEscape {
InsideSearchView = false
ui.FocusMap["SearchView"] = false
UI.App.SetFocus(UI.ExpandedView)
}
})

122
render.go
View File

@ -1,122 +0,0 @@
package main
import (
"image"
"os"
"github.com/aditya-K2/gomp/cache"
"github.com/aditya-K2/gomp/utils"
"github.com/nfnt/resize"
"github.com/spf13/viper"
"gitlab.com/diamondburned/ueberzug-go"
)
/*
Renderer is just a channel on which we will send the Path to the song whose
Image is to be Rendered. This channel is passed to the openImage which in turn is called
by the Start() function as a go routine.
*/
type Renderer struct {
c chan string
}
/*
Returns a new Renderer with a string channel
*/
func newRenderer() *Renderer {
c := make(chan string)
return &Renderer{
c: c,
}
}
/*
Send Image Path to Renderer
*/
func (self *Renderer) Send(path string) {
self.c <- path
}
/*
Go Routine that will Be Called and will listen on the channel c
for changes and on getting a string over the channel will open the Image and
keep listening again. This will keep the image blocked ( i.e no need to use time.Sleep() etc. )
and saves resources too.
*/
func openImage(path string, c chan string) {
fw, fh := utils.GetFontWidth()
var im *ueberzug.Image
if path != "stop" {
extractedImage := getImagePath(path)
img2, _ := GetImg(extractedImage)
im, _ = ueberzug.NewImage(img2, int(float32(IMG_X)*fw)+viper.GetInt("ADDITIONAL_PADDING_X"), int(float32(IMG_Y)*fh)+viper.GetInt("ADDITIONAL_PADDING_Y"))
}
d := <-c
if im != nil {
im.Clear()
}
if d != "stop" {
openImage(d, c)
} else {
openImage("stop", c)
}
}
/*
Initialises the Renderer and calls the go routine openImage and passes the channel
as argument.
*/
func (self *Renderer) Start(path string) {
go openImage(path, self.c)
}
/*
This Function returns the path to the image that is to be rendered it checks first for the image in the cache
else it adds the image to the cache and then extracts it and renders it.
*/
func getImagePath(path string) string {
a, err := CONN.ListInfo(path)
var extractedImage string
if err == nil && len(a) != 0 {
if cache.Exists(a[0]["artist"], a[0]["album"]) {
extractedImage = cache.GenerateName(a[0]["artist"], a[0]["album"])
} else {
imagePath := cache.GenerateName(a[0]["artist"], a[0]["album"])
absPath := utils.CheckDirectoryFmt(viper.GetString("MUSIC_DIRECTORY")) + path
extractedImage = utils.ExtractImageFromFile(absPath, imagePath)
if extractedImage == viper.GetString("DEFAULT_IMAGE_PATH") && viper.GetString("GET_COVER_ART_FROM_LAST_FM") == "TRUE" {
downloadedImage, err := getImageFromLastFM(a[0]["artist"], a[0]["album"], imagePath)
if err == nil {
NOTIFICATION_SERVER.Send("Image From LastFM")
extractedImage = downloadedImage
} else {
NOTIFICATION_SERVER.Send("Falling Back to Default Image.")
}
} else {
NOTIFICATION_SERVER.Send("Extracted Image Successfully")
}
}
}
return extractedImage
}
func GetImg(uri string) (image.Image, error) {
f, err := os.Open(uri)
if err != nil {
return nil, err
}
defer f.Close()
img, _, err := image.Decode(f)
if err != nil {
return nil, err
}
fw, fh := utils.GetFontWidth()
img = resize.Resize(
uint(float32(IMG_W)*(fw+float32(viper.GetFloat64("IMAGE_WIDTH_EXTRA_X")))), uint(float32(IMG_H)*(fh+float32(viper.GetFloat64("IMAGE_WIDTH_EXTRA_Y")))),
img,
resize.Bilinear,
)
return img, nil
}

View File

@ -1,6 +1,7 @@
package utils
package render
import (
"github.com/aditya-K2/gomp/utils"
"os"
"strings"
@ -67,17 +68,17 @@ func ExtractImageFromFile(uri string, imagePath string) string {
if strings.HasSuffix(uri, ".mp3") {
imagePath := GetMp3Image(uri, imagePath)
if imagePath == "" {
Copy(viper.GetString("DEFAULT_IMAGE_PATH"), _i)
utils.Copy(viper.GetString("DEFAULT_IMAGE_PATH"), _i)
return viper.GetString("DEFAULT_IMAGE_PATH")
}
} else if strings.HasSuffix(uri, ".flac") {
imagePath := GetFlacImage(uri, imagePath)
if imagePath == "" {
Copy(viper.GetString("DEFAULT_IMAGE_PATH"), _i)
utils.Copy(viper.GetString("DEFAULT_IMAGE_PATH"), _i)
return viper.GetString("DEFAULT_IMAGE_PATH")
}
} else {
Copy(viper.GetString("DEFAULT_IMAGE_PATH"), _i)
utils.Copy(viper.GetString("DEFAULT_IMAGE_PATH"), _i)
return viper.GetString("DEFAULT_IMAGE_PATH")
}
return imagePath

View File

@ -1,4 +1,4 @@
package main
package render
import (
"errors"

124
render/render.go Normal file
View File

@ -0,0 +1,124 @@
package render
import (
"image"
"os"
"github.com/aditya-K2/gomp/ui"
"github.com/fhs/gompd/mpd"
"github.com/aditya-K2/gomp/cache"
"github.com/aditya-K2/gomp/utils"
"github.com/nfnt/resize"
"github.com/spf13/viper"
"gitlab.com/diamondburned/ueberzug-go"
)
var (
CONN *mpd.Client
Notify interface{ Send(string) }
)
func SetConnection(c *mpd.Client) {
CONN = c
}
func SetNotificationServer(n interface{ Send(string) }) {
Notify = n
}
// Renderer is just a channel on which we will send the Path to the song whose
// Image is to be Rendered. This channel is passed to the OpenImage which in turn is called
// by the Start() function as a go routine.
type Renderer struct {
c chan string
}
// Returns a new Renderer with a string channel
func NewRenderer() *Renderer {
c := make(chan string)
return &Renderer{
c: c,
}
}
// Send Image Path to Renderer
func (self *Renderer) Send(path string) {
self.c <- path
}
// Go Routine that will Be Called and will listen on the channel c
// for changes and on getting a string over the channel will open the Image and
// keep listening again. This will keep the image blocked ( i.e no need to use time.Sleep() etc. )
// and saves resources too.
func OpenImage(path string, c chan string) {
fw, fh := utils.GetFontWidth()
var im *ueberzug.Image
if path != "stop" {
extractedImage := GetImagePath(path)
img2, _ := GetImg(extractedImage)
im, _ = ueberzug.NewImage(img2, int(float32(ui.IMG_X)*fw)+viper.GetInt("ADDITIONAL_PADDING_X"), int(float32(ui.IMG_Y)*fh)+viper.GetInt("ADDITIONAL_PADDING_Y"))
}
d := <-c
if im != nil {
im.Clear()
}
if d != "stop" {
OpenImage(d, c)
} else {
OpenImage("stop", c)
}
}
// Initialises the Renderer and calls the go routine OpenImage and passes the channel
// as argument.
func (self *Renderer) Start(path string) {
go OpenImage(path, self.c)
}
// This Function returns the path to the image that is to be rendered it checks first for the image in the cache
// else it adds the image to the cache and then extracts it and renders it.
func GetImagePath(path string) string {
a, err := CONN.ListInfo(path)
var extractedImage string
if err == nil && len(a) != 0 {
if cache.Exists(a[0]["artist"], a[0]["album"]) {
extractedImage = cache.GenerateName(a[0]["artist"], a[0]["album"])
} else {
imagePath := cache.GenerateName(a[0]["artist"], a[0]["album"])
absPath := utils.CheckDirectoryFmt(viper.GetString("MUSIC_DIRECTORY")) + path
extractedImage = ExtractImageFromFile(absPath, imagePath)
if extractedImage == viper.GetString("DEFAULT_IMAGE_PATH") && viper.GetString("GET_COVER_ART_FROM_LAST_FM") == "TRUE" {
downloadedImage, err := getImageFromLastFM(a[0]["artist"], a[0]["album"], imagePath)
if err == nil {
Notify.Send("Image From LastFM")
extractedImage = downloadedImage
} else {
Notify.Send("Falling Back to Default Image.")
}
} else {
Notify.Send("Extracted Image Successfully")
}
}
}
return extractedImage
}
func GetImg(uri string) (image.Image, error) {
f, err := os.Open(uri)
if err != nil {
return nil, err
}
defer f.Close()
img, _, err := image.Decode(f)
if err != nil {
return nil, err
}
fw, fh := utils.GetFontWidth()
img = resize.Resize(
uint(float32(ui.IMG_W)*(fw+float32(viper.GetFloat64("IMAGE_WIDTH_EXTRA_X")))), uint(float32(ui.IMG_H)*(fh+float32(viper.GetFloat64("IMAGE_WIDTH_EXTRA_Y")))),
img,
resize.Bilinear,
)
return img, nil
}

View File

@ -1,4 +1,4 @@
package main
package ui
import (
"github.com/aditya-K2/tview"
@ -16,9 +16,9 @@ type Application struct {
Pages *tview.Pages
}
func newApplication(r *Renderer) *Application {
func NewApplication() *Application {
var pBar *progressBar = newProgressBar(r)
var pBar *progressBar = newProgressBar()
expandedView := tview.NewTable()
Navbar := tview.NewTable()
searchBar := tview.NewInputField()
@ -60,7 +60,7 @@ func newApplication(r *Renderer) *Application {
AddItem(searchBar, 3, 1, false).
AddItem(sNavExpViewFlex, 0, 1, false)
mainFlex := tview.NewFlex().SetDirection(tview.FlexRow).
lex := tview.NewFlex().SetDirection(tview.FlexRow).
AddItem(searchBarFlex, 0, 8, false).
AddItem(pBar.t, 5, 1, false)
@ -68,7 +68,7 @@ func newApplication(r *Renderer) *Application {
expandedView.SetSelectable(true, false)
rootPages := tview.NewPages()
rootPages.AddPage("Main", mainFlex, true, true)
rootPages.AddPage("Main", lex, true, true)
App := tview.NewApplication()
App.SetRoot(rootPages, true).SetFocus(expandedView)

24
ui/focus.go Normal file
View File

@ -0,0 +1,24 @@
package ui
// The Focus Map Helps to keep track of which UI Element Currently Has the Focus It can be queried to get the Current
// UI Element with Focus and also can set UI Focus keep in mind that it isn't Focus Map that is Responsible to change
// the Focus that is Done through the Update Function of UI.ExpandedView */
var FocusMap map[string]bool
func GenerateFocusMap() {
FocusMap = make(map[string]bool)
FocusMap["Playlist"] = true
FocusMap["FileBrowser"] = false
FocusMap["SearchView"] = false
}
func HasFocus(s string) bool {
return FocusMap[s]
}
func SetFocus(s string) {
for k := range FocusMap {
FocusMap[k] = false
}
FocusMap[s] = true
}

View File

@ -1,6 +1,7 @@
package main
package notify
import (
"github.com/aditya-K2/gomp/ui"
"time"
"github.com/aditya-K2/gomp/utils"
@ -9,6 +10,14 @@ import (
"github.com/gdamore/tcell/v2"
)
var (
UI *ui.Application
)
func ConnectUI(a *ui.Application) {
UI = a
}
/* Notification Primitive */
type Notification struct {
*tview.Box

View File

@ -1,16 +1,30 @@
package main
package ui
import (
"fmt"
"strconv"
"github.com/fhs/gompd/mpd"
"github.com/aditya-K2/gomp/utils"
"github.com/aditya-K2/tview"
"github.com/gdamore/tcell/v2"
)
var CurrentSong string
var (
CurrentSong string
CONN *mpd.Client
RENDERER interface{ Send(string) }
)
func SetConnection(c *mpd.Client) {
CONN = c
}
func ConnectRenderer(r interface{ Send(string) }) {
RENDERER = r
}
// The progressBar is just a string which is separated by the color formatting String
// for e.g
@ -26,7 +40,7 @@ type progressBar struct {
// This Function returns a progressBar with a table of two rows
// the First row will contain information about the current Song
// and the Second one will contain the progressBar
func newProgressBar(r *Renderer) *progressBar {
func newProgressBar() *progressBar {
p := progressBar{}
a := tview.NewTable().
@ -38,7 +52,7 @@ func newProgressBar(r *Renderer) *progressBar {
a.SetBorder(true)
a.SetDrawFunc(func(s tcell.Screen, x, y, width, height int) (int, int, int, int) {
p.updateTitle(r)
p.updateTitle()
p.updateProgress()
return p.t.GetInnerRect()
})
@ -49,16 +63,16 @@ func newProgressBar(r *Renderer) *progressBar {
return &p
}
func (s *progressBar) updateTitle(r *Renderer) {
func (s *progressBar) updateTitle() {
_currentAttributes, err := CONN.CurrentSong()
if err == nil {
song := "[green::bi]" + _currentAttributes["Title"] + "[-:-:-] - " + "[blue::b]" + _currentAttributes["Artist"] + "\n"
s.t.GetCell(0, 0).Text = song
if len(_currentAttributes) == 0 && CurrentSong != "" {
CurrentSong = ""
r.Send("stop")
RENDERER.Send("stop")
} else if song != CurrentSong && len(_currentAttributes) != 0 {
r.Send(_currentAttributes["file"])
RENDERER.Send(_currentAttributes["file"])
CurrentSong = song
}
}
@ -72,14 +86,28 @@ func (s *progressBar) updateProgress() {
if du != 0 {
percentage := el / du * 100
if err == nil && err1 == nil {
s.t.SetTitle(fmt.Sprintf("[[::i] %s [-:-:-]Shuffle: %s Repeat: %s Volume: %s ]", utils.FormatString(_status["state"]), utils.FormatString(_status["random"]), utils.FormatString(_status["repeat"]), _status["volume"])).SetTitleAlign(tview.AlignRight)
s.t.SetTitle(fmt.Sprintf("[[::i] %s [-:-:-]Shuffle: %s Repeat: %s Volume: %s ]",
utils.FormatString(_status["state"]),
utils.FormatString(_status["random"]),
utils.FormatString(_status["repeat"]),
_status["volume"])).
SetTitleAlign(tview.AlignRight)
s.t.GetCell(2, 0).Text = utils.GetText(float64(_width), percentage, utils.StrTime(el)+"/"+utils.StrTime(du)+"("+strconv.FormatFloat(percentage, 'f', 2, 32)+"%"+")")
} else {
s.t.SetTitle(fmt.Sprintf("[[::i] %s [-:-:-]Shuffle: %s Repeat: %s]", utils.FormatString(_status["state"]), utils.FormatString(_status["random"]), utils.FormatString(_status["repeat"]))).SetTitleAlign(tview.AlignRight)
s.t.SetTitle(fmt.Sprintf("[[::i] %s [-:-:-]Shuffle: %s Repeat: %s]",
utils.FormatString(_status["state"]),
utils.FormatString(_status["random"]),
utils.FormatString(_status["repeat"]))).
SetTitleAlign(tview.AlignRight)
s.t.GetCell(2, 0).Text = ""
}
} else {
s.t.SetTitle(fmt.Sprintf("[[::i] %s [-:-:-]Shuffle: %s Repeat: %s Volume: %s ]", utils.FormatString(_status["state"]), utils.FormatString(_status["random"]), utils.FormatString(_status["repeat"]), _status["volume"])).SetTitleAlign(tview.AlignRight)
s.t.SetTitle(fmt.Sprintf("[[::i] %s [-:-:-]Shuffle: %s Repeat: %s Volume: %s ]",
utils.FormatString(_status["state"]),
utils.FormatString(_status["random"]),
utils.FormatString(_status["repeat"]),
_status["volume"])).
SetTitleAlign(tview.AlignRight)
s.t.GetCell(2, 0).Text = utils.GetText(float64(_width), 0, " ---:---")
}
}