filmbuff 0.1.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.
@@ -0,0 +1,7 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .DS_Store
6
+ .rvmrc
7
+ .rspec
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/History ADDED
@@ -0,0 +1,16 @@
1
+ == 0.1.0 2011-03-06
2
+
3
+ * major enhancements
4
+ * Rewrote everything from the ground up. A lot of unnecessary functionality
5
+ has been removed.
6
+ * Added RSpec tests
7
+
8
+ == 0.0.2 2010-09-30
9
+
10
+ * minor enhancements
11
+ * Renamed "HISTORY" to "History"
12
+
13
+ == 0.0.1 2010-09-06
14
+
15
+ * major enhancements
16
+ * Alpha release - Code needs to be cleaned up before 0.1.0
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Kristoffer Sachse
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "filmbuff/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "filmbuff"
7
+ s.version = Filmbuff::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Kristoffer Sachse"]
10
+ s.email = ["kristoffer@sachse.nu"]
11
+ s.homepage = "https://github.com/sachse/filmbuff"
12
+ s.summary = %q{A Ruby wrapper for IMDb's JSON API}
13
+ s.description = %q{Film Buff provides a Ruby wrapper for IMDb's JSON API, which is the fastest and easiest way to get information from IMDb.}
14
+
15
+ s.rubyforge_project = "filmbuff"
16
+
17
+ s.required_ruby_version = ">= 1.9.2"
18
+
19
+ s.add_dependency("httparty", "0.7.4")
20
+
21
+ s.add_development_dependency("rspec", "2.5.0")
22
+
23
+ s.files = `git ls-files`.split("\n")
24
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
25
+ s.require_paths = ["lib"]
26
+ end
@@ -0,0 +1,6 @@
1
+ require 'httparty'
2
+
3
+ dir = File.expand_path(File.dirname(__FILE__))
4
+ require File.join(dir, 'filmbuff', 'httparty_icebox')
5
+ require File.join(dir, 'filmbuff', 'imdb')
6
+ require File.join(dir, 'filmbuff', 'title')
@@ -0,0 +1,283 @@
1
+ # = Icebox : Caching for HTTParty
2
+ #
3
+ # Cache responses in HTTParty models [http://github.com/jnunemaker/httparty]
4
+ #
5
+ # === Usage
6
+ #
7
+ # class Foo
8
+ # include HTTParty
9
+ # include HTTParty::Icebox
10
+ # cache :store => 'file', :timeout => 600, :location => MY_APP_ROOT.join('tmp', 'cache')
11
+ # end
12
+ #
13
+ # Modeled after Martyn Loughran's APICache [http://github.com/newbamboo/api_cache]
14
+ # and Ruby On Rails's caching [http://api.rubyonrails.org/classes/ActiveSupport/Cache.html]
15
+ #
16
+ # Author: Karel Minarik [www.karmi.cz]
17
+ #
18
+ # === Notes
19
+ #
20
+ # Thanks to Amit Chakradeo for pointing out response objects have to be stored marhalled on FS
21
+ # Thanks to Marlin Forbes for pointing out the query parameters have to be included in the cache key
22
+ #
23
+ #
24
+
25
+ require 'logger'
26
+ require 'fileutils'
27
+ require 'tmpdir'
28
+ require 'pathname'
29
+ require 'digest/md5'
30
+
31
+ module HTTParty #:nodoc:
32
+ # == Caching for HTTParty
33
+ # See documentation in HTTParty::Icebox::ClassMethods.cache
34
+ #
35
+ module Icebox
36
+
37
+ module ClassMethods
38
+
39
+ # Enable caching and set cache options
40
+ # Returns memoized cache object
41
+ #
42
+ # Following options are available, default values are in []:
43
+ #
44
+ # +store+:: Storage mechanism for cached data (memory, filesystem, your own) [memory]
45
+ # +timeout+:: Cache expiration in seconds [60]
46
+ # +logger+:: Path to logfile or logger instance [nil, silent]
47
+ #
48
+ # Any additional options are passed to the Cache constructor
49
+ #
50
+ # Usage:
51
+ #
52
+ # # Enable caching in HTTParty, in memory, for 1 minute
53
+ # cache # Use default values
54
+ #
55
+ # # Enable caching in HTTParty, on filesystem (/tmp), for 10 minutes
56
+ # cache :store => 'file', :timeout => 600, :location => '/tmp/'
57
+ #
58
+ # # Use your own cache store (see +AbstractStore+ class below)
59
+ # cache :store => 'memcached', :timeout => 600, :server => '192.168.1.1:1001'
60
+ #
61
+ def cache(options={})
62
+ options[:store] ||= 'memory'
63
+ options[:timeout] ||= 60
64
+ logger = options[:logger]
65
+ @cache ||= Cache.new( options.delete(:store), options )
66
+ end
67
+
68
+ end
69
+
70
+ # When included, extend class with +cache+ method
71
+ # and redefine +get+ method to use cache
72
+ #
73
+ def self.included(receiver) #:nodoc:
74
+ receiver.extend ClassMethods
75
+ receiver.class_eval do
76
+
77
+ # Get reponse from network
78
+ #
79
+ # TODO: Why alias :new :old is not working here? Returns NoMethodError
80
+ #
81
+ def self.get_without_caching(path, options={})
82
+ perform_request Net::HTTP::Get, path, options
83
+ end
84
+
85
+ # Get response from cache, if available
86
+ #
87
+ def self.get_with_caching(path, options={})
88
+ key = path.downcase # this makes a copy of path
89
+ key << options[:query].to_s if defined? options[:query]
90
+ if cache.exists?(key) and not cache.stale?(key)
91
+ Cache.logger.debug "CACHE -- GET #{path}#{options[:query]}"
92
+ return cache.get(key)
93
+ else
94
+ Cache.logger.debug "/!\\ NETWORK -- GET #{path}#{options[:query]}"
95
+ response = get_without_caching(path, options)
96
+ cache.set(key, response) if response.code.to_s == "200" # this works for string and integer response codes
97
+ return response
98
+ end
99
+ end
100
+
101
+ # Redefine original HTTParty +get+ method to use cache
102
+ #
103
+ def self.get(path, options={})
104
+ self.get_with_caching(path, options)
105
+ end
106
+
107
+ end
108
+ end
109
+
110
+ # === Cache container
111
+ #
112
+ # Pass a store name ('memory', etc) to new
113
+ #
114
+ class Cache
115
+ attr_accessor :store
116
+
117
+ def initialize(store, options={})
118
+ self.class.logger = options[:logger]
119
+ @store = self.class.lookup_store(store).new(options)
120
+ end
121
+
122
+ def get(key)
123
+ @store.get encode(key) unless stale?(key)
124
+ end
125
+
126
+ def set(key, value)
127
+ # puts "Cache.set, key: #{key}, value: #{value}"
128
+ @store.set encode(key), value
129
+ end
130
+
131
+ def exists?(key)
132
+ @store.exists? encode(key)
133
+ end
134
+
135
+ def stale?(key)
136
+ @store.stale? encode(key)
137
+ end
138
+
139
+ def self.logger
140
+ @logger || default_logger
141
+ end
142
+
143
+ def self.default_logger
144
+ logger = ::Logger.new(STDERR)
145
+ end
146
+
147
+ # Pass a filename (String), IO object, Logger instance or +nil+ to silence the logger
148
+ def self.logger=(device)
149
+ @logger = device.kind_of?(::Logger) ? device : ::Logger.new(device)
150
+ end
151
+
152
+ private
153
+ # Return store class based on passed name
154
+ def self.lookup_store(name)
155
+ store_name = "#{name.capitalize}Store"
156
+ return Store::const_get(store_name)
157
+ rescue NameError => e
158
+ raise Store::StoreNotFound, "The cache store '#{store_name}' was not found. Did you load any such class?"
159
+ end
160
+
161
+ def encode(key)
162
+ Digest::MD5.hexdigest(key)
163
+ end
164
+ end
165
+
166
+
167
+ # === Cache stores
168
+ #
169
+ module Store
170
+
171
+ class StoreNotFound < StandardError; end #:nodoc:
172
+
173
+ # ==== Abstract Store
174
+ # Inherit your store from this class
175
+ # *IMPORTANT*: Do not forget to call +super+ in your +initialize+ method!
176
+ #
177
+ class AbstractStore
178
+ def initialize(options={})
179
+ raise ArgumentError, "You need to set the :timeout parameter" unless options[:timeout]
180
+ @timeout = options[:timeout]
181
+ message = "Cache: Using #{self.class.to_s.split('::').last}"
182
+ message << " in location: #{options[:location]}" if options[:location]
183
+ message << " with timeout #{options[:timeout]} sec"
184
+ Cache.logger.info message unless options[:logger].nil?
185
+ return self
186
+ end
187
+ %w{set get exists? stale?}.each do |method_name|
188
+ define_method(method_name) { raise NoMethodError, "Please implement method #{method_name} in your store class" }
189
+ end
190
+ end
191
+
192
+ # ==== Store objects in memory
193
+ # See HTTParty::Icebox::ClassMethods.cache
194
+ #
195
+ class MemoryStore < AbstractStore
196
+ def initialize(options={})
197
+ super; @store = {}; self
198
+ end
199
+ def set(key, value)
200
+ Cache.logger.info("Cache: set (#{key})")
201
+ @store[key] = [Time.now, value]; true
202
+ end
203
+ def get(key)
204
+ data = @store[key][1]
205
+ Cache.logger.info("Cache: #{data.nil? ? "miss" : "hit"} (#{key})")
206
+ data
207
+ end
208
+ def exists?(key)
209
+ !@store[key].nil?
210
+ end
211
+ def stale?(key)
212
+ return true unless exists?(key)
213
+ Time.now - created(key) > @timeout
214
+ end
215
+ private
216
+ def created(key)
217
+ @store[key][0]
218
+ end
219
+ end
220
+
221
+ # ==== Store objects on the filesystem
222
+ # See HTTParty::Icebox::ClassMethods.cache
223
+ #
224
+ class FileStore < AbstractStore
225
+ def initialize(options={})
226
+ super
227
+ options[:location] ||= Dir::tmpdir
228
+ @path = Pathname.new( options[:location] )
229
+ FileUtils.mkdir_p( @path )
230
+ self
231
+ end
232
+ def set(key, value)
233
+ Cache.logger.info("Cache: set (#{key})")
234
+ File.open( @path.join(key), 'w' ) { |file| file << Marshal.dump(value) }
235
+ true
236
+ end
237
+ def get(key)
238
+ data = Marshal.load(File.read( @path.join(key)))
239
+ Cache.logger.info("Cache: #{data.nil? ? "miss" : "hit"} (#{key})")
240
+ data
241
+ end
242
+ def exists?(key)
243
+ File.exists?( @path.join(key) )
244
+ end
245
+ def stale?(key)
246
+ return true unless exists?(key)
247
+ Time.now - created(key) > @timeout
248
+ end
249
+ private
250
+ def created(key)
251
+ File.mtime( @path.join(key) )
252
+ end
253
+ end
254
+ end
255
+
256
+ end
257
+ end
258
+
259
+
260
+ # Major parts of this code are based on architecture of ApiCache.
261
+ # Copyright (c) 2008 Martyn Loughran
262
+ #
263
+ # Other parts are inspired by the ActiveSupport::Cache in Ruby On Rails.
264
+ # Copyright (c) 2005-2009 David Heinemeier Hansson
265
+ #
266
+ # Permission is hereby granted, free of charge, to any person obtaining
267
+ # a copy of this software and associated documentation files (the
268
+ # "Software"), to deal in the Software without restriction, including
269
+ # without limitation the rights to use, copy, modify, merge, publish,
270
+ # distribute, sublicense, and/or sell copies of the Software, and to
271
+ # permit persons to whom the Software is furnished to do so, subject to
272
+ # the following conditions:
273
+ #
274
+ # The above copyright notice and this permission notice shall be
275
+ # included in all copies or substantial portions of the Software.
276
+ #
277
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
278
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
279
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
280
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
281
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
282
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
283
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,46 @@
1
+ module FilmBuff
2
+ class IMDb
3
+ attr_reader :locale
4
+
5
+ include HTTParty
6
+ include HTTParty::Icebox
7
+ cache :store => 'file', :timeout => 120, :location => Dir.tmpdir
8
+
9
+ base_uri 'app.imdb.com'
10
+ default_params = {
11
+ "api" => "v1",
12
+ "app_id" => "iphone1_1",
13
+ "locale" => @locale,
14
+ "timestamp" => Time.now.utc.to_i,
15
+ "sig" => "app1_1"
16
+ }
17
+
18
+ def initialize
19
+ @locale = "en_US"
20
+ end
21
+
22
+ public
23
+ def locale=(locale)
24
+ locales = %w[ de_DE en_US es_ES fr_FR it_IT pt_PT ]
25
+
26
+ if locales.include? locale
27
+ @locale = locale
28
+ else
29
+ raise "Unknown locale. Only the following are allowed:\n" <<
30
+ locales.join(", ")
31
+ end
32
+ end
33
+
34
+ def find_by_id(imdb_id)
35
+ result = self.class.get('/title/maindetails', :query => {
36
+ tconst: imdb_id
37
+ }).parsed_response
38
+ Title.new(result["data"])
39
+ end
40
+
41
+ def find_by_title(title)
42
+ results = self.class.get('/find', :query => { q: title }).parsed_response
43
+ find_by_id(results["data"]["results"][0]["list"][0]["tconst"])
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,21 @@
1
+ module FilmBuff
2
+ class Title
3
+ attr_accessor :imdb_id, :title, :tagline, :plot, :runtime, :rating, :votes,
4
+ :poster_url, :genres, :release_date
5
+
6
+ def initialize(options = {})
7
+ @imdb_id = options["tconst"]
8
+ @title = options["title"]
9
+ @tagline = options["tagline"]
10
+ @plot = options["plot"]["outline"] if options["plot"]
11
+ @runtime = "#{(options["runtime"]["time"]/60).to_i} min" if
12
+ options["runtime"]
13
+ @rating = options["rating"]
14
+ @votes = options["num_votes"]
15
+ @poster_url = options["image"]["url"] if options["image"]
16
+ @genres = options["genres"] || []
17
+ @release_date = options["release_date"]["normal"] if
18
+ options["release_date"]["normal"]
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,3 @@
1
+ module Filmbuff
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,50 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '../spec_helper'))
2
+
3
+ describe FilmBuff::IMDb do
4
+ before(:all) do
5
+ @imdb = FilmBuff::IMDb.new
6
+ end
7
+
8
+ describe "#locale" do
9
+ it "returns the locale" do
10
+ @imdb.locale.should == "en_US"
11
+ end
12
+ end
13
+
14
+ describe "#locale=" do
15
+ context "given valid locale" do
16
+ it "sets locale to the given value" do
17
+ @imdb.locale = "de_DE"
18
+ @imdb.locale.should == "de_DE"
19
+ end
20
+ end
21
+
22
+ context "given invalid locale" do
23
+ it "raises an exception" do
24
+ lambda { @imdb.locale = "da_DK" }.should raise_error StandardError
25
+ end
26
+ end
27
+ end
28
+
29
+ describe "#find_by_id" do
30
+ context "given valid ID" do
31
+ before(:all) do
32
+ @title = @imdb.find_by_id("tt0032138")
33
+ end
34
+
35
+ it "returns a Title" do
36
+ @title.instance_of?(FilmBuff::Title).should be_true
37
+ end
38
+ end
39
+ end
40
+
41
+ describe "#find_by_title" do
42
+ before(:all) do
43
+ @title = @imdb.find_by_title("The Wizard of Oz")
44
+ end
45
+
46
+ it "returns a Title" do
47
+ @title.instance_of?(FilmBuff::Title).should be_true
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,51 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '../spec_helper'))
2
+
3
+ describe FilmBuff::Title do
4
+ before(:all) do
5
+ @imdb = FilmBuff::IMDb.new
6
+ @title = @imdb.find_by_id("tt0032138")
7
+ end
8
+
9
+ it "has an IMDb ID" do
10
+ @title.imdb_id.should == "tt0032138"
11
+ end
12
+
13
+ it "has a title" do
14
+ @title.title.should == "The Wizard of Oz"
15
+ end
16
+
17
+ it "has a tagline" do
18
+ @title.tagline.should == "\"The Wizard\" Musical Returns By " <<
19
+ "Unprecedented Demand! [UK re-release]"
20
+ end
21
+
22
+ it "has a plot" do
23
+ @title.plot.should == "Dorothy Gale is swept away to a magical land in " <<
24
+ "a tornado and embarks on a quest to see the Wizard who can help her " <<
25
+ "return home."
26
+ end
27
+
28
+ it "has a runtime" do
29
+ @title.runtime.should == "101 min"
30
+ end
31
+
32
+ it "has a rating" do
33
+ @title.rating.should be_a(Float)
34
+ end
35
+
36
+ it "has an amount of votes" do
37
+ @title.votes.should be_a(Integer)
38
+ end
39
+
40
+ it "has a poster URL" do
41
+ @title.poster_url.should match /http:\/\/ia.media-imdb.com\/images\/.*/
42
+ end
43
+
44
+ it "has genres" do
45
+ @title.genres.should == %w[ Adventure Comedy Family Fantasy Musical]
46
+ end
47
+
48
+ it "has a release date" do
49
+ @title.release_date.should == DateTime.parse("1939-08-25")
50
+ end
51
+ end
File without changes
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'filmbuff')
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: filmbuff
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.0
6
+ platform: ruby
7
+ authors:
8
+ - Kristoffer Sachse
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-03-06 00:00:00 +01:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: httparty
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - "="
23
+ - !ruby/object:Gem::Version
24
+ version: 0.7.4
25
+ type: :runtime
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - "="
34
+ - !ruby/object:Gem::Version
35
+ version: 2.5.0
36
+ type: :development
37
+ version_requirements: *id002
38
+ description: Film Buff provides a Ruby wrapper for IMDb's JSON API, which is the fastest and easiest way to get information from IMDb.
39
+ email:
40
+ - kristoffer@sachse.nu
41
+ executables: []
42
+
43
+ extensions: []
44
+
45
+ extra_rdoc_files: []
46
+
47
+ files:
48
+ - .gitignore
49
+ - Gemfile
50
+ - History
51
+ - LICENSE
52
+ - Rakefile
53
+ - filmbuff.gemspec
54
+ - lib/filmbuff.rb
55
+ - lib/filmbuff/httparty_icebox.rb
56
+ - lib/filmbuff/imdb.rb
57
+ - lib/filmbuff/title.rb
58
+ - lib/filmbuff/version.rb
59
+ - spec/filmbuff/imdb_spec.rb
60
+ - spec/filmbuff/title_spec.rb
61
+ - spec/filmbuff_spec.rb
62
+ - spec/spec_helper.rb
63
+ has_rdoc: true
64
+ homepage: https://github.com/sachse/filmbuff
65
+ licenses: []
66
+
67
+ post_install_message:
68
+ rdoc_options: []
69
+
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: 1.9.2
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: "0"
84
+ requirements: []
85
+
86
+ rubyforge_project: filmbuff
87
+ rubygems_version: 1.6.0
88
+ signing_key:
89
+ specification_version: 3
90
+ summary: A Ruby wrapper for IMDb's JSON API
91
+ test_files:
92
+ - spec/filmbuff/imdb_spec.rb
93
+ - spec/filmbuff/title_spec.rb
94
+ - spec/filmbuff_spec.rb
95
+ - spec/spec_helper.rb