Mass rewrite

This commit is contained in:
Gregory Eremin 2012-10-12 16:45:51 +04:00
parent 3bf68d152e
commit 7c42a90a6c
46 changed files with 656 additions and 696 deletions

5
.gitignore vendored
View File

@ -1,8 +1,5 @@
coverage coverage
rdoc
doc
.yardoc
.bundle .bundle
pkg
tmp tmp
Gemfile.lock Gemfile.lock
.gem

View File

@ -1,4 +1,3 @@
rvm: rvm:
- 1.8.7
- 1.9.2 - 1.9.2
- 1.9.3 - 1.9.3

View File

@ -3,3 +3,4 @@ Huge thanks to that people for making this gem better!
Applicat (https://github.com/Applicat) Applicat (https://github.com/Applicat)
Jens Fahnenbruck (https://github.com/jigfox) Jens Fahnenbruck (https://github.com/jigfox)
Sander Nieuwenhuizen (https://github.com/munkius) Sander Nieuwenhuizen (https://github.com/munkius)
Savater Sebastien (https://github.com/blakink)

View File

@ -1,13 +0,0 @@
#!/usr/bin/env rake
require "bundler/gem_tasks"
require "rspec/core/rake_task"
RSpec::Core::RakeTask.new("spec")
task :default => :spec
task :test => :spec
desc "Open an irb session preloaded with this library"
task :console do
sh "irb -rubygems -I lib -r musicbrainz.rb"
end

View File

@ -1,25 +0,0 @@
# -*- encoding: utf-8 -*-
module MusicBrainz
class << self
def query_interval
$stdout.send :puts, "WARNING! MusicBrainz.query_interval is deprecated. Use MusicBrainz::Tools::Proxy.query_interval"
MusicBrainz::Tools::Proxy.query_interval
end
def query_interval=(sec)
$stdout.send :puts, "WARNING! MusicBrainz.query_interval= is deprecated. Use MusicBrainz::Tools::Proxy.query_interval"
MusicBrainz::Tools::Proxy.query_interval = sec
end
def cache_path
$stdout.send :puts, "WARNING! MusicBrainz.cache_path is deprecated. Use MusicBrainz::Tools::Cache.cache_path"
MusicBrainz::Tools::Cache.cache_path
end
def cache_path=(path)
$stdout.send :puts, "WARNING! MusicBrainz.cache_path= is deprecated. Use MusicBrainz::Tools::Cache.cache_path"
MusicBrainz::Tools::Cache.cache_path = path
end
end
end

View File

@ -1,30 +1,34 @@
# -*- encoding: utf-8 -*- require "digest/sha1"
require "fileutils"
require "open-uri" require "faraday"
require "socket"
require "nokogiri" require "nokogiri"
require "cgi"
require "musicbrainz/version"
require "musicbrainz/deprecated"
require "musicbrainz/middleware"
require "musicbrainz/configuration"
require "musicbrainz/client_modules/transparent_proxy"
require "musicbrainz/client_modules/failsafe_proxy"
require "musicbrainz/client_modules/caching_proxy"
require "musicbrainz/client"
require "musicbrainz/models/base_model"
require "musicbrainz/models/artist"
require "musicbrainz/models/release_group"
require "musicbrainz/models/release"
require "musicbrainz/models/track"
require "musicbrainz/bindings/artist"
require "musicbrainz/bindings/artist_search"
require "musicbrainz/bindings/artist_release_groups"
require "musicbrainz/bindings/release_group"
require "musicbrainz/bindings/release_group_releases"
require "musicbrainz/bindings/release"
require "musicbrainz/bindings/release_tracks"
require "musicbrainz/bindings/track"
module MusicBrainz module MusicBrainz
module Tools; end GH_PAGE_URL = "http://git.io/brainz"
module Parsers; end
end end
require "version"
require "deprecated"
require "musicbrainz/base"
require "musicbrainz/artist"
require "musicbrainz/release_group"
require "musicbrainz/release"
require "musicbrainz/track"
require "tools/configuration"
require "tools/cache"
require "tools/proxy"
require "parsers/base"
require "parsers/artist"
require "parsers/release_group"
require "parsers/release"
require "parsers/track"

View File

@ -1,63 +0,0 @@
# -*- encoding: utf-8 -*-
module MusicBrainz
class Artist < Base
field :id, String
field :type, String
field :name, String
field :country, String
field :date_begin, Time
field :date_end, Time
field :urls, Hash
def release_groups
@release_groups ||= nil
if @release_groups.nil? and !id.nil?
@release_groups = self.class.load({
:parser => :artist_release_groups,
:create_models => MusicBrainz::ReleaseGroup
}, {
:resource => :release_group,
:artist => id
})
@release_groups.sort!{ |a, b| a.first_release_date <=> b.first_release_date }
end
@release_groups
end
class << self
def find(mbid)
load({
:parser => :artist_model,
:create_model => MusicBrainz::Artist
}, {
:resource => :artist,
:id => mbid,
:inc => [:url_rels]
})
end
def search(name)
load({
:parser => :artist_search
}, {
:resource => :artist,
:query => "artist:" << CGI.escape(name).gsub(/\!/, '\!'),
:limit => 10
})
end
def discography(mbid)
artist = find(mbid)
artist.release_groups.each { |rg| rg.releases.each { |r| r.tracks } }
artist
end
def find_by_name(name)
matches = search(name)
matches.length.zero? ? nil : find(matches.first[:mbid])
end
end
end
end

View File

@ -1,71 +0,0 @@
# -*- encoding: utf-8 -*-
module MusicBrainz
class Base
class << self
def field(name, type)
@fields ||= {}
@fields[name] = type
define_method(name) {
instance_variable_get("@#{name}")
}
define_method("#{name}=") { |val|
instance_variable_set("@#{name}", validate_type(val, type))
}
end
def load(params, query)
parser = MusicBrainz::Parsers.get_by_name(params[:parser])
xml = MusicBrainz::Tools::Proxy.query(query)
result = parser[:const].send(parser[:method], Nokogiri::XML(xml))
if params[:create_model]
result_model = params[:create_model].new
result.each { |field, value|
result_model.send("#{field}=".to_sym, value)
}
result_model
elsif params[:create_models]
result_models = []
result.each { |item|
result_model = params[:create_models].new
item.each { |field, value|
result_model.send("#{field}=".to_sym, value)
}
result_models << result_model
}
result_models
else
result
end
end
end
def initialize
self.class.instance_variable_get("@fields").each { |name, type|
instance_variable_set("@#{name}", nil)
}
end
def validate_type(val, type)
if type == Integer
val.to_i
elsif type == Float
val.to_f
elsif type == String
val.to_s
elsif type == Time
if val.nil? or val == ""
val = "2030-12-31"
elsif val.split("-").length == 1
val << "-12-31"
elsif val.split("-").length == 2
val << "-31"
end
Time.utc(*val.split("-"))
else
val
end
end
end
end

View File

@ -0,0 +1,23 @@
# encoding: UTF-8
module MusicBrainz
module Bindings
module Artist
def parse(xml)
xml = xml.xpath('./artist') unless xml.xpath('./artist').empty?
{
id: (xml.attribute('id').value rescue nil),
type: (xml.attribute('type').value rescue nil),
name: (xml.xpath('./name').text.gsub(/[`]/, "'") rescue nil),
country: (xml.xpath('./country').text rescue nil),
date_begin: (xml.xpath('./life-span/begin').text rescue nil),
date_end: (xml.xpath('./life-span/end').text rescue nil),
urls: (Hash[xml.xpath('./relation-list[@target-type="url"]/relation').map{ |xml|
[xml.attribute('type').value.downcase.split(" ").join("_").to_sym, xml.xpath('./target').text]
}] rescue {})
}
end
extend self
end
end
end

View File

@ -0,0 +1,13 @@
module MusicBrainz
module Bindings
module ArtistReleaseGroups
def parse(xml)
xml.xpath('./release-group-list/release-group').map do |xml|
MusicBrainz::Bindings::ReleaseGroup.parse(xml)
end
end
extend self
end
end
end

View File

@ -0,0 +1,23 @@
# encoding: UTF-8
module MusicBrainz
module Bindings
module ArtistSearch
def parse(xml)
xml.xpath('./artist-list/artist').map do |xml|
{
id: (xml.attribute('id').value rescue nil),
mbid: (xml.attribute('id').value rescue nil), # Old shit
name: (xml.xpath('./name').text.gsub(/[`]/, "'") rescue nil),
sort_name: (xml.xpath('./sort-name').gsub(/[`]/, "'") rescue nil),
type: (xml.attribute('type').value rescue nil),
score: (xml.attribute('score').value.to_i rescue nil),
desc: (xml.xpath('./disambiguation').value rescue nil),
aliases: (xml.xpath('./alias-list/alias').map{ |xml| xml.text } rescue [])
} rescue nil
end.delete_if{ |item| item.nil? }
end
extend self
end
end
end

View File

@ -0,0 +1,19 @@
module MusicBrainz
module Bindings
module Release
def parse(xml)
xml = xml.xpath('./release') unless xml.xpath('./release').empty?
{
id: (xml.attribute('id').value rescue nil),
title: (xml.xpath('./title').text rescue nil),
status: (xml.xpath('./status').text rescue nil),
country: (xml.xpath('./country').text rescue nil),
format: (xml.xpath('./medium-list/medium/format').text rescue nil),
date: (xml.xpath('./date').text rescue nil)
}
end
extend self
end
end
end

View File

@ -0,0 +1,18 @@
module MusicBrainz
module Bindings
module ReleaseGroup
def parse(xml)
xml = xml.xpath('./release-group') unless xml.xpath('./release-group').empty?
{
id: (xml.attribute('id').value rescue nil),
type: (xml.attribute('type').value rescue nil),
title: (xml.xpath('./title').text rescue nil),
desc: (xml.xpath('./disambiguation').text rescue nil),
first_release_date: (xml.xpath('./first-release-date').text rescue nil)
}
end
extend self
end
end
end

View File

@ -0,0 +1,13 @@
module MusicBrainz
module Bindings
module ReleaseGroupReleases
def parse(xml)
xml.xpath('./release-list/release').map do |xml|
MusicBrainz::Bindings::Release.parse(xml)
end
end
extend self
end
end
end

View File

@ -0,0 +1,13 @@
module MusicBrainz
module Bindings
module ReleaseTracks
def parse(xml)
xml.xpath('./release/medium-list/medium/track-list/track').map do |xml|
MusicBrainz::Bindings::Track.parse(xml)
end
end
extend self
end
end
end

View File

@ -0,0 +1,16 @@
module MusicBrainz
module Bindings
module Track
def parse(xml)
{
position: (xml.xpath('./position').text rescue nil),
recording_id: (xml.xpath('./recording').attribute('id').value rescue nil),
title: (xml.xpath('./recording/title').text rescue nil),
length: (xml.xpath('./recording/length').text rescue nil)
}
end
extend self
end
end
end

67
lib/musicbrainz/client.rb Normal file
View File

@ -0,0 +1,67 @@
module MusicBrainz
module Client
def http
@faraday ||= Faraday.new do |f|
f.request :url_encoded # form-encode POST params
f.adapter Faraday.default_adapter # make requests with Net::HTTP
f.use MusicBrainz::Middleware # run requests with correct headers
end
end
def load(resource, query, params)
response = contents_of(build_url(resource, query))
xml = Nokogiri::XML.parse(response).remove_namespaces!.xpath('/metadata')
data = params[:binding].parse(xml)
if params[:create_model]
result_model = params[:create_model].new
data.each do |field, value|
result_model.send("#{field}=".to_sym, value)
end
result_model
elsif params[:create_models]
result_models = []
data.each do |item|
result_model = params[:create_models].new
item.each do |field, value|
result_model.send("#{field}=".to_sym, value)
end
result_models << result_model
end
if params[:sort]
result_models.sort!{ |a, b| a.send(params[:sort]) <=> b.send(params[:sort]) }
end
result_models
else
data
end
end
def contents_of(url)
if method_defined? :get_contents
get_contents url
else
http.get url
end
end
def build_url(resource, params)
"#{MusicBrainz.config.web_service_url}#{resource.to_s.gsub('_', '-')}" <<
((id = params.delete(:id)) ? "/#{id}?" : "?") <<
params.map do |key, value|
key = key.to_s.gsub('_', '-')
value = if value.is_a?(Array)
value.map{ |el| el.to_s.gsub('_', '-') }.join('+')
else
value.to_s
end
"#{key}=#{value}"
end.join('&')
end
include ClientModules::TransparentProxy
include ClientModules::FailsafeProxy
include ClientModules::CachingProxy
extend self
end
end

View File

@ -0,0 +1,38 @@
module MusicBrainz
module ClientModules
module CachingProxy
def cache_path
MusicBrainz.config.cache_path
end
def clear_cache
FileUtils.rm_r(cache_path) if cache_path && File.exist?(cache_path)
end
def get_contents(url)
return super unless MusicBrainz.config.perform_caching
token = Digest::SHA256.hexdigest(url)
file_path = "#{cache_path}/#{token[0..1]}/#{token[2..3]}/#{token[4..-1]}.xml"
response = nil
if File.exist?(file_path)
response = File.open(file_path).gets
else
response = super
unless response.nil? or response.empty?
FileUtils.mkdir_p file_path.split('/')[0..-2].join('/')
File.open(file_path, 'w') do |f|
f.puts response
f.chmod 0755
f.close
end
end
end
response
end
end
end
end

View File

@ -0,0 +1,23 @@
module MusicBrainz
module ClientModules
module FailsafeProxy
def get_contents(url)
response = nil
MusicBrainz.config.tries_limit.times do
time_passed = Time.now.to_f - @last_query_time ||= 0.0
if time_passed < MusicBrainz.config.query_interval
sleep(MusicBrainz.config.query_interval - time_passed)
end
response = super
@last_query_time = Time.now.to_f
break if response.status == 200
end
response.body rescue nil
end
end
end
end

View File

@ -0,0 +1,9 @@
module MusicBrainz
module ClientModules
module TransparentProxy
def get_contents(url)
http.get url
end
end
end
end

View File

@ -0,0 +1,42 @@
module MusicBrainz
class Configuration
attr_accessor :app_name, :app_version, :contact,
:web_service_url,
:query_interval, :tries_limit,
:cache_path, :perform_caching
DEFAULT_WEB_SERVICE_URL = "http://musicbrainz.org/ws/2/"
DEFAULT_QUERY_INTERVAL = 1.5
DEFAULT_TRIES_LIMIT = 5
DEFAULT_CACHE_PATH = File.join(File.dirname(__FILE__), "..", "tmp", "cache")
DEFAULT_PERFORM_CACHING = false
def initialize
@web_service_url = DEFAULT_WEB_SERVICE_URL
@query_interval = DEFAULT_QUERY_INTERVAL
@tries_limit = DEFAULT_TRIES_LIMIT
@cache_path = DEFAULT_CACHE_PATH
@perform_caching = DEFAULT_PERFORM_CACHING
end
def user_agent_string
%w[ app_name app_version contact ].each do |param|
raise "#{param} must be set" if instance_variable_get("@#{param}").nil?
end
"#{@app_name}/#{@app_version} ( #{@contact} )"
end
end
module Configurable
def configure
raise "Configuration missing" unless block_given?
yield @config ||= MusicBrainz::Configuration.new
end
def config
@config
end
end
extend Configurable
end

View File

@ -0,0 +1,45 @@
module MusicBrainz
def query_interval
MusicBrainz.config.query_interval
end
def query_interval=(value)
MusicBrainz.config.query_interval = value
end
def cache_path
MusicBrainz.config.cache_path
end
def cache_path=(value)
MusicBrainz.config.cache_path = value
end
module Tools
module Proxy
def query_interval
MusicBrainz.config.query_interval
end
def query_interval=(value)
MusicBrainz.config.query_interval = value
end
extend self
end
module Cache
def cache_path
MusicBrainz.config.cache_path
end
def cache_path=(value)
MusicBrainz.config.cache_path = value
end
extend self
end
end
extend self
end

View File

@ -0,0 +1,10 @@
module MusicBrainz
class Middleware < Faraday::Middleware
def call(env)
env[:request_headers]["User-Agent"] = MusicBrainz.config.user_agent_string
env[:request_headers]["Via"] = "gem musicbrainz/#{VERSION} (#{GH_PAGE_URL})"
@app.call(env)
end
end
end

View File

@ -0,0 +1,51 @@
module MusicBrainz
class Artist
include BaseModel
field :id, String
field :type, String
field :name, String
field :country, String
field :date_begin, Time
field :date_end, Time
field :urls, Hash
attr_writer :release_groups
def release_groups
@release_groups ||= Client::load(:release_group, { artist: id }, {
binding: MusicBrainz::Bindings::ArtistReleaseGroups,
create_models: MusicBrainz::ReleaseGroup,
sort: :first_release_date
}) unless @id.nil?
end
class << self
def find(id)
Client.load(:artist, { id: id, inc: [:url_rels] }, {
binding: MusicBrainz::Bindings::Artist,
create_model: MusicBrainz::Artist
})
end
def search(name)
name = CGI.escape(name).gsub(/\!/, '\!')
Client.load(:artist, { query: "artist:#{name}", limit: 10 }, {
binding: MusicBrainz::Bindings::ArtistSearch
})
end
def discography(mbid)
artist = find(mbid)
artist.release_groups.each { |rg| rg.releases.each { |r| r.tracks } }
artist
end
def find_by_name(name)
matches = search(name)
matches.empty? ? nil : find(matches.first[:id])
end
end
end
end

View File

@ -0,0 +1,43 @@
module MusicBrainz
module BaseModel
def self.included(klass)
klass.send(:include, InstanceMethods)
klass.send(:extend, ClassMethods)
end
module ClassMethods
def field(name, type)
self.class_exec do
attr_reader name
define_method("#{name}=") do |val|
instance_variable_set("@#{name}", validate_type(val, type))
end
end
end
end
module InstanceMethods
def validate_type(val, type)
if type == Integer
val.to_i
elsif type == Float
val.to_f
elsif type == String
val.to_s
elsif type == Time
if val.nil? or val == ""
val = "2030-12-31"
elsif val.split("-").length == 1
val << "-12-31"
elsif val.split("-").length == 2
val << "-31"
end
Time.utc(*val.split("-"))
else
val
end
end
end
end
end

View File

@ -0,0 +1,31 @@
module MusicBrainz
class Release
include BaseModel
field :id, String
field :title, String
field :status, String
field :format, String
field :date, Time
field :country, String
attr_writer :tracks
def tracks
@tracks ||= Client::load(:release, { id: id, inc: [:recordings, :media], limit: 100 }, {
binding: MusicBrainz::Bindings::ReleaseTracks,
create_models: MusicBrainz::Track,
sort: :position
}) unless @id.nil?
end
class << self
def find(id)
Client.load(:release, { id: id, inc: [:media] }, {
binding: MusicBrainz::Bindings::Release,
create_model: MusicBrainz::Release
})
end
end
end
end

View File

@ -0,0 +1,31 @@
module MusicBrainz
class ReleaseGroup
include BaseModel
field :id, String
field :type, String
field :title, String
field :desc, String
field :first_release_date, Time
alias_method :disambiguation, :desc
attr_writer :releases
def releases
@releases ||= Client::load(:release, { release_group: id, inc: [:media], limit: 100 }, {
binding: MusicBrainz::Bindings::ReleaseGroupReleases,
create_models: MusicBrainz::Release,
sort: :date
}) unless @id.nil?
end
class << self
def find(id)
Client.load(:release_group, { id: id }, {
binding: MusicBrainz::Bindings::ReleaseGroup,
create_model: MusicBrainz::ReleaseGroup
})
end
end
end
end

View File

@ -0,0 +1,19 @@
module MusicBrainz
class Track
include BaseModel
field :position, Integer
field :recording_id, String
field :title, String
field :length, Integer
class << self
def find(id)
Client.load(:recording, { id: id }, {
binding: MusicBrainz::Bindings::Track,
create_model: MusicBrainz::Track
})
end
end
end
end

View File

@ -1,43 +0,0 @@
# -*- encoding: utf-8 -*-
module MusicBrainz
class Release < Base
field :id, String
field :title, String
field :status, String
field :format, String
field :date, Time
field :country, String
def tracks
@tracks ||= nil
if @tracks.nil? and !id.nil?
@tracks = self.class.load({
:parser => :release_tracks,
:create_models => MusicBrainz::Track
}, {
:resource => :release,
:id => id,
:inc => [:recordings, :media],
:limit => 100
})
@tracks.sort{ |a, b| a.position <=> b.position }
end
@tracks
end
class << self
def find(mbid)
load({
:parser => :release_model,
:create_model => MusicBrainz::Release
}, {
:resource => :release,
:id => mbid,
:inc => [:media]
})
end
end
end
end

View File

@ -1,41 +0,0 @@
# -*- encoding: utf-8 -*-
module MusicBrainz
class ReleaseGroup < Base
field :id, String
field :type, String
field :title, String
field :disambiguation, String
field :first_release_date, Time
def releases
@releases ||= nil
if @releases.nil? and !id.nil?
@releases = self.class.load({
:parser => :release_group_releases,
:create_models => MusicBrainz::Release
}, {
:resource => :release,
:release_group => self.id,
:inc => [:media],
:limit => 100
})
@releases.sort!{ |a, b| a.date <=> b.date }
end
@releases
end
class << self
def find(mbid)
load({
:parser => :release_group_model,
:create_model => MusicBrainz::ReleaseGroup
}, {
:resource => :release_group,
:id => mbid
})
end
end
end
end

View File

@ -1,23 +0,0 @@
# -*- encoding: utf-8 -*-
module MusicBrainz
class Track < Base
field :position, Integer
field :recording_id, String
field :title, String
field :length, Integer
class << self
def find(mbid)
load({
:parser => :track_model,
:create_model => MusicBrainz::Track
}, {
:resource => :recording,
:id => mbid
})
end
end
end
end

View File

@ -0,0 +1,3 @@
module MusicBrainz
VERSION = "0.8"
end

View File

@ -1,47 +0,0 @@
# -*- encoding: utf-8 -*-
module MusicBrainz
module Parsers
class Artist < Base
class << self
def model(xml)
res = {
:id => safe_get_attr(xml, "artist", "id"),
:type => safe_get_attr(xml, "artist", "type"),
:name => safe_get_value(xml, "artist > name").gsub(/[`]/, "'"),
:country => safe_get_value(xml, "artist > country"),
:date_begin => safe_get_value(xml, "artist > life-span > begin"),
:date_end => safe_get_value(xml, "artist > life-span > end"),
:urls => {}
}
xml.css("relation-list[target-type='url'] > relation").each { |rel|
res[:urls][rel.attr("type").downcase.split(" ").join("_").to_sym] = rel.css("target").text
}
res
end
def search(xml)
xml.css("artist-list > artist").map do |a|
{
:name => a.first_element_child.text.gsub(/[`]/, "'"),
:sort_name => safe_get_value(a, "sort-name").gsub(/[`]/, "'"),
:score => (safe_get_attr(a, nil, "score").to_i rescue 0),
:desc => safe_get_value(a, "disambiguation"),
:type => safe_get_attr(a, nil, "type"),
:mbid => safe_get_attr(a, nil, "id"),
:aliases => a.css("alias-list > alias").map { |item| item.text }
}
end
end
def release_groups(xml)
release_groups = []
xml.css("release-group").each do |rg|
release_groups << MusicBrainz::Parsers::ReleaseGroup.model(rg)
end
release_groups
end
end
end
end
end

View File

@ -1,41 +0,0 @@
# -*- encoding: utf-8 -*-
module MusicBrainz
module Parsers
class << self
def get_by_name(name)
case name
when :artist_model
{ :const => MusicBrainz::Parsers::Artist, :method => :model }
when :artist_search
{ :const => MusicBrainz::Parsers::Artist, :method => :search }
when :artist_release_groups
{ :const => MusicBrainz::Parsers::Artist, :method => :release_groups }
when :release_group_model
{ :const => MusicBrainz::Parsers::ReleaseGroup, :method => :model }
when :release_group_releases
{ :const => MusicBrainz::Parsers::ReleaseGroup, :method => :releases }
when :release_model
{ :const => MusicBrainz::Parsers::Release, :method => :model }
when :release_tracks
{ :const => MusicBrainz::Parsers::Release, :method => :tracks }
when :track_model
{ :const => MusicBrainz::Parsers::Track, :method => :model }
end
end
end
class Base
class << self
def safe_get_attr(xml, path, name)
node = path.nil? ? xml : (xml.css(path).first unless xml.css(path).empty?)
node.attr(name) unless node.nil? or node.attr(name).nil?
end
def safe_get_value(xml, path)
xml.css(path).first.text unless xml.css(path).empty?
end
end
end
end
end

View File

@ -1,28 +0,0 @@
# -*- encoding: utf-8 -*-
module MusicBrainz
module Parsers
class Release < Base
class << self
def model(xml)
{
:id => safe_get_attr(xml, nil, "id") || safe_get_attr(xml, "release", "id"),
:title => safe_get_value(xml, "title"),
:status => safe_get_value(xml, "status"),
:country => safe_get_value(xml, "country"),
:format => safe_get_value(xml, "medium-list > medium > format"),
:date => safe_get_value(xml, "date")
}
end
def tracks(xml)
tracks = []
xml.css("medium-list > medium > track-list > track").each do |r|
tracks << MusicBrainz::Parsers::Track.model(r)
end
tracks
end
end
end
end
end

View File

@ -1,27 +0,0 @@
# -*- encoding: utf-8 -*-
module MusicBrainz
module Parsers
class ReleaseGroup < Base
class << self
def model(xml)
{
:id => safe_get_attr(xml, nil, "id") || safe_get_attr(xml, "release-group", "id"),
:type => safe_get_attr(xml, nil, "type") || safe_get_attr(xml, "release-group", "type"),
:title => safe_get_value(xml, "title"),
:disambiguation => safe_get_value(xml, "disambiguation"),
:first_release_date => safe_get_value(xml, "first-release-date")
}
end
def releases(xml)
releases = []
xml.css("release").each do |r|
releases << MusicBrainz::Parsers::Release.model(r)
end
releases
end
end
end
end
end

View File

@ -1,18 +0,0 @@
# -*- encoding: utf-8 -*-
module MusicBrainz
module Parsers
class Track < Base
class << self
def model(xml)
{
:position => safe_get_value(xml, "position"),
:recording_id => safe_get_attr(xml, "recording", "id"),
:title => safe_get_value(xml, "recording > title"),
:length => safe_get_value(xml, "length") || safe_get_value(xml, "recording > length")
}
end
end
end
end
end

View File

@ -1,48 +0,0 @@
# -*- encoding: utf-8 -*-
module MusicBrainz
module Tools
class Cache
class << self
@@cache_path = nil
def cache_path=(path)
@@cache_path = path
end
def cache_path
@@cache_path
end
def clear_cache
FileUtils.rm_r(@@cache_path) if @@cache_path && File.exist?(@@cache_path)
end
def cache_contents(url)
response = nil
url_parts = url.split('/')
file_name = url_parts.pop
directory = url_parts.pop
file_path = @@cache_path ? "#{@@cache_path}/#{directory}/#{file_name}" : nil
if file_path && File.exist?(file_path)
response = File.open(file_path).gets
else
response = yield
unless response.nil? or file_path.nil?
FileUtils.mkdir_p file_path.split('/')[0..-2].join('/')
file = File.new(file_path, 'w')
file.puts(response.gets) # .force_encoding('UTF-8')
file.chmod(0755)
file.close
response.rewind
end
end
response
end
end
end
end
end

View File

@ -1,54 +0,0 @@
# -*- encoding: utf-8 -*-
module MusicBrainz
def self.configure
yield @config ||= MusicBrainz::Tools::Configuration.new
end
def self.config
@config
end
module Tools
class Configuration
def self.add_config name, value=nil
self.instance_variable_set "@#{name}", value
class_eval <<-RUBY
def #{name}=(value)
@#{name} = value
end
def #{name}
@#{name} || self.class.instance_variable_get('@#{name}')
end
RUBY
end
DEFAULT_USER_AGENT = "gem musicbrainz (https://github.com/magnolia-fan/musicbrainz) @ " + Socket.gethostname
add_config :application
add_config :version
add_config :contact
add_config :query_interval, 1.5
add_config :tries_limit, 5
add_config :web_service_url, "http://musicbrainz.org/ws/2/"
def user_agent
return @user_agent if @user_agent
if application
@user_agent = application
@user_agent << "/#{version}" if version
@user_agent << " (#{contact})" if contact
@user_agent << ' via '
end
@user_agent = "#{@user_agent}#{DEFAULT_USER_AGENT}"
end
end
end
end

View File

@ -1,57 +0,0 @@
# -*- encoding: utf-8 -*-
module MusicBrainz
module Tools
class Proxy
class << self
@@last_query_time = 0
def config
MusicBrainz.config
end
def query_interval=(sec)
config.query_interval = sec.to_f
end
def tries_limit=(num)
config.tries_limit = num.to_i
end
def query(params = {})
url = config.web_service_url + params[:resource].to_s.gsub('_', '-') + '/' + (params[:id].to_s || '')
params.delete(:resource)
params.delete(:id) unless params[:id].nil?
url << '?' + params.map{ |k, v|
k = k.to_s.gsub('_', '-')
v = (v.is_a?(Array) ? v.map{ |_| _.to_s.gsub('_', '-') }.join('+') : v.to_s)
k + '=' + v
}.join('&') unless params.empty?
MusicBrainz::Tools::Cache.cache_contents(url) {
get_contents url
}
end
def get_contents(url)
response = nil
config.tries_limit.times {
time_passed = Time.now.to_f - @@last_query_time
sleep(config.query_interval - time_passed) if time_passed < config.query_interval
begin
response = open(url, "User-Agent" => config.user_agent)
@@last_query_time = Time.now.to_f
rescue => e
response = nil if e.io.status[0].to_i == 404
end
break unless response.nil?
}
response
end
end
end
end
end

View File

@ -1,5 +0,0 @@
# -*- encoding: utf-8 -*-
module MusicBrainz
VERSION = "0.7.1"
end

View File

@ -1,6 +1,4 @@
# -*- encoding: utf-8 -*- require File.expand_path('../lib/musicbrainz/version', __FILE__)
require File.expand_path('../lib/version', __FILE__)
Gem::Specification.new do |gem| Gem::Specification.new do |gem|
gem.authors = ["Gregory Eremin"] gem.authors = ["Gregory Eremin"]
@ -8,16 +6,16 @@ Gem::Specification.new do |gem|
gem.summary = %q{MusicBrainz Web Service wrapper with ActiveRecord-style models} gem.summary = %q{MusicBrainz Web Service wrapper with ActiveRecord-style models}
gem.homepage = "http://github.com/magnolia-fan/musicbrainz" gem.homepage = "http://github.com/magnolia-fan/musicbrainz"
gem.files = `git ls-files`.split($\) gem.files = %x{ git ls-files }.split($\)
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
gem.name = "musicbrainz" gem.name = "musicbrainz"
gem.require_paths = ["lib"] gem.require_paths = %w[ lib ]
gem.version = MusicBrainz::VERSION gem.version = MusicBrainz::VERSION
gem.license = "MIT" gem.license = "MIT"
gem.add_dependency("faraday")
gem.add_dependency("nokogiri") gem.add_dependency("nokogiri")
gem.add_development_dependency("rake")
gem.add_development_dependency("awesome_print")
gem.add_development_dependency("rspec") gem.add_development_dependency("rspec")
gem.add_development_dependency("awesome_print")
end end

View File

@ -4,35 +4,47 @@ require "spec_helper"
describe MusicBrainz do describe MusicBrainz do
before(:all) { before(:all) {
@old_cache_path = MusicBrainz::Tools::Cache.cache_path @old_cache_path = MusicBrainz.config.cache_path
@old_query_interval = MusicBrainz.config.query_interval
} }
before(:each) { before(:each) {
$stdout.stub!(:puts) MusicBrainz.config.cache_path = nil
MusicBrainz::Tools::Cache.cache_path = nil MusicBrainz.config.query_interval = nil
} }
after(:all) { after(:all) {
MusicBrainz::Tools::Cache.cache_path = @old_cache_path MusicBrainz.config.cache_path = @old_cache_path
MusicBrainz.config.query_interval = @old_query_interval
} }
it "allows deprecated use of cache_path" do it "allows deprecated use of cache_path" do
MusicBrainz::Tools::Cache.cache_path = "some/path" MusicBrainz.config.cache_path = "test1"
MusicBrainz::cache_path.should == "some/path"
MusicBrainz::Tools::Cache.cache_path.should == "test1"
MusicBrainz.cache_path.should == "test1"
end end
it "allows deprecated use of cache_path=" do it "allows deprecated use of cache_path=" do
MusicBrainz.cache_path = "some/path" MusicBrainz::Tools::Cache.cache_path = "test2"
MusicBrainz::Tools::Cache.cache_path.should == "some/path" MusicBrainz.config.cache_path.should == "test2"
MusicBrainz.cache_path = "test3"
MusicBrainz.config.cache_path.should == "test3"
end end
it "allows deprecated use of query_interval" do it "allows deprecated use of query_interval" do
MusicBrainz::Tools::Proxy.query_interval = 2 MusicBrainz.config.query_interval = 2
MusicBrainz::query_interval.should == 2
MusicBrainz::Tools::Proxy.query_interval.should == 2
MusicBrainz.query_interval.should == 2
end end
it "allows deprecated use of query_interval=" do it "allows deprecated use of query_interval=" do
MusicBrainz.query_interval = 2 MusicBrainz::Tools::Proxy.query_interval = 3
MusicBrainz::Tools::Proxy.query_interval.should == 2 MusicBrainz.config.query_interval.should == 3
MusicBrainz.query_interval = 4
MusicBrainz.config.query_interval.should == 4
end end
end end

View File

@ -24,15 +24,9 @@ describe MusicBrainz::Artist do
matches = MusicBrainz::Artist.search('Chris Martin') matches = MusicBrainz::Artist.search('Chris Martin')
matches[0][:score].should == 100 matches[0][:score].should == 100
matches[0][:mbid].should == "98d1ec5a-dd97-4c0b-9c83-7928aac89bca" matches[0][:id].should == "98d1ec5a-dd97-4c0b-9c83-7928aac89bca"
matches[1][:score].should == 100 matches[1][:score].should == 100
matches[1][:mbid].should == "af2ab893-3212-4226-9e73-73a1660b6952" matches[1][:id].should == "af2ab893-3212-4226-9e73-73a1660b6952"
matches[2][:score].should == 95
matches[2][:mbid].should == "444d1b63-534b-4ea6-89f0-0af6ab2e20c3"
matches[3][:score].should == 95
matches[3][:mbid].should == "b732a912-af95-472c-be52-b14610734c64"
matches[4][:score].should == 95
matches[4][:mbid].should == "90fff570-a4ef-4cd4-ba21-e00c7261b05a"
end end
it "finds name first than alias" do it "finds name first than alias" do

View File

@ -1,12 +1,16 @@
# -*- encoding: utf-8 -*-
require "rubygems" require "rubygems"
require "bundler/setup" require "bundler/setup"
require "ap"
require "musicbrainz" require "musicbrainz"
MusicBrainz::Tools::Cache.cache_path = "tmp/cache" MusicBrainz.configure do |c|
test_email = %x{ git config --global --get user.email }.gsub(/\n/, "")
test_email = "magnolia_fan@me.com" if test_email.empty?
c.app_name = "MusicBrainzGemTestSuite"
c.app_version = MusicBrainz::VERSION
c.contact = test_email
c.perform_caching = true
end
RSpec.configure do |config| RSpec.configure do |config|
# Configuration is not currently necessary # Configuration is not currently necessary

View File

@ -1,59 +1,64 @@
# -*- encoding: utf-8 -*- # -*- encoding: utf-8 -*-
require "ostruct"
require "spec_helper" require "spec_helper"
describe MusicBrainz::Tools::Cache do describe MusicBrainz::Tools::Cache do
before(:all) do before(:all) do
@old_cache_path = MusicBrainz::Tools::Cache.cache_path @old_cache_path = MusicBrainz::Tools::Cache.cache_path
@tmp_cache_path = File.join(File.dirname(__FILE__), '../../tmp/cache/tools') @tmp_cache_path = File.join(File.dirname(__FILE__), "../../tmp/cache/test")
@test_mbid = "69b39eab-6577-46a4-a9f5-817839092033"
@test_cache_file = "#{@tmp_cache_path}/03/48/ec6c2bee685d9a96f95ed46378f624714e7a4650b0d44c1a8eee5bac2480.xml"
end end
after(:all) do after(:all) do
MusicBrainz::Tools::Cache.cache_path = @old_cache_path MusicBrainz.config.cache_path = @old_cache_path
end end
before(:each) do before(:each) do
file_path = File.join(File.dirname(__FILE__), "../fixtures/kasabian.xml") file_path = File.join(File.dirname(__FILE__), "../fixtures/kasabian.xml")
@test_response = ::StringIO.new(File.open(file_path).gets) @test_response = File.open(file_path).read
end end
context "with cache enabled" do context "with cache enabled" do
it "calls get contents only once when requesting the resource twice" do it "calls http only once when requesting the resource twice" do
MusicBrainz::Tools::Cache.cache_path = @tmp_cache_path MusicBrainz.config.cache_path = @tmp_cache_path
mbid = "69b39eab-6577-46a4-a9f5-817839092033" File.exist?(@test_cache_file).should be_false
MusicBrainz::Tools::Proxy.stub(:get_contents).and_return(@test_response) # Stubbing
MusicBrainz::Tools::Proxy.should_receive(:get_contents).once MusicBrainz::Client.http.stub(:get).and_return(OpenStruct.new(status: 200, body: @test_response))
MusicBrainz::Client.http.should_receive(:get).once
File.exist?("#{@tmp_cache_path}/artist/#{mbid}?inc=url-rels").should be_false 2.times do
artist = MusicBrainz::Artist.find(mbid) artist = MusicBrainz::Artist.find(@test_mbid)
artist.should be_a_kind_of(MusicBrainz::Artist) artist.should be_a_kind_of(MusicBrainz::Artist)
File.exist?(@test_cache_file).should be_true
end
File.exist?("#{@tmp_cache_path}/artist/#{mbid}?inc=url-rels").should be_true MusicBrainz::Client.clear_cache
artist = MusicBrainz::Artist.find(mbid)
artist.should be_a_kind_of(MusicBrainz::Artist)
MusicBrainz::Tools::Cache.clear_cache
end end
end end
context "with cache disabled" do context "with cache disabled" do
it "calls get contents twice when requesting the resource twice" do it "calls http twice when requesting the resource twice" do
MusicBrainz::Tools::Cache.cache_path = nil MusicBrainz.config.perform_caching = false
mbid = "69b39eab-6577-46a4-a9f5-817839092033" File.exist?(@test_cache_file).should be_false
MusicBrainz::Tools::Proxy.stub(:get_contents).and_return(@test_response) # Hacking for test performance purposes
MusicBrainz::Tools::Proxy.should_receive(:get_contents).twice MusicBrainz.config.query_interval = 0.0
File.exist?("#{@tmp_cache_path}/artist/#{mbid}?inc=url-rels").should be_false # Stubbing
artist = MusicBrainz::Artist.find(mbid) MusicBrainz::Client.http.stub(:get).and_return(OpenStruct.new(status: 200, body: @test_response))
artist.should be_a_kind_of(MusicBrainz::Artist) MusicBrainz::Client.http.should_receive(:get).twice
File.exist?("#{@tmp_cache_path}/artist/#{mbid}?inc=url-rels").should be_false 2.times do
@test_response.rewind artist = MusicBrainz::Artist.find(@test_mbid)
MusicBrainz.stub(:get_contents).and_return(@test_response) artist.should be_a_kind_of(MusicBrainz::Artist)
artist = MusicBrainz::Artist.find(mbid) File.exist?(@test_cache_file).should be_false
artist.should be_a_kind_of(MusicBrainz::Artist) end
MusicBrainz.config.perform_caching = true
MusicBrainz.config.query_interval = 1.5
end end
end end
end end