Compare commits
7 Commits
ed508bbf3b
...
d94843be9f
Author | SHA1 | Date | |
---|---|---|---|
d94843be9f | |||
89c6372f58 | |||
1a69cf8892 | |||
1a9a805c46 | |||
23aa53bde7 | |||
7be65b6a9a | |||
101e68372d |
@ -5,6 +5,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.quimbo.fr/odwrtw/canape/backend/models"
|
"git.quimbo.fr/odwrtw/canape/backend/models"
|
||||||
|
"git.quimbo.fr/odwrtw/canape/backend/web"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
@ -12,7 +13,6 @@ import (
|
|||||||
|
|
||||||
// Channel represents the channel of the user and the server
|
// Channel represents the channel of the user and the server
|
||||||
type Channel struct {
|
type Channel struct {
|
||||||
log *logrus.Entry
|
|
||||||
// Channel where the eventer will write messages to
|
// Channel where the eventer will write messages to
|
||||||
serverEventStream chan ServerEvent
|
serverEventStream chan ServerEvent
|
||||||
// Channel where the eventer will write errors to
|
// Channel where the eventer will write errors to
|
||||||
@ -31,7 +31,7 @@ type Channel struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// go routine writing events to the websocket connection
|
// go routine writing events to the websocket connection
|
||||||
func (c *Channel) writer() {
|
func (c *Channel) writer(env *web.Env) {
|
||||||
// Create the ping timer that will ping the client every pingWait seconds
|
// Create the ping timer that will ping the client every pingWait seconds
|
||||||
// to check that he's still listening
|
// to check that he's still listening
|
||||||
pingTicker := time.NewTicker(pingWait)
|
pingTicker := time.NewTicker(pingWait)
|
||||||
@ -46,30 +46,30 @@ func (c *Channel) writer() {
|
|||||||
case <-pingTicker.C:
|
case <-pingTicker.C:
|
||||||
_ = c.conn.SetWriteDeadline(time.Now().Add(writeWait))
|
_ = c.conn.SetWriteDeadline(time.Now().Add(writeWait))
|
||||||
if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
|
if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
|
||||||
c.log.Warnf("error writing message: %s", err)
|
env.Log.Warnf("error writing message: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case e := <-c.serverEventStream:
|
case e := <-c.serverEventStream:
|
||||||
_ = c.conn.SetWriteDeadline(time.Now().Add(writeWait))
|
_ = c.conn.SetWriteDeadline(time.Now().Add(writeWait))
|
||||||
if err := c.conn.WriteJSON(e); err != nil {
|
if err := c.conn.WriteJSON(e); err != nil {
|
||||||
c.log.Warnf("error writing JSON message: %s", err)
|
env.Log.Warnf("error writing JSON message: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case err := <-c.serverErrorStream:
|
case err := <-c.serverErrorStream:
|
||||||
_ = c.conn.SetWriteDeadline(time.Now().Add(writeWait))
|
_ = c.conn.SetWriteDeadline(time.Now().Add(writeWait))
|
||||||
if err := c.conn.WriteJSON(err); err != nil {
|
if err := c.conn.WriteJSON(err); err != nil {
|
||||||
c.log.Warnf("error writing JSON error: %s", err)
|
env.Log.Warnf("error writing JSON error: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case <-c.done:
|
case <-c.done:
|
||||||
c.log.Debug("all done finished")
|
env.Log.Debug("all done finished")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// go routine reading messages from the websocket connection
|
// go routine reading messages from the websocket connection
|
||||||
func (c *Channel) reader() {
|
func (c *Channel) reader(env *web.Env) {
|
||||||
// Read loop
|
// Read loop
|
||||||
_ = c.conn.SetReadDeadline(time.Now().Add(pongWait))
|
_ = c.conn.SetReadDeadline(time.Now().Add(pongWait))
|
||||||
for {
|
for {
|
||||||
@ -79,19 +79,19 @@ func (c *Channel) reader() {
|
|||||||
if err := c.conn.ReadJSON(&msg); err != nil {
|
if err := c.conn.ReadJSON(&msg); err != nil {
|
||||||
switch e := err.(type) {
|
switch e := err.(type) {
|
||||||
case *websocket.CloseError:
|
case *websocket.CloseError:
|
||||||
c.log.Info("close error")
|
env.Log.Info("close error")
|
||||||
case net.Error:
|
case net.Error:
|
||||||
if e.Timeout() {
|
if e.Timeout() {
|
||||||
c.log.WithField(
|
env.Log.WithField(
|
||||||
"error", err,
|
"error", err,
|
||||||
).Warn("timeout")
|
).Warn("timeout")
|
||||||
} else {
|
} else {
|
||||||
c.log.WithField(
|
env.Log.WithField(
|
||||||
"error", err,
|
"error", err,
|
||||||
).Warn("unknown net error")
|
).Warn("unknown net error")
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
c.log.WithField(
|
env.Log.WithField(
|
||||||
"error", err,
|
"error", err,
|
||||||
).Warn("unknown error reading message")
|
).Warn("unknown error reading message")
|
||||||
}
|
}
|
||||||
@ -104,31 +104,31 @@ func (c *Channel) reader() {
|
|||||||
|
|
||||||
e, ok := Eventers[msg.Message]
|
e, ok := Eventers[msg.Message]
|
||||||
if !ok {
|
if !ok {
|
||||||
c.log.Warnf("no such event to subscribe %q", msg.Message)
|
env.Log.Warnf("no such event to subscribe %q", msg.Message)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
switch msg.Type {
|
switch msg.Type {
|
||||||
case "subscribe":
|
case "subscribe":
|
||||||
c.log.Debugf("subscribe to %s", msg.Message)
|
env.Log.Debugf("subscribe to %s", msg.Message)
|
||||||
if _, ok := c.Events[e.Name]; ok {
|
if _, ok := c.Events[e.Name]; ok {
|
||||||
c.log.Infof("user %s is already subscribed to %s", c.User.Name, e.Name)
|
env.Log.Infof("user %s is already subscribed to %s", c.User.Name, e.Name)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := e.Subscribe(c); err != nil {
|
if err := e.Subscribe(c); err != nil {
|
||||||
c.Error(e.Name, err)
|
c.Error(e.Name, err, env.Log)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
c.Events[e.Name] = struct{}{}
|
c.Events[e.Name] = struct{}{}
|
||||||
case "unsubscribe":
|
case "unsubscribe":
|
||||||
c.log.Debugf("unsubscribe from %s", msg.Message)
|
env.Log.Debugf("unsubscribe from %s", msg.Message)
|
||||||
if _, ok := c.Events[e.Name]; !ok {
|
if _, ok := c.Events[e.Name]; !ok {
|
||||||
c.log.Infof("user %s is not subscribed to %s", c.User.Name, e.Name)
|
env.Log.Infof("user %s is not subscribed to %s", c.User.Name, e.Name)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
e.Unsubscribe(c)
|
e.Unsubscribe(c)
|
||||||
default:
|
default:
|
||||||
c.log.Warnf("invalid type: %s", msg.Type)
|
env.Log.Warnf("invalid type: %s", msg.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -147,11 +147,11 @@ func (c *Channel) close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Error sends an error into the errorStream channel
|
// Error sends an error into the errorStream channel
|
||||||
func (c *Channel) Error(name string, err error) {
|
func (c *Channel) Error(name string, err error, log *logrus.Entry) {
|
||||||
if c.closed {
|
if c.closed {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.log.WithField("name", name).Warn(err)
|
log.WithField("name", name).Warn(err)
|
||||||
c.serverErrorStream <- ServerError{
|
c.serverErrorStream <- ServerError{
|
||||||
Event: Event{
|
Event: Event{
|
||||||
Type: name,
|
Type: name,
|
||||||
@ -165,11 +165,11 @@ func (c *Channel) Error(name string, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FatalError sends an error into the errorStream channel
|
// FatalError sends an error into the errorStream channel
|
||||||
func (c *Channel) FatalError(name string, err error) {
|
func (c *Channel) FatalError(name string, err error, log *logrus.Entry) {
|
||||||
if c.closed {
|
if c.closed {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.log.WithField("name", name).Warn(err)
|
log.WithField("name", name).Warn(err)
|
||||||
c.serverErrorStream <- ServerError{
|
c.serverErrorStream <- ServerError{
|
||||||
Event: Event{
|
Event: Event{
|
||||||
Type: name,
|
Type: name,
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.quimbo.fr/odwrtw/canape/backend/auth"
|
"git.quimbo.fr/odwrtw/canape/backend/auth"
|
||||||
@ -22,14 +23,24 @@ const (
|
|||||||
writeWait = 30 * time.Second
|
writeWait = 30 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
// Eventers is a map of all the available Eventers
|
// Eventers is a global map of all the available Eventers
|
||||||
var Eventers = map[string]*PolochonEventers{
|
var Eventers map[string]*PolochonEventers
|
||||||
torrentEventName: NewTorrentEventers(),
|
var once sync.Once
|
||||||
videoEventName: NewVideoEventers(),
|
|
||||||
|
func initEventers(env *web.Env) {
|
||||||
|
once.Do(func() {
|
||||||
|
env.Log.Infof("Initialising eventers")
|
||||||
|
Eventers = map[string]*PolochonEventers{
|
||||||
|
torrentEventName: NewTorrentEventers(env),
|
||||||
|
videoEventName: NewVideoEventers(env),
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// WsHandler handles the websockets messages
|
// WsHandler handles the websockets messages
|
||||||
func WsHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
func WsHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||||
|
initEventers(env)
|
||||||
|
|
||||||
// Get the user
|
// Get the user
|
||||||
user := auth.GetCurrentUser(r, env.Log)
|
user := auth.GetCurrentUser(r, env.Log)
|
||||||
|
|
||||||
@ -67,7 +78,6 @@ func WsHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
|||||||
serverErrorStream: serverErrorStream,
|
serverErrorStream: serverErrorStream,
|
||||||
done: make(chan struct{}, 1),
|
done: make(chan struct{}, 1),
|
||||||
conn: ws,
|
conn: ws,
|
||||||
log: env.Log,
|
|
||||||
User: user,
|
User: user,
|
||||||
db: env.Database,
|
db: env.Database,
|
||||||
Events: map[string]struct{}{},
|
Events: map[string]struct{}{},
|
||||||
@ -76,9 +86,9 @@ func WsHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Launch the go routine responsible for writing events in the websocket
|
// Launch the go routine responsible for writing events in the websocket
|
||||||
go c.writer()
|
go c.writer(env)
|
||||||
// Launch the reader responsible for reading events from the websocket
|
// Launch the reader responsible for reading events from the websocket
|
||||||
c.reader()
|
c.reader(env)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -122,6 +132,7 @@ func PolochonHookHandler(env *web.Env, w http.ResponseWriter, r *http.Request) e
|
|||||||
|
|
||||||
// HookDebugHandler handles the websockets messages
|
// HookDebugHandler handles the websockets messages
|
||||||
func HookDebugHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
func HookDebugHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||||
|
initEventers(env)
|
||||||
debug := map[string]map[string][]*Channel{}
|
debug := map[string]map[string][]*Channel{}
|
||||||
|
|
||||||
for e, event := range Eventers {
|
for e, event := range Eventers {
|
||||||
|
@ -5,13 +5,13 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"git.quimbo.fr/odwrtw/canape/backend/models"
|
"git.quimbo.fr/odwrtw/canape/backend/models"
|
||||||
"github.com/sirupsen/logrus"
|
"git.quimbo.fr/odwrtw/canape/backend/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BaseEventer represents the basis of a Eventer
|
// BaseEventer represents the basis of a Eventer
|
||||||
type BaseEventer struct {
|
type BaseEventer struct {
|
||||||
|
env *web.Env
|
||||||
users []*Channel
|
users []*Channel
|
||||||
log *logrus.Entry
|
|
||||||
name string
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,22 +20,17 @@ type BaseEventer struct {
|
|||||||
// Eventer per polochon
|
// Eventer per polochon
|
||||||
type PolochonEventers struct {
|
type PolochonEventers struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
|
env *web.Env
|
||||||
Name string
|
Name string
|
||||||
log *logrus.Entry
|
|
||||||
polochons map[string]Eventer
|
polochons map[string]Eventer
|
||||||
NewEventer func(polo *models.Polochon, log *logrus.Entry) (Eventer, error)
|
NewEventer func(*web.Env, *models.Polochon) (Eventer, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEventers returns a new PolochonEventers
|
// NewEventers returns a new PolochonEventers
|
||||||
func NewEventers() *PolochonEventers {
|
func NewEventers(env *web.Env) *PolochonEventers {
|
||||||
// Setup the logger
|
|
||||||
logger := logrus.New()
|
|
||||||
logger.Formatter = &logrus.TextFormatter{FullTimestamp: true}
|
|
||||||
logger.Level = logrus.DebugLevel
|
|
||||||
|
|
||||||
return &PolochonEventers{
|
return &PolochonEventers{
|
||||||
|
env: env,
|
||||||
polochons: map[string]Eventer{},
|
polochons: map[string]Eventer{},
|
||||||
log: logrus.NewEntry(logger),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,7 +41,7 @@ func (p *PolochonEventers) Subscribe(chanl *Channel) error {
|
|||||||
p.Lock()
|
p.Lock()
|
||||||
defer p.Unlock()
|
defer p.Unlock()
|
||||||
|
|
||||||
p.log.Debugf("subscribing with the user %s", chanl.User.Name)
|
p.env.Log.Debugf("subscribing with the user %s", chanl.User.Name)
|
||||||
if !chanl.User.PolochonActivated {
|
if !chanl.User.PolochonActivated {
|
||||||
return fmt.Errorf("polochon not activated")
|
return fmt.Errorf("polochon not activated")
|
||||||
}
|
}
|
||||||
@ -55,7 +50,7 @@ func (p *PolochonEventers) Subscribe(chanl *Channel) error {
|
|||||||
// listening
|
// listening
|
||||||
tn, ok := p.polochons[chanl.User.PolochonID.String]
|
tn, ok := p.polochons[chanl.User.PolochonID.String]
|
||||||
if !ok {
|
if !ok {
|
||||||
p.log.Debugf("create new eventer for polochon %s", chanl.User.PolochonID.String)
|
p.env.Log.Debugf("create new eventer for polochon %s", chanl.User.PolochonID.String)
|
||||||
|
|
||||||
// Get the user's polochon
|
// Get the user's polochon
|
||||||
polo, err := chanl.User.GetPolochon(chanl.db)
|
polo, err := chanl.User.GetPolochon(chanl.db)
|
||||||
@ -64,7 +59,7 @@ func (p *PolochonEventers) Subscribe(chanl *Channel) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a new Eventer for this polochon
|
// Create a new Eventer for this polochon
|
||||||
tn, err = p.NewEventer(polo, p.log)
|
tn, err = p.NewEventer(p.env, polo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -82,7 +77,7 @@ func (p *PolochonEventers) Subscribe(chanl *Channel) error {
|
|||||||
// Add the Channel to the Eventer
|
// Add the Channel to the Eventer
|
||||||
tn.Append(chanl)
|
tn.Append(chanl)
|
||||||
|
|
||||||
p.log.Debugf("eventer created for polochon %s", chanl.User.PolochonID.String)
|
p.env.Log.Debugf("eventer created for polochon %s", chanl.User.PolochonID.String)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,21 +88,21 @@ func (p *PolochonEventers) Unsubscribe(chanl *Channel) {
|
|||||||
p.Lock()
|
p.Lock()
|
||||||
defer p.Unlock()
|
defer p.Unlock()
|
||||||
|
|
||||||
p.log.Debugf("unsubscribing from %s with %s", p.Name, chanl.User.Name)
|
p.env.Log.Debugf("unsubscribing from %s with %s", p.Name, chanl.User.Name)
|
||||||
|
|
||||||
tn, ok := p.polochons[chanl.User.PolochonID.String]
|
tn, ok := p.polochons[chanl.User.PolochonID.String]
|
||||||
if !ok {
|
if !ok {
|
||||||
p.log.Warnf("no eventer for polochon %s, not unsubscribing", chanl.User.PolochonID.String)
|
p.env.Log.Warnf("no eventer for polochon %s, not unsubscribing", chanl.User.PolochonID.String)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tn.Unsubscribe(chanl); err != nil {
|
if err := tn.Unsubscribe(chanl); err != nil {
|
||||||
p.log.Errorf("failed to unsubscribe eventer: %s", err.Error())
|
p.env.Log.Errorf("failed to unsubscribe eventer: %s", err.Error())
|
||||||
// TODO: check if we need to return here
|
// TODO: check if we need to return here
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(tn.Subscribers()) == 0 {
|
if len(tn.Subscribers()) == 0 {
|
||||||
p.log.Debugf("empty subscribers for this polochon, delete it")
|
p.env.Log.Debugf("empty subscribers for this polochon, delete it")
|
||||||
tn.Finish()
|
tn.Finish()
|
||||||
// Delete the polochon from the Eventer when it's finished
|
// Delete the polochon from the Eventer when it's finished
|
||||||
delete(p.polochons, chanl.User.PolochonID.String)
|
delete(p.polochons, chanl.User.PolochonID.String)
|
||||||
@ -133,7 +128,7 @@ func (e *BaseEventer) Unsubscribe(chanl *Channel) error {
|
|||||||
i++
|
i++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
e.log.Debugf("found the user channel %s for user %s, deleting it...", chanl.ID, chanl.User.Name)
|
e.env.Log.Debugf("found the user channel %s for user %s, deleting it...", chanl.ID, chanl.User.Name)
|
||||||
|
|
||||||
// Delete this event from the list of events the channel is subscribed
|
// Delete this event from the list of events the channel is subscribed
|
||||||
delete(e.users[i].Events, e.name)
|
delete(e.users[i].Events, e.name)
|
||||||
@ -162,7 +157,7 @@ func (e *BaseEventer) Unsubscribe(chanl *Channel) error {
|
|||||||
func (e *BaseEventer) FatalError(err error) {
|
func (e *BaseEventer) FatalError(err error) {
|
||||||
for _, chanl := range e.users {
|
for _, chanl := range e.users {
|
||||||
// Send the error
|
// Send the error
|
||||||
chanl.FatalError(e.name, err)
|
chanl.FatalError(e.name, err, e.env.Log)
|
||||||
// Delete the event from the channel events
|
// Delete the event from the channel events
|
||||||
delete(chanl.Events, e.name)
|
delete(chanl.Events, e.name)
|
||||||
}
|
}
|
||||||
@ -191,7 +186,7 @@ func (e *BaseEventer) NotifyAll(data interface{}) {
|
|||||||
|
|
||||||
// Send the events to all the subscribed users
|
// Send the events to all the subscribed users
|
||||||
for _, chanl := range e.users {
|
for _, chanl := range e.users {
|
||||||
e.log.Debugf("sending event to %s", chanl.User.Name)
|
e.env.Log.Debugf("sending event to %s", chanl.User.Name)
|
||||||
chanl.sendEvent(event)
|
chanl.sendEvent(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.quimbo.fr/odwrtw/canape/backend/models"
|
"git.quimbo.fr/odwrtw/canape/backend/models"
|
||||||
|
"git.quimbo.fr/odwrtw/canape/backend/web"
|
||||||
"github.com/odwrtw/papi"
|
"github.com/odwrtw/papi"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TorrentEventer represents the Eventer for torrents
|
// TorrentEventer represents the Eventer for torrents
|
||||||
@ -15,21 +15,24 @@ type TorrentEventer struct {
|
|||||||
*BaseEventer
|
*BaseEventer
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
pClient *papi.Client
|
pClient *papi.Client
|
||||||
torrents []papi.Torrent
|
// previous keep the previous data
|
||||||
|
previous []*papi.Torrent
|
||||||
|
// data holds the computed data
|
||||||
|
data []*models.TorrentVideo
|
||||||
}
|
}
|
||||||
|
|
||||||
var torrentEventName = "torrents"
|
var torrentEventName = "torrents"
|
||||||
|
|
||||||
// NewTorrentEventers returns a new PolochonEventers for torrents
|
// NewTorrentEventers returns a new PolochonEventers for torrents
|
||||||
func NewTorrentEventers() *PolochonEventers {
|
func NewTorrentEventers(env *web.Env) *PolochonEventers {
|
||||||
eventer := NewEventers()
|
eventer := NewEventers(env)
|
||||||
eventer.NewEventer = NewTorrentEventer
|
eventer.NewEventer = NewTorrentEventer
|
||||||
eventer.Name = torrentEventName
|
eventer.Name = torrentEventName
|
||||||
return eventer
|
return eventer
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTorrentEventer returns a new Eventer for a specific Polochon
|
// NewTorrentEventer returns a new Eventer for a specific Polochon
|
||||||
func NewTorrentEventer(polo *models.Polochon, log *logrus.Entry) (Eventer, error) {
|
func NewTorrentEventer(env *web.Env, polo *models.Polochon) (Eventer, error) {
|
||||||
// Create a new papi client
|
// Create a new papi client
|
||||||
client, err := polo.NewPapiClient()
|
client, err := polo.NewPapiClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -39,13 +42,12 @@ func NewTorrentEventer(polo *models.Polochon, log *logrus.Entry) (Eventer, error
|
|||||||
// This is the first time this polochon is requested, create the TorrentEventer
|
// This is the first time this polochon is requested, create the TorrentEventer
|
||||||
tn := &TorrentEventer{
|
tn := &TorrentEventer{
|
||||||
BaseEventer: &BaseEventer{
|
BaseEventer: &BaseEventer{
|
||||||
|
env: env,
|
||||||
users: []*Channel{},
|
users: []*Channel{},
|
||||||
log: log,
|
|
||||||
name: torrentEventName,
|
name: torrentEventName,
|
||||||
},
|
},
|
||||||
pClient: client,
|
pClient: client,
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
torrents: []papi.Torrent{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return tn, nil
|
return tn, nil
|
||||||
@ -60,7 +62,7 @@ func (t *TorrentEventer) Append(chanl *Channel) {
|
|||||||
Type: torrentEventName,
|
Type: torrentEventName,
|
||||||
Status: OK,
|
Status: OK,
|
||||||
},
|
},
|
||||||
Data: t.torrents,
|
Data: t.data,
|
||||||
}
|
}
|
||||||
|
|
||||||
chanl.sendEvent(event)
|
chanl.sendEvent(event)
|
||||||
@ -77,7 +79,7 @@ func (t *TorrentEventer) Launch() error {
|
|||||||
|
|
||||||
err := t.torrentsUpdate()
|
err := t.torrentsUpdate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.log.Warnf("got error getting torrents: %s", err)
|
t.env.Log.Warnf("got error getting torrents: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@ -85,10 +87,10 @@ func (t *TorrentEventer) Launch() error {
|
|||||||
case <-timeTicker.C:
|
case <-timeTicker.C:
|
||||||
err := t.torrentsUpdate()
|
err := t.torrentsUpdate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.log.Warnf("got error getting torrents: %s", err)
|
t.env.Log.Warnf("got error getting torrents: %s", err)
|
||||||
}
|
}
|
||||||
case <-t.done:
|
case <-t.done:
|
||||||
t.log.Debug("quit torrent notifier")
|
t.env.Log.Debug("quit torrent notifier")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,21 +98,23 @@ func (t *TorrentEventer) Launch() error {
|
|||||||
|
|
||||||
// torrentsUpdate sends to the eventStream if torrents change
|
// torrentsUpdate sends to the eventStream if torrents change
|
||||||
func (t *TorrentEventer) torrentsUpdate() error {
|
func (t *TorrentEventer) torrentsUpdate() error {
|
||||||
// Get torrents
|
|
||||||
torrents, err := t.pClient.GetTorrents()
|
torrents, err := t.pClient.GetTorrents()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if reflect.DeepEqual(t.torrents, torrents) {
|
if reflect.DeepEqual(t.previous, torrents) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
t.log.Debugf("torrents have changed!")
|
t.env.Log.Debugf("torrents have changed!")
|
||||||
|
|
||||||
t.NotifyAll(torrents)
|
data := models.NewTorrentVideos(t.env.Backend.Detailer, t.env.Database, t.env.Log, torrents)
|
||||||
|
|
||||||
t.torrents = torrents
|
t.NotifyAll(data)
|
||||||
|
|
||||||
|
t.previous = torrents
|
||||||
|
t.data = data
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -118,6 +122,6 @@ func (t *TorrentEventer) torrentsUpdate() error {
|
|||||||
// Finish implements the Eventer interface
|
// Finish implements the Eventer interface
|
||||||
// It is called when there is no more users subscribed
|
// It is called when there is no more users subscribed
|
||||||
func (t *TorrentEventer) Finish() {
|
func (t *TorrentEventer) Finish() {
|
||||||
t.log.Debugf("sending the done channel")
|
t.env.Log.Debugf("sending the done channel")
|
||||||
t.done <- struct{}{}
|
t.done <- struct{}{}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ package events
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"git.quimbo.fr/odwrtw/canape/backend/models"
|
"git.quimbo.fr/odwrtw/canape/backend/models"
|
||||||
"github.com/sirupsen/logrus"
|
"git.quimbo.fr/odwrtw/canape/backend/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
// VideoEventer represents the Eventer for tests
|
// VideoEventer represents the Eventer for tests
|
||||||
@ -13,19 +13,19 @@ type VideoEventer struct {
|
|||||||
var videoEventName = "newVideo"
|
var videoEventName = "newVideo"
|
||||||
|
|
||||||
// NewVideoEventers implements the Eventer interface
|
// NewVideoEventers implements the Eventer interface
|
||||||
func NewVideoEventers() *PolochonEventers {
|
func NewVideoEventers(env *web.Env) *PolochonEventers {
|
||||||
eventer := NewEventers()
|
eventer := NewEventers(env)
|
||||||
eventer.NewEventer = NewVideoEventer
|
eventer.NewEventer = NewVideoEventer
|
||||||
eventer.Name = videoEventName
|
eventer.Name = videoEventName
|
||||||
return eventer
|
return eventer
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewVideoEventer returns a new Eventer
|
// NewVideoEventer returns a new Eventer
|
||||||
func NewVideoEventer(polo *models.Polochon, log *logrus.Entry) (Eventer, error) {
|
func NewVideoEventer(env *web.Env, polo *models.Polochon) (Eventer, error) {
|
||||||
return &VideoEventer{
|
return &VideoEventer{
|
||||||
BaseEventer: &BaseEventer{
|
BaseEventer: &BaseEventer{
|
||||||
|
env: env,
|
||||||
users: []*Channel{},
|
users: []*Channel{},
|
||||||
log: log,
|
|
||||||
name: videoEventName,
|
name: videoEventName,
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -196,7 +196,7 @@ func RefreshMovies(env *web.Env) {
|
|||||||
|
|
||||||
// Iterate over the map of movies to refresh them
|
// Iterate over the map of movies to refresh them
|
||||||
for id := range movieMap {
|
for id := range movieMap {
|
||||||
movie := movies.New(id, nil, nil, false, env.Config.PublicDir, env.Config.ImgURLPrefix)
|
movie := movies.New(env, id, nil, nil, false)
|
||||||
// Refresh the movie
|
// Refresh the movie
|
||||||
err := movie.Refresh(env, env.Config.Movie.Detailers)
|
err := movie.Refresh(env, env.Config.Movie.Detailers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -80,7 +80,7 @@ func GetMovies(env *web.Env, user *models.User, source string, category string)
|
|||||||
// Fill all the movies infos from the list of IDs
|
// Fill all the movies infos from the list of IDs
|
||||||
for _, id := range media.IDs {
|
for _, id := range media.IDs {
|
||||||
pMovie, _ := pMovies.Has(id)
|
pMovie, _ := pMovies.Has(id)
|
||||||
movie := movies.New(id, client, pMovie, moviesWishlist.IsMovieInWishlist(id), env.Config.PublicDir, env.Config.ImgURLPrefix)
|
movie := movies.New(env, id, client, pMovie, moviesWishlist.IsMovieInWishlist(id))
|
||||||
// First check in the DB
|
// First check in the DB
|
||||||
before := []polochon.Detailer{env.Backend.Detailer}
|
before := []polochon.Detailer{env.Backend.Detailer}
|
||||||
// Then with the default detailers
|
// Then with the default detailers
|
||||||
|
@ -49,7 +49,11 @@ func run() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
backend := &models.Backend{Database: db}
|
backend := &models.Backend{
|
||||||
|
Database: db,
|
||||||
|
}
|
||||||
|
models.SetPublicDir(cf.PublicDir)
|
||||||
|
models.SetImgURLPrefix(cf.ImgURLPrefix)
|
||||||
|
|
||||||
// Generate auth params
|
// Generate auth params
|
||||||
authParams := auth.Params{
|
authParams := auth.Params{
|
||||||
|
@ -47,6 +47,7 @@ func (b *Backend) GetShowDetails(pShow *polochon.Show, log *logrus.Entry) error
|
|||||||
log.Warnf("error while getting episodes: %s", err)
|
log.Warnf("error while getting episodes: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package models
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
@ -25,8 +26,12 @@ const (
|
|||||||
FROM episodes WHERE show_imdb_id=$1;`
|
FROM episodes WHERE show_imdb_id=$1;`
|
||||||
|
|
||||||
getEpisodeQuery = `
|
getEpisodeQuery = `
|
||||||
SELECT *
|
SELECT s.title show_title, e.*
|
||||||
FROM episodes WHERE show_imdb_id=$1 AND season=$2 AND episode=$3;`
|
FROM shows s , episodes e
|
||||||
|
WHERE s.imdb_id = e.show_imdb_id
|
||||||
|
AND e.show_imdb_id=$1
|
||||||
|
AND e.season=$2
|
||||||
|
AND e.episode=$3;`
|
||||||
)
|
)
|
||||||
|
|
||||||
// episodeDB represents the Episode in the DB
|
// episodeDB represents the Episode in the DB
|
||||||
@ -36,6 +41,7 @@ type episodeDB struct {
|
|||||||
ImdbID string `db:"imdb_id"`
|
ImdbID string `db:"imdb_id"`
|
||||||
ShowImdbID string `db:"show_imdb_id"`
|
ShowImdbID string `db:"show_imdb_id"`
|
||||||
ShowTvdbID int `db:"show_tvdb_id"`
|
ShowTvdbID int `db:"show_tvdb_id"`
|
||||||
|
ShowTitle string `db:"show_title"`
|
||||||
Season int `db:"season"`
|
Season int `db:"season"`
|
||||||
Episode int `db:"episode"`
|
Episode int `db:"episode"`
|
||||||
Title string `db:"title"`
|
Title string `db:"title"`
|
||||||
@ -70,6 +76,7 @@ func FillEpisodeFromDB(eDB *episodeDB, pEpisode *polochon.ShowEpisode) {
|
|||||||
// Keep the data that never changes but only if we have it
|
// Keep the data that never changes but only if we have it
|
||||||
updateIfNonEmpty(&pEpisode.EpisodeImdbID, eDB.ImdbID)
|
updateIfNonEmpty(&pEpisode.EpisodeImdbID, eDB.ImdbID)
|
||||||
updateIfNonEmpty(&pEpisode.ShowImdbID, eDB.ShowImdbID)
|
updateIfNonEmpty(&pEpisode.ShowImdbID, eDB.ShowImdbID)
|
||||||
|
updateIfNonEmpty(&pEpisode.ShowTitle, eDB.ShowTitle)
|
||||||
updateIfNonZeroInt(&pEpisode.TvdbID, eDB.TvdbID)
|
updateIfNonZeroInt(&pEpisode.TvdbID, eDB.TvdbID)
|
||||||
updateIfNonZeroInt(&pEpisode.ShowTvdbID, eDB.ShowTvdbID)
|
updateIfNonZeroInt(&pEpisode.ShowTvdbID, eDB.ShowTvdbID)
|
||||||
pEpisode.Season = eDB.Season
|
pEpisode.Season = eDB.Season
|
||||||
@ -80,6 +87,12 @@ func FillEpisodeFromDB(eDB *episodeDB, pEpisode *polochon.ShowEpisode) {
|
|||||||
pEpisode.Thumb = eDB.Thumb
|
pEpisode.Thumb = eDB.Thumb
|
||||||
pEpisode.Runtime = eDB.Runtime
|
pEpisode.Runtime = eDB.Runtime
|
||||||
pEpisode.Aired = eDB.Aired
|
pEpisode.Aired = eDB.Aired
|
||||||
|
pEpisode.Thumb = imageURL(fmt.Sprintf(
|
||||||
|
"shows/%s/%d-%d.jpg",
|
||||||
|
eDB.ShowImdbID,
|
||||||
|
eDB.Season,
|
||||||
|
eDB.Episode,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetEpisode gets an episode and fills the polochon episode
|
// GetEpisode gets an episode and fills the polochon episode
|
||||||
|
30
backend/models/global.go
Normal file
30
backend/models/global.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Public gobal variables
|
||||||
|
var (
|
||||||
|
PublicDir string
|
||||||
|
ImgURLPrefix string
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetPublicDir sets the public dir
|
||||||
|
func SetPublicDir(s string) {
|
||||||
|
PublicDir = s
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetImgURLPrefix sets the img url prefix
|
||||||
|
func SetImgURLPrefix(s string) {
|
||||||
|
ImgURLPrefix = s
|
||||||
|
}
|
||||||
|
|
||||||
|
func imageURL(suffix string) string {
|
||||||
|
imgFile := filepath.Join(PublicDir, "img", suffix)
|
||||||
|
if _, err := os.Stat(imgFile); !os.IsNotExist(err) {
|
||||||
|
return ImgURLPrefix + suffix
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
@ -89,6 +89,7 @@ func FillMovieFromDB(mDB *movieDB, pMovie *polochon.Movie) {
|
|||||||
pMovie.Genres = mDB.Genres
|
pMovie.Genres = mDB.Genres
|
||||||
pMovie.SortTitle = mDB.SortTitle
|
pMovie.SortTitle = mDB.SortTitle
|
||||||
pMovie.Tagline = mDB.Tagline
|
pMovie.Tagline = mDB.Tagline
|
||||||
|
pMovie.Thumb = imageURL("movies/" + mDB.ImdbID + ".jpg")
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateFromMovie will update the movieDB from a Movie
|
// updateFromMovie will update the movieDB from a Movie
|
||||||
|
@ -42,7 +42,7 @@ type showDB struct {
|
|||||||
|
|
||||||
// UpsertShow a show in the database
|
// UpsertShow a show in the database
|
||||||
func UpsertShow(db *sqlx.DB, s *polochon.Show) error {
|
func UpsertShow(db *sqlx.DB, s *polochon.Show) error {
|
||||||
sDB := NewShowFromPolochon(s)
|
sDB := newShowFromPolochon(s)
|
||||||
// Upsert the show
|
// Upsert the show
|
||||||
r, err := db.NamedQuery(upsertShowQuery, sDB)
|
r, err := db.NamedQuery(upsertShowQuery, sDB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -60,8 +60,8 @@ func UpsertShow(db *sqlx.DB, s *polochon.Show) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewShowFromPolochon returns an showDB from a polochon Show
|
// newShowFromPolochon returns an showDB from a polochon Show
|
||||||
func NewShowFromPolochon(s *polochon.Show) *showDB {
|
func newShowFromPolochon(s *polochon.Show) *showDB {
|
||||||
sDB := showDB{
|
sDB := showDB{
|
||||||
ImdbID: s.ImdbID,
|
ImdbID: s.ImdbID,
|
||||||
TvdbID: s.TvdbID,
|
TvdbID: s.TvdbID,
|
||||||
@ -103,4 +103,7 @@ func FillShowFromDB(sDB *showDB, pShow *polochon.Show) {
|
|||||||
pShow.TvdbID = sDB.TvdbID
|
pShow.TvdbID = sDB.TvdbID
|
||||||
pShow.Year = sDB.Year
|
pShow.Year = sDB.Year
|
||||||
pShow.FirstAired = &sDB.FirstAired
|
pShow.FirstAired = &sDB.FirstAired
|
||||||
|
pShow.Banner = imageURL("shows/" + sDB.ImdbID + "/banner.jpg")
|
||||||
|
pShow.Fanart = imageURL("shows/" + sDB.ImdbID + "/fanart.jpg")
|
||||||
|
pShow.Poster = imageURL("shows/" + sDB.ImdbID + "/poster.jpg")
|
||||||
}
|
}
|
||||||
|
68
backend/models/torrent_video.go
Normal file
68
backend/models/torrent_video.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/odwrtw/papi"
|
||||||
|
polochon "github.com/odwrtw/polochon/lib"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TorrentVideo reprensents a torrent embeding the video inforamtions
|
||||||
|
type TorrentVideo struct {
|
||||||
|
*papi.Torrent
|
||||||
|
Img string `json:"img"`
|
||||||
|
Video polochon.Video `json:"video,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTorrentVideo returns a new TorrentVideo
|
||||||
|
func NewTorrentVideo(t *papi.Torrent) *TorrentVideo {
|
||||||
|
torrent := &polochon.Torrent{
|
||||||
|
ImdbID: t.ImdbID,
|
||||||
|
Type: polochon.VideoType(t.Type),
|
||||||
|
Quality: polochon.Quality(t.Quality),
|
||||||
|
Season: t.Season,
|
||||||
|
Episode: t.Episode,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TorrentVideo{
|
||||||
|
Torrent: t,
|
||||||
|
Video: torrent.Video(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update updates the Torrent video with the database details
|
||||||
|
func (t *TorrentVideo) Update(detailer polochon.Detailer, db *sqlx.DB, log *logrus.Entry) {
|
||||||
|
if t.Video == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: refresh the video in db if not found
|
||||||
|
err := detailer.GetDetails(t.Video, log)
|
||||||
|
if err != nil {
|
||||||
|
log.WithField("function", "TorrentVideo.Update").Errorf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := t.Video.(type) {
|
||||||
|
case *polochon.ShowEpisode:
|
||||||
|
if v.Show != nil {
|
||||||
|
if err := GetShow(db, v.Show); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.Img = v.Show.Poster
|
||||||
|
v.Show = nil
|
||||||
|
}
|
||||||
|
case *polochon.Movie:
|
||||||
|
t.Img = v.Thumb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTorrentVideos returns a new slice of TorrentVideo from papi torrents
|
||||||
|
func NewTorrentVideos(detailer polochon.Detailer, db *sqlx.DB, log *logrus.Entry, torrents []*papi.Torrent) []*TorrentVideo {
|
||||||
|
tv := make([]*TorrentVideo, len(torrents))
|
||||||
|
for i := range torrents {
|
||||||
|
tv[i] = NewTorrentVideo(torrents[i])
|
||||||
|
tv[i].Update(detailer, db, log)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tv
|
||||||
|
}
|
@ -76,7 +76,7 @@ func RefreshMovieHandler(env *web.Env, w http.ResponseWriter, r *http.Request) e
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a new movie
|
// Create a new movie
|
||||||
m := New(id, client, pMovie, isWishlisted, env.Config.PublicDir, env.Config.ImgURLPrefix)
|
m := New(env, id, client, pMovie, isWishlisted)
|
||||||
|
|
||||||
// Refresh the movie's infos
|
// Refresh the movie's infos
|
||||||
if err := m.Refresh(env, env.Config.Movie.Detailers); err != nil {
|
if err := m.Refresh(env, env.Config.Movie.Detailers); err != nil {
|
||||||
@ -141,12 +141,11 @@ func SearchMovie(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
|||||||
for _, m := range movies {
|
for _, m := range movies {
|
||||||
pMovie, _ := pMovies.Has(m.ImdbID)
|
pMovie, _ := pMovies.Has(m.ImdbID)
|
||||||
movie := New(
|
movie := New(
|
||||||
|
env,
|
||||||
m.ImdbID,
|
m.ImdbID,
|
||||||
client,
|
client,
|
||||||
pMovie,
|
pMovie,
|
||||||
moviesWishlist.IsMovieInWishlist(m.ImdbID),
|
moviesWishlist.IsMovieInWishlist(m.ImdbID),
|
||||||
env.Config.PublicDir,
|
|
||||||
env.Config.ImgURLPrefix,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// First check in the DB
|
// First check in the DB
|
||||||
@ -252,12 +251,11 @@ func GetWishlistHandler(env *web.Env, w http.ResponseWriter, r *http.Request) er
|
|||||||
for _, imdbID := range moviesWishlist.List() {
|
for _, imdbID := range moviesWishlist.List() {
|
||||||
pMovie, _ := pMovies.Has(imdbID)
|
pMovie, _ := pMovies.Has(imdbID)
|
||||||
movie := New(
|
movie := New(
|
||||||
|
env,
|
||||||
imdbID,
|
imdbID,
|
||||||
client,
|
client,
|
||||||
pMovie,
|
pMovie,
|
||||||
moviesWishlist.IsMovieInWishlist(imdbID),
|
moviesWishlist.IsMovieInWishlist(imdbID),
|
||||||
env.Config.PublicDir,
|
|
||||||
env.Config.ImgURLPrefix,
|
|
||||||
)
|
)
|
||||||
// First check in the DB
|
// First check in the DB
|
||||||
before := []polochon.Detailer{env.Backend.Detailer}
|
before := []polochon.Detailer{env.Backend.Detailer}
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -44,7 +43,8 @@ func (m *Movie) MarshalJSON() ([]byte, error) {
|
|||||||
Container string `json:"container"`
|
Container string `json:"container"`
|
||||||
}{
|
}{
|
||||||
Alias: (*Alias)(m),
|
Alias: (*Alias)(m),
|
||||||
PosterURL: m.PosterURL(),
|
// TODO: remove this field to use m.Thumb
|
||||||
|
PosterURL: m.Thumb,
|
||||||
Subtitles: []subtitles.Subtitle{},
|
Subtitles: []subtitles.Subtitle{},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,12 +74,12 @@ func (m *Movie) MarshalJSON() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new Movie with all the needed infos
|
// New returns a new Movie with all the needed infos
|
||||||
func New(imdbID string, client *papi.Client, pMovie *papi.Movie, isWishlisted bool, publicDir, imgURLPrefix string) *Movie {
|
func New(env *web.Env, imdbID string, client *papi.Client, pMovie *papi.Movie, isWishlisted bool) *Movie {
|
||||||
return &Movie{
|
return &Movie{
|
||||||
client: client,
|
client: client,
|
||||||
pMovie: pMovie,
|
pMovie: pMovie,
|
||||||
publicDir: publicDir,
|
publicDir: env.Config.PublicDir,
|
||||||
imgURLPrefix: imgURLPrefix,
|
imgURLPrefix: env.Config.ImgURLPrefix,
|
||||||
Wishlisted: isWishlisted,
|
Wishlisted: isWishlisted,
|
||||||
Movie: &polochon.Movie{
|
Movie: &polochon.Movie{
|
||||||
ImdbID: imdbID,
|
ImdbID: imdbID,
|
||||||
@ -115,7 +115,7 @@ func (m *Movie) GetDetails(env *web.Env, detailers []polochon.Detailer) error {
|
|||||||
// If found, return
|
// If found, return
|
||||||
// If not, retrives details with the detailers 'after' and update them in
|
// If not, retrives details with the detailers 'after' and update them in
|
||||||
// database
|
// database
|
||||||
func (m *Movie) GetAndFetch(env *web.Env, before []polochon.Detailer, after []polochon.Detailer) error {
|
func (m *Movie) GetAndFetch(env *web.Env, before, after []polochon.Detailer) error {
|
||||||
log := env.Log.WithFields(logrus.Fields{
|
log := env.Log.WithFields(logrus.Fields{
|
||||||
"imdb_id": m.ImdbID,
|
"imdb_id": m.ImdbID,
|
||||||
"function": "movies.GetAndFetch",
|
"function": "movies.GetAndFetch",
|
||||||
@ -182,7 +182,7 @@ func (m *Movie) GetTorrents(env *web.Env, torrenters []polochon.Torrenter) error
|
|||||||
// If found, return
|
// If found, return
|
||||||
// If not, retrives torrents with the torrenters 'after' and update them in
|
// If not, retrives torrents with the torrenters 'after' and update them in
|
||||||
// database
|
// database
|
||||||
func (m *Movie) GetAndFetchTorrents(env *web.Env, before []polochon.Torrenter, after []polochon.Torrenter) error {
|
func (m *Movie) GetAndFetchTorrents(env *web.Env, before, after []polochon.Torrenter) error {
|
||||||
log := env.Log.WithFields(logrus.Fields{
|
log := env.Log.WithFields(logrus.Fields{
|
||||||
"imdb_id": m.ImdbID,
|
"imdb_id": m.ImdbID,
|
||||||
"function": "movies.GetAndFetchTorrents",
|
"function": "movies.GetAndFetchTorrents",
|
||||||
@ -244,15 +244,6 @@ func (m *Movie) imgFile() string {
|
|||||||
return filepath.Join(m.publicDir, "img", m.imgURL())
|
return filepath.Join(m.publicDir, "img", m.imgURL())
|
||||||
}
|
}
|
||||||
|
|
||||||
// PosterURL returns the image URL or the default image if the poster is not yet downloaded
|
|
||||||
func (m *Movie) PosterURL() string {
|
|
||||||
// Check if the movie image exists
|
|
||||||
if _, err := os.Stat(m.imgFile()); os.IsNotExist(err) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return m.imgURLPrefix + m.imgURL()
|
|
||||||
}
|
|
||||||
|
|
||||||
// getPolochonMovies returns an array of the user's polochon movies
|
// getPolochonMovies returns an array of the user's polochon movies
|
||||||
func getPolochonMovies(user *models.User, env *web.Env) ([]*Movie, error) {
|
func getPolochonMovies(user *models.User, env *web.Env) ([]*Movie, error) {
|
||||||
movies := []*Movie{}
|
movies := []*Movie{}
|
||||||
@ -278,12 +269,11 @@ func getPolochonMovies(user *models.User, env *web.Env) ([]*Movie, error) {
|
|||||||
// Create Movies objects from the movies retrieved
|
// Create Movies objects from the movies retrieved
|
||||||
for _, pmovie := range pmovies.List() {
|
for _, pmovie := range pmovies.List() {
|
||||||
movie := New(
|
movie := New(
|
||||||
|
env,
|
||||||
pmovie.ImdbID,
|
pmovie.ImdbID,
|
||||||
client,
|
client,
|
||||||
pmovie,
|
pmovie,
|
||||||
moviesWishlist.IsMovieInWishlist(pmovie.ImdbID),
|
moviesWishlist.IsMovieInWishlist(pmovie.ImdbID),
|
||||||
env.Config.PublicDir,
|
|
||||||
env.Config.ImgURLPrefix,
|
|
||||||
)
|
)
|
||||||
movies = append(movies, movie)
|
movies = append(movies, movie)
|
||||||
}
|
}
|
||||||
|
@ -82,16 +82,12 @@ func (e *Episode) MarshalJSON() ([]byte, error) {
|
|||||||
AudioCodec: audioCodec,
|
AudioCodec: audioCodec,
|
||||||
VideoCodec: videoCodec,
|
VideoCodec: videoCodec,
|
||||||
Container: container,
|
Container: container,
|
||||||
Thumb: e.getThumbURL(),
|
Thumb: e.Thumb,
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Marshal(episodeToMarshal)
|
return json.Marshal(episodeToMarshal)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Episode) getThumbURL() string {
|
|
||||||
return e.show.GetImageURL(fmt.Sprintf("%d-%d", e.Season, e.Episode))
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewEpisode returns an Episode
|
// NewEpisode returns an Episode
|
||||||
func NewEpisode(show *Show, season, episode int) *Episode {
|
func NewEpisode(show *Show, season, episode int) *Episode {
|
||||||
return &Episode{
|
return &Episode{
|
||||||
|
@ -38,9 +38,9 @@ func (s *Show) MarshalJSON() ([]byte, error) {
|
|||||||
PosterURL string `json:"poster_url"`
|
PosterURL string `json:"poster_url"`
|
||||||
}{
|
}{
|
||||||
alias: (*alias)(s),
|
alias: (*alias)(s),
|
||||||
BannerURL: s.GetImageURL("banner"),
|
BannerURL: s.Banner,
|
||||||
FanartURL: s.GetImageURL("fanart"),
|
FanartURL: s.Fanart,
|
||||||
PosterURL: s.GetImageURL("poster"),
|
PosterURL: s.Poster,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create Episode obj from polochon.Episodes and add them to the object to
|
// Create Episode obj from polochon.Episodes and add them to the object to
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"git.quimbo.fr/odwrtw/canape/backend/auth"
|
"git.quimbo.fr/odwrtw/canape/backend/auth"
|
||||||
|
"git.quimbo.fr/odwrtw/canape/backend/models"
|
||||||
"git.quimbo.fr/odwrtw/canape/backend/web"
|
"git.quimbo.fr/odwrtw/canape/backend/web"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/odwrtw/papi"
|
"github.com/odwrtw/papi"
|
||||||
@ -46,11 +47,12 @@ func ListHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
|||||||
return env.RenderError(w, err)
|
return env.RenderError(w, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
torrents, err := client.GetTorrents()
|
list, err := client.GetTorrents()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return env.RenderError(w, err)
|
return env.RenderError(w, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
torrents := models.NewTorrentVideos(env.Backend.Detailer, env.Database, env.Log, list)
|
||||||
return env.RenderJSON(w, torrents)
|
return env.RenderJSON(w, torrents)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,7 +170,7 @@ const TorrentsDropdown = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const TorrentsDropdownTitle = () => {
|
const TorrentsDropdownTitle = () => {
|
||||||
const count = useSelector((state) => state.torrents.torrents.length);
|
const count = useSelector((state) => state.torrents.count);
|
||||||
if (count === 0) {
|
if (count === 0) {
|
||||||
return <span>Torrents</span>;
|
return <span>Torrents</span>;
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import React, { useState } from "react";
|
import React from "react";
|
||||||
import PropTypes from "prop-types";
|
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
|
||||||
|
|
||||||
import { prettySize } from "../../utils";
|
import { AddTorrent } from "./list/addTorrent";
|
||||||
import { addTorrent, removeTorrent } from "../../actions/torrents";
|
import { Torrents } from "./list/torrents";
|
||||||
|
|
||||||
export const TorrentList = () => {
|
export const TorrentList = () => {
|
||||||
return (
|
return (
|
||||||
@ -15,108 +13,3 @@ export const TorrentList = () => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const AddTorrent = () => {
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const [url, setUrl] = useState("");
|
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
if (url === "") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
dispatch(
|
|
||||||
addTorrent({
|
|
||||||
result: { url: url },
|
|
||||||
})
|
|
||||||
);
|
|
||||||
setUrl("");
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form onSubmit={(e) => handleSubmit(e)}>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="form-control mb-3 w-100"
|
|
||||||
placeholder="Add torrent URL"
|
|
||||||
onSubmit={handleSubmit}
|
|
||||||
value={url}
|
|
||||||
onChange={(e) => setUrl(e.target.value)}
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const Torrents = () => {
|
|
||||||
const torrents = useSelector((state) => state.torrents.torrents);
|
|
||||||
|
|
||||||
if (torrents.length === 0) {
|
|
||||||
return (
|
|
||||||
<div className="jumbotron">
|
|
||||||
<h2>No torrents</h2>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="d-flex flex-wrap">
|
|
||||||
{torrents.map((torrent, index) => (
|
|
||||||
<Torrent key={index} torrent={torrent} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const Torrent = ({ torrent }) => {
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
|
|
||||||
var progressStyle = torrent.status.is_finished
|
|
||||||
? "success"
|
|
||||||
: "info progress-bar-striped progress-bar-animated";
|
|
||||||
const progressBarClass = "progress-bar bg-" + progressStyle;
|
|
||||||
|
|
||||||
var percentDone = torrent.status.percent_done;
|
|
||||||
const started = percentDone !== 0;
|
|
||||||
if (started) {
|
|
||||||
percentDone = Number(percentDone).toFixed(1) + "%";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pretty sizes
|
|
||||||
const downloadedSize = prettySize(torrent.status.downloaded_size);
|
|
||||||
const totalSize = prettySize(torrent.status.total_size);
|
|
||||||
const downloadRate = prettySize(torrent.status.download_rate) + "/s";
|
|
||||||
return (
|
|
||||||
<div className="card w-100 mb-3">
|
|
||||||
<h5 className="card-header">
|
|
||||||
<span className="text text-break">{torrent.status.name}</span>
|
|
||||||
<span
|
|
||||||
className="fa fa-trash clickable pull-right"
|
|
||||||
onClick={() => dispatch(removeTorrent(torrent.status.id))}
|
|
||||||
></span>
|
|
||||||
</h5>
|
|
||||||
<div className="card-body pb-0">
|
|
||||||
{started && (
|
|
||||||
<React.Fragment>
|
|
||||||
<div className="progress bg-light">
|
|
||||||
<div
|
|
||||||
className={progressBarClass}
|
|
||||||
style={{ width: percentDone }}
|
|
||||||
role="progressbar"
|
|
||||||
aria-valuenow={percentDone}
|
|
||||||
aria-valuemin="0"
|
|
||||||
aria-valuemax="100"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
<p>
|
|
||||||
{downloadedSize} / {totalSize} - {percentDone} - {downloadRate}
|
|
||||||
</p>
|
|
||||||
</React.Fragment>
|
|
||||||
)}
|
|
||||||
{!started && <p>Download not yet started</p>}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
Torrent.propTypes = {
|
|
||||||
torrent: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
35
frontend/js/components/torrents/list/addTorrent.js
Normal file
35
frontend/js/components/torrents/list/addTorrent.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
|
||||||
|
import { addTorrent } from "../../../actions/torrents";
|
||||||
|
|
||||||
|
export const AddTorrent = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const [url, setUrl] = useState("");
|
||||||
|
|
||||||
|
const handleSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (url === "") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(
|
||||||
|
addTorrent({
|
||||||
|
result: { url: url },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
setUrl("");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={(e) => handleSubmit(e)}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control mb-3 w-100"
|
||||||
|
placeholder="Add torrent URL"
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
value={url}
|
||||||
|
onChange={(e) => setUrl(e.target.value)}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
17
frontend/js/components/torrents/list/poster.js
Normal file
17
frontend/js/components/torrents/list/poster.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import React from "react";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
|
export const Poster = ({ url }) => {
|
||||||
|
if (!url || url === "") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="col-md-2 d-none d-md-block">
|
||||||
|
<img className="card-img" src={url} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
Poster.propTypes = {
|
||||||
|
url: PropTypes.string,
|
||||||
|
};
|
49
frontend/js/components/torrents/list/progress.js
Normal file
49
frontend/js/components/torrents/list/progress.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import React from "react";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
|
import { prettySize } from "../../../utils";
|
||||||
|
|
||||||
|
export const Progress = ({ torrent }) => {
|
||||||
|
var progressStyle = torrent.status.is_finished
|
||||||
|
? "success"
|
||||||
|
: "info progress-bar-striped progress-bar-animated";
|
||||||
|
const progressBarClass = "progress-bar bg-" + progressStyle;
|
||||||
|
|
||||||
|
var percentDone = torrent.status.percent_done;
|
||||||
|
const started = percentDone !== 0;
|
||||||
|
if (started) {
|
||||||
|
percentDone = Number(percentDone).toFixed(1) + "%";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pretty sizes
|
||||||
|
const downloadedSize = prettySize(torrent.status.downloaded_size);
|
||||||
|
const totalSize = prettySize(torrent.status.total_size);
|
||||||
|
const downloadRate = prettySize(torrent.status.download_rate) + "/s";
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="progress bg-light">
|
||||||
|
<div
|
||||||
|
className={progressBarClass}
|
||||||
|
style={{ width: percentDone }}
|
||||||
|
role="progressbar"
|
||||||
|
aria-valuenow={percentDone}
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="100"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
{started && (
|
||||||
|
<p>
|
||||||
|
{downloadedSize} / {totalSize} - {percentDone} - {downloadRate}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{!started && (
|
||||||
|
<p>
|
||||||
|
<small>Not yet started</small>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
Progress.propTypes = {
|
||||||
|
torrent: PropTypes.object.isRequired,
|
||||||
|
};
|
44
frontend/js/components/torrents/list/torrent.js
Normal file
44
frontend/js/components/torrents/list/torrent.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import React from "react";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
|
||||||
|
import { prettyEpisodeNameWithoutShow } from "../../../utils";
|
||||||
|
import { removeTorrent } from "../../../actions/torrents";
|
||||||
|
|
||||||
|
import { Progress } from "./progress";
|
||||||
|
|
||||||
|
export const Torrent = ({ torrent }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const title = (torrent) => {
|
||||||
|
if (torrent.type !== "episode" || !torrent.video) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
prettyEpisodeNameWithoutShow(
|
||||||
|
torrent.video.season,
|
||||||
|
torrent.video.episode
|
||||||
|
) + " - "
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="border-top">
|
||||||
|
<div className="card-text d-flex flex-row">
|
||||||
|
<div className="mt-1 flex-fill">
|
||||||
|
<span className="text text-break">{title(torrent)}</span>
|
||||||
|
<small className="text-muted text-break">{torrent.status.name}</small>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="fa fa-trash btn text-right"
|
||||||
|
onClick={() => dispatch(removeTorrent(torrent.status.id))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Progress torrent={torrent} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
Torrent.propTypes = {
|
||||||
|
torrent: PropTypes.object.isRequired,
|
||||||
|
};
|
49
frontend/js/components/torrents/list/torrentGroup.js
Normal file
49
frontend/js/components/torrents/list/torrentGroup.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import React from "react";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
|
||||||
|
import { Torrent } from "./torrent";
|
||||||
|
import { Poster } from "./poster";
|
||||||
|
|
||||||
|
export const TorrentGroup = ({ torrentKey }) => {
|
||||||
|
const torrents = useSelector((state) =>
|
||||||
|
state.torrents.torrents.get(torrentKey)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (torrents.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const title = (torrent) => {
|
||||||
|
switch (torrent.type) {
|
||||||
|
case "movie":
|
||||||
|
return torrent.video.title;
|
||||||
|
case "episode":
|
||||||
|
return torrent.video.show_title;
|
||||||
|
default:
|
||||||
|
return "Files";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-100 mb-3 card">
|
||||||
|
<div className="row no-gutters">
|
||||||
|
<Poster url={torrents[0].img} />
|
||||||
|
<div className="col-sm">
|
||||||
|
<div className="card-body">
|
||||||
|
<h4 className="card-title">{title(torrents[0])}</h4>
|
||||||
|
{torrents.map((torrent, i) => (
|
||||||
|
<Torrent
|
||||||
|
key={torrent.video ? torrent.video.imdb_id : i}
|
||||||
|
torrent={torrent}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
TorrentGroup.propTypes = {
|
||||||
|
torrentKey: PropTypes.string.isRequired,
|
||||||
|
};
|
26
frontend/js/components/torrents/list/torrents.js
Normal file
26
frontend/js/components/torrents/list/torrents.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
|
||||||
|
import { TorrentGroup } from "./torrentGroup";
|
||||||
|
|
||||||
|
export const Torrents = () => {
|
||||||
|
const torrentsKeys = useSelector((state) =>
|
||||||
|
Array.from(state.torrents.torrents.keys())
|
||||||
|
);
|
||||||
|
|
||||||
|
if (torrentsKeys.length === 0) {
|
||||||
|
return (
|
||||||
|
<div className="jumbotron">
|
||||||
|
<h2>No torrents</h2>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="d-flex flex-wrap">
|
||||||
|
{torrentsKeys.map((key) => (
|
||||||
|
<TorrentGroup key={key} torrentKey={key} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -3,10 +3,42 @@ import { produce } from "immer";
|
|||||||
const defaultState = {
|
const defaultState = {
|
||||||
fetching: false,
|
fetching: false,
|
||||||
searching: false,
|
searching: false,
|
||||||
torrents: [],
|
torrents: new Map(),
|
||||||
|
count: 0,
|
||||||
searchResults: [],
|
searchResults: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Group the torrents by imdb id
|
||||||
|
const formatTorrents = (input) => {
|
||||||
|
let torrents = new Map();
|
||||||
|
if (!input) {
|
||||||
|
return torrents;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.forEach((t) => {
|
||||||
|
let key;
|
||||||
|
switch (t.type) {
|
||||||
|
case "movie":
|
||||||
|
key = t.video ? t.video.imdb_id : "unknown";
|
||||||
|
break;
|
||||||
|
case "episode":
|
||||||
|
key = t.video ? t.video.show_imdb_id : "unknown";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
key = "unknown";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!torrents.has(key)) {
|
||||||
|
torrents.set(key, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
torrents.get(key).push(t);
|
||||||
|
});
|
||||||
|
|
||||||
|
return torrents;
|
||||||
|
};
|
||||||
|
|
||||||
export default (state = defaultState, action) =>
|
export default (state = defaultState, action) =>
|
||||||
produce(state, (draft) => {
|
produce(state, (draft) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
@ -16,7 +48,8 @@ export default (state = defaultState, action) =>
|
|||||||
|
|
||||||
case "TORRENTS_FETCH_FULFILLED":
|
case "TORRENTS_FETCH_FULFILLED":
|
||||||
draft.fetching = false;
|
draft.fetching = false;
|
||||||
draft.torrents = action.payload.response.data;
|
draft.torrents = formatTorrents(action.payload.response.data);
|
||||||
|
draft.count = action.payload.response.data.length;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "TORRENTS_SEARCH_PENDING":
|
case "TORRENTS_SEARCH_PENDING":
|
||||||
|
@ -18,6 +18,9 @@ export const prettyDurationFromMinutes = (runtime) => {
|
|||||||
|
|
||||||
const pad = (d) => (d < 10 ? "0" + d.toString() : d.toString());
|
const pad = (d) => (d < 10 ? "0" + d.toString() : d.toString());
|
||||||
|
|
||||||
|
export const prettyEpisodeNameWithoutShow = (season, episode) =>
|
||||||
|
`S${pad(season)}E${pad(episode)}`;
|
||||||
|
|
||||||
export const prettyEpisodeName = (showName, season, episode) =>
|
export const prettyEpisodeName = (showName, season, episode) =>
|
||||||
`${showName} S${pad(season)}E${pad(episode)}`;
|
`${showName} S${pad(season)}E${pad(episode)}`;
|
||||||
|
|
||||||
|
6
go.mod
6
go.mod
@ -1,6 +1,6 @@
|
|||||||
module git.quimbo.fr/odwrtw/canape
|
module git.quimbo.fr/odwrtw/canape
|
||||||
|
|
||||||
go 1.12
|
go 1.14
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||||
@ -15,8 +15,8 @@ require (
|
|||||||
github.com/mattn/go-sqlite3 v1.10.0 // indirect
|
github.com/mattn/go-sqlite3 v1.10.0 // indirect
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
||||||
github.com/odwrtw/errors v0.0.0-20170604160533-c747b9d17833
|
github.com/odwrtw/errors v0.0.0-20170604160533-c747b9d17833
|
||||||
github.com/odwrtw/papi v0.0.0-20200410143325-49e6f827259d
|
github.com/odwrtw/papi v0.0.0-20200413153625-62744e1c1b73
|
||||||
github.com/odwrtw/polochon v0.0.0-20200410143337-006e3fb9fb55
|
github.com/odwrtw/polochon v0.0.0-20200413153516-6d6ff1d17684
|
||||||
github.com/phyber/negroni-gzip v0.0.0-20180113114010-ef6356a5d029
|
github.com/phyber/negroni-gzip v0.0.0-20180113114010-ef6356a5d029
|
||||||
github.com/pioz/tvdb v0.0.0-20190503215423-f45c687faba9 // indirect
|
github.com/pioz/tvdb v0.0.0-20190503215423-f45c687faba9 // indirect
|
||||||
github.com/robfig/cron v1.1.0
|
github.com/robfig/cron v1.1.0
|
||||||
|
6
go.sum
6
go.sum
@ -134,10 +134,12 @@ github.com/odwrtw/guessit v0.0.0-20200131084001-f88613483547/go.mod h1:W22g7wtc0
|
|||||||
github.com/odwrtw/imdb-watchlist v0.0.0-20190417175016-b7a9f7503d69 h1:ow6b/4Jj7J5iYwU678/rbijvaNUJrYkg13j9Nivkung=
|
github.com/odwrtw/imdb-watchlist v0.0.0-20190417175016-b7a9f7503d69 h1:ow6b/4Jj7J5iYwU678/rbijvaNUJrYkg13j9Nivkung=
|
||||||
github.com/odwrtw/imdb-watchlist v0.0.0-20190417175016-b7a9f7503d69/go.mod h1:o2tLH95CtNdqhDb0aS2NbU+1I4PmaNsODpr33Ry0JC0=
|
github.com/odwrtw/imdb-watchlist v0.0.0-20190417175016-b7a9f7503d69/go.mod h1:o2tLH95CtNdqhDb0aS2NbU+1I4PmaNsODpr33Ry0JC0=
|
||||||
github.com/odwrtw/papi v0.0.0-20190413103029-bd5bfea85ae6/go.mod h1:CXotdtODLpW0/yuFV5XH8Rmrj0eAfPLvdMKykPM2WCk=
|
github.com/odwrtw/papi v0.0.0-20190413103029-bd5bfea85ae6/go.mod h1:CXotdtODLpW0/yuFV5XH8Rmrj0eAfPLvdMKykPM2WCk=
|
||||||
github.com/odwrtw/papi v0.0.0-20200410143325-49e6f827259d h1:it4hnCveS8eFymg0ll9KRzO/iQm/olSW0sb8Ctm3gXI=
|
|
||||||
github.com/odwrtw/papi v0.0.0-20200410143325-49e6f827259d/go.mod h1:eY0skvVHJBwbSJ18uq2c1T4SvhdEV8R0XFSb0zKh5Yo=
|
github.com/odwrtw/papi v0.0.0-20200410143325-49e6f827259d/go.mod h1:eY0skvVHJBwbSJ18uq2c1T4SvhdEV8R0XFSb0zKh5Yo=
|
||||||
github.com/odwrtw/polochon v0.0.0-20200410143337-006e3fb9fb55 h1:hcHBTi+HfYz5p6wgtvQCbrdog0uOB/7eVxPZA5Qff80=
|
github.com/odwrtw/papi v0.0.0-20200413153625-62744e1c1b73 h1:19mh4fw/WGFtYg/7oHg1Y5rU9ZRxD3LqrtwY2NI6l6w=
|
||||||
|
github.com/odwrtw/papi v0.0.0-20200413153625-62744e1c1b73/go.mod h1:eY0skvVHJBwbSJ18uq2c1T4SvhdEV8R0XFSb0zKh5Yo=
|
||||||
github.com/odwrtw/polochon v0.0.0-20200410143337-006e3fb9fb55/go.mod h1:sAYf/A5tDmins2GHZn2mEFarmYltAZv+bcmSKSxDUaI=
|
github.com/odwrtw/polochon v0.0.0-20200410143337-006e3fb9fb55/go.mod h1:sAYf/A5tDmins2GHZn2mEFarmYltAZv+bcmSKSxDUaI=
|
||||||
|
github.com/odwrtw/polochon v0.0.0-20200413153516-6d6ff1d17684 h1:b9JDu8423NGXOYN0gtoiVrLKbZ/CfAyc2ouCRuE+mI8=
|
||||||
|
github.com/odwrtw/polochon v0.0.0-20200413153516-6d6ff1d17684/go.mod h1:rBjekia21ToZoTxJqR/5Ued8EYwKTtamq+bo/XINOjA=
|
||||||
github.com/odwrtw/tpb v0.0.0-20200130133144-c846aa382c6f h1:fwEIGT+o3e8+XkBqrwsE3/+9ketTQXflPhCkv3/w990=
|
github.com/odwrtw/tpb v0.0.0-20200130133144-c846aa382c6f h1:fwEIGT+o3e8+XkBqrwsE3/+9ketTQXflPhCkv3/w990=
|
||||||
github.com/odwrtw/tpb v0.0.0-20200130133144-c846aa382c6f/go.mod h1:updLvMbQo2xHoz94MX9+GqmSoKhf6E8fs/J+wLvvu6A=
|
github.com/odwrtw/tpb v0.0.0-20200130133144-c846aa382c6f/go.mod h1:updLvMbQo2xHoz94MX9+GqmSoKhf6E8fs/J+wLvvu6A=
|
||||||
github.com/odwrtw/trakttv v0.0.0-20200404161731-0d594827e4f9 h1:PuQLHO75MXUsJpf9BcTVxvR/FCkdn1MZnZt6h3o6cJI=
|
github.com/odwrtw/trakttv v0.0.0-20200404161731-0d594827e4f9 h1:PuQLHO75MXUsJpf9BcTVxvR/FCkdn1MZnZt6h3o6cJI=
|
||||||
|
Loading…
x
Reference in New Issue
Block a user