1
0
mirror of https://github.com/taigrr/wtf synced 2025-01-18 04:03:14 -08:00

Merge pull request #477 from wtfutil/20190622-transmission-module

20190622 transmission module
This commit is contained in:
Chris Cummer 2019-06-26 17:17:54 -07:00 committed by GitHub
commit 32bb91a1a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 302 additions and 9 deletions

3
go.mod
View File

@ -39,7 +39,10 @@ require (
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f // indirect
github.com/gorilla/mux v1.7.1 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.9.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.1 // indirect
github.com/hashicorp/golang-lru v0.5.1 // indirect
github.com/hekmon/cunits v2.0.1+incompatible // indirect
github.com/hekmon/transmissionrpc v0.0.0-20190525133028-1d589625bacd
github.com/jessevdk/go-flags v1.4.0
github.com/kisielk/errcheck v1.2.0 // indirect
github.com/kisielk/gotool v1.0.0 // indirect

6
go.sum
View File

@ -122,9 +122,15 @@ github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z
github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hekmon/cunits v2.0.1+incompatible h1:yy/Wq5YvNtrweqfeRjvrvMdBMH6axBrlL8t7arLlm5A=
github.com/hekmon/cunits v2.0.1+incompatible/go.mod h1:0QdfIGGkucx1VgStMNiHOYn84t/Ru65b+D3z1QszVPc=
github.com/hekmon/transmissionrpc v0.0.0-20190525133028-1d589625bacd h1:bZZYKxyrUV8EsKB5AjsPsJiWE7n0FDURijlSzYZVqAM=
github.com/hekmon/transmissionrpc v0.0.0-20190525133028-1d589625bacd/go.mod h1:b1R6hlzo+gEUNWGh53mw9mPWyYyODdZu/qlVqT+W+PU=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=

View File

@ -41,6 +41,7 @@ import (
"github.com/wtfutil/wtf/modules/textfile"
"github.com/wtfutil/wtf/modules/todo"
"github.com/wtfutil/wtf/modules/todoist"
"github.com/wtfutil/wtf/modules/transmission"
"github.com/wtfutil/wtf/modules/travisci"
"github.com/wtfutil/wtf/modules/trello"
"github.com/wtfutil/wtf/modules/twitter"
@ -181,6 +182,9 @@ func MakeWidget(
case "todoist":
settings := todoist.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig)
widget = todoist.NewWidget(app, pages, settings)
case "transmission":
settings := transmission.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig)
widget = transmission.NewWidget(app, pages, settings)
case "travisci":
settings := travisci.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig)
widget = travisci.NewWidget(app, pages, settings)

View File

@ -29,7 +29,6 @@ type Settings struct {
// NewSettingsFromYAML creates a new settings instance from a YAML config block
func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings {
settings := Settings{
common: cfg.NewCommonSettingsFromModule(name, defaultTitle, ymlConfig, globalConfig),

View File

@ -17,18 +17,18 @@ type Widget struct {
// NewWidget creates a new instance of a widget
func NewWidget(app *tview.Application, settings *Settings) *Widget {
widget := Widget{
widget := &Widget{
TextWidget: wtf.NewTextWidget(app, settings.common, false),
settings: settings,
}
return &widget
return widget
}
/* -------------------- Exported Functions -------------------- */
// Fetch rettrieves HIBP data from the HIBP API
// Fetch retrieves HIBP data from the HIBP API
func (widget *Widget) Fetch(accounts []string) ([]*Status, error) {
data := []*Status{}

View File

@ -17,15 +17,15 @@ func (widget *Widget) display() {
title := fmt.Sprintf("[green]%s[white]", proj.Project.Name)
str := ""
for index, item := range proj.tasks {
for idx, item := range proj.tasks {
row := fmt.Sprintf(
`[%s]| | %s[%s]`,
widget.RowColor(index),
widget.RowColor(idx),
tview.Escape(item.Content),
widget.RowColor(index),
widget.RowColor(idx),
)
str += wtf.HighlightableHelper(widget.View, row, index, len(item.Content))
str += wtf.HighlightableHelper(widget.View, row, idx, len(item.Content))
}
widget.ScrollableWidget.Redraw(title, str, false)

View File

@ -0,0 +1,77 @@
package transmission
import (
"fmt"
"strings"
"github.com/hekmon/transmissionrpc"
"github.com/rivo/tview"
"github.com/wtfutil/wtf/wtf"
)
func (widget *Widget) contentFrom(data []*transmissionrpc.Torrent) string {
str := ""
for idx, torrent := range data {
torrName := *torrent.Name
row := fmt.Sprintf(
"[%s] %s %s%s[white]",
widget.RowColor(idx),
widget.torrentPercentDone(torrent),
widget.torrentState(torrent),
tview.Escape(widget.prettyTorrentName(torrName)),
)
str += wtf.HighlightableHelper(widget.View, row, idx, len(torrName))
}
return str
}
func (widget *Widget) display() {
if len(widget.torrents) == 0 {
widget.ScrollableWidget.Redraw(widget.CommonSettings.Title, "no torrents", false)
return
}
content := widget.contentFrom(widget.torrents)
widget.ScrollableWidget.Redraw(widget.CommonSettings.Title, content, false)
}
func (widget *Widget) prettyTorrentName(name string) string {
str := strings.Replace(name, "[", "(", -1)
str = strings.Replace(str, "]", ")", -1)
return str
}
func (widget *Widget) torrentPercentDone(torrent *transmissionrpc.Torrent) string {
pctDone := *torrent.PercentDone
str := fmt.Sprintf("%3d", int(pctDone*100))
if pctDone == 0.0 {
str = "[gray::b]" + str
} else if pctDone == 1.0 {
str = "[green::b]" + str
} else {
str = "[lightblue::b]" + str
}
return str + "[white]"
}
func (widget *Widget) torrentState(torrent *transmissionrpc.Torrent) string {
str := ""
switch *torrent.Status {
case transmissionrpc.TorrentStatusStopped:
str += "[gray]"
case transmissionrpc.TorrentStatusDownload:
str += "[lightblue]"
case transmissionrpc.TorrentStatusSeed:
str += "[green]"
}
return str
}

View File

@ -0,0 +1,16 @@
package transmission
import "github.com/gdamore/tcell"
func (widget *Widget) initializeKeyboardControls() {
widget.SetKeyboardChar("/", widget.ShowHelp, "Show/hide this help prompt")
widget.SetKeyboardChar("j", widget.Prev, "Select previous item")
widget.SetKeyboardChar("k", widget.Next, "Select next item")
widget.SetKeyboardChar("u", widget.Unselect, "Clear selection")
widget.SetKeyboardKey(tcell.KeyCtrlD, widget.deleteSelectedTorrent, "Delete selected torrent")
widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select next item")
widget.SetKeyboardKey(tcell.KeyEnter, widget.pauseUnpauseTorrent, "Pause/unpause torrent")
widget.SetKeyboardKey(tcell.KeyEsc, widget.Unselect, "Clear selection")
widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous item")
}

View File

@ -0,0 +1,38 @@
package transmission
import (
"github.com/olebedev/config"
"github.com/wtfutil/wtf/cfg"
)
// Settings defines the configuration properties for this module
type Settings struct {
common *cfg.Common
host string
https bool
password string
port int
url string
username string
}
const (
defaultTitle = "Transmission"
)
// NewSettingsFromYAML creates a new settings instance from a YAML config block
func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings {
settings := Settings{
common: cfg.NewCommonSettingsFromModule(name, defaultTitle, ymlConfig, globalConfig),
host: ymlConfig.UString("host"),
https: ymlConfig.UBool("https", false),
password: ymlConfig.UString("password"),
port: ymlConfig.UInt("port", 9091),
url: ymlConfig.UString("url", "/transmission/"),
username: ymlConfig.UString("username", ""),
}
return &settings
}

View File

@ -0,0 +1,150 @@
package transmission
import (
"errors"
"github.com/hekmon/transmissionrpc"
"github.com/rivo/tview"
"github.com/wtfutil/wtf/wtf"
)
// Widget is the container for transmission data
type Widget struct {
wtf.KeyboardWidget
wtf.ScrollableWidget
client *transmissionrpc.Client
settings *Settings
torrents []*transmissionrpc.Torrent
}
// NewWidget creates a new instance of a widget
func NewWidget(app *tview.Application, pages *tview.Pages, settings *Settings) *Widget {
widget := Widget{
KeyboardWidget: wtf.NewKeyboardWidget(app, pages, settings.common),
ScrollableWidget: wtf.NewScrollableWidget(app, settings.common, true),
settings: settings,
}
widget.SetRenderFunction(widget.display)
widget.initializeKeyboardControls()
widget.View.SetInputCapture(widget.InputCapture)
widget.KeyboardWidget.SetView(widget.View)
// Create a persisten transmission client for use in the calls below
client, err := transmissionrpc.New(widget.settings.host, widget.settings.username, widget.settings.password, nil)
if err != nil {
client = nil
}
widget.client = client
return &widget
}
/* -------------------- Exported Functions -------------------- */
// Fetch retrieves torrent data from the Transmission daemon
func (widget *Widget) Fetch() ([]*transmissionrpc.Torrent, error) {
if widget.client == nil {
return nil, errors.New("client could not be initialized")
}
torrents, err := widget.client.TorrentGetAll()
if err != nil {
return nil, err
}
return torrents, nil
}
// Refresh updates the data for this widget and displays it onscreen
func (widget *Widget) Refresh() {
torrents, err := widget.Fetch()
if err != nil {
widget.SetItemCount(0)
widget.ScrollableWidget.Redraw(widget.CommonSettings.Title, err.Error(), false)
return
}
widget.torrents = torrents
widget.SetItemCount(len(torrents))
widget.display()
}
// HelpText returns the help text for this widget
func (widget *Widget) HelpText() string {
return widget.KeyboardWidget.HelpText()
}
// Next selects the next item in the list
func (widget *Widget) Next() {
widget.ScrollableWidget.Next()
}
// Prev selects the previous item in the list
func (widget *Widget) Prev() {
widget.ScrollableWidget.Prev()
}
// Unselect clears the selection of list items
func (widget *Widget) Unselect() {
widget.ScrollableWidget.Unselect()
widget.RenderFunction()
}
/* -------------------- Unexported Functions -------------------- */
func (widget *Widget) currentTorrent() *transmissionrpc.Torrent {
if len(widget.torrents) == 0 {
return nil
}
return widget.torrents[widget.Selected]
}
// deleteSelected removes the selected torrent from transmission
// This action is non-destructive, it does not delete the files on the host
func (widget *Widget) deleteSelectedTorrent() {
if widget.client == nil {
return
}
currTorrent := widget.currentTorrent()
if currTorrent == nil {
return
}
ids := []int64{*currTorrent.ID}
removePayload := &transmissionrpc.TorrentRemovePayload{
IDs: ids,
DeleteLocalData: false,
}
widget.client.TorrentRemove(removePayload)
}
// pauseUnpauseTorrent either pauses or unpauses the downloading and seeding of the selected torrent
func (widget *Widget) pauseUnpauseTorrent() {
if widget.client == nil {
return
}
currTorrent := widget.currentTorrent()
if currTorrent == nil {
return
}
ids := []int64{*currTorrent.ID}
if *currTorrent.Status == transmissionrpc.TorrentStatusStopped {
widget.client.TorrentStartIDs(ids)
} else {
widget.client.TorrentStopIDs(ids)
}
widget.display()
}

View File

@ -24,6 +24,7 @@ func NewScrollableWidget(app *tview.Application, commonSettings *cfg.Common, foc
widget.Unselect()
widget.View.SetScrollable(true)
widget.View.SetRegions(true)
return widget
}
@ -71,7 +72,6 @@ func (widget *ScrollableWidget) Unselect() {
}
func (widget *ScrollableWidget) Redraw(title, content string, wrap bool) {
widget.TextWidget.Redraw(title, content, wrap)
widget.app.QueueUpdateDraw(func() {
widget.View.Highlight(strconv.Itoa(widget.Selected)).ScrollToHighlight()