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
-63
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
-71
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
+23
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
@@ -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
+23
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
+19
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
+18
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
@@ -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
@@ -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
+16
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
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
@@ -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
@@ -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
@@ -0,0 +1,9 @@
module MusicBrainz
module ClientModules
module TransparentProxy
def get_contents(url)
http.get url
end
end
end
end
+42
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
+45
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
+10
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
+51
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
+43
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
+31
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
+31
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
+19
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
-43
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
-41
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
-23
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
+3
View File
@@ -0,0 +1,3 @@
module MusicBrainz
VERSION = "0.8"
end