Fix random VCR failures in Pixiv tests. Sometimes tests randomly fail because the PHPSESSID they use in their HTTP requests to Pixiv is different than the one that was originally recorded by VCR. This causes VCR to complain that the requests don't match. This is caused by the PHPSESSID being globally cached in Memcache. Depending on the order the tests run in (which is random), one set of tests can use a PHPSESSID that was recorded for a /different/ set of tests. Improve Pixiv URL matching. * Allow URLs that are missing the http:// part. These are sometimes seen in artist entries. * Ignore URLs from random Pixiv domains such as dic.pixiv.net, blog.pixiv.net, etc. These are also sometimes in artist entries. Improve normalize_for_artist_finder! URL matching. * Normalize www.pixiv.net/stacc/username URLs. * Correctly normalize URLs that are missing the illust ID part on the end (i.e. http://i2.pixiv.net/img04/img/syounen_no_uta/). These are common in artist entries. Match URLs strictly when normalizing for artist entries. Only normalize Pixiv URLs that strictly match a known format. Pass any unrecognized URLs through without attempting to normalize them, just to be safe. Normalize URLs when saving artist entries.
460 lines
12 KiB
Ruby
460 lines
12 KiB
Ruby
class Artist < ActiveRecord::Base
|
|
before_create :initialize_creator
|
|
before_validation :normalize_name
|
|
after_save :create_version
|
|
after_save :save_url_string
|
|
after_save :categorize_tag
|
|
validates_uniqueness_of :name
|
|
belongs_to :creator, :class_name => "User"
|
|
has_many :members, :class_name => "Artist", :foreign_key => "group_name", :primary_key => "name"
|
|
has_many :urls, :dependent => :destroy, :class_name => "ArtistUrl"
|
|
has_many :versions, lambda {order("artist_versions.id ASC")}, :class_name => "ArtistVersion"
|
|
has_one :wiki_page, :foreign_key => "title", :primary_key => "name"
|
|
has_one :tag_alias, :foreign_key => "antecedent_name", :primary_key => "name"
|
|
has_one :tag, :foreign_key => "name", :primary_key => "name"
|
|
accepts_nested_attributes_for :wiki_page
|
|
attr_accessible :body, :name, :url_string, :other_names, :other_names_comma, :group_name, :wiki_page_attributes, :notes, :as => [:member, :gold, :builder, :platinum, :contributor, :janitor, :moderator, :default, :admin]
|
|
attr_accessible :is_active, :as => [:builder, :contributor, :janitor, :moderator, :default, :admin]
|
|
attr_accessible :is_banned, :as => :admin
|
|
|
|
module UrlMethods
|
|
extend ActiveSupport::Concern
|
|
|
|
module ClassMethods
|
|
def find_all_by_url(url)
|
|
url = ArtistUrl.normalize(url)
|
|
artists = []
|
|
|
|
# return [] unless Sources::Site.new(url).normalized_for_artist_finder?
|
|
|
|
while artists.empty? && url.size > 10
|
|
u = url.sub(/\/+$/, "") + "/"
|
|
u = u.to_escaped_for_sql_like.gsub(/\*/, '%') + '%'
|
|
artists += Artist.joins(:urls).where(["artists.is_active = TRUE AND artist_urls.normalized_url LIKE ? ESCAPE E'\\\\'", u]).limit(10).order("artists.name").all
|
|
url = File.dirname(url) + "/"
|
|
break if url =~ /pixiv\.net\/(?:img\/)?$/i
|
|
break if url =~ /lohas\.nicoseiga\.jp\/priv\/$/i
|
|
break if url =~ /(?:data|media)\.tumblr\.com\/[a-z0-9]+\/$/i
|
|
break if url =~ /deviantart\.net\//i
|
|
end
|
|
|
|
artists.inject({}) {|h, x| h[x.name] = x; h}.values.slice(0, 20)
|
|
end
|
|
end
|
|
|
|
def save_url_string
|
|
if @url_string
|
|
urls.clear
|
|
|
|
@url_string.scan(/\S+/).each do |url|
|
|
urls.create(:url => url)
|
|
end
|
|
end
|
|
end
|
|
|
|
def url_string=(string)
|
|
@url_string = string
|
|
end
|
|
|
|
def url_string
|
|
@url_string || urls.map {|x| x.url}.join("\n")
|
|
end
|
|
|
|
def url_string_changed?
|
|
url_string.scan(/\S+/) != urls.map(&:url)
|
|
end
|
|
end
|
|
|
|
module NameMethods
|
|
extend ActiveSupport::Concern
|
|
|
|
module ClassMethods
|
|
def normalize_name(name)
|
|
name.to_s.mb_chars.downcase.strip.gsub(/ /, '_').to_s
|
|
end
|
|
end
|
|
|
|
def normalize_name
|
|
self.name = Artist.normalize_name(name)
|
|
end
|
|
|
|
def pretty_name
|
|
name.tr("_", " ")
|
|
end
|
|
|
|
def other_names_array
|
|
other_names.try(:split, /\s/)
|
|
end
|
|
|
|
def other_names_comma
|
|
other_names_array.try(:join, ", ")
|
|
end
|
|
|
|
def other_names_comma=(string)
|
|
self.other_names = string.split(/,/).map {|x| Artist.normalize_name(x)}.join(" ")
|
|
end
|
|
end
|
|
|
|
module GroupMethods
|
|
def member_names
|
|
members.map(&:name).join(", ")
|
|
end
|
|
end
|
|
|
|
module VersionMethods
|
|
def create_version(force=false)
|
|
if name_changed? || url_string_changed? || is_active_changed? || is_banned_changed? || other_names_changed? || group_name_changed? || notes_changed? || force
|
|
if merge_version?
|
|
merge_version
|
|
else
|
|
create_new_version
|
|
end
|
|
end
|
|
end
|
|
|
|
def create_new_version
|
|
ArtistVersion.create(
|
|
:artist_id => id,
|
|
:name => name,
|
|
:updater_id => CurrentUser.user.id,
|
|
:updater_ip_addr => CurrentUser.ip_addr,
|
|
:url_string => url_string,
|
|
:is_active => is_active,
|
|
:is_banned => is_banned,
|
|
:other_names => other_names,
|
|
:group_name => group_name
|
|
)
|
|
end
|
|
|
|
def merge_version
|
|
prev = versions.last
|
|
prev.update_attributes(
|
|
:name => name,
|
|
:url_string => url_string,
|
|
:is_active => is_active,
|
|
:is_banned => is_banned,
|
|
:other_names => other_names,
|
|
:group_name => group_name
|
|
)
|
|
end
|
|
|
|
def merge_version?
|
|
prev = versions.last
|
|
prev && prev.updater_id == CurrentUser.user.id && prev.updated_at > 1.hour.ago
|
|
end
|
|
|
|
def revert_to!(version)
|
|
self.name = version.name
|
|
self.url_string = version.url_string
|
|
self.is_active = version.is_active
|
|
self.other_names = version.other_names
|
|
self.group_name = version.group_name
|
|
save
|
|
end
|
|
end
|
|
|
|
module FactoryMethods
|
|
def new_with_defaults(params)
|
|
Artist.new.tap do |artist|
|
|
if params[:name]
|
|
artist.name = params[:name]
|
|
post = CurrentUser.without_safe_mode do
|
|
Post.tag_match("source:http #{artist.name}").first
|
|
end
|
|
unless post.nil? || post.source.blank?
|
|
artist.url_string = post.source
|
|
end
|
|
end
|
|
|
|
if params[:other_names]
|
|
artist.other_names = params[:other_names]
|
|
end
|
|
|
|
if params[:urls]
|
|
artist.url_string = params[:urls]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
module NoteMethods
|
|
def notes
|
|
if wiki_page
|
|
wiki_page.body
|
|
else
|
|
nil
|
|
end
|
|
end
|
|
|
|
def notes=(msg)
|
|
if name_changed? && name_was.present?
|
|
old_wiki_page = WikiPage.titled(name_was).first
|
|
end
|
|
|
|
if wiki_page
|
|
if name_changed? && name_was.present?
|
|
wiki_page.body = wiki_page.body + "\n\n" + msg
|
|
else
|
|
wiki_page.body = msg
|
|
end
|
|
if wiki_page.body_changed?
|
|
wiki_page.save
|
|
@notes_changed = true
|
|
end
|
|
elsif old_wiki_page
|
|
old_wiki_page.title = name
|
|
old_wiki_page.body = msg
|
|
if old_wiki_page.body_changed? || old_wiki_page.title_changed?
|
|
old_wiki_page.save
|
|
@notes_changed = true
|
|
end
|
|
elsif msg.present?
|
|
self.wiki_page = WikiPage.new(:title => name, :body => msg)
|
|
@notes_changed = true
|
|
end
|
|
end
|
|
|
|
def notes_changed?
|
|
!!@notes_changed
|
|
end
|
|
end
|
|
|
|
module TagMethods
|
|
def has_tag_alias?
|
|
TagAlias.active.exists?(["antecedent_name = ?", name])
|
|
end
|
|
|
|
def tag_alias_name
|
|
TagAlias.active.find_by_antecedent_name(name).consequent_name
|
|
end
|
|
|
|
def category_name
|
|
Tag.category_for(name)
|
|
end
|
|
|
|
def categorize_tag
|
|
if new_record? || name_changed?
|
|
Tag.find_or_create_by_name("artist:#{name}")
|
|
end
|
|
end
|
|
end
|
|
|
|
module BanMethods
|
|
def unban!
|
|
Post.transaction do
|
|
CurrentUser.without_safe_mode do
|
|
ti = TagImplication.where(:antecedent_name => name, :consequent_name => "banned_artist").first
|
|
ti.destroy if ti
|
|
|
|
begin
|
|
Post.tag_match(name).each do |post|
|
|
post.unban!
|
|
fixed_tags = post.tag_string.sub(/(?:\A| )banned_artist(?:\Z| )/, " ").strip
|
|
post.update_attributes(:tag_string => fixed_tags)
|
|
end
|
|
rescue Post::SearchError
|
|
# swallow
|
|
end
|
|
|
|
update_column(:is_banned, false)
|
|
end
|
|
end
|
|
end
|
|
|
|
def ban!
|
|
Post.transaction do
|
|
CurrentUser.without_safe_mode do
|
|
begin
|
|
Post.tag_match(name).each do |post|
|
|
post.ban!
|
|
end
|
|
rescue Post::SearchError
|
|
# swallow
|
|
end
|
|
|
|
# potential race condition but unlikely
|
|
unless TagImplication.where(:antecedent_name => name, :consequent_name => "banned_artist").exists?
|
|
tag_implication = TagImplication.create(:antecedent_name => name, :consequent_name => "banned_artist")
|
|
tag_implication.delay(:queue => "default").process!
|
|
end
|
|
|
|
update_column(:is_banned, true)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
module SearchMethods
|
|
def active
|
|
where("is_active = true")
|
|
end
|
|
|
|
def banned
|
|
where("is_banned = true")
|
|
end
|
|
|
|
def url_matches(string)
|
|
matches = find_all_by_url(string).map(&:id)
|
|
|
|
if matches.any?
|
|
where("id in (?)", matches)
|
|
else
|
|
where("false")
|
|
end
|
|
end
|
|
|
|
def other_names_match(string)
|
|
if string =~ /\*/ && CurrentUser.user.is_builder?
|
|
where("other_names ILIKE ? ESCAPE E'\\\\'", string.to_escaped_for_sql_like)
|
|
else
|
|
where("other_names_index @@ to_tsquery('danbooru', E?)", Artist.normalize_name(string).to_escaped_for_tsquery)
|
|
end
|
|
end
|
|
|
|
def group_name_matches(name)
|
|
stripped_name = normalize_name(name).to_escaped_for_sql_like
|
|
where("group_name LIKE ? ESCAPE E'\\\\'", stripped_name)
|
|
end
|
|
|
|
def name_matches(name)
|
|
stripped_name = normalize_name(name).to_escaped_for_sql_like
|
|
where("name LIKE ? ESCAPE E'\\\\'", stripped_name)
|
|
end
|
|
|
|
def named(name)
|
|
where("name = ?", normalize_name(name))
|
|
end
|
|
|
|
def any_name_matches(name)
|
|
stripped_name = normalize_name(name).to_escaped_for_sql_like
|
|
if name =~ /\*/ && CurrentUser.user.is_builder?
|
|
where("(name LIKE ? ESCAPE E'\\\\' OR other_names LIKE ? ESCAPE E'\\\\')", stripped_name, stripped_name)
|
|
else
|
|
name_for_tsquery = normalize_name(name).to_escaped_for_tsquery
|
|
where("(name LIKE ? ESCAPE E'\\\\' OR other_names_index @@ to_tsquery('danbooru', E?))", stripped_name, name_for_tsquery)
|
|
end
|
|
end
|
|
|
|
def search(params)
|
|
q = where("true")
|
|
params = {} if params.blank?
|
|
|
|
case params[:name]
|
|
when /^http/
|
|
q = q.url_matches(params[:name])
|
|
|
|
when /name:(.+)/
|
|
q = q.name_matches($1)
|
|
|
|
when /other:(.+)/
|
|
q = q.other_names_match($1)
|
|
|
|
when /group:(.+)/
|
|
q = q.group_name_matches($1)
|
|
|
|
when /status:banned/
|
|
q = q.banned
|
|
|
|
when /status:active/
|
|
q = q.where("is_banned = false and is_active = true")
|
|
|
|
when /./
|
|
q = q.any_name_matches(params[:name])
|
|
end
|
|
|
|
params[:order] ||= params.delete(:sort)
|
|
case params[:order]
|
|
when "name"
|
|
q = q.reorder("name")
|
|
when "updated_at"
|
|
q = q.reorder("updated_at desc")
|
|
else
|
|
q = q.reorder("id desc")
|
|
end
|
|
|
|
if params[:is_active] == "true"
|
|
q = q.active
|
|
elsif params[:is_active] == "false"
|
|
q = q.where("is_active = false")
|
|
end
|
|
|
|
if params[:is_banned] == "true"
|
|
q = q.banned
|
|
elsif params[:is_banned] == "false"
|
|
q = q.where("is_banned = false")
|
|
end
|
|
|
|
if params[:id].present?
|
|
q = q.where("id = ?", params[:id])
|
|
end
|
|
|
|
if params[:creator_name].present?
|
|
q = q.where("creator_id = (select _.id from users _ where lower(_.name) = ?)", params[:creator_name].tr(" ", "_").mb_chars.downcase)
|
|
end
|
|
|
|
if params[:creator_id].present?
|
|
q = q.where("creator_id = ?", params[:creator_id].to_i)
|
|
end
|
|
|
|
if params[:empty_only] == "true"
|
|
q = q.joins(:tag).where("tags.post_count = 0")
|
|
end
|
|
|
|
q
|
|
end
|
|
end
|
|
|
|
module ApiMethods
|
|
def hidden_attributes
|
|
super + [:other_names_index]
|
|
end
|
|
|
|
def legacy_api_hash
|
|
return {
|
|
:id => id,
|
|
:name => name,
|
|
:other_names => other_names,
|
|
:group_name => group_name,
|
|
:urls => artist_urls.map {|x| x.url},
|
|
:is_active => is_active?,
|
|
:updater_id => 0
|
|
}
|
|
end
|
|
end
|
|
|
|
include UrlMethods
|
|
include NameMethods
|
|
include GroupMethods
|
|
include VersionMethods
|
|
extend FactoryMethods
|
|
include NoteMethods
|
|
include TagMethods
|
|
include BanMethods
|
|
extend SearchMethods
|
|
include ApiMethods
|
|
|
|
def status
|
|
if is_banned? && is_active?
|
|
"Banned"
|
|
elsif is_banned?
|
|
"Banned Deleted"
|
|
elsif is_active?
|
|
"Active"
|
|
else
|
|
"Deleted"
|
|
end
|
|
end
|
|
|
|
def initialize_creator
|
|
self.creator_id = CurrentUser.user.id
|
|
end
|
|
|
|
def deletable_by?(user)
|
|
user.is_builder?
|
|
end
|
|
|
|
def visible?
|
|
!is_banned? || CurrentUser.user.is_janitor?
|
|
end
|
|
end
|