filmbuff 0.1.0

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