Select Git revision
security_classes
logic.go 10.11 KiB
package main
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
)
// maps user input to events
type logicMatchEntry struct {
event func(string)
match *regexp.Regexp
}
// compile regex patterns and map to actions
var (
logicMatchTable = map[string]*logicMatchEntry{
"logicSearch": &logicMatchEntry{logicSearch, regexp.MustCompile(`(?m)^[^\:]`)},
"logicNumber": &logicMatchEntry{logicNumber, regexp.MustCompile(`(?m)^\:[0-9]+(\ |$)`)},
"logicPage": &logicMatchEntry{logicPage, regexp.MustCompile(`(?m)^\::[0-9]+$`)},
"logicSub": &logicMatchEntry{logicSub, regexp.MustCompile(`(?m)^\:sub$`)}, // avoid matching conflict with subadd!
"logicSubAdd": &logicMatchEntry{logicSubAdd, regexp.MustCompile(`(?m)^\:subadd`)},
"logicSubDel": &logicMatchEntry{logicSubDel, regexp.MustCompile(`(?m)^\:subdel`)},
"logicUser": &logicMatchEntry{logicUser, regexp.MustCompile(`(?m)^\:user`)},
"logicNew": &logicMatchEntry{logicNew, regexp.MustCompile(`(?m)^\:new$`)},
"logicSet": &logicMatchEntry{logicSet, regexp.MustCompile(`(?m)^\:set`)},
"logicHelp": &logicMatchEntry{logicHelp, regexp.MustCompile(`(?m)^\:help`)},
"logicQuit": &logicMatchEntry{logicQuit, regexp.MustCompile(`(?m)^\:q$`)},
}
)
type logicWindowState int
// window states
const (
logicWindowNone logicWindowState = iota
logicWindowSearch
logicWindowSub
logicWindowUser
)
var (
logicVarLineCount = 30
logicVarWindow = logicWindowNone
logicVarVideos []*videoContainer = []*videoContainer{}
logicVarLazy = &listLazy{}
logicVarUsers = []string{}
logicVarUser = ""
logicVarSearch = ""
)
// logicAction parses the input string and performs the given action
func logicAction(input string) {
// match pattern
logicMatch(input)(logicTrim(input))
}
// logicMatch returns the best fitting function for the given input
func logicMatch(input string) func(string) {
for _, entry := range logicMatchTable {
if entry.match.MatchString(input) {
return entry.event
}
}
return logicUnknown
}
// logicTrim separates and returns the commands arguments
func logicTrim(trim string) string {
if logicMatchTable["logicNumber"].match.MatchString(trim) {
return trim[1:]
}
if logicMatchTable["logicPage"].match.MatchString(trim) {
return trim[2:]
}
if strings.Index(trim, ":") == 0 {
out := strings.SplitN(trim, " ", 2)
if len(out) > 1 {
return out[1]
}
return ""
}
return trim
}
// logicSearch searches for the given video title
func logicSearch(title string) {
upd := func(state int) []*videoContainer {
return videoListSearch(title, strconv.Itoa(state+1))
}
logicVarLazy = listNew(upd)
logicSearchPage("searching: "+title, 0)
}
//logicSearchPage searches the title in the given page
func logicSearchPage(title string, page int) {
cliStatus("page " + strconv.Itoa(page) + " | " + title)
logicVarWindow = logicWindowSearch
logicVarSearch = title
list := logicVarLazy.listSlice(page*logicVarLineCount, (page+1)*logicVarLineCount)
if list == nil {
cliStatusError("network error: unable to load data")
return
}
if len(list) == 0 {
cliStatusError("no results for the given query")
}
logicList(-1, list)
}
// logicNumber selects the given list number
func logicNumber(command string) {
splits := strings.SplitN(command, " ", 2)
player := logicGetPlayer()
if len(splits) > 1 {
player = splits[1]
}
sel, err := strconv.Atoi(splits[0])
errorCare(err)
switch logicVarWindow {
case logicWindowNone:
cliStatusError("error: video list is empty")
case logicWindowUser:
fallthrough
case logicWindowSearch:
if sel < len(logicVarVideos) {
logicPlay(player, sel) // non blocking
} else {
cliStatusError("error: video id not in list")
}
case logicWindowSub:
if sel < len(logicVarUsers) {
logicUser(logicVarUsers[sel])
} else {
cliStatusError("error: user id not in list")
}
}
}
// logicPage selects the given page
func logicPage(page string) {
pnum, err := strconv.Atoi(page)
errorCare(err)
switch logicVarWindow {
case logicWindowSearch:
logicSearchPage(logicVarSearch, pnum)
case logicWindowUser:
logicUserPage(logicVarUser, pnum)
}
}
// logicGetUsers retieves a list of all subscripted users
func logicGetUsers() []string {
subs, err := storeReadFile(storeDefaultSubscriptions)
errorCare(err)
users := []string{}
for _, user := range strings.Split(subs, "\n") {
if len(user) > 2 && user[0] == '"' && user[len(user)-1] == '"' {
users = append(users, user[1:len(user)-1])
}
}
return users
}
// logicSub lists the subscriptions
func logicSub(none string) {
columns := 4
logicVarWindow = logicWindowSub
id := make([][]string, columns)
us := make([][]string, columns)
users := logicGetUsers()
sort.Strings(users)
newcol := len(users)/columns + 1
for i, u := range users {
id[i/newcol] = append(id[i/newcol], strconv.Itoa(i))
us[i/newcol] = append(us[i/newcol], u)
}
logicVarUsers = users
cliStatus("list of subscriptions")
cliTable(-1, id[0], us[0], id[1], us[1], id[2], us[2], id[3], us[3])
}
// logicSubAdd adds the given user to the subscription list
func logicSubAdd(user string) {
if user == "" && logicVarWindow == logicWindowUser {
user = logicVarUser
}
if strings.Contains(user, "\"") {
cliStatusError("error: illicit username")
return
}
subs, err := storeReadFile(storeDefaultSubscriptions)
errorCare(err)
if strings.Contains(subs, "\""+user+"\"") {
cliStatusError("error: user '" + user + "' already in list")
return
}
errorCare(storeWriteFile(storeDefaultSubscriptions, subs+"\""+user+"\"\n"))
cliStatus("subscription '" + user + "' added")
cliClearDown()
}
// logicSubAdd adds the given user to the subscription list
func logicSubDel(user string) {
if user == "" && logicVarWindow == logicWindowUser {
user = logicVarUser
}
if strings.Contains(user, "\"") {
cliStatusError("error: illicit username")
return
}
subs, err := storeReadFile(storeDefaultSubscriptions)
errorCare(err)
if !strings.Contains(subs, "\""+user+"\"") {
cliStatusError("error: user '" + user + "' not in list")
return
}
errorCare(storeWriteFile(storeDefaultSubscriptions, strings.Replace(subs, "\""+user+"\"\n", "", 1))) // meh
cliStatus("subscription '" + user + "' deleted")
cliClearDown()
}
// logicUser shows the videos of the given user
func logicUser(user string) {
logicUserPage(user, 0)
}
// logicUserPage shows the page of the given user
func logicUserPage(user string, page int) {
// TODO: support pages (priority: low)
cliStatus("page " + strconv.Itoa(page) + " | user: " + user)
logicVarWindow = logicWindowUser
logicVarUser = user
logicList(-1, videoListUser(user))
}
// logicNew shows a list of new videos
func logicNew(none string) {
logicVarWindow = logicWindowSearch
logicVarSearch = ""
users := logicGetUsers()
upd := func(state int) []*videoContainer {
if state < len(users) {
return videoListUser(users[state])
}
return nil // all users updated (wont occur in reality)
}
logicVarLazy = listNew(upd)
cliStatus("updating users ...")
logicVarLazy.listPar(len(users))
logicVarLazy.listGet(0) // downloads all user pages as par = len(users)
cliHome()
fmt.Println()
cliStatus("sorting ...")
sort.Sort(&videoSort{logicVarLazy.listCached()})
logicSearchPage("new videos", 0)
}
// logicSet sets the key to the given value. Format: "key value"
func logicSet(perform string) {
logicSetPlayer(perform)
}
// logicHelp shows the help
func logicHelp(none string) {
cliStatus("available commands and usage")
cliClearDown()
help := `
to search for a video simply input keywords
commands:
:[number] [player] select list item (optional: video player)
::[number] select list page
:user [user] show videos of user
:sub show subscriptions
:new show new subscribed videos
:subadd [user] add given or current user
:subdel [user] delete given or current user
:set [player] set default video player
:help show this help
:q quit the program
`
fmt.Println(help)
}
// logicUnknown shows a message if the command could not be parsed
func logicUnknown(command string) {
cliStatusError("error: unknown command")
}
// logicQuit exits the program
func logicQuit(none string) {
cliReset()
os.Exit(0)
}
// logicList shows a video list and highlights the line with the given
// line number. If hightlight < 0 no line will be highlighted.
// The global video list logicVarVideos will be set by this function.
func logicList(highlight int, videos []*videoContainer) {
logicVarVideos = videos
cliClearDown()
id, titles, urls, durations, users, dates, views := []string{}, []string{}, []string{}, []string{}, []string{}, []string{}, []string{}
for i, v := range videos {
id = append(id, strconv.Itoa(i))
titles = append(titles, cliTrimLength(v.title, " >>", 55))
urls = append(urls, v.url)
durations = append(durations, v.duration)
users = append(users, v.user)
dates = append(dates, v.date)
views = append(views, v.views)
}
cliTable(highlight, id, titles, durations, users, views, dates)
}
// logicGetPlayer gets the default video player
func logicGetPlayer() string {
player, err := storeReadFile(storeDefaultPlayer)
errorCare(err)
return strings.Replace(player, "\n", "", -1)
}
// logicSetPlayer sets the default video player
func logicSetPlayer(player string) {
errorCare(storeWriteFile(storeDefaultPlayer, player))
cliStatus("default player set to '" + player + "'")
cliClearDown()
}
// logicPlay plays the video with the given video id in the player
func logicPlay(player string, id int) {
video := logicVarVideos[id]
cmd := append(strings.Split(player, " "), "https://www.youtube.com/watch?v="+video.url)
script := filepath.Join(storeDefaultScripts, cmd[0])
exists, err := storeFileExists(script)
errorCare(err)
if exists {
cmd[0] = script
player = "$" + player // visual indicator for user script
}
exe := exec.Command(cmd[0], cmd[1:]...)
if err := exe.Start(); err != nil {
cliStatusError(fmt.Sprint("playback error: ", err))
} else {
cliStatus("[" + player + "] " + video.title)
logicList(id, logicVarVideos)
}
go exe.Wait()
}