plex-ruby 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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