1
0
Fork 0

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
rdoc
doc
.yardoc
.bundle
pkg
tmp
Gemfile.lock
.gem

View File

@ -1,4 +1,3 @@
rvm:
- 1.8.7
- 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)
Jens Fahnenbruck (https://github.com/jigfox)
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 "socket"
require "faraday"
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 Tools; end
module Parsers; end
GH_PAGE_URL = "http://git.io/brainz"
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/version', __FILE__)
require File.expand_path('../lib/musicbrainz/version', __FILE__)
Gem::Specification.new do |gem|
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.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.test_files = gem.files.grep(%r{^(test|spec|features)/})
gem.name = "musicbrainz"
gem.require_paths = ["lib"]
gem.require_paths = %w[ lib ]
gem.version = MusicBrainz::VERSION
gem.license = "MIT"
gem.add_dependency("faraday")
gem.add_dependency("nokogiri")
gem.add_development_dependency("rake")
gem.add_development_dependency("awesome_print")
gem.add_development_dependency("rspec")
gem.add_development_dependency("awesome_print")
end

View File

@ -4,35 +4,47 @@ require "spec_helper"
describe MusicBrainz do
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) {
$stdout.stub!(:puts)
MusicBrainz::Tools::Cache.cache_path = nil
MusicBrainz.config.cache_path = nil
MusicBrainz.config.query_interval = nil
}
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
MusicBrainz::Tools::Cache.cache_path = "some/path"
MusicBrainz::cache_path.should == "some/path"
MusicBrainz.config.cache_path = "test1"
MusicBrainz::Tools::Cache.cache_path.should == "test1"
MusicBrainz.cache_path.should == "test1"
end
it "allows deprecated use of cache_path=" do
MusicBrainz.cache_path = "some/path"
MusicBrainz::Tools::Cache.cache_path.should == "some/path"
MusicBrainz::Tools::Cache.cache_path = "test2"
MusicBrainz.config.cache_path.should == "test2"
MusicBrainz.cache_path = "test3"
MusicBrainz.config.cache_path.should == "test3"
end
it "allows deprecated use of query_interval" do
MusicBrainz::Tools::Proxy.query_interval = 2
MusicBrainz::query_interval.should == 2
MusicBrainz.config.query_interval = 2
MusicBrainz::Tools::Proxy.query_interval.should == 2
MusicBrainz.query_interval.should == 2
end
it "allows deprecated use of query_interval=" do
MusicBrainz.query_interval = 2
MusicBrainz::Tools::Proxy.query_interval.should == 2
MusicBrainz::Tools::Proxy.query_interval = 3
MusicBrainz.config.query_interval.should == 3
MusicBrainz.query_interval = 4
MusicBrainz.config.query_interval.should == 4
end
end

View File

@ -24,15 +24,9 @@ describe MusicBrainz::Artist do
matches = MusicBrainz::Artist.search('Chris Martin')
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][:mbid].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"
matches[1][:id].should == "af2ab893-3212-4226-9e73-73a1660b6952"
end
it "finds name first than alias" do

View File

@ -1,12 +1,16 @@
# -*- encoding: utf-8 -*-
require "rubygems"
require "bundler/setup"
require "ap"
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|
# Configuration is not currently necessary

View File

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