|
@@ -0,0 +1,360 @@
|
|
|
+package plexapi
|
|
|
+
|
|
|
+import (
|
|
|
+ "encoding/xml"
|
|
|
+ "fmt"
|
|
|
+ "io/ioutil"
|
|
|
+ "net/http"
|
|
|
+ "strings"
|
|
|
+ "sync"
|
|
|
+
|
|
|
+ "code.senomas.com/go/util"
|
|
|
+ log "github.com/Sirupsen/logrus"
|
|
|
+)
|
|
|
+
|
|
|
+// Server struct
|
|
|
+type Server struct {
|
|
|
+ api *API
|
|
|
+ host string
|
|
|
+ XMLName xml.Name `xml:"Server"`
|
|
|
+ AccessToken string `xml:"accessToken,attr"`
|
|
|
+ Name string `xml:"name,attr"`
|
|
|
+ Address string `xml:"address,attr"`
|
|
|
+ Port int `xml:"port,attr"`
|
|
|
+ Version string `xml:"version,attr"`
|
|
|
+ Scheme string `xml:"scheme,attr"`
|
|
|
+ Host string `xml:"host,attr"`
|
|
|
+ LocalAddresses string `xml:"localAddresses,attr"`
|
|
|
+ MachineIdentifier string `xml:"machineIdentifier,attr"`
|
|
|
+ CreatedAt int `xml:"createdAt,attr"`
|
|
|
+ UpdatedAt int `xml:"updatedAt,attr"`
|
|
|
+ Owned int `xml:"owned,attr"`
|
|
|
+ Synced int `xml:"synced,attr"`
|
|
|
+}
|
|
|
+
|
|
|
+func (server *Server) setHeader(req *http.Request) {
|
|
|
+ req.Header.Add("X-Plex-Product", "plex-sync")
|
|
|
+ req.Header.Add("X-Plex-Version", "1.0.0")
|
|
|
+ req.Header.Add("X-Plex-Client-Identifier", "donkey")
|
|
|
+ if server.api.userInfo.Token != "" {
|
|
|
+ req.Header.Add("X-Plex-Token", server.api.userInfo.Token)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func (server *Server) getContainer(url string) (container MediaContainer, err error) {
|
|
|
+ log.WithField("url", url).Debugf("GET")
|
|
|
+ req, err := http.NewRequest("GET", url, nil)
|
|
|
+ if err != nil {
|
|
|
+ log.WithField("url", url).WithError(err).Errorf("http.GET")
|
|
|
+ return container, err
|
|
|
+ }
|
|
|
+ server.setHeader(req)
|
|
|
+
|
|
|
+ resp, err := server.api.client.Do(req)
|
|
|
+ if err != nil {
|
|
|
+ return container, err
|
|
|
+ }
|
|
|
+ defer resp.Body.Close()
|
|
|
+
|
|
|
+ log.WithField("url", url).WithField("status", resp.Status).Debugf("RESP")
|
|
|
+
|
|
|
+ body, err := ioutil.ReadAll(resp.Body)
|
|
|
+ if err != nil {
|
|
|
+ log.WithField("url", url).WithError(err).Errorf("RESP")
|
|
|
+ return container, err
|
|
|
+ }
|
|
|
+
|
|
|
+ err = xml.Unmarshal(body, &container)
|
|
|
+ return container, err
|
|
|
+}
|
|
|
+
|
|
|
+// Check func
|
|
|
+func (server *Server) Check() {
|
|
|
+ var urls []string
|
|
|
+ urls = append(urls, fmt.Sprintf("https://%s:%v", server.Address, server.Port))
|
|
|
+ for _, la := range strings.Split(server.LocalAddresses, ",") {
|
|
|
+ urls = append(urls, fmt.Sprintf("https://%s:32400", la))
|
|
|
+ }
|
|
|
+
|
|
|
+ var checkOnce util.CheckOnce
|
|
|
+ var wg sync.WaitGroup
|
|
|
+
|
|
|
+ out := make(chan string)
|
|
|
+ defer close(out)
|
|
|
+
|
|
|
+ for _, pu := range urls {
|
|
|
+ url := pu
|
|
|
+ wg.Add(1)
|
|
|
+ go func() error {
|
|
|
+ defer wg.Done()
|
|
|
+
|
|
|
+ if !checkOnce.IsDone() {
|
|
|
+ log.Debugf("CHECK %s %s", server.Name, url)
|
|
|
+ c, err := server.getContainer(url)
|
|
|
+ if err != nil {
|
|
|
+ log.Debugf("ERROR GET %s '%s': %v", server.Name, url, err)
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ if c.FriendlyName != server.Name {
|
|
|
+ log.Fatal("WRONG SERVER ", c.FriendlyName, " ", server.Name)
|
|
|
+ }
|
|
|
+ // log.Debugf("RESP BODY\n%s", util.JSONPrettyPrint(c))
|
|
|
+ checkOnce.Done(func() {
|
|
|
+ out <- url
|
|
|
+ })
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+ }()
|
|
|
+ }
|
|
|
+
|
|
|
+ go func() {
|
|
|
+ wg.Wait()
|
|
|
+ checkOnce.Done(func() {
|
|
|
+ out <- ""
|
|
|
+ })
|
|
|
+ }()
|
|
|
+
|
|
|
+ server.host = <-out
|
|
|
+ log.Debugf("URL %s %s", server.Name, server.host)
|
|
|
+}
|
|
|
+
|
|
|
+// Perform func
|
|
|
+func (server *Server) Perform(cmd, path string) error {
|
|
|
+ if server.host != "" {
|
|
|
+ url := server.host + path
|
|
|
+ log.WithField("url", url).Debugf(cmd)
|
|
|
+ req, err := http.NewRequest(cmd, url, nil)
|
|
|
+ if err != nil {
|
|
|
+ log.WithField("url", url).WithError(err).Errorf("http.%s", cmd)
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ server.setHeader(req)
|
|
|
+
|
|
|
+ resp, err := server.api.client.Do(req)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ defer resp.Body.Close()
|
|
|
+
|
|
|
+ log.WithField("url", url).WithField("status", resp.Status).Debugf("RESP")
|
|
|
+
|
|
|
+ body, err := ioutil.ReadAll(resp.Body)
|
|
|
+ if err != nil {
|
|
|
+ log.WithField("url", url).WithError(err).Errorf("RESP")
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ log.Debugf("RESP BODY\n%s", body)
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return fmt.Errorf("NO HOST")
|
|
|
+}
|
|
|
+
|
|
|
+// GetContainer func
|
|
|
+func (server *Server) GetContainer(path string) (container MediaContainer, err error) {
|
|
|
+ if server.host != "" {
|
|
|
+ container, err = server.getContainer(server.host + path)
|
|
|
+ return container, err
|
|
|
+ }
|
|
|
+ return container, fmt.Errorf("NO HOST")
|
|
|
+}
|
|
|
+
|
|
|
+// GetDirectories func
|
|
|
+func (server *Server) GetDirectories() (directories []Directory, err error) {
|
|
|
+ container, err := server.GetContainer("/library/sections")
|
|
|
+ if err != nil {
|
|
|
+ return directories, err
|
|
|
+ }
|
|
|
+ for _, d := range container.Directories {
|
|
|
+ nd := d
|
|
|
+ // nd.server = server
|
|
|
+ directories = append(directories, nd)
|
|
|
+ }
|
|
|
+ return directories, err
|
|
|
+}
|
|
|
+
|
|
|
+// GetVideos func
|
|
|
+func (server *Server) GetVideos(wg *sync.WaitGroup, out chan<- interface{}) {
|
|
|
+ cs := make(chan MediaContainer)
|
|
|
+ dirs := make(chan Directory)
|
|
|
+
|
|
|
+ wg.Add(1)
|
|
|
+ go func() {
|
|
|
+ container, err := server.GetContainer("/library/sections")
|
|
|
+ if err == nil {
|
|
|
+ cs <- container
|
|
|
+ } else {
|
|
|
+ wg.Done()
|
|
|
+ }
|
|
|
+ }()
|
|
|
+ for i, il := 0, server.api.HTTP.WorkerSize; i < il; i++ {
|
|
|
+ go func() {
|
|
|
+ for c := range cs {
|
|
|
+ func() {
|
|
|
+ defer wg.Done()
|
|
|
+ for _, d := range c.Directories {
|
|
|
+ wg.Add(1)
|
|
|
+ d.Paths = c.Paths
|
|
|
+ d.Keys = c.Keys
|
|
|
+ d.Keys = append(d.Keys, KeyInfo{Key: d.Key, Type: d.Type, Title: d.Title})
|
|
|
+ dirs <- d
|
|
|
+ }
|
|
|
+ for _, v := range c.Videos {
|
|
|
+ // if v.GUID == "" {
|
|
|
+ // meta, err := server.GetMeta(v)
|
|
|
+ // if err != nil {
|
|
|
+ // log.Fatal("GetMeta ", err)
|
|
|
+ // }
|
|
|
+ // v = meta
|
|
|
+ // }
|
|
|
+ v.server = server
|
|
|
+ v.Paths = c.Paths
|
|
|
+ v.Keys = c.Keys
|
|
|
+ var idx []string
|
|
|
+ for _, px := range v.Media.Parts {
|
|
|
+ for _, kk := range v.Paths {
|
|
|
+ if strings.HasPrefix(px.File, kk) {
|
|
|
+ idx = append(idx, px.File[len(kk):])
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if len(idx) > 0 {
|
|
|
+ v.FID = strings.Join(idx, ":")
|
|
|
+ v.FID = strings.Replace(v.FID, "\\", "/", -1)
|
|
|
+ } else {
|
|
|
+ v.FID = v.GUID
|
|
|
+ }
|
|
|
+ wg.Add(1)
|
|
|
+ out <- v
|
|
|
+ }
|
|
|
+ }()
|
|
|
+ }
|
|
|
+ }()
|
|
|
+ go func() {
|
|
|
+ for d := range dirs {
|
|
|
+ func() {
|
|
|
+ defer wg.Done()
|
|
|
+
|
|
|
+ if strings.HasPrefix(d.Key, "/library/") {
|
|
|
+ cc, err := server.GetContainer(d.Key)
|
|
|
+ if err == nil {
|
|
|
+ wg.Add(1)
|
|
|
+ cc.Keys = d.Keys
|
|
|
+ cc.Paths = d.Paths
|
|
|
+ for _, l := range d.Locations {
|
|
|
+ if l.Path != "" {
|
|
|
+ cc.Paths = append(cc.Paths, l.Path)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ cs <- cc
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ cc, err := server.GetContainer(fmt.Sprintf("/library/sections/%v/all", d.Key))
|
|
|
+ if err == nil {
|
|
|
+ wg.Add(1)
|
|
|
+ cc.Keys = d.Keys
|
|
|
+ cc.Paths = d.Paths
|
|
|
+ for _, l := range d.Locations {
|
|
|
+ if l.Path != "" {
|
|
|
+ cc.Paths = append(cc.Paths, l.Path)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ cs <- cc
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }()
|
|
|
+ }
|
|
|
+ }()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// GetMeta func
|
|
|
+func (server *Server) GetMeta(video Video) (meta Video, err error) {
|
|
|
+ if video.GUID != "" {
|
|
|
+ return video, nil
|
|
|
+ }
|
|
|
+ var mc MediaContainer
|
|
|
+ mc, err = server.GetContainer(video.Key)
|
|
|
+ if err != nil {
|
|
|
+ return video, err
|
|
|
+ }
|
|
|
+ return mc.Videos[0], nil
|
|
|
+}
|
|
|
+
|
|
|
+// MarkWatched func
|
|
|
+func (server *Server) MarkWatched(key string) error {
|
|
|
+ url := server.host + "/:/scrobble?identifier=com.plexapp.plugins.library&key=" + key
|
|
|
+ log.WithField("url", url).WithField("server", server.Name).Debug("MarkWatched.GET")
|
|
|
+ req, err := http.NewRequest("GET", url, nil)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ server.setHeader(req)
|
|
|
+
|
|
|
+ resp, err := server.api.client.Do(req)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ defer resp.Body.Close()
|
|
|
+
|
|
|
+ body, err := ioutil.ReadAll(resp.Body)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ log.WithField("url", url).WithField("server", server.Name).Debugf("MarkWatched.RESULT\n%s", body)
|
|
|
+
|
|
|
+ return err
|
|
|
+}
|
|
|
+
|
|
|
+// MarkUnwatched func
|
|
|
+func (server *Server) MarkUnwatched(key string) error {
|
|
|
+ url := server.host + "/:/unscrobble?identifier=com.plexapp.plugins.library&key=" + key
|
|
|
+ log.WithField("url", url).WithField("server", server.Name).Debug("MarkUnwatched.GET")
|
|
|
+ req, err := http.NewRequest("GET", url, nil)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ server.setHeader(req)
|
|
|
+
|
|
|
+ resp, err := server.api.client.Do(req)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ defer resp.Body.Close()
|
|
|
+
|
|
|
+ body, err := ioutil.ReadAll(resp.Body)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ log.WithField("url", url).WithField("server", server.Name).Debugf("MarkUnwatched.RESULT\n%s", body)
|
|
|
+
|
|
|
+ return err
|
|
|
+}
|
|
|
+
|
|
|
+// SetViewOffset func
|
|
|
+func (server *Server) SetViewOffset(key, offset string) error {
|
|
|
+ url := server.host + "/:/progress?key=" + key + "&identifier=com.plexapp.plugins.library&time=" + offset
|
|
|
+ log.WithField("url", url).WithField("server", server.Name).Debug("SetViewOffset.GET")
|
|
|
+ req, err := http.NewRequest("GET", url, nil)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ server.setHeader(req)
|
|
|
+
|
|
|
+ resp, err := server.api.client.Do(req)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ defer resp.Body.Close()
|
|
|
+
|
|
|
+ body, err := ioutil.ReadAll(resp.Body)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ log.WithField("url", url).WithField("server", server.Name).Debugf("SetViewOffset.RESULT\n%s", body)
|
|
|
+
|
|
|
+ return err
|
|
|
+}
|