plex_server.go 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. package plexapi
  2. import (
  3. "encoding/xml"
  4. "fmt"
  5. "io/ioutil"
  6. "net/http"
  7. "strings"
  8. "sync"
  9. "code.senomas.com/go/util"
  10. log "github.com/Sirupsen/logrus"
  11. )
  12. // Server struct
  13. type Server struct {
  14. api *API
  15. host string
  16. XMLName xml.Name `xml:"Server"`
  17. AccessToken string `xml:"accessToken,attr"`
  18. Name string `xml:"name,attr"`
  19. Address string `xml:"address,attr"`
  20. Port int `xml:"port,attr"`
  21. Version string `xml:"version,attr"`
  22. Scheme string `xml:"scheme,attr"`
  23. Host string `xml:"host,attr"`
  24. LocalAddresses string `xml:"localAddresses,attr"`
  25. MachineIdentifier string `xml:"machineIdentifier,attr"`
  26. CreatedAt int `xml:"createdAt,attr"`
  27. UpdatedAt int `xml:"updatedAt,attr"`
  28. Owned int `xml:"owned,attr"`
  29. Synced int `xml:"synced,attr"`
  30. }
  31. func (server *Server) setHeader(req *http.Request) {
  32. req.Header.Add("X-Plex-Product", "plex-sync")
  33. req.Header.Add("X-Plex-Version", "1.0.0")
  34. req.Header.Add("X-Plex-Client-Identifier", "donkey")
  35. if server.api.userInfo.Token != "" {
  36. req.Header.Add("X-Plex-Token", server.api.userInfo.Token)
  37. }
  38. }
  39. func (server *Server) getContainer(url string) (container MediaContainer, err error) {
  40. log.WithField("url", url).Debugf("GET")
  41. req, err := http.NewRequest("GET", url, nil)
  42. if err != nil {
  43. log.WithField("url", url).WithError(err).Errorf("http.GET")
  44. return container, err
  45. }
  46. server.setHeader(req)
  47. resp, err := server.api.client.Do(req)
  48. if err != nil {
  49. return container, err
  50. }
  51. defer resp.Body.Close()
  52. log.WithField("url", url).WithField("status", resp.Status).Debugf("RESP")
  53. body, err := ioutil.ReadAll(resp.Body)
  54. if err != nil {
  55. log.WithField("url", url).WithError(err).Errorf("RESP")
  56. return container, err
  57. }
  58. err = xml.Unmarshal(body, &container)
  59. return container, err
  60. }
  61. // Check func
  62. func (server *Server) Check() {
  63. var urls []string
  64. urls = append(urls, fmt.Sprintf("https://%s:%v", server.Address, server.Port))
  65. for _, la := range strings.Split(server.LocalAddresses, ",") {
  66. urls = append(urls, fmt.Sprintf("https://%s:32400", la))
  67. }
  68. var checkOnce util.CheckOnce
  69. var wg sync.WaitGroup
  70. out := make(chan string)
  71. defer close(out)
  72. for _, pu := range urls {
  73. url := pu
  74. wg.Add(1)
  75. go func() error {
  76. defer wg.Done()
  77. if !checkOnce.IsDone() {
  78. log.Debugf("CHECK %s %s", server.Name, url)
  79. c, err := server.getContainer(url)
  80. if err != nil {
  81. log.Debugf("ERROR GET %s '%s': %v", server.Name, url, err)
  82. return err
  83. }
  84. if c.FriendlyName != server.Name {
  85. log.Fatal("WRONG SERVER ", c.FriendlyName, " ", server.Name)
  86. }
  87. // log.Debugf("RESP BODY\n%s", util.JSONPrettyPrint(c))
  88. checkOnce.Done(func() {
  89. out <- url
  90. })
  91. }
  92. return nil
  93. }()
  94. }
  95. go func() {
  96. wg.Wait()
  97. checkOnce.Done(func() {
  98. out <- ""
  99. })
  100. }()
  101. server.host = <-out
  102. log.Debugf("URL %s %s", server.Name, server.host)
  103. }
  104. // Perform func
  105. func (server *Server) Perform(cmd, path string) error {
  106. if server.host != "" {
  107. url := server.host + path
  108. log.WithField("url", url).Debugf(cmd)
  109. req, err := http.NewRequest(cmd, url, nil)
  110. if err != nil {
  111. log.WithField("url", url).WithError(err).Errorf("http.%s", cmd)
  112. return err
  113. }
  114. server.setHeader(req)
  115. resp, err := server.api.client.Do(req)
  116. if err != nil {
  117. return err
  118. }
  119. defer resp.Body.Close()
  120. log.WithField("url", url).WithField("status", resp.Status).Debugf("RESP")
  121. body, err := ioutil.ReadAll(resp.Body)
  122. if err != nil {
  123. log.WithField("url", url).WithError(err).Errorf("RESP")
  124. return err
  125. }
  126. log.Debugf("RESP BODY\n%s", body)
  127. return err
  128. }
  129. return fmt.Errorf("NO HOST")
  130. }
  131. // GetContainer func
  132. func (server *Server) GetContainer(path string) (container MediaContainer, err error) {
  133. if server.host != "" {
  134. container, err = server.getContainer(server.host + path)
  135. return container, err
  136. }
  137. return container, fmt.Errorf("NO HOST")
  138. }
  139. // GetDirectories func
  140. func (server *Server) GetDirectories() (directories []Directory, err error) {
  141. container, err := server.GetContainer("/library/sections")
  142. if err != nil {
  143. return directories, err
  144. }
  145. for _, d := range container.Directories {
  146. nd := d
  147. // nd.server = server
  148. directories = append(directories, nd)
  149. }
  150. return directories, err
  151. }
  152. // GetVideos func
  153. func (server *Server) GetVideos(wg *sync.WaitGroup, out chan<- interface{}) {
  154. cs := make(chan MediaContainer)
  155. dirs := make(chan Directory)
  156. wg.Add(1)
  157. go func() {
  158. container, err := server.GetContainer("/library/sections")
  159. if err == nil {
  160. cs <- container
  161. } else {
  162. wg.Done()
  163. }
  164. }()
  165. for i, il := 0, server.api.HTTP.WorkerSize; i < il; i++ {
  166. go func() {
  167. for c := range cs {
  168. func() {
  169. defer wg.Done()
  170. for _, d := range c.Directories {
  171. wg.Add(1)
  172. d.Paths = c.Paths
  173. d.Keys = c.Keys
  174. d.Keys = append(d.Keys, KeyInfo{Key: d.Key, Type: d.Type, Title: d.Title})
  175. dirs <- d
  176. }
  177. for _, v := range c.Videos {
  178. // if v.GUID == "" {
  179. // meta, err := server.GetMeta(v)
  180. // if err != nil {
  181. // log.Fatal("GetMeta ", err)
  182. // }
  183. // v = meta
  184. // }
  185. v.server = server
  186. v.Paths = c.Paths
  187. v.Keys = c.Keys
  188. var idx []string
  189. for _, px := range v.Media.Parts {
  190. for _, kk := range v.Paths {
  191. if strings.HasPrefix(px.File, kk) {
  192. idx = append(idx, px.File[len(kk):])
  193. }
  194. }
  195. }
  196. if len(idx) > 0 {
  197. v.FID = strings.Join(idx, ":")
  198. v.FID = strings.Replace(v.FID, "\\", "/", -1)
  199. } else {
  200. v.FID = v.GUID
  201. }
  202. wg.Add(1)
  203. out <- v
  204. }
  205. }()
  206. }
  207. }()
  208. go func() {
  209. for d := range dirs {
  210. func() {
  211. defer wg.Done()
  212. if strings.HasPrefix(d.Key, "/library/") {
  213. cc, err := server.GetContainer(d.Key)
  214. if err == nil {
  215. wg.Add(1)
  216. cc.Keys = d.Keys
  217. cc.Paths = d.Paths
  218. for _, l := range d.Locations {
  219. if l.Path != "" {
  220. cc.Paths = append(cc.Paths, l.Path)
  221. }
  222. }
  223. cs <- cc
  224. }
  225. } else {
  226. cc, err := server.GetContainer(fmt.Sprintf("/library/sections/%v/all", d.Key))
  227. if err == nil {
  228. wg.Add(1)
  229. cc.Keys = d.Keys
  230. cc.Paths = d.Paths
  231. for _, l := range d.Locations {
  232. if l.Path != "" {
  233. cc.Paths = append(cc.Paths, l.Path)
  234. }
  235. }
  236. cs <- cc
  237. }
  238. }
  239. }()
  240. }
  241. }()
  242. }
  243. }
  244. // GetMeta func
  245. func (server *Server) GetMeta(video Video) (meta Video, err error) {
  246. if video.GUID != "" {
  247. return video, nil
  248. }
  249. var mc MediaContainer
  250. mc, err = server.GetContainer(video.Key)
  251. if err != nil {
  252. return video, err
  253. }
  254. return mc.Videos[0], nil
  255. }
  256. // MarkWatched func
  257. func (server *Server) MarkWatched(key string) error {
  258. url := server.host + "/:/scrobble?identifier=com.plexapp.plugins.library&key=" + key
  259. log.WithField("url", url).WithField("server", server.Name).Debug("MarkWatched.GET")
  260. req, err := http.NewRequest("GET", url, nil)
  261. if err != nil {
  262. return err
  263. }
  264. server.setHeader(req)
  265. resp, err := server.api.client.Do(req)
  266. if err != nil {
  267. return err
  268. }
  269. defer resp.Body.Close()
  270. body, err := ioutil.ReadAll(resp.Body)
  271. if err != nil {
  272. return err
  273. }
  274. log.WithField("url", url).WithField("server", server.Name).Debugf("MarkWatched.RESULT\n%s", body)
  275. return err
  276. }
  277. // MarkUnwatched func
  278. func (server *Server) MarkUnwatched(key string) error {
  279. url := server.host + "/:/unscrobble?identifier=com.plexapp.plugins.library&key=" + key
  280. log.WithField("url", url).WithField("server", server.Name).Debug("MarkUnwatched.GET")
  281. req, err := http.NewRequest("GET", url, nil)
  282. if err != nil {
  283. return err
  284. }
  285. server.setHeader(req)
  286. resp, err := server.api.client.Do(req)
  287. if err != nil {
  288. return err
  289. }
  290. defer resp.Body.Close()
  291. body, err := ioutil.ReadAll(resp.Body)
  292. if err != nil {
  293. return err
  294. }
  295. log.WithField("url", url).WithField("server", server.Name).Debugf("MarkUnwatched.RESULT\n%s", body)
  296. return err
  297. }
  298. // SetViewOffset func
  299. func (server *Server) SetViewOffset(key, offset string) error {
  300. url := server.host + "/:/progress?key=" + key + "&identifier=com.plexapp.plugins.library&time=" + offset
  301. log.WithField("url", url).WithField("server", server.Name).Debug("SetViewOffset.GET")
  302. req, err := http.NewRequest("GET", url, nil)
  303. if err != nil {
  304. return err
  305. }
  306. server.setHeader(req)
  307. resp, err := server.api.client.Do(req)
  308. if err != nil {
  309. return err
  310. }
  311. defer resp.Body.Close()
  312. body, err := ioutil.ReadAll(resp.Body)
  313. if err != nil {
  314. return err
  315. }
  316. log.WithField("url", url).WithField("server", server.Name).Debugf("SetViewOffset.RESULT\n%s", body)
  317. return err
  318. }