plex-ruby 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore CHANGED
@@ -1,4 +1,5 @@
1
1
  *.gem
2
2
  .bundle
3
3
  Gemfile.lock
4
+ .rake_tasks~
4
5
  pkg/*
data/CHANGELOG.md CHANGED
@@ -3,3 +3,14 @@
3
3
  * Initial Release
4
4
  * Browsing a library works
5
5
  * Sending commands to a clients works intermittently, needs lots of work
6
+
7
+ ## 0.1.0
8
+
9
+ * Gem released of RubyGems
10
+
11
+ ## 0.2.0
12
+
13
+ * Added documentation
14
+ * Added bang methods that clears caches
15
+ * Fix naming of 'libary' to 'library'
16
+ * Moved static content into Constants (Wow!\s)
data/README.md CHANGED
@@ -13,6 +13,14 @@ Add to your `Gemfile` and run the `bundle` command
13
13
  gem 'plex-ruby'
14
14
  ```
15
15
 
16
+ Or
17
+
18
+ ```ruby
19
+ gem install plex-ruby
20
+
21
+ require 'plex-ruby'
22
+ ```
23
+
16
24
  I developed this using Ruby 1.9.2 so no guaranties that it will work with
17
25
  lesser versions of Ruby.
18
26
 
data/lib/plex-ruby.rb CHANGED
@@ -4,6 +4,11 @@ require 'cgi'
4
4
 
5
5
  module Plex
6
6
 
7
+ # Converts camel case names that are commonly found in the Plex APIs into
8
+ # ruby friendly names. I.E. playMedia -> play_media
9
+ #
10
+ # @param [String] camel case name to be converted
11
+ # @return [String] snake case form
7
12
  def self.snake_case(string)
8
13
  string.gsub(/::/, '/').
9
14
  gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
@@ -12,6 +17,10 @@ module Plex
12
17
  downcase
13
18
  end
14
19
 
20
+ # The base url of the Plex Media Server, I.E. 'http://localhost:32400'
21
+ # !WARNING! This method will most likely be replaced in future versions
22
+ #
23
+ # @return [String] bases url of the Plex Media Server
15
24
  def self.url
16
25
  @@base_url
17
26
  end
@@ -25,7 +34,7 @@ end
25
34
  require 'plex-ruby/parser'
26
35
  require 'plex-ruby/server'
27
36
  require 'plex-ruby/client'
28
- require 'plex-ruby/libary'
37
+ require 'plex-ruby/library'
29
38
  require 'plex-ruby/section'
30
39
  require 'plex-ruby/video'
31
40
  require 'plex-ruby/media'
@@ -1,6 +1,12 @@
1
1
  module Plex
2
2
  class Client
3
3
 
4
+ NAV_METHODS = %w(moveUp moveDown moveLeft moveRight pageUp pageDown nextLetter
5
+ previousLetter select back contextMenu toggleOSD)
6
+
7
+ PLAYBACK_METHODS = %w(play pause stop rewind fastForward stepForward
8
+ bigStepForward stepBack bigStepBack skipNext skipPrevious)
9
+
4
10
  attr_reader :name, :host, :address, :port, :machine_identifier, :version
5
11
 
6
12
  def initialize(node)
@@ -12,8 +18,12 @@ module Plex
12
18
  @version = node.attr('version')
13
19
  end
14
20
 
15
- %w(moveUp moveDown moveLeft moveRight pageUp pageDown nextLetter previousLetter
16
- select back contextMenu toggleOSD).each { |nav|
21
+ # Navigation methods
22
+ # Sends a movement command to the client to move around menus and such
23
+ #
24
+ # @return [True, nil] true if it worked, nil if something went wrong check
25
+ # the console for the error message
26
+ NAV_METHODS.each { |nav|
17
27
  class_eval %(
18
28
  def #{Plex.snake_case(nav)}
19
29
  ping player_url+'/navigation/#{nav}'
@@ -21,8 +31,12 @@ module Plex
21
31
  )
22
32
  }
23
33
 
24
- %w(play pause stop rewind fastForward stepForward bigStepForward stepBack
25
- bigStepBack skipNext skipPrevious).each { |playback|
34
+ # Playback methods
35
+ # Sends a playback command to the client to play / pause videos and such
36
+ #
37
+ # @return [True, nil] true if it worked, nil if something went wrong check
38
+ # the console for the error message
39
+ PLAYBACK_METHODS.each { |playback|
26
40
  class_eval %(
27
41
  def #{Plex.snake_case(playback)}
28
42
  ping player_url+'/playback/#{playback}'
@@ -34,6 +48,15 @@ module Plex
34
48
  ping player_url+"/application/playFile"
35
49
  end
36
50
 
51
+ # Plays a video that is in the library
52
+ #
53
+ # @param [String] the key of the video that we want to play. (see
54
+ # Episode#key) (see Movie#key)
55
+ # @param [String] no clue what this does, its the Plex Remote Command API though
56
+ # @param [String] no clue what this does, its the Plex Remote Command API though
57
+ # @param [String] no clue what this does, its the Plex Remote Command API though
58
+ # @return [True, nil] true if it worked, nil if something went wrong check
59
+ # the console for the error message
37
60
  def play_media(key, user_agent = nil, http_cookies = nil, view_offset = nil)
38
61
  url = player_url+'/application/playMedia?'
39
62
  url += "path=#{CGI::escape(Plex.url+key)}"
@@ -45,22 +68,47 @@ module Plex
45
68
  ping url
46
69
  end
47
70
 
71
+ # Take a screenshot of whats on the Plex Client
72
+ #
73
+ # @param [String, Fixnum] width of the screenshot
74
+ # @param [String, Fixnum] height of the screenshot
75
+ # @param [String, Fixnum] quality of the screenshot
76
+ # @return [True, nil] true if it worked, nil if something went wrong check
77
+ # the console for the error message
48
78
  def screenshot(width, height, quality)
49
79
  url = player_url+'/application/screenshot?'
50
80
  url += "width=#{width}"
51
81
  url += "&height=#{height}"
82
+ url += "&quality=#{quality}"
83
+
84
+ ping url
52
85
  end
53
86
 
87
+ # Sends a string message to the Plex Client
88
+ #
89
+ # @param [String] message to send
90
+ # @return [True, nil] true if it worked, nil if something went wrong check
91
+ # the console for the error message
54
92
  def send_string(text)
55
- ping player_url+"/application/sendString?text=#{CGI::escape(text)}"
93
+ ping player_url+"/application/sendString?text=#{CGI::escape(text.to_s)}"
56
94
  end
57
95
 
96
+ # Sends a key code to the Plex Client. Key codes represent key presses on
97
+ # a keyboard. Codes are the ASCII value of the letter one wants pressed.
98
+ # It should be noted that the Plex devs have told people to try and avoid
99
+ # using this method when writing plugins, as different users can have
100
+ # different key mappings.
101
+ #
102
+ # @param [String, Fixnum] key code to send
103
+ # @return [True, nil] true if it worked, nil if something went wrong check
104
+ # the console for the error message
58
105
  def send_key(code)
59
- ping player_url+"/application/sendKey?code=#{CGI::escape(code)}"
106
+ ping player_url+"/application/sendKey?code=#{CGI::escape(code.to_s)}"
60
107
  end
61
108
 
109
+ # (see #send_key)
62
110
  def send_virtual_key(code)
63
- ping player_url+"/application/sendVirtualKey?code=#{CGI::escape(code)}"
111
+ ping player_url+"/application/sendVirtualKey?code=#{CGI::escape(code.to_s)}"
64
112
  end
65
113
 
66
114
  private
@@ -7,6 +7,8 @@ module Plex
7
7
  @key = key
8
8
  end
9
9
 
10
+ # Delegates all method calls to the video object that represents this
11
+ # episode, if that video object responds to the method.
10
12
  def method_missing(method, *args, &block)
11
13
  if video.respond_to? method
12
14
  video.send(method, *args, &block)
@@ -0,0 +1,54 @@
1
+ module Plex
2
+ class Library
3
+
4
+ # Grab a specific section
5
+ #
6
+ # @param [String, Fixnum] key of the section we want
7
+ # @return [Section] section with that key
8
+ def section(id)
9
+ search_sections(xml_doc, id).first
10
+ end
11
+
12
+ # Cache busting version of #section
13
+ def section!(id)
14
+ search_sections(xml_doc!, id).first
15
+ end
16
+
17
+ # A list of sections that are located in this library
18
+ #
19
+ # @return [Array] list of sections
20
+ def sections
21
+ @sections ||= search_sections(xml_doc)
22
+ end
23
+
24
+ # Cache busting version of #sections
25
+ def sections!
26
+ @sections = search_sections(xml_doc!)
27
+ end
28
+
29
+ def key
30
+ "/library/sections"
31
+ end
32
+
33
+ private
34
+
35
+ def search_sections(doc, key = nil)
36
+ term = key ? "Directory[@key='#{key}']" : 'Directory'
37
+ doc.search(term).map { |m| Plex::Section.new(m.attributes) }
38
+ end
39
+
40
+ def xml_doc
41
+ @xml_doc ||= base_doc
42
+ end
43
+
44
+ def xml_doc!
45
+ @xml_doc = base_doc
46
+ end
47
+
48
+ def base_doc
49
+ Nokogiri::XML( open(Plex.url+key) )
50
+ end
51
+
52
+
53
+ end
54
+ end
@@ -7,6 +7,8 @@ module Plex
7
7
  @key = key
8
8
  end
9
9
 
10
+ # Delegates all method calls to the video object that represents this
11
+ # movie, if that video object responds to the method.
10
12
  def method_missing(method, *args, &block)
11
13
  if video.respond_to? method
12
14
  video.send(method, *args, &block)
@@ -7,6 +7,13 @@ module Plex
7
7
  @node = node
8
8
  end
9
9
 
10
+ # Parses a XML node and returns the structure it represents. This is
11
+ # currently used to parse Sections as we don't know whether a Section holds
12
+ # a list of Shows or a list of Movies. The parsing is done recursively.
13
+ #
14
+ # @return [Array, Movie, Episode, Show] depending on what node it is given,
15
+ # will return an Array of Movies or Shows, a single Movie, and single
16
+ # Episode, or a single Show
10
17
  def parse
11
18
  case node.name
12
19
  when 'document'
@@ -1,38 +1,78 @@
1
1
  module Plex
2
- # Found at /libary/metadata/:key
2
+ # Found at /library/metadata/:key
3
3
  class Season
4
4
 
5
+ ATTRIBUTES = %w(ratingKey guid type title summary index thumb leafCount
6
+ viewedLeafCount addedAt updatedAt)
7
+
5
8
  attr_reader :key
6
9
 
7
10
  def initialize(key)
8
11
  @key = key
9
12
  end
10
13
 
11
- %w(ratingKey guid type title summary index thumb leafCount viewedLeafCount
12
- addedAt updatedAt).each { |method|
14
+ # A Season has a key, which allows us to do lazy loading. A season will
15
+ # not be fully loaded unless one of its attributes is called. Then the
16
+ # Season will load itself from its key. Once loaded it caches its self.
17
+ # For every attribute there is a cache busting version wich is just the
18
+ # name of the attribute followed by '!'. For exsample <tt>season.type</tt>
19
+ # and <tt>season.type!</tt>
20
+ ATTRIBUTES.each { |method|
13
21
  class_eval %(
14
22
  def #{Plex.snake_case(method)}; directory.attr('#{method}') end
23
+ def #{Plex.snake_case(method)}!; directory!.attr('#{method}') end
15
24
  )
16
25
  }
17
26
 
27
+ # Returns the list of episodes in the library that are a part of this Season
28
+ #
29
+ # @return [Array] list of episodes in this season that are on the server
18
30
  def episodes
19
- @episodes ||=
20
- children.search("Video").map { |m| Plex::Episode.new(m.attr('key')) }
31
+ @episodes ||= episodes_from_video(children)
32
+ end
33
+
34
+ # Cache busting version of #episodes
35
+ def episodes!
36
+ @episodes = episodes_from_video(children!)
21
37
  end
22
38
 
23
39
  private
24
40
 
41
+ def base_doc
42
+ Nokogiri::XML( open(Plex.url+key) )
43
+ end
44
+
45
+ def base_children_doc
46
+ Nokogiri::XML( open(Plex.url+key+'/children') )
47
+ end
48
+
25
49
  def xml_doc
26
- @xml_doc ||= Nokogiri::XML( open(Plex.url+key) )
50
+ @xml_doc ||= base_doc
51
+ end
52
+
53
+ def xml_doc!
54
+ @xml_doc = base_doc
27
55
  end
28
56
 
29
57
  def children
30
- @children ||= Nokogiri::XML( open(Plex.url+key+'/children') )
58
+ @children ||= base_children_doc
59
+ end
60
+
61
+ def children!
62
+ @children = base_children_doc
63
+ end
64
+
65
+ def episodes_from_video(node)
66
+ node.search("Video").map { |m| Plex::Episode.new(m.attr('key')) }
31
67
  end
32
68
 
33
69
  def directory
34
70
  @directory ||= xml_doc.search("Directory").first
35
71
  end
36
72
 
73
+ def directory!
74
+ @directory = xml_doc!.search("Directory").first
75
+ end
76
+
37
77
  end
38
78
  end
@@ -1,6 +1,8 @@
1
1
  module Plex
2
2
  class Section
3
3
 
4
+ GROUPS = %w(all unwatched newest recentlyAdded recentlyViewed onDeck)
5
+
4
6
  attr_reader :refreshing, :type, :title, :art, :agent, :scanner, :language,
5
7
  :updated_at
6
8
 
@@ -16,11 +18,22 @@ module Plex
16
18
  @updated_at = options['updatedAt'].value
17
19
  end
18
20
 
21
+ # NOT IMPLEMENTED
19
22
  def refresh(deep = false, force = false)
20
23
  end
21
24
 
22
25
 
23
- %w(all unwatched newest recentlyAdded recentlyViewed onDeck).each { |method|
26
+ # Returns a list of shows or movies that are in this Section.
27
+ #
28
+ # all - all videos in this Section
29
+ # unwatched - videos unwatched in this Section
30
+ # newest - most recent videos in this Section
31
+ # recently_added - recently added videos in this Section
32
+ # recently_viewed - recently viewed videos in this Section
33
+ # on_deck - videos that are "on deck" in this Section
34
+ #
35
+ # @return [Array] list of Shows or Movies in that group
36
+ GROUPS.each { |method|
24
37
  class_eval %(
25
38
  def #{Plex.snake_case(method)}
26
39
  Plex::Parser.new( Nokogiri::XML(open(Plex.url+key+'/#{method}')) ).parse
@@ -9,21 +9,41 @@ module Plex
9
9
  Plex.url = "http://#{host}:#{port}"
10
10
  end
11
11
 
12
- def system
12
+ # The library of this server
13
+ #
14
+ # @return [Library] this Servers library
15
+ def library
16
+ @library ||= Plex::Libary.new
13
17
  end
14
18
 
15
- def libary
16
- @libary ||= Plex::Libary.new
19
+ # The Plex clients that are connected to this Server
20
+ #
21
+ # @return [Array] list of Clients connected to this server
22
+ def clients
23
+ @clients ||= search_clients clients_doc
17
24
  end
18
25
 
19
- def clients
20
- @clients ||= clients_doc.search('Server').map { |m| Plex::Client.new(m) }
26
+ # Cache busting version of #clients
27
+ def clients!
28
+ @clients = search_clients clients_doc!
21
29
  end
22
30
 
23
31
  private
24
32
 
33
+ def clients_base
34
+ Nokogiri::XML( open(Plex.url+'/clients') )
35
+ end
36
+
25
37
  def clients_doc
26
- @clients_doc ||= Nokogiri::XML( open(Plex.url+'/clients') )
38
+ @clients_doc ||= clients_base
39
+ end
40
+
41
+ def clients_doc!
42
+ @clients_doc = clients_base
43
+ end
44
+
45
+ def search_clients(node)
46
+ node.search('Server').map { |m| Plex::Client.new(m) }
27
47
  end
28
48
 
29
49
  end
@@ -1,40 +1,76 @@
1
1
  module Plex
2
- # Found at /libary/metadata/:key
2
+ # Found at /library/metadata/:key
3
3
  class Show
4
4
 
5
+ ATTRIBUTES = %w(guid studio title contentRating summary index rating year thumb
6
+ art banner theme duration originallyAvailableAt leafCount
7
+ viewedLeafCount addedAt updatedAt)
8
+
5
9
  attr_reader :key
6
10
 
7
11
  def initialize(key)
8
12
  @key = key
9
13
  end
10
14
 
11
- %w(guid studio title contentRating summary index rating year thumb art banner
12
- theme duration originallyAvailableAt leafCount viewedLeafCount addedAt
13
- updatedAt).each { |method|
15
+ # A Show has a key, which allows us to do lazy loading. A Show will
16
+ # not be fully loaded unless one of its attributes is called. Then the
17
+ # Show will load itself from its key. Once loaded it caches its self.
18
+ ATTRIBUTES.each { |method|
14
19
  class_eval %(
15
20
  def #{Plex.snake_case(method)}; @#{method} ||= directory.attr('#{method}') end
21
+ def #{Plex.snake_case(method)}!; @#{method} = directory!.attr('#{method}') end
16
22
  )
17
23
  }
18
24
 
25
+ # The list of seasons in the library that belong to this Show
26
+ #
27
+ # @return [Array] list of Seasons that are a part of this Show
19
28
  def seasons
20
- @seasons ||=
21
- children.search('Directory').map do |season|
22
- Plex::Season.new(season.attr('key')[0..-10]) # Remove /children
23
- end.compact
29
+ @seasons ||= search_children children
30
+ end
31
+
32
+ def seasons!
33
+ @seasons = search_children children!
24
34
  end
25
35
 
26
36
  private
27
37
 
38
+ def base_doc
39
+ Nokogiri::XML( open(Plex.url+key) )
40
+ end
41
+
42
+ def children_base
43
+ Nokogiri::XML( open(Plex.url+key+'/children') )
44
+ end
45
+
46
+ def xml_doc
47
+ @xml_doc ||= base_doc
48
+ end
49
+
50
+ def xml_doc!
51
+ @xml_doc = base_doc
52
+ end
53
+
28
54
  def children
29
- @children ||= Nokogiri::XML( open(Plex.url+key+'/children') )
55
+ @children ||= children_base
56
+ end
57
+
58
+ def children!
59
+ @children = children_base
30
60
  end
31
61
 
32
62
  def directory
33
63
  @directory ||= xml_doc.search('Directory').first
34
64
  end
35
-
36
- def xml_doc
37
- @xml_doc ||= Nokogiri::XML( open(Plex.url+key) )
65
+
66
+ def directory!
67
+ @directory = xml_doc!.search('Directory').first
68
+ end
69
+
70
+ def search_children(node)
71
+ node.search('Directory').map do |season|
72
+ Plex::Season.new(season.attr('key')[0..-10]) # Remove /children
73
+ end
38
74
  end
39
75
 
40
76
  end
@@ -1,3 +1,3 @@
1
1
  module Plex
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: plex-ruby
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.1.0
5
+ version: 0.2.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - Eric Koslow
@@ -65,7 +65,7 @@ files:
65
65
  - lib/plex-ruby.rb
66
66
  - lib/plex-ruby/client.rb
67
67
  - lib/plex-ruby/episode.rb
68
- - lib/plex-ruby/libary.rb
68
+ - lib/plex-ruby/library.rb
69
69
  - lib/plex-ruby/media.rb
70
70
  - lib/plex-ruby/movie.rb
71
71
  - lib/plex-ruby/null.rb
@@ -1,25 +0,0 @@
1
- module Plex
2
- class Libary
3
-
4
- def section(id)
5
- xml_doc.search("Directory[@key='#{id}']").map do |m|
6
- Plex::Section.new(m.attributes)
7
- end.first
8
- end
9
-
10
- def sections
11
- xml_doc.search('Directory').map { |m| Plex::Section.new(m.attributes) }
12
- end
13
-
14
- def key
15
- "/library/sections"
16
- end
17
-
18
- private
19
-
20
- def xml_doc
21
- @xml_doc ||= Nokogiri::XML( open(Plex.url+key) )
22
- end
23
-
24
- end
25
- end